Skip to content

Commit 573d157

Browse files
authored
Merge pull request #482 from python-cmd2/run_at_invocation
Add example and documentation for #452
2 parents 34adfe4 + 029d734 commit 573d157

File tree

2 files changed

+215
-21
lines changed

2 files changed

+215
-21
lines changed

docs/integrating.rst

Lines changed: 101 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,111 @@
33
Integrating cmd2 with external tools
44
====================================
55

6-
Throughout this documentation we have focused on the **90%** use case, that is the use case we believe around 90+% of
7-
our user base is looking for. This focuses on ease of use and the best out-of-the-box experience where developers get
8-
the most functionality for the least amount of effort. We are talking about running cmd2 applications with the
9-
``cmdloop()`` method::
6+
7+
Integrating cmd2 with the shell
8+
-------------------------------
9+
10+
Typically you would invoke a ``cmd2`` program by typing::
11+
12+
$ python mycmd2program.py
13+
14+
or::
15+
16+
$ mycmd2program.py
17+
18+
Either of these methods will launch your program and enter the ``cmd2`` command
19+
loop, which allows the user to enter commands, which are then executed by your
20+
program.
21+
22+
You may want to execute commands in your program without prompting the user for
23+
any input. There are several ways you might accomplish this task. The easiest
24+
one is to pipe commands and their arguments into your program via standard
25+
input. You don't need to do anything to your program in order to use this
26+
technique. Here's a demonstration using the ``examples/example.py`` included in
27+
the source code of ``cmd2``::
28+
29+
$ echo "speak -p some words" | python examples/example.py
30+
omesay ordsway
31+
32+
Using this same approach you could create a text file containing the commands
33+
you would like to run, one command per line in the file. Say your file was
34+
called ``somecmds.txt``. To run the commands in the text file using your
35+
``cmd2`` program (from a Windows command prompt)::
36+
37+
c:\cmd2> type somecmds.txt | python.exe examples/example.py
38+
omesay ordsway
39+
40+
By default, ``cmd2`` programs also look for commands pass as arguments from the
41+
operating system shell, and execute those commands before entering the command
42+
loop::
43+
44+
$ python examples/example.py help
45+
46+
Documented commands (type help <topic>):
47+
========================================
48+
alias help load orate pyscript say shell speak
49+
edit history mumble py quit set shortcuts unalias
50+
51+
(Cmd)
52+
53+
You may need more control over command line arguments passed from the operating
54+
system shell. For example, you might have a command inside your ``cmd2`` program
55+
which itself accepts arguments, and maybe even option strings. Say you wanted to
56+
run the ``speak`` command from the operating system shell, but have it say it in
57+
pig latin::
58+
59+
$ python example/example.py speak -p hello there
60+
python example.py speak -p hello there
61+
usage: speak [-h] [-p] [-s] [-r REPEAT] words [words ...]
62+
speak: error: the following arguments are required: words
63+
*** Unknown syntax: -p
64+
*** Unknown syntax: hello
65+
*** Unknown syntax: there
66+
(Cmd)
67+
68+
Uh-oh, that's not what we wanted. ``cmd2`` treated ``-p``, ``hello``, and
69+
``there`` as commands, which don't exist in that program, thus the syntax errors.
70+
71+
There is an easy way around this, which is demonstrated in
72+
``examples/cmd_as_argument.py``. By setting ``allow_cli_args=False`` you can so
73+
your own argument parsing of the command line::
74+
75+
$ python examples/cmd_as_argument.py speak -p hello there
76+
ellohay heretay
77+
78+
Check the source code of this example, especially the ``main()`` function, to
79+
see the technique.
80+
81+
82+
Integrating cmd2 with event loops
83+
---------------------------------
84+
85+
Throughout this documentation we have focused on the **90%** use case, that is
86+
the use case we believe around **90+%** of our user base is looking for. This
87+
focuses on ease of use and the best out-of-the-box experience where developers
88+
get the most functionality for the least amount of effort. We are talking about
89+
running cmd2 applications with the ``cmdloop()`` method::
1090

1191
from cmd2 import Cmd
1292
class App(Cmd):
1393
# customized attributes and methods here
1494
app = App()
1595
app.cmdloop()
1696

17-
However, there are some limitations to this way of using
18-
``cmd2``, mainly that ``cmd2`` owns the inner loop of a program. This can be unnecessarily restrictive and can prevent
19-
using libraries which depend on controlling their own event loop.
20-
21-
22-
Integrating cmd2 with event loops
23-
---------------------------------
97+
However, there are some limitations to this way of using ``cmd2``, mainly that
98+
``cmd2`` owns the inner loop of a program. This can be unnecessarily
99+
restrictive and can prevent using libraries which depend on controlling their
100+
own event loop.
24101

25-
Many Python concurrency libraries involve or require an event loop which they are in control of such as asyncio_,
26-
gevent_, Twisted_, etc.
102+
Many Python concurrency libraries involve or require an event loop which they
103+
are in control of such as asyncio_, gevent_, Twisted_, etc.
27104

28105
.. _asyncio: https://docs.python.org/3/library/asyncio.html
29106
.. _gevent: http://www.gevent.org/
30107
.. _Twisted: https://twistedmatrix.com
31108

32-
``cmd2`` applications can be executed in a fashion where ``cmd2`` doesn't own the main loop for the program by using
33-
code like the following::
109+
``cmd2`` applications can be executed in a fashion where ``cmd2`` doesn't own
110+
the main loop for the program by using code like the following::
34111

35112
import cmd2
36113

@@ -50,11 +127,13 @@ code like the following::
50127

51128
app.postloop()
52129

