#include #include #include #include #include "Publishing/edition.h" #include "assert.h" #include "characters.h" #include "commandline/longoptions.h" #include "commandline/parameters.h" #include "cstring.h" #include "english.h" #include "object.h" #include "types/boolean.h" #include "types/composites/composites.h" #include "types/numbers/integer.h" static void help(cstring const pathname) { assert(pathname); const size_t indent = strlen(pathname); (void)fprintf(stdout, "Usage: %s [-[d|e|h|i|j|l|m|v]...] [--option[ |=]value ...]\n" " %*c [--] [filename|- [argument ...]]\n" "\n" " -d Increase debugging information (repeat for more).\n" " -e Interactive executive mode (default if input is a terminal on %%stdin).\n" " -h Display this help text.\n" " -i Interpreter file, see execve(2).\n" " -j Jobserver mode.\n" " -l Display version, legal and license information.\n" " -m Minimal mode (default).\n" " -v Display version information.\n" #undef longoptionhelp #define each(LONGOPTION) as \ " --" #LONGOPTION " " #define longoptionhelp(TEXT) as \ #TEXT "\n" foreach (longoption) #undef each #undef longoptionhelp #define longoptionhelp(TEXT) " -- (marks the end of argument processing)\n" " filename (can be in the PostScript %%device%%file form)\n" " - (replaced with %%stdin)\n" " argument(s) (//userdict /ARGUMENTS [(argument) readonly ...] readonly put)\n" "\n" "A non-zero exit status is returned for unhandled errors.\n" "See also: %s(1) and Adobe's PostScript Language Reference manuals.\n", pathname, (assert(indent <= INT_MAX), (int)indent), ' ', edition.title ); } static void hint(cstring const pathname, cstring const * const argument) { assert(pathname); assert(argument); assert(*argument); (void)fprintf(stderr, "%s: Error reading argument \"%s\". For help try '%s -h' or 'man %s'.\n", pathname, *argument, pathname, edition.title ); } static void legal(void) { (void)fprintf(stdout, "%s\n%s\n", edition.description, edition.notices); } static void licensing(void) { (void)fprintf(stdout, "%s\n", edition.licensing); } static void version(void) { (void)fprintf(stdout, "%s %s\n", edition.title, edition.version); } static integer numeric(cstring digits) { if (digits) { natural number = 0; for (natural digit; digit = decimal[*(byte *)digits]; ++digits) { digit &= 0xff; if (number > largestinteger / 10) goto error; number *= 10; if (number > largestinteger - digit) goto error; number += digit; } if (*digits == '\0') return (integer)number; } error: return -1; } static cstring matches(cstring option, cstring key, cstring const ** const argument) { for (assert(option), assert(key); *option == *key; ++option, ++key) { if (*option == '\0') { assert(argument); assert(*argument); return *++*argument; } } if (*option == '=' && *key == '\0' && *++option) return option; else return 0; } static inline boolean valueof(cstring const string, cstring const value) { assert(string); assert(value); return (strcmp(string, value) == 0); } static boolean longoption(configure * const configuration, cstring const option, cstring const ** const arguments) { assert(configuration); assert(option); assert(arguments); assert(*arguments); const struct {cstring const name; object * const parameter;} options[] = { #define each(LONGOPTION) as \ {.name = #LONGOPTION, .parameter = &configuration->LONGOPTION}, foreach (longoption) #undef each }; for (size_t entry = 0; entry < sizeof options / sizeof *options; ++entry) { cstring argument = 0; if (argument = matches(option, options[entry].name, arguments)) { switch (options[entry].parameter->type) { case booleantype: { if (valueof(argument, "true")) *options[entry].parameter = boolean(true); else if (valueof(argument, "false")) *options[entry].parameter = boolean(false); else break; return true; } case integertype: { integer amount = numeric(argument); if (amount < 0) break; *options[entry].parameter = integer(amount); return true; } case stringtype: { *options[entry].parameter = string(argument); return true; } } break; } } return false; } configure commandlineparameters(cstring const * const arguments, configure configuration) { assert(arguments); assert(*arguments); #define each(LONGOPTION) as \ assert(configuration.LONGOPTION.type != nulltype); foreach (longoption) #undef each cstring const *argument = arguments; cstring pathname = *argument++; natural ExecutionModeselected = 0; boolean processing = true; while (*argument) { switch ((*argument)[0]) { case '\0': { ++argument; continue; } case '-': { if (processing && (*argument)[1]) break; } default: { assert(configuration.FileName.type == stringtype); configuration.FileName = string( valueof(*argument, "-") ? "%stdin" : *argument ); configuration.arguments = (struct arguments){.list = ++argument}; while (*argument) ++argument; ptrdiff_t difference = argument - configuration.arguments.list; assert(difference >= 0); assert(difference <= longestlength); configuration.arguments.length = (natural)difference; continue; } } for (cstring option = &(*argument)[1]; *option; ++option) { switch (*option) { case '-': { if (*++option) { cstring const *error = argument; if (longoption(&configuration, option, &argument) == false) { argument = error; goto error; } } else { processing = false; } break; } case 'd': { assert(configuration.DebugLevel.type == integertype); if (configuration.DebugLevel.integer < largestinteger) ++configuration.DebugLevel.integer; continue; } case 'e': { if (ExecutionModeselected++) goto error; assert(configuration.ExecutionMode.type == stringtype); configuration.ExecutionMode = string("interactive"); continue; } case 'h': { version(); legal(); printf("\n"); help(pathname); return (configure){.error = true}; } case 'i': { if (ExecutionModeselected++) goto error; assert(configuration.ExecutionMode.type == stringtype); configuration.ExecutionMode = string("script"); continue; } case 'j': { if (ExecutionModeselected++) goto error; assert(configuration.ExecutionMode.type == stringtype); configuration.ExecutionMode = string("jobserver"); continue; } case 'l': { version(); legal(); licensing(); return (configure){.error = true}; } case 'm': { if (ExecutionModeselected++) goto error; assert(configuration.ExecutionMode.type == stringtype); configuration.ExecutionMode = string("minimal"); continue; } case 'v': { version(); return (configure){.error = true}; } default: error: { hint(pathname, argument); return (configure){.error = true}; } } break; } ++argument; } return configuration; }