changeset 147:f636c55c7cc4

Implement C printf-style format specifiers for -F option @fields@.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 25 Oct 2017 23:20:09 +0300
parents 263cd25749a1
children 01cde57da297
files sidinfo.c
diffstat 1 files changed, 260 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/sidinfo.c	Tue Jun 13 01:42:56 2017 +0300
+++ b/sidinfo.c	Wed Oct 25 23:20:09 2017 +0300
@@ -19,6 +19,12 @@
 
 enum
 {
+    OFMT_FORMAT    = 0x0002,
+};
+
+
+enum
+{
     OTYPE_OTHER    = 0,
     OTYPE_STR      = 1,
     OTYPE_INT      = 2,
@@ -31,6 +37,7 @@
     char *str;
     char chr;
     int flags;
+    char *fmt;
 } PSFStackItem;
 
 
@@ -151,6 +158,9 @@
         "are expanded to their value. Also, escape sequences \\r, \\n and \\t\n"
         "can be used: -F \"hash=@hash@\\ncopy=@copyright@\\n\"\n"
         "\n"
+        "The -F fields can be further formatted via printf-style specifiers:\n"
+        "-F \"@copyright:'%%-30s'@\"\n"
+        "\n"
         "When specifying HVSC path, it is preferable to use -H/--hvsc option,\n"
         "as STIL.txt and Songlengths.txt will be automatically used from there.\n"
         , th_prog_name);
@@ -266,6 +276,220 @@
 }
 
 
+int siItemFormatStrPutInt(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
+    const int value, const int f_radix, int f_flags, int f_width, int f_prec,
+    const BOOL f_unsig, char *(f_alt)(const char *buf, const size_t blen, const int vret, const int flags))
+{
+    char buf[64];
+    int f_len = 0, vret;
+    BOOL f_neg = FALSE;
+
+    vret = th_vprintf_buf_int(buf, sizeof(buf), &f_len, value,
+         f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg);
+
+    if (vret == EOF)
+        return 0;
+
+    return th_vprintf_put_int_format(ctx, vputch, buf, f_flags, f_width, f_prec, f_len, vret, f_neg, f_unsig, f_alt);
+}
+
+
+int siItemFormatStrPrintDo(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *fmt,
+    const PSFOption *opt, const char *d_str, const int d_int)
+{
+    int ret = 0;
+
+    while (*fmt)
+    {
+        if (*fmt != '%')
+        {
+            if ((ret = vputch(ctx, *fmt)) == EOF)
+                goto out;
+        }
+        else
+        {
+            int f_width = -1, f_prec = -1, f_flags = 0;
+            BOOL end = FALSE;
+
+            fmt++;
+
+            // Check for flags
+            while (!end)
+            {
+                switch (*fmt)
+                {
+                    case '#':
+                        f_flags |= TH_PF_ALT;
+                        break;
+
+                    case '+':
+                        f_flags |= TH_PF_SIGN;
+                        break;
+
+                    case '0':
+                        f_flags |= TH_PF_ZERO;
+                        break;
+
+                    case '-':
+                        f_flags |= TH_PF_LEFT;
+                        break;
+
+                    case ' ':
+                        f_flags |= TH_PF_SPACE;
+                        break;
+
+                    case '\'':
+                        f_flags |= TH_PF_GROUP;
+                        break;
+
+                    default:
+                        end = TRUE;
+                        break;
+                }
+                if (!end) fmt++;
+            }
+
+            // Get field width
+            if (*fmt == '*')
+            {
+                return -101;
+            }
+            else
+            {
+                f_width = 0;
+                while (th_isdigit(*fmt))
+                    f_width = f_width * 10 + (*fmt++ - '0');
+            }
+
+            // Check for field precision
+            if (*fmt == '.')
+            {
+                fmt++;
+                if (*fmt == '*')
+                {
+                    return -102;
+                }
+                else
+                {
+                    // If no digit after '.', precision is to be 0
+                    f_prec = 0;
+                    while (th_isdigit(*fmt))
+                        f_prec = f_prec * 10 + (*fmt++ - '0');
+                }
+            }
+
+
+            // Check for length modifiers (only some are supported currently)
+            switch (*fmt)
+            {
+                case 0:
+                    return -104;
+
+                case 'o':
+                    if (opt->type != OTYPE_INT) return -120;
+                    if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 8, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_oct)) == EOF)
+                        goto out;
+                    break;
+
+                case 'u':
+                case 'i':
+                case 'd':
+                    if (opt->type != OTYPE_INT) return -120;
+                    if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 10, f_flags, f_width, f_prec, *fmt == 'u', NULL)) == EOF)
+                        goto out;
+                    break;
+
+                case 'x':
+                case 'X':
+                    if (opt->type != OTYPE_INT) return -120;
+                    if (*fmt == 'X')
+                        f_flags |= TH_PF_UPCASE;
+                    if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 16, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_hex)) == EOF)
+                        goto out;
+                    break;
+
+                case 's':
+                    if (opt->type != OTYPE_STR) return -121;
+                    if ((ret = th_vprintf_put_str(ctx, vputch, d_str, f_flags, f_width, f_prec)) == EOF)
+                        goto out;
+                    break;
+
+                //case '%':
+                default:
+                    if ((ret = vputch(ctx, *fmt)) == EOF)
+                        goto out;
+                    break;
+            }
+        }
+        fmt++;
+    }
+
+out:
+    return ret == EOF ? ret : ctx->ipos;
+}
+
+
+static int siItemFormatStrPutCH(th_vprintf_ctx *ctx, const char ch)
+{
+    if (ctx->pos + 1 >= ctx->size)
+    {
+        ctx->size += 64;
+        if ((ctx->buf = th_realloc(ctx->buf, ctx->size)) == NULL)
+            return EOF;
+    }
+
+    ctx->buf[ctx->pos] = ch;
+
+    ctx->pos++;
+    ctx->ipos++;
+    return ch;
+}
+
+
+char * siItemFormatStrPrint(const char *fmt, const PSFOption *opt, const char *d_str, const int d_int)
+{
+    th_vprintf_ctx ctx;
+
+    ctx.size = 128;
+    ctx.buf = th_malloc(ctx.size);
+    ctx.pos = 0;
+    ctx.ipos = 0;
+
+    if (ctx.buf == NULL)
+        return NULL;
+
+    if (siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCH, fmt, opt, d_str, d_int) <= 0)
+        goto err;
+
+    if (siItemFormatStrPutCH(&ctx, 0) < 0)
+        goto err;
+
+    return ctx.buf;
+
+err:
+    th_free(ctx.buf);
+    return NULL;
+}
+
+
+static int siItemFormatStrPutCHNone(th_vprintf_ctx *ctx, const char ch)
+{
+    ctx->pos++;
+    ctx->ipos++;
+    return ch;
+}
+
+
+BOOL siItemFormatStrCheck(const char *fmt, const PSFOption *opt)
+{
+    th_vprintf_ctx ctx;
+
+    memset(&ctx, 0, sizeof(ctx));
+
+    return siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCHNone, fmt, opt, NULL, 0) >= 0;
+}
+
+
 //
 // Parse a format string into a PSFStack structure
 //
