summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/integrating.rst122
-rwxr-xr-xexamples/cmd_as_argument.py114
2 files changed, 215 insertions, 21 deletions
diff --git a/docs/integrating.rst b/docs/integrating.rst
index a234173c..8f605e06 100644
--- a/docs/integrating.rst
+++ b/docs/integrating.rst
@@ -3,10 +3,90 @@
Integrating cmd2 with external tools
====================================
-Throughout this documentation we have focused on the **90%** use case, that is the use case we believe around 90+% of
-our user base is looking for. This focuses on ease of use and the best out-of-the-box experience where developers get
-the most functionality for the least amount of effort. We are talking about running cmd2 applications with the
-``cmdloop()`` method::
+
+Integrating cmd2 with the shell
+-------------------------------
+
+Typically you would invoke a ``cmd2`` program by typing::
+
+ $ python mycmd2program.py
+
+or::
+
+ $ mycmd2program.py
+
+Either of these methods will launch your program and enter the ``cmd2`` command
+loop, which allows the user to enter commands, which are then executed by your
+program.
+
+You may want to execute commands in your program without prompting the user for
+any input. There are several ways you might accomplish this task. The easiest
+one is to pipe commands and their arguments into your program via standard
+input. You don't need to do anything to your program in order to use this
+technique. Here's a demonstration using the ``examples/example.py`` included in
+the source code of ``cmd2``::
+
+ $ echo "speak -p some words" | python examples/example.py
+ omesay ordsway
+
+Using this same approach you could create a text file containing the commands
+you would like to run, one command per line in the file. Say your file was
+called ``somecmds.txt``. To run the commands in the text file using your
+``cmd2`` program (from a Windows command prompt)::
+
+ c:\cmd2> type somecmds.txt | python.exe examples/example.py
+ omesay ordsway
+
+By default, ``cmd2`` programs also look for commands pass as arguments from the
+operating system shell, and execute those commands before entering the command
+loop::
+
+ $ python examples/example.py help
+
+ Documented commands (type help <topic>):
+ ========================================
+ alias help load orate pyscript say shell speak
+ edit history mumble py quit set shortcuts unalias
+
+ (Cmd)
+
+You may need more control over command line arguments passed from the operating
+system shell. For example, you might have a command inside your ``cmd2`` program
+which itself accepts arguments, and maybe even option strings. Say you wanted to
+run the ``speak`` command from the operating system shell, but have it say it in
+pig latin::
+
+ $ python example/example.py speak -p hello there
+ python example.py speak -p hello there
+ usage: speak [-h] [-p] [-s] [-r REPEAT] words [words ...]
+ speak: error: the following arguments are required: words
+ *** Unknown syntax: -p
+ *** Unknown syntax: hello
+ *** Unknown syntax: there
+ (Cmd)
+
+Uh-oh, that's not what we wanted. ``cmd2`` treated ``-p``, ``hello``, and
+``there`` as commands, which don't exist in that program, thus the syntax errors.
+
+There is an easy way around this, which is demonstrated in
+``examples/cmd_as_argument.py``. By setting ``allow_cli_args=False`` you can so
+your own argument parsing of the command line::
+
+ $ python examples/cmd_as_argument.py speak -p hello there
+ ellohay heretay
+
+Check the source code of this example, especially the ``main()`` function, to
+see the technique.
+
+
+Integrating cmd2 with event loops
+---------------------------------
+
+Throughout this documentation we have focused on the **90%** use case, that is
+the use case we believe around **90+%** of our user base is looking for. This
+focuses on ease of use and the best out-of-the-box experience where developers
+get the most functionality for the least amount of effort. We are talking about
+running cmd2 applications with the ``cmdloop()`` method::
from cmd2 import Cmd
class App(Cmd):
@@ -14,23 +94,20 @@ the most functionality for the least amount of effort. We are talking about run
app = App()
app.cmdloop()
-However, there are some limitations to this way of using
-``cmd2``, mainly that ``cmd2`` owns the inner loop of a program. This can be unnecessarily restrictive and can prevent
-using libraries which depend on controlling their own event loop.
-
-
-Integrating cmd2 with event loops
----------------------------------
+However, there are some limitations to this way of using ``cmd2``, mainly that
+``cmd2`` owns the inner loop of a program. This can be unnecessarily
+restrictive and can prevent using libraries which depend on controlling their
+own event loop.
-Many Python concurrency libraries involve or require an event loop which they are in control of such as asyncio_,
-gevent_, Twisted_, etc.
+Many Python concurrency libraries involve or require an event loop which they
+are in control of such as asyncio_, gevent_, Twisted_, etc.
.. _asyncio: https://docs.python.org/3/library/asyncio.html
.. _gevent: http://www.gevent.org/
.. _Twisted: https://twistedmatrix.com
-``cmd2`` applications can be executed in a fashion where ``cmd2`` doesn't own the main loop for the program by using
-code like the following::
+``cmd2`` applications can be executed in a fashion where ``cmd2`` doesn't own
+the main loop for the program by using code like the following::
import cmd2
@@ -50,11 +127,13 @@ code like the following::
app.postloop()
-The **runcmds_plus_hooks()** method is a convenience method to run multiple commands via **onecmd_plus_hooks()**. It
-properly deals with ``load`` commands which under the hood put commands in a FIFO queue as it reads them in from a
+The **runcmds_plus_hooks()** method is a convenience method to run multiple
+commands via **onecmd_plus_hooks()**. It properly deals with ``load`` commands
+which under the hood put commands in a FIFO queue as it reads them in from a
script file.
-The **onecmd_plus_hooks()** method will do the following to execute a single ``cmd2`` command in a normal fashion:
+The **onecmd_plus_hooks()** method will do the following to execute a single
+``cmd2`` command in a normal fashion:
1. Call `preparse()` - for backwards compatibility with prior releases of cmd2, now deprecated
2. Parse user input into `Statement` object
@@ -73,9 +152,10 @@ The **onecmd_plus_hooks()** method will do the following to execute a single ``c
15. Call methods registered with `register_cmdfinalization_hook()`
16. Call `postparsing_postcmd()` - for backwards compatibility - deprecated
-Running in this fashion enables the ability to integrate with an external event loop. However, how to integrate with
-any specific event loop is beyond the scope of this documentation. Please note that running in this fashion comes with
-several disadvantages, including:
+Running in this fashion enables the ability to integrate with an external event
+loop. However, how to integrate with any specific event loop is beyond the
+scope of this documentation. Please note that running in this fashion comes
+with several disadvantages, including:
* Requires the developer to write more code
* Does not support transcript testing
diff --git a/examples/cmd_as_argument.py b/examples/cmd_as_argument.py
new file mode 100755
index 00000000..eb46eedf
--- /dev/null
+++ b/examples/cmd_as_argument.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# coding=utf-8
+"""
+A sample application for cmd2.
+
+This example is very similar to example.py, but had additional
+code in main() that shows how to accept a command from
+the command line at invocation:
+
+$ python cmd_as_argument.py speak -p hello there
+
+
+"""
+
+import argparse
+import random
+import sys
+
+import cmd2
+
+
+class CmdLineApp(cmd2.Cmd):
+ """ Example cmd2 application. """
+
+ # Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist
+ # default_to_shell = True
+ MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh']
+ MUMBLE_FIRST = ['so', 'like', 'well']
+ MUMBLE_LAST = ['right?']
+
+ def __init__(self):
+ self.allow_cli_args = False
+ self.multiline_commands = ['orate']
+ self.maxrepeats = 3
+
+ # Add stuff to settable and shortcuts before calling base class initializer
+ self.settable['maxrepeats'] = 'max repetitions for speak command'
+ self.shortcuts.update({'&': 'speak'})
+
+ # Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell
+ super().__init__(use_ipython=False)
+
+ speak_parser = argparse.ArgumentParser()
+ speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
+ speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
+ speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
+ speak_parser.add_argument('words', nargs='+', help='words to say')
+
+ @cmd2.with_argparser(speak_parser)
+ def do_speak(self, args):
+ """Repeats what you tell me to."""
+ words = []
+ for word in args.words:
+ if args.piglatin:
+ word = '%s%say' % (word[1:], word[0])
+ if args.shout:
+ word = word.upper()
+ words.append(word)
+ repetitions = args.repeat or 1
+ for i in range(min(repetitions, self.maxrepeats)):
+ # .poutput handles newlines, and accommodates output redirection too
+ self.poutput(' '.join(words))
+
+ do_say = do_speak # now "say" is a synonym for "speak"
+ do_orate = do_speak # another synonym, but this one takes multi-line input
+
+ mumble_parser = argparse.ArgumentParser()
+ mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat')
+ mumble_parser.add_argument('words', nargs='+', help='words to say')
+
+ @cmd2.with_argparser(mumble_parser)
+ def do_mumble(self, args):
+ """Mumbles what you tell me to."""
+ repetitions = args.repeat or 1
+ for i in range(min(repetitions, self.maxrepeats)):
+ output = []
+ if random.random() < .33:
+ output.append(random.choice(self.MUMBLE_FIRST))
+ for word in args.words:
+ if random.random() < .40:
+ output.append(random.choice(self.MUMBLES))
+ output.append(word)
+ if random.random() < .25:
+ output.append(random.choice(self.MUMBLE_LAST))
+ self.poutput(' '.join(output))
+
+
+def main(argv=None):
+ """Run when invoked from the operating system shell"""
+
+ parser = argparse.ArgumentParser(
+ description='Commands as arguments'
+ )
+ command_help = 'optional command to run, if no command given, enter an interactive shell'
+ parser.add_argument('command', nargs='?',
+ help=command_help)
+ arg_help = 'optional arguments for command'
+ parser.add_argument('command_args', nargs=argparse.REMAINDER,
+ help=arg_help)
+
+ args = parser.parse_args(argv)
+
+ c = CmdLineApp()
+
+ if args.command:
+ # we have a command, run it and then exit
+ c.onecmd_plus_hooks('{} {}'.format(args.command, ' '.join(args.command_args)))
+ else:
+ # we have no command, drop into interactive mode
+ c.cmdloop()
+
+
+if __name__ == '__main__':
+ sys.exit(main()) \ No newline at end of file