implement str_format with tests
authorTero Marttila <terom@fixme.fi>
Sat, 11 Apr 2009 06:03:08 +0300
changeset 129 361740b82fe5
parent 128 6c5a5fdfd1d5
child 130 ffefb6d85ea6
implement str_format with tests
src/error.h
src/str.c
src/str.h
src/test.c
--- a/src/error.h	Sat Apr 11 04:41:44 2009 +0300
+++ b/src/error.h	Sat Apr 11 06:03:08 2009 +0300
@@ -113,6 +113,9 @@
     ERR_PCRE_COMPILE,       ///< pcre_compile: <error_msg>
     ERR_PCRE_EXEC,          ///< pcre_exec: <error code>
 
+    /** str errors */
+    _ERR_STR        = 0x000f00,
+
     /** General errors */
     _ERR_GENERAL    = 0xffff00,
     ERR_CMD_OPT,
--- a/src/str.c	Sat Apr 11 04:41:44 2009 +0300
+++ b/src/str.c	Sat Apr 11 06:03:08 2009 +0300
@@ -4,18 +4,21 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
+#include <stdbool.h>
+
 #include <assert.h>
 
-size_t str_append (char *buf, size_t buf_size, const char *str)
+size_t str_append_num (char *buf, size_t buf_size, const char *str, ssize_t len)
 {
     size_t str_len;
     
     // copy the bytes
-    for (str_len = 0; *str && buf_size > 1; str_len++, buf_size--)
+    // len will either be >0 or <0
+    for (str_len = 0; *str && buf_size > 1 && len != 0; str_len++, buf_size--, len--)
         *buf++ = *str++;
     
     // count the rest
-    for (; *str; str++)
+    for (; *str && len != 0; str++, len--)
         str_len++;
     
     if (buf_size)
@@ -24,6 +27,12 @@
 
     // ok
     return str_len;
+
+}
+
+size_t str_append (char *buf, size_t buf_size, const char *str)
+{
+    return str_append_num(buf, buf_size, str, -1);
 }
 
 size_t str_append_char (char *buf, size_t buf_size, char c)
@@ -158,3 +167,140 @@
     return data_size;
 }
 
+/**
+ * Output the data for a single parameter
+ */
+static err_t str_format_param (char **buf, size_t *buf_size, const char *name, const char *flags, str_format_cb func, void *arg)
+{
+    const char *value;
+    ssize_t value_len = -1;
+    char flag;
+    bool use_quote = false;
+    err_t err;
+    
+    // look it up
+    if ((err = func(name, &value, &value_len, arg)))
+        return err;
+    
+    // not found?
+    if (!value)
+        return ERR_STR_FMT_NAME;
+    
+    // parse flags
+    while ((flag = *flags++)) {
+        switch (flag) {
+            case 'r':
+                // quote
+                use_quote = true;
+
+                break;
+
+            default:
+                // unknown flag
+                return ERR_STR_FMT_FLAG;
+    
+        }
+    }
+
+    // output value
+    if (use_quote)
+        *buf += str_advance(NULL, buf_size, str_quote(*buf, *buf_size, value, value_len));
+
+    else
+        *buf += str_advance(NULL, buf_size, str_append_num(*buf, *buf_size, value, value_len));
+
+    // ok
+    return SUCCESS;
+}
+
+err_t str_format (char *buf, size_t buf_size, const char *format, str_format_cb func, void *arg)
+{
+    char name_buf[STR_FORMAT_PARAM_MAX + 1], *name_ptr;
+    size_t name_size;
+    char flags_buf[STR_FORMAT_FLAGS_MAX + 1], *flags_ptr;
+    size_t flags_size;
+    bool in_param = false, in_param_flags = false;
+    err_t err;
+    
+    // iterate over the format string
+    do {
+        // check buffer state
+        if (!buf_size)
+            return ERR_STR_FMT_BUF_LEN;
+        
+        // inspect this char
+        switch (*format) {
+            case '{':
+                // syntax
+                if (in_param)
+                    return ERR_STR_FMT_TAG;
+                
+                // init state
+                in_param = true;
+                name_ptr = name_buf;
+                name_size = sizeof(name_buf);
+                flags_ptr = flags_buf;
+                flags_size = sizeof(flags_buf);
+
+                *name_ptr = *flags_ptr = '\0';
+                
+                break;
+            
+
+            case '}':
+                // syntax
+                if (!in_param)
+                    return ERR_STR_FMT_TAG;
+                
+                // reset state
+                in_param = false;
+                in_param_flags = false;
+
+                // output token
+                if ((err = str_format_param(&buf, &buf_size, name_buf, flags_buf, func, arg)))
+                    return err;
+
+                break;
+
+            case ':':
+                if (in_param) {
+                    // set state
+                    in_param_flags = true;
+
+                    break;
+                }
+
+                /* Fallthrough */
+
+            default:
+                if (in_param && in_param_flags ) {
+                    // add to param flags
+                    flags_ptr += str_advance(NULL, &flags_size, str_append_char(flags_ptr, flags_size, *format));
+
+                    if (!flags_size)
+                        return ERR_STR_FMT_FLAGS_LEN;
+               
+
+                } else if (in_param) {
+                    // add to param name
+                    name_ptr += str_advance(NULL, &name_size, str_append_char(name_ptr, name_size, *format));
+
+                    if (!name_size)
+                        return ERR_STR_FMT_NAME_LEN;
+                } else {
+                    // add to output
+                    buf += str_advance(NULL, &buf_size, str_append_char(buf, buf_size, *format));
+
+                }
+
+                break;
+        }
+    } while (*format++);
+
+    // syntax
+    if (in_param)
+        return ERR_STR_FMT_TAG;
+    
+    // ok
+    return SUCCESS;
+}
--- a/src/str.h	Sat Apr 11 04:41:44 2009 +0300
+++ b/src/str.h	Sat Apr 11 06:03:08 2009 +0300
@@ -7,12 +7,34 @@
  * Miscellaneous string utility functions
  */
 #include <sys/types.h>
