Example #1
0
 def deco(func):
     if argname is None:
         argspec = six.getfullargspec(func).args
         if len(argspec) == 2:
             argname2 = argspec[1]
         else:
             argname2 = _("VALUE")
     else:
         argname2 = argname
     help2 = getdoc(func) if help is None else help
     if not help2:
         help2 = str(func)
     func._switch_info = SwitchInfo(
         names=names,
         envname=envname,
         argtype=argtype,
         list=list,
         func=func,
         mandatory=mandatory,
         overridable=overridable,
         group=group,
         requires=requires,
         excludes=excludes,
         argname=argname2,
         help=help2)
     return func
Example #2
0
 def deco(func):
     if argname is None:
         argspec = six.getfullargspec(func).args
         if len(argspec) == 2:
             argname2 = argspec[1]
         else:
             argname2 = _("VALUE")
     else:
         argname2 = argname
     help2 = getdoc(func) if help is None else help
     if not help2:
         help2 = str(func)
     func._switch_info = SwitchInfo(
         names=names,
         envname=envname,
         argtype=argtype,
         list=list,
         func=func,
         mandatory=mandatory,
         overridable=overridable,
         group=group,
         requires=requires,
         excludes=excludes,
         argname=argname2,
         help=help2)
     return func
Example #3
0
    def __call__(self, function):
        m = six.getfullargspec(function)
        args_names = list(m.args[1:])

        positional = [None] * len(args_names)
        varargs = None

        for i in range(min(len(positional), len(self.args))):
            positional[i] = self.args[i]

        if len(args_names) + 1 == len(self.args):
            varargs = self.args[-1]

        # All args are positional, so convert kargs to positional
        for item in self.kargs:
            if item == m.varargs:
                varargs = self.kargs[item]
            else:
                positional[args_names.index(item)] = self.kargs[item]

        function.positional = positional
        function.positional_varargs = varargs
        return function
Example #4
0
 def __call__(self, function):
     m = six.getfullargspec(function)
     args_names = list(m.args[1:])
         
     positional = [None]*len(args_names)
     varargs = None
     
     for i in range(min(len(positional),len(self.args))):
         positional[i] = self.args[i]
     
     if len(args_names) + 1 == len(self.args):
         varargs = self.args[-1]
     
      # All args are positional, so convert kargs to positional
     for item in self.kargs:
         if item == m.varargs:
             varargs = self.kargs[item]
         else:
             positional[args_names.index(item)] = self.kargs[item]
         
     function.positional = positional
     function.positional_varargs = varargs
     return function
