Source code for wield.declarative.argparse.oo_argparse

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: © 2021 Massachusetts Institute of Technology.
# SPDX-FileCopyrightText: © 2021 Lee McCuller <mcculler@caltech.edu>
# NOTICE: authors should document their contributions in concisely in NOTICE
# with details inline in source files, comments, and docstrings.
"""
TODO, needs big documentation
"""

import sys
import argparse
import collections

from ..overridable_object import OverridableObject
from ..utilities import SuperBase, NOARG
from ..properties import mproperty


TYPES = dict(
    command="command",
    group="group",
    argument="argument",
)


[docs] class OOArgParse( OverridableObject, SuperBase, object, ): @classmethod def __cls_argparse__( cls, args=None, _sys_exit=True, __usage_prog__=None, base_kwargs=dict(), no_commands=False, **exkwargs ): arguments = dict() commands = dict() groups = dict() for attr_name in dir(cls): attr_obj = getattr(cls, attr_name) arganno = getattr(attr_obj, "_argparse", None) if arganno: if arganno.type == TYPES['argument']: # mask the argument if it was specified in an extra kwarg if attr_name not in exkwargs: arguments[attr_name] = arganno elif arganno.type == TYPES['group']: groups[attr_name] = arganno elif arganno.type == TYPES['command']: commands[attr_name] = arganno description = getattr(cls, "__arg_desc__", cls.__doc__) ap = argparse.ArgumentParser( prog=__usage_prog__, description=description, add_help=False ) ap.add_argument( "-h", "--help", "--halp!", dest="help", action="store_true", help="Show this help screen" # help = argparse.SUPPRESS, ) # build groups glist = collections.deque(sorted([(v.order, k, v) for k, v in groups.items()])) gengroups = dict() gengroups[None] = ap while glist: order, name, gbunch = glist.pop() useparse = gengroups.get(gbunch.group, None) if useparse is None: if gbunch.group not in groups: raise RuntimeError("Group {0} not defined!".format(gbunch.group)) # reinsert in back glist.appendleft((name, gbunch)) continue group = gbunch.func(useparse) gengroups[name] = group default_cmd = getattr(cls, "__arg_default__", None) commands_byname = dict() for name, cbunch in commands.items(): commands_byname[cbunch.cmd_name] = cbunch cmdlist = list( k for o, k in sorted((v.order, k) for k, v in commands_byname.items()) ) if not no_commands: if default_cmd is None: ap.add_argument( "__command__", metavar="COMMAND", choices=cmdlist, help=argparse.SUPPRESS, ) else: if commands_byname: ap.add_argument( "__command__", metavar="COMMAND", choices=cmdlist + [""], default=None, nargs="?", help=argparse.SUPPRESS, ) if len(commands_byname) > 0: ap.add_argument( "__args__", metavar="args", nargs=argparse.REMAINDER, help=argparse.SUPPRESS, ) argdo = dict() for order, argname, argbunch in sorted( [(v.order, k, v) for k, v in arguments.items()] ): useparse = gengroups.get(argbunch.group, None) argdo[argname] = argbunch.func(useparse) if args is None: args = sys.argv[1:] print_help = False # TODO fix -h handling requiring otherwise perfect other arguments if "-h" in args or "--help" in args or "--halp!" in args: try: class BooBoo(Exception): pass def error(message): raise BooBoo(message) ap.error = error args_parsed = ap.parse_args(args) print_help = args_parsed.help except BooBoo: print_help = True else: args_parsed = ap.parse_args(args) print_help = args_parsed.help if print_help: spp = ap.add_subparsers( dest="command", help="(May have subarguments, use -h after them for command specific help)", ) if default_cmd is not None: if default_cmd.__doc__ is not None: doc = default_cmd.__doc__ + " [Default, no arguments]" else: doc = "Default Action [command only]" spp.add_parser( '""', help=doc, ) if not no_commands: for name, cbunch in commands.items(): commands_byname[cbunch.cmd_name] = cbunch if cbunch.takes_arguments: extra = " [takes arguments]" else: extra = " [command only]" spp.add_parser( cbunch.cmd_name, help=cbunch.description + extra, ) ap.print_help() sys.exit(0) kwargs = dict(base_kwargs) for argname, argbunch in arguments.items(): val = getattr(args_parsed, argname) if val is not NOARG: kwargs[argbunch.name_inject] = val if commands_byname and not no_commands: command = getattr(args_parsed, "__command__", NOARG) if command is not None: command_idx_in_args = args.index(command) call_args = args[:command_idx_in_args] else: call_args = args[:] else: command = None call_args = args args_sub = getattr(args_parsed, "__args__", NOARG) # add in the extra kwargs that were given kwargs.update(exkwargs) kwargs["__cls_argparse_args__"] = tuple(call_args) kwargs["__cls_argparse_cmd__"] = command kwargs["__cls_argparse_cmd_args__"] = ( tuple(args_sub) if args_sub is not NOARG else None ) obj = cls(**kwargs) if no_commands: return obj if command is None: ret = obj.__arg_default__() else: cbunch = commands_byname[command] call = getattr(obj, cbunch.run_name) # TODO, check about taking additional arguments if not cbunch.takes_arguments and args_sub: raise RuntimeError("Final Command does not take additional arguments") ret = call(args_sub) if _sys_exit: sys.exit(ret) return ret @mproperty def __cls_argparse_args__(self, args=None): return args @mproperty def __cls_argparse_cmd__(self, args=None): return args @mproperty def __cls_argparse_cmd_args__(self, args=None): return args