+#include "error.h"
 
 /**
- * Writes the given string into the given buffer, writing at most buf_size bytes, including the terminating NUL.
+ * Error codes
+ */
+enum str_error_code {
+    _ERR_STR_BEGIN = _ERR_STR,
+    ERR_STR_FMT_TAG,                ///< invalid parameter tag syntax
+    ERR_STR_FMT_NAME_LEN,           ///< invalid parameter name length
+    ERR_STR_FMT_NAME,               ///< invalid/unknown parameter name
+    ERR_STR_FMT_FLAGS_LEN,          ///< invalid paramter flags length
+    ERR_STR_FMT_FLAG,               ///< invalid paramter flag
+    ERR_STR_FMT_BUF_LEN,            ///< output buffer ran out
+};
+
+/**
+ * Writes the given string into the given buffer, reading at most len bytes (or up to NUL if -1), and writing at most
+ * buf_size bytes, including the terminating NUL.
  *
  * Returns the number of input bytes that would have been written, not including the terminating NUL.
  */
+size_t str_append_num (char *buf, size_t buf_size, const char *str, ssize_t len);
+
+/**
+ * Like str_append_num with len = -1.
+ *
+ * @see str_append_num()
+ */
 size_t str_append (char *buf, size_t buf_size, const char *str);
 
 /**
@@ -60,6 +82,32 @@
  * @param str_len number of bytes of input to process, or -1 to use strlen()
  * @return the total number of bytes that would have been written out, may be more than buf_size
  */
-size_t str_quote (char *buf_ptr, size_t buf_size, const char *str, ssize_t str_len);
+size_t str_quote (char *buf, size_t buf_size, const char *str, ssize_t str_len);
+
+/**
+ * Callback function used by str_format to look up a value for a parameter.
+ *
+ * @param name the name of the paramter in the format string
+ * @param value returned pointer to param value
+ * @param value_len returned param value length, or -1 for strlen
+ * @param arg the context arg given to str_format
+ * @return the parameter value, or NULL to error out
+ */
+typedef err_t (*str_format_cb) (const char *name, const char **value, ssize_t *value_len, void *arg);
+
+/**
+ * Maximum length of a parameter name
+ */
+#define STR_FORMAT_PARAM_MAX 32
+
+/**
+ * Maximum length of a parameter flags section
+ */
+#define STR_FORMAT_FLAGS_MAX 8
+
+/**
+ * Format an output string based on the given template, filling in parameter values using a callback function.
+ */
+err_t str_format (char *buf, size_t buf_size, const char *format, str_format_cb func, void *arg);
 
 #endif
--- a/src/test.c	Sat Apr 11 04:41:44 2009 +0300
+++ b/src/test.c	Sat Apr 11 06:03:08 2009 +0300
@@ -250,6 +250,44 @@
     assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
 }
 
+struct str_format_ctx {
+    const char *name;
+
+    const char *value;
+};
+
+err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
+{
+    struct str_format_ctx *ctx = arg;
+
+    assert_strcmp(name, ctx->name);
+
+    *value = ctx->value;
+    *value_len = -1;
+
+    return SUCCESS;
+}
+
+void assert_str_format (const char *format, const char *name, const char *value, const char *out)
+{
+    struct str_format_ctx ctx = { name, value };
+    char buf[512];
+
+    assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx));
+    
+    log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out);
+
+    assert_strcmp(buf, out);
+}
+
+void test_str_format (void)
+{
+    log_info("test str_format()");
+
+    assert_str_format("foo", NULL, NULL, "foo");
+    assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux");
+}
+
 void test_dump_str (void)
 {
     log_info("dumping example strings on stdout:");
@@ -1166,6 +1204,7 @@
 
 static struct test _tests[] = {
     DEF_TEST(           str_quote                   ),
+    DEF_TEST(           str_format                  ),
     DEF_TEST(           dump_str                    ),
     DEF_TEST(           sock_test                   ),
     DEF_TEST(           line_proto                  ),