Example #5
0
    def help(self):  # @ReservedAssignment
        """Prints this help message and quits"""
        if self._get_prog_version():
            self.version()
            print("")
        if self.DESCRIPTION:
            print(self.DESCRIPTION.strip() + '\n')

        def split_indentation(s):
            """Identifies the initial indentation (all spaces) of the string and returns the indentation as well
            as the remainder of the line.
            """
            i = 0
            while i < len(s) and s[i] == ' ':
                i += 1
            return s[:i], s[i:]

        def paragraphs(text):
            """Yields each paragraph of text along with its initial and subsequent indentations to be used by
            textwrap.TextWrapper.

            Identifies list items from their first non-space character being one of bullets '-', '*', and '/'.
            However, bullet '/' is invisible and is removed from the list item.

            :param text: The text to separate into paragraphs
            """

            paragraph = None
            initial_indent = ""
            subsequent_indent = ""

            def current():
                """Yields the current result if present.
                """
                if paragraph:
                    yield paragraph, initial_indent, subsequent_indent

            for part in text.lstrip("\n").split("\n"):
                indent, line = split_indentation(part)

                if len(line) == 0:
                    # Starting a new paragraph
                    for item in current():
                        yield item
                    yield "", "", ""

                    paragraph = None
                    initial_indent = ""
                    subsequent_indent = ""
                else:
                    # Adding to current paragraph
                    def is_list_item(line):
                        """Returns true if the first element of 'line' is a bullet character.
                        """
                        bullets = ['-', '*', '/']
                        return line[0] in bullets

                    def has_invisible_bullet(line):
                        """Returns true if the first element of 'line' is the invisible bullet ('/').
                        """
                        return line[0] == '/'

                    if is_list_item(line):
                        # Done with current paragraph
                        for item in current():
                            yield item

                        if has_invisible_bullet(line):
                            line = line[1:]

                        paragraph = line
                        initial_indent = indent

                        # Calculate extra indentation for subsequent lines of this list item
                        i = 1
                        while i < len(line) and line[i] == ' ':
                            i += 1
                        subsequent_indent = indent + " " * i
                    else:
                        if not paragraph:
                            # Start a new paragraph
                            paragraph = line
                            initial_indent = indent
                            subsequent_indent = indent
                        else:
                            # Add to current paragraph
                            paragraph = paragraph + ' ' + line

            for item in current():
                yield item

        def wrapped_paragraphs(text, width):
            """Yields each line of each paragraph of text after wrapping them on 'width' number of columns.

            :param text: The text to yield wrapped lines of
            :param width: The width of the wrapped output
            """
            if not text:
                return

            width = max(width, 1)

            for paragraph, initial_indent, subsequent_indent in paragraphs(
                    text):
                wrapper = TextWrapper(
                    width,
                    initial_indent=initial_indent,
                    subsequent_indent=subsequent_indent)
                w = wrapper.wrap(paragraph)
                for line in w:
                    yield line
                if len(w) == 0:
                    yield ""

        cols, _ = get_terminal_size()
        for line in wrapped_paragraphs(self.DESCRIPTION_MORE, cols):
            print(line)

        m = six.getfullargspec(self.main)
        tailargs = m.args[1:]  # skip self
        if m.defaults:
            for i, d in enumerate(reversed(m.defaults)):
                tailargs[-i - 1] = "[{0}={1}]".format(tailargs[-i - 1], d)
        if m.varargs:
            tailargs.append("{0}...".format(m.varargs, ))
        tailargs = " ".join(tailargs)

        with self.COLOR_USAGE:
            print(T_("Usage:"))
            if not self.USAGE:
                if self._subcommands:
                    self.USAGE = T_(
                        "    {progname} [SWITCHES] [SUBCOMMAND [SWITCHES]] {tailargs}\n"
                    )
                else:
                    self.USAGE = T_("    {progname} [SWITCHES] {tailargs}\n")
            print(self.USAGE.format(
                progname=colors.filter(self.PROGNAME), tailargs=tailargs))

        by_groups = {}
        for si in self._switches_by_func.values():
            if si.group not in by_groups:
                by_groups[si.group] = []
            by_groups[si.group].append(si)

        def switchs(by_groups, show_groups):
            for grp, swinfos in sorted(
                    by_groups.items(), key=lambda item: item[0]):
                if show_groups:
                    lgrp = T_(grp) if grp in _switch_groups else grp
                    print(self.COLOR_GROUPS[grp] | lgrp + ':')

                for si in sorted(swinfos, key=lambda si: si.names):
                    swnames = ", ".join(("-" if len(n) == 1 else "--") + n
                                        for n in si.names
                                        if n in self._switches_by_name
                                        and self._switches_by_name[n] == si)
                    if si.argtype:
                        if hasattr(si.argtype, '__name__'):
                            typename = si.argtype.__name__
                        else:
                            typename = str(si.argtype)
                        argtype = " {0}:{1}".format(si.argname.upper(),
                                                    typename)
                    else:
                        argtype = ""
                    prefix = swnames + argtype
                    yield si, prefix, self.COLOR_GROUPS[grp]

                if show_groups:
                    print("")

        sw_width = max(
            len(prefix) for si, prefix, color in switchs(by_groups, False)) + 4
        description_indent = "    {0}{1}{2}"
        wrapper = TextWrapper(width=max(cols - min(sw_width, 60), 50) - 6)
        indentation = "\n" + " " * (cols - wrapper.width)

        for switch_info, prefix, color in switchs(by_groups, True):
            help = switch_info.help  # @ReservedAssignment
            if switch_info.list:
                help += T_("; may be given multiple times")
            if switch_info.mandatory:
                help += T_("; required")
            if switch_info.requires:
                help += T_("; requires {0}").format(", ".join(
                    (("-" if len(switch) == 1 else "--") + switch)
                    for switch in switch_info.requires))
            if switch_info.excludes:
                help += T_("; excludes {0}").format(", ".join(
                    (("-" if len(switch) == 1 else "--") + switch)
                    for switch in switch_info.excludes))

            msg = indentation.join(
                wrapper.wrap(" ".join(l.strip() for l in help.splitlines())))

            if len(prefix) + wrapper.width >= cols:
                padding = indentation
            else:
                padding = " " * max(cols - wrapper.width - len(prefix) - 4, 1)
            print(description_indent.format(color | prefix, padding,
                                            color | msg))

        if self._subcommands:
            gc = self.COLOR_GROUPS["Subcommands"]
            print(gc | T_("Sub-commands:"))
            for name, subcls in sorted(self._subcommands.items()):
                with gc:
                    subapp = subcls.get()
                    doc = subapp.DESCRIPTION if subapp.DESCRIPTION else getdoc(
                        subapp)
                    if self.SUBCOMMAND_HELPMSG:
                        help = doc + "; " if doc else ""  # @ReservedAssignment
                        help += self.SUBCOMMAND_HELPMSG.format(
                            parent=self.PROGNAME, sub=name)
                    else:
                        help = doc if doc else ""  # @ReservedAssignment

                    msg = indentation.join(
                        wrapper.wrap(" ".join(
                            l.strip() for l in help.splitlines())))

                    if len(name) + wrapper.width >= cols:
                        padding = indentation
                    else:
                        padding = " " * max(
                            cols - wrapper.width - len(name) - 4, 1)
                    if colors.contains_colors(subcls.name):
                        bodycolor = colors.extract(subcls.name)
                    else:
                        bodycolor = gc

                    print(description_indent.format(
                        subcls.name, padding, bodycolor | colors.filter(msg)))
