Mercurial > hg > sidinfo
comparison sidinfo.c @ 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 |
comparison
equal
deleted
inserted
replaced
146:263cd25749a1 | 147:f636c55c7cc4 |
---|---|
17 #define SET_SLDB_FILENAME "Songlengths.txt" | 17 #define SET_SLDB_FILENAME "Songlengths.txt" |
18 | 18 |
19 | 19 |
20 enum | 20 enum |
21 { | 21 { |
22 OFMT_FORMAT = 0x0002, | |
23 }; | |
24 | |
25 | |
26 enum | |
27 { | |
22 OTYPE_OTHER = 0, | 28 OTYPE_OTHER = 0, |
23 OTYPE_STR = 1, | 29 OTYPE_STR = 1, |
24 OTYPE_INT = 2, | 30 OTYPE_INT = 2, |
25 }; | 31 }; |
26 | 32 |
29 { | 35 { |
30 int cmd; | 36 int cmd; |
31 char *str; | 37 char *str; |
32 char chr; | 38 char chr; |
33 int flags; | 39 int flags; |
40 char *fmt; | |
34 } PSFStackItem; | 41 } PSFStackItem; |
35 | 42 |
36 | 43 |
37 typedef struct | 44 typedef struct |
38 { | 45 { |
149 "\n" | 156 "\n" |
150 "Format strings for '-F' option are composed of @fields@ that\n" | 157 "Format strings for '-F' option are composed of @fields@ that\n" |
151 "are expanded to their value. Also, escape sequences \\r, \\n and \\t\n" | 158 "are expanded to their value. Also, escape sequences \\r, \\n and \\t\n" |
152 "can be used: -F \"hash=@hash@\\ncopy=@copyright@\\n\"\n" | 159 "can be used: -F \"hash=@hash@\\ncopy=@copyright@\\n\"\n" |
153 "\n" | 160 "\n" |
161 "The -F fields can be further formatted via printf-style specifiers:\n" | |
162 "-F \"@copyright:'%%-30s'@\"\n" | |
163 "\n" | |
154 "When specifying HVSC path, it is preferable to use -H/--hvsc option,\n" | 164 "When specifying HVSC path, it is preferable to use -H/--hvsc option,\n" |
155 "as STIL.txt and Songlengths.txt will be automatically used from there.\n" | 165 "as STIL.txt and Songlengths.txt will be automatically used from there.\n" |
156 , th_prog_name); | 166 , th_prog_name); |
157 } | 167 } |
158 | 168 |
264 | 274 |
265 return TRUE; | 275 return TRUE; |
266 } | 276 } |
267 | 277 |
268 | 278 |
279 int siItemFormatStrPutInt(th_vprintf_ctx *ctx, th_vprintf_putch vputch, | |
280 const int value, const int f_radix, int f_flags, int f_width, int f_prec, | |
281 const BOOL f_unsig, char *(f_alt)(const char *buf, const size_t blen, const int vret, const int flags)) | |
282 { | |
283 char buf[64]; | |
284 int f_len = 0, vret; | |
285 BOOL f_neg = FALSE; | |
286 | |
287 vret = th_vprintf_buf_int(buf, sizeof(buf), &f_len, value, | |
288 f_radix, f_flags & TH_PF_UPCASE, f_unsig, &f_neg); | |
289 | |
290 if (vret == EOF) | |
291 return 0; | |
292 | |
293 return th_vprintf_put_int_format(ctx, vputch, buf, f_flags, f_width, f_prec, f_len, vret, f_neg, f_unsig, f_alt); | |
294 } | |
295 | |
296 | |
297 int siItemFormatStrPrintDo(th_vprintf_ctx *ctx, th_vprintf_putch vputch, const char *fmt, | |
298 const PSFOption *opt, const char *d_str, const int d_int) | |
299 { | |
300 int ret = 0; | |
301 | |
302 while (*fmt) | |
303 { | |
304 if (*fmt != '%') | |
305 { | |
306 if ((ret = vputch(ctx, *fmt)) == EOF) | |
307 goto out; | |
308 } | |
309 else | |
310 { | |
311 int f_width = -1, f_prec = -1, f_flags = 0; | |
312 BOOL end = FALSE; | |
313 | |
314 fmt++; | |
315 | |
316 // Check for flags | |
317 while (!end) | |
318 { | |
319 switch (*fmt) | |
320 { | |
321 case '#': | |
322 f_flags |= TH_PF_ALT; | |
323 break; | |
324 | |
325 case '+': | |
326 f_flags |= TH_PF_SIGN; | |
327 break; | |
328 | |
329 case '0': | |
330 f_flags |= TH_PF_ZERO; | |
331 break; | |
332 | |
333 case '-': | |
334 f_flags |= TH_PF_LEFT; | |
335 break; | |
336 | |
337 case ' ': | |
338 f_flags |= TH_PF_SPACE; | |
339 break; | |
340 | |
341 case '\'': | |
342 f_flags |= TH_PF_GROUP; | |
343 break; | |
344 | |
345 default: | |
346 end = TRUE; | |
347 break; | |
348 } | |
349 if (!end) fmt++; | |
350 } | |
351 | |
352 // Get field width | |
353 if (*fmt == '*') | |
354 { | |
355 return -101; | |
356 } | |
357 else | |
358 { | |
359 f_width = 0; | |
360 while (th_isdigit(*fmt)) | |
361 f_width = f_width * 10 + (*fmt++ - '0'); | |
362 } | |
363 | |
364 // Check for field precision | |
365 if (*fmt == '.') | |
366 { | |
367 fmt++; | |
368 if (*fmt == '*') | |
369 { | |
370 return -102; | |
371 } | |
372 else | |
373 { | |
374 // If no digit after '.', precision is to be 0 | |
375 f_prec = 0; | |
376 while (th_isdigit(*fmt)) | |
377 f_prec = f_prec * 10 + (*fmt++ - '0'); | |
378 } | |
379 } | |
380 | |
381 | |
382 // Check for length modifiers (only some are supported currently) | |
383 switch (*fmt) | |
384 { | |
385 case 0: | |
386 return -104; | |
387 | |
388 case 'o': | |
389 if (opt->type != OTYPE_INT) return -120; | |
390 if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 8, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_oct)) == EOF) | |
391 goto out; | |
392 break; | |
393 | |
394 case 'u': | |
395 case 'i': | |
396 case 'd': | |
397 if (opt->type != OTYPE_INT) return -120; | |
398 if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 10, f_flags, f_width, f_prec, *fmt == 'u', NULL)) == EOF) | |
399 goto out; | |
400 break; | |
401 | |
402 case 'x': | |
403 case 'X': | |
404 if (opt->type != OTYPE_INT) return -120; | |
405 if (*fmt == 'X') | |
406 f_flags |= TH_PF_UPCASE; | |
407 if ((ret = siItemFormatStrPutInt(ctx, vputch, d_int, 16, f_flags, f_width, f_prec, TRUE, th_vprintf_altfmt_hex)) == EOF) | |
408 goto out; | |
409 break; | |
410 | |
411 case 's': | |
412 if (opt->type != OTYPE_STR) return -121; | |
413 if ((ret = th_vprintf_put_str(ctx, vputch, d_str, f_flags, f_width, f_prec)) == EOF) | |
414 goto out; | |
415 break; | |
416 | |
417 //case '%': | |
418 default: | |
419 if ((ret = vputch(ctx, *fmt)) == EOF) | |
420 goto out; | |
421 break; | |
422 } | |
423 } | |
424 fmt++; | |
425 } | |
426 | |
427 out: | |
428 return ret == EOF ? ret : ctx->ipos; | |
429 } | |
430 | |
431 | |
432 static int siItemFormatStrPutCH(th_vprintf_ctx *ctx, const char ch) | |
433 { | |
434 if (ctx->pos + 1 >= ctx->size) | |
435 { | |
436 ctx->size += 64; | |
437 if ((ctx->buf = th_realloc(ctx->buf, ctx->size)) == NULL) | |
438 return EOF; | |
439 } | |
440 | |
441 ctx->buf[ctx->pos] = ch; | |
442 | |
443 ctx->pos++; | |
444 ctx->ipos++; | |
445 return ch; | |
446 } | |
447 | |
448 | |
449 char * siItemFormatStrPrint(const char *fmt, const PSFOption *opt, const char *d_str, const int d_int) | |
450 { | |
451 th_vprintf_ctx ctx; | |
452 | |
453 ctx.size = 128; | |
454 ctx.buf = th_malloc(ctx.size); | |
455 ctx.pos = 0; | |
456 ctx.ipos = 0; | |
457 | |
458 if (ctx.buf == NULL) | |
459 return NULL; | |
460 | |
461 if (siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCH, fmt, opt, d_str, d_int) <= 0) | |
462 goto err; | |
463 | |
464 if (siItemFormatStrPutCH(&ctx, 0) < 0) | |
465 goto err; | |
466 | |
467 return ctx.buf; | |
468 | |
469 err: | |
470 th_free(ctx.buf); | |
471 return NULL; | |
472 } | |
473 | |
474 | |
475 static int siItemFormatStrPutCHNone(th_vprintf_ctx *ctx, const char ch) | |
476 { | |
477 ctx->pos++; | |
478 ctx->ipos++; | |
479 return ch; | |
480 } | |
481 | |
482 | |
483 BOOL siItemFormatStrCheck(const char *fmt, const PSFOption *opt) | |
484 { | |
485 th_vprintf_ctx ctx; | |
486 | |
487 memset(&ctx, 0, sizeof(ctx)); | |
488 | |
489 return siItemFormatStrPrintDo(&ctx, siItemFormatStrPutCHNone, fmt, opt, NULL, 0) >= 0; | |
490 } | |
491 | |
492 | |
269 // | 493 // |
270 // Parse a format string into a PSFStack structure | 494 // Parse a format string into a PSFStack structure |
271 // | 495 // |
272 BOOL argParsePSFormatStr(PSFStack *stack, const char *fmt) | 496 BOOL argParsePSFormatStr(PSFStack *stack, const char *fmt) |
273 { | 497 { |
312 if (!siStackAddItem(stack, &item)) | 536 if (!siStackAddItem(stack, &item)) |
313 return FALSE; | 537 return FALSE; |
314 } | 538 } |
315 else | 539 else |
316 { | 540 { |
317 char *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH); | 541 char *fopt = NULL, *pfield, *field = th_strndup_trim(start, fmt - start, TH_TRIM_BOTH); |
542 if ((pfield = strchr(field, ':')) != NULL) | |
543 { | |
544 *pfield = 0; | |
545 fopt = th_strdup_trim(pfield + 1, TH_TRIM_BOTH); | |
546 } | |
318 | 547 |
319 int ret = argMatchPSFieldError(field); | 548 int ret = argMatchPSFieldError(field); |
320 if (ret >= 0) | 549 if (ret >= 0) |
321 { | 550 { |
322 item.cmd = ret; | 551 item.cmd = ret; |
323 item.flags = 0; | 552 item.flags = 0; |
553 item.fmt = NULL; | |
324 item.str = NULL; | 554 item.str = NULL; |
555 | |
556 if (fopt != NULL) | |
557 { | |
558 if (siItemFormatStrCheck(fopt, &optPSOptions[item.cmd])) | |
559 { | |
560 item.flags |= OFMT_FORMAT; | |
561 item.fmt = th_strdup(fopt); | |
562 } | |
563 else | |
564 { | |
565 THERR("Invalid field format specifier '%s' in '%s'.\n", fopt, field); | |
566 rval = FALSE; | |
567 } | |
568 } | |
325 | 569 |
326 if (!siStackAddItem(stack, &item)) | 570 if (!siStackAddItem(stack, &item)) |
327 rval = FALSE; | 571 rval = FALSE; |
328 } | 572 } |
329 else | 573 else |
330 rval = FALSE; | 574 rval = FALSE; |
331 | 575 |
576 th_free(fopt); | |
332 th_free(field); | 577 th_free(field); |
333 } | 578 } |
334 | 579 |
335 mode = 0; | 580 mode = 0; |
336 fmt++; | 581 fmt++; |
450 | 695 |
451 | 696 |
452 static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const PSFStackItem *item, const char *d_str, const int d_int) | 697 static void siPrintPSIDInfoLine(FILE *outFile, BOOL *shown, const PSFStackItem *item, const char *d_str, const int d_int) |
453 { | 698 { |
454 const PSFOption *opt = &optPSOptions[item->cmd]; | 699 const PSFOption *opt = &optPSOptions[item->cmd]; |
455 char *str = NULL; | 700 char *fmt, *str = NULL; |
456 | 701 |
457 switch (opt->type) | 702 switch (opt->type) |
458 { | 703 { |
459 case OTYPE_INT: | 704 case OTYPE_INT: |
460 str = th_strdup_printf(optHexadecimal ? "$%04x" : "%d", d_int); | 705 if (item->flags & OFMT_FORMAT) |
706 fmt = item->fmt; | |
707 else | |
708 fmt = optHexadecimal ? "$%04x" : "%d"; | |
709 | |
710 str = siItemFormatStrPrint(fmt, opt, d_str, d_int); | |
461 break; | 711 break; |
462 | 712 |
463 case OTYPE_STR: | 713 case OTYPE_STR: |
464 str = th_strdup(d_str); | 714 if (item->flags & OFMT_FORMAT) |
715 fmt = item->fmt; | |
716 else | |
717 fmt = "%s"; | |
718 | |
719 str = siItemFormatStrPrint(fmt, opt, d_str, d_int); | |
465 break; | 720 break; |
466 } | 721 } |
467 | 722 |
468 siPrintFieldPrefix(outFile, opt); | 723 siPrintFieldPrefix(outFile, opt); |
469 | 724 |
579 BOOL argHandleFile(char *filename) | 834 BOOL argHandleFile(char *filename) |
580 { | 835 { |
581 PSIDHeader *psid = NULL; | 836 PSIDHeader *psid = NULL; |
582 th_ioctx *inFile = NULL; | 837 th_ioctx *inFile = NULL; |
583 FILE *outFile; | 838 FILE *outFile; |
584 int index; | |
585 BOOL shown = FALSE; | 839 BOOL shown = FALSE; |
586 | 840 |
587 optNFiles++; | 841 optNFiles++; |
588 outFile = stdout; | 842 outFile = stdout; |
589 | 843 |
603 // Get songlength information, if any | 857 // Get songlength information, if any |
604 if (sidSLDB != NULL) | 858 if (sidSLDB != NULL) |
605 psid->lengths = si_sldb_get_by_hash(sidSLDB, psid->hash); | 859 psid->lengths = si_sldb_get_by_hash(sidSLDB, psid->hash); |
606 | 860 |
607 // Output | 861 // Output |
608 for (index = 0; index < optFormat.nitems; index++) | 862 for (int index = 0; index < optFormat.nitems; index++) |
609 { | 863 { |
610 PSFStackItem *item = &optFormat.items[index]; | 864 PSFStackItem *item = &optFormat.items[index]; |
611 switch (item->cmd) | 865 switch (item->cmd) |
612 { | 866 { |
613 case -1: | 867 case -1: |