diff --git a/docopt.py b/docopt.py index f4698b0..6fb8ea4 100644 --- a/docopt.py +++ b/docopt.py @@ -6,21 +6,21 @@ * Copyright (c) 2013 Vladimir Keleshev, vladimir@keleshev.com """ - -from __future__ import print_function - -import re import sys +import re + __all__ = ['docopt'] -__version__ = '0.6.2' +__version__ = '0.6.2tj1' class DocoptLanguageError(Exception): + """Error in construction of usage-message by developer.""" class DocoptExit(SystemExit): + """Exit in case user invoked program with incorrect arguments.""" usage = '' @@ -30,6 +30,7 @@ def __init__(self, message=''): class Pattern(object): + def __eq__(self, other): return repr(self) == repr(other) @@ -96,14 +97,11 @@ def transform(pattern): class LeafPattern(Pattern): + """Leaf/terminal node of a pattern tree.""" - name = value = None def __init__(self, name, value=None): - if name is not None: - self.name = name - if value is not None: - self.value = value + self.name, self.value = name, value def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.name, self.value) @@ -133,6 +131,7 @@ def match(self, left, collected=None): class BranchPattern(Pattern): + """Branch/inner node of a pattern tree.""" def __init__(self, *children): @@ -149,6 +148,7 @@ def flat(self, *types): class Argument(LeafPattern): + def single_match(self, left): for n, pattern in enumerate(left): if type(pattern) is Argument: @@ -156,15 +156,15 @@ def single_match(self, left): return None, None @classmethod - def parse(cls, source): - name = re.findall('(<\S*?>)', source)[0] - value = re.findall('\[default: (.*)\]', source, flags=re.I) - return cls(name, value[0] if value else None) + def parse(class_, source): + name = re.findall('(<\\S*?>)', source)[0] + value = re.findall('\\[default: (.*)\\]', source, flags=re.I) + return class_(name, value[0] if value else None) class Command(Argument): + def __init__(self, name, value=False): - super(Command, self).__init__(name, value) self.name, self.value = name, value def single_match(self, left): @@ -178,8 +178,8 @@ def single_match(self, left): class Option(LeafPattern): + def __init__(self, short=None, long=None, argcount=0, value=False): - super(Option, self).__init__(None, value) assert argcount in (0, 1) self.short, self.long, self.argcount = short, long, argcount self.value = None if value is False and argcount else value @@ -197,7 +197,7 @@ def parse(class_, option_description): else: argcount = 1 if argcount: - matched = re.findall('\[default: (.*)\]', description, flags=re.I) + matched = re.findall('\\[default: (.*)\\]', description, flags=re.I) value = matched[0] if matched else None return class_(short, long, argcount, value) @@ -275,20 +275,20 @@ def match(self, left, collected=None): if matched: outcomes.append(outcome) if outcomes: - return min(outcomes, key=lambda _outcome: len(_outcome[1])) + return min(outcomes, key=lambda outcome: len(outcome[1])) return False, left, collected class Tokens(list): + def __init__(self, source, error=DocoptExit): - super(Tokens, self).__init__() self += source.split() if hasattr(source, 'split') else source self.error = error @staticmethod def from_pattern(source): source = re.sub(r'([\[\]\(\)\|]|\.\.\.)', r' \1 ', source) - source = [s for s in re.split('\s+|(\S*<.*?>)', source) if s] + source = [s for s in re.split('\\s+|(\\S*<.*?>)', source) if s] return Tokens(source, error=DocoptLanguageError) def move(self): @@ -404,6 +404,7 @@ def parse_atom(tokens, options): | long | shorts | argument | command ; """ token = tokens.current() + result = [] if token in '([': tokens.move() matching, pattern = {'(': [')', Required], '[': [']', Optional]}[token] @@ -453,7 +454,7 @@ def parse_defaults(doc): for s in parse_section('options:', doc): # FIXME corner case "bla: options: --foo" _, _, s = s.partition(':') # get rid of "options:" - split = re.split('\n[ \t]*(-\S+?)', '\n' + s)[1:] + split = re.split('\n[ \t]*(-\\S+?)', '\n' + s)[1:] split = [s1 + s2 for s1, s2 in zip(split[::2], split[1::2])] options = [Option.parse(s) for s in split if s.startswith('-')] defaults += options @@ -520,7 +521,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): Example ------- - >>> from docopt_c.docopt import docopt + >>> from docopt import docopt >>> doc = ''' ... Usage: ... my_program tcp [--timeout=] @@ -561,7 +562,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): options = parse_defaults(doc) pattern = parse_pattern(formal_usage(DocoptExit.usage), options) # [default] syntax for argument is disabled - # for a in pattern.flat(Argument): + #for a in pattern.flat(Argument): # same_name = [d for d in arguments if d.name == a.name] # if same_name: # a.value = same_name[0].value @@ -570,7 +571,7 @@ def docopt(doc, argv=None, help=True, version=None, options_first=False): for options_shortcut in pattern.flat(OptionsShortcut): doc_options = parse_defaults(doc) options_shortcut.children = list(set(doc_options) - pattern_options) - # if any_options: + #if any_options: # options_shortcut.children += [Option(o.short, o.long, o.argcount) # for o in argv if type(o) is Option] extras(help, version, argv, doc) diff --git a/docopt_c.py b/docopt_c.py index 9067051..c29608e 100755 --- a/docopt_c.py +++ b/docopt_c.py @@ -26,7 +26,7 @@ """ __author__ = "Vladimir Keleshev" -__version__ = "2.0rc2" +__version__ = "2.0rc2tj4" __description__ = "C generator for language for description of command-line interfaces" import numbers @@ -43,96 +43,49 @@ #ifndef DOCOPT_$header_no_ext_H #define DOCOPT_$header_no_ext_H -#include - -#if defined(__STDC__) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +/** \\addtogroup docopt Command Line + * Command line processing. + * @{ + **/ +#include #include -#elif !defined(_STDBOOL_H) -#define _STDBOOL_H - -#include - -#ifdef true -#undef true -#endif -#ifdef false -#undef false -#endif -#ifdef bool -#undef bool -#endif - -#define true 1 -#define false (!true) -typedef size_t bool; - -#endif - -#if defined(_AIX) - -#include - -#elif defined(__FreeBSD__) || defined(__NetBSD__) -|| defined(__OpenBSD__) || defined(__bsdi__) -|| defined(__DragonFly__) || defined(macintosh) -|| defined(__APPLE__) || defined(__APPLE_CC__) - -#include - -#elif defined(__HAIKU__) - -#include - -#elif defined(__linux__) || defined(linux) || defined(__linux) - -#include - -#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,22) - -#include - -#else - -#define ARG_MAX 131072 /* # bytes of args + environ for exec() */ -/* it's no longer defined, see this example and more at https://unix.stackexchange.com/q/120642 */ - -#endif - -#elif (defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) \ - || defined(__DragonFly__) || defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) - -#include - -#if defined(__APPLE__) || defined(__APPLE_CC__) -/* ARG_MAX gives a segfault on macOS when used for array size below */ -#undef ARG_MAX -#undef NCARGS -#endif - -#else - -#include - -#endif - -#ifndef ARG_MAX -#ifdef NCARGS -#define ARG_MAX NCARGS -#else -#define ARG_MAX 131072 -#endif -#endif - +/** + * Data structure to hold the command line arguments, after parsing. This + * structure is autogenerated using `docopt`. + **/ struct DocoptArgs { $commands$arguments$flags$options - /* special */ + + /** + * Usage message, included in help output + **/ const char *usage_pattern; + + /** + * Help message, including option and flag messages + **/ const char *help_message[$help_message_n]; }; -struct DocoptArgs docopt(int, char *[], bool, const char *); +/** + * Decode the command line arguments. + * + * @param argc Number of parameters to parse, generally an actual parameter + * to main() + * @param argv Array of arguments, generally an actual parameter to main() + * @param helpCallback If defined, then the help or version commands will + * be immediately executed, printing a result and + * calling this function with an int status code. + * Normally, pass `exit` from stdlib.h. + * @param version The current version of this program, to be displayed for + * the `-v` or `--version` output + * + * @return `DocoptArgs` structure representing the parsed form. + **/ +struct DocoptArgs ${primary_command}CommandLineParser(int argc, char *argv[], void ( *helpCallback )( int statusCode ), const char *version); +/// @} #endif """ @@ -143,19 +96,20 @@ #include #include "$header_name" +#define ARG_MAX 256 -struct Command { +struct ${prefix}Command { const char *name; bool value; }; -struct Argument { +struct ${prefix}Argument { const char *name; const char *value; - const char *array[ARG_MAX]; + const void *unused; }; -struct Option { +struct ${prefix}Option { const char *oshort; const char *olong; bool argcount; @@ -167,28 +121,27 @@ int n_commands; int n_arguments; int n_options; - struct Command *commands; - struct Argument *arguments; - struct Option *options; + struct ${prefix}Command *commands; + struct ${prefix}Argument *arguments; + struct ${prefix}Option *options; }; /* * Tokens object */ - -struct Tokens { +struct ${prefix}Tokens { int argc; char **argv; int i; char *current; }; -const char usage_pattern[] = +const char ${primary_command}_usage_pattern[] = $usage_pattern; -struct Tokens tokens_new(int argc, char **argv) { - struct Tokens ts; +static struct ${prefix}Tokens tokens_new(int argc, char **argv) { + struct ${prefix}Tokens ts; ts.argc = argc; ts.argv = argv; ts.i = 0; @@ -196,7 +149,7 @@ return ts; } -struct Tokens *tokens_move(struct Tokens *ts) { +static struct ${prefix}Tokens *tokens_move(struct ${prefix}Tokens *ts) { if (ts->i < ts->argc) { ts->current = ts->argv[++ts->i]; } @@ -207,30 +160,13 @@ } -/* - * ARGV parsing functions - */ - -int parse_doubledash(struct Tokens *ts, struct Elements *elements) { - /* - int n_commands = elements->n_commands; - int n_arguments = elements->n_arguments; - Command *commands = elements->commands; - Argument *arguments = elements->arguments; - - not implemented yet - return parsed + [Argument(None, v) for v in tokens] - */ - return 0; -} - -int parse_long(struct Tokens *ts, struct Elements *elements) { +static int parse_long(struct ${prefix}Tokens *ts, struct Elements *elements) { int i; int len_prefix; int n_options = elements->n_options; char *eq = strchr(ts->current, '='); - struct Option *option; - struct Option *options = elements->options; + struct ${prefix}Option *option = NULL; + struct ${prefix}Option *options = elements->options; len_prefix = (eq - (ts->current)) / sizeof(char); for (i = 0; i < n_options; i++) { @@ -240,14 +176,14 @@ } if (i == n_options) { /* TODO: %s is not a unique prefix */ - fprintf(stderr, "%s is not recognized\\n", ts->current); + fprintf(stdout, "%s is not recognized\\n\\n", ts->current); return 1; } tokens_move(ts); if (option->argcount) { if (eq == NULL) { if (ts->current == NULL) { - fprintf(stderr, "%s requires argument\\n", option->olong); + fprintf(stdout, "%s requires argument\\n\\n", option->olong); return 1; } option->argument = ts->current; @@ -257,7 +193,7 @@ } } else { if (eq != NULL) { - fprintf(stderr, "%s must not have an argument\\n", option->olong); + fprintf(stdout, "%s must not have an argument\\n\\n", option->olong); return 1; } option->value = true; @@ -265,12 +201,12 @@ return 0; } -int parse_shorts(struct Tokens *ts, struct Elements *elements) { +static int parse_shorts(struct ${prefix}Tokens *ts, struct Elements *elements) { char *raw; int i; int n_options = elements->n_options; - struct Option *option; - struct Option *options = elements->options; + struct ${prefix}Option *option = NULL; + struct ${prefix}Option *options = elements->options; raw = &ts->current[1]; tokens_move(ts); @@ -282,7 +218,7 @@ } if (i == n_options) { /* TODO -%s is specified ambiguously %d times */ - fprintf(stderr, "-%c is not recognized\\n", raw[0]); + fprintf(stdout, "-%c is not recognized\\n\\n", raw[0]); return EXIT_FAILURE; } raw++; @@ -291,7 +227,7 @@ } else { if (raw[0] == '\\0') { if (ts->current == NULL) { - fprintf(stderr, "%s requires argument\\n", option->oshort); + fprintf(stdout, "%s requires argument\\n\\n", option->oshort); return EXIT_FAILURE; } raw = ts->current; @@ -304,43 +240,58 @@ return EXIT_SUCCESS; } -int parse_argcmd(struct Tokens *ts, struct Elements *elements) { +static int parse_argcmd(struct ${prefix}Tokens *ts, struct Elements *elements) { int i; int n_commands = elements->n_commands; - /* int n_arguments = elements->n_arguments; */ - struct Command *command; - struct Command *commands = elements->commands; + int n_arguments = elements->n_arguments; + struct ${prefix}Command *command; + struct ${prefix}Command *commands = elements->commands; + struct ${prefix}Argument *argument; /* Argument *arguments = elements->arguments; */ - for (i = 0; i < n_commands; i++) { - command = &commands[i]; - if (strcmp(command->name, ts->current) == 0) { - command->value = true; - tokens_move(ts); - return EXIT_SUCCESS; + if( ts->i > 0 ) // Don't consider the actual command + { + for (i = 0; i < n_commands; i++) { + command = &commands[i]; + if (strcmp(command->name, ts->current) == 0) { + command->value = true; + tokens_move(ts); + return EXIT_SUCCESS; + } + } + + for (i = 0; i < n_arguments; i++) + { + argument = &elements->arguments[i]; + if( ts->current && !argument->value ) + { + argument->value = ts->current; + tokens_move(ts); + return EXIT_SUCCESS; + } } } /* not implemented yet, just skip for now parsed.append(Argument(None, tokens.move())) */ /* - fprintf(stderr, "! argument '%s' has been ignored\\n", ts->current); - fprintf(stderr, " '"); + fprintf(stdout, "! argument '%s' has been ignored\\n", ts->current); + fprintf(stdout, " '"); for (i=0; iargc ; i++) - fprintf(stderr, "%s ", ts->argv[i]); - fprintf(stderr, "'\\n"); + fprintf(stdout, "%s ", ts->argv[i]); + fprintf(stdout, "'\\n"); */ tokens_move(ts); return EXIT_SUCCESS; } -int parse_args(struct Tokens *ts, struct Elements *elements) { +static int parse_args(struct ${prefix}Tokens *ts, struct Elements *elements) { int ret = EXIT_FAILURE; - while (ts->current != NULL) { - if (strcmp(ts->current, "--") == 0) { - ret = parse_doubledash(ts, elements); - if (ret == EXIT_FAILURE) break; - } else if (ts->current[0] == '-' && ts->current[1] == '-') { + while (ts->current != NULL) + { + // Standalone -- is not supported + + if (ts->current[0] == '-' && ts->current[1] == '-') { ret = parse_long(ts, elements); } else if (ts->current[0] == '-' && ts->current[1] != '\\0') { ret = parse_shorts(ts, elements); @@ -351,61 +302,81 @@ return ret; } -int elems_to_args(struct Elements *elements, struct DocoptArgs *args, - const bool help, const char *version) { - struct Command *command; - struct Argument *argument; - struct Option *option; - int i, j; +static void printHelp( struct DocoptArgs *args, const char *version ) +{ + printf( "%s (v%s)\\n", args->help_message[0], version ); + + for (int j = 1; j < $help_message_n; j++) + puts(args->help_message[j]); +} + +static void printVersion( const char *version ) +{ + puts( version ); +} + +static int elems_to_args(struct Elements *elements, struct DocoptArgs *args, + const bool doHelpVersionPrint, const char *version) +{ + int returnValue = EXIT_SUCCESS; + struct ${prefix}Command *command; + struct ${prefix}Argument *argument; + struct ${prefix}Option *option; + int i; - /* fix gcc-related compiler warnings (unused) */ + // fix gcc-related compiler warnings (unused) (void) command; (void) argument; - /* options */ + // options for (i = 0; i < elements->n_options; i++) { option = &elements->options[i]; - if (help && option->value && strcmp(option->olong, "--help") == 0) { - for (j = 0; j < $help_message_n; j++) - puts(args->help_message[j]); - return EXIT_FAILURE; - } else if (version && option->value && - strcmp(option->olong, "--version") == 0) { - puts(version); - return EXIT_FAILURE; - }$if_flag$if_option + $if_flag + $if_option } - /* commands */ + + // commands for (i = 0; i < elements->n_commands; i++) { command = &elements->commands[i]; $if_command } - /* arguments */ + + // arguments for (i = 0; i < elements->n_arguments; i++) { argument = &elements->arguments[i]; $if_argument } - return EXIT_SUCCESS; -} + if( 0 ) + { + if( 0 == strcmp(option->olong, "--help") ) { + args->help = 1; + returnValue = EXIT_FAILURE; + } else if( 0 == strcmp(option->olong, "--version") ) { + args->version = 1; + returnValue = EXIT_FAILURE; + } + } + + return (doHelpVersionPrint && (args->help||args->version)) ? EXIT_FAILURE : returnValue; +} -/* - * Main docopt function +/** + * Decode the command line arguments as defined for $primary_command + * application. */ - -struct DocoptArgs docopt(int argc, char *argv[], const bool help, const char *version) { +struct DocoptArgs ${primary_command}CommandLineParser( int argc, char *argv[], void ( *helpCallback )( int statusCode ), const char *version) { struct DocoptArgs args = {$defaults - usage_pattern, + ${primary_command}_usage_pattern, $help_message }; - struct Command commands[] = {$elems_cmds + struct ${prefix}Command commands[] = {$elems_cmds }; - struct Argument arguments[] = {$elems_args + struct ${prefix}Argument arguments[] = {$elems_args }; - struct Option options[] = {$elems_opts + struct ${prefix}Option options[] = {$elems_opts }; struct Elements elements; - int return_code = EXIT_SUCCESS; elements.n_commands = $t_elems_n_commands; elements.n_arguments = $t_elems_n_arguments; @@ -414,21 +385,34 @@ elements.arguments = arguments; elements.options = options; - if (argc == 1) { - argv[argc++] = "--help"; - argv[argc++] = NULL; - return_code = EXIT_FAILURE; - } + bool isError = false; + + // Inject help into the arg list if no arguments passed + // if (argc == 1) { + // isError = true; + // } + bool exitOnHelp = (helpCallback != NULL); + struct ${prefix}Tokens ts = tokens_new(argc, argv); + isError |= parse_args(&ts, &elements); + if( isError || elems_to_args(&elements, &args, exitOnHelp, version) ) { - struct Tokens ts = tokens_new(argc, argv); - if (parse_args(&ts, &elements)) - exit(EXIT_FAILURE); + args.help = isError; // Any error forces help to print + + if( exitOnHelp ) + { + if( args.version ) + printVersion( version ); + else + printHelp( &args, version ); + + helpCallback( isError ); + } } - if (elems_to_args(&elements, &args, help, version)) - exit(return_code); + return args; } + """ def to_initializer(val): @@ -491,7 +475,7 @@ def c_if_argument(arg): def c_if_flag(obj): - return ' else if (strcmp(option->o{typ!s}, {val!s}) == 0) {{\n' \ + return '\nif (strcmp(option->o{typ!s}, {val!s}) == 0) {{\n' \ ' args->{prop!s} = option->value;\n' \ '}}\n'.format(typ=('long' if obj.long else 'short'), val=to_c(obj.long or obj.short), @@ -499,7 +483,7 @@ def c_if_flag(obj): def c_if_option(obj): - return ' else if (strcmp(option->o{typ!s}, {val!s}) == 0) {{\n' \ + return '\nif (strcmp(option->o{typ!s}, {val!s}) == 0) {{\n' \ ' if (option->argument) {{\n' \ ' args->{prop!s} = (char *) option->argument;\n' \ ' }}\n}}\n'.format(typ=('long' if obj.long else 'short'), @@ -538,7 +522,7 @@ def parse_leafs(pattern, all_options): def null_if_zero(s): - return 'NULL' if s is None or len(s) == 0 else s + return '{}' if s is None or len(s) == 0 else s def main(): @@ -569,6 +553,9 @@ def main(): doc = args[''] usage = docopt.parse_section('usage:', doc) + primaryCommand = usage[0].splitlines()[1].lstrip().split()[0]; + primaryCommand = primaryCommand.replace('-','_') + error_str_l = 'More than one ', '"usage:" (case-insensitive)', ' not found.' usage = {0: error_str_l[1:], 1: usage[0] if usage else None}.get(len(usage), error_str_l[:2]) if isinstance(usage, list): @@ -584,18 +571,17 @@ def main(): for cmd in commands) t_commands = '\n{indent}/* commands */\n{indent}{t_commands};'.format(indent=_indent, t_commands=t_commands) \ if t_commands != '' else '' - t_arguments = ';\n{indent}'.join('char *{!s}'.format(c_name(arg.name)) + t_arguments = ';\n{indent}'.join('\n{indent}/**\n{indent} * Value of primary argument {comment}\n{indent} **/\n{indent}char *{token!s}'.format(indent=_indent, comment=arg.name, token=c_name(arg.name)) for arg in arguments) - t_arguments = '\n{indent}/* arguments */\n{indent}{t_arguments};'.format(indent=_indent, t_arguments=t_arguments) \ + t_arguments = '\n{t_arguments};'.format(indent=_indent, t_arguments=t_arguments) \ if t_arguments != '' else '' - t_flags = ';\n{indent}'.format(indent=_indent).join('size_t {!s}'.format(c_name(flag.long or flag.short)) + t_flags = ';\n{indent}'.format(indent=_indent).join('\n{indent}/**\n{indent} * Value of option argument {comment}\n{indent} **/\n{indent}size_t {token!s}'.format(indent=_indent, comment=flag.long or flag.short, token=c_name(flag.long or flag.short)) for flag in flags) - t_flags = '\n{indent}/* options without arguments */\n{indent}{t_flags};'.format(indent=_indent, t_flags=t_flags) \ + t_flags = '\n\n{indent}{t_flags};'.format(indent=_indent, t_flags=t_flags) \ if t_flags != '' else '' - t_options = ';\n{indent}'.format(indent=_indent).join('char *{!s}'.format(c_name(opt.long or opt.short)) + t_options = ';\n{indent}'.format(indent=_indent).join('\n{indent}/**\n{indent} * Value of option argument {comment}\n{indent} **/\n{indent}char *{token!s}'.format(indent=_indent, comment=opt.long or opt.short, token=c_name(opt.long or opt.short)) for opt in options) - t_options = '\n{indent}/* options with arguments */\n{indent}{t_options};'.format(indent=_indent, - t_options=t_options) \ + t_options = '\n{indent}{t_options};'.format(indent=_indent, t_options=t_options) \ if t_options != '' else '' t_defaults = ', '.join(to_c(leaf.value) for leaf in leafs) t_defaults = re.sub(r'"(.*?)"', r'(char *) "\1"', t_defaults) @@ -634,8 +620,11 @@ def main(): indent=_indent * 2, t_if_argument='\n{indent}'.format(indent=_indent * 2).join(t_if_argument.splitlines()) ) if t_if_argument != '' else '' - t_if_flag = ''.join('\n{indent}'.format(indent=_indent * 2).join(c_if_flag(flag).splitlines()) - for flag in flags) + + t_if_flag = ''.join( + '\n{indent}'.format(indent=_indent * 2).join(c_if_flag(flag).splitlines()) + for flag in flags + ) t_if_option = ''.join( '\n{indent}'.format(indent=_indent * 2).join(c_if_option(opt).splitlines()) for opt in options @@ -657,6 +646,8 @@ def main(): doc_n = len(doc) template_out = Template(args['--template']).safe_substitute( + primary_command=primaryCommand, + prefix=primaryCommand.capitalize(), help_message='\n{indent}'.format(indent=_indent).join(to_initializer(doc).splitlines()), help_message_n=doc_n, usage_pattern='\n{indent}'.format(indent=_indent * 2).join(to_c(usage).splitlines()), @@ -675,6 +666,8 @@ def main(): ) template_header_out = Template(args['--template-header']).safe_substitute( + primary_command=primaryCommand, + prefix=primaryCommand.capitalize(), commands=t_commands, arguments=t_arguments, flags=t_flags,