Example #6
0
    def _validate_args(self, swfuncs, tailargs):
        if six.get_method_function(self.help) in swfuncs:
            raise ShowHelp()
        if six.get_method_function(self.helpall) in swfuncs:
            raise ShowHelpAll()
        if six.get_method_function(self.version) in swfuncs:
            raise ShowVersion()

        requirements = {}
        exclusions = {}
        for swinfo in self._switches_by_func.values():
            if swinfo.mandatory and not swinfo.func in swfuncs:
                raise MissingMandatorySwitch(
                    T_("Switch {0} is mandatory").format("/".join(
                        ("-" if len(n) == 1 else "--") + n
                        for n in swinfo.names)))
            requirements[swinfo.func] = set(
                self._switches_by_name[req] for req in swinfo.requires)
            exclusions[swinfo.func] = set(
                self._switches_by_name[exc] for exc in swinfo.excludes)

        # TODO: compute topological order

        gotten = set(swfuncs.keys())
        for func in gotten:
            missing = set(f.func for f in requirements[func]) - gotten
            if missing:
                raise SwitchCombinationError(
                    T_("Given {0}, the following are missing {1}").format(
                        swfuncs[func].swname,
                        [self._switches_by_func[f].names[0] for f in missing]))
            invalid = set(f.func for f in exclusions[func]) & gotten
            if invalid:
                raise SwitchCombinationError(
                    T_("Given {0}, the following are invalid {1}").format(
                        swfuncs[func].swname,
                        [swfuncs[f].swname for f in invalid]))

        m = six.getfullargspec(self.main)
        max_args = six.MAXSIZE if m.varargs else len(m.args) - 1
        min_args = len(m.args) - 1 - (len(m.defaults) if m.defaults else 0)
        if len(tailargs) < min_args:
            raise PositionalArgumentsError(
                ngettext(
                    "Expected at least {0} positional argument, got {1}",
                    "Expected at least {0} positional arguments, got {1}",
                    min_args).format(min_args, tailargs))
        elif len(tailargs) > max_args:
            raise PositionalArgumentsError(
                ngettext("Expected at most {0} positional argument, got {1}",
                         "Expected at most {0} positional arguments, got {1}",
                         max_args).format(max_args, tailargs))

        # Positional arguement validataion
        if hasattr(self.main, 'positional'):
            tailargs = self._positional_validate(
                tailargs, self.main.positional, self.main.positional_varargs,
                m.args[1:], m.varargs)

        elif hasattr(m, 'annotations'):
            args_names = list(m.args[1:])
            positional = [None] * len(args_names)
            varargs = None

            # All args are positional, so convert kargs to positional
            for item in m.annotations:
                if item == m.varargs:
                    varargs = m.annotations[item]
                elif item != 'return':
                    positional[args_names.index(item)] = m.annotations[item]

            tailargs = self._positional_validate(tailargs, positional, varargs,
                                                 m.args[1:], m.varargs)

        ordered = [(f, a)
                   for _, f, a in sorted([(sf.index, f, sf.val)
                                          for f, sf in swfuncs.items()])]
        return ordered, tailargs
