diff options
-rw-r--r-- | docs/integrating.rst | 122 | ||||
-rwxr-xr-x | examples/cmd_as_argument.py | 114 |
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 |