From be8a5268a8dfa8613443a8c08c4e79be30d2dd5c Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Wed, 19 Jan 2022 15:13:55 +0100 Subject: [PATCH] Replace unmaintained cmdln.py with a custom code Cmdln.py is unmaintained for years and uses deprecated optparse. Let's replace it with a simpler custom code. Also remove code that generates man page, we'll replace it with a 3rd party tool. --- osc/cmdln.py | 1710 +++++--------------------------------------- osc/commandline.py | 766 ++++++++------------ setup.py | 61 -- 3 files changed, 488 insertions(+), 2049 deletions(-) diff --git a/osc/cmdln.py b/osc/cmdln.py index 3a0904b7..fafc3013 100644 --- a/osc/cmdln.py +++ b/osc/cmdln.py @@ -1,126 +1,34 @@ -# Copyright (c) 2002-2005 ActiveState Corp. -# License: MIT (see LICENSE.txt for license details) -# Author: Trent Mick (TrentM@ActiveState.com) -# Home: http://trentm.com/projects/cmdln/ - - -"""An improvement on Python's standard cmd.py module. - -As with cmd.py, this module provides "a simple framework for writing -line-oriented command intepreters." This module provides a 'RawCmdln' -class that fixes some design flaws in cmd.Cmd, making it more scalable -and nicer to use for good 'cvs'- or 'svn'-style command line interfaces -or simple shells. And it provides a 'Cmdln' class that add -optparse-based option processing. Basically you use it like this: - - import cmdln - - class MySVN(cmdln.Cmdln): - name = "svn" - - @cmdln.alias('stat', 'st') - @cmdln.option('-v', '--verbose', action='store_true' - help='print verbose information') - def do_status(self, subcmd, opts, *paths): - print "handle 'svn status' command" - - #... - - if __name__ == "__main__": - shell = MySVN() - retval = shell.main() - sys.exit(retval) - -See the README.txt or for more -details. +""" +A modern, lightweight alternative to cmdln.py from https://github.com/trentm/cmdln """ -__revision__ = "$Id: cmdln.py 1666 2007-05-09 03:13:03Z trentm $" -__version_info__ = (1, 0, 0) -__version__ = '.'.join(map(str, __version_info__)) -import os -import re -import cmd -import optparse +import argparse import sys -import time -from pprint import pprint -from datetime import datetime -def introspect_handler(handler): - defaults = handler.__defaults__ - if not defaults: - defaults = [] - else: - defaults = list(handler.__defaults__) - return \ - defaults, \ - handler.__code__.co_argcount, \ - handler.__code__.co_varnames, \ - handler.__code__.co_flags, \ - handler.__func__ +def option(*args, **kwargs): + """ + Decorator to add an option to the optparser argument of a Cmdln subcommand. + Example: + class MyShell(cmdln.Cmdln): + @cmdln.option("-f", "--force", help="force removal") + def do_remove(self, subcmd, opts, *args): + #... + """ + def decorate(f): + if not hasattr(f, "options"): + f.options = [] + new_args = [i for i in args if i] + f.options.append((new_args, kwargs)) + return f + return decorate -#---- globals - -LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3) - -# An unspecified optional argument when None is a meaningful value. -_NOT_SPECIFIED = ("Not", "Specified") - -# Pattern to match a TypeError message from a call that -# failed because of incorrect number of arguments (see -# Python/getargs.c). -_INCORRECT_NUM_ARGS_RE = re.compile( - r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))") - -_INCORRECT_NUM_ARGS_RE_PY3 = re.compile( - r"(missing\s+\d+.*)") - -# Static bits of man page -MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands" -.SH NAME -%(name)s \- Program to do useful things. -.SH SYNOPSIS -.B %(name)s -[\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...] -.br -.B %(name)s -\fIhelp SUBCOMMAND\fR -.SH DESCRIPTION -""" -MAN_COMMANDS_HEADER = r""" -.SS COMMANDS -""" -MAN_OPTIONS_HEADER = r""" -.SS GLOBAL OPTIONS -""" -MAN_FOOTER = r""" -.SH AUTHOR -This man page is automatically generated. -""" - -#---- exceptions - -class CmdlnError(Exception): - """A cmdln.py usage error.""" - def __init__(self, msg): - self.msg = msg - def __str__(self): - return self.msg - -class CmdlnUserError(Exception): - """An error by a user of a cmdln-based tool/shell.""" - pass - - - -#---- public methods and classes def alias(*aliases): - """Decorator to add aliases for Cmdln.do_* command handlers. + """ + Decorator to add aliases for Cmdln.do_* command handlers. Example: class MyShell(cmdln.Cmdln): @@ -135,987 +43,10 @@ def alias(*aliases): return f return decorate -MAN_REPLACES = [ - (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5\-\6'), - (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5'), - (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4'), - (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3\-\4'), - (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3'), - (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3'), - (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2'), - (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2'), - (re.compile(r"^'"), r" '"), - ] -def man_escape(text): - ''' - Escapes text to be included in man page. - - For now it only escapes dashes in command line options. - ''' - for repl in MAN_REPLACES: - text = repl[0].sub(repl[1], text) - return text - -class RawCmdln(cmd.Cmd): - """An improved (on cmd.Cmd) framework for building multi-subcommand - scripts (think "svn" & "cvs") and simple shells (think "pdb" and - "gdb"). - - A simple example: - - import cmdln - - class MySVN(cmdln.RawCmdln): - name = "svn" - - @cmdln.aliases('stat', 'st') - def do_status(self, argv): - print "handle 'svn status' command" - - if __name__ == "__main__": - shell = MySVN() - retval = shell.main() - sys.exit(retval) - - See for more information. +def hide(): """ - name = None # if unset, defaults basename(sys.argv[0]) - prompt = None # if unset, defaults to self.name+"> " - version = None # if set, default top-level options include --version - - # Default messages for some 'help' command error cases. - # They are interpolated with one arg: the command. - nohelp = "no help on '%s'" - unknowncmd = "unknown command: '%s'" - - helpindent = '' # string with which to indent help output - - # Default man page parts, please change them in subclass - man_header = MAN_HEADER - man_commands_header = MAN_COMMANDS_HEADER - man_options_header = MAN_OPTIONS_HEADER - man_footer = MAN_FOOTER - - def __init__(self, completekey='tab', - stdin=None, stdout=None, stderr=None): - """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None) - - The optional argument 'completekey' is the readline name of a - completion key; it defaults to the Tab key. If completekey is - not None and the readline module is available, command completion - is done automatically. - - The optional arguments 'stdin', 'stdout' and 'stderr' specify - alternate input, output and error output file objects; if not - specified, sys.* are used. - - If 'stdout' but not 'stderr' is specified, stdout is used for - error output. This is to provide least surprise for users used - to only the 'stdin' and 'stdout' options with cmd.Cmd. - """ - if self.name is None: - self.name = os.path.basename(sys.argv[0]) - if self.prompt is None: - self.prompt = self.name+"> " - self._name_str = self._str(self.name) - self._prompt_str = self._str(self.prompt) - if stdin is not None: - self.stdin = stdin - else: - self.stdin = sys.stdin - if stdout is not None: - self.stdout = stdout - else: - self.stdout = sys.stdout - if stderr is not None: - self.stderr = stderr - elif stdout is not None: - self.stderr = stdout - else: - self.stderr = sys.stderr - self.cmdqueue = [] - self.completekey = completekey - self.cmdlooping = False - - def get_optparser(self): - """Hook for subclasses to set the option parser for the - top-level command/shell. - - This option parser is used retrieved and used by `.main()' to - handle top-level options. - - The default implements a single '-h|--help' option. Sub-classes - can return None to have no options at the top-level. Typically - an instance of CmdlnOptionParser should be returned. - """ - version = (self.version is not None - and "%s %s" % (self._name_str, self.version) - or None) - return CmdlnOptionParser(self, version=version) - - def get_version(self): - """ - Returns version of program. To be replaced in subclass. - """ - return __version__ - - def postoptparse(self): - """Hook method executed just after `.main()' parses top-level - options. - - When called `self.values' holds the results of the option parse. - """ - pass - - def main(self, argv=None, loop=LOOP_NEVER): - """A possible mainline handler for a script, like so: - - import cmdln - class MyCmd(cmdln.Cmdln): - name = "mycmd" - ... - - if __name__ == "__main__": - MyCmd().main() - - By default this will use sys.argv to issue a single command to - 'MyCmd', then exit. The 'loop' argument can be use to control - interactive shell behaviour. - - Arguments: - "argv" (optional, default sys.argv) is the command to run. - It must be a sequence, where the first element is the - command name and subsequent elements the args for that - command. - "loop" (optional, default LOOP_NEVER) is a constant - indicating if a command loop should be started (i.e. an - interactive shell). Valid values (constants on this module): - LOOP_ALWAYS start loop and run "argv", if any - LOOP_NEVER run "argv" (or .emptyline()) and exit - LOOP_IF_EMPTY run "argv", if given, and exit; - otherwise, start loop - """ - if argv is None: - argv = sys.argv - else: - argv = argv[:] # don't modify caller's list - - self.optparser = self.get_optparser() - if self.optparser: # i.e. optparser=None means don't process for opts - try: - self.options, args = self.optparser.parse_args(argv[1:]) - # store args so we can use them in self.postoptparse() - self.args = args - except CmdlnUserError as ex: - msg = "%s: %s\nTry '%s help' for info.\n"\ - % (self.name, ex, self.name) - self.stderr.write(self._str(msg)) - self.stderr.flush() - return 1 - except StopOptionProcessing as ex: - return 0 - else: - self.options, args = None, argv[1:] - self.postoptparse() - - if loop == LOOP_ALWAYS: - if args: - self.cmdqueue.append(args) - return self.cmdloop() - elif loop == LOOP_NEVER: - if args: - return self.cmd(args) - else: - return self.emptyline() - elif loop == LOOP_IF_EMPTY: - if args: - return self.cmd(args) - else: - return self.cmdloop() - - def cmd(self, argv): - """Run one command and exit. - - "argv" is the arglist for the command to run. argv[0] is the - command to run. If argv is an empty list then the - 'emptyline' handler is run. - - Returns the return value from the command handler. - """ - assert isinstance(argv, (list, tuple)), \ - "'argv' is not a sequence: %r" % argv - retval = None - try: - argv = self.precmd(argv) - retval = self.onecmd(argv) - self.postcmd(argv) - except: - if not self.cmdexc(argv): - raise - retval = 1 - return retval - - def _str(self, s): - """Safely convert the given str/unicode to a string for printing.""" - try: - return str(s) - except UnicodeError: - #XXX What is the proper encoding to use here? 'utf-8' seems - # to work better than "getdefaultencoding" (usually - # 'ascii'), on OS X at least. - #return s.encode(sys.getdefaultencoding(), "replace") - return s.encode("utf-8", "replace") - - def cmdloop(self, intro=None): - """Repeatedly issue a prompt, accept input, parse into an argv, and - dispatch (via .precmd(), .onecmd() and .postcmd()), passing them - the argv. In other words, start a shell. - - "intro" (optional) is a introductory message to print when - starting the command loop. This overrides the class - "intro" attribute, if any. - """ - self.cmdlooping = True - self.preloop() - if self.use_rawinput and self.completekey: - try: - import readline - self.old_completer = readline.get_completer() - readline.set_completer(self.complete) - readline.parse_and_bind(self.completekey+": complete") - except ImportError: - pass - try: - if intro is None: - intro = self.intro - if intro: - intro_str = self._str(intro) - self.stdout.write(intro_str+'\n') - self.stop = False - retval = None - while not self.stop: - if self.cmdqueue: - argv = self.cmdqueue.pop(0) - assert isinstance(argv, (list, tuple)), \ - "item on 'cmdqueue' is not a sequence: %r" % argv - else: - if self.use_rawinput: - try: - try: - #python 2.x - line = raw_input(self._prompt_str) - except NameError: - line = input(self._prompt_str) - except EOFError: - line = 'EOF' - else: - self.stdout.write(self._prompt_str) - self.stdout.flush() - line = self.stdin.readline() - if not len(line): - line = 'EOF' - else: - line = line[:-1] # chop '\n' - argv = line2argv(line) - try: - argv = self.precmd(argv) - retval = self.onecmd(argv) - self.postcmd(argv) - except: - if not self.cmdexc(argv): - raise - retval = 1 - self.lastretval = retval - self.postloop() - finally: - if self.use_rawinput and self.completekey: - try: - import readline - readline.set_completer(self.old_completer) - except ImportError: - pass - self.cmdlooping = False - return retval - - def precmd(self, argv): - """Hook method executed just before the command argv is - interpreted, but after the input prompt is generated and issued. - - "argv" is the cmd to run. - - Returns an argv to run (i.e. this method can modify the command - to run). - """ - return argv - - def postcmd(self, argv): - """Hook method executed just after a command dispatch is finished. - - "argv" is the command that was run. - """ - pass - - def cmdexc(self, argv): - """Called if an exception is raised in any of precmd(), onecmd(), - or postcmd(). If True is returned, the exception is deemed to have - been dealt with. Otherwise, the exception is re-raised. - - The default implementation handles CmdlnUserError's, which - typically correspond to user error in calling commands (as - opposed to programmer error in the design of the script using - cmdln.py). - """ - exc_type, exc, traceback = sys.exc_info() - if isinstance(exc, CmdlnUserError): - msg = "%s %s: %s\nTry '%s help %s' for info.\n"\ - % (self.name, argv[0], exc, self.name, argv[0]) - self.stderr.write(self._str(msg)) - self.stderr.flush() - return True - - def onecmd(self, argv): - if not argv: - return self.emptyline() - self.lastcmd = argv - cmdname = self._get_canonical_cmd_name(argv[0]) - if cmdname: - handler = self._get_cmd_handler(cmdname) - if handler: - return self._dispatch_cmd(handler, argv) - return self.default(argv) - - def _dispatch_cmd(self, handler, argv): - return handler(argv) - - def default(self, argv): - """Hook called to handle a command for which there is no handler. - - "argv" is the command and arguments to run. - - The default implementation writes and error message to stderr - and returns an error exit status. - - Returns a numeric command exit status. - """ - errmsg = self._str(self.unknowncmd % (argv[0],)) - if self.cmdlooping: - self.stderr.write(errmsg+"\n") - else: - self.stderr.write("%s: %s\nTry '%s help' for info.\n" - % (self._name_str, errmsg, self._name_str)) - self.stderr.flush() - return 1 - - def parseline(self, line): - # This is used by Cmd.complete (readline completer function) to - # massage the current line buffer before completion processing. - # We override to drop special '!' handling. - line = line.strip() - if not line: - return None, None, line - elif line[0] == '?': - line = 'help ' + line[1:] - i, n = 0, len(line) - while i < n and line[i] in self.identchars: - i = i+1 - cmd, arg = line[:i], line[i:].strip() - return cmd, arg, line - - def helpdefault(self, cmd, known): - """Hook called to handle help on a command for which there is no - help handler. - - "cmd" is the command name on which help was requested. - "known" is a boolean indicating if this command is known - (i.e. if there is a handler for it). - - Returns a return code. - """ - if known: - msg = self._str(self.nohelp % (cmd,)) - if self.cmdlooping: - self.stderr.write(msg + '\n') - else: - self.stderr.write("%s: %s\n" % (self.name, msg)) - else: - msg = self.unknowncmd % (cmd,) - if self.cmdlooping: - self.stderr.write(msg + '\n') - else: - self.stderr.write("%s: %s\n" - "Try '%s help' for info.\n" - % (self.name, msg, self.name)) - self.stderr.flush() - return 1 - - - def do_help(self, argv): - """${cmd_name}: give detailed help on a specific sub-command - - usage: - ${name} help [SUBCOMMAND] - """ - if len(argv) > 1: # asking for help on a particular command - doc = None - cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1] - if not cmdname: - return self.helpdefault(argv[1], False) - else: - helpfunc = getattr(self, "help_"+cmdname, None) - if helpfunc: - doc = helpfunc() - else: - handler = self._get_cmd_handler(cmdname) - if handler: - doc = handler.__doc__ - if doc is None: - return self.helpdefault(argv[1], handler != None) - else: # bare "help" command - doc = self.__class__.__doc__ # try class docstring - if doc is None: - # Try to provide some reasonable useful default help. - if self.cmdlooping: - prefix = "" - else: - prefix = self.name+' ' - doc = """usage: - %sSUBCOMMAND [ARGS...] - %shelp [SUBCOMMAND] - - ${option_list} - ${command_list} - ${help_list} - """ % (prefix, prefix) - cmdname = None - - if doc: # *do* have help content, massage and print that - doc = self._help_reindent(doc) - doc = self._help_preprocess(doc, cmdname) - doc = doc.rstrip() + '\n' # trim down trailing space - self.stdout.write(self._str(doc)) - self.stdout.flush() - do_help.aliases = ["?"] - - - def do_man(self, argv): - """${cmd_name}: generates a man page - - usage: - ${name} man - """ - mandate = datetime.utcfromtimestamp(int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) - self.stdout.write( - self.man_header % { - 'date': mandate.strftime('%b %Y'), - 'version': self.get_version(), - 'name': self.name, - 'ucname': self.name.upper() - } - ) - - self.stdout.write(self.man_commands_header) - commands = self._help_get_command_list() - for command, doc in commands: - cmdname = command.split(' ')[0] - text = self._help_preprocess(doc, cmdname) - lines = [] - for line in text.splitlines(False): - if line[:8] == ' ' * 8: - line = line[8:] - lines.append(man_escape(line)) - - self.stdout.write( - '.TP\n\\fB%s\\fR\n%s\n' % (command, '\n'.join(lines))) - - self.stdout.write(self.man_options_header) - self.stdout.write( - man_escape(self._help_preprocess('${option_list}', None))) - - self.stdout.write(self.man_footer) - - self.stdout.flush() - - def _help_reindent(self, help, indent=None): - """Hook to re-indent help strings before writing to stdout. - - "help" is the help content to re-indent - "indent" is a string with which to indent each line of the - help content after normalizing. If unspecified or None - then the default is use: the 'self.helpindent' class - attribute. By default this is the empty string, i.e. - no indentation. - - By default, all common leading whitespace is removed and then - the lot is indented by 'self.helpindent'. When calculating the - common leading whitespace the first line is ignored -- hence - help content for Conan can be written as follows and have the - expected indentation: - - def do_crush(self, ...): - '''${cmd_name}: crush your enemies, see them driven before you... - - c.f. Conan the Barbarian''' - """ - if indent is None: - indent = self.helpindent - lines = help.splitlines(0) - _dedentlines(lines, skip_first_line=True) - lines = [(indent+line).rstrip() for line in lines] - return '\n'.join(lines) - - def _help_preprocess(self, help, cmdname): - """Hook to preprocess a help string before writing to stdout. - - "help" is the help string to process. - "cmdname" is the canonical sub-command name for which help - is being given, or None if the help is not specific to a - command. - - By default the following template variables are interpolated in - help content. (Note: these are similar to Python 2.4's - string.Template interpolation but not quite.) - - ${name} - The tool's/shell's name, i.e. 'self.name'. - ${option_list} - A formatted table of options for this shell/tool. - ${command_list} - A formatted table of available sub-commands. - ${help_list} - A formatted table of additional help topics (i.e. 'help_*' - methods with no matching 'do_*' method). - ${cmd_name} - The name (and aliases) for this sub-command formatted as: - "NAME (ALIAS1, ALIAS2, ...)". - ${cmd_usage} - A formatted usage block inferred from the command function - signature. - ${cmd_option_list} - A formatted table of options for this sub-command. (This is - only available for commands using the optparse integration, - i.e. using @cmdln.option decorators or manually setting the - 'optparser' attribute on the 'do_*' method.) - - Returns the processed help. - """ - preprocessors = { - "${name}": self._help_preprocess_name, - "${option_list}": self._help_preprocess_option_list, - "${command_list}": self._help_preprocess_command_list, - "${help_list}": self._help_preprocess_help_list, - "${cmd_name}": self._help_preprocess_cmd_name, - "${cmd_usage}": self._help_preprocess_cmd_usage, - "${cmd_option_list}": self._help_preprocess_cmd_option_list, - } - - for marker, preprocessor in preprocessors.items(): - if marker in help: - help = preprocessor(help, cmdname) - return help - - def _help_preprocess_name(self, help, cmdname=None): - return help.replace("${name}", self.name) - - def _help_preprocess_option_list(self, help, cmdname=None): - marker = "${option_list}" - indent, indent_width = _get_indent(marker, help) - suffix = _get_trailing_whitespace(marker, help) - - if self.optparser: - # Setup formatting options and format. - # - Indentation of 4 is better than optparse default of 2. - # C.f. Damian Conway's discussion of this in Perl Best - # Practices. - self.optparser.formatter.indent_increment = 4 - self.optparser.formatter.current_indent = indent_width - block = self.optparser.format_option_help() + '\n' - else: - block = "" - - help_msg = help.replace(indent+marker+suffix, block, 1) - return help_msg - - def _help_get_command_list(self): - # Find any aliases for commands. - token2canonical = self._get_canonical_map() - aliases = {} - for token, cmdname in token2canonical.items(): - if token == cmdname: - continue - aliases.setdefault(cmdname, []).append(token) - - # Get the list of (non-hidden) commands and their - # documentation, if any. - cmdnames = {} # use a dict to strip duplicates - for attr in self.get_names(): - if attr.startswith("do_"): - cmdnames[attr[3:]] = True - linedata = [] - for cmdname in sorted(cmdnames.keys()): - if aliases.get(cmdname): - a = sorted(aliases[cmdname]) - cmdstr = "%s (%s)" % (cmdname, ", ".join(a)) - else: - cmdstr = cmdname - doc = None - try: - helpfunc = getattr(self, 'help_'+cmdname) - except AttributeError: - handler = self._get_cmd_handler(cmdname) - if handler: - doc = handler.__doc__ - else: - doc = helpfunc() - - # Strip "${cmd_name}: " from the start of a command's doc. Best - # practice dictates that command help strings begin with this, but - # it isn't at all wanted for the command list. - to_strip = "${cmd_name}:" - if doc and doc.lstrip().startswith(to_strip): - #log.debug("stripping %r from start of %s's help string", - # to_strip, cmdname) - doc = (doc.lstrip())[len(to_strip):].lstrip() - if not getattr(self._get_cmd_handler(cmdname), "hidden", None): - linedata.append( (cmdstr, doc) ) - - return linedata - - def _help_preprocess_command_list(self, help, cmdname=None): - marker = "${command_list}" - indent, indent_width = _get_indent(marker, help) - suffix = _get_trailing_whitespace(marker, help) - - linedata = self._help_get_command_list() - - if linedata: - subindent = indent + ' '*4 - lines = _format_linedata(linedata, subindent, indent_width+4) - block = indent + "commands:\n" \ - + '\n'.join(lines) + "\n\n" - help = help.replace(indent+marker+suffix, block, 1) - return help - - def _help_preprocess_help_list(self, help, cmdname=None): - marker = "${help_list}" - indent, indent_width = _get_indent(marker, help) - suffix = _get_trailing_whitespace(marker, help) - - # Determine the additional help topics, if any. - helpnames = {} - token2cmdname = self._get_canonical_map() - for attr in self.get_names(): - if not attr.startswith("help_"): - continue - helpname = attr[5:] - if helpname not in token2cmdname: - helpnames[helpname] = True - - if helpnames: - helpnames = sorted(helpnames.keys()) - linedata = [(self.name+" help "+n, "") for n in helpnames] - - subindent = indent + ' '*4 - lines = _format_linedata(linedata, subindent, indent_width+4) - block = indent + "additional help topics:\n" \ - + '\n'.join(lines) + "\n\n" - else: - block = '' - help_msg = help.replace(indent+marker+suffix, block, 1) - return help_msg - - def _help_preprocess_cmd_name(self, help, cmdname=None): - marker = "${cmd_name}" - handler = self._get_cmd_handler(cmdname) - if not handler: - raise CmdlnError("cannot preprocess '%s' into help string: " - "could not find command handler for %r" - % (marker, cmdname)) - s = cmdname - if hasattr(handler, "aliases"): - s += " (%s)" % (", ".join(handler.aliases)) - help_msg = help.replace(marker, s) - return help_msg - - #TODO: this only makes sense as part of the Cmdln class. - # Add hooks to add help preprocessing template vars and put - # this one on that class. - def _help_preprocess_cmd_usage(self, help, cmdname=None): - marker = "${cmd_usage}" - handler = self._get_cmd_handler(cmdname) - if not handler: - raise CmdlnError("cannot preprocess '%s' into help string: " - "could not find command handler for %r" - % (marker, cmdname)) - indent, indent_width = _get_indent(marker, help) - suffix = _get_trailing_whitespace(marker, help) - - func_defaults, co_argcount, co_varnames, co_flags, _ = introspect_handler(handler) - CO_FLAGS_ARGS = 4 - CO_FLAGS_KWARGS = 8 - - # Adjust argcount for possible *args and **kwargs arguments. - argcount = co_argcount - if co_flags & CO_FLAGS_ARGS: - argcount += 1 - if co_flags & CO_FLAGS_KWARGS: - argcount += 1 - - # Determine the usage string. - usage = "%s %s" % (self.name, cmdname) - if argcount <= 2: # handler ::= do_FOO(self, argv) - usage += " [ARGS...]" - elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...) - argnames = list(co_varnames[3:argcount]) - tail = "" - if co_flags & CO_FLAGS_KWARGS: - name = argnames.pop(-1) - import warnings - # There is no generally accepted mechanism for passing - # keyword arguments from the command line. Could - # *perhaps* consider: arg=value arg2=value2 ... - warnings.warn("argument '**%s' on '%s.%s' command " - "handler will never get values" - % (name, self.__class__.__name__, - getattr(func, "__name__", getattr(func, "func_name")))) - if co_flags & CO_FLAGS_ARGS: - name = argnames.pop(-1) - tail = "[%s...]" % name.upper() - while func_defaults: - func_defaults.pop(-1) - name = argnames.pop(-1) - tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail) - while argnames: - name = argnames.pop(-1) - tail = "%s %s" % (name.upper(), tail) - usage += ' ' + tail - - block_lines = [ - self.helpindent + "Usage:", - self.helpindent + ' '*4 + usage - ] - block = '\n'.join(block_lines) + '\n\n' - - help_msg = help.replace(indent+marker+suffix, block, 1) - return help_msg - - #TODO: this only makes sense as part of the Cmdln class. - # Add hooks to add help preprocessing template vars and put - # this one on that class. - def _help_preprocess_cmd_option_list(self, help, cmdname=None): - marker = "${cmd_option_list}" - handler = self._get_cmd_handler(cmdname) - if not handler: - raise CmdlnError("cannot preprocess '%s' into help string: " - "could not find command handler for %r" - % (marker, cmdname)) - indent, indent_width = _get_indent(marker, help) - suffix = _get_trailing_whitespace(marker, help) - if hasattr(handler, "optparser"): - # Setup formatting options and format. - # - Indentation of 4 is better than optparse default of 2. - # C.f. Damian Conway's discussion of this in Perl Best - # Practices. - handler.optparser.formatter.indent_increment = 4 - handler.optparser.formatter.current_indent = indent_width - block = handler.optparser.format_option_help() + '\n' - else: - block = "" - - help_msg = help.replace(indent+marker+suffix, block, 1) - return help_msg - - def _get_canonical_cmd_name(self, token): - c_map = self._get_canonical_map() - return c_map.get(token, None) - - def _get_canonical_map(self): - """Return a mapping of available command names and aliases to - their canonical command name. - """ - cacheattr = "_token2canonical" - if not hasattr(self, cacheattr): - # Get the list of commands and their aliases, if any. - token2canonical = {} - cmd2funcname = {} # use a dict to strip duplicates - for attr in self.get_names(): - if attr.startswith("do_"): - cmdname = attr[3:] - elif attr.startswith("_do_"): - cmdname = attr[4:] - else: - continue - cmd2funcname[cmdname] = attr - token2canonical[cmdname] = cmdname - for cmdname, funcname in cmd2funcname.items(): # add aliases - func = getattr(self, funcname) - aliases = getattr(func, "aliases", []) - for alias in aliases: - if alias in cmd2funcname: - import warnings - warnings.warn("'%s' alias for '%s' command conflicts " - "with '%s' handler" - % (alias, cmdname, cmd2funcname[alias])) - continue - token2canonical[alias] = cmdname - setattr(self, cacheattr, token2canonical) - return getattr(self, cacheattr) - - def _get_cmd_handler(self, cmdname): - handler = None - try: - handler = getattr(self, 'do_' + cmdname) - except AttributeError: - try: - # Private command handlers begin with "_do_". - handler = getattr(self, '_do_' + cmdname) - except AttributeError: - pass - return handler - - def _do_EOF(self, argv): - # Default EOF handler - # Note: an actual EOF is redirected to this command. - #TODO: separate name for this. Currently it is available from - # command-line. Is that okay? - self.stdout.write('\n') - self.stdout.flush() - self.stop = True - - def emptyline(self): - # Different from cmd.Cmd: don't repeat the last command for an - # emptyline. - if self.cmdlooping: - pass - else: - return self.do_help(["help"]) - - -#---- optparse.py extension to fix (IMO) some deficiencies -# -# See the class _OptionParserEx docstring for details. -# - -class StopOptionProcessing(Exception): - """Indicate that option *and argument* processing should stop - cleanly. This is not an error condition. It is similar in spirit to - StopIteration. This is raised by _OptionParserEx's default "help" - and "version" option actions and can be raised by custom option - callbacks too. - - Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx) - usage is: - - parser = CmdlnOptionParser(mycmd) - parser.add_option("-f", "--force", dest="force") - ... - try: - opts, args = parser.parse_args() - except StopOptionProcessing: - # normal termination, "--help" was probably given - sys.exit(0) - """ - -class _OptionParserEx(optparse.OptionParser): - """An optparse.OptionParser that uses exceptions instead of sys.exit. - - This class is an extension of optparse.OptionParser that differs - as follows: - - Correct (IMO) the default OptionParser error handling to never - sys.exit(). Instead OptParseError exceptions are passed through. - - Add the StopOptionProcessing exception (a la StopIteration) to - indicate normal termination of option processing. - See StopOptionProcessing's docstring for details. - - I'd also like to see the following in the core optparse.py, perhaps - as a RawOptionParser which would serve as a base class for the more - generally used OptionParser (that works as current): - - Remove the implicit addition of the -h|--help and --version - options. They can get in the way (e.g. if want '-?' and '-V' for - these as well) and it is not hard to do: - optparser.add_option("-h", "--help", action="help") - optparser.add_option("--version", action="version") - These are good practices, just not valid defaults if they can - get in the way. - """ - def error(self, msg): - raise optparse.OptParseError(msg) - - def exit(self, status=0, msg=None): - if status == 0: - raise StopOptionProcessing(msg) - else: - #TODO: don't lose status info here - raise optparse.OptParseError(msg) - - - -#---- optparse.py-based option processing support - -class CmdlnOptionParser(_OptionParserEx): - """An optparse.OptionParser class more appropriate for top-level - Cmdln options. For parsing of sub-command options, see - SubCmdOptionParser. - - Changes: - - disable_interspersed_args() by default, because a Cmdln instance - has sub-commands which may themselves have options. - - Redirect print_help() to the Cmdln.do_help() which is better - equiped to handle the "help" action. - - error() will raise a CmdlnUserError: OptionParse.error() is meant - to be called for user errors. Raising a well-known error here can - make error handling clearer. - - Also see the changes in _OptionParserEx. - """ - def __init__(self, cmdln, **kwargs): - self.cmdln = cmdln - kwargs["prog"] = self.cmdln.name - _OptionParserEx.__init__(self, **kwargs) - self.disable_interspersed_args() - - def print_help(self, file=None): - self.cmdln.onecmd(["help"]) - - def error(self, msg): - raise CmdlnUserError(msg) - - -class SubCmdOptionParser(_OptionParserEx): - def set_cmdln_info(self, cmdln, subcmd): - """Called by Cmdln to pass relevant info about itself needed - for print_help(). - """ - self.cmdln = cmdln - self.subcmd = subcmd - - def print_help(self, file=None): - self.cmdln.onecmd(["help", self.subcmd]) - - def error(self, msg): - raise CmdlnUserError(msg) - - -def option(*args, **kwargs): - """Decorator to add an option to the optparser argument of a Cmdln - subcommand. - - Example: - class MyShell(cmdln.Cmdln): - @cmdln.option("-f", "--force", help="force removal") - def do_remove(self, subcmd, opts, *args): - #... - """ - #XXX Is there a possible optimization for many options to not have a - # large stack depth here? - def decorate(f): - if not hasattr(f, "optparser"): - f.optparser = SubCmdOptionParser() - f.optparser.add_option(*args, **kwargs) - return f - return decorate - -def hide(*args): - """For obsolete calls, hide them in help listings. + For obsolete calls, hide them in help listings. Example: class MyShell(cmdln.Cmdln): @@ -1124,466 +55,185 @@ def hide(*args): #...implement 'shell' command """ def decorate(f): - f.hidden = 1 + f.hidden = True return f return decorate -class Cmdln(RawCmdln): - """An improved (on cmd.Cmd) framework for building multi-subcommand - scripts (think "svn" & "cvs") and simple shells (think "pdb" and - "gdb"). +class HelpFormatter(argparse.RawDescriptionHelpFormatter): + def _format_action(self, action): + if isinstance(action, argparse._SubParsersAction): + parts = [] + for i in action._get_subactions(): + if i.help == argparse.SUPPRESS: + # don't display commands with suppressed help + continue + if len(i.metavar) > 20: + parts.append("%*s%-21s" % (self._current_indent, "", i.metavar)) + parts.append("%*s %s" % (self._current_indent + 21, "", i.help)) + else: + parts.append("%*s%-21s %s" % (self._current_indent, "", i.metavar, i.help)) + return "\n".join(parts) + return super()._format_action(action) - A simple example: - import cmdln +class Cmdln: + def get_argparser_usage(self): + return "%(prog)s [global opts] [--help] [opts] [args]" - class MySVN(cmdln.Cmdln): - name = "svn" + def get_subcommand_prog(self, subcommand): + return f"{self.argparser.prog} [global opts] {subcommand}" - @cmdln.aliases('stat', 'st') - @cmdln.option('-v', '--verbose', action='store_true' - help='print verbose information') - def do_status(self, subcmd, opts, *paths): - print "handle 'svn status' command" + def _remove_leading_spaces_from_text(self, text): + lines = text.splitlines() + lines = self._remove_leading_spaces_from_lines(lines) + return "\n".join(lines) - #... + def _remove_leading_spaces_from_lines(self, lines): + # compute the indentation (leading spaces) in the docstring + leading_spaces = 0 + for line in lines: + line_leading_spaces = len(line) - len(line.lstrip(' ')) + if leading_spaces == 0: + leading_spaces = line_leading_spaces + leading_spaces = min(leading_spaces, line_leading_spaces) + # dedent the lines (remove leading spaces) + lines = [line[leading_spaces:] for line in lines] + return lines - if __name__ == "__main__": - shell = MySVN() - retval = shell.main() - sys.exit(retval) - - 'Cmdln' extends 'RawCmdln' by providing optparse option processing - integration. See this class' _dispatch_cmd() docstring and - for more information. - """ - def _dispatch_cmd(self, handler, argv): - """Introspect sub-command handler signature to determine how to - dispatch the command. The raw handler provided by the base - 'RawCmdln' class is still supported: - - def do_foo(self, argv): - # 'argv' is the vector of command line args, argv[0] is - # the command name itself (i.e. "foo" or an alias) - pass - - In addition, if the handler has more than 2 arguments option - processing is automatically done (using optparse): - - @cmdln.option('-v', '--verbose', action='store_true') - def do_bar(self, subcmd, opts, *args): - # subcmd = <"bar" or an alias> - # opts = - if opts.verbose: - print "lots of debugging output..." - # args = - for arg in args: - bar(arg) - - TODO: explain that "*args" can be other signatures as well. - - The `cmdln.option` decorator corresponds to an `add_option()` - method call on an `optparse.OptionParser` instance. - - You can declare a specific number of arguments: - - @cmdln.option('-v', '--verbose', action='store_true') - def do_bar2(self, subcmd, opts, bar_one, bar_two): - #... - - and an appropriate error message will be raised/printed if the - command is called with a different number of args. + def create_argparser(self): """ - co_argcount = introspect_handler(handler)[1] - if co_argcount == 2: # handler ::= do_foo(self, argv) - return handler(argv) - elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...) - try: - optparser = handler.optparser - except AttributeError: - optparser = introspect_handler(handler)[4].optparser = SubCmdOptionParser() - assert isinstance(optparser, SubCmdOptionParser) - optparser.set_cmdln_info(self, argv[0]) - try: - opts, args = optparser.parse_args(argv[1:]) - except StopOptionProcessing: - #TODO: this doesn't really fly for a replacement of - # optparse.py behaviour, does it? - return 0 # Normal command termination + Create `.argparser` and `.subparsers`. + Override this method to replace them with your own. + """ + self.argparser = argparse.ArgumentParser( + usage=self.get_argparser_usage(), + description=self._remove_leading_spaces_from_text(self.__doc__), + formatter_class=HelpFormatter, + ) + self.subparsers = self.argparser.add_subparsers( + title="commands", + dest="command", + ) - try: - return handler(argv[0], opts, *args) - except TypeError as ex: - # Some TypeError's are user errors: - # do_foo() takes at least 4 arguments (3 given) - # do_foo() takes at most 5 arguments (6 given) - # do_foo() takes exactly 5 arguments (6 given) - # Raise CmdlnUserError for these with a suitably - # massaged error message. - tb = sys.exc_info()[2] # the traceback object - if tb.tb_next is not None: - # If the traceback is more than one level deep, then the - # TypeError do *not* happen on the "handler(...)" call - # above. In that we don't want to handle it specially - # here: it would falsely mask deeper code errors. - raise - msg = ex.args[0] - match = _INCORRECT_NUM_ARGS_RE.search(msg) - match_py3 = _INCORRECT_NUM_ARGS_RE_PY3.search(msg) - if match: - msg = list(match.groups()) - msg[1] = int(msg[1]) - 3 - if msg[1] == 1: - msg[2] = msg[2].replace("arguments", "argument") - msg[3] = int(msg[3]) - 3 - msg = ''.join(map(str, msg)) - raise CmdlnUserError(msg) - elif match_py3: - raise CmdlnUserError(match_py3.group(1)) - else: - raise - else: - raise CmdlnError("incorrect argcount for %s(): takes %d, must " - "take 2 for 'argv' signature or 3+ for 'opts' " - "signature" % (handler.__name__, co_argcount)) + self.pre_argparse() + # map command name to `do_*` function that runs the command + self.cmd_map = {} + # map aliases back to the command names + self.alias_to_cmd_name_map = {} -#---- internal support functions - -def _format_linedata(linedata, indent, indent_width): - """Format specific linedata into a pleasant layout. - - "linedata" is a list of 2-tuples of the form: - (, ) - "indent" is a string to use for one level of indentation - "indent_width" is a number of columns by which the - formatted data will be indented when printed. - - The column is held to 15 columns. - """ - lines = [] - WIDTH = 78 - indent_width - SPACING = 3 - MAX_NAME_WIDTH = 15 - - NAME_WIDTH = min(max(len(s) for s, d in linedata), MAX_NAME_WIDTH) - DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING - for namestr, doc in linedata: - line = indent + namestr - if len(namestr) <= NAME_WIDTH: - line += ' ' * (NAME_WIDTH + SPACING - len(namestr)) - else: - lines.append(line) - line = indent + ' ' * (NAME_WIDTH + SPACING) - line += _summarize_doc(doc, DOC_WIDTH) - lines.append(line.rstrip()) - return lines - -def _summarize_doc(doc, length=60): - r"""Parse out a short one line summary from the given doclines. - - "doc" is the doc string to summarize. - "length" is the max length for the summary - - >>> _summarize_doc("this function does this") - 'this function does this' - >>> _summarize_doc("this function does this", 10) - 'this fu...' - >>> _summarize_doc("this function does this\nand that") - 'this function does this and that' - >>> _summarize_doc("this function does this\n\nand that") - 'this function does this' - """ - if doc is None: - return "" - assert length > 3, "length <= 3 is absurdly short for a doc summary" - doclines = doc.strip().splitlines(0) - if not doclines: - return "" - - summlines = [] - for i, line in enumerate(doclines): - stripped = line.strip() - if not stripped: - break - summlines.append(stripped) - if len(''.join(summlines)) >= length: - break - - summary = ' '.join(summlines) - if len(summary) > length: - summary = summary[:length-3] + "..." - return summary - - -def line2argv(line): - r"""Parse the given line into an argument vector. - - "line" is the line of input to parse. - - This may get niggly when dealing with quoting and escaping. The - current state of this parsing may not be completely thorough/correct - in this respect. - - >>> from cmdln import line2argv - >>> line2argv("foo") - ['foo'] - >>> line2argv("foo bar") - ['foo', 'bar'] - >>> line2argv("foo bar ") - ['foo', 'bar'] - >>> line2argv(" foo bar") - ['foo', 'bar'] - - Quote handling: - - >>> line2argv("'foo bar'") - ['foo bar'] - >>> line2argv('"foo bar"') - ['foo bar'] - >>> line2argv(r'"foo\"bar"') - ['foo"bar'] - >>> line2argv("'foo bar' spam") - ['foo bar', 'spam'] - >>> line2argv("'foo 'bar spam") - ['foo bar', 'spam'] - >>> line2argv("'foo") - Traceback (most recent call last): - ... - ValueError: command line is not terminated: unfinished single-quoted segment - >>> line2argv('"foo') - Traceback (most recent call last): - ... - ValueError: command line is not terminated: unfinished double-quoted segment - >>> line2argv('some\tsimple\ttests') - ['some', 'simple', 'tests'] - >>> line2argv('a "more complex" test') - ['a', 'more complex', 'test'] - >>> line2argv('a more="complex test of " quotes') - ['a', 'more=complex test of ', 'quotes'] - >>> line2argv('a more" complex test of " quotes') - ['a', 'more complex test of ', 'quotes'] - >>> line2argv('an "embedded \\"quote\\""') - ['an', 'embedded "quote"'] - """ - import string - line = line.strip() - argv = [] - state = "default" - arg = None # the current argument being parsed - i = -1 - while True: - i += 1 - if i >= len(line): - break - ch = line[i] - - if ch == "\\": # escaped char always added to arg, regardless of state - if arg is None: - arg = "" - i += 1 - arg += line[i] - continue - - if state == "single-quoted": - if ch == "'": - state = "default" - else: - arg += ch - elif state == "double-quoted": - if ch == '"': - state = "default" - else: - arg += ch - elif state == "default": - if ch == '"': - if arg is None: - arg = "" - state = "double-quoted" - elif ch == "'": - if arg is None: - arg = "" - state = "single-quoted" - elif ch in string.whitespace: - if arg is not None: - argv.append(arg) - arg = None - else: - if arg is None: - arg = "" - arg += ch - if arg is not None: - argv.append(arg) - if state != "default": - raise ValueError("command line is not terminated: unfinished %s " - "segment" % state) - return argv - - -def argv2line(argv): - r"""Put together the given argument vector into a command line. - - "argv" is the argument vector to process. - - >>> from cmdln import argv2line - >>> argv2line(['foo']) - 'foo' - >>> argv2line(['foo', 'bar']) - 'foo bar' - >>> argv2line(['foo', 'bar baz']) - 'foo "bar baz"' - >>> argv2line(['foo"bar']) - 'foo"bar' - >>> print argv2line(['foo" bar']) - 'foo" bar' - >>> print argv2line(["foo' bar"]) - "foo' bar" - >>> argv2line(["foo'bar"]) - "foo'bar" - """ - escapedArgs = [] - for arg in argv: - if ' ' in arg and '"' not in arg: - arg = '"'+arg+'"' - elif ' ' in arg and "'" not in arg: - arg = "'"+arg+"'" - elif ' ' in arg: - arg = arg.replace('"', r'\"') - arg = '"'+arg+'"' - escapedArgs.append(arg) - return ' '.join(escapedArgs) - - -# Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook -def _dedentlines(lines, tabsize=8, skip_first_line=False): - """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines - - "lines" is a list of lines to dedent. - "tabsize" is the tab width to use for indent width calculations. - "skip_first_line" is a boolean indicating if the first line should - be skipped for calculating the indent width and for dedenting. - This is sometimes useful for docstrings and similar. - - Same as dedent() except operates on a sequence of lines. Note: the - lines list is modified **in-place**. - """ - DEBUG = False - if DEBUG: - print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ - % (tabsize, skip_first_line)) - indents = [] - margin = None - for i, line in enumerate(lines): - if i == 0 and skip_first_line: - continue - indent = 0 - for ch in line: - if ch == ' ': - indent += 1 - elif ch == '\t': - indent += tabsize - (indent % tabsize) - elif ch in '\r\n': - continue # skip all-whitespace lines - else: - break - else: - continue # skip all-whitespace lines - if DEBUG: - print("dedent: indent=%d: %r" % (indent, line)) - if margin is None: - margin = indent - else: - margin = min(margin, indent) - if DEBUG: - print("dedent: margin=%r" % margin) - - if margin is not None and margin > 0: - for i, line in enumerate(lines): - if i == 0 and skip_first_line: + for attr in dir(self): + if not attr.startswith("do_"): continue - removed = 0 - for j, ch in enumerate(line): - if ch == ' ': - removed += 1 - elif ch == '\t': - removed += tabsize - (removed % tabsize) - elif ch in '\r\n': - if DEBUG: - print("dedent: %r: EOL -> strip up to EOL" % line) - lines[i] = lines[i][j:] - break - else: - raise ValueError("unexpected non-whitespace char %r in " - "line %r while removing %d-space margin" - % (ch, line, margin)) - if DEBUG: - print("dedent: %r: %r -> removed %d/%d"\ - % (line, ch, removed, margin)) - if removed == margin: - lines[i] = lines[i][j+1:] - break - elif removed > margin: - lines[i] = ' '*(removed-margin) + lines[i][j+1:] - break - return lines -def _dedent(text, tabsize=8, skip_first_line=False): - """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text + cmd_name = attr[3:] + cmd_func = getattr(self, attr) - "text" is the text to dedent. - "tabsize" is the tab width to use for indent width calculations. - "skip_first_line" is a boolean indicating if the first line should - be skipped for calculating the indent width and for dedenting. - This is sometimes useful for docstrings and similar. + # extract data from the function + options = getattr(cmd_func, "options", []) + aliases = getattr(cmd_func, "aliases", []) + hidden = getattr(cmd_func, "hidden", False) - textwrap.dedent(s), but don't expand tabs to spaces - """ - lines = text.splitlines(1) - _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) - return ''.join(lines) + # map command name and aliases to the function + self.cmd_map[cmd_name] = cmd_func + self.alias_to_cmd_name_map[cmd_name] = cmd_name + for i in aliases: + self.cmd_map[i] = cmd_func + self.alias_to_cmd_name_map[i] = cmd_name + # split doctext into lines, allow the first line to start at a new line + help_lines = cmd_func.__doc__.lstrip().splitlines() -def _get_indent(marker, s, tab_width=8): - """_get_indent(marker, s, tab_width=8) -> - (, )""" - # Figure out how much the marker is indented. - INDENT_CHARS = tuple(' \t') - start = s.index(marker) - i = start - while i > 0: - if s[i-1] not in INDENT_CHARS: - break - i -= 1 - indent = s[i:start] - indent_width = 0 - for ch in indent: - if ch == ' ': - indent_width += 1 - elif ch == '\t': - indent_width += tab_width - (indent_width % tab_width) - return indent, indent_width + # use the first line as help text + help_text = help_lines.pop(0) -def _get_trailing_whitespace(marker, s): - """Return the whitespace content trailing the given 'marker' in string 's', - up to and including a newline. - """ - suffix = '' - start = s.index(marker) + len(marker) - i = start - while i < len(s): - if s[i] in ' \t': - suffix += s[i] - elif s[i] in '\r\n': - suffix += s[i] - if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n': - suffix += s[i+1] - break + # use the remaining lines as description + help_lines = self._remove_leading_spaces_from_lines(help_lines) + help_desc = "\n".join(help_lines) + help_desc = help_desc.strip() + + if hidden: + help_text = argparse.SUPPRESS + + subparser = self.subparsers.add_parser( + cmd_name, + aliases=aliases, + help=help_text, + description=help_desc, + prog=self.get_subcommand_prog(cmd_name), + formatter_class=HelpFormatter + ) + for option_args, option_kwargs in options: + subparser.add_argument(*option_args, **option_kwargs) + + # HACK: inject 'args' to all commands so we don't have to decorate all of them + subparser.add_argument('args', nargs='*') + + def argparse_error(self, *args, **kwargs): + """ + Raise an argument parser error. + Automatically pick the right parser for the main program or a subcommand. + """ + if not self.options.command: + parser = self.argparser else: - break - i += 1 - return suffix + parser = self.subparsers._name_parser_map.get(self.options.command, self.argparser) + parser.error(*args, **kwargs) + def pre_argparse(self): + """ + Hook method executed after `.main()` creates `.argparser` instance + and before `parse_args()` is called. + """ + pass -# vim: sw=4 et + def post_argparse(self): + """ + Hook method executed after `.main()` calls `parse_args()`. + When called, `.options` and `.args` hold the results of `parse_args()`. + """ + pass + + def main(self, argv=None): + if argv is None: + argv = sys.argv + else: + argv = argv[:] # don't modify caller's list + + self.create_argparser() + + self.options = self.argparser.parse_args(argv[1:]) + self.args = getattr(self.options, "args", []) + + self.post_argparse() + + if not self.options.command: + self.argparser.error("Please specify a command") + + # find the `do_*` function to call by its name + cmd = self.cmd_map[self.options.command] + # run the command with parsed args + cmd(self.options.command, self.options, *self.args) + + @alias("?") + def do_help(self, subcmd, opts, *args): + """ + Give detailed help on a specific sub-command + + usage: + %(prog)s [SUBCOMMAND] + """ + if not args: + self.argparser.print_help() + return + + for action in self.argparser._actions: + if not isinstance(action, argparse._SubParsersAction): + continue + + for choice, subparser in action.choices.items(): + if choice == args[0]: + subparser.print_help() + return diff --git a/osc/commandline.py b/osc/commandline.py index c5542185..e7733696 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -22,32 +22,6 @@ from .util import safewriter from .util.helper import _html_escape, format_table -MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands" -.SH NAME -%(name)s \- openSUSE build service command-line tool. -.SH SYNOPSIS -.B %(name)s -[\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...] -.br -.B %(name)s -\fIhelp SUBCOMMAND\fR -.SH DESCRIPTION -openSUSE build service command-line tool. -""" -MAN_FOOTER = r""" -.SH "SEE ALSO" -Type 'osc help ' for more detailed help on a specific subcommand. -.PP -For additional information, see - * http://en.opensuse.org/openSUSE:Build_Service_Tutorial - * http://en.opensuse.org/openSUSE:OSC -.PP -You can modify osc commands, or roll your own, via the plugin API: - * http://en.opensuse.org/openSUSE:OSC_plugins -.SH AUTHOR -osc was written by several authors. This man page is automatically generated. -""" - HELP_MULTIBUILD_MANY = """Only work with the specified flavors of a multibuild package. Globs are resolved according to _multibuild file from server. Empty string is resolved to a package without a flavor.""" @@ -55,16 +29,17 @@ Empty string is resolved to a package without a flavor.""" HELP_MULTIBUILD_ONE = "Only work with the specified flavor of a multibuild package." +def get_parser(): + osc = Osc() + osc.create_argparser() + return osc.argparser + + class Osc(cmdln.Cmdln): - """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...] - or: osc help SUBCOMMAND + """ + openSUSE commander is a command-line interface to the Open Build Service. + Type 'osc --help' for help on a specific subcommand. - openSUSE build service command-line tool. - Type 'osc help ' for help on a specific subcommand. - - ${command_list} - ${help_list} - global ${option_list} For additional information, see * http://en.opensuse.org/openSUSE:Build_Service_Tutorial * http://en.opensuse.org/openSUSE:OSC @@ -75,15 +50,9 @@ class Osc(cmdln.Cmdln): name = 'osc' conf = None - man_header = MAN_HEADER - man_footer = MAN_FOOTER - - def __init__(self, *args, **kwargs): - # the plugins have to be loaded before the - # superclass' __init__ method is called + def __init__(self): + self.options = None self._load_plugins() - cmdln.Cmdln.__init__(self, *args, **kwargs) - cmdln.Cmdln.do_help.aliases.append('h') sys.stderr = safewriter.SafeWriter(sys.stderr) sys.stdout = safewriter.SafeWriter(sys.stdout) @@ -100,48 +69,46 @@ class Osc(cmdln.Cmdln): return project.replace(conf.config['project_separator'], ':') return project - def get_optparser(self): - """this is the parser for "global" options (not specific to subcommand)""" + def pre_argparse(self): + """Add global options to the parser (options that are not specific to any subcommand)""" - optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version()) - optparser.add_option('--debugger', action='store_true', + optparser = self.argparser + optparser.add_argument('--debugger', action='store_true', help='jump into the debugger before executing anything') - optparser.add_option('--post-mortem', action='store_true', + optparser.add_argument('--post-mortem', action='store_true', help='jump into the debugger in case of errors') - optparser.add_option('-t', '--traceback', action='store_true', + optparser.add_argument('-t', '--traceback', action='store_true', help='print call trace in case of errors') - optparser.add_option('-H', '--http-debug', action='store_true', + optparser.add_argument('-H', '--http-debug', action='store_true', help='debug HTTP traffic (filters some headers)') - optparser.add_option('--http-full-debug', action='store_true', + optparser.add_argument('--http-full-debug', action='store_true', help='debug HTTP traffic (filters no headers)') - optparser.add_option('-d', '--debug', action='store_true', + optparser.add_argument('-d', '--debug', action='store_true', help='print info useful for debugging') - optparser.add_option('-A', '--apiurl', dest='apiurl', + optparser.add_argument('-A', '--apiurl', dest='apiurl', metavar='URL/alias', help='specify URL to access API server at or an alias') - optparser.add_option('-c', '--config', dest='conffile', + optparser.add_argument('-c', '--config', dest='conffile', metavar='FILE', help='specify alternate configuration file') - optparser.add_option('--no-keyring', action='store_true', + optparser.add_argument('--no-keyring', action='store_true', help='disable usage of desktop keyring system') - optparser.add_option('--no-gnome-keyring', action='store_true', + optparser.add_argument('--no-gnome-keyring', action='store_true', help='disable usage of GNOME Keyring') - optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0, + optparser.add_argument('-v', '--verbose', dest='verbose', action='count', default=0, help='increase verbosity') - optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1, + optparser.add_argument('-q', '--quiet', dest='verbose', action='store_const', const=-1, help='be quiet, not verbose') - return optparser - - def postoptparse(self): + def post_argparse(self): """merge commandline options into the config""" - if ( - not self.args - or self._get_canonical_cmd_name(self.args[0]) == "help" - or {'-h', '--help'} & set(self.args) - ): - # avoid loading config that may trigger prompt for username, password etc. + # avoid loading config that may trigger prompt for username, password etc. + if not self.options.command: + # no command specified + return + if self.alias_to_cmd_name_map.get(self.options.command, None) == "help": + # help command specified return try: @@ -163,16 +130,16 @@ class Osc(cmdln.Cmdln): apiurl = self.options.apiurl conf.interactive_config_setup(e.file, apiurl) print('done', file=sys.stderr) - self.postoptparse() + self.post_argparse() except oscerr.ConfigMissingApiurl as e: print(e.msg, file=sys.stderr) conf.interactive_config_setup(e.file, e.url, initial=False) - self.postoptparse() + self.post_argparse() except oscerr.ConfigMissingCredentialsError as e: print(e.msg) print('Please enter new credentials.') conf.interactive_config_setup(e.file, e.url, initial=False) - self.postoptparse() + self.post_argparse() self.options.verbose = conf.config['verbose'] self.download_progress = None @@ -180,14 +147,6 @@ class Osc(cmdln.Cmdln): from .meter import create_text_meter self.download_progress = create_text_meter() - - def get_cmd_help(self, cmdname): - doc = self._get_cmd_handler(cmdname).__doc__ - doc = self._help_reindent(doc) - doc = self._help_preprocess(doc, cmdname) - doc = doc.rstrip() + '\n' # trim down trailing space - return self._str(doc) - def get_api_url(self): try: localdir = os.getcwd() @@ -205,28 +164,9 @@ class Osc(cmdln.Cmdln): else: return conf.config['apiurl'] - # overridden from class Cmdln() to use config variables in help texts - def _help_preprocess(self, help, cmdname): - help_msg = cmdln.Cmdln._help_preprocess(self, help, cmdname) - return help_msg % conf.config - - def _help_preprocess_cmd_name(self, help, cmdname=None): - if cmdname is None: - return help - return cmdln.Cmdln._help_preprocess_cmd_name(self, help, cmdname) - - def _help_preprocess_cmd_option_list(self, help, cmdname=None): - if cmdname is None: - return help - return cmdln.Cmdln._help_preprocess_cmd_option_list(self, help, cmdname) - - def _help_preprocess_cmd_usage(self, help, cmdname=None): - if cmdname is None: - return help - return cmdln.Cmdln._help_preprocess_cmd_usage(self, help, cmdname) - def do_init(self, subcmd, opts, project, package=None, scm_url=None): - """${cmd_name}: Initialize a directory as working copy + """ + Initialize a directory as working copy Initialize an existing directory to be a working copy of an (already existing) buildservice project/package. @@ -245,7 +185,6 @@ class Osc(cmdln.Cmdln): osc init PRJ osc init PRJ PAC osc init PRJ PAC SCM_URL - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -297,7 +236,8 @@ class Osc(cmdln.Cmdln): @cmdln.option('-R', '--revision', metavar='REVISION', help='specify revision (only for sources)') def do_list(self, subcmd, opts, *args): - """${cmd_name}: List sources or binaries on the server + """ + List sources or binaries on the server Examples for listing sources: ls # list all projects (deprecated) @@ -323,10 +263,9 @@ class Osc(cmdln.Cmdln): ls -b PROJECT -r REPO # list binaries in REPO ls -b PROJECT PACKAGE REPO ARCH - Usage: - ${cmd_name} [PROJECT [PACKAGE]] - ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]] - ${cmd_option_list} + usage: + ls [PROJECT [PACKAGE]] + ls -b [PROJECT [PACKAGE [REPO [ARCH]]]] """ args = slash_split(args) @@ -498,14 +437,14 @@ class Osc(cmdln.Cmdln): @cmdln.option('--extend-package-names', default=False, action="store_true", help='Extend packages names with project name as suffix') def do_addcontainers(self, subcmd, opts, *args): - """${cmd_name}: Add maintained containers for a give package + """ + Add maintained containers for a give package The command adds all containers which are marked as maintained and contain an rpm originating from the specified source package. Examples: osc addcontainers [PROJECT PACKAGE] - ${cmd_option_list} """ args = slash_split(args) @@ -536,7 +475,8 @@ class Osc(cmdln.Cmdln): @cmdln.option('-e', '--enable-all', action='store_true', help='Enable all added channels including the ones disabled by default.') def do_addchannels(self, subcmd, opts, *args): - """${cmd_name}: Add channels to project. + """ + Add channels to project The command adds all channels which are defined to be used for a given source package. The source link target is used to lookup the channels. The command can be @@ -546,7 +486,6 @@ class Osc(cmdln.Cmdln): Examples: osc addchannels [PROJECT [PACKAGE]] - ${cmd_option_list} """ args = slash_split(args) @@ -581,7 +520,8 @@ class Osc(cmdln.Cmdln): @cmdln.alias('enablechannel') def do_enablechannels(self, subcmd, opts, *args): - """${cmd_name}: Enables channels + """ + Enables channels Enables existing channel packages in a project. Enabling means adding the needed repositories for building. @@ -589,7 +529,6 @@ class Osc(cmdln.Cmdln): Examples: osc enablechannels [PROJECT [CHANNEL_PACKAGE]] - ${cmd_option_list} """ args = slash_split(args) @@ -624,7 +563,8 @@ class Osc(cmdln.Cmdln): @cmdln.option('-f', '--force', action='store_true', help='force generation of new patchinfo file, do not update existing one.') def do_patchinfo(self, subcmd, opts, *args): - """${cmd_name}: Generate and edit a patchinfo file. + """ + Generate and edit a patchinfo file A patchinfo file describes the packages for an update and the kind of problem it solves. @@ -634,7 +574,6 @@ class Osc(cmdln.Cmdln): Examples: osc patchinfo osc patchinfo [PROJECT [PATCH_NAME]] - ${cmd_option_list} """ args = slash_split(args) @@ -702,12 +641,12 @@ class Osc(cmdln.Cmdln): @cmdln.alias('dp') @cmdln.option('-r', '--raw', action='store_true', help='deprecated option') def do_develproject(self, subcmd, opts, *args): - """${cmd_name}: print the devel project / package of a package + """ + Print the devel project / package of a package Examples: osc develproject PRJ PKG osc develproject - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -731,7 +670,8 @@ class Osc(cmdln.Cmdln): @cmdln.alias('ca') def do_cleanassets(self, subcmd, opts, *args): - """${cmd_name}: Clean all previous downloaded assets. + """ + Clean all previous downloaded assets This is useful to prepare a new git commit. """ @@ -739,7 +679,8 @@ class Osc(cmdln.Cmdln): @cmdln.alias('da') def do_downloadassets(self, subcmd, opts, *args): - """${cmd_name}: Download all assets referenced in the build descriptions + """ + Download all assets referenced in the build descriptions """ download_assets(".") @@ -747,11 +688,10 @@ class Osc(cmdln.Cmdln): @cmdln.option('-u', '--unset', action='store_true', help='remove devel project') def do_setdevelproject(self, subcmd, opts, *args): - """${cmd_name}: Set the devel project / package of a package + """Set the devel project / package of a package Examples: osc setdevelproject [PRJ PKG] DEVPRJ [DEVPKG] - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -777,11 +717,11 @@ class Osc(cmdln.Cmdln): def do_showlinked(self, subcmd, opts, *args): - """${cmd_name}: Show all packages linking to a given one + """ + Show all packages linking to a given one Examples: osc showlinked [PROJECT PACKAGE] - ${cmd_option_list} """ args = slash_split(args) @@ -814,17 +754,17 @@ class Osc(cmdln.Cmdln): @cmdln.option('', '--scm-token', metavar ='SCM_TOKEN', help='The scm\'s access token (only in combination with a --operation=workflow option)') def do_token(self, subcmd, opts, *args): - """${cmd_name}: Show and manage authentication token + """ + Show and manage authentication token Authentication token can be used to run specific commands without sending credentials. - Usage: + usage: osc token osc token --create [--operation ] [ ] osc token --delete osc token --trigger [--operation ] [ ] - ${cmd_option_list} """ args = slash_split(args) @@ -914,7 +854,8 @@ class Osc(cmdln.Cmdln): @cmdln.option('--delete', action='store_true', help='delete a pattern or attribute') def do_meta(self, subcmd, opts, *args): - """${cmd_name}: Show meta information, or edit it + """ + Show meta information, or edit it Show or edit build service metadata of type . @@ -942,20 +883,18 @@ class Osc(cmdln.Cmdln): When trying to edit a non-existing resource, it is created implicitly. - Examples: osc meta prj PRJ osc meta pkg PRJ PKG osc meta pkg PRJ PKG -e - Usage: + usage: osc meta [-r|--revision REV] ARGS... osc meta ARGS... osc meta [-m|--message TEXT] -e|--edit ARGS... osc meta [-m|--message TEXT] -F|--file ARGS... osc meta pattern --delete PRJ PATTERN osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create [--set ]|--delete|--set ] - ${cmd_option_list} """ args = slash_split(args) @@ -1242,7 +1181,8 @@ class Osc(cmdln.Cmdln): @cmdln.alias("submitreq") @cmdln.alias("submitpac") def do_submitrequest(self, subcmd, opts, *args): - """${cmd_name}: Create request to submit source into another Project + """ + Create request to submit source into another Project [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information on this topic.] @@ -1255,8 +1195,6 @@ class Osc(cmdln.Cmdln): osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG] osc submitpac ... is a shorthand for osc submitreq --cleanup ... - - ${cmd_option_list} """ def _check_service(root): serviceinfo = root.find('serviceinfo') @@ -1460,8 +1398,7 @@ class Osc(cmdln.Cmdln): dst_package = src_package dst_project = self._process_project_name(dst_project) else: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('request')) + self.argparse_error("Incorrect number of arguments.") # check for failed source service u = makeurl(apiurl, ['source', src_project, src_package]) @@ -1685,8 +1622,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. dst_package = src_package dst_project = self._process_project_name(dst_project) else: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('request')) + self.argparse_error("Incorrect number of arguments.") if not opts.nodevelproject: devloc = None @@ -1868,8 +1804,9 @@ Please submit there instead, or use --nodevelproject to force direct submission. return actionxml - @cmdln.option('-a', '--action', action='callback', callback = _actionparser, dest = 'actions', - help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner') +# TODO: fix ValueError: unknown action "callback" +# @cmdln.option('-a', '--action', action='callback', callback = _actionparser, dest = 'actions', +# help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner') @cmdln.option('-m', '--message', metavar='TEXT', help='specify message TEXT') @cmdln.option('-r', '--revision', metavar='REV', @@ -1889,7 +1826,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='proceed without asking.') @cmdln.alias("creq") def do_createrequest(self, subcmd, opts, *args): - """${cmd_name}: create multiple requests with a single command + """ + Create multiple requests with a single command usage: osc creq [OPTIONS] [ @@ -1903,11 +1841,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. ] Option -m works for all types of request, the rest work only for submit. - example: + + Example: osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok This will submit all modified packages under current directory, delete project home:someone:branches:openSUSE:Tools and change the devel project to home:someone:branches:openSUSE:Tools for package osc in project openSUSE:Tools. - ${cmd_option_list} """ src_update = conf.config['submitrequest_on_accept_action'] or None # we should check here for home::branch and default to update, but that would require OBS 1.7 server @@ -1991,7 +1929,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias("reqms") @cmdln.alias("reqbs") def do_requestmaintainership(self, subcmd, opts, *args): - """${cmd_name}: requests to add user as maintainer or bugowner + """ + Requests to add user as maintainer or bugowner usage: osc requestmaintainership # for current user in checked out package @@ -2003,8 +1942,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc requestmaintainership PROJECT PACKAGE group:NAME # request for specified group osc requestbugownership ... # accepts same parameters but uses bugowner role - - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -2077,13 +2014,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias("droprequest") @cmdln.alias("deletereq") def do_deleterequest(self, subcmd, opts, *args): - """${cmd_name}: Request to delete (or 'drop') a package or project + """ + Request to delete (or 'drop') a package or project usage: osc deletereq [-m TEXT] # works in checked out project/package osc deletereq [-m TEXT] PROJECT PACKAGE osc deletereq [-m TEXT] PROJECT [--all|--repository REPOSITORY] - ${cmd_option_list} """ args = slash_split(args) @@ -2137,14 +2074,16 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias("cr") @cmdln.alias("changedevelreq") def do_changedevelrequest(self, subcmd, opts, *args): - """${cmd_name}: Create request to change the devel package definition. + """ + Create request to change the devel package definition [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information on this topic.] See the "request" command for showing and modifying existing requests. - osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE] + usage: + osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE] """ if len(args) == 0 and is_package_dir('.') and find_default_project(): wd = os.curdir @@ -2197,7 +2136,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'declined,new,review' otherwise help='only list requests in one of the comma separated given states (new/review/accepted/revoked/declined) or "all" [default="declined,new,review", or "all", if no args given]') @cmdln.option('-D', '--days', metavar='DAYS', - help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]') + help='only list requests in state "new" or changed in the last DAYS.') @cmdln.option('-U', '--user', metavar='USER', help='requests or reviews limited for the specified USER') @cmdln.option('-G', '--group', metavar='GROUP', @@ -2234,7 +2173,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias("review") # FIXME: rewrite this mess and split request and review def do_request(self, subcmd, opts, *args): - """${cmd_name}: Show or modify requests and reviews + """ + Show or modify requests and reviews [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information on this topic.] @@ -2310,8 +2250,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc review decline [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID osc review reopen [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID osc review supersede [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID SUPERSEDING_ID - - ${cmd_option_list} """ args = slash_split(args) @@ -2338,9 +2276,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. if opts.incoming: conf.config['include_request_from_project'] = False - if args[0] == 'help': - return self.do_help(['help', 'request']) - cmds = ['list', 'ls', 'log', 'show', 'decline', 'reopen', 'clone', 'accept', 'approve', 'cancelapproval', 'approvenew', 'wipe', 'setincident', 'supersede', 'revoke', 'checkout', 'co', 'priorize', 'prioritize'] if subcmd != 'review' and args[0] not in cmds: @@ -2760,11 +2695,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias("createpac") @cmdln.alias("edituser") @cmdln.alias("usermeta") - @cmdln.hide(1) + @cmdln.hide() def do_editmeta(self, subcmd, opts, *args): - """${cmd_name}: - - Obsolete command to edit metadata. Use 'meta' now. + """ + Obsolete command to edit metadata. Use 'meta' now See the help output of 'meta'. """ @@ -2781,7 +2715,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-u', '--unset', action='store_true', help='remove revision in link, it will point always to latest revision') def do_setlinkrev(self, subcmd, opts, *args): - """${cmd_name}: Updates a revision number in a source link. + """ + Updates a revision number in a source link This command adds or updates a specified revision number in a source link. The current revision of the source is used, if no revision number is specified. @@ -2789,7 +2724,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc setlinkrev osc setlinkrev PROJECT [PACKAGE] - ${cmd_option_list} """ args = slash_split(args) @@ -2812,8 +2746,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. elif len(args) == 1: project = self._process_project_name(args[0]) else: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('setlinkrev')) + self.argparse_error("Incorrect number of arguments.") if package: packages = [package] @@ -2830,7 +2763,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_linktobranch(self, subcmd, opts, *args): - """${cmd_name}: Convert a package containing a classic link with patch to a branch + """ + Convert a package containing a classic link with patch to a branch This command tells the server to convert a _link with or without a project.diff to a branch. This is a full copy with a _link file pointing to the branched place. @@ -2838,7 +2772,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc linktobranch # can be used in checked out package osc linktobranch PROJECT PACKAGE - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -2867,7 +2800,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify message TEXT') def do_detachbranch(self, subcmd, opts, *args): - """${cmd_name}: replace a link with its expanded sources + """ + Replace a link with its expanded sources If a package is a link it is replaced with its expanded sources. The link does not exist anymore. @@ -2875,7 +2809,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc detachbranch # can be used in package working copy osc detachbranch PROJECT PACKAGE - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -2924,7 +2857,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-N', '--new-package', action='store_true', help='create a link to a not yet existing package') def do_linkpac(self, subcmd, opts, *args): - """${cmd_name}: "Link" a package to another package + """ + "Link" a package to another package A linked package is a clone of another package, but plus local modifications. It can be cross-project. @@ -2945,15 +2879,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC] - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() if not args or len(args) < 3: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('linkpac')) + self.argparse_error("Incorrect number of arguments.") rev, dummy = parseRevisionOption(opts.revision) vrev = None @@ -2993,7 +2925,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-d', '--disable-publish', action='store_true', help='disable publishing of the aggregated package') def do_aggregatepac(self, subcmd, opts, *args): - """${cmd_name}: "Aggregate" a package to another package + """ + "Aggregate" a package to another package Aggregation of a package means that the build results (binaries) of a package are basically copied into another project. @@ -3008,14 +2941,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc aggregatepac SOURCEPRJ SOURCEPAC[:FLAVOR] DESTPRJ [DESTPAC] - ${cmd_option_list} """ args = slash_split(args) if not args or len(args) < 3: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('aggregatepac')) + self.argparse_error("Incorrect number of arguments.") src_project = self._process_project_name(args[0]) src_package = args[1] @@ -3056,7 +2987,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-e', '--expand', action='store_true', help='if the source package is a link then copy the expanded version of the link') def do_copypac(self, subcmd, opts, *args): - """${cmd_name}: Copy a package + """ + Copy a package A way to copy package to somewhere else. @@ -3071,14 +3003,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC] - ${cmd_option_list} """ args = slash_split(args) if not args or len(args) < 3: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('copypac')) + self.argparse_error("Incorrect number of arguments.") src_project = self._process_project_name(args[0]) src_package = args[1] @@ -3142,15 +3072,14 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--no-delay', action='store_true', help="Don't put the release job in a queue to be run later, but immediately run it. Thus the next call to osc prjresult will reflect it. Otherwise there is no way to know if it is finished or didn't start yet.") def do_release(self, subcmd, opts, *args): - """${cmd_name}: Release sources and binaries + """ + Release sources and binaries This command is used to transfer sources and binaries without rebuilding them. It requires defined release targets set to trigger="manual". usage: osc release [ SOURCEPROJECT [ SOURCEPACKAGE ] ] - - ${cmd_option_list} """ args = slash_split(args) @@ -3202,7 +3131,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-p', '--package', metavar='PKG', action='append', help='specify packages to release') def do_releaserequest(self, subcmd, opts, *args): - """${cmd_name}: Create a release request + """ + Create a release request For maintenance incident projects: @@ -3222,8 +3152,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc releaserequest [-p package] [ SOURCEPROJECT ] - - ${cmd_option_list} """ # FIXME: additional parameters can be a certain repo list to create a partitial release @@ -3271,7 +3199,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify message TEXT') def do_createincident(self, subcmd, opts, *args): - """${cmd_name}: Create a maintenance incident + """ + Create a maintenance incident [See http://openbuildservice.org/help/manuals/obs-reference-guide/cha.obs.maintenance_setup.html for information on this topic.] @@ -3283,7 +3212,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc createincident [ MAINTENANCEPROJECT ] - ${cmd_option_list} """ args = slash_split(args) @@ -3345,7 +3273,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Superseding another request by this one') @cmdln.alias("mr") def do_maintenancerequest(self, subcmd, opts, *args): - """${cmd_name}: Create a request for starting a maintenance incident. + """ + Create a request for starting a maintenance incident [See http://openbuildservice.org/help/manuals/obs-reference-guide/cha.obs.maintenance_setup.html for information on this topic.] @@ -3362,7 +3291,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. package (the one in the current directory) from the project of this package to be submitted to the release project this package links to. This syntax is only valid when specified from a package subdirectory. - ${cmd_option_list} """ #FIXME: the follow syntax would make more sense and would obsolete the --release-project parameter # but is incompatible with the current one @@ -3478,7 +3406,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('sm') @cmdln.alias('maintained') def do_mbranch(self, subcmd, opts, *args): - """${cmd_name}: Search or branch multiple instances of a package + """ + Search or branch multiple instances of a package This command is used for searching all relevant instances of packages and creating links of them in one project. @@ -3499,7 +3428,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc sm [SOURCEPACKAGE] [-a ATTRIBUTE] osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ] - ${cmd_option_list} """ args = slash_split(args) apiurl = self.get_api_url() @@ -3597,7 +3525,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--add-repositories-rebuild', metavar='add_repositories_rebuild', help='specify the used rebuild strategy for new repositories') def do_branch(self, subcmd, opts, *args): - """${cmd_name}: Branch a package + """ + Branch a package [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information on this topic.] @@ -3624,7 +3553,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE osc getpac SOURCEPACKAGE osc bco ... - ${cmd_option_list} """ if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': @@ -3727,7 +3655,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify log message TEXT') def do_undelete(self, subcmd, opts, *args): - """${cmd_name}: Restores a deleted project or package on the server. + """ + Restores a deleted project or package on the server The server restores a package including the sources and meta configuration. Binaries remain to be lost and will be rebuild. @@ -3735,8 +3664,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc undelete PROJECT osc undelete PROJECT PACKAGE [PACKAGE ...] - - ${cmd_option_list} """ args = slash_split(args) @@ -3767,7 +3694,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify log message TEXT') def do_rdelete(self, subcmd, opts, *args): - """${cmd_name}: Delete a project or packages on the server. + """ + Delete a project or packages on the server As a safety measure, project must be empty (i.e., you need to delete all packages first). Also, packages must have no requests pending (i.e., you need @@ -3779,8 +3707,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc rdelete [-r] [-f] PROJECT [PACKAGE] - - ${cmd_option_list} """ args = slash_split(args) @@ -3830,12 +3756,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify log message TEXT') def do_lock(self, subcmd, opts, project, package=None): - """${cmd_name}: Locks a project or package. + """ + Locks a project or package usage: osc lock PROJECT [PACKAGE] - - ${cmd_option_list} """ apiurl = self.get_api_url() kind = 'prj' @@ -3859,14 +3784,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-m', '--message', metavar='TEXT', help='specify log message TEXT') def do_unlock(self, subcmd, opts, *args): - """${cmd_name}: Unlocks a project or package + """ + Unlocks a project or package Unlocks a locked project or package. A comment is required. usage: osc unlock PROJECT [PACKAGE] - - ${cmd_option_list} """ args = slash_split(args) @@ -3897,12 +3821,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. else: unlock_project(apiurl, prj, msg) - - @cmdln.hide(1) + @cmdln.hide() def do_deletepac(self, subcmd, opts, *args): - """${cmd_name}: - - Obsolete command to delete package. Use 'delete' or 'rdelete' now. + """ + Obsolete command to delete package. Use 'delete' or 'rdelete' now See the help output of 'delete' and 'rdelete'. """ @@ -3916,12 +3838,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. return 2 - @cmdln.hide(1) + @cmdln.hide() @cmdln.option('-f', '--force', action='store_true', help='deletes a project and its packages') def do_deleteprj(self, subcmd, opts, project): - """${cmd_name}: - + """ Obsolete command to delete project. Use 'rdelete' now. See the help output of 'rdelete'. @@ -3936,12 +3857,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('', '--specfile', metavar='FILE', help='Path to specfile. (if you pass more than working copy this option is ignored)') def do_updatepacmetafromspec(self, subcmd, opts, *args): - """${cmd_name}: Update package meta information from a specfile + """ + Update package meta information from a specfile ARG, if specified, is a package working copy. - - ${cmd_usage} - ${cmd_option_list} """ args = parseargs(args) @@ -3977,12 +3896,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-u', '--unexpand', action='store_true', help='Local changes only, ignore changes in linked package sources') def do_diff(self, subcmd, opts, *args): - """${cmd_name}: Generates a diff + """ + Generates a diff Generates a diff, comparing local changes against the repository server. - ${cmd_usage} + usage: ARG, if specified, is a filename to include in the diff. Default: all files. @@ -3993,8 +3913,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc diff --link PROJ PACK osc linkdiff PROJ PACK Compare a package against the link base (ignoring working copy changes). - - ${cmd_option_list} """ if (subcmd == 'ldiff' or subcmd == 'linkdiff'): @@ -4080,7 +3998,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--xml', action='store_true', help='show diff as xml (only for issues diff)') def do_rdiff(self, subcmd, opts, *args): - """${cmd_name}: Server-side "pretty" diff of two packages + """ + Server-side "pretty" diff of two packages Compares two packages (three or four arguments) or shows the changes of a specified revision of a package (two arguments) @@ -4088,9 +4007,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. If no revision is specified the latest revision is used. usage: - osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC] - osc ${cmd_name} PROJECT PACKAGE - ${cmd_option_list} + osc rdiff OLDPRJ OLDPAC NEWPRJ [NEWPAC] + osc rdiff PROJECT PACKAGE """ args = slash_split(args) @@ -4239,14 +4157,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. dest='nomissingok', help='fail if the parent package does not exist on the server') def do_pdiff(self, subcmd, opts, *args): - """${cmd_name}: Quick alias to diff the content of a package with its parent. + """ + Quick alias to diff the content of a package with its parent - Usage: + usage: osc pdiff [--plain|-p] [--nomissing-ok|-n] osc pdiff [--plain|-p] [--nomissing-ok|-n] PKG osc pdiff [--plain|-p] [--nomissing-ok|-n] PRJ PKG - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -4379,7 +4296,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='show diffstat of differences') def do_prdiff(self, subcmd, opts, *args): - """${cmd_name}: Server-side diff of two projects + """ + Server-side diff of two projects Compares two projects and either summarizes or outputs the differences in full. In the second form, a project is compared @@ -4387,11 +4305,9 @@ Please submit there instead, or use --nodevelproject to force direct submission. is treated as NEWPRJ). The home branch is optional if the current working directory is a checked out copy of it. - Usage: + usage: osc prdiff [OPTIONS] OLDPRJ NEWPRJ osc prdiff [OPTIONS] [home:$USER:branch:$PRJ] - - ${cmd_option_list} """ if len(args) > 2: @@ -4466,14 +4382,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_repourls(self, subcmd, opts, *args): - """${cmd_name}: Shows URLs of .repo files + """ + Shows URLs of .repo files Shows URLs on which to access the project repositories. usage: osc repourls [PROJECT] - - ${cmd_option_list} """ import tempfile @@ -4520,12 +4435,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_browse(self, subcmd, opts, *args): - """${cmd_name}: opens browser + """ + Opens browser usage: osc browse [PROJECT [PACKAGE]] - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -4586,7 +4500,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Skip all files with a given size') @cmdln.alias('co') def do_checkout(self, subcmd, opts, *args): - """${cmd_name}: Check out content from the repository + """ + Check out content from the repository Check out content from the repository server, creating a local working copy. @@ -4609,8 +4524,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. with the result of rpm -q --qf '%%{DISTURL}\\n' PACKAGE osc co obs://API/PROJECT/PLATFORM/REVISION-PACKAGE - - ${cmd_option_list} """ if opts.unexpand_link: @@ -4619,8 +4532,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. expand_link = True if not args: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('checkout')) + self.argparse_error("Incorrect number of arguments.") # A DISTURL can be found in build results to be able to relocate the source used to build # obs://$OBS_INSTANCE/$PROJECT/$REPOSITORY/$XSRCMD5-$PACKAGE(:$FLAVOR) @@ -4747,8 +4659,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. print_request_list(apiurl, project) else: - raise oscerr.WrongArgs('Missing argument.\n\n' \ - + self.get_cmd_help('checkout')) + self.argparse_error("Incorrect number of arguments.") @cmdln.option('-q', '--quiet', action='store_true', @@ -4760,7 +4671,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. '"exclude_glob" config option') @cmdln.alias('st') def do_status(self, subcmd, opts, *args): - """${cmd_name}: Show status of files in working copy + """ + Show status of files in working copy Show the status of files in a local working copy, indicating whether files have been changed locally, deleted, added, ... @@ -4785,7 +4697,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc status [OPTS] [PATH...] - ${cmd_option_list} """ if opts.quiet and opts.verbose: @@ -4828,7 +4739,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-f', '--force', action='store_true', help='add files even if they are excluded by the exclude_glob config option') def do_add(self, subcmd, opts, *args): - """${cmd_name}: Mark files to be added upon the next commit + """ + Mark files to be added upon the next commit In case a URL is given the file will get downloaded and registered to be downloaded by the server as well via the download_url source service. @@ -4839,11 +4751,9 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc add URL [URL...] osc add FILE [FILE...] - ${cmd_option_list} """ if not args: - raise oscerr.WrongArgs('Missing argument.\n\n' \ - + self.get_cmd_help('add')) + self.argparse_error("Incorrect number of arguments.") # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it for arg in parseargs(args): @@ -4858,11 +4768,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_mkpac(self, subcmd, opts, *args): - """${cmd_name}: Create a new package under version control + """ + Create a new package under version control usage: osc mkpac new_package - ${cmd_option_list} """ if not conf.config['do_package_tracking']: print("to use this feature you have to enable \'do_package_tracking\' " \ @@ -4878,14 +4788,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='If CWD is a project dir then scan all package dirs as well') @cmdln.alias('ar') def do_addremove(self, subcmd, opts, *args): - """${cmd_name}: Adds new files, removes disappeared files + """ + Adds new files, removes disappeared files Adds all files new in the local copy, and removes all disappeared files. ARG, if specified, is a package working copy. - - ${cmd_usage} - ${cmd_option_list} """ args = parseargs(args) @@ -4952,7 +4860,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--skip-local-service-run', '--noservice', default=False, action="store_true", help='Skip service run of configured source services for local run') def do_commit(self, subcmd, opts, *args): - """${cmd_name}: Upload content to the repository server + """ + Upload content to the repository server Upload content which is changed in your working copy, to the repository server. @@ -4961,9 +4870,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc ci # current dir osc ci osc ci file1 file2 ... - - ${cmd_usage} - ${cmd_option_list} """ try: self._commit(subcmd, opts, args) @@ -5125,7 +5031,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Skip all files with a given size') @cmdln.alias('up') def do_update(self, subcmd, opts, *args): - """${cmd_name}: Update a working copy + """ + Update a working copy examples: @@ -5145,9 +5052,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. sources will be checked out. Without this option, the _link file and patches will be checked out. The option --unexpand-link can be used to switch back to the "raw" source with a _link file plus patch(es). - - ${cmd_usage} - ${cmd_option_list} """ if opts.expand_link and opts.unexpand_link: @@ -5256,7 +5160,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('del') @cmdln.alias('remove') def do_delete(self, subcmd, opts, *args): - """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin' + """ + Mark files or package directories to be deleted upon the next 'checkin' usage: cd .../PROJECT/PACKAGE @@ -5272,13 +5177,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. If you are sure that you want to remove a package and all its files use \'--force\' switch. Sometimes this also works without --force. - - ${cmd_option_list} """ if not args: - raise oscerr.WrongArgs('Missing argument.\n\n' \ - + self.get_cmd_help('delete')) + self.argparse_error("Incorrect number of arguments.") args = parseargs(args) # check if args contains a package which was removed by @@ -5325,7 +5227,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_resolved(self, subcmd, opts, *args): - """${cmd_name}: Remove 'conflicted' state on working copy files + """ + Remove 'conflicted' state on working copy files If an upstream change can't be merged automatically, a file is put into in 'conflicted' ('C') state. Within the file, conflicts are marked with @@ -5340,12 +5243,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc resolved FILE [FILE...] - ${cmd_option_list} """ if not args: - raise oscerr.WrongArgs('Missing argument.\n\n' \ - + self.get_cmd_help('resolved')) + self.argparse_error("Incorrect number of arguments.") args = parseargs(args) pacs = findpacs(args) @@ -5358,15 +5259,14 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('dists') def do_distributions(self, subcmd, opts, *args): - """${cmd_name}: Shows all available distributions + """ + Shows all available distributions This command shows the available distributions. For active distributions it shows the name, project and name of the repository and a suggested default repository name. usage: osc distributions - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -5379,10 +5279,9 @@ Please submit there instead, or use --nodevelproject to force direct submission. print(format_table(rows, headers).rstrip()) - @cmdln.hide(1) + @cmdln.hide() def do_results_meta(self, subcmd, opts, *args): - """${cmd_name}: - + """ Obsolete command to show build results. Use 'results --xml' now. See the help output of 'results'. @@ -5393,7 +5292,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. print("See 'osc help results'.", file=sys.stderr) return 2 - @cmdln.hide(1) + @cmdln.hide() @cmdln.option('-l', '--last-build', action='store_true', help='show last build results (succeeded/failed/unknown)') @cmdln.option('-r', '--repo', action='append', default = [], @@ -5403,9 +5302,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('', '--xml', action='store_true', help='generate output in XML (former results_meta)') def do_rresults(self, subcmd, opts, *args): - """${cmd_name}: - - Obsolete command to show build results. Use 'results' now. + """ + Obsolete command to show build results. Use 'results' now See the help output of 'results'. """ @@ -5418,10 +5316,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-f', '--force', action='store_true', default=False, help="Don't ask and delete files") def do_rremove(self, subcmd, opts, project, package, *files): - """${cmd_name}: Remove source files from selected package - - ${cmd_usage} - ${cmd_option_list} + """ + Remove source files from selected package """ apiurl = self.get_api_url() @@ -5483,13 +5379,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--show-excluded', action='store_true', help='show repos that are excluded for this package') def do_results(self, subcmd, opts, *args): - """${cmd_name}: Shows the build results of a package or project + """ + Shows the build results of a package or project - Usage: + usage: osc results # (inside working copy of PRJ or PKG) osc results PROJECT [PACKAGE[:FLAVOR]] - - ${cmd_option_list} """ args = slash_split(args) @@ -5583,13 +5478,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='show packages that are excluded in all repos, also hide repos that have only excluded packages') @cmdln.alias('pr') def do_prjresults(self, subcmd, opts, *args): - """${cmd_name}: Shows project-wide build results + """ + Shows project-wide build results - Usage: + usage: osc prjresults (inside working copy) osc prjresults PROJECT - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -5631,11 +5525,9 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='show only packages with buildstatus STATUS (see legend)') @cmdln.option('-n', '--name-filter', metavar='EXPR', help='show only packages whose names match EXPR') - - @cmdln.hide(1) + @cmdln.hide() def do_rprjresults(self, subcmd, opts, *args): - """${cmd_name}: - + """ Obsolete command to show project-wide build results. Use 'prjresults' now. See the help output of 'prjresults'. @@ -5649,7 +5541,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('rpmlint') @cmdln.alias('lint') def do_rpmlintlog(self, subcmd, opts, *args): - """${cmd_name}: Shows the rpmlint logfile + """ + Shows the rpmlint logfile Shows the rpmlint logfile to analyse if there are any problems with the spec file and the built binaries. @@ -5681,7 +5574,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-s', '--strip-time', action='store_true', help='strip leading build time from the log') def do_buildlog(self, subcmd, opts, *args): - """${cmd_name}: Shows the build log of a package + """ + Shows the build log of a package Shows the log file of the build of a package. Can be used to follow the log while it is being written. @@ -5694,8 +5588,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. results' output. If the buildlog url is used buildlog command has the same behavior as remotebuildlog. - ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL] - ${cmd_option_list} + buildlog [REPOSITORY ARCH | BUILDLOGURL] """ import osc.build @@ -5790,7 +5683,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-s', '--strip-time', action='store_true', help='strip leading build time from the log') def do_remotebuildlog(self, subcmd, opts, *args): - """${cmd_name}: Shows the build log of a package + """ + Shows the build log of a package Shows the log file of the build of a package. Can be used to follow the log while it is being written. @@ -5803,7 +5697,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc remotebuildlog project/package[:flavor]/repository/arch or osc remotebuildlog buildlogurl - ${cmd_option_list} """ if len(args) == 1 and args[0].startswith('http'): apiurl, project, package, repository, arch = parse_buildlogurl(args[0]) @@ -5869,13 +5762,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-s', '--strip-time', action='store_true', help='strip leading build time from the log') def do_localbuildlog(self, subcmd, opts, *args): - """${cmd_name}: Shows the build log of a local buildchroot + """ + Shows the build log of a local buildchroot usage: osc lbl [REPOSITORY [ARCH]] osc lbl # show log of newest last local build - - ${cmd_option_list} """ if conf.config['build-type']: # FIXME: raise Exception instead @@ -5924,7 +5816,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-M', '--multibuild-package', metavar='FLAVOR', help=HELP_MULTIBUILD_ONE) @cmdln.alias('tr') def do_triggerreason(self, subcmd, opts, *args): - """${cmd_name}: Show reason why a package got triggered to build + """ + Show reason why a package got triggered to build The server decides when a package needs to get rebuild, this command shows the detailed reason for a package. A brief reason is also stored @@ -5939,8 +5832,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage in package or project directory: osc triggerreason REPOSITORY ARCH osc triggerreason PROJECT PACKAGE[:FLAVOR] REPOSITORY ARCH - - ${cmd_option_list} """ wd = os.curdir args = slash_split(args) @@ -5989,8 +5880,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. # FIXME: the new osc syntax should allow to specify multiple packages # FIXME: the command should optionally use buildinfo data to show all dependencies def do_dependson(self, subcmd, opts, *args): - """${cmd_name}: dependson shows the build dependencies inside of a project, valid for a - given repository and architecture. + """ + Dependson shows the build dependencies inside of a project, valid for a given repository and architecture The command can be used to find build dependencies (wrt. a given repository and arch) that reside in the same project. To see all build dependencies use the buildinfo command. @@ -6009,15 +5900,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc dependson PROJECT [PACKAGE] REPOSITORY ARCH - - ${cmd_option_list} """ self._dependson(False, *args) def do_whatdependson(self, subcmd, opts, *args): - """${cmd_name}: Show the packages that require the specified package during the - build. + """ + Show the packages that require the specified package during the build The command whatdependson can be used to find out what will be triggered when a certain package changes. @@ -6032,8 +5921,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH - - ${cmd_option_list} """ self._dependson(True, *args) @@ -6091,7 +5978,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-p', '--prefer-pkgs', metavar='DIR', action='append', help='Prefer packages from this directory when installing the build-root') def do_buildinfo(self, subcmd, opts, *args): - """${cmd_name}: Shows the build info + """ + Shows the build info Shows the build "info" which is used in building a package. This command is mostly used internally by the 'build' subcommand. @@ -6121,8 +6009,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. Note: if BUILD_DESCR does not exist locally the remote BUILD_DESCR is used osc buildinfo [OPTS] PROJECT PACKAGE[:FLAVOR] REPOSITORY ARCH [BUILD_DESCR] - - ${cmd_option_list} """ wd = os.curdir args = slash_split(args) @@ -6177,7 +6063,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_buildconfig(self, subcmd, opts, *args): - """${cmd_name}: Shows the build config + """ + Shows the build config Shows the build configuration which is used in building a package. This command is mostly used internally by the 'build' command. @@ -6192,7 +6079,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc buildconfig REPOSITORY (in pkg or prj dir) osc buildconfig PROJECT REPOSITORY - ${cmd_option_list} """ wd = os.curdir @@ -6220,15 +6106,14 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_workerinfo(self, subcmd, opts, worker): - """${cmd_name}: gets the information to a worker from the server + """ + Gets the information to a worker from the server Examples: osc workerinfo x86_64:goat:1 - Usage: + usage: osc workerinfo : - - ${cmd_option_list} """ apiurl = self.get_api_url() print(''.join(get_worker_info(apiurl, worker))) @@ -6237,7 +6122,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('', '--ignore-file', action='store_true', help='ignore _constraints file and only check project constraints') def do_checkconstraints(self, subcmd, opts, *args): - """${cmd_name}: check the constraints and view compliant workers + """ + Check the constraints and view compliant workers Checks the constraints for compliant workers. @@ -6249,8 +6135,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc checkconstraints [OPTS] REPOSITORY ARCH CONSTRAINTSFILE osc checkconstraints [OPTS] CONSTRAINTSFILE osc checkconstraints [OPTS] - - ${cmd_option_list} """ repository = arch = constraintsfile = None args = slash_split(args) @@ -6305,14 +6189,14 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('repos') @cmdln.alias('platforms') def do_repositories(self, subcmd, opts, *args): - """${cmd_name}: shows repositories configured for a project. - It skips repositories by default which are disabled for a given package. + """ + Shows repositories configured for a project + + It skips repositories by default which are disabled for a given package. usage: osc repos osc repos [PROJECT] [PACKAGE] - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -6640,7 +6524,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('shell') @cmdln.alias('wipe') def do_build(self, subcmd, opts, *args): - """${cmd_name}: Build a package on your local machine + """ + Build a package on your local machine The command works from a package checkout (local changes are fine). @@ -6703,8 +6588,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. # OSC_SU_WRAPPER overrides the setting of su-wrapper. # OSC_BUILD_ROOT overrides the setting of build-root. # OSC_PACKAGECACHEDIR overrides the setting of packagecachedir. - - ${cmd_option_list} """ import osc.build @@ -6955,7 +6838,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help=HELP_MULTIBUILD_ONE) @cmdln.alias('buildhist') def do_buildhistory(self, subcmd, opts, *args): - """${cmd_name}: Shows the build history of a package + """ + Shows the build history of a package The arguments REPOSITORY and ARCH can be taken from the first two columns of the 'osc repos' output. @@ -6963,7 +6847,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc buildhist REPOSITORY ARCHITECTURE osc buildhist PROJECT PACKAGE[:FLAVOR] REPOSITORY ARCHITECTURE - ${cmd_option_list} """ args = slash_split(args) @@ -7004,7 +6887,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help=HELP_MULTIBUILD_ONE) @cmdln.alias('jobhist') def do_jobhistory(self, subcmd, opts, *args): - """${cmd_name}: Shows the job history of a project + """ + Shows the job history of a project The arguments REPOSITORY and ARCH can be taken from the first two columns of the 'osc repos' output. @@ -7012,7 +6896,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc jobhist REPOSITORY ARCHITECTURE (in project dir) osc jobhist PROJECT [PACKAGE[:FLAVOR]] REPOSITORY ARCHITECTURE - ${cmd_option_list} """ wd = os.curdir args = slash_split(args) @@ -7053,11 +6936,10 @@ Please submit there instead, or use --nodevelproject to force direct submission. print_jobhistory(apiurl, project, package, repository, arch, format, opts.limit) - @cmdln.hide(1) + @cmdln.hide() def do_rlog(self, subcmd, opts, *args): - """${cmd_name}: - - Obsolete command to show commit logs. Use 'log' now. + """ + Obsolete command to show commit logs. Use 'log' now See the help output of 'log'. """ @@ -7078,13 +6960,12 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-M', '--meta', action='store_true', help='checkout out meta data instead of sources' ) def do_log(self, subcmd, opts, *args): - """${cmd_name}: Shows the commit log of a package + """ + Shows the commit log of a package - Usage: + usage: osc log (inside working copy) osc log remote_project [remote_package] - - ${cmd_option_list} """ args = slash_split(args) @@ -7126,7 +7007,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. run_pager(log) def do_service(self, subcmd, opts, *args): - """${cmd_name}: Handle source services + """ + Handle source services Source services can be used to modify sources like downloading files, verify files, generating files or modify existing files. @@ -7156,8 +7038,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. Not for common usage anymore: localrun lr the same as "run" but services with mode "serveronly" are also executed disabledrun dr run all services with mode "disabled" - - ${cmd_option_list} """ # disabledrun and localrun exists as well, but are considered to be obsolete @@ -7231,7 +7111,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Rebuild all packages of entire project') @cmdln.alias('rebuildpac') def do_rebuild(self, subcmd, opts, *args): - """${cmd_name}: Trigger package rebuilds + """ + Trigger package rebuilds Note that it is normally NOT needed to kick off rebuilds like this, because they principally happen in a fully automatic way, triggered by source @@ -7243,7 +7124,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc rebuild [PROJECT [PACKAGE[:FLAVOR] [REPOSITORY [ARCH]]]] - ${cmd_option_list} """ args = slash_split(args) @@ -7294,13 +7174,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_info(self, subcmd, opts, *args): - """${cmd_name}: Print information about a working copy + """ + Print information about a working copy Print information about each ARG (default: '.') ARG is a working-copy path. - - ${cmd_usage} - ${cmd_option_list} """ args = parseargs(args) @@ -7312,7 +7190,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-M', '--multibuild-package', metavar='FLAVOR', action='append', help=HELP_MULTIBUILD_MANY) def do_sendsysrq(self, subcmd, opts, *args): - """${cmd_name}: trigger a sysrq in a running build + """ + Trigger a sysrq in a running build This is only going to work when the build is running in a supported VM. Also only a subset of sysrq are supported. Typical use case for debugging @@ -7321,7 +7200,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. usage: osc sendsysrq REPOSITORY ARCH SYSRQ osc sendsysrq PROJECT PACKAGE[:FLAVOR] REPOSITORY ARCH SYSRQ - ${cmd_option_list} """ args = slash_split(args) @@ -7366,11 +7244,11 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Restart all running builds of entire project') @cmdln.alias('abortbuild') def do_restartbuild(self, subcmd, opts, *args): - """${cmd_name}: Restart the build of a certain project or package + """ + Restart the build of a certain project or package usage: osc restartbuild [PROJECT [PACKAGE[:FLAVOR] [REPOSITORY [ARCH]]]] - ${cmd_option_list} """ args = slash_split(args) @@ -7434,7 +7312,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Delete all binaries regardless of the package status (previously default)') @cmdln.alias("unpublish") def do_wipebinaries(self, subcmd, opts, *args): - """${cmd_name}: Delete all binary packages of a certain project/package + """ + Delete all binary packages of a certain project/package With the optional argument you can specify a certain package otherwise all binary packages in the project will be deleted. @@ -7444,7 +7323,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc wipebinaries OPTS PROJECT [PACKAGE[:FLAVOR]] osc unpublish OPTS # works in checked out project dir osc unpublish OPTS PROJECT [PACKAGE[:FLAVOR]] - ${cmd_option_list} """ args = slash_split(args) @@ -7517,7 +7395,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--ccache', action="store_true", help='allow fetching ccache archive') def do_getbinaries(self, subcmd, opts, *args): - """${cmd_name}: Download binaries to a local directory + """ + Download binaries to a local directory This command downloads packages directly from the api server. Thus, it directly accesses the packages that are used for building @@ -7529,7 +7408,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc getbinaries PROJECT REPOSITORY ARCHITECTURE osc getbinaries PROJECT PACKAGE REPOSITORY ARCHITECTURE osc getbinaries PROJECT PACKAGE REPOSITORY ARCHITECTURE FILE - ${cmd_option_list} """ args = slash_split(args) @@ -7540,7 +7418,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. binary = None if opts.multibuild_package and ((len(args) > 2) or (len(args) <= 2 and is_project_dir(os.getcwd()))): - self.optparser.error("The -M/--multibuild-package option can be only used from a package checkout.") + self.argparse_error("The -M/--multibuild-package option can be only used from a package checkout.") if len(args) < 1 and is_package_dir('.'): self.print_repos() @@ -7656,27 +7534,28 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--maintained', action='store_true', help='limit search results to packages with maintained attribute set.') def do_my(self, subcmd, opts, *args): - """${cmd_name}: show waiting work, packages, projects or requests involving yourself + """ + Show waiting work, packages, projects or requests involving yourself - Examples: - # list all open tasks for me - osc ${cmd_name} [work] - # list packages where I am bugowner - osc ${cmd_name} pkg -b - # list projects where I am maintainer - osc ${cmd_name} prj -m - # list request for all my projects and packages - osc ${cmd_name} rq - # list requests, excluding project 'foo' and 'bar' - osc ${cmd_name} rq --exclude-project foo,bar - # list requests I made - osc ${cmd_name} sr + Examples: + # list all open tasks for me + osc my [work] + # list packages where I am bugowner + osc my pkg -b + # list projects where I am maintainer + osc my prj -m + # list request for all my projects and packages + osc my rq + # list requests, excluding project 'foo' and 'bar' + osc my rq --exclude-project foo,bar + # list requests I made + osc my sr - ${cmd_usage} - where TYPE is one of requests, submitrequests, - projects or packages (rq, sr, prj or pkg) + usage: + osc my - ${cmd_option_list} + where TYPE is one of requests, submitrequests, + projects or packages (rq, sr, prj or pkg) """ # TODO: please clarify the difference between sr and rq. @@ -7911,7 +7790,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('se') @cmdln.alias('bse') def do_search(self, subcmd, opts, *args): - """${cmd_name}: Search for a project and/or package. + """ + Search for a project and/or package If no option is specified osc will search for projects and packages which contains the \'search term\' in their name, @@ -7921,7 +7801,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc search \'search term\' osc bse ... ('osc search --binary') osc se 'perl(Foo::Bar)' ('osc search --package perl-Foo-Bar') - ${cmd_option_list} """ def build_xpath(attr, what, substr = False): if substr: @@ -8146,7 +8025,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-c', '--commit', action='store_true', help='commit the new files') def do_importsrcpkg(self, subcmd, opts, srpm): - """${cmd_name}: Import a new package from a src.rpm + """ + Import a new package from a src.rpm A new package dir will be created inside the project dir (if no project is specified and the current working dir is a @@ -8159,9 +8039,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. SRPM is the path of the src.rpm in the local filesystem, or an URL. - - ${cmd_usage} - ${cmd_option_list} """ import glob from .util import rpmquery @@ -8286,7 +8163,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. nargs=2, action='append', dest='headers', help='add the specified header to the request') def do_api(self, subcmd, opts, url): - """${cmd_name}: Issue an arbitrary request to the API + """ + Issue an arbitrary request to the API Useful for testing. @@ -8299,9 +8177,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc api /source/home:user osc api -X PUT -T /etc/fstab source/home:user/test5/myfstab osc api -e /configuration - - ${cmd_usage} - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -8375,17 +8250,18 @@ Please submit there instead, or use --nodevelproject to force direct submission. help='Define message as commit entry or request description') @cmdln.alias('bugowner') def do_maintainer(self, subcmd, opts, *args): - """${cmd_name}: Show maintainers according to server side configuration + """ + Show maintainers according to server side configuration - # Search for official maintained sources in OBS instance - osc maintainer BINARY_OR_PACKAGE_NAME - osc maintainer -U - osc maintainer -G + # Search for official maintained sources in OBS instance + osc maintainer BINARY_OR_PACKAGE_NAME + osc maintainer -U + osc maintainer -G - # Lookup via containers - osc maintainer - osc maintainer PRJ - osc maintainer PRJ PKG + # Lookup via containers + osc maintainer + osc maintainer PRJ + osc maintainer PRJ PKG The tool looks up the default responsible person for a certain project or package. When using with an OBS 2.4 (or later) server it is doing the lookup for @@ -8397,9 +8273,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. Please use "osc meta pkg" in case you need to know the definition in a specific container. PRJ and PKG default to current working-copy path. - - ${cmd_usage} - ${cmd_option_list} """ def get_maintainer_data(apiurl, maintainer, verbose=False): tags = ('email',) @@ -8648,10 +8521,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('who') @cmdln.alias('user') def do_whois(self, subcmd, opts, *usernames): - """${cmd_name}: Show fullname and email of a buildservice user - - ${cmd_usage} - ${cmd_option_list} + """ + Show fullname and email of a buildservice user """ apiurl = self.get_api_url() if len(usernames) < 1: @@ -8677,7 +8548,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.alias('blame') @cmdln.alias('less') def do_cat(self, subcmd, opts, *args): - """${cmd_name}: Output the content of a file to standard output + """ + Output the content of a file to standard output Examples: osc cat file @@ -8691,9 +8563,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc blame file osc blame project package file - - ${cmd_usage} - ${cmd_option_list} """ if len(args) == 1 and (args[0].startswith('http://') or @@ -8758,7 +8627,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-d', '--destdir', default='repairlink', metavar='DIR', help='destination directory') def do_repairlink(self, subcmd, opts, *args): - """${cmd_name}: Repair a broken source link + """ + Repair a broken source link This command checks out a package with merged source changes. It uses a 3-way merge to resolve file conflicts. After reviewing/repairing @@ -8774,8 +8644,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. * Pull conflicting changes from one project into another one: osc repairlink PROJECT PACKAGE INTO_PROJECT [INTO_PACKAGE] - - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -8957,9 +8825,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. def do_pull(self, subcmd, opts, *args): - """${cmd_name}: merge the changes of the link target into your working copy. - - ${cmd_option_list} + """ + Merge the changes of the link target into your working copy """ if not is_package_dir('.'): @@ -9097,7 +8964,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--sslcert', action='store_true', default=False, help='fetch SSL certificate instead of GPG key') def do_signkey(self, subcmd, opts, *args): - """${cmd_name}: Manage Project Signing Key + """ + Manage Project Signing Key osc signkey [--create|--delete|--extend] osc signkey [--notraverse] @@ -9114,9 +8982,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. "KDE:KDE4" will be used). WARNING: THE OLD KEY CANNOT BE RESTORED AFTER USING DELETE OR CREATE - - ${cmd_usage} - ${cmd_option_list} """ apiurl = self.get_api_url() @@ -9181,7 +9046,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-e', '--just-edit', action='store_true', default=False, help='just open changes (cannot be used with -m)') def do_vc(self, subcmd, opts, *args): - """${cmd_name}: Edit the changes file + """ + Edit the changes file osc vc [-m MESSAGE|-e] [filename[.changes]|path [file_with_comment]] If no is given, exactly one *.changes or *.spec file has to @@ -9195,9 +9061,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. email = user@defined.email or can be specified via mailaddr environment variable. - - ${cmd_usage} - ${cmd_option_list} """ from subprocess import Popen @@ -9266,10 +9129,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-f', '--force', action='store_true', help='forces removal of entire package and its files') def do_mv(self, subcmd, opts, source, dest): - """${cmd_name}: Move SOURCE file to DEST and keep it under version control - - ${cmd_usage} - ${cmd_option_list} + """ + Move SOURCE file to DEST and keep it under version control """ if not os.path.isfile(source): @@ -9310,7 +9171,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--dump-full', action='store_true', help='dump the complete configuration (including \'pass\' and \'passx\' options)') def do_config(self, subcmd, opts, *args): - """${cmd_name}: get/set a config option + """ + Get/set a config option Examples: osc config section option (get current value) @@ -9319,9 +9181,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc config section --change-password (changes the password in section "section") (section is either an apiurl or an alias or 'general') osc config --dump (dump the complete configuration) - - ${cmd_usage} - ${cmd_option_list} """ prompt_value = 'Value: ' if opts.change_password: @@ -9395,15 +9254,13 @@ Please submit there instead, or use --nodevelproject to force direct submission. print('\'%s\': \'%s\' is set to \'%s\'' % (section, opt, newval)) def do_revert(self, subcmd, opts, *files): - """${cmd_name}: Restore changed files or the entire working copy. + """ + Restore changed files or the entire working copy Examples: osc revert osc revert . Note: this only works for package working copies - - ${cmd_usage} - ${cmd_option_list} """ pacs = findpacs(files) for p in pacs: @@ -9415,19 +9272,16 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('--force-apiurl', action='store_true', help='ask once for an apiurl and force this apiurl for all inconsistent projects/packages') def do_repairwc(self, subcmd, opts, *args): - """${cmd_name}: try to repair an inconsistent working copy + """ + Try to repair an inconsistent working copy Examples: osc repairwc - Note: if is omitted it defaults to '.' ( can be - a project or package working copy) + Note: if is omitted it defaults to '.' ( can be a project or package working copy) Warning: This command might delete some files in the storedir (.osc). Please check the state of the wc afterwards (via 'osc status'). - - ${cmd_usage} - ${cmd_option_list} """ def get_apiurl(apiurls): print('No apiurl is defined for this working copy.\n' \ @@ -9483,18 +9337,15 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-n', '--dry-run', action='store_true', help='print the results without actually removing a file') def do_clean(self, subcmd, opts, *args): - """${cmd_name}: removes all untracked files from the package working copy + """ + Removes all untracked files from the package working copy Examples: osc clean - Note: if is omitted it defaults to '.' ( has to - be a package working copy) + Note: if is omitted it defaults to '.' ( has to be a package working copy) Warning: This command removes all files with status '?'. - - ${cmd_usage} - ${cmd_option_list} """ pacs = parseargs(args) # do a sanity check first @@ -9520,7 +9371,8 @@ Please submit there instead, or use --nodevelproject to force direct submission. @cmdln.option('-p', '--parent', help='reply to comment with parent id', metavar='PARENT') def do_comment(self, subcmd, opts, *args): - """${cmd_name}: List / create / delete comments + """ + List / create / delete comments On create: If -p is given a reply to the ID is created. Otherwise @@ -9538,7 +9390,6 @@ Please submit there instead, or use --nodevelproject to force direct submission. osc comment create [-p PARENT_ID] [-c COMMENT] request REQUEST_ID osc comment delete ID - """ comment = None @@ -9546,8 +9397,7 @@ Please submit there instead, or use --nodevelproject to force direct submission. apiurl = self.get_api_url() if len(args) < 2: - raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ - + self.get_cmd_help('comment')) + self.argparse_error("Incorrect number of arguments.") cmds = ['list', 'create', 'delete'] if args[0] not in cmds: diff --git a/setup.py b/setup.py index a210c90a..6edfd149 100755 --- a/setup.py +++ b/setup.py @@ -1,69 +1,11 @@ #!/usr/bin/env python3 -import distutils.core -import gzip -import os import setuptools -from distutils.command import build, install_data -import osc.commandline import osc.core -class build_osc(build.build): - """ - Custom build command which generates man page. - """ - - def build_man_page(self): - """ - """ - try: - os.makedirs(self.build_base) - except OSError: - pass - man_path = os.path.join(self.build_base, 'osc.1.gz') - distutils.log.info('generating %s' % man_path) - outfile = gzip.open(man_path, 'wt') - osccli = osc.commandline.Osc(stdout=outfile) - # FIXME: we cannot call the main method because osc expects an ~/.oscrc - # file (this would break builds in environments like the obs) - # osccli.main(argv = ['osc','man']) - osccli.optparser = osccli.get_optparser() - osccli.do_man(None) - outfile.close() - - def run(self): - super().run() - self.build_man_page() - - -# take a potential build-base option into account (for instance, if osc is -# build and installed like this: -# python setup.py build --build-base= ... install ...) -class install_data(install_data.install_data): - def initialize_options(self): - super().initialize_options() - self.built_data = None - - def finalize_options(self): - super().finalize_options() - self.set_undefined_options('build', ('build_base', 'built_data')) - data_files = [] - for f in self.data_files: - # f is either a str or a (dir, files) pair - # (see distutils.command.install_data.install_data.run) - if isinstance(f, str): - data_files.append(os.path.join(self.built_data, f)) - else: - data_files.append((f[0], [os.path.join(self.built_data, i) for i in f[1]])) - self.data_files = data_files - - -data_files = [] -data_files.append((os.path.join('share', 'man', 'man1'), ['osc.1.gz'])) - with open("README.md") as fh: lines = fh.readlines() while lines: @@ -77,8 +19,6 @@ with open("README.md") as fh: long_description = "".join(lines) cmdclass = { - 'build': build_osc, - 'install_data': install_data } # keep build deps minimal and be tolerant to missing sphinx @@ -104,7 +44,6 @@ setuptools.setup( url='http://en.opensuse.org/openSUSE:OSC', download_url='https://github.com/openSUSE/osc', packages=['osc', 'osc.util'], - data_files=data_files, install_requires=['cryptography', 'urllib3'], extras_require={ 'RPM signature verification': ['rpm'],