From 92ae130c38520b249eb7351cfb0da1ad67d3d3cf Mon Sep 17 00:00:00 2001 From: kotfu Date: Tue, 2 Jul 2019 19:02:36 -0600 Subject: Major overhaul of documentation structure for #709 --- docs/alternatives.rst | 53 ------ docs/api/cmd.rst | 2 + docs/api/decorators.rst | 10 ++ docs/api/exceptions.rst | 6 + docs/api/utility_classes.rst | 12 ++ docs/api/utility_functions.rst | 48 +++++ docs/argument_processing.rst | 314 --------------------------------- docs/conf.py | 7 +- docs/doc_conventions.rst | 109 ++++++++++++ docs/examples/quickstart.rst | 4 + docs/features/argument_processing.rst | 320 ++++++++++++++++++++++++++++++++++ docs/features/generating_output.rst | 10 ++ docs/features/help.rst | 8 + docs/features/hooks.rst | 305 ++++++++++++++++++++++++++++++++ docs/features/transcript.rst | 193 ++++++++++++++++++++ docs/freefeatures.rst | 4 +- docs/hooks.rst | 305 -------------------------------- docs/index.rst | 141 ++++++++++----- docs/install.rst | 155 ---------------- docs/migrating/free_features.rst | 5 + docs/migrating/incompatibilities.rst | 21 +++ docs/migrating/minimum.rst | 4 + docs/migrating/nextsteps.rst | 6 + docs/migrating/why.rst | 25 +++ docs/overview.rst | 27 --- docs/overview/alternatives.rst | 53 ++++++ docs/overview/featuretour.rst | 6 + docs/overview/installation.rst | 155 ++++++++++++++++ docs/overview/resources.rst | 13 ++ docs/transcript.rst | 193 -------------------- 30 files changed, 1417 insertions(+), 1097 deletions(-) delete mode 100644 docs/alternatives.rst create mode 100644 docs/api/cmd.rst create mode 100644 docs/api/decorators.rst create mode 100644 docs/api/exceptions.rst create mode 100644 docs/api/utility_classes.rst create mode 100644 docs/api/utility_functions.rst delete mode 100644 docs/argument_processing.rst create mode 100644 docs/doc_conventions.rst create mode 100644 docs/examples/quickstart.rst create mode 100644 docs/features/argument_processing.rst create mode 100644 docs/features/generating_output.rst create mode 100644 docs/features/help.rst create mode 100644 docs/features/hooks.rst create mode 100644 docs/features/transcript.rst delete mode 100644 docs/hooks.rst delete mode 100644 docs/install.rst create mode 100644 docs/migrating/free_features.rst create mode 100644 docs/migrating/incompatibilities.rst create mode 100644 docs/migrating/minimum.rst create mode 100644 docs/migrating/nextsteps.rst create mode 100644 docs/migrating/why.rst delete mode 100644 docs/overview.rst create mode 100644 docs/overview/alternatives.rst create mode 100644 docs/overview/featuretour.rst create mode 100644 docs/overview/installation.rst create mode 100644 docs/overview/resources.rst delete mode 100644 docs/transcript.rst (limited to 'docs') diff --git a/docs/alternatives.rst b/docs/alternatives.rst deleted file mode 100644 index bf1545d6..00000000 --- a/docs/alternatives.rst +++ /dev/null @@ -1,53 +0,0 @@ -============================ -Alternatives to cmd and cmd2 -============================ - -For programs that do not interact with the user in a continuous loop - -programs that simply accept a set of arguments from the command line, return -results, and do not keep the user within the program's environment - all -you need are sys_\ .argv (the command-line arguments) and argparse_ -(for parsing UNIX-style options and flags). Though some people may prefer docopt_ -or click_ to argparse_. - -.. _sys: https://docs.python.org/3/library/sys.html -.. _argparse: https://docs.python.org/3/library/argparse.html -.. _docopt: https://pypi.python.org/pypi/docopt -.. _click: http://click.pocoo.org - - -The curses_ module produces applications that interact via a plaintext -terminal window, but are not limited to simple text input and output; -they can paint the screen with options that are selected from using the -cursor keys. However, programming a curses_-based application is not as -straightforward as using cmd_. - -.. _curses: https://docs.python.org/3/library/curses.html -.. _cmd: https://docs.python.org/3/library/cmd.html - -Several Python packages exist for building interactive command-line applications -approximately similar in concept to cmd_ applications. None of them -share ``cmd2``'s close ties to cmd_, but they may be worth investigating -nonetheless. Two of the most mature and full featured are: - - * `Python Prompt Toolkit`_ - * Click_ - -.. _`Python Prompt Toolkit`: https://github.com/jonathanslenders/python-prompt-toolkit - -`Python Prompt Toolkit`_ is a library for building powerful interactive command lines and terminal applications in -Python. It provides a lot of advanced visual features like syntax highlighting, bottom bars, and the ability to -create fullscreen apps. - -Click_ is a Python package for creating beautiful command line interfaces in a composable way with as little code as -necessary. It is more geared towards command line utilities instead of command line interpreters, but it can be used -for either. - -Getting a working command-interpreter application based on either `Python Prompt Toolkit`_ or Click_ requires a good -deal more effort and boilerplate code than ``cmd2``. ``cmd2`` focuses on providing an excellent out-of-the-box experience -with as many useful features as possible built in for free with as little work required on the developer's part as -possible. We believe that ``cmd2`` provides developers the easiest way to write a command-line interpreter, while -allowing a good experience for end users. If you are seeking a visually richer end-user experience and don't -mind investing more development time, we would recommend checking out `Python Prompt Toolkit`_. - -In the future, we may investigate options for incorporating the usage of `Python Prompt Toolkit`_ and/or Click_ into -``cmd2`` applications. diff --git a/docs/api/cmd.rst b/docs/api/cmd.rst new file mode 100644 index 00000000..f9eefab2 --- /dev/null +++ b/docs/api/cmd.rst @@ -0,0 +1,2 @@ +cmd object +========== diff --git a/docs/api/decorators.rst b/docs/api/decorators.rst new file mode 100644 index 00000000..d7bfa138 --- /dev/null +++ b/docs/api/decorators.rst @@ -0,0 +1,10 @@ +Decorators +========== + +.. autofunction:: cmd2.cmd2.with_category + +.. autofunction:: cmd2.cmd2.with_argument_list + +.. autofunction:: cmd2.cmd2.with_argparser_and_unknown_args + +.. autofunction:: cmd2.cmd2.with_argparser diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst new file mode 100644 index 00000000..656c4a5a --- /dev/null +++ b/docs/api/exceptions.rst @@ -0,0 +1,6 @@ +Exceptions +========== + +.. autoexception:: cmd2.cmd2.EmbeddedConsoleExit + +.. autoexception:: cmd2.cmd2.EmptyStatement diff --git a/docs/api/utility_classes.rst b/docs/api/utility_classes.rst new file mode 100644 index 00000000..7ed0c584 --- /dev/null +++ b/docs/api/utility_classes.rst @@ -0,0 +1,12 @@ +Utility Classes +=============== + +.. autoclass:: cmd2.utils.StdSim + +.. autoclass:: cmd2.utils.ByteBuf + +.. autoclass:: cmd2.utils.ProcReader + +.. autoclass:: cmd2.utils.ContextFlag + +.. autoclass:: cmd2.utils.RedirectionSavedState diff --git a/docs/api/utility_functions.rst b/docs/api/utility_functions.rst new file mode 100644 index 00000000..57a720bf --- /dev/null +++ b/docs/api/utility_functions.rst @@ -0,0 +1,48 @@ +Utility Functions +================= + +.. autofunction:: cmd2.utils.is_quoted + +.. autofunction:: cmd2.utils.quote_string_if_needed + +.. autofunction:: cmd2.utils.strip_quotes + +.. autofunction:: cmd2.cmd2.categorize + +.. autofunction:: cmd2.utils.center_text + +.. autofunction:: cmd2.utils.strip_quotes + +.. autofunction:: cmd2.utils.namedtuple_with_defaults + +.. autofunction:: cmd2.utils.cast + +.. autofunction:: cmd2.utils.which + +.. autofunction:: cmd2.utils.is_text_file + +.. autofunction:: cmd2.utils.remove_duplicates + +.. autofunction:: cmd2.utils.norm_fold + +.. autofunction:: cmd2.utils.try_int_or_force_to_lower_case + +.. autofunction:: cmd2.utils.alphabetical_sort + +.. autofunction:: cmd2.utils.unquote_specific_tokens + +.. autofunction:: cmd2.utils.natural_sort + +.. autofunction:: cmd2.utils.natural_keys + +.. autofunction:: cmd2.utils.expand_user_in_tokens + +.. autofunction:: cmd2.utils.expand_user + +.. autofunction:: cmd2.utils.find_editor + +.. autofunction:: cmd2.utils.get_exes_in_path + +.. autofunction:: cmd2.utils.files_from_glob_patterns + +.. autofunction:: cmd2.utils.files_from_glob_pattern diff --git a/docs/argument_processing.rst b/docs/argument_processing.rst deleted file mode 100644 index a1fc107b..00000000 --- a/docs/argument_processing.rst +++ /dev/null @@ -1,314 +0,0 @@ -.. _decorators: - -=================== -Argument Processing -=================== - -``cmd2`` makes it easy to add sophisticated argument processing to your commands using the ``argparse`` python module. -``cmd2`` handles the following for you: - -1. Parsing input and quoted strings like the Unix shell -2. Parse the resulting argument list using an instance of ``argparse.ArgumentParser`` that you provide -3. Passes the resulting ``argparse.Namespace`` object to your command function. The ``Namespace`` includes the - ``Statement`` object that was created when parsing the command line. It is stored in the ``__statement__`` - attribute of the ``Namespace``. -4. Adds the usage message from the argument parser to your command. -5. Checks if the ``-h/--help`` option is present, and if so, display the help message for the command - -These features are all provided by the ``@with_argparser`` decorator which is importable from ``cmd2``. - -See the either the argprint_ or decorator_ example to learn more about how to use the various ``cmd2`` argument -processing decorators in your ``cmd2`` applications. - -.. _argprint: https://github.com/python-cmd2/cmd2/blob/master/examples/arg_print.py -.. _decorator: https://github.com/python-cmd2/cmd2/blob/master/examples/decorator_example.py - - -Decorators provided by cmd2 for argument processing -=================================================== -``cmd2`` provides the following decorators for assisting with parsing arguments passed to commands: - -.. automethod:: cmd2.cmd2.with_argument_list -.. automethod:: cmd2.cmd2.with_argparser -.. automethod:: cmd2.cmd2.with_argparser_and_unknown_args - -All of these decorators accept an optional **preserve_quotes** argument which defaults to ``False``. -Setting this argument to ``True`` is useful for cases where you are passing the arguments to another -command which might have its own argument parsing. - - -Using the argument parser decorator -=================================== - -For each command in the ``cmd2`` subclass which requires argument parsing, -create a unique instance of ``argparse.ArgumentParser()`` which can parse the -input appropriately for the command. Then decorate the command method with -the ``@with_argparser`` decorator, passing the argument parser as the -first parameter to the decorator. This changes the second argument to the command method, which will contain the results -of ``ArgumentParser.parse_args()``. - -Here's what it looks like:: - - import argparse - from cmd2 import with_argparser - - argparser = argparse.ArgumentParser() - argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') - argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') - argparser.add_argument('-r', '--repeat', type=int, help='output [n] times') - argparser.add_argument('word', nargs='?', help='word to say') - - @with_argparser(argparser) - def do_speak(self, opts) - """Repeats what you tell me to.""" - arg = opts.word - if opts.piglatin: - arg = '%s%say' % (arg[1:], arg[0]) - if opts.shout: - arg = arg.upper() - repetitions = opts.repeat or 1 - for i in range(min(repetitions, self.maxrepeats)): - self.poutput(arg) - -.. warning:: - - It is important that each command which uses the ``@with_argparser`` decorator be passed a unique instance of a - parser. This limitation is due to bugs in CPython prior to Python 3.7 which make it impossible to make a deep copy - of an instance of a ``argparse.ArgumentParser``. - - See the table_display_ example for a work-around that demonstrates how to create a function which returns a unique - instance of the parser you want. - - -.. note:: - - The ``@with_argparser`` decorator sets the ``prog`` variable in - the argument parser based on the name of the method it is decorating. - This will override anything you specify in ``prog`` variable when - creating the argument parser. - -.. _table_display: https://github.com/python-cmd2/cmd2/blob/master/examples/table_display.py - - -Help Messages -============= - -By default, cmd2 uses the docstring of the command method when a user asks -for help on the command. When you use the ``@with_argparser`` -decorator, the docstring for the ``do_*`` method is used to set the description for the ``argparse.ArgumentParser``. - -With this code:: - - import argparse - from cmd2 import with_argparser - - argparser = argparse.ArgumentParser() - argparser.add_argument('tag', help='tag') - argparser.add_argument('content', nargs='+', help='content to surround with tag') - @with_argparser(argparser) - def do_tag(self, args): - """create a html tag""" - self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) - self.stdout.write('\n') - -the ``help tag`` command displays: - -.. code-block:: none - - usage: tag [-h] tag content [content ...] - - create a html tag - - positional arguments: - tag tag - content content to surround with tag - - optional arguments: - -h, --help show this help message and exit - - -If you would prefer you can set the ``description`` while instantiating the ``argparse.ArgumentParser`` and leave the -docstring on your method empty:: - - import argparse - from cmd2 import with_argparser - - argparser = argparse.ArgumentParser(description='create an html tag') - argparser.add_argument('tag', help='tag') - argparser.add_argument('content', nargs='+', help='content to surround with tag') - @with_argparser(argparser) - def do_tag(self, args): - self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) - self.stdout.write('\n') - -Now when the user enters ``help tag`` they see: - -.. code-block:: none - - usage: tag [-h] tag content [content ...] - - create an html tag - - positional arguments: - tag tag - content content to surround with tag - - optional arguments: - -h, --help show this help message and exit - - -To add additional text to the end of the generated help message, use the ``epilog`` variable:: - - import argparse - from cmd2 import with_argparser - - argparser = argparse.ArgumentParser(description='create an html tag', - epilog='This command can not generate tags with no content, like
.') - argparser.add_argument('tag', help='tag') - argparser.add_argument('content', nargs='+', help='content to surround with tag') - @with_argparser(argparser) - def do_tag(self, args): - self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) - self.stdout.write('\n') - -Which yields: - -.. code-block:: none - - usage: tag [-h] tag content [content ...] - - create an html tag - - positional arguments: - tag tag - content content to surround with tag - - optional arguments: - -h, --help show this help message and exit - - This command can not generate tags with no content, like
- -.. warning:: - - If a command **foo** is decorated with one of cmd2's argparse decorators, then **help_foo** will not - be invoked when ``help foo`` is called. The argparse_ module provides a rich API which can be used to - tweak every aspect of the displayed help and we encourage ``cmd2`` developers to utilize that. - -.. _argparse: https://docs.python.org/3/library/argparse.html - - -Receiving an argument list -========================== - -The default behavior of ``cmd2`` is to pass the user input directly to your -``do_*`` methods as a string. The object passed to your method is actually a -``Statement`` object, which has additional attributes that may be helpful, -including ``arg_list`` and ``argv``:: - - class CmdLineApp(cmd2.Cmd): - """ Example cmd2 application. """ - - def do_say(self, statement): - # statement contains a string - self.poutput(statement) - - def do_speak(self, statement): - # statement also has a list of arguments - # quoted arguments remain quoted - for arg in statement.arg_list: - self.poutput(arg) - - def do_articulate(self, statement): - # statement.argv contains the command - # and the arguments, which have had quotes - # stripped - for arg in statement.argv: - self.poutput(arg) - - -If you don't want to access the additional attributes on the string passed to -you``do_*`` method you can still have ``cmd2`` apply shell parsing rules to the -user input and pass you a list of arguments instead of a string. Apply the -``@with_argument_list`` decorator to those methods that should receive an -argument list instead of a string:: - - from cmd2 import with_argument_list - - class CmdLineApp(cmd2.Cmd): - """ Example cmd2 application. """ - - def do_say(self, cmdline): - # cmdline contains a string - pass - - @with_argument_list - def do_speak(self, arglist): - # arglist contains a list of arguments - pass - - -Using the argument parser decorator and also receiving a list of unknown positional arguments -=============================================================================================== -If you want all unknown arguments to be passed to your command as a list of strings, then -decorate the command method with the ``@with_argparser_and_unknown_args`` decorator. - -Here's what it looks like:: - - import argparse - from cmd2 import with_argparser_and_unknown_args - - dir_parser = argparse.ArgumentParser() - dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line") - - @with_argparser_and_unknown_args(dir_parser) - def do_dir(self, args, unknown): - """List contents of current directory.""" - # No arguments for this command - if unknown: - self.perror("dir does not take any positional arguments:") - self.do_help('dir') - self.last_result = CommandResult('', 'Bad arguments') - return - - # Get the contents as a list - contents = os.listdir(self.cwd) - - ... - -Using custom argparse.Namespace with argument parser decorators -=============================================================================================== -In some cases, it may be necessary to write custom ``argparse`` code that is dependent on state data of your -application. To support this ability while still allowing use of the decorators, both ``@with_argparser`` and -``@with_argparser_and_unknown_args`` have an optional argument called ``ns_provider``. - -``ns_provider`` is a Callable that accepts a ``cmd2.Cmd`` object as an argument and returns an ``argparse.Namespace``:: - - Callable[[cmd2.Cmd], argparse.Namespace] - -For example:: - - def settings_ns_provider(self) -> argparse.Namespace: - """Populate an argparse Namespace with current settings""" - ns = argparse.Namespace() - ns.app_settings = self.settings - return ns - -To use this function with the argparse decorators, do the following:: - - @with_argparser(my_parser, ns_provider=settings_ns_provider) - -The Namespace is passed by the decorators to the ``argparse`` parsing functions which gives your custom code access -to the state data it needs for its parsing logic. - -Sub-commands -============ -Sub-commands are supported for commands using either the ``@with_argparser`` or -``@with_argparser_and_unknown_args`` decorator. The syntax for supporting them is based on argparse sub-parsers. - -You may add multiple layers of sub-commands for your command. Cmd2 will automatically traverse and tab-complete -sub-commands for all commands using argparse. - -See the subcommands_ and tab_autocompletion_ example to learn more about how to use sub-commands in your ``cmd2`` application. - -.. _subcommands: https://github.com/python-cmd2/cmd2/blob/master/examples/subcommands.py -.. _tab_autocompletion: https://github.com/python-cmd2/cmd2/blob/master/examples/tab_autocompletion.py diff --git a/docs/conf.py b/docs/conf.py index f22a117d..cc7fea2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,7 @@ sys.path.insert(0, os.path.abspath('..')) # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.autosectionlabel', 'sphinx.ext.intersphinx', 'sphinx.ext.doctest', 'sphinx.ext.todo'] @@ -55,8 +56,8 @@ master_doc = 'index' # General information about the project. project = 'cmd2' -copyright = '2010-2018, Catherine Devlin and Todd Leonhardt' -author = 'Catherine Devlin and Todd Leonhardt' +copyright = '2010-2019, Catherine Devlin and Todd Leonhardt' +author = 'cmd2 contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -85,6 +86,8 @@ pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False +# configure autosectionlabel extension +autosectionlabel_prefix_document = True # -- Options for HTML output --------------------------------------------------- diff --git a/docs/doc_conventions.rst b/docs/doc_conventions.rst new file mode 100644 index 00000000..504c930d --- /dev/null +++ b/docs/doc_conventions.rst @@ -0,0 +1,109 @@ +Documentation Conventions +========================= + +Guiding Principles +------------------ + +Follow the `Documentation Principles +`_ described by +`Write The Docs `_ + +In addition: + +- We have gone to great lengths to retain compatibility with the standard library cmd, the documentation should make it easy for developers to understand how to move from cmd to cmd2, and what benefits that will provide +- We should provide both descriptive and reference documentation. +- API reference documentation should be generated from docstrings in the code +- Documentation should include rich hyperlinking to other areas of the documentation, and to the API reference + + +Naming Files +------------ + +- all lower case file names +- if the name has multiple words, separate them with an underscore +- all documentation file names end in '.rst' + + +Heirarchy of headings +--------------------- + +show the heirarchy of sphinx headings we use, and the conventions (underline only, no overline) + +Use '=', then '-', then '~'. If your document needs more levels than that, break +it into separate documents. + +You only have to worry about the heirarchy of headings within a single file. Sphinx +handles the intra-file heirarchy magically on it's own. + +Use two blank lines before every heading unless it's the first heading in the file. Use one +blank line after every heading + + +Code +---- + +This documentation declares python as the default Sphinx domain. Python code or interactive +python sessions can be presented by either: + +- finishing the preceding paragraph with a ``::`` and indenting the code +- use the ``.. code-block::`` directive + +If you want to show other code, like shell commands, then use ``.. code-block: shell``. + + +Table of Contents and Captions +------------------------------ + + +Hyperlinks +---------- + +If you want to use an external hyperlink target, define the target at the top of the page, +not the bottom. + + +We use the Sphinx `autosectionlabel `_ extension. This allows you to reference any header in any document +by:: + + See :ref:`features/argument_processing:Help Messages` + +or :: + + See :ref:`custom title` + +Which render like + +See :ref:`features/argument_processing:Help Messages` + +and + +See :ref:`custom title` + + + +Autolinking +----------- + + +Referencing cmd2 API documentation +---------------------------------- + + +Info and Warning Callouts +------------------------- + + +Wrapping +-------- + +Hard wrap all text with line lengths less than 80 characters. It makes everything +easier when editing documentation, and has no impact on reading documentation +because we render to html. + + +Referencing cmd2 +----------------- + +Whenever you reference ``cmd2`` in the documentation, enclose it in double backticks. This +indicates an inline literal in restructured text, and makes it stand out when rendered as html. + diff --git a/docs/examples/quickstart.rst b/docs/examples/quickstart.rst new file mode 100644 index 00000000..778cdbee --- /dev/null +++ b/docs/examples/quickstart.rst @@ -0,0 +1,4 @@ +Building Your First cmd2 Application +==================================== + +Quickly show how to build a simple `cmd2` application. diff --git a/docs/features/argument_processing.rst b/docs/features/argument_processing.rst new file mode 100644 index 00000000..20ab7879 --- /dev/null +++ b/docs/features/argument_processing.rst @@ -0,0 +1,320 @@ +.. _decorators: + +Argument Processing +=================== + +``cmd2`` makes it easy to add sophisticated argument processing to your commands using the ``argparse`` python module. +``cmd2`` handles the following for you: + +1. Parsing input and quoted strings like the Unix shell +2. Parse the resulting argument list using an instance of ``argparse.ArgumentParser`` that you provide +3. Passes the resulting ``argparse.Namespace`` object to your command function. The ``Namespace`` includes the + ``Statement`` object that was created when parsing the command line. It is stored in the ``__statement__`` + attribute of the ``Namespace``. +4. Adds the usage message from the argument parser to your command. +5. Checks if the ``-h/--help`` option is present, and if so, display the help message for the command + +These features are all provided by the ``@with_argparser`` decorator which is importable from ``cmd2``. + +See the either the argprint_ or decorator_ example to learn more about how to use the various ``cmd2`` argument +processing decorators in your ``cmd2`` applications. + +.. _argprint: https://github.com/python-cmd2/cmd2/blob/master/examples/arg_print.py +.. _decorator: https://github.com/python-cmd2/cmd2/blob/master/examples/decorator_example.py + + +Decorators provided by cmd2 for argument processing +--------------------------------------------------- + +``cmd2`` provides the following decorators for assisting with parsing arguments passed to commands: + +.. automethod:: cmd2.cmd2.with_argument_list + :noindex: +.. automethod:: cmd2.cmd2.with_argparser + :noindex: +.. automethod:: cmd2.cmd2.with_argparser_and_unknown_args + :noindex: + +All of these decorators accept an optional **preserve_quotes** argument which defaults to ``False``. +Setting this argument to ``True`` is useful for cases where you are passing the arguments to another +command which might have its own argument parsing. + + +Using the argument parser decorator +----------------------------------- + +For each command in the ``cmd2`` subclass which requires argument parsing, +create a unique instance of ``argparse.ArgumentParser()`` which can parse the +input appropriately for the command. Then decorate the command method with +the ``@with_argparser`` decorator, passing the argument parser as the +first parameter to the decorator. This changes the second argument to the command method, which will contain the results +of ``ArgumentParser.parse_args()``. + +Here's what it looks like:: + + import argparse + from cmd2 import with_argparser + + argparser = argparse.ArgumentParser() + argparser.add_argument('-p', '--piglatin', action='store_true', help='atinLay') + argparser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE') + argparser.add_argument('-r', '--repeat', type=int, help='output [n] times') + argparser.add_argument('word', nargs='?', help='word to say') + + @with_argparser(argparser) + def do_speak(self, opts) + """Repeats what you tell me to.""" + arg = opts.word + if opts.piglatin: + arg = '%s%say' % (arg[1:], arg[0]) + if opts.shout: + arg = arg.upper() + repetitions = opts.repeat or 1 + for i in range(min(repetitions, self.maxrepeats)): + self.poutput(arg) + +.. warning:: + + It is important that each command which uses the ``@with_argparser`` decorator be passed a unique instance of a + parser. This limitation is due to bugs in CPython prior to Python 3.7 which make it impossible to make a deep copy + of an instance of a ``argparse.ArgumentParser``. + + See the table_display_ example for a work-around that demonstrates how to create a function which returns a unique + instance of the parser you want. + + +.. note:: + + The ``@with_argparser`` decorator sets the ``prog`` variable in + the argument parser based on the name of the method it is decorating. + This will override anything you specify in ``prog`` variable when + creating the argument parser. + +.. _table_display: https://github.com/python-cmd2/cmd2/blob/master/examples/table_display.py + + +Help Messages +------------- + +By default, cmd2 uses the docstring of the command method when a user asks +for help on the command. When you use the ``@with_argparser`` +decorator, the docstring for the ``do_*`` method is used to set the description for the ``argparse.ArgumentParser``. + +With this code:: + + import argparse + from cmd2 import with_argparser + + argparser = argparse.ArgumentParser() + argparser.add_argument('tag', help='tag') + argparser.add_argument('content', nargs='+', help='content to surround with tag') + @with_argparser(argparser) + def do_tag(self, args): + """create a html tag""" + self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) + self.stdout.write('\n') + +the ``help tag`` command displays: + +.. code-block:: none + + usage: tag [-h] tag content [content ...] + + create a html tag + + positional arguments: + tag tag + content content to surround with tag + + optional arguments: + -h, --help show this help message and exit + + +If you would prefer you can set the ``description`` while instantiating the ``argparse.ArgumentParser`` and leave the +docstring on your method empty:: + + import argparse + from cmd2 import with_argparser + + argparser = argparse.ArgumentParser(description='create an html tag') + argparser.add_argument('tag', help='tag') + argparser.add_argument('content', nargs='+', help='content to surround with tag') + @with_argparser(argparser) + def do_tag(self, args): + self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) + self.stdout.write('\n') + +Now when the user enters ``help tag`` they see: + +.. code-block:: none + + usage: tag [-h] tag content [content ...] + + create an html tag + + positional arguments: + tag tag + content content to surround with tag + + optional arguments: + -h, --help show this help message and exit + + +To add additional text to the end of the generated help message, use the ``epilog`` variable:: + + import argparse + from cmd2 import with_argparser + + argparser = argparse.ArgumentParser(description='create an html tag', + epilog='This command can not generate tags with no content, like
.') + argparser.add_argument('tag', help='tag') + argparser.add_argument('content', nargs='+', help='content to surround with tag') + @with_argparser(argparser) + def do_tag(self, args): + self.stdout.write('<{0}>{1}'.format(args.tag, ' '.join(args.content))) + self.stdout.write('\n') + +Which yields: + +.. code-block:: none + + usage: tag [-h] tag content [content ...] + + create an html tag + + positional arguments: + tag tag + content content to surround with tag + + optional arguments: + -h, --help show this help message and exit + + This command can not generate tags with no content, like
+ +.. warning:: + + If a command **foo** is decorated with one of cmd2's argparse decorators, then **help_foo** will not + be invoked when ``help foo`` is called. The argparse_ module provides a rich API which can be used to + tweak every aspect of the displayed help and we encourage ``cmd2`` developers to utilize that. + +.. _argparse: https://docs.python.org/3/library/argparse.html + + +Receiving an argument list +-------------------------- + +The default behavior of ``cmd2`` is to pass the user input directly to your +``do_*`` methods as a string. The object passed to your method is actually a +``Statement`` object, which has additional attributes that may be helpful, +including ``arg_list`` and ``argv``:: + + class CmdLineApp(cmd2.Cmd): + """ Example cmd2 application. """ + + def do_say(self, statement): + # statement contains a string + self.poutput(statement) + + def do_speak(self, statement): + # statement also has a list of arguments + # quoted arguments remain quoted + for arg in statement.arg_list: + self.poutput(arg) + + def do_articulate(self, statement): + # statement.argv contains the command + # and the arguments, which have had quotes + # stripped + for arg in statement.argv: + self.poutput(arg) + + +If you don't want to access the additional attributes on the string passed to +you``do_*`` method you can still have ``cmd2`` apply shell parsing rules to the +user input and pass you a list of arguments instead of a string. Apply the +``@with_argument_list`` decorator to those methods that should receive an +argument list instead of a string:: + + from cmd2 import with_argument_list + + class CmdLineApp(cmd2.Cmd): + """ Example cmd2 application. """ + + def do_say(self, cmdline): + # cmdline contains a string + pass + + @with_argument_list + def do_speak(self, arglist): + # arglist contains a list of arguments + pass + + +Using the argument parser decorator and also receiving a list of unknown positional arguments +--------------------------------------------------------------------------------------------- + +If you want all unknown arguments to be passed to your command as a list of strings, then +decorate the command method with the ``@with_argparser_and_unknown_args`` decorator. + +Here's what it looks like:: + + import argparse + from cmd2 import with_argparser_and_unknown_args + + dir_parser = argparse.ArgumentParser() + dir_parser.add_argument('-l', '--long', action='store_true', help="display in long format with one item per line") + + @with_argparser_and_unknown_args(dir_parser) + def do_dir(self, args, unknown): + """List contents of current directory.""" + # No arguments for this command + if unknown: + self.perror("dir does not take any positional arguments:") + self.do_help('dir') + self.last_result = CommandResult('', 'Bad arguments') + return + + # Get the contents as a list + contents = os.listdir(self.cwd) + + ... + +Using custom argparse.Namespace with argument parser decorators +--------------------------------------------------------------- + +In some cases, it may be necessary to write custom ``argparse`` code that is dependent on state data of your +application. To support this ability while still allowing use of the decorators, both ``@with_argparser`` and +``@with_argparser_and_unknown_args`` have an optional argument called ``ns_provider``. + +``ns_provider`` is a Callable that accepts a ``cmd2.Cmd`` object as an argument and returns an ``argparse.Namespace``:: + + Callable[[cmd2.Cmd], argparse.Namespace] + +For example:: + + def settings_ns_provider(self) -> argparse.Namespace: + """Populate an argparse Namespace with current settings""" + ns = argparse.Namespace() + ns.app_settings = self.settings + return ns + +To use this function with the argparse decorators, do the following:: + + @with_argparser(my_parser, ns_provider=settings_ns_provider) + +The Namespace is passed by the decorators to the ``argparse`` parsing functions which gives your custom code access +to the state data it needs for its parsing logic. + +Sub-commands +------------ + +Sub-commands are supported for commands using either the ``@with_argparser`` or +``@with_argparser_and_unknown_args`` decorator. The syntax for supporting them is based on argparse sub-parsers. + +You may add multiple layers of sub-commands for your command. Cmd2 will automatically traverse and tab-complete +sub-commands for all commands using argparse. + +See the subcommands_ and tab_autocompletion_ example to learn more about how to use sub-commands in your ``cmd2`` application. + +.. _subcommands: https://github.com/python-cmd2/cmd2/blob/master/examples/subcommands.py +.. _tab_autocompletion: https://github.com/python-cmd2/cmd2/blob/master/examples/tab_autocompletion.py diff --git a/docs/features/generating_output.rst b/docs/features/generating_output.rst new file mode 100644 index 00000000..a4a928cf --- /dev/null +++ b/docs/features/generating_output.rst @@ -0,0 +1,10 @@ +Generating Output +================= + +how to generate output + +poutput + +perror + +paging diff --git a/docs/features/help.rst b/docs/features/help.rst new file mode 100644 index 00000000..e5cc0451 --- /dev/null +++ b/docs/features/help.rst @@ -0,0 +1,8 @@ +Help +==== + +use the categorize() function to create help categories + +Use ``help_method()`` to custom roll your own help messages. + +See :ref:`features/argument_processing:Help Messages` diff --git a/docs/features/hooks.rst b/docs/features/hooks.rst new file mode 100644 index 00000000..5db97fe5 --- /dev/null +++ b/docs/features/hooks.rst @@ -0,0 +1,305 @@ +.. cmd2 documentation for application and command lifecycle and the available hooks + +cmd2 Application Lifecycle and Hooks +==================================== + +The typical way of starting a cmd2 application is as follows:: + + import cmd2 + class App(cmd2.Cmd): + # customized attributes and methods here + + if __name__ == '__main__': + app = App() + app.cmdloop() + +There are several pre-existing methods and attributes which you can tweak to +control the overall behavior of your application before, during, and after the +command processing loop. + +Application Lifecycle Hooks +--------------------------- + +You can register methods to be called at the beginning of the command loop:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_preloop_hook(self.myhookmethod) + + def myhookmethod(self): + self.poutput("before the loop begins") + +To retain backwards compatibility with `cmd.Cmd`, after all registered preloop +hooks have been called, the ``preloop()`` method is called. + +A similar approach allows you to register functions to be called after the +command loop has finished:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_postloop_hook(self.myhookmethod) + + def myhookmethod(self): + self.poutput("before the loop begins") + +To retain backwards compatibility with `cmd.Cmd`, after all registered postloop +hooks have been called, the ``postloop()`` method is called. + +Preloop and postloop hook methods are not passed any parameters and any return +value is ignored. + + +Application Lifecycle Attributes +-------------------------------- + +There are numerous attributes of and arguments to ``cmd2.Cmd`` which have +a significant effect on the application behavior upon entering or during the +main loop. A partial list of some of the more important ones is presented here: + +- **intro**: *str* - if provided this serves as the intro banner printed once + at start of application, after ``preloop`` runs +- **allow_cli_args**: *bool* - if True (default), then searches for -t or + --test at command line to invoke transcript testing mode instead of a normal + main loop and also processes any commands provided as arguments on the + command line just prior to entering the main loop +- **echo**: *bool* - if True, then the command line entered is echoed to the + screen (most useful when running scripts) +- **prompt**: *str* - sets the prompt which is displayed, can be dynamically + changed based on application state and/or command results + + +Command Processing Loop +----------------------- + +When you call `.cmdloop()`, the following sequence of events are repeated until +the application exits: + +#. Output the prompt +#. Accept user input +#. Parse user input into `Statement` object +#. Call methods registered with `register_postparsing_hook()` +#. Redirect output, if user asked for it and it's allowed +#. Start timer +#. Call methods registered with `register_precmd_hook()` +#. Call `precmd()` - for backwards compatibility with ``cmd.Cmd`` +#. Add statement to history +#. Call `do_command` method +#. Call methods registered with `register_postcmd_hook()` +#. Call `postcmd(stop, statement)` - for backwards compatibility with ``cmd.Cmd`` +#. Stop timer and display the elapsed time +#. Stop redirecting output if it was redirected +#. Call methods registered with `register_cmdfinalization_hook()` + +By registering hook methods, steps 4, 8, 12, and 16 allow you to run code +during, and control the flow of the command processing loop. Be aware that +plugins also utilize these hooks, so there may be code running that is not part +of your application. Methods registered for a hook are called in the order they +were registered. You can register a function more than once, and it will be +called each time it was registered. + +Postparsing, precommand, and postcommand hook methods share some common ways to +influence the command processing loop. + +If a hook raises a ``cmd2.EmptyStatement`` exception: +- no more hooks (except command finalization hooks) of any kind will be called +- if the command has not yet been executed, it will not be executed +- no error message will be displayed to the user + +If a hook raises any other exception: +- no more hooks (except command finalization hooks) of any kind will be called +- if the command has not yet been executed, it will not be executed +- the exception message will be displayed for the user. + +Specific types of hook methods have additional options as described below. + +Postparsing Hooks +^^^^^^^^^^^^^^^^^ + +Postparsing hooks are called after the user input has been parsed but before +execution of the command. These hooks can be used to: + +- modify the user input +- run code before every command executes +- cancel execution of the current command +- exit the application + +When postparsing hooks are called, output has not been redirected, nor has the +timer for command execution been started. + +To define and register a postparsing hook, do the following:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_postparsing_hook(self.myhookmethod) + + def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: + # the statement object created from the user input + # is available as params.statement + return params + +``register_postparsing_hook()`` checks the method signature of the passed callable, +and raises a ``TypeError`` if it has the wrong number of parameters. It will +also raise a ``TypeError`` if the passed parameter and return value are not annotated +as ``PostparsingData``. + +The hook method will be passed one parameter, a ``PostparsingData`` object +which we will refer to as ``params``. ``params`` contains two attributes. +``params.statement`` is a ``Statement`` object which describes the parsed +user input. There are many useful attributes in the ``Statement`` +object, including ``.raw`` which contains exactly what the user typed. +``params.stop`` is set to ``False`` by default. + +The hook method must return a ``PostparsingData`` object, and it is very +convenient to just return the object passed into the hook method. The hook +method may modify the attributes of the object to influece the behavior of +the application. If ``params.stop`` is set to true, a fatal failure is +triggered prior to execution of the command, and the application exits. + +To modify the user input, you create a new ``Statement`` object and return it in +``params.statement``. Don't try and directly modify the contents of a +``Statement`` object, there be dragons. Instead, use the various attributes in a +``Statement`` object to construct a new string, and then parse that string to +create a new ``Statement`` object. + +``cmd2.Cmd()`` uses an instance of ``cmd2.StatementParser`` to parse user input. +This instance has been configured with the proper command terminators, multiline +commands, and other parsing related settings. This instance is available as the +``self.statement_parser`` attribute. Here's a simple example which shows the +proper technique:: + + def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: + if not '|' in params.statement.raw: + newinput = params.statement.raw + ' | less' + params.statement = self.statement_parser.parse(newinput) + return params + +If a postparsing hook returns a ``PostparsingData`` object with the ``stop`` +attribute set to ``True``: + +- no more hooks of any kind (except command finalization hooks) will be called +- the command will not be executed +- no error message will be displayed to the user +- the application will exit + + +Precommand Hooks +^^^^^^^^^^^^^^^^^ + +Precommand hooks can modify the user input, but can not request the application +terminate. If your hook needs to be able to exit the application, you should +implement it as a postparsing hook. + +Once output is redirected and the timer started, all the hooks registered with +``register_precmd_hook()`` are called. Here's how to do it:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_precmd_hook(self.myhookmethod) + + def myhookmethod(self, data: cmd2.plugin.PrecommandData) -> cmd2.plugin.PrecommandData: + # the statement object created from the user input + # is available as data.statement + return data + +``register_precmd_hook()`` checks the method signature of the passed callable, +and raises a ``TypeError`` if it has the wrong number of parameters. It will +also raise a ``TypeError`` if the parameters and return value are not annotated +as ``PrecommandData``. + +You may choose to modify the user input by creating a new ``Statement`` with +different properties (see above). If you do so, assign your new ``Statement`` +object to ``data.statement``. + +The precommand hook must return a ``PrecommandData`` object. You don't have to +create this object from scratch, you can just return the one passed into the hook. + +After all registered precommand hooks have been called, +``self.precmd(statement)`` will be called. To retain full backward compatibility +with ``cmd.Cmd``, this method is passed a ``Statement``, not a +``PrecommandData`` object. + + +Postcommand Hooks +^^^^^^^^^^^^^^^^^^ + +Once the command method has returned (i.e. the ``do_command(self, statement) +method`` has been called and returns, all postcommand hooks are called. If +output was redirected by the user, it is still redirected, and the command timer +is still running. + +Here's how to define and register a postcommand hook:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_postcmd_hook(self.myhookmethod) + + def myhookmethod(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.PostcommandData: + return data + +Your hook will be passed a ``PostcommandData`` object, which has a ``statement`` +attribute that describes the command which was executed. If your postcommand +hook method gets called, you are guaranteed that the command method was called, +and that it didn't raise an exception. + +If any postcommand hook raises an exception, the exception will be displayed to +the user, and no further postcommand hook methods will be called. Command +finalization hooks, if any, will be called. + +After all registered postcommand hooks have been called, +``self.postcmd(statement)`` will be called to retain full backward compatibility +with ``cmd.Cmd``. + +If any postcommand hook (registered or ``self.postcmd()``) returns a ``PostcommandData`` object +with the stop attribute set to ``True``, subsequent postcommand hooks will still be called, as +will the command finalization hooks, but once those hooks have all been called, the application +will terminate. Likewise, if ``self.postcmd()`` returns ``True``, the command finalization hooks +will be called before the application terminates. + +Any postcommand hook can change the value of the ``stop`` parameter before +returning it, and the modified value will be passed to the next postcommand +hook. The value returned by the final postcommand hook will be passed to the +command finalization hooks, which may further modify the value. If your hook +blindly returns ``False``, a prior hook's requst to exit the application will +not be honored. It's best to return the value you were passed unless you have a +compelling reason to do otherwise. + + +Command Finalization Hooks +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Command finalization hooks are called even if one of the other types of hooks or +the command method raise an exception. Here's how to create and register a +command finalization hook:: + + class App(cmd2.Cmd): + def __init__(self, *args, *kwargs): + super().__init__(*args, **kwargs) + self.register_cmdfinalization_hook(self.myhookmethod) + + def myhookmethod(self, stop, statement): + return stop + +Command Finalization hooks must check whether the statement object is ``None``. There are certain circumstances where these hooks may be called before the statement has been parsed, so you can't always rely on having a statement. + +If any prior postparsing or precommand hook has requested the application to +terminate, the value of the ``stop`` parameter passed to the first command +finalization hook will be ``True``. Any command finalization hook can change the +value of the ``stop`` parameter before returning it, and the modified value will +be passed to the next command finalization hook. The value returned by the final +command finalization hook will determine whether the application terminates or +not. + +This approach to command finalization hooks can be powerful, but it can also +cause problems. If your hook blindly returns ``False``, a prior hook's requst to +exit the application will not be honored. It's best to return the value you were +passed unless you have a compelling reason to do otherwise. + +If any command finalization hook raises an exception, no more command +finalization hooks will be called. If the last hook to return a value returned +``True``, then the exception will be rendered, and the application will +terminate. diff --git a/docs/features/transcript.rst b/docs/features/transcript.rst new file mode 100644 index 00000000..089ab704 --- /dev/null +++ b/docs/features/transcript.rst @@ -0,0 +1,193 @@ +======================== +Transcript based testing +======================== + +A transcript is both the input and output of a successful session of a +``cmd2``-based app which is saved to a text file. With no extra work on your +part, your app can play back these transcripts as a unit test. Transcripts can +contain regular expressions, which provide the flexibility to match responses +from commands that produce dynamic or variable output. + +.. highlight:: none + +Creating a transcript +===================== + +Automatically from history +-------------------------- +A transcript can automatically generated based upon commands previously executed in the *history* using ``history -t``:: + + (Cmd) help + ... + (Cmd) help history + ... + (Cmd) history 1:2 -t transcript.txt + 2 commands and outputs saved to transcript file 'transcript.txt' + +This is by far the easiest way to generate a transcript. + +.. warning:: + + Make sure you use the **poutput()** method in your ``cmd2`` application for generating command output. This method + of the ``cmd2.Cmd`` class ensure that output is properly redirected when redirecting to a file, piping to a shell + command, and when generating a transcript. + +Automatically from a script file +-------------------------------- +A transcript can also be automatically generated from a script file using ``run_script -t``:: + + (Cmd) run_script scripts/script.txt -t transcript.txt + 2 commands and their outputs saved to transcript file 'transcript.txt' + (Cmd) + +This is a particularly attractive option for automatically regenerating transcripts for regression testing as your ``cmd2`` +application changes. + +Manually +-------- +Here's a transcript created from ``python examples/example.py``:: + + (Cmd) say -r 3 Goodnight, Gracie + Goodnight, Gracie + Goodnight, Gracie + Goodnight, Gracie + (Cmd) mumble maybe we could go to lunch + like maybe we ... could go to hmmm lunch + (Cmd) mumble maybe we could go to lunch + well maybe we could like go to er lunch right? + +This transcript has three commands: they are on the lines that begin with the +prompt. The first command looks like this:: + + (Cmd) say -r 3 Goodnight, Gracie + +Following each command is the output generated by that command. + +The transcript ignores all lines in the file until it reaches the first line +that begins with the prompt. You can take advantage of this by using the first +lines of the transcript as comments:: + + # Lines at the beginning of the transcript that do not + ; start with the prompt i.e. '(Cmd) ' are ignored. + /* You can use them for comments. */ + + All six of these lines before the first prompt are treated as comments. + + (Cmd) say -r 3 Goodnight, Gracie + Goodnight, Gracie + Goodnight, Gracie + Goodnight, Gracie + (Cmd) mumble maybe we could go to lunch + like maybe we ... could go to hmmm lunch + (Cmd) mumble maybe we could go to lunch + maybe we could like go to er lunch right? + +In this example I've used several different commenting styles, and even bare +text. It doesn't matter what you put on those beginning lines. Everything before:: + + (Cmd) say -r 3 Goodnight, Gracie + +will be ignored. + + +Regular Expressions +=================== + +If we used the above transcript as-is, it would likely fail. As you can see, +the ``mumble`` command doesn't always return the same thing: it inserts random +words into the input. + +Regular expressions can be included in the response portion of a transcript, +and are surrounded by slashes:: + + (Cmd) mumble maybe we could go to lunch + /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ + (Cmd) mumble maybe we could go to lunch + /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ + +Without creating a tutorial on regular expressions, this one matches anything +that has the words ``maybe``, ``could``, and ``lunch`` in that order. It doesn't +ensure that ``we`` or ``go`` or ``to`` appear in the output, but it does work if +mumble happens to add words to the beginning or the end of the output. + +Since the output could be multiple lines long, ``cmd2`` uses multiline regular +expression matching, and also uses the ``DOTALL`` flag. These two flags subtly +change the behavior of commonly used special characters like ``.``, ``^`` and +``$``, so you may want to double check the `Python regular expression +documentation `_. + +If your output has slashes in it, you will need to escape those slashes so the +stuff between them is not interpred as a regular expression. In this transcript:: + + (Cmd) say cd /usr/local/lib/python3.6/site-packages + /usr/local/lib/python3.6/site-packages + +the output contains slashes. The text between the first slash and the second +slash, will be interpreted as a regular expression, and those two slashes will +not be included in the comparison. When replayed, this transcript would +therefore fail. To fix it, we could either write a regular expression to match +the path instead of specifying it verbatim, or we can escape the slashes:: + + (Cmd) say cd /usr/local/lib/python3.6/site-packages + \/usr\/local\/lib\/python3.6\/site-packages + +.. warning:: + + Be aware of trailing spaces and newlines. Your commands might output + trailing spaces which are impossible to see. Instead of leaving them + invisible, you can add a regular expression to match them, so that you can + see where they are when you look at the transcript:: + + (Cmd) set prompt + prompt: (Cmd)/ / + + Some terminal emulators strip trailing space when you copy text from them. + This could make the actual data generated by your app different than the + text you pasted into the transcript, and it might not be readily obvious why + the transcript is not passing. Consider using :ref:`output_redirection` to + the clipboard or to a file to ensure you accurately capture the output of + your command. + + If you aren't using regular expressions, make sure the newlines at the end + of your transcript exactly match the output of your commands. A common cause + of a failing transcript is an extra or missing newline. + + If you are using regular expressions, be aware that depending on how you + write your regex, the newlines after the regex may or may not matter. + ``\Z`` matches *after* the newline at the end of the string, whereas + ``$`` matches the end of the string *or* just before a newline. + + +Running a transcript +==================== + +Once you have created a transcript, it's easy to have your application play it +back and check the output. From within the ``examples/`` directory:: + + $ python example.py --test transcript_regex.txt + . + ---------------------------------------------------------------------- + Ran 1 test in 0.013s + + OK + +The output will look familiar if you use ``unittest``, because that's exactly +what happens. Each command in the transcript is run, and we ``assert`` the +output matches the expected result from the transcript. + +.. note:: + + If you have set ``allow_cli_args`` to False in order to disable parsing of + command line arguments at invocation, then the use of ``-t`` or ``--test`` + to run transcript testing is automatically disabled. In this case, you can + alternatively provide a value for the optional ``transcript_files`` when + constructing the instance of your ``cmd2.Cmd`` derived class in order to + cause a transcript test to run:: + + from cmd2 import Cmd + class App(Cmd): + # customized attributes and methods here + + if __name__ == '__main__': + app = App(transcript_files=['exampleSession.txt']) + app.cmdloop() diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst index a06bab90..e7a4c35b 100644 --- a/docs/freefeatures.rst +++ b/docs/freefeatures.rst @@ -387,7 +387,7 @@ save the first 5 commands entered in this session to a text file:: (Cmd) history :5 -o history.txt The ``history`` command can also save both the commands and their output to a -text file. This is called a transcript. See :doc:`transcript` for more +text file. This is called a transcript. See :doc:`features/transcript` for more information on how transcripts work, and what you can use them for. To create a transcript use the ``-t`` or ``--transcription`` option:: @@ -494,7 +494,7 @@ back into the app as a unit test. OK -See :doc:`transcript` for more details. +See :doc:`features/transcript` for more details. Tab-Completion diff --git a/docs/hooks.rst b/docs/hooks.rst deleted file mode 100644 index 5db97fe5..00000000 --- a/docs/hooks.rst +++ /dev/null @@ -1,305 +0,0 @@ -.. cmd2 documentation for application and command lifecycle and the available hooks - -cmd2 Application Lifecycle and Hooks -==================================== - -The typical way of starting a cmd2 application is as follows:: - - import cmd2 - class App(cmd2.Cmd): - # customized attributes and methods here - - if __name__ == '__main__': - app = App() - app.cmdloop() - -There are several pre-existing methods and attributes which you can tweak to -control the overall behavior of your application before, during, and after the -command processing loop. - -Application Lifecycle Hooks ---------------------------- - -You can register methods to be called at the beginning of the command loop:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_preloop_hook(self.myhookmethod) - - def myhookmethod(self): - self.poutput("before the loop begins") - -To retain backwards compatibility with `cmd.Cmd`, after all registered preloop -hooks have been called, the ``preloop()`` method is called. - -A similar approach allows you to register functions to be called after the -command loop has finished:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_postloop_hook(self.myhookmethod) - - def myhookmethod(self): - self.poutput("before the loop begins") - -To retain backwards compatibility with `cmd.Cmd`, after all registered postloop -hooks have been called, the ``postloop()`` method is called. - -Preloop and postloop hook methods are not passed any parameters and any return -value is ignored. - - -Application Lifecycle Attributes --------------------------------- - -There are numerous attributes of and arguments to ``cmd2.Cmd`` which have -a significant effect on the application behavior upon entering or during the -main loop. A partial list of some of the more important ones is presented here: - -- **intro**: *str* - if provided this serves as the intro banner printed once - at start of application, after ``preloop`` runs -- **allow_cli_args**: *bool* - if True (default), then searches for -t or - --test at command line to invoke transcript testing mode instead of a normal - main loop and also processes any commands provided as arguments on the - command line just prior to entering the main loop -- **echo**: *bool* - if True, then the command line entered is echoed to the - screen (most useful when running scripts) -- **prompt**: *str* - sets the prompt which is displayed, can be dynamically - changed based on application state and/or command results - - -Command Processing Loop ------------------------ - -When you call `.cmdloop()`, the following sequence of events are repeated until -the application exits: - -#. Output the prompt -#. Accept user input -#. Parse user input into `Statement` object -#. Call methods registered with `register_postparsing_hook()` -#. Redirect output, if user asked for it and it's allowed -#. Start timer -#. Call methods registered with `register_precmd_hook()` -#. Call `precmd()` - for backwards compatibility with ``cmd.Cmd`` -#. Add statement to history -#. Call `do_command` method -#. Call methods registered with `register_postcmd_hook()` -#. Call `postcmd(stop, statement)` - for backwards compatibility with ``cmd.Cmd`` -#. Stop timer and display the elapsed time -#. Stop redirecting output if it was redirected -#. Call methods registered with `register_cmdfinalization_hook()` - -By registering hook methods, steps 4, 8, 12, and 16 allow you to run code -during, and control the flow of the command processing loop. Be aware that -plugins also utilize these hooks, so there may be code running that is not part -of your application. Methods registered for a hook are called in the order they -were registered. You can register a function more than once, and it will be -called each time it was registered. - -Postparsing, precommand, and postcommand hook methods share some common ways to -influence the command processing loop. - -If a hook raises a ``cmd2.EmptyStatement`` exception: -- no more hooks (except command finalization hooks) of any kind will be called -- if the command has not yet been executed, it will not be executed -- no error message will be displayed to the user - -If a hook raises any other exception: -- no more hooks (except command finalization hooks) of any kind will be called -- if the command has not yet been executed, it will not be executed -- the exception message will be displayed for the user. - -Specific types of hook methods have additional options as described below. - -Postparsing Hooks -^^^^^^^^^^^^^^^^^ - -Postparsing hooks are called after the user input has been parsed but before -execution of the command. These hooks can be used to: - -- modify the user input -- run code before every command executes -- cancel execution of the current command -- exit the application - -When postparsing hooks are called, output has not been redirected, nor has the -timer for command execution been started. - -To define and register a postparsing hook, do the following:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_postparsing_hook(self.myhookmethod) - - def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - # the statement object created from the user input - # is available as params.statement - return params - -``register_postparsing_hook()`` checks the method signature of the passed callable, -and raises a ``TypeError`` if it has the wrong number of parameters. It will -also raise a ``TypeError`` if the passed parameter and return value are not annotated -as ``PostparsingData``. - -The hook method will be passed one parameter, a ``PostparsingData`` object -which we will refer to as ``params``. ``params`` contains two attributes. -``params.statement`` is a ``Statement`` object which describes the parsed -user input. There are many useful attributes in the ``Statement`` -object, including ``.raw`` which contains exactly what the user typed. -``params.stop`` is set to ``False`` by default. - -The hook method must return a ``PostparsingData`` object, and it is very -convenient to just return the object passed into the hook method. The hook -method may modify the attributes of the object to influece the behavior of -the application. If ``params.stop`` is set to true, a fatal failure is -triggered prior to execution of the command, and the application exits. - -To modify the user input, you create a new ``Statement`` object and return it in -``params.statement``. Don't try and directly modify the contents of a -``Statement`` object, there be dragons. Instead, use the various attributes in a -``Statement`` object to construct a new string, and then parse that string to -create a new ``Statement`` object. - -``cmd2.Cmd()`` uses an instance of ``cmd2.StatementParser`` to parse user input. -This instance has been configured with the proper command terminators, multiline -commands, and other parsing related settings. This instance is available as the -``self.statement_parser`` attribute. Here's a simple example which shows the -proper technique:: - - def myhookmethod(self, params: cmd2.plugin.PostparsingData) -> cmd2.plugin.PostparsingData: - if not '|' in params.statement.raw: - newinput = params.statement.raw + ' | less' - params.statement = self.statement_parser.parse(newinput) - return params - -If a postparsing hook returns a ``PostparsingData`` object with the ``stop`` -attribute set to ``True``: - -- no more hooks of any kind (except command finalization hooks) will be called -- the command will not be executed -- no error message will be displayed to the user -- the application will exit - - -Precommand Hooks -^^^^^^^^^^^^^^^^^ - -Precommand hooks can modify the user input, but can not request the application -terminate. If your hook needs to be able to exit the application, you should -implement it as a postparsing hook. - -Once output is redirected and the timer started, all the hooks registered with -``register_precmd_hook()`` are called. Here's how to do it:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_precmd_hook(self.myhookmethod) - - def myhookmethod(self, data: cmd2.plugin.PrecommandData) -> cmd2.plugin.PrecommandData: - # the statement object created from the user input - # is available as data.statement - return data - -``register_precmd_hook()`` checks the method signature of the passed callable, -and raises a ``TypeError`` if it has the wrong number of parameters. It will -also raise a ``TypeError`` if the parameters and return value are not annotated -as ``PrecommandData``. - -You may choose to modify the user input by creating a new ``Statement`` with -different properties (see above). If you do so, assign your new ``Statement`` -object to ``data.statement``. - -The precommand hook must return a ``PrecommandData`` object. You don't have to -create this object from scratch, you can just return the one passed into the hook. - -After all registered precommand hooks have been called, -``self.precmd(statement)`` will be called. To retain full backward compatibility -with ``cmd.Cmd``, this method is passed a ``Statement``, not a -``PrecommandData`` object. - - -Postcommand Hooks -^^^^^^^^^^^^^^^^^^ - -Once the command method has returned (i.e. the ``do_command(self, statement) -method`` has been called and returns, all postcommand hooks are called. If -output was redirected by the user, it is still redirected, and the command timer -is still running. - -Here's how to define and register a postcommand hook:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_postcmd_hook(self.myhookmethod) - - def myhookmethod(self, data: cmd2.plugin.PostcommandData) -> cmd2.plugin.PostcommandData: - return data - -Your hook will be passed a ``PostcommandData`` object, which has a ``statement`` -attribute that describes the command which was executed. If your postcommand -hook method gets called, you are guaranteed that the command method was called, -and that it didn't raise an exception. - -If any postcommand hook raises an exception, the exception will be displayed to -the user, and no further postcommand hook methods will be called. Command -finalization hooks, if any, will be called. - -After all registered postcommand hooks have been called, -``self.postcmd(statement)`` will be called to retain full backward compatibility -with ``cmd.Cmd``. - -If any postcommand hook (registered or ``self.postcmd()``) returns a ``PostcommandData`` object -with the stop attribute set to ``True``, subsequent postcommand hooks will still be called, as -will the command finalization hooks, but once those hooks have all been called, the application -will terminate. Likewise, if ``self.postcmd()`` returns ``True``, the command finalization hooks -will be called before the application terminates. - -Any postcommand hook can change the value of the ``stop`` parameter before -returning it, and the modified value will be passed to the next postcommand -hook. The value returned by the final postcommand hook will be passed to the -command finalization hooks, which may further modify the value. If your hook -blindly returns ``False``, a prior hook's requst to exit the application will -not be honored. It's best to return the value you were passed unless you have a -compelling reason to do otherwise. - - -Command Finalization Hooks -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Command finalization hooks are called even if one of the other types of hooks or -the command method raise an exception. Here's how to create and register a -command finalization hook:: - - class App(cmd2.Cmd): - def __init__(self, *args, *kwargs): - super().__init__(*args, **kwargs) - self.register_cmdfinalization_hook(self.myhookmethod) - - def myhookmethod(self, stop, statement): - return stop - -Command Finalization hooks must check whether the statement object is ``None``. There are certain circumstances where these hooks may be called before the statement has been parsed, so you can't always rely on having a statement. - -If any prior postparsing or precommand hook has requested the application to -terminate, the value of the ``stop`` parameter passed to the first command -finalization hook will be ``True``. Any command finalization hook can change the -value of the ``stop`` parameter before returning it, and the modified value will -be passed to the next command finalization hook. The value returned by the final -command finalization hook will determine whether the application terminates or -not. - -This approach to command finalization hooks can be powerful, but it can also -cause problems. If your hook blindly returns ``False``, a prior hook's requst to -exit the application will not be honored. It's best to return the value you were -passed unless you have a compelling reason to do otherwise. - -If any command finalization hook raises an exception, no more command -finalization hooks will be called. If the last hook to return a value returned -``True``, then the exception will be rendered, and the application will -terminate. diff --git a/docs/index.rst b/docs/index.rst index 5f9c4c3d..515209e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,23 +1,15 @@ -.. cmd2 documentation master file, created by - sphinx-quickstart on Wed Feb 10 12:05:28 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - cmd2 ==== -A python package for building powerful command-line interpreter (CLI) -programs. Extends the Python Standard Library's cmd_ package. +.. default-domain:: py .. _cmd: https://docs.python.org/3/library/cmd.html -.. _`cmd2 project page`: https://github.com/python-cmd2/cmd2 -.. _`project bug tracker`: https://github.com/python-cmd2/cmd2/issues +A python package for building powerful command-line interpreter (CLI) +programs. Extends the Python Standard Library's cmd_ package. The basic use of ``cmd2`` is identical to that of cmd_. -.. highlight:: python - 1. Create a subclass of ``cmd2.Cmd``. Define attributes and ``do_*`` methods to control its behavior. Throughout this documentation, we will assume that you are naming your subclass ``App``:: @@ -31,51 +23,108 @@ The basic use of ``cmd2`` is identical to that of cmd_. app = App() app.cmdloop() -.. note:: - The tab-completion feature provided by cmd_ relies on underlying capability provided by GNU readline or an - equivalent library. Linux distros will almost always come with the required library installed. - For macOS, we recommend using the `gnureadline `_ Python module which includes - a statically linked version of GNU readline. Alternatively on macOS the ``conda`` package manager that comes - with the Anaconda Python distro can be used to install ``readline`` (preferably from conda-forge) or the - `Homebrew `_ package manager can be used to to install the ``readline`` package. - For Windows, we recommend installing the `pyreadline `_ Python module. +Overview +-------- -Resources ---------- +[create links with short descriptions to the various overview pages here] -* cmd_ -* `cmd2 project page`_ -* `project bug tracker`_ -* Florida PyCon 2017: `slides `_, `video `_ +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Overview + + overview/featuretour + overview/installation + overview/alternatives + overview/resources + examples/quickstart -These docs will refer to ``App`` as your ``cmd2.Cmd`` -subclass, and ``app`` as an instance of ``App``. Of -course, in your program, you may name them whatever -you want. -Contents: +Migrating from cmd +------------------ + +[create links with short descriptions to the various migrating pages here] .. toctree:: :maxdepth: 2 + :hidden: + :caption: Migrating from cmd - install - overview - freefeatures - settingchanges - unfreefeatures - transcript - argument_processing - integrating - hooks - alternatives + migrating/why + migrating/incompatibilities + migrating/minimum + migrating/free_features + migrating/nextsteps + + +Features +-------- + +[create links with short descriptions to the various feature pages here] + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Features + + features/generating_output + features/argument_processing + features/help + features/transcript + features/hooks -Compatibility -============= -Tested and working with Python 3.5+ on Windows, macOS, and Linux. +Examples +-------------------- -Index -===== +[create links with short descriptions to the various examples pages here] + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Examples -* :ref:`genindex` + examples/quickstart + + +API Reference +------------- + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: API Reference + + api/cmd + api/decorators + api/exceptions + api/utility_functions + api/utility_classes + + +Meta +---- + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: Meta + + doc_conventions + + +To Be Integrated +---------------- + +Files from old documentation to be integrated into new structure + +.. toctree:: + :maxdepth: 2 + :hidden: + :caption: To Be Integrated + + freefeatures + integrating + settingchanges + unfreefeatures diff --git a/docs/install.rst b/docs/install.rst deleted file mode 100644 index 62765704..00000000 --- a/docs/install.rst +++ /dev/null @@ -1,155 +0,0 @@ - -Installation Instructions -========================= - -This section covers the basics of how to install, upgrade, and uninstall ``cmd2``. - -Installing ----------- -First you need to make sure you have Python 3.5+, pip_, and setuptools_. Then you can just use pip to -install from PyPI_. - -.. _pip: https://pypi.python.org/pypi/pip -.. _setuptools: https://pypi.python.org/pypi/setuptools -.. _PyPI: https://pypi.python.org/pypi - -.. note:: - - Depending on how and where you have installed Python on your system and on what OS you are using, you may need to - have administrator or root privileges to install Python packages. If this is the case, take the necessary steps - required to run the commands in this section as root/admin, e.g.: on most Linux or Mac systems, you can precede them - with ``sudo``:: - - sudo pip install - - -Requirements for Installing -~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* If you have Python 3 >=3.5 installed from `python.org - `_, you will already have pip_ and - setuptools_, but may need to upgrade to the latest versions: - - On Linux or OS X: - - :: - - pip install -U pip setuptools - - - On Windows: - - :: - - python -m pip install -U pip setuptools - - -.. _`pip_install`: - -Use pip for Installing -~~~~~~~~~~~~~~~~~~~~~~ - -pip_ is the recommended installer. Installing packages from PyPI_ with pip is easy:: - - pip install cmd2 - -This should also install the required 3rd-party dependencies, if necessary. - - -.. _github: - -Install from GitHub using pip -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The latest version of ``cmd2`` can be installed directly from the master branch on GitHub using pip_:: - - pip install -U git+git://github.com/python-cmd2/cmd2.git - -This should also install the required 3rd-party dependencies, if necessary. - - -Install from Debian or Ubuntu repos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We recommend installing from pip_, but if you wish to install from Debian or Ubuntu repos this can be done with -apt-get. - -For Python 3:: - - sudo apt-get install python3-cmd2 - -This will also install the required 3rd-party dependencies. - -.. warning:: - - Versions of ``cmd2`` before 0.7.0 should be considered to be of unstable "beta" quality and should not be relied upon - for production use. If you cannot get a version >= 0.7 from your OS repository, then we recommend - installing from either pip or GitHub - see :ref:`pip_install` or :ref:`github`. - - -Deploy cmd2.py with your project -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``cmd2`` is contained in a small number of Python files, which can be easily copied into your project. *The -copyright and license notice must be retained*. - -This is an option suitable for advanced Python users. You can simply include the files within your project's hierarchy. -If you want to modify ``cmd2``, this may be a reasonable option. Though, we encourage you to use stock ``cmd2`` and -either composition or inheritance to achieve the same goal. - -This approach will obviously NOT automatically install the required 3rd-party dependencies, so you need to make sure -the following Python packages are installed: - - * attrs - * colorama - * pyperclip - * wcwidth - -On Windows, there is an additional dependency: - - * pyreadline - - -Upgrading cmd2 --------------- - -Upgrade an already installed ``cmd2`` to the latest version from PyPI_:: - - pip install -U cmd2 - -This will upgrade to the newest stable version of ``cmd2`` and will also upgrade any dependencies if necessary. - - -Uninstalling cmd2 ------------------ -If you wish to permanently uninstall ``cmd2``, this can also easily be done with pip_:: - - pip uninstall cmd2 - - -Extra requirement for macOS -=========================== -macOS comes with the `libedit `_ library which is similar, but not identical, to GNU Readline. -Tab-completion for ``cmd2`` applications is only tested against GNU Readline. - -There are several ways GNU Readline can be installed within a Python environment on a Mac, detailed in the following subsections. - -gnureadline Python module -------------------------- -Install the `gnureadline `_ Python module which is statically linked against a specific compatible version of GNU Readline:: - - pip install -U gnureadline - -readline via conda ------------------- -Install the **readline** package using the ``conda`` package manager included with the Anaconda Python distribution:: - - conda install readline - -readline via brew ------------------ -Install the **readline** package using the Homebrew package manager (compiles from source):: - - brew install openssl - brew install pyenv - brew install readline - -Then use pyenv to compile Python and link against the installed readline diff --git a/docs/migrating/free_features.rst b/docs/migrating/free_features.rst new file mode 100644 index 00000000..afb29fc3 --- /dev/null +++ b/docs/migrating/free_features.rst @@ -0,0 +1,5 @@ +What you get for free +===================== + +A brief list (with links to details) of major features you get for free once +you migrate. diff --git a/docs/migrating/incompatibilities.rst b/docs/migrating/incompatibilities.rst new file mode 100644 index 00000000..3d7ddcfb --- /dev/null +++ b/docs/migrating/incompatibilities.rst @@ -0,0 +1,21 @@ +Incompatibilities +================= + +.. _cmd: https://docs.python.org/3/library/cmd.html + +``cmd2`` strives to be drop-in compatible with cmd_, however there are a few things +that are not. + + +cmd.emptyline() +--------------- + +The `cmd.emptyline() +`_ function is +called when an empty line is entered in response to the prompt. By default, in +cmd_ if this method is not overridden, it repeats and executes the last nonempty +command entered. However, no end user we have encountered views this as +expected or desirable default behavior. Thus, the default behavior in ``cmd2`` +is to simply go to the next line and issue the prompt again. At this time, cmd2 +completely ignores empty lines and the base class cmd.emptyline() method never +gets called and thus the emptyline() behavior cannot be overridden. diff --git a/docs/migrating/minimum.rst b/docs/migrating/minimum.rst new file mode 100644 index 00000000..098ba79c --- /dev/null +++ b/docs/migrating/minimum.rst @@ -0,0 +1,4 @@ +Minimum required changes +======================== + +The minimum required changes to move to cmd2 \ No newline at end of file diff --git a/docs/migrating/nextsteps.rst b/docs/migrating/nextsteps.rst new file mode 100644 index 00000000..3f560501 --- /dev/null +++ b/docs/migrating/nextsteps.rst @@ -0,0 +1,6 @@ +Next Steps +========== + +What features (with links to details) are easy to implement next + +:doc:`Help <../features/help>` diff --git a/docs/migrating/why.rst b/docs/migrating/why.rst new file mode 100644 index 00000000..d1121128 --- /dev/null +++ b/docs/migrating/why.rst @@ -0,0 +1,25 @@ +Why Migrate to cmd2 +=================== + +.. _cmd: https://docs.python.org/3/library/cmd.html + +``cmd2`` is an extension of cmd_, the Python Standard Library's module for +creating simple interactive command-line applications. + +``cmd2`` can be used as a drop-in replacement for cmd_. Simply importing ``cmd2`` +in place of cmd_ will add many features to an application without any further +modifications. + +Understanding the use of cmd_ is the first step in learning the use of ``cmd2``. +Once you have read the cmd_ docs, return here to learn the ways that ``cmd2`` +differs from cmd_. + + +Describe why you would want to migrate, and the benefits of doing so + +Unicode + +features + +active community + diff --git a/docs/overview.rst b/docs/overview.rst deleted file mode 100644 index 75b8caa9..00000000 --- a/docs/overview.rst +++ /dev/null @@ -1,27 +0,0 @@ - -======== -Overview -======== - -``cmd2`` is an extension of cmd_, the Python Standard Library's module for -creating simple interactive command-line applications. - -``cmd2`` can be used as a drop-in replacement for cmd_. Simply importing ``cmd2`` -in place of cmd_ will add many features to an application without any further -modifications. - -Understanding the use of cmd_ is the first step in learning the use of ``cmd2``. -Once you have read the cmd_ docs, return here to learn the ways that ``cmd2`` -differs from cmd_. - -.. note:: - - ``cmd2`` is not quite a drop-in replacement for cmd_. - The `cmd.emptyline() `_ function is called - when an empty line is entered in response to the prompt. By default, in cmd_ if this method is not overridden, it - repeats and executes the last nonempty command entered. However, no end user we have encountered views this as - expected or desirable default behavior. Thus, the default behavior in ``cmd2`` is to simply go to the next line - and issue the prompt again. At this time, cmd2 completely ignores empty lines and the base class cmd.emptyline() - method never gets called and thus the emptyline() behavior cannot be overridden. - -.. _cmd: https://docs.python.org/3/library/cmd.html diff --git a/docs/overview/alternatives.rst b/docs/overview/alternatives.rst new file mode 100644 index 00000000..bf1545d6 --- /dev/null +++ b/docs/overview/alternatives.rst @@ -0,0 +1,53 @@ +============================ +Alternatives to cmd and cmd2 +============================ + +For programs that do not interact with the user in a continuous loop - +programs that simply accept a set of arguments from the command line, return +results, and do not keep the user within the program's environment - all +you need are sys_\ .argv (the command-line arguments) and argparse_ +(for parsing UNIX-style options and flags). Though some people may prefer docopt_ +or click_ to argparse_. + +.. _sys: https://docs.python.org/3/library/sys.html +.. _argparse: https://docs.python.org/3/library/argparse.html +.. _docopt: https://pypi.python.org/pypi/docopt +.. _click: http://click.pocoo.org + + +The curses_ module produces applications that interact via a plaintext +terminal window, but are not limited to simple text input and output; +they can paint the screen with options that are selected from using the +cursor keys. However, programming a curses_-based application is not as +straightforward as using cmd_. + +.. _curses: https://docs.python.org/3/library/curses.html +.. _cmd: https://docs.python.org/3/library/cmd.html + +Several Python packages exist for building interactive command-line applications +approximately similar in concept to cmd_ applications. None of them +share ``cmd2``'s close ties to cmd_, but they may be worth investigating +nonetheless. Two of the most mature and full featured are: + + * `Python Prompt Toolkit`_ + * Click_ + +.. _`Python Prompt Toolkit`: https://github.com/jonathanslenders/python-prompt-toolkit + +`Python Prompt Toolkit`_ is a library for building powerful interactive command lines and terminal applications in +Python. It provides a lot of advanced visual features like syntax highlighting, bottom bars, and the ability to +create fullscreen apps. + +Click_ is a Python package for creating beautiful command line interfaces in a composable way with as little code as +necessary. It is more geared towards command line utilities instead of command line interpreters, but it can be used +for either. + +Getting a working command-interpreter application based on either `Python Prompt Toolkit`_ or Click_ requires a good +deal more effort and boilerplate code than ``cmd2``. ``cmd2`` focuses on providing an excellent out-of-the-box experience +with as many useful features as possible built in for free with as little work required on the developer's part as +possible. We believe that ``cmd2`` provides developers the easiest way to write a command-line interpreter, while +allowing a good experience for end users. If you are seeking a visually richer end-user experience and don't +mind investing more development time, we would recommend checking out `Python Prompt Toolkit`_. + +In the future, we may investigate options for incorporating the usage of `Python Prompt Toolkit`_ and/or Click_ into +``cmd2`` applications. diff --git a/docs/overview/featuretour.rst b/docs/overview/featuretour.rst new file mode 100644 index 00000000..15754733 --- /dev/null +++ b/docs/overview/featuretour.rst @@ -0,0 +1,6 @@ +Features +======== + +Briefly describe the list of major features, linking to the more detailed description +of each features elsewhere in the documentation. + diff --git a/docs/overview/installation.rst b/docs/overview/installation.rst new file mode 100644 index 00000000..62765704 --- /dev/null +++ b/docs/overview/installation.rst @@ -0,0 +1,155 @@ + +Installation Instructions +========================= + +This section covers the basics of how to install, upgrade, and uninstall ``cmd2``. + +Installing +---------- +First you need to make sure you have Python 3.5+, pip_, and setuptools_. Then you can just use pip to +install from PyPI_. + +.. _pip: https://pypi.python.org/pypi/pip +.. _setuptools: https://pypi.python.org/pypi/setuptools +.. _PyPI: https://pypi.python.org/pypi + +.. note:: + + Depending on how and where you have installed Python on your system and on what OS you are using, you may need to + have administrator or root privileges to install Python packages. If this is the case, take the necessary steps + required to run the commands in this section as root/admin, e.g.: on most Linux or Mac systems, you can precede them + with ``sudo``:: + + sudo pip install + + +Requirements for Installing +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +* If you have Python 3 >=3.5 installed from `python.org + `_, you will already have pip_ and + setuptools_, but may need to upgrade to the latest versions: + + On Linux or OS X: + + :: + + pip install -U pip setuptools + + + On Windows: + + :: + + python -m pip install -U pip setuptools + + +.. _`pip_install`: + +Use pip for Installing +~~~~~~~~~~~~~~~~~~~~~~ + +pip_ is the recommended installer. Installing packages from PyPI_ with pip is easy:: + + pip install cmd2 + +This should also install the required 3rd-party dependencies, if necessary. + + +.. _github: + +Install from GitHub using pip +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The latest version of ``cmd2`` can be installed directly from the master branch on GitHub using pip_:: + + pip install -U git+git://github.com/python-cmd2/cmd2.git + +This should also install the required 3rd-party dependencies, if necessary. + + +Install from Debian or Ubuntu repos +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +We recommend installing from pip_, but if you wish to install from Debian or Ubuntu repos this can be done with +apt-get. + +For Python 3:: + + sudo apt-get install python3-cmd2 + +This will also install the required 3rd-party dependencies. + +.. warning:: + + Versions of ``cmd2`` before 0.7.0 should be considered to be of unstable "beta" quality and should not be relied upon + for production use. If you cannot get a version >= 0.7 from your OS repository, then we recommend + installing from either pip or GitHub - see :ref:`pip_install` or :ref:`github`. + + +Deploy cmd2.py with your project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``cmd2`` is contained in a small number of Python files, which can be easily copied into your project. *The +copyright and license notice must be retained*. + +This is an option suitable for advanced Python users. You can simply include the files within your project's hierarchy. +If you want to modify ``cmd2``, this may be a reasonable option. Though, we encourage you to use stock ``cmd2`` and +either composition or inheritance to achieve the same goal. + +This approach will obviously NOT automatically install the required 3rd-party dependencies, so you need to make sure +the following Python packages are installed: + + * attrs + * colorama + * pyperclip + * wcwidth + +On Windows, there is an additional dependency: + + * pyreadline + + +Upgrading cmd2 +-------------- + +Upgrade an already installed ``cmd2`` to the latest version from PyPI_:: + + pip install -U cmd2 + +This will upgrade to the newest stable version of ``cmd2`` and will also upgrade any dependencies if necessary. + + +Uninstalling cmd2 +----------------- +If you wish to permanently uninstall ``cmd2``, this can also easily be done with pip_:: + + pip uninstall cmd2 + + +Extra requirement for macOS +=========================== +macOS comes with the `libedit `_ library which is similar, but not identical, to GNU Readline. +Tab-completion for ``cmd2`` applications is only tested against GNU Readline. + +There are several ways GNU Readline can be installed within a Python environment on a Mac, detailed in the following subsections. + +gnureadline Python module +------------------------- +Install the `gnureadline `_ Python module which is statically linked against a specific compatible version of GNU Readline:: + + pip install -U gnureadline + +readline via conda +------------------ +Install the **readline** package using the ``conda`` package manager included with the Anaconda Python distribution:: + + conda install readline + +readline via brew +----------------- +Install the **readline** package using the Homebrew package manager (compiles from source):: + + brew install openssl + brew install pyenv + brew install readline + +Then use pyenv to compile Python and link against the installed readline diff --git a/docs/overview/resources.rst b/docs/overview/resources.rst new file mode 100644 index 00000000..487ac316 --- /dev/null +++ b/docs/overview/resources.rst @@ -0,0 +1,13 @@ +Resources +========= + +.. _cmd: https://docs.python.org/3/library/cmd.html +.. _`cmd2 project page`: https://github.com/python-cmd2/cmd2 +.. _`project bug tracker`: https://github.com/python-cmd2/cmd2/issues + +Project related links and other resources: + +* cmd_ +* `cmd2 project page`_ +* `project bug tracker`_ +* Florida PyCon 2017: `slides `_, `video `_ diff --git a/docs/transcript.rst b/docs/transcript.rst deleted file mode 100644 index 089ab704..00000000 --- a/docs/transcript.rst +++ /dev/null @@ -1,193 +0,0 @@ -======================== -Transcript based testing -======================== - -A transcript is both the input and output of a successful session of a -``cmd2``-based app which is saved to a text file. With no extra work on your -part, your app can play back these transcripts as a unit test. Transcripts can -contain regular expressions, which provide the flexibility to match responses -from commands that produce dynamic or variable output. - -.. highlight:: none - -Creating a transcript -===================== - -Automatically from history --------------------------- -A transcript can automatically generated based upon commands previously executed in the *history* using ``history -t``:: - - (Cmd) help - ... - (Cmd) help history - ... - (Cmd) history 1:2 -t transcript.txt - 2 commands and outputs saved to transcript file 'transcript.txt' - -This is by far the easiest way to generate a transcript. - -.. warning:: - - Make sure you use the **poutput()** method in your ``cmd2`` application for generating command output. This method - of the ``cmd2.Cmd`` class ensure that output is properly redirected when redirecting to a file, piping to a shell - command, and when generating a transcript. - -Automatically from a script file --------------------------------- -A transcript can also be automatically generated from a script file using ``run_script -t``:: - - (Cmd) run_script scripts/script.txt -t transcript.txt - 2 commands and their outputs saved to transcript file 'transcript.txt' - (Cmd) - -This is a particularly attractive option for automatically regenerating transcripts for regression testing as your ``cmd2`` -application changes. - -Manually --------- -Here's a transcript created from ``python examples/example.py``:: - - (Cmd) say -r 3 Goodnight, Gracie - Goodnight, Gracie - Goodnight, Gracie - Goodnight, Gracie - (Cmd) mumble maybe we could go to lunch - like maybe we ... could go to hmmm lunch - (Cmd) mumble maybe we could go to lunch - well maybe we could like go to er lunch right? - -This transcript has three commands: they are on the lines that begin with the -prompt. The first command looks like this:: - - (Cmd) say -r 3 Goodnight, Gracie - -Following each command is the output generated by that command. - -The transcript ignores all lines in the file until it reaches the first line -that begins with the prompt. You can take advantage of this by using the first -lines of the transcript as comments:: - - # Lines at the beginning of the transcript that do not - ; start with the prompt i.e. '(Cmd) ' are ignored. - /* You can use them for comments. */ - - All six of these lines before the first prompt are treated as comments. - - (Cmd) say -r 3 Goodnight, Gracie - Goodnight, Gracie - Goodnight, Gracie - Goodnight, Gracie - (Cmd) mumble maybe we could go to lunch - like maybe we ... could go to hmmm lunch - (Cmd) mumble maybe we could go to lunch - maybe we could like go to er lunch right? - -In this example I've used several different commenting styles, and even bare -text. It doesn't matter what you put on those beginning lines. Everything before:: - - (Cmd) say -r 3 Goodnight, Gracie - -will be ignored. - - -Regular Expressions -=================== - -If we used the above transcript as-is, it would likely fail. As you can see, -the ``mumble`` command doesn't always return the same thing: it inserts random -words into the input. - -Regular expressions can be included in the response portion of a transcript, -and are surrounded by slashes:: - - (Cmd) mumble maybe we could go to lunch - /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ - (Cmd) mumble maybe we could go to lunch - /.*\bmaybe\b.*\bcould\b.*\blunch\b.*/ - -Without creating a tutorial on regular expressions, this one matches anything -that has the words ``maybe``, ``could``, and ``lunch`` in that order. It doesn't -ensure that ``we`` or ``go`` or ``to`` appear in the output, but it does work if -mumble happens to add words to the beginning or the end of the output. - -Since the output could be multiple lines long, ``cmd2`` uses multiline regular -expression matching, and also uses the ``DOTALL`` flag. These two flags subtly -change the behavior of commonly used special characters like ``.``, ``^`` and -``$``, so you may want to double check the `Python regular expression -documentation `_. - -If your output has slashes in it, you will need to escape those slashes so the -stuff between them is not interpred as a regular expression. In this transcript:: - - (Cmd) say cd /usr/local/lib/python3.6/site-packages - /usr/local/lib/python3.6/site-packages - -the output contains slashes. The text between the first slash and the second -slash, will be interpreted as a regular expression, and those two slashes will -not be included in the comparison. When replayed, this transcript would -therefore fail. To fix it, we could either write a regular expression to match -the path instead of specifying it verbatim, or we can escape the slashes:: - - (Cmd) say cd /usr/local/lib/python3.6/site-packages - \/usr\/local\/lib\/python3.6\/site-packages - -.. warning:: - - Be aware of trailing spaces and newlines. Your commands might output - trailing spaces which are impossible to see. Instead of leaving them - invisible, you can add a regular expression to match them, so that you can - see where they are when you look at the transcript:: - - (Cmd) set prompt - prompt: (Cmd)/ / - - Some terminal emulators strip trailing space when you copy text from them. - This could make the actual data generated by your app different than the - text you pasted into the transcript, and it might not be readily obvious why - the transcript is not passing. Consider using :ref:`output_redirection` to - the clipboard or to a file to ensure you accurately capture the output of - your command. - - If you aren't using regular expressions, make sure the newlines at the end - of your transcript exactly match the output of your commands. A common cause - of a failing transcript is an extra or missing newline. - - If you are using regular expressions, be aware that depending on how you - write your regex, the newlines after the regex may or may not matter. - ``\Z`` matches *after* the newline at the end of the string, whereas - ``$`` matches the end of the string *or* just before a newline. - - -Running a transcript -==================== - -Once you have created a transcript, it's easy to have your application play it -back and check the output. From within the ``examples/`` directory:: - - $ python example.py --test transcript_regex.txt - . - ---------------------------------------------------------------------- - Ran 1 test in 0.013s - - OK - -The output will look familiar if you use ``unittest``, because that's exactly -what happens. Each command in the transcript is run, and we ``assert`` the -output matches the expected result from the transcript. - -.. note:: - - If you have set ``allow_cli_args`` to False in order to disable parsing of - command line arguments at invocation, then the use of ``-t`` or ``--test`` - to run transcript testing is automatically disabled. In this case, you can - alternatively provide a value for the optional ``transcript_files`` when - constructing the instance of your ``cmd2.Cmd`` derived class in order to - cause a transcript test to run:: - - from cmd2 import Cmd - class App(Cmd): - # customized attributes and methods here - - if __name__ == '__main__': - app = App(transcript_files=['exampleSession.txt']) - app.cmdloop() -- cgit v1.2.1