src/lib/str.c
author Tero Marttila <terom@fixme.fi>
Thu, 28 May 2009 00:35:02 +0300
branchnew-lib-errors
changeset 218 5229a5d098b2
parent 216 a10ba529ae39
permissions -rw-r--r--
some of spbot and lib compiles
#include "str.h"

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#include <assert.h>

const struct error_list str_errors = ERROR_LIST("str",
    ERROR_TYPE(     ERR_STR_FMT_TAG,            "invalid parameter tag syntax"      ),
    ERROR_TYPE(     ERR_STR_FMT_NAME_LEN,       "invalid parameter name length"     ),
    ERROR_TYPE(     ERR_STR_FMT_NAME,           "invalid/unknown parameter name"    ),
    ERROR_TYPE(     ERR_STR_FMT_FLAGS_LEN,      "invalid paramter flags length"     ),
    ERROR_TYPE(     ERR_STR_FMT_FLAG,           "invalid paramter flag"             ),
    ERROR_TYPE(     ERR_STR_FMT_BUF_LEN,        "output buffer ran out"             )
);

size_t str_append_num (char *buf, size_t buf_size, const char *str, ssize_t len)
{
    size_t str_len;
    
    // copy the bytes
    // 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 && len != 0; str++, len--)
        str_len++;
    
    if (buf_size)
        // NUL-terminate
        *buf = '\0';

    // 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)
{
    if (buf_size > 1)
        *buf++ = c;

    if (buf_size > 0)
        *buf = '\0';
    
    return 1;
}

size_t str_append_fmt_va (char *buf, size_t buf_size, const char *fmt, va_list vargs)
{
    int ret;

    // do the formatting
    ret = vsnprintf(buf, buf_size, fmt, vargs);

    // XXX: not all vsnprintf implementations do this
    assert(ret >= 0);

    // ok
    return ret;
}

size_t str_append_fmt (char *buf, size_t buf_size, const char *fmt, ...)
{
    va_list vargs;
    int ret;

    va_start(vargs, fmt);
    ret = str_append_fmt_va(buf, buf_size, fmt, vargs);
    va_end(vargs);

    return ret;
}

static const struct str_quote_item {
    /** The char value to quote */
    char c;
    
    /** The quoted value */
    const char *out;
} _quote_table[] = {
    { '\'', "\\'"   },
    { '\\', "\\\\"  },
    { '\0', "\\0"   },
    { '\r', "\\r"   },
    { '\n', "\\n"   },
    { 0,    NULL    }
};

size_t str_quote_char (char *buf, size_t buf_size, char c)
{
    const struct str_quote_item *quote_item = _quote_table;

    // special quote?
    for (quote_item = _quote_table; quote_item->c || quote_item->out; quote_item++) {
        if (quote_item->c == c)
            return str_append(buf, buf_size, quote_item->out);
    }
    
    // output directly?
    if (isprint(c))
        return str_append_char(buf, buf_size, c); 
    
    else 
        return str_append_fmt(buf, buf_size, "\\x%02x", c);
}

size_t str_advance (size_t *data_size, size_t *buf_size, size_t len)
{
    if (data_size)
        *data_size += len;

    if (len > *buf_size)
        *buf_size = 0;

    else
        *buf_size -= len;

    return len;
}

/**
 * Number of bytes reserved by str_quote for the trailing overflow stuff
 */
#define STR_QUOTE_RESERVED 5

size_t str_quote (char *buf, size_t buf_size, const char *str, ssize_t str_len)
{
    size_t data_size = 0;

    // NULL?
    if (str == NULL) {
        return str_append(buf, buf_size, "NULL");

    } else {
        // calc length?
        if (str_len < 0)
            str_len = strlen(str);

        // quote
        buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\''));
    
        // dump each char
        while (str_len--) { 
            if (buf_size > STR_QUOTE_RESERVED) {
                size_t char_size;

                // output and count the chars
                char_size = str_quote_char(buf, buf_size, *str++);
                
                // if there's still enough room left, commit this and continue
                if (buf_size > char_size && buf_size - char_size >= STR_QUOTE_RESERVED)
                    buf += str_advance(&data_size, &buf_size, char_size);
                
                else
                    // keep the buf pointer intact, we'll overwrite it now
                    data_size += char_size;

            } else {
                // continue counting the chars, but don't output anything anymore
                data_size += str_quote_char(NULL, 0, *str++);
            }
        }

        // end quote
        buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\''));
    }        

    // overflow -> mark
    if (buf_size < STR_QUOTE_RESERVED)
        buf += str_advance(NULL, &buf_size, str_append(buf, buf_size, "..."));

    // the total outputted chars
    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, error_t *err)
{
    const char *value;
    ssize_t value_len = -1;
    char flag;
    bool use_quote = false;
    
    // look it up
    if (func(name, &value, &value_len, arg, err))
        return PUSH_ERROR(err, &str_errors, ERR_STR_FMT_VALUE);
    
    // not found?
    if (!value)
        return SET_ERROR(err, &str_errors, ERR_STR_FMT_NAME);
    
    // parse flags
    while ((flag = *flags++)) {
        switch (flag) {
            case 'r':
                // quote
                use_quote = true;

                break;

            default:
                // unknown flag
                return SET_ERROR(err, &str_errors, 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, error_t *err)
{
    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;
    
    // iterate over the format string
    do {
        // check buffer state
        if (!buf_size)
            return SET_ERROR(err, &str_errors, ERR_STR_FMT_BUF_LEN);
        
        // inspect this char
        switch (*format) {
            case '{':
                // syntax
                if (in_param)
                    return SET_ERROR(err, &str_errors, 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 SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG);
                
                // reset state
                in_param = false;
                in_param_flags = false;

                // output token
                if (str_format_param(&buf, &buf_size, name_buf, flags_buf, func, arg, err))
                    return error_code(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 SET_ERROR(err, &str_errors, 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 SET_ERROR(err, &str_errors, 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 SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG);
    
    // ok
    return SUCCESS;
}