@@ -314,21 +538,42 @@
             }
             else
             {
-                char *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH);
+                char *fopt = NULL, *pfield, *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH);
+                if ((pfield = strchr(field, ':')) != NULL)
+                {
+                    *pfield = 0;
+                    fopt = th_strdup_trim(pfield + 1, TH_TRIM_BOTH);
+                }
 
                 int ret = argMatchPSFieldError(field);
                 if (ret >= 0)
                 {
                     item.cmd = ret;
                     item.flags = 0;
+                    item.fmt = NULL;
                     item.str = NULL;
 
+                    if (fopt != NULL)
+                    {
+                        if (siItemFormatStrCheck(fopt, &optPSOptions[item.cmd]))
+                        {
+                            item.flags |= OFMT_FORMAT;
+                            item.fmt = th_strdup(fopt);
+                        }
+                        else
+                        {
+                            THERR("Invalid field format specifier '%s' in '%s'.\n", fopt, field);
+                            rval = FALSE;
+                        }
+                    }
+
                     if (!siStackAddItem(stack, &item))
                         rval = FALSE;
                 }
                 else
                     rval = FALSE;
 
+                th_free(fopt);
                 th_free(field);
             }
 
@@ -452,16 +697,26 @@
 static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const PSFStackItem *item, const char *d_str, const int d_int)
 {
     const PSFOption *opt = &optPSOptions[item->cmd];
-    char *str = NULL;
+    char *fmt, *str = NULL;
 
     switch (opt->type)
     {
         case OTYPE_INT:
-            str = th_strdup_printf(optHexadecimal ? "$%04x" : "%d", d_int);
+            if (item->flags & OFMT_FORMAT)
+                fmt = item->fmt;
+            else
+                fmt = optHexadecimal ? "$%04x" : "%d";
+
+            str = siItemFormatStrPrint(fmt, opt, d_str, d_int);
             break;
 
         case OTYPE_STR:
-            str = th_strdup(d_str);
+            if (item->flags & OFMT_FORMAT)
+                fmt = item->fmt;
+            else
+                fmt = "%s";
+
+            str = siItemFormatStrPrint(fmt, opt, d_str, d_int);
             break;
     }
 
@@ -581,7 +836,6 @@
     PSIDHeader *psid = NULL;
     th_ioctx *inFile = NULL;
     FILE *outFile;
-    int index;
     BOOL shown = FALSE;
 
     optNFiles++;
@@ -605,7 +859,7 @@
         psid->lengths = si_sldb_get_by_hash(sidSLDB, psid->hash);
 
     // Output
-    for (index = 0; index < optFormat.nitems; index++)
+    for (int index = 0; index < optFormat.nitems; index++)
     {
         PSFStackItem *item = &optFormat.items[index];
         switch (item->cmd)