Example #7
0
    def help(self):  # @ReservedAssignment
        """Prints this help message and quits"""
        if self._get_prog_version():
            self.version()
            print("")
        if self.DESCRIPTION:
            print(self.DESCRIPTION.strip() + '\n')

        m = six.getfullargspec(self.main)
        tailargs = m.args[1:]  # skip self
        if m.defaults:
            for i, d in enumerate(reversed(m.defaults)):
                tailargs[-i - 1] = "[{0}={1}]".format(tailargs[-i - 1], d)
        if m.varargs:
            tailargs.append("{0}...".format(m.varargs, ))
        tailargs = " ".join(tailargs)

        with self.COLOR_USAGE:
            print(T_("Usage:"))
            if not self.USAGE:
                if self._subcommands:
                    self.USAGE = T_(
                        "    {progname} [SWITCHES] [SUBCOMMAND [SWITCHES]] {tailargs}\n"
                    )
                else:
                    self.USAGE = T_("    {progname} [SWITCHES] {tailargs}\n")
            print(
                self.USAGE.format(progname=colors.filter(self.PROGNAME),
                                  tailargs=tailargs))

        by_groups = {}
        for si in self._switches_by_func.values():
            if si.group not in by_groups:
                by_groups[si.group] = []
            by_groups[si.group].append(si)

        def switchs(by_groups, show_groups):
            for grp, swinfos in sorted(by_groups.items(),
                                       key=lambda item: item[0]):
                if show_groups:
                    lgrp = T_(grp) if grp in _switch_groups else grp
                    print(self.COLOR_GROUPS[grp] | lgrp)

                for si in sorted(swinfos, key=lambda si: si.names):
                    swnames = ", ".join(("-" if len(n) == 1 else "--") + n
                                        for n in si.names
                                        if n in self._switches_by_name
                                        and self._switches_by_name[n] == si)
                    if si.argtype:
                        if hasattr(si.argtype, '__name__'):
                            typename = si.argtype.__name__
                        else:
                            typename = str(si.argtype)
                        argtype = " {0}:{1}".format(si.argname.upper(),
                                                    typename)
                    else:
                        argtype = ""
                    prefix = swnames + argtype
                    yield si, prefix, self.COLOR_GROUPS[grp]

                if show_groups:
                    print("")

        sw_width = max(
            len(prefix) for si, prefix, color in switchs(by_groups, False)) + 4
        cols, _ = get_terminal_size()
        description_indent = "    {0}{1}{2}"
        wrapper = TextWrapper(width=max(cols - min(sw_width, 60), 50) - 6)
        indentation = "\n" + " " * (cols - wrapper.width)

        for switch_info, prefix, color in switchs(by_groups, True):
            help = switch_info.help  # @ReservedAssignment
            if switch_info.list:
                help += T_("; may be given multiple times")
            if switch_info.mandatory:
                help += T_("; required")
            if switch_info.requires:
                help += T_("; requires {0}").format(", ".join(
                    (("-" if len(switch) == 1 else "--") + switch)
                    for switch in switch_info.requires))
            if switch_info.excludes:
                help += T_("; excludes {0}").format(", ".join(
                    (("-" if len(switch) == 1 else "--") + switch)
                    for switch in switch_info.excludes))

            msg = indentation.join(
                wrapper.wrap(" ".join(l.strip() for l in help.splitlines())))

            if len(prefix) + wrapper.width >= cols:
                padding = indentation
            else:
                padding = " " * max(cols - wrapper.width - len(prefix) - 4, 1)
            print(
                description_indent.format(color | prefix, padding,
                                          color | msg))

        if self._subcommands:
            gc = self.COLOR_GROUPS["Subcommands"]
            print(gc | T_("Subcommands:"))
            for name, subcls in sorted(self._subcommands.items()):
                with gc:
                    subapp = subcls.get()
                    doc = subapp.DESCRIPTION if subapp.DESCRIPTION else getdoc(
                        subapp)
                    if self.SUBCOMMAND_HELPMSG:
                        help = doc + "; " if doc else ""  # @ReservedAssignment
                        help += self.SUBCOMMAND_HELPMSG.format(
                            parent=self.PROGNAME, sub=name)
                    else:
                        help = doc if doc else ""  # @ReservedAssignment

                    msg = indentation.join(
                        wrapper.wrap(" ".join(l.strip()
                                              for l in help.splitlines())))

                    if len(name) + wrapper.width >= cols:
                        padding = indentation
                    else:
                        padding = " " * max(
                            cols - wrapper.width - len(name) - 4, 1)
                    if colors.contains_colors(subcls.name):
                        bodycolor = colors.extract(subcls.name)
                    else:
                        bodycolor = gc

                    print(
                        description_indent.format(
                            subcls.name, padding,
                            bodycolor | colors.filter(msg)))
