From d989fa739033bab480df0f7b7fe72761fdddb222 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 29 Apr 2014 22:44:46 +0100 Subject: [PATCH] Implement "x help" and "x apropos" for all command sets (bos, vos, ...) Signed-off-by: David Howells --- suite/apropos.py | 71 ++++++++++++++ suite/argparse.py | 4 +- suite/commands/bos/restart.py | 4 +- suite/commands/vos/examine.py | 2 +- suite/exception.py | 7 +- suite/help.py | 173 ++++++++++++++++++++++++++++++++++ suite/lib/output.py | 4 + suite/main.py | 82 ++++++---------- 8 files changed, 288 insertions(+), 59 deletions(-) create mode 100644 suite/apropos.py create mode 100644 suite/help.py diff --git a/suite/apropos.py b/suite/apropos.py new file mode 100644 index 0000000..04d1198 --- /dev/null +++ b/suite/apropos.py @@ -0,0 +1,71 @@ +# +# AFS Server management toolkit: Command line help search +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs.exception import AFSException +from afs.argparse import * +from afs.lib.output import * + +help = "Search by help text" + +command_arguments = [ + [ "topic", get_string, "rs", "" ], +] + +description = r""" +Search by help text +""" + +def search_help(prog, cmdsetmod, commands, topic): + matches = dict() + for i in commands: + if i == "help": + command = __import__("afs.help", globals(), locals(), ['*']) + if topic in command.help.casefold(): + matches["help"] = command.help + continue + + if i == "apropos": + if topic in help.casefold(): + matches["apropos"] = help + continue + + command = cmdsetmod.get_command(i) + if hasattr(command, "help"): + if topic in command.help.casefold(): + matches[i] = command.help + return matches + +############################################################################### +# +# +# +############################################################################### +def main(params): + matches = search_help(params["_prog"], params["_cmdsetmod"], params["_commands"], + params["topic"].casefold()) + + if not matches: + output("Sorry, no commands found\n") + else: + for i in sorted(matches.keys()): + output(i, ": ", matches[i], "\n") diff --git a/suite/argparse.py b/suite/argparse.py index 01a70b3..66c5513 100644 --- a/suite/argparse.py +++ b/suite/argparse.py @@ -13,7 +13,7 @@ as published by the Free Software Foundation; either version 2 of the Licence, or (at your option) any later version. """ -from exception import AFSArgumentError +from exception import AFSArgumentError, AFSHelpFlag from afs.lib.output import set_verbosity def get_cell(switch, params): @@ -185,7 +185,7 @@ def parse_arguments(args, available_arguments, argument_size_limits, i = i + 1 if switch == "help": - raise AFSArgumentError("Print help message") + raise AFSHelpFlag if switch == "": raise AFSArgumentError("Missing switch name") diff --git a/suite/commands/bos/restart.py b/suite/commands/bos/restart.py index 47f9411..5ffbf65 100644 --- a/suite/commands/bos/restart.py +++ b/suite/commands/bos/restart.py @@ -26,7 +26,7 @@ from afs.argparse import * from afs.lib.output import * import kafs -help = "Restarts a server process" +help = "Restart a server process" command_arguments = [ [ "server", get_bosserver, "rs", "" ], @@ -53,7 +53,7 @@ argument_size_limits = { } description = r""" -Restarts a server process +Restart a server process """ def main(params): diff --git a/suite/commands/vos/examine.py b/suite/commands/vos/examine.py index 7743c5d..253fbd9 100644 --- a/suite/commands/vos/examine.py +++ b/suite/commands/vos/examine.py @@ -61,7 +61,7 @@ def display_vldb(params, vldb): if vldb.volumeId[kafs.ROVOL] != 0: outputf(" ROnly: {:<10d}", vldb.volumeId[kafs.ROVOL]) if vldb.volumeId[kafs.BACKVOL] != 0: - outputf " Backup: {:<10d}", vldb.volumeId[kafs.BACKVOL]) + outputf(" Backup: {:<10d}", vldb.volumeId[kafs.BACKVOL]) output("\n") display_vldb_site_list(params, vldb, " ") diff --git a/suite/exception.py b/suite/exception.py index 88bba14..7d90a8c 100644 --- a/suite/exception.py +++ b/suite/exception.py @@ -3,5 +3,10 @@ class AFSException(Exception): pass class AFSArgumentError(AFSException): - """Base class for all AFS Toolkit exceptions.""" + """An argument parsing error was encountered""" + pass + +class AFSHelpFlag(AFSException): + """The -help flag was specified to a command.""" pass + diff --git a/suite/help.py b/suite/help.py new file mode 100644 index 0000000..068c0b7 --- /dev/null +++ b/suite/help.py @@ -0,0 +1,173 @@ +# +# AFS Server management toolkit: Command line help +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs.exception import AFSException +from afs.argparse import * +from afs.lib.output import * + +help = "Get help on commandsw" + +command_arguments = [ + [ "topic", get_strings, "om", "+" ], + [ "help", get_dummy, "fn" ], +] + +description = r""" +Display the server encryption keys from the KeyFile file +""" + +############################################################################### +# +# Basic command list +# +############################################################################### +def display_command_list(prog, cmdsetmod, commands): + output(prog, ": Commands are:\n") + for i in sorted(commands): + if i == "help": + desc = "Get help on commands" + elif i == "apropos": + desc = "Search by help text" + else: + command = cmdsetmod.get_command(i) + if hasattr(command, "alias"): + desc = "Alias for '" + command.alias + "'" + elif not hasattr(command, "help"): + desc = "** no help **" + else: + desc = command.help + outputf("{:<15s} {:s}\n", i, desc) + +############################################################################### +# +# Print a topic +# +############################################################################### +def display_command_arguments(args): + for arg in args: + if arg[2] == "fn" or arg[2][0] == "o": + opt_o = "[" + opt_c = "]" + else: + opt_o = "" + opt_c = "" + output(" ", opt_o, "-", arg[0]) + if arg[2] != "fn": + output(" ", arg[3]) + output(opt_c) + output(" [-help]") + +def display_aliases(prog, cmdsetmod, commands, topic): + aliases = [] + for i in commands: + if i == "help" or i == "apropos": + continue + command = cmdsetmod.get_command(i) + if hasattr(command, "alias"): + if command.alias == topic: + aliases.append(i) + if aliases: + output("aliases: ", aliases[0]) + if len(aliases) > 1: + for i in range(1, len(aliases)): + output(", ", aliases[i]) + output("\n") + +def display_help_on_topics(prog, cmdsetmod, commands, topics): + for topic in topics: + if topic == "help": + output("bos help: ", help, "\n") + display_command_arguments(command_arguments) + continue + + if topic == "apropos": + command = __import__("afs.apropos", globals(), locals(), ['*']) + output("bos apropos: ", command.help, "\n") + display_command_arguments(command.command_arguments) + continue + + # See if the command is in the set + try: + found = False + for i in commands: + if i == topic: + found = topic + break + if i.startswith(topic): + if found: + raise RuntimeError("Ambiguous topic '" + topic + "'; use 'apropos' to list\n") + found = i + if not found: + raise RuntimeError("Unknown topic '" + topic + "'\n") + topic = found + except RuntimeError as e: + error(e) + set_exitcode(5) + continue + + # Load the command module + command = cmdsetmod.get_command(topic) + + # If it's an alias, then switch to the real module + if hasattr(command, "alias"): + alias = " (alias for " + command.alias + ")" + command = cmdsetmod.get_command(command.alias) + else: + alias = "" + + output(prog, " ", topic, ": ", command.help, alias, "\n") + display_aliases(prog, cmdsetmod, commands, topic) + output("Usage: ", prog, " ", topic) + display_command_arguments(command.command_arguments) + output("\n") + desc = command.description + if desc[0] == "\n": + desc = desc[1:] + output(desc) + +############################################################################### +# +# +# +############################################################################### +def main(params): + prog = params["_prog"] + cmdsetmod = params["_cmdsetmod"] + commands = params["_commands"] + + if "help" in params: + display_help_on_topics(prog, cmdsetmod, commands, [ "help" ]) + elif "topic" in params: + display_help_on_topics(prog, cmdsetmod, commands, params["topic"]) + else: + display_command_list(prog, cmdsetmod, commands) + +############################################################################### +# +# Handle the -help flag being applied to a command +# +############################################################################### +def helpflag(prog, cmdsetmod, topic, command): + output("Usage: ", prog, " ", topic) + display_command_arguments(command.command_arguments) + output("\n") diff --git a/suite/lib/output.py b/suite/lib/output.py index 6ad42db..17906f7 100644 --- a/suite/lib/output.py +++ b/suite/lib/output.py @@ -67,6 +67,10 @@ def get_exitcode(): global exitcode return exitcode +def set_exitcode(n): + global exitcode + exitcode = n + def verbose(*args, **kwargs): global verbosity if verbosity: diff --git a/suite/main.py b/suite/main.py index 0634995..b4b936f 100644 --- a/suite/main.py +++ b/suite/main.py @@ -29,55 +29,13 @@ import sys, os, traceback import afs.commands from afs.lib.output import * -from exception import AFSArgumentError - -# -# The commands map -# -class Commands(dict): - """Commands class. It performs on-demand module loading - """ - def canonical_cmd(self, key): - """Return the canonical name for a possibly-shortened - command name. - """ - candidates = [cmd for cmd in self.keys() if cmd.startswith(key)] - - if not candidates: - out.error('Unknown command: %s' % key, - 'Try "%s help" for a list of supported commands' % prog) - sys.exit(2) - - if len(candidates) > 1: - out.error('Ambiguous command: %s' % key, - 'Candidates are: %s' % ', '.join(candidates)) - sys.exit(2) - - return candidates[0] - - def __getitem__(self, key): - cmd_mod = self.get(key) or self.get(self.canonical_cmd(key)) - return afs.commands.get_command(cmd_mod) - -#cmd_list = afs.commands.get_commands() -#print(cmd_list); - -#commands = Commands((cmd, mod) for cmd, (mod, kind, help) -# in cmd_list.iteritems()) - -def print_help(): - print('usage: %s [options]' % os.path.basename(sys.argv[0])) - print() - print('Generic commands:') - print(' help print the detailed command usage') - print(' version display version information') - print(' copyright display copyright information') - print() - afs.commands.pretty_command_list(cmd_list, sys.stdout) +from exception import AFSArgumentError, AFSHelpFlag +############################################################################### # # The main function (command dispatcher) # +############################################################################### def _main(): """The main function """ @@ -122,7 +80,8 @@ def _main(): # Import the command set cmdsetmod = afs.commands.import_command_set(cmdset) commands = cmdsetmod.get_command_list() - #print("CMDS:", commands) + commands.append("help") + commands.append("apropos") # See if the command is in the set found = False @@ -133,7 +92,7 @@ def _main(): if i.startswith(cmd): if found: raise RuntimeError("Command '" + cmd + "' is ambiguous") - found = cmd + found = i if not found: raise RuntimeError("Command '" + cmd + "' is unknown") cmd = found @@ -144,12 +103,21 @@ def _main(): else: sys.argv[0] += ' {:s}'.format(cmd) - command = cmdsetmod.get_command(cmd) - - # If it's an alias, then switch to the real module - if hasattr(command, "alias"): - cmd = command.alias + if cmd == "help": + command = __import__("afs.help", globals(), locals(), ['*']) + elif cmd == "apropos": + command = __import__("afs.apropos", globals(), locals(), ['*']) + else: command = cmdsetmod.get_command(cmd) + # If it's an alias, then switch to the real module + if hasattr(command, "alias"): + cmd = command.alias + command = cmdsetmod.get_command(cmd) + + if hasattr(command, "cant_combine_arguments"): + cant_combine_arguments = command.cant_combine_arguments + else: + cant_combine_arguments = {} if hasattr(command, "argument_size_limits"): argument_size_limits = command.argument_size_limits @@ -161,16 +129,24 @@ def _main(): params = afs.argparse.parse_arguments(sys.argv[1:], command.command_arguments, argument_size_limits, - command.cant_combine_arguments) + cant_combine_arguments) except AFSArgumentError as e: print(prog + ":", e, file=sys.stderr) sys.exit(2) + except AFSHelpFlag: + helper = __import__("afs.help", globals(), locals(), ['*']) + helper.helpflag(prog, cmdsetmod, cmd, command) + sys.exit(0) # Stick in the default cell if there isn't one if "cell" not in params: from afs.lib.cell import cell params["cell"] = cell() + params["_prog"] = prog + params["_cmdsetmod"] = cmdsetmod + params["_commands"] = commands + # These modules are only used from this point onwards and do not # need to be imported earlier from afs.exception import AFSException -- 2.49.0