From ab200eb50513f5bf900cc066bd59ad964b0ab86e Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Sat, 28 Apr 2012 19:11:25 -0400 Subject: documentation improvements --- cliff/app.py | 46 ++++++++++++++++---- cliff/command.py | 3 ++ cliff/commandmanager.py | 4 ++ cliff/interactive.py | 23 ++++++++-- docs/source/classes.rst | 6 +++ docs/source/demoapp.rst | 16 ++++--- docs/source/history.rst | 2 + docs/source/index.rst | 3 ++ docs/source/interactive_mode.rst | 94 ++++++++++++++++++++++++++++++++++++++++ docs/source/introduction.rst | 11 ++++- 10 files changed, 189 insertions(+), 19 deletions(-) create mode 100644 docs/source/interactive_mode.rst diff --git a/cliff/app.py b/cliff/app.py index bd42477..6b1a851 100644 --- a/cliff/app.py +++ b/cliff/app.py @@ -15,6 +15,21 @@ LOG = logging.getLogger(__name__) class App(object): """Application base class. + + :param description: one-liner explaining the program purpose + :paramtype description: str + :param version: application version number + :paramtype version: str + :param command_manager: plugin loader + :paramtype command_manager: cliff.commandmanager.CommandManager + :param stdin: Standard input stream + :paramtype stdin: readable I/O stream + :param stdout: Standard output stream + :paramtype stdout: writable I/O stream + :param stderr: Standard error output stream + :paramtype stderr: writable I/O stream + :param interactive_app_factory: callable to create an interactive application + :paramtype interactive_app_factory: cliff.interactive.InteractiveApp """ NAME = os.path.splitext(os.path.basename(sys.argv[0]))[0] @@ -24,21 +39,16 @@ class App(object): DEFAULT_VERBOSE_LEVEL = 1 def __init__(self, description, version, command_manager, - stdin=None, stdout=None, stderr=None): + stdin=None, stdout=None, stderr=None, + interactive_app_factory=InteractiveApp): """Initialize the application. - - :param description: One liner explaining the program purpose - :param version: String containing the application version number - :param command_manager: A CommandManager instance - :param stdin: Standard input stream - :param stdout: Standard output stream - :param stderr: Standard error output stream """ self.command_manager = command_manager self.command_manager.add_command('help', HelpCommand) self.stdin = stdin or sys.stdin self.stdout = stdout or sys.stdout self.stderr = stderr or sys.stderr + self.interactive_app_factory = interactive_app_factory self.parser = self.build_option_parser(description, version) self.interactive_mode = False @@ -47,6 +57,11 @@ class App(object): Subclasses may override this method to extend the parser with more global options. + + :param description: full description of the application + :paramtype description: str + :param version: version number for the application + :paramtype version: str """ parser = argparse.ArgumentParser( description=description, @@ -116,6 +131,9 @@ class App(object): def run(self, argv): """Equivalent to the main program for the application. + + :param argv: input arguments and options + :paramtype argv: list of str """ self.options, remainder = self.parser.parse_known_args(argv) self.configure_logging() @@ -138,17 +156,27 @@ class App(object): def prepare_to_run_command(self, cmd): """Perform any preliminary work needed to run a command. + + :param cmd: command processor being invoked + :paramtype cmd: cliff.command.Command """ return def clean_up(self, cmd, result, err): """Hook run after a command is done to shutdown the app. + + :param cmd: command processor being invoked + :paramtype cmd: cliff.command.Command + :param result: return value of cmd + :paramtype result: int + :param err: exception or None + :paramtype err: Exception """ return def interact(self): self.interactive_mode = True - interpreter = InteractiveApp(self, self.command_manager, self.stdin, self.stdout) + interpreter = self.interactive_app_factory(self, self.command_manager, self.stdin, self.stdout) interpreter.prompt = '(%s) ' % self.NAME interpreter.cmdloop() return 0 diff --git a/cliff/command.py b/cliff/command.py index 94c8e8e..7ff526e 100644 --- a/cliff/command.py +++ b/cliff/command.py @@ -6,6 +6,9 @@ import inspect class Command(object): """Base class for command plugins. + + :param app: Application instance invoking the command. + :paramtype app: cliff.app.App """ __metaclass__ = abc.ABCMeta diff --git a/cliff/commandmanager.py b/cliff/commandmanager.py index bb8bfe2..135714a 100644 --- a/cliff/commandmanager.py +++ b/cliff/commandmanager.py @@ -23,6 +23,10 @@ class EntryPointWrapper(object): class CommandManager(object): """Discovers commands and handles lookup based on argv data. + + :param namespace: String containing the setuptools entrypoint namespace + for the plugins to be loaded. For example, + ``'cliff.formatter.list'``. """ def __init__(self, namespace): self.commands = {} diff --git a/cliff/interactive.py b/cliff/interactive.py index 33aefc6..493235c 100644 --- a/cliff/interactive.py +++ b/cliff/interactive.py @@ -12,6 +12,20 @@ LOG = logging.getLogger(__name__) class InteractiveApp(cmd2.Cmd): + """Provides "interactive mode" features. + + Refer to the cmd2_ and cmd_ documentation for details + about subclassing and configuring this class. + + .. _cmd2: http://packages.python.org/cmd2/index.html + .. _cmd: http://docs.python.org/library/cmd.html + + :param parent_app: The calling application (expected to be derived + from :class:`cliff.main.App`). + :param command_manager: A :class:`cliff.commandmanager.CommandManager` instance. + :param stdin: Standard input stream + :param stdout: Standard output stream + """ use_rawinput = True doc_header = "Shell commands (type help ):" @@ -32,10 +46,8 @@ class InteractiveApp(cmd2.Cmd): self.parent_app.run_subcommand(line_parts) def completedefault(self, text, line, begidx, endidx): - """Tab-completion for commands known to the command manager. - - Does not handle options on the commands. - """ + # Tab-completion for commands known to the command manager. + # Does not handle options on the commands. if not text: completions = sorted(n for n, v in self.command_manager) else: @@ -75,6 +87,9 @@ class InteractiveApp(cmd2.Cmd): return def get_names(self): + # Override the base class version to filter out + # things that look like they should be hidden + # from the user. return [n for n in cmd2.Cmd.get_names(self) if not n.startswith('do__') diff --git a/docs/source/classes.rst b/docs/source/classes.rst index a1cadbb..65abd4e 100644 --- a/docs/source/classes.rst +++ b/docs/source/classes.rst @@ -8,6 +8,12 @@ App .. autoclass:: cliff.app.App :members: +InteractiveApp +============== + +.. autoclass:: cliff.interactive.InteractiveApp + :members: + CommandManager ============== diff --git a/docs/source/demoapp.rst b/docs/source/demoapp.rst index 1832077..84ca5d3 100644 --- a/docs/source/demoapp.rst +++ b/docs/source/demoapp.rst @@ -85,14 +85,20 @@ The :class:`DemoApp` class inherits from :class:`App` and overrides also passes a :class:`CommandManager` instance configured to look for plugins in the ``cliff.demo`` namespace. -The :func:`prepare_to_run_command` method of :class:`DemoApp` will be -invoked after the main program arguments are parsed and the command is -identified, but before the command is given its arguments and -run. This hook is intended for opening connections to remote web +The :func:`initialize_app` method of :class:`DemoApp` will be invoked +after the main program arguments are parsed, but before any command +processing is performed and before the application enters interactive +mode. This hook is intended for opening connections to remote web services, databases, etc. using arguments passed to the main application. -The :func:`clean_up` method of :class:`DemoApp` is invoked after the +The :func:`prepare_to_run_command` method of :class:`DemoApp` will be +invoked after a command is identified, but before the command is given +its arguments and run. This hook is intended for pre-command +validation or setup that must be repeated and cannot be handled by +:func:`initialize_app`. + +The :func:`clean_up` method of :class:`DemoApp` is invoked after a command runs. If the command raised an exception, the exception object is passed to :func:`clean_up`. Otherwise the ``err`` argument is ``None``. diff --git a/docs/source/history.rst b/docs/source/history.rst index 303b9cd..58b5fb3 100644 --- a/docs/source/history.rst +++ b/docs/source/history.rst @@ -5,6 +5,8 @@ dev - Add shell formatter for single objects. + - Add interactive mode. + - Expand documentation. 0.3 diff --git a/docs/source/index.rst b/docs/source/index.rst index 8994669..0a13664 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,7 @@ Contents: demoapp list_commands show_commands + interactive_mode classes install developers @@ -27,3 +28,5 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + +.. todolist:: diff --git a/docs/source/interactive_mode.rst b/docs/source/interactive_mode.rst new file mode 100644 index 0000000..519e89a --- /dev/null +++ b/docs/source/interactive_mode.rst @@ -0,0 +1,94 @@ +================== + Interactive Mode +================== + +In addition to running single commands from the command line, cliff +supports an interactive mode in which the user is presented with a +separate command shell. All of the command plugins available from the +command line are automatically configured as commands within the +shell. + +Refer to the cmd2_ documentation for more details about features of +the shell. + +.. _cmd2: http://packages.python.org/cmd2/index.html + +.. todo:: Add details about configuring and interacting with the shell (copy from cmd2 docs) + +Example +======= + +The ``cliffdemo`` application enters interactive mode if no command is +specified on the command line. + +:: + + (.venv)$ cliffdemo + (cliffdemo) help + + Shell commands (type help ): + =================================== + cmdenvironment edit hi l list pause r save shell show + ed help history li load py run set shortcuts + + Undocumented commands: + ====================== + EOF eof exit q quit + + Application commands (type help ): + ========================================= + files help simple file error two part + +To obtain instructions for a built-in or application command, use the +``help`` command: + +:: + + (cliffdemo) help simple + usage: simple [-h] + + A simple command that prints a message. + + optional arguments: + -h, --help show this help message and exit + +The commands can be run, including options and arguments, as on the +regular command line: + +:: + + (cliffdemo) simple + sending greeting + hi! + (cliffdemo) files + +----------------------+-------+ + | Name | Size | + +----------------------+-------+ + | .git | 578 | + | .gitignore | 268 | + | .tox | 238 | + | .venv | 204 | + | announce.rst | 1015 | + | announce.rst~ | 708 | + | cliff | 884 | + | cliff.egg-info | 340 | + | cliffdemo.log | 2193 | + | cliffdemo.log.1 | 10225 | + | demoapp | 408 | + | dist | 136 | + | distribute_setup.py | 15285 | + | distribute_setup.pyc | 15196 | + | docs | 238 | + | LICENSE | 11358 | + | Makefile | 376 | + | Makefile~ | 94 | + | MANIFEST.in | 186 | + | MANIFEST.in~ | 344 | + | README.rst | 1063 | + | setup.py | 5855 | + | setup.py~ | 8128 | + | tests | 204 | + | tox.ini | 76 | + | tox.ini~ | 421 | + +----------------------+-------+ + (cliffdemo) diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index afef2bf..0951f19 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -19,7 +19,7 @@ fit. Cliff Objects ============= -Cliff is organized around three objects that are combined to create a +Cliff is organized around four objects that are combined to create a useful command line program. The Application @@ -50,6 +50,15 @@ responsible for taking action based on instructions from the user. It defines its own local argument parser (usually using argparse_) and a :func:`run` method that does the appropriate work. +The Interactive Application +--------------------------- + +The main program uses an :class:`cliff.interactive.InteractiveApp` +instance to provide a command-shell mode in which the user can type +multiple commands before the program exits. Many cliff-based +applications will be able to use the default implementation of +:class:`InteractiveApp` without subclassing it. + .. _setuptools entry points: http://packages.python.org/distribute/setuptools.html .. _argparse: http://docs.python.org/library/argparse.html -- cgit v1.2.1