Example #8
0
    def help(self):  # @ReservedAssignment
        """Prints this help message and quits"""
        if self._get_prog_version():
            self.version()
            print("")
        if self.DESCRIPTION:
            print(self.DESCRIPTION.strip() + '\n')

        m = six.getfullargspec(self.main)
        tailargs = m.args[1:]  # skip self
        if m.defaults:
            for i, d in enumerate(reversed(m.defaults)):
                tailargs[-i - 1] = "[%s=%r]" % (tailargs[-i - 1], d)
        if m.varargs:
            tailargs.append("%s..." % (m.varargs,))
        tailargs = " ".join(tailargs)

        with self.COLOR_USAGE:
            print("Usage:")
            if not self.USAGE:
                if self._subcommands:
                    self.USAGE = "    %(progname)s [SWITCHES] [SUBCOMMAND [SWITCHES]] %(tailargs)s\n"
                else:
                    self.USAGE = "    %(progname)s [SWITCHES] %(tailargs)s\n"
            print(self.USAGE % {"progname": colors.filter(self.PROGNAME), "tailargs": tailargs})

        by_groups = {}
        for si in self._switches_by_func.values():
            if si.group not in by_groups:
                by_groups[si.group] = []
            by_groups[si.group].append(si)

        def switchs(by_groups, show_groups):
            for grp, swinfos in sorted(by_groups.items(), key = lambda item: item[0]):
                if show_groups:
                    print(self.COLOR_GROUPS[grp] | grp)

                for si in sorted(swinfos, key = lambda si: si.names):
                    swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names
                        if n in self._switches_by_name and self._switches_by_name[n] == si)
                    if si.argtype:
                        if isinstance(si.argtype, type):
                            typename = si.argtype.__name__
                        else:
                            typename = str(si.argtype)
                        argtype = " %s:%s" % (si.argname.upper(), typename)
                    else:
                        argtype = ""
                    prefix = swnames + argtype
                    yield si, prefix, self.COLOR_GROUPS[grp]

                if show_groups:
                    print("")

        sw_width = max(len(prefix) for si, prefix, color in switchs(by_groups, False)) + 4
        cols, _ = get_terminal_size()
        description_indent = "    %s%s%s"
        wrapper = TextWrapper(width = max(cols - min(sw_width, 60), 50) - 6)
        indentation = "\n" + " " * (cols - wrapper.width)

        for si, prefix, color in switchs(by_groups, True):
            help = si.help  # @ReservedAssignment
            if si.list:
                help += "; may be given multiple times"
            if si.mandatory:
                help += "; required"
            if si.requires:
                help += "; requires %s" % (", ".join((("-" if len(s) == 1 else "--") + s) for s in si.requires))
            if si.excludes:
                help += "; excludes %s" % (", ".join((("-" if len(s) == 1 else "--") + s) for s in si.excludes))

            msg = indentation.join(wrapper.wrap(" ".join(l.strip() for l in help.splitlines())))

            if len(prefix) + wrapper.width >= cols:
                padding = indentation
            else:
                padding = " " * max(cols - wrapper.width - len(prefix) - 4, 1)
            print(description_indent % (color | prefix, padding, color | msg))

        if self._subcommands:
            gc = self.COLOR_GROUPS["Subcommands"]
            print(gc | "Subcommands:")
            for name, subcls in sorted(self._subcommands.items()):
                with gc:
                    subapp = subcls.get()
                    doc = subapp.DESCRIPTION if subapp.DESCRIPTION else getdoc(subapp)
                    if self.SUBCOMMAND_HELPMSG:
                        help = doc + "; " if doc else ""  # @ReservedAssignment
                        help += self.SUBCOMMAND_HELPMSG.format(parent=self.PROGNAME, sub=name)
                    else:
                        help = doc if doc else "" # @ReservedAssignment

                    msg = indentation.join(wrapper.wrap(" ".join(l.strip() for l in help.splitlines())))

                    if len(name) + wrapper.width >= cols:
                        padding = indentation
                    else:
                        padding = " " * max(cols - wrapper.width - len(name) - 4, 1)
                    if colors.contains_colors(subcls.name):
                        bodycolor = colors.extract(subcls.name)
                    else:
                        bodycolor = gc

                    print(description_indent
                            % (subcls.name, padding,
                               bodycolor | colors.filter(msg)))