def __init__(self, executable): # Filter colors if self.PROGNAME is None: self.PROGNAME = os.path.basename(executable) elif isinstance(self.PROGNAME, colors._style): self.PROGNAME = self.PROGNAME | os.path.basename(executable) elif colors.filter(self.PROGNAME) == "": self.PROGNAME = colors.extract( self.PROGNAME) | os.path.basename(executable) if self.DESCRIPTION is None: self.DESCRIPTION = getdoc(self) # Allow None for the colors self.COLOR_GROUPS = defaultdict( lambda: colors.do_nothing, dict() if type(self).COLOR_GROUPS is None else type(self).COLOR_GROUPS, ) self.COLOR_GROUP_TITLES = defaultdict( lambda: colors.do_nothing, self.COLOR_GROUPS if type(self).COLOR_GROUP_TITLES is None else type(self).COLOR_GROUP_TITLES, ) if type(self).COLOR_USAGE is None: self.COLOR_USAGE = colors.do_nothing self.executable = executable self._switches_by_name = {} self._switches_by_func = {} self._switches_by_envar = {} self._subcommands = {} for cls in reversed(type(self).mro()): for obj in cls.__dict__.values(): if isinstance(obj, Subcommand): name = colors.filter(obj.name) if name.startswith("-"): raise SubcommandError( T_("Sub-command names cannot start with '-'")) # it's okay for child classes to override sub-commands set by their parents self._subcommands[name] = obj continue swinfo = getattr(obj, "_switch_info", None) if not swinfo: continue for name in swinfo.names: if name in self._unbound_switches: continue if (name in self._switches_by_name and not self._switches_by_name[name].overridable): raise SwitchError( T_("Switch {name} already defined and is not overridable" ).format(name=name)) self._switches_by_name[name] = swinfo self._switches_by_func[swinfo.func] = swinfo if swinfo.envname: self._switches_by_envar[swinfo.envname] = swinfo
def __init__(self, executable): # Filter colors if self.PROGNAME is None: self.PROGNAME = os.path.basename(executable) elif isinstance(self.PROGNAME, colors._style): self.PROGNAME = self.PROGNAME | os.path.basename(executable) elif colors.filter(self.PROGNAME) == '': self.PROGNAME = colors.extract( self.PROGNAME) | os.path.basename(executable) if self.DESCRIPTION is None: self.DESCRIPTION = getdoc(self) # Allow None for the colors self.COLOR_GROUPS = defaultdict( lambda: colors.do_nothing, dict() if type(self).COLOR_GROUPS is None else type(self).COLOR_GROUPS) if type(self).COLOR_USAGE is None: self.COLOR_USAGE = colors.do_nothing self.executable = executable self._switches_by_name = {} self._switches_by_func = {} self._switches_by_envar = {} self._subcommands = {} for cls in reversed(type(self).mro()): for obj in cls.__dict__.values(): if isinstance(obj, Subcommand): name = colors.filter(obj.name) if name.startswith("-"): raise SubcommandError( T_("Sub-command names cannot start with '-'")) # it's okay for child classes to override sub-commands set by their parents self._subcommands[name] = obj continue swinfo = getattr(obj, "_switch_info", None) if not swinfo: continue for name in swinfo.names: if name in self._unbound_switches: continue if name in self._switches_by_name and not self._switches_by_name[name].overridable: raise SwitchError( T_("Switch {name} already defined and is not overridable" ).format(name=name)) self._switches_by_name[name] = swinfo self._switches_by_func[swinfo.func] = swinfo if swinfo.envname: self._switches_by_envar[swinfo.envname] = swinfo
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)))
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)))
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)))