53-
The **runcmds_plus_hooks()** method is a convenience method to run multiple commands via **onecmd_plus_hooks()**. It
54-
properly deals with ``load`` commands which under the hood put commands in a FIFO queue as it reads them in from a
130+
The **runcmds_plus_hooks()** method is a convenience method to run multiple
131+
commands via **onecmd_plus_hooks()**. It properly deals with ``load`` commands
132+
which under the hood put commands in a FIFO queue as it reads them in from a
55133
script file.
56134

57-
The **onecmd_plus_hooks()** method will do the following to execute a single ``cmd2`` command in a normal fashion:
135+
The **onecmd_plus_hooks()** method will do the following to execute a single
136+
``cmd2`` command in a normal fashion:
58137

59138
1. Call `preparse()` - for backwards compatibility with prior releases of cmd2, now deprecated
60139
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
73152
15. Call methods registered with `register_cmdfinalization_hook()`
74153
16. Call `postparsing_postcmd()` - for backwards compatibility - deprecated
75154

76-
Running in this fashion enables the ability to integrate with an external event loop. However, how to integrate with
77-
any specific event loop is beyond the scope of this documentation. Please note that running in this fashion comes with
78-
several disadvantages, including:
155+
Running in this fashion enables the ability to integrate with an external event
156+
loop. However, how to integrate with any specific event loop is beyond the
157+
scope of this documentation. Please note that running in this fashion comes
158+
with several disadvantages, including:
79159

80160
* Requires the developer to write more code
81161
* Does not support transcript testing

examples/cmd_as_argument.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
"""
4+
A sample application for cmd2.
5+
6+
This example is very similar to example.py, but had additional
7+
code in main() that shows how to accept a command from
8+
the command line at invocation:
9+
10+
$ python cmd_as_argument.py speak -p hello there
11+
12+
13+
"""
14+
15+
import argparse
16+
import random
17+
import sys
18+
19+
import cmd2
20+
21+
22+
class CmdLineApp(cmd2.Cmd):
23+
""" Example cmd2 application. """
24+
25+
# Setting this true makes it run a shell command if a cmd2/cmd command doesn't exist
26+
# default_to_shell = True
27+
MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh']
28+
MUMBLE_FIRST = ['so', 'like', 'well']
29+
MUMBLE_LAST = ['right?']
30+
31+
def __init__(self):
32+
self.allow_cli_args = False
33+
self.multiline_commands = ['orate']
34+
self.maxrepeats = 3
35+
36+
# Add stuff to settable and shortcuts before calling base class initializer
37+
self.settable['maxrepeats'] = 'max repetitions for speak command'
38+
self.shortcuts.update({'&': 'speak'})
39+
40+
# Set use_ipython to True to enable the "ipy" command which embeds and interactive IPython shell
41+
super().__init__(use_ipython=False)
42+
43+
speak_parser = argparse.ArgumentParser()
44+
speak_parser.add_argument('-p', '--piglatin', action='store_true', help='atinLay')
45+
speak_parser.add_argument('-s', '--shout', action='store_true', help='N00B EMULATION MODE')
46+
speak_parser.add_argument('-r', '--repeat', type=int, help='output [n] times')
47+
speak_parser.add_argument('words', nargs='+', help='words to say')
48+
49+
@cmd2.with_argparser(speak_parser)
50+
def do_speak(self, args):
51+
"""Repeats what you tell me to."""
52+
words = []
53+
for word in args.words:
54+
if args.piglatin:
55+
word = '%s%say' % (word[1:], word[0])
56+
if args.shout:
57+
word = word.upper()
58+
words.append(word)
59+
repetitions = args.repeat or 1
60+
for i in range(min(repetitions, self.maxrepeats)):
61+
# .poutput handles newlines, and accommodates output redirection too
62+
self.poutput(' '.join(words))
63+
64+
do_say = do_speak # now "say" is a synonym for "speak"
65+
do_orate = do_speak # another synonym, but this one takes multi-line input
66+
67+
mumble_parser = argparse.ArgumentParser()
68+
mumble_parser.add_argument('-r', '--repeat', type=int, help='how many times to repeat')
69+
mumble_parser.add_argument('words', nargs='+', help='words to say')
70+
71+
@cmd2.with_argparser(mumble_parser)
72+
def do_mumble(self, args):
73+
"""Mumbles what you tell me to."""
74+
repetitions = args.repeat or 1
75+
for i in range(min(repetitions, self.maxrepeats)):
76+
output = []
77+
if random.random() < .33:
78+
output.append(random.choice(self.MUMBLE_FIRST))
79+
for word in args.words:
80+
if random.random() < .40:
81+
output.append(random.choice(self.MUMBLES))
82+
output.append(word)
83+
if random.random() < .25:
84+
output.append(random.choice(self.MUMBLE_LAST))
85+
self.poutput(' '.join(output))
86+
87+
88+
def main(argv=None):
89+
"""Run when invoked from the operating system shell"""
90+
91+
parser = argparse.ArgumentParser(
92+
description='Commands as arguments'
93+
)
94+
command_help = 'optional command to run, if no command given, enter an interactive shell'
95+
parser.add_argument('command', nargs='?',
96+
help=command_help)
97+
arg_help = 'optional arguments for command'
98+
parser.add_argument('command_args', nargs=argparse.REMAINDER,
99+
help=arg_help)
100+
101+
args = parser.parse_args(argv)
102+
103+
c = CmdLineApp()
104+
105+
if args.command:
106+
# we have a command, run it and then exit
107+
c.onecmd_plus_hooks('{} {}'.format(args.command, ' '.join(args.command_args)))
108+
else:
109+
# we have no command, drop into interactive mode
110+
c.cmdloop()
111+
112+
113+
if __name__ == '__main__':
114+
sys.exit(main())

0 commit comments

Comments
 (0)