def set_default_command(parser, func): """ Replacement for :meth:`ArgParser.set_default_command`. It does add the parser's arg variable for the unknown args as suppressed argument to the func """ def _get_option_from_var(var): return '--{0}'.format(var.replace('_', '-')) unknown_args_var = getattr(parser, 'unknown_args_varname', None) if unknown_args_var is not None: unknown_args_suppress_dict = \ {'help': argparse.SUPPRESS, 'option_strings': (_get_option_from_var(unknown_args_var),)} # prepare for checking whether unknown_args is in func signature inferred_args = list(argh.assembling._get_args_from_signature(func)) dests = OrderedDict() for argspec in inferred_args: dest = argh.assembling._get_parser_param_kwargs(parser, argspec)['dest'] dests[dest] = argspec if argh.assembling._get_dest(parser, unknown_args_suppress_dict) in dests: declared_args = getattr(func, ATTR_ARGS, []) declared_args.insert(0, unknown_args_suppress_dict) setattr(func, ATTR_ARGS, declared_args) argh.set_default_command(parser, func)
def set_default_command(parser, function): """ Sets default command (i.e. a function) for given parser. If `parser.description` is empty and the function has a docstring, it is used as the description. .. note:: An attempt to set default command to a parser which already has subparsers (e.g. added with :func:`~argh.assembling.add_commands`) results in a `RuntimeError`. .. note:: If there are both explicitly declared arguments (e.g. via :func:`~argh.decorators.arg`) and ones inferred from the function signature (e.g. via :func:`~argh.decorators.command`), declared ones will be merged into inferred ones. If an argument does not conform function signature, `AssemblingError` is raised. .. note:: If the parser was created with ``add_help=True`` (which is by default), option name ``-h`` is silently removed from any argument. """ if parser._subparsers: raise RuntimeError('Cannot set default command to a parser with ' 'existing subparsers') function = _fix_compat_issue29(function) spec = get_arg_spec(function) declared_args = getattr(function, ATTR_ARGS, []) inferred_args = list(_get_args_from_signature(function)) if inferred_args and declared_args: # We've got a mixture of declared and inferred arguments # a mapping of "dest" strings to argument declarations. # # * a "dest" string is a normalized form of argument name, i.e.: # # '-f', '--foo' → 'foo' # 'foo-bar' → 'foo_bar' # # * argument declaration is a dictionary representing an argument; # it is obtained either from _get_args_from_signature() or from # an @arg decorator (as is). # dests = OrderedDict() for argspec in inferred_args: dest = _get_parser_param_kwargs(parser, argspec)['dest'] dests[dest] = argspec for declared_kw in declared_args: # an argument is declared via decorator dest = _get_dest(parser, declared_kw) if dest in dests: # the argument is already known from function signature # # now make sure that this declared arg conforms to the function # signature and therefore only refines an inferred arg: # # @arg('my-foo') maps to func(my_foo) # @arg('--my-bar') maps to func(my_bar=...) # either both arguments are positional or both are optional decl_positional = _is_positional(declared_kw['option_strings']) infr_positional = _is_positional(dests[dest]['option_strings']) if decl_positional != infr_positional: kinds = {True: 'positional', False: 'optional'} raise AssemblingError( '{func}: argument "{dest}" declared as {kind_i} ' '(in function signature) and {kind_d} (via decorator)' .format( func=function.__name__, dest=dest, kind_i=kinds[infr_positional], kind_d=kinds[decl_positional], )) # merge explicit argument declaration into the inferred one # (e.g. `help=...`) dests[dest].update(**declared_kw) else: # the argument is not in function signature varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: # function accepts **kwargs; the argument goes into it dests[dest] = declared_kw else: # there's no way we can map the argument declaration # to function signature xs = (dests[x]['option_strings'] for x in dests) raise AssemblingError( '{func}: argument {flags} does not fit ' 'function signature: {sig}'.format( flags=', '.join(declared_kw['option_strings']), func=function.__name__, sig=', '.join('/'.join(x) for x in xs))) # pack the modified data back into a list inferred_args = dests.values() command_args = inferred_args or declared_args # add types, actions, etc. (e.g. default=3 implies type=int) command_args = [_guess(x) for x in command_args] for draft in command_args: draft = draft.copy() dest_or_opt_strings = draft.pop('option_strings') if parser.add_help and '-h' in dest_or_opt_strings: dest_or_opt_strings = [x for x in dest_or_opt_strings if x != '-h'] completer = draft.pop('completer', None) try: action = parser.add_argument(*dest_or_opt_strings, **draft) if COMPLETION_ENABLED and completer: action.completer = completer except Exception as e: raise type(e)('{func}: cannot add arg {args}: {msg}'.format( args='/'.join(dest_or_opt_strings), func=function.__name__, msg=e)) if function.__doc__ and not parser.description: parser.description = function.__doc__ parser.set_defaults(function=function)
def set_default_command(parser, function): """ Sets default command (i.e. a function) for given parser. If `parser.description` is empty and the function has a docstring, it is used as the description. .. note:: An attempt to set default command to a parser which already has subparsers (e.g. added with :func:`~argh.assembling.add_commands`) results in a `AssemblingError`. .. note:: If there are both explicitly declared arguments (e.g. via :func:`~argh.decorators.arg`) and ones inferred from the function signature (e.g. via :func:`~argh.decorators.command`), declared ones will be merged into inferred ones. If an argument does not conform function signature, `AssemblingError` is raised. .. note:: If the parser was created with ``add_help=True`` (which is by default), option name ``-h`` is silently removed from any argument. """ if parser._subparsers: _require_support_for_default_command_with_subparsers() spec = get_arg_spec(function) declared_args = getattr(function, ATTR_ARGS, []) inferred_args = list(_get_args_from_signature(function)) if inferred_args and declared_args: # We've got a mixture of declared and inferred arguments # a mapping of "dest" strings to argument declarations. # # * a "dest" string is a normalized form of argument name, i.e.: # # '-f', '--foo' → 'foo' # 'foo-bar' → 'foo_bar' # # * argument declaration is a dictionary representing an argument; # it is obtained either from _get_args_from_signature() or from # an @arg decorator (as is). # dests = OrderedDict() for argspec in inferred_args: dest = _get_parser_param_kwargs(parser, argspec)['dest'] dests[dest] = argspec for declared_kw in declared_args: # an argument is declared via decorator dest = _get_dest(parser, declared_kw) if dest in dests: # the argument is already known from function signature # # now make sure that this declared arg conforms to the function # signature and therefore only refines an inferred arg: # # @arg('my-foo') maps to func(my_foo) # @arg('--my-bar') maps to func(my_bar=...) # either both arguments are positional or both are optional decl_positional = _is_positional(declared_kw['option_strings']) infr_positional = _is_positional(dests[dest]['option_strings']) if decl_positional != infr_positional: kinds = {True: 'positional', False: 'optional'} raise AssemblingError( '{func}: argument "{dest}" declared as {kind_i} ' '(in function signature) and {kind_d} (via decorator)'. format( func=function.__name__, dest=dest, kind_i=kinds[infr_positional], kind_d=kinds[decl_positional], )) # merge explicit argument declaration into the inferred one # (e.g. `help=...`) dests[dest].update(**declared_kw) else: # the argument is not in function signature varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: # function accepts **kwargs; the argument goes into it dests[dest] = declared_kw else: # there's no way we can map the argument declaration # to function signature xs = (dests[x]['option_strings'] for x in dests) raise AssemblingError( '{func}: argument {flags} does not fit ' 'function signature: {sig}'.format( flags=', '.join(declared_kw['option_strings']), func=function.__name__, sig=', '.join('/'.join(x) for x in xs))) # pack the modified data back into a list inferred_args = dests.values() command_args = inferred_args or declared_args # add types, actions, etc. (e.g. default=3 implies type=int) command_args = [_guess(x) for x in command_args] for draft in command_args: draft = draft.copy() if 'help' not in draft: draft.update(help=DEFAULT_ARGUMENT_TEMPLATE) dest_or_opt_strings = draft.pop('option_strings') if parser.add_help and '-h' in dest_or_opt_strings: dest_or_opt_strings = [x for x in dest_or_opt_strings if x != '-h'] completer = draft.pop('completer', None) try: action = parser.add_argument(*dest_or_opt_strings, **draft) if COMPLETION_ENABLED and completer: action.completer = completer except Exception as e: raise type(e)('{func}: cannot add arg {args}: {msg}'.format( args='/'.join(dest_or_opt_strings), func=function.__name__, msg=e)) if function.__doc__ and not parser.description: parser.description = function.__doc__ parser.set_defaults(**{ DEST_FUNCTION: function, })
def set_default_command(parser, function): """ Sets default command (i.e. a function) for given parser. If `parser.description` is empty and the function has a docstring, it is used as the description. .. note:: An attempt to set default command to a parser which already has subparsers (e.g. added with :func:`~argh.assembling.add_commands`) results in a `RuntimeError`. .. note:: If there are both explicitly declared arguments (e.g. via :func:`~argh.decorators.arg`) and ones inferred from the function signature (e.g. via :func:`~argh.decorators.command`), declared ones will be merged into inferred ones. If an argument does not conform function signature, `AssemblingError` is raised. .. note:: If the parser was created with ``add_help=True`` (which is by default), option name ``-h`` is silently removed from any argument. """ if parser._subparsers: raise RuntimeError('Cannot set default command to a parser with ' 'existing subparsers') spec = get_arg_spec(function) declared_args = getattr(function, ATTR_ARGS, []) inferred_args = list(_get_args_from_signature(function)) toggleables = getattr(function, ATTR_TOGGLEABLES, []) if inferred_args and declared_args: # We've got a mixture of declared and inferred arguments # a mapping of "dest" strings to argument declarations. # # * a "dest" string is a normalized form of argument name, i.e.: # # '-f', '--foo' → 'foo' # 'foo-bar' → 'foo_bar' # # * argument declaration is a dictionary representing an argument; # it is obtained either from _get_args_from_signature() or from # an @arg decorator (as is). # dests = OrderedDict() for argspec in inferred_args: dest = _get_parser_param_kwargs(parser, argspec)['dest'] dests[dest] = argspec for declared_kw in declared_args: # an argument is declared via decorator dest = _get_dest(parser, declared_kw) if dest in dests: # the argument is already known from function signature # # now make sure that this declared arg conforms to the function # signature and therefore only refines an inferred arg: # # @arg('my-foo') maps to func(my_foo) # @arg('--my-bar') maps to func(my_bar=...) # either both arguments are positional or both are optional decl_positional = _is_positional(declared_kw['option_strings']) infr_positional = _is_positional(dests[dest]['option_strings']) if decl_positional != infr_positional: kinds = {True: 'positional', False: 'optional'} raise AssemblingError( '{func}: argument "{dest}" declared as {kind_i} ' '(in function signature) and {kind_d} (via decorator)' .format( func=function.__name__, dest=dest, kind_i=kinds[infr_positional], kind_d=kinds[decl_positional], )) # merge explicit argument declaration into the inferred one # (e.g. `help=...`) dests[dest].update(**declared_kw) else: # the argument is not in function signature varkw = getattr(spec, 'varkw', getattr(spec, 'keywords', [])) if varkw: # function accepts **kwargs; the argument goes into it dests[dest] = declared_kw else: # there's no way we can map the argument declaration # to function signature xs = (dests[x]['option_strings'] for x in dests) raise AssemblingError( '{func}: argument {flags} does not fit ' 'function signature: {sig}'.format( flags=', '.join(declared_kw['option_strings']), func=function.__name__, sig=', '.join('/'.join(x) for x in xs))) # pack the modified data back into a list inferred_args = dests.values() opt_string_togmap = {} for toggleable, inv_prefix in [(t, i) for t,i in toggleables]: if toggleable.find('_') >= 0: raise AssemblingError("Toggleable destinations cannot contain underscores") #for each toggleable, verify there exists a matching destination matched_dest = False for arg in inferred_args: for opt_str in arg['option_strings']: if opt_str == toggleable: matched_dest = True opt_string_togmap[opt_str] = (toggleable[2:], inv_prefix) if not matched_dest: raise AssemblingError("Unrecognized destination for toggleable: {}".format(toggleable)) command_args = inferred_args or declared_args # add types, actions, etc. (e.g. default=3 implies type=int) command_args = [_guess(x) for x in command_args] for draft in command_args: draft = draft.copy() if 'help' not in draft: draft.update(help=DEFAULT_ARGUMENT_TEMPLATE) dest_or_opt_strings = draft.pop('option_strings') if parser.add_help and '-h' in dest_or_opt_strings: dest_or_opt_strings = [x for x in dest_or_opt_strings if x != '-h'] completer = draft.pop('completer', None) try: if dest_or_opt_strings[-1] in opt_string_togmap: # if we're working with a toggleable list of opt_strings, make mutually exclusive # and set to opposite defaults & storing actions toggleable, inv_prefix = opt_string_togmap[dest_or_opt_strings[-1]] group = parser.add_mutually_exclusive_group() draft['action'] = 'store_true' draft['dest'] = toggleable.replace('-', '_') # XXX unsure about desired behavior in autocompletion of toggleables case action = group.add_argument(*dest_or_opt_strings, **draft) not_dest_or_opt_strings = tuple(map(lambda x : '--{}-{}'.format(inv_prefix, x.lstrip('-')), dest_or_opt_strings)) draft['action'] = 'store_false' group.add_argument(*not_dest_or_opt_strings, **draft) else: action = parser.add_argument(*dest_or_opt_strings, **draft) if COMPLETION_ENABLED and completer: action.completer = completer except Exception as e: raise type(e)('{func}: cannot add arg {args}: {msg}'.format( args='/'.join(dest_or_opt_strings), func=function.__name__, msg=e)) if function.__doc__ and not parser.description: parser.description = function.__doc__ parser.set_defaults(function=function)