# HG changeset patch # User Matti Hamalainen # Date 1508962809 -10800 # Node ID f636c55c7cc470de200d6aef2ee7dc16743ff0db # Parent 263cd25749a175e6f14b4056f08849566add1870 Implement C printf-style format specifiers for -F option @fields@. diff -r 263cd25749a1 -r f636c55c7cc4 sidinfo.c --- 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)