diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CHANGES.rst | 5 | ||||
-rwxr-xr-x | README.rst | 53 | ||||
-rwxr-xr-x | cmd2.py | 431 | ||||
-rw-r--r-- | docs/conf.py | 75 | ||||
-rw-r--r-- | docs/freefeatures.rst | 72 | ||||
-rw-r--r-- | docs/index.rst | 17 | ||||
-rw-r--r-- | docs/pycon2010/fileutil.py | 12 | ||||
-rw-r--r-- | docs/pycon2010/graph.py | 41 | ||||
-rw-r--r-- | docs/pycon2010/pirate.py | 5 | ||||
-rw-r--r-- | docs/pycon2010/pirate2.py | 6 | ||||
-rw-r--r-- | docs/pycon2010/pirate3.py | 12 | ||||
-rw-r--r-- | docs/pycon2010/pirate4.py | 18 | ||||
-rw-r--r-- | docs/pycon2010/pirate5.py | 19 | ||||
-rw-r--r-- | docs/pycon2010/pirate6.py | 20 | ||||
-rw-r--r-- | docs/pycon2010/pirate7.py | 22 | ||||
-rw-r--r-- | docs/pycon2010/pirate8.py | 25 | ||||
-rw-r--r-- | docs/pycon2010/pycon2010.rst | 64 | ||||
-rw-r--r-- | docs/pycon2010/schematic.py | 32 | ||||
-rw-r--r-- | docs/unfreefeatures.rst | 42 | ||||
-rwxr-xr-x | example/example.py | 9 | ||||
-rw-r--r-- | ignoreBug.py | 11 | ||||
-rwxr-xr-x | setup.py | 29 | ||||
-rw-r--r-- | tests/conftest.py | 1 | ||||
-rw-r--r-- | tests/test_cmd2.py | 11 | ||||
-rw-r--r-- | tests/test_transcript.py | 5 | ||||
-rw-r--r-- | tox.ini | 1 |
27 files changed, 555 insertions, 485 deletions
@@ -4,4 +4,4 @@ dist cmd2.egg-info .idea .cache - +*.pyc
\ No newline at end of file diff --git a/CHANGES.rst b/CHANGES.rst index b873df3d..1dcc0c2f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -44,3 +44,8 @@ News * Fix subprocess.mswindows bug * Add Python3.6 support * Drop distutils from setup.py + +0.7.0 +----- + +* Refactor to use six module for a unified codebase which supports both Python 2 and Python 3 @@ -40,8 +40,8 @@ Instructions for implementing each feature follow. - Searchable command history
- All commands will automatically be tracked in the session's history, unless the command is listed in Cmd's excludeFromHistory attribute.
- The history is accessed through the `history`, `list`, and `run` commands
+ All commands will automatically be tracked in the session's history, unless the command is listed in Cmd's excludeFromHistory attribute.
+ The history is accessed through the `history`, `list`, and `run` commands
(and their abbreviations: `hi`, `li`, `l`, `r`).
If you wish to exclude some of your custom commands from the history, append their names
to the list at Cmd.ExcludeFromHistory.
@@ -49,37 +49,37 @@ Instructions for implementing each feature follow. - Load commands from file, save to file, edit commands in file
Type `help load`, `help save`, `help edit` for details.
-
+
- Multi-line commands
Any command accepts multi-line input when its name is listed in `Cmd.multilineCommands`.
- The program will keep expecting input until a line ends with any of the characters
+ The program will keep expecting input until a line ends with any of the characters
in `Cmd.terminators` . The default terminators are `;` and `/n` (empty newline).
-
+
- Case-insensitive commands
All commands are case-insensitive, unless `Cmd.caseInsensitive` is set to `False`.
-
+
- Special-character shortcut commands (beyond cmd's "@" and "!")
To create a single-character shortcut for a command, update `Cmd.shortcuts`.
-
+
- Settable environment parameters
- To allow a user to change an environment parameter during program execution,
+ To allow a user to change an environment parameter during program execution,
append the parameter's name to `Cmd.settable`.
-
-- Parsing commands with `optparse` options (flags)
+
+- Parsing commands with `optparse` options (flags)
::
-
+
@options([make_option('-m', '--myoption', action="store_true", help="all about my option")])
def do_myfunc(self, arg, opts):
if opts.myoption:
...
-
+
See Python standard library's `optparse` documentation: http://docs.python.org/lib/optparse-defining-options.html
-
+
cmd2 can be installed with `easy_install cmd2`
Cheese Shop page: http://pypi.python.org/pypi/cmd2
@@ -87,16 +87,16 @@ Cheese Shop page: http://pypi.python.org/pypi/cmd2 Example cmd2 application (example/example.py) ::
'''A sample application for cmd2.'''
-
+
from cmd2 import Cmd, make_option, options, Cmd2TestCase
import unittest, optparse, sys
-
+
class CmdLineApp(Cmd):
multilineCommands = ['orate']
Cmd.shortcuts.update({'&': 'speak'})
maxrepeats = 3
Cmd.settable.append('maxrepeats')
-
+
@options([make_option('-p', '--piglatin', action="store_true", help="atinLay"),
make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE"),
make_option('-r', '--repeat', type="int", help="output [n] times")
@@ -114,14 +114,14 @@ Example cmd2 application (example/example.py) :: self.stdout.write('\n')
# self.stdout.write is better than "print", because Cmd can be
# initialized with a non-standard output destination
-
+
do_say = do_speak # now "say" is a synonym for "speak"
do_orate = do_speak # another synonym, but this one takes multi-line input
-
+
class TestMyAppCase(Cmd2TestCase):
CmdApp = CmdLineApp
transcriptFileName = 'exampleSession.txt'
-
+
parser = optparse.OptionParser()
parser.add_option('-t', '--test', dest='unittests', action='store_true', default=False, help='Run unit test suite')
(callopts, callargs) = parser.parse_args()
@@ -133,8 +133,8 @@ Example cmd2 application (example/example.py) :: app.cmdloop()
The following is a sample session running example.py.
-Thanks to `TestMyAppCase(Cmd2TestCase)`, it also serves as a test
-suite for example.py when saved as `exampleSession.txt`.
+Thanks to `TestMyAppCase(Cmd2TestCase)`, it also serves as a test
+suite for example.py when saved as `exampleSession.txt`.
Running `python example.py -t` will run all the commands in the
transcript against `example.py`, verifying that the output produced
matches the transcript.
@@ -142,12 +142,12 @@ matches the transcript. example/exampleSession.txt::
(Cmd) help
-
+
Documented commands (type help <topic>):
========================================
- _load edit history li load pause run say shell show
+ _load edit history li load pause run say shell show
ed hi l list orate r save set shortcuts speak
-
+
Undocumented commands:
======================
EOF cmdenvironment eof exit help q quit
@@ -155,14 +155,14 @@ example/exampleSession.txt:: (Cmd) help say
Repeats what you tell me to.
Usage: speak [options] arg
-
+
Options:
-h, --help show this help message and exit
-p, --piglatin atinLay
-s, --shout N00B EMULATION MODE
-r REPEAT, --repeat=REPEAT
output [n] times
-
+
(Cmd) say goodnight, Gracie
goodnight, Gracie
(Cmd) say -ps --repeat=5 goodnight, Gracie
@@ -222,3 +222,4 @@ example/exampleSession.txt:: now: --->
---> say goodbye
goodbye
+
@@ -1,3 +1,4 @@ +# coding=utf-8 """Variant on standard library's cmd with extra features. To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you @@ -24,65 +25,62 @@ written to use `self.stdout.write()`, mercurial repository at http://www.assembla.com/wiki/show/python-cmd2 """ import cmd -import re -import os -import sys +import copy +import datetime +import doctest +import glob import optparse +import os +import platform +import re import subprocess +import sys import tempfile -import doctest -import unittest -import datetime -import urllib -import glob import traceback -import platform -import copy +import unittest from code import InteractiveConsole from optparse import make_option + import pyparsing -__version__ = '0.6.9a' +# next(it) gets next item of iterator it. This is a replacement for calling it.next() in Python 2 and next(it) in Py3 +from six import next -try: - raw_input -except NameError: - raw_input = input +# Possible types for text data. This is basestring() in Python 2 and str in Python 3. +from six import string_types + +# raw_input() for Python 2 or input() for Python 3 +from six.moves import input + +# itertools.zip() for Python 2 or zip() for Python 3 - produces an iterator in both cases +from six.moves import zip + +# Python 2 urllib2.urlopen() or Python3 urllib.request.urlopen() +from six.moves.urllib.request import urlopen # Python 3 compatability hack due to no built-in file keyword in Python 3 -# Due to two occurences of isinstance(<foo>, file) checking to see if something is of file type +# Due to one occurence of isinstance(<foo>, file) checking to see if something is of file type try: file except NameError: import io file = io.TextIOWrapper -if sys.version_info[0] == 2: - pyparsing.ParserElement.enablePackrat() - -""" -Packrat is causing Python3 errors that I don't understand. - -> /usr/local/Cellar/python3/3.2/lib/python3.2/site-packages/pyparsing-1.5.6-py3.2.egg/pyparsing.py(999)scanString() --> nextLoc,tokens = parseFn( instring, preloc, callPreParse=False ) -(Pdb) n -NameError: global name 'exc' is not defined +__version__ = '0.7.0' -(Pdb) parseFn -<bound method Or._parseCache of {Python style comment ^ C style comment}> +# Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past +pyparsing.ParserElement.enablePackrat() -Bug report filed: https://sourceforge.net/tracker/?func=detail&atid=617311&aid=3381439&group_id=97203 -""" class OptionParser(optparse.OptionParser): def exit(self, status=0, msg=None): self.values._exit = True if msg: - print (msg) + print(msg) def print_help(self, *args, **kwargs): try: - print (self._func.__doc__) + print(self._func.__doc__) except AttributeError: pass optparse.OptionParser.print_help(self, *args, **kwargs) @@ -127,9 +125,10 @@ def _which(editor): except OSError: return None + optparse.Values.get = _attr_get_ -options_defined = [] # used to distinguish --options from SQL-style --comments +options_defined = [] # used to distinguish --options from SQL-style --comments def options(option_list, arg_desc="arg"): @@ -152,18 +151,20 @@ def options(option_list, arg_desc="arg"): option_list = [option_list] for opt in option_list: options_defined.append(pyparsing.Literal(opt.get_opt_string())) + def option_setup(func): optionParser = OptionParser() for opt in option_list: optionParser.add_option(opt) optionParser.set_usage("%s [options] %s" % (func.__name__[3:], arg_desc)) optionParser._func = func + def new_func(instance, arg): try: opts, newArgList = optionParser.parse_args(arg.split()) # Must find the remaining args in the original argument list, but # mustn't include the command itself - #if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command: + # if hasattr(arg, 'parsed') and newArgList[0] == arg.parsed.command: # newArgList = newArgList[1:] newArgs = remaining_args(arg, newArgList) if isinstance(arg, ParsedString): @@ -171,7 +172,7 @@ def options(option_list, arg_desc="arg"): else: arg = newArgs except optparse.OptParseError as e: - print (e) + print(e) optionParser.print_help() return if hasattr(opts, '_exit'): @@ -181,6 +182,7 @@ def options(option_list, arg_desc="arg"): new_func.__doc__ = '%s\n%s' % (func.__doc__, optionParser.format_help()) return new_func + return option_setup @@ -196,9 +198,11 @@ Download from http://sourceforge.net/projects/pywin32/""" errmsg = """Redirecting to or from paste buffer requires xclip to be installed on operating system. On Debian/Ubuntu, 'sudo apt-get install xclip' will install it.""" + def __init__(self): Exception.__init__(self, self.errmsg) + pastebufferr = """Redirecting to or from paste buffer requires %s to be installed on operating system. %s""" @@ -206,14 +210,18 @@ to be installed on operating system. if sys.platform == "win32": try: import win32clipboard + + def get_paste_buffer(): win32clipboard.OpenClipboard(0) try: result = win32clipboard.GetClipboardData() except TypeError: - result = '' #non-text + result = '' # non-text win32clipboard.CloseClipboard() return result + + def write_to_paste_buffer(txt): win32clipboard.OpenClipboard(0) win32clipboard.EmptyClipboard() @@ -222,30 +230,41 @@ if sys.platform == "win32": except ImportError: def get_paste_buffer(*args): raise OSError(pastebufferr % ('pywin32', 'Download from http://sourceforge.net/projects/pywin32/')) + + write_to_paste_buffer = get_paste_buffer elif sys.platform == 'darwin': can_clip = False try: # test for pbcopy - AFAIK, should always be installed on MacOS - subprocess.check_call('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.check_call('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE) can_clip = True except (subprocess.CalledProcessError, OSError, IOError): pass if can_clip: def get_paste_buffer(): - pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + pbcopyproc = subprocess.Popen('pbcopy -help', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE) return pbcopyproc.stdout.read() + + def write_to_paste_buffer(txt): - pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + pbcopyproc = subprocess.Popen('pbcopy', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE) pbcopyproc.communicate(txt.encode()) else: def get_paste_buffer(*args): - raise OSError(pastebufferr % ('pbcopy', 'On MacOS X - error should not occur - part of the default installation')) + raise OSError( + pastebufferr % ('pbcopy', 'On MacOS X - error should not occur - part of the default installation')) + + write_to_paste_buffer = get_paste_buffer else: can_clip = False try: - subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.check_call('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, + stderr=subprocess.PIPE) can_clip = True except AttributeError: # check_call not defined, Python < 2.5 try: @@ -253,17 +272,21 @@ else: xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) xclipproc.stdin.write(teststring) xclipproc.stdin.close() - xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) if xclipproc.stdout.read() == teststring: can_clip = True - except Exception: # hate a bare Exception call, but exception classes vary too much b/t stdlib versions + except Exception: # hate a bare Exception call, but exception classes vary too much b/t stdlib versions pass except Exception: - pass # something went wrong with xclip and we cannot use it + pass # something went wrong with xclip and we cannot use it if can_clip: def get_paste_buffer(): - xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + xclipproc = subprocess.Popen('xclip -o -sel clip', shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) return xclipproc.stdout.read() + + def write_to_paste_buffer(txt): xclipproc = subprocess.Popen('xclip -sel clip', shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) xclipproc.stdin.write(txt.encode()) @@ -275,6 +298,8 @@ else: else: def get_paste_buffer(*args): raise OSError(pastebufferr % ('xclip', 'On Debian/Ubuntu, install with "sudo apt-get install xclip"')) + + write_to_paste_buffer = get_paste_buffer pyparsing.ParserElement.setDefaultWhitespaceChars(' \t') @@ -295,6 +320,7 @@ class ParsedString(str): new.parsed.statement['args'] = newargs return new + class StubbornDict(dict): '''Dictionary that tolerates many input formats. Create it with stubbornDict(arg) factory function. @@ -309,8 +335,10 @@ class StubbornDict(dict): >>> sorted(d.items()) [('girl', 'Frauelein, Maedchen'), ('large', 'gross'), ('plaid', ''), ('plain', ''), ('shoe', 'schuh'), ('small', 'klein')] ''' + def update(self, arg): dict.update(self, StubbornDict.to_dict(arg)) + append = update def __iadd__(self, arg): @@ -393,28 +421,29 @@ def ljust(x, width, fillchar=' '): x = (x + [fillchar] * width)[:width] return x + class Cmd(cmd.Cmd): echo = False - case_insensitive = True # Commands recognized regardless of case + case_insensitive = True # Commands recognized regardless of case continuation_prompt = '> ' - timing = False # Prints elapsed time for each command + timing = False # Prints elapsed time for each command # make sure your terminators are not in legalChars! legalChars = u'!#$%.:?@_' + pyparsing.alphanums + pyparsing.alphas8bit shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'} excludeFromHistory = '''run r list l history hi ed edit li eof'''.split() default_to_shell = False noSpecialParse = 'set ed edit exit'.split() - defaultExtension = 'txt' # For ``save``, ``load``, etc. - default_file_name = 'command.txt' # For ``save``, ``load``, etc. - abbrev = True # Abbreviated commands recognized + defaultExtension = 'txt' # For ``save``, ``load``, etc. + default_file_name = 'command.txt' # For ``save``, ``load``, etc. + abbrev = True # Abbreviated commands recognized current_script_dir = None reserved_words = [] - feedback_to_output = False # Do include nonessentials in >, | output - quiet = False # Do not suppress nonessential output + feedback_to_output = False # Do include nonessentials in >, | output + quiet = False # Do not suppress nonessential output debug = False locals_in_py = True kept_state = None - redirector = '>' # for sending output to file + redirector = '>' # for sending output to file settable = stubbornDict(''' prompt colors Colorized output (*nix only) @@ -440,7 +469,7 @@ class Cmd(cmd.Cmd): def perror(self, errmsg, statement=None): if self.debug: traceback.print_exc() - print (str(errmsg)) + print(str(errmsg)) def pfeedback(self, msg): """For printing nonessential feedback. Can be silenced with `quiet`. @@ -449,7 +478,8 @@ class Cmd(cmd.Cmd): if self.feedback_to_output: self.poutput(msg) else: - print (msg) + print(msg) + _STOP_AND_EXIT = True # distinguish end of script file from actual exit _STOP_SCRIPT_NO_EXIT = -999 editor = os.environ.get('EDITOR') @@ -461,14 +491,15 @@ class Cmd(cmd.Cmd): if _which(editor): break - colorcodes = {'bold':{True:'\x1b[1m',False:'\x1b[22m'}, - 'cyan':{True:'\x1b[36m',False:'\x1b[39m'}, - 'blue':{True:'\x1b[34m',False:'\x1b[39m'}, - 'red':{True:'\x1b[31m',False:'\x1b[39m'}, - 'magenta':{True:'\x1b[35m',False:'\x1b[39m'}, - 'green':{True:'\x1b[32m',False:'\x1b[39m'}, - 'underline':{True:'\x1b[4m',False:'\x1b[24m'}} + colorcodes = {'bold': {True: '\x1b[1m', False: '\x1b[22m'}, + 'cyan': {True: '\x1b[36m', False: '\x1b[39m'}, + 'blue': {True: '\x1b[34m', False: '\x1b[39m'}, + 'red': {True: '\x1b[31m', False: '\x1b[39m'}, + 'magenta': {True: '\x1b[35m', False: '\x1b[39m'}, + 'green': {True: '\x1b[32m', False: '\x1b[39m'}, + 'underline': {True: '\x1b[4m', False: '\x1b[24m'}} colors = (platform.system() != 'Windows') + def colorize(self, val, color): '''Given a string (``val``), returns that string wrapped in UNIX-style special characters that turn on (and then off) text color and style. @@ -485,11 +516,11 @@ class Cmd(cmd.Cmd): self.stdout.write(""" Commands are %(casesensitive)scase-sensitive. Commands may be terminated with: %(terminators)s - Settable parameters: %(settable)s\n""" % \ - { 'casesensitive': (self.case_insensitive and 'not ') or '', - 'terminators': str(self.terminators), - 'settable': ' '.join(self.settable) - }) + Settable parameters: %(settable)s\n""" % + {'casesensitive': (self.case_insensitive and 'not ') or '', + 'terminators': str(self.terminators), + 'settable': ' '.join(self.settable) + }) def do_help(self, arg): if arg: @@ -516,13 +547,12 @@ class Cmd(cmd.Cmd): def do_shortcuts(self, args): """Lists single-key shortcuts available.""" result = "\n".join('%s: %s' % (sc[0], sc[1]) for sc in sorted(self.shortcuts)) - self.stdout.write("Single-key shortcuts for other commands:\n%s\n" % (result)) + self.stdout.write("Single-key shortcuts for other commands:\n{}\n".format(result)) prefixParser = pyparsing.Empty() commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment]) commentGrammars.addParseAction(lambda x: '') - commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo( - pyparsing.stringEnd ^ '*/') + commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/') terminators = [';'] blankLinesAllowed = False multilineCommands = [] @@ -533,25 +563,25 @@ class Cmd(cmd.Cmd): >>> c.multilineCommands = ['multiline'] >>> c.case_insensitive = True >>> c._init_parser() - >>> print (c.parser.parseString('').dump()) + >>> print(c.parser.parseString('').dump()) [] - >>> print (c.parser.parseString('').dump()) + >>> print(c.parser.parseString('').dump()) [] - >>> print (c.parser.parseString('/* empty command */').dump()) + >>> print(c.parser.parseString('/* empty command */').dump()) [] - >>> print (c.parser.parseString('plainword').dump()) + >>> print(c.parser.parseString('plainword').dump()) ['plainword', ''] - command: plainword - statement: ['plainword', ''] - command: plainword - >>> print (c.parser.parseString('termbare;').dump()) + >>> print(c.parser.parseString('termbare;').dump()) ['termbare', '', ';', ''] - command: termbare - statement: ['termbare', '', ';'] - command: termbare - terminator: ; - terminator: ; - >>> print (c.parser.parseString('termbare; suffx').dump()) + >>> print(c.parser.parseString('termbare; suffx').dump()) ['termbare', '', ';', 'suffx'] - command: termbare - statement: ['termbare', '', ';'] @@ -559,19 +589,19 @@ class Cmd(cmd.Cmd): - terminator: ; - suffix: suffx - terminator: ; - >>> print (c.parser.parseString('barecommand').dump()) + >>> print(c.parser.parseString('barecommand').dump()) ['barecommand', ''] - command: barecommand - statement: ['barecommand', ''] - command: barecommand - >>> print (c.parser.parseString('COMmand with args').dump()) + >>> print(c.parser.parseString('COMmand with args').dump()) ['command', 'with args'] - args: with args - command: command - statement: ['command', 'with args'] - args: with args - command: command - >>> print (c.parser.parseString('command with args and terminator; and suffix').dump()) + >>> print(c.parser.parseString('command with args and terminator; and suffix').dump()) ['command', 'with args and terminator', ';', 'and suffix'] - args: with args and terminator - command: command @@ -581,20 +611,20 @@ class Cmd(cmd.Cmd): - terminator: ; - suffix: and suffix - terminator: ; - >>> print (c.parser.parseString('simple | piped').dump()) + >>> print(c.parser.parseString('simple | piped').dump()) ['simple', '', '|', ' piped'] - command: simple - pipeTo: piped - statement: ['simple', ''] - command: simple - >>> print (c.parser.parseString('double-pipe || is not a pipe').dump()) + >>> print(c.parser.parseString('double-pipe || is not a pipe').dump()) ['double', '-pipe || is not a pipe'] - args: -pipe || is not a pipe - command: double - statement: ['double', '-pipe || is not a pipe'] - args: -pipe || is not a pipe - command: double - >>> print (c.parser.parseString('command with args, terminator;sufx | piped').dump()) + >>> print(c.parser.parseString('command with args, terminator;sufx | piped').dump()) ['command', 'with args, terminator', ';', 'sufx', '|', ' piped'] - args: with args, terminator - command: command @@ -605,7 +635,7 @@ class Cmd(cmd.Cmd): - terminator: ; - suffix: sufx - terminator: ; - >>> print (c.parser.parseString('output into > afile.txt').dump()) + >>> print(c.parser.parseString('output into > afile.txt').dump()) ['output', 'into', '>', 'afile.txt'] - args: into - command: output @@ -614,7 +644,7 @@ class Cmd(cmd.Cmd): - statement: ['output', 'into'] - args: into - command: output - >>> print (c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump()) + >>> print(c.parser.parseString('output into;sufx | pipethrume plz > afile.txt').dump()) ['output', 'into', ';', 'sufx', '|', ' pipethrume plz', '>', 'afile.txt'] - args: into - command: output @@ -627,7 +657,7 @@ class Cmd(cmd.Cmd): - terminator: ; - suffix: sufx - terminator: ; - >>> print (c.parser.parseString('output to paste buffer >> ').dump()) + >>> print(c.parser.parseString('output to paste buffer >> ').dump()) ['output', 'to paste buffer', '>>', ''] - args: to paste buffer - command: output @@ -635,7 +665,7 @@ class Cmd(cmd.Cmd): - statement: ['output', 'to paste buffer'] - args: to paste buffer - command: output - >>> print (c.parser.parseString('ignore the /* commented | > */ stuff;').dump()) + >>> print(c.parser.parseString('ignore the /* commented | > */ stuff;').dump()) ['ignore', 'the /* commented | > */ stuff', ';', ''] - args: the /* commented | > */ stuff - command: ignore @@ -644,7 +674,7 @@ class Cmd(cmd.Cmd): - command: ignore - terminator: ; - terminator: ; - >>> print (c.parser.parseString('has > inside;').dump()) + >>> print(c.parser.parseString('has > inside;').dump()) ['has', '> inside', ';', ''] - args: > inside - command: has @@ -653,10 +683,10 @@ class Cmd(cmd.Cmd): - command: has - terminator: ; - terminator: ; - >>> print (c.parser.parseString('multiline has > inside an unfinished command').dump()) + >>> print(c.parser.parseString('multiline has > inside an unfinished command').dump()) ['multiline', ' has > inside an unfinished command'] - multilineCommand: multiline - >>> print (c.parser.parseString('multiline has > inside;').dump()) + >>> print(c.parser.parseString('multiline has > inside;').dump()) ['multiline', 'has > inside', ';', ''] - args: has > inside - multilineCommand: multiline @@ -665,10 +695,10 @@ class Cmd(cmd.Cmd): - multilineCommand: multiline - terminator: ; - terminator: ; - >>> print (c.parser.parseString('multiline command /* with comment in progress;').dump()) + >>> print(c.parser.parseString('multiline command /* with comment in progress;').dump()) ['multiline', ' command /* with comment in progress;'] - multilineCommand: multiline - >>> print (c.parser.parseString('multiline command /* with comment complete */ is done;').dump()) + >>> print(c.parser.parseString('multiline command /* with comment complete */ is done;').dump()) ['multiline', 'command /* with comment complete */ is done', ';', ''] - args: command /* with comment complete */ is done - multilineCommand: multiline @@ -677,7 +707,7 @@ class Cmd(cmd.Cmd): - multilineCommand: multiline - terminator: ; - terminator: ; - >>> print (c.parser.parseString('multiline command ends\n\n').dump()) + >>> print(c.parser.parseString('multiline command ends\n\n').dump()) ['multiline', 'command ends', '\n', '\n'] - args: command ends - multilineCommand: multiline @@ -686,7 +716,7 @@ class Cmd(cmd.Cmd): - multilineCommand: multiline - terminator: ['\n', '\n'] - terminator: ['\n', '\n'] - >>> print (c.parser.parseString('multiline command "with term; ends" now\n\n').dump()) + >>> print(c.parser.parseString('multiline command "with term; ends" now\n\n').dump()) ['multiline', 'command "with term; ends" now', '\n', '\n'] - args: command "with term; ends" now - multilineCommand: multiline @@ -695,7 +725,7 @@ class Cmd(cmd.Cmd): - multilineCommand: multiline - terminator: ['\n', '\n'] - terminator: ['\n', '\n'] - >>> print (c.parser.parseString('what if "quoted strings /* seem to " start comments?').dump()) + >>> print(c.parser.parseString('what if "quoted strings /* seem to " start comments?').dump()) ['what', 'if "quoted strings /* seem to " start comments?'] - args: if "quoted strings /* seem to " start comments? - command: what @@ -703,21 +733,25 @@ class Cmd(cmd.Cmd): - args: if "quoted strings /* seem to " start comments? - command: what ''' - #outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output') - outputParser = (pyparsing.Literal(self.redirector *2) | \ - (pyparsing.WordStart() + self.redirector) | \ + # outputParser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output') + outputParser = (pyparsing.Literal(self.redirector * 2) | + (pyparsing.WordStart() + self.redirector) | pyparsing.Regex('[^=]' + self.redirector))('output') - terminatorParser = pyparsing.Or([(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator') + terminatorParser = pyparsing.Or( + [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator') stringEnd = pyparsing.stringEnd ^ '\nEOF' - self.multilineCommand = pyparsing.Or([pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand') + self.multilineCommand = pyparsing.Or( + [pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand') oneLineCommand = (~self.multilineCommand + pyparsing.Word(self.legalChars))('command') pipe = pyparsing.Keyword('|', identChars='|') self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '') doNotParse = self.commentGrammars | self.commentInProgress | pyparsing.quotedString afterElements = \ pyparsing.Optional(pipe + pyparsing.SkipTo(outputParser ^ stringEnd, ignore=doNotParse)('pipeTo')) + \ - pyparsing.Optional(outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('outputTo')) + pyparsing.Optional( + outputParser + pyparsing.SkipTo(stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())( + 'outputTo')) if self.case_insensitive: self.multilineCommand.setParseAction(lambda x: x[0].lower()) oneLineCommand.setParseAction(lambda x: x[0].lower()) @@ -726,14 +760,21 @@ class Cmd(cmd.Cmd): else: self.blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator') self.blankLineTerminator.setResultsName('terminator') - self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + self.blankLineTerminator)('statement') - self.multilineParser = (((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('args') + terminatorParser)('statement') + - pyparsing.SkipTo(outputParser ^ pipe ^ stringEnd, ignore=doNotParse).setParseAction(lambda x: x[0].strip())('suffix') + afterElements) + self.blankLineTerminationParser = ((self.multilineCommand ^ oneLineCommand) + + pyparsing.SkipTo(self.blankLineTerminator, ignore=doNotParse).setParseAction( + lambda x: x[0].strip())('args') + self.blankLineTerminator)('statement') + self.multilineParser = (((self.multilineCommand ^ oneLineCommand) + pyparsing.SkipTo(terminatorParser, + ignore=doNotParse).setParseAction( + lambda x: x[0].strip())('args') + terminatorParser)('statement') + + pyparsing.SkipTo(outputParser ^ pipe ^ stringEnd, ignore=doNotParse).setParseAction( + lambda x: x[0].strip())('suffix') + afterElements) self.multilineParser.ignore(self.commentInProgress) - self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser, ignore=doNotParse).setParseAction(lambda x:x[0].strip())('args'))('statement') + + self.singleLineParser = ((oneLineCommand + pyparsing.SkipTo(terminatorParser ^ stringEnd ^ pipe ^ outputParser, + ignore=doNotParse).setParseAction( + lambda x: x[0].strip())('args'))('statement') + pyparsing.Optional(terminatorParser) + afterElements) - #self.multilineParser = self.multilineParser.setResultsName('multilineParser') - #self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser') + # self.multilineParser = self.multilineParser.setResultsName('multilineParser') + # self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser') self.blankLineTerminationParser = self.blankLineTerminationParser.setResultsName('statement') self.parser = self.prefixParser + ( stringEnd | @@ -741,7 +782,7 @@ class Cmd(cmd.Cmd): self.singleLineParser | self.blankLineTerminationParser | self.multilineCommand + pyparsing.SkipTo(stringEnd, ignore=doNotParse) - ) + ) self.parser.ignore(self.commentGrammars) inputMark = pyparsing.Literal('<') @@ -757,6 +798,7 @@ class Cmd(cmd.Cmd): def preparse(self, raw, **kwargs): return raw + def postparse(self, parseResult): return parseResult @@ -786,6 +828,7 @@ class Cmd(cmd.Cmd): def postparsing_precmd(self, statement): stop = 0 return stop, statement + def postparsing_postcmd(self, stop): return stop @@ -795,16 +838,18 @@ class Cmd(cmd.Cmd): if target in dir(self): result = target else: - if self.abbrev: # accept shortened versions of commands + if self.abbrev: # accept shortened versions of commands funcs = [fname for fname in self.keywords if fname.startswith(arg)] if len(funcs) == 1: result = 'do_' + funcs[0] return result + def onecmd_plus_hooks(self, line): # The outermost level of try/finally nesting can be condensed once # Python 2.4 support can be dropped. stop = 0 try: + statement = '' try: statement = self.complete_statement(line) (stop, statement) = self.postparsing_precmd(statement) @@ -828,11 +873,10 @@ class Cmd(cmd.Cmd): self.perror(str(e), statement) finally: return self.postparsing_postcmd(stop) + def complete_statement(self, line): """Keep accepting lines of input until the command is complete.""" - if (not line) or ( - not pyparsing.Or(self.commentGrammars). - setParseAction(lambda x: '').transformString(line)): + if not line or (not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line)): raise EmptyStatement() statement = self.parsed(line) while statement.parsed.multilineCommand and (statement.parsed.terminator == ''): @@ -847,7 +891,8 @@ class Cmd(cmd.Cmd): if statement.parsed.pipeTo: self.kept_state = Statekeeper(self, ('stdout',)) self.kept_sys = Statekeeper(sys, ('stdout',)) - self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) + self.redirect = subprocess.Popen(statement.parsed.pipeTo, shell=True, stdout=subprocess.PIPE, + stdin=subprocess.PIPE) sys.stdout = self.stdout = self.redirect.stdin elif statement.parsed.output: if (not statement.parsed.outputTo) and (not can_clip): @@ -915,7 +960,7 @@ class Cmd(cmd.Cmd): if self.use_rawinput: try: - line = raw_input(prompt) + line = input(prompt) except EOFError: line = 'EOF' else: @@ -925,7 +970,7 @@ class Cmd(cmd.Cmd): if not len(line): line = 'EOF' else: - if line[-1] == '\n': # this was always true in Cmd + if line[-1] == '\n': # this was always true in Cmd line = line[:-1] return line @@ -944,21 +989,21 @@ class Cmd(cmd.Cmd): import readline self.old_completer = readline.get_completer() readline.set_completer(self.complete) - readline.parse_and_bind(self.completekey+": complete") + readline.parse_and_bind(self.completekey + ": complete") except ImportError: pass + stop = None try: if intro is not None: self.intro = intro if self.intro: - self.stdout.write(str(self.intro)+"\n") - stop = None + self.stdout.write(str(self.intro) + "\n") while not stop: if self.cmdqueue: line = self.cmdqueue.pop(0) else: line = self.pseudo_raw_input(self.prompt) - if (self.echo) and (isinstance(self.stdin, file)): + if self.echo and isinstance(self.stdin, file): self.stdout.write(line + '\n') stop = self.onecmd_plus_hooks(line) self.postloop() @@ -972,11 +1017,13 @@ class Cmd(cmd.Cmd): return stop def do_EOF(self, arg): - return self._STOP_SCRIPT_NO_EXIT # End of script; should not exit app + return self._STOP_SCRIPT_NO_EXIT # End of script; should not exit app + do_eof = do_EOF def do_quit(self, arg): return self._STOP_AND_EXIT + do_exit = do_quit do_q = do_quit @@ -991,11 +1038,12 @@ class Cmd(cmd.Cmd): | a list of tuples -> interpreted as (value, text), so that the return value can differ from the text advertised to the user ''' - if isinstance(options, basestring): - options = zip(options.split(), options.split()) + local_opts = options + if isinstance(options, string_types): + local_opts = list(zip(options.split(), options.split())) fulloptions = [] - for opt in options: - if isinstance(opt, basestring): + for opt in local_opts: + if isinstance(opt, string_types): fulloptions.append((opt, opt)) else: try: @@ -1003,19 +1051,18 @@ class Cmd(cmd.Cmd): except IndexError: fulloptions.append((opt[0], opt[0])) for (idx, (value, text)) in enumerate(fulloptions): - self.poutput(' %2d. %s\n' % (idx+1, text)) + self.poutput(' %2d. %s\n' % (idx + 1, text)) while True: - response = raw_input(prompt) + response = input(prompt) try: response = int(response) result = fulloptions[response - 1][0] break except ValueError: - pass # loop and ask again + pass # loop and ask again return result - @options([make_option('-l', '--long', action="store_true", - help="describe function of parameter")]) + @options([make_option('-l', '--long', action="store_true", help="describe function of parameter")]) def do_show(self, arg, opts): '''Shows value of a parameter.''' param = arg.strip().lower() @@ -1032,7 +1079,7 @@ class Cmd(cmd.Cmd): else: self.poutput(result[p]) else: - raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param) + raise LookupError("Parameter '%s' not supported (type 'show' for list of parameters)." % param) def do_set(self, arg): ''' @@ -1067,7 +1114,7 @@ class Cmd(cmd.Cmd): def do_pause(self, arg): 'Displays the specified text then waits for the user to press RETURN.' - raw_input(arg + '\n') + input(arg + '\n') def do_shell(self, arg): 'execute a command as if at the OS prompt.' @@ -1091,8 +1138,10 @@ class Cmd(cmd.Cmd): else: def quit(): raise EmbeddedConsoleExit + def onecmd_plus_hooks(arg): return self.onecmd_plus_hooks(arg + '\n') + def run(arg): try: file = open(arg) @@ -1100,23 +1149,26 @@ class Cmd(cmd.Cmd): file.close() except IOError as e: self.perror(e) + self.pystate['quit'] = quit self.pystate['exit'] = quit self.pystate['cmd'] = onecmd_plus_hooks self.pystate['run'] = run + keepstate = None try: cprt = 'Type "help", "copyright", "credits" or "license" for more information.' - keepstate = Statekeeper(sys, ('stdin','stdout')) + keepstate = Statekeeper(sys, ('stdin', 'stdout')) sys.stdout = self.stdout sys.stdin = self.stdin - interp.interact(banner= "Python %s on %s\n%s\n(%s)\n%s" % - (sys.version, sys.platform, cprt, self.__class__.__name__, self.do_py.__doc__)) + interp.interact(banner="Python %s on %s\n%s\n(%s)\n%s" % + (sys.version, sys.platform, cprt, self.__class__.__name__, self.do_py.__doc__)) except EmbeddedConsoleExit: pass - keepstate.restore() + if keepstate is not None: + keepstate.restore() @options([make_option('-s', '--script', action="store_true", help="Script format; no separation lines"), - ], arg_desc = '(limit on which commands to include)') + ], arg_desc='(limit on which commands to include)') def do_history(self, arg, opts): """history [arg]: lists past commands issued @@ -1134,6 +1186,7 @@ class Cmd(cmd.Cmd): self.poutput(hi) else: self.stdout.write(hi.pr()) + def last_matching(self, arg): try: if arg: @@ -1142,6 +1195,7 @@ class Cmd(cmd.Cmd): return self.history[-1] except IndexError: return None + def do_list(self, arg): """list [arg]: lists last command issued @@ -1189,11 +1243,13 @@ class Cmd(cmd.Cmd): os.system('%s %s' % (self.editor, filename)) self.do__load(filename) + do_edit = do_ed - saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums)^'*')("idx") + + saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums) ^ '*')("idx") + pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") + pyparsing.stringEnd) + def do_save(self, arg): """`save [N] [filename.ext]` @@ -1211,26 +1267,26 @@ class Cmd(cmd.Cmd): if args.idx == '*': saveme = '\n\n'.join(self.history[:]) elif args.idx: - saveme = self.history[int(args.idx)-1] + saveme = self.history[int(args.idx) - 1] else: saveme = self.history[-1] try: f = open(os.path.expanduser(fname), 'w') f.write(saveme) f.close() - self.pfeedback('Saved to %s' % (fname)) + self.pfeedback('Saved to {}'.format(fname)) except Exception as e: - self.perror('Error saving %s' % (fname)) + self.perror('Error saving {}'.format(fname)) raise def read_file_or_url(self, fname): # TODO: not working on localhost - if isinstance(fname, file): + if os.path.isfile(fname): result = open(fname, 'r') else: match = self.urlre.match(fname) if match: - result = urllib.urlopen(match.group(1)) + result = urlopen(match.group(1)) else: fname = os.path.expanduser(fname) try: @@ -1252,6 +1308,7 @@ class Cmd(cmd.Cmd): self.do__load('%s %s' % (targetname, args)) urlre = re.compile('(https?://[-\\w\\./]+)') + def do_load(self, arg=None): """Runs script of command(s) from a file or URL.""" if arg is None: @@ -1264,8 +1321,8 @@ class Cmd(cmd.Cmd): except IOError as e: self.perror('Problem accessing script from %s: \n%s' % (targetname, e)) return - keepstate = Statekeeper(self, ('stdin','use_rawinput','prompt', - 'continuation_prompt','current_script_dir')) + keepstate = Statekeeper(self, ('stdin', 'use_rawinput', 'prompt', + 'continuation_prompt', 'current_script_dir')) self.stdin = target self.use_rawinput = False self.prompt = self.continuation_prompt = '' @@ -1275,6 +1332,7 @@ class Cmd(cmd.Cmd): keepstate.restore() self.lastcmd = '' return stop and (stop != self._STOP_SCRIPT_NO_EXIT) + do__load = do_load # avoid an unfortunate legacy use of do_load from sqlpython def do_run(self, arg): @@ -1290,6 +1348,7 @@ class Cmd(cmd.Cmd): self.pfeedback(runme) if runme: stop = self.onecmd_plus_hooks(runme) + do_r = do_run def fileimport(self, statement, source): @@ -1305,8 +1364,9 @@ class Cmd(cmd.Cmd): def runTranscriptTests(self, callargs): class TestMyAppCase(Cmd2TestCase): CmdApp = self.__class__ + self.__class__.testfiles = callargs - sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() + sys.argv = [sys.argv[0]] # the --test argument upsets unittest.main() testcase = TestMyAppCase() runner = unittest.TextTestRunner() result = runner.run(testcase) @@ -1317,11 +1377,11 @@ class Cmd(cmd.Cmd): if self.onecmd_plus_hooks(initial_command + '\n'): return self._STOP_AND_EXIT - def cmdloop(self): + def cmdloop(self, intro=None): parser = optparse.OptionParser() parser.add_option('-t', '--test', dest='test', - action="store_true", - help='Test against transcript(s) in FILE (wildcards OK)') + action="store_true", + help='Test against transcript(s) in FILE (wildcards OK)') (callopts, callargs) = parser.parse_args() if callopts.test: self.runTranscriptTests(callargs) @@ -1329,15 +1389,19 @@ class Cmd(cmd.Cmd): if not self.run_commands_at_invocation(callargs): self._cmdloop() + class HistoryItem(str): listformat = '-------------------------[%d]\n%s\n' + def __init__(self, instr): str.__init__(self) self.lowercase = self.lower() self.idx = None + def pr(self): return self.listformat % (self.idx, str(self)) + class History(list): '''A list of HistoryItems that knows how to respond to user requests. >>> h = History([HistoryItem('first'), HistoryItem('second'), HistoryItem('third'), HistoryItem('fourth')]) @@ -1360,17 +1424,20 @@ class History(list): >>> h.search('/IR/') ['first', 'third'] ''' + def zero_based_index(self, onebased): result = onebased if result > 0: result -= 1 return result + def to_index(self, raw): if raw: result = self.zero_based_index(int(raw)) else: result = None return result + def search(self, target): target = target.strip() if target[0] == target[-1] == '/' and len(target) > 1: @@ -1379,7 +1446,9 @@ class History(list): target = re.escape(target) pattern = re.compile(target, re.IGNORECASE) return [s for s in self if pattern.search(s)] + spanpattern = re.compile(r'^\s*(?P<start>\-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>\-?\d+)?\s*$') + def span(self, raw): if raw.lower() in ('*', '-', 'all'): raw = ':' @@ -1402,10 +1471,12 @@ class History(list): return result rangePattern = re.compile(r'^\s*(?P<start>[\d]+)?\s*\-\s*(?P<end>[\d]+)?\s*$') + def append(self, new): new = HistoryItem(new) list.append(self, new) new.idx = len(self) + def extend(self, new): for n in new: self.append(n) @@ -1418,7 +1489,7 @@ class History(list): if getme < 0: return self[:(-1 * getme)] else: - return [self[getme-1]] + return [self[getme - 1]] except IndexError: return [] except ValueError: @@ -1436,16 +1507,19 @@ class History(list): if getme.startswith(r'/') and getme.endswith(r'/'): finder = re.compile(getme[1:-1], re.DOTALL | re.MULTILINE | re.IGNORECASE) + def isin(hi): return finder.search(hi) else: def isin(hi): - return (getme.lower() in hi.lowercase) + return getme.lower() in hi.lowercase return [itm for itm in self if isin(itm)] + class NotSettableError(Exception): pass + def cast(current, new): """Tries to force a new value into the same type as the current.""" typ = type(current) @@ -1458,54 +1532,64 @@ def cast(current, new): new = new.lower() except: pass - if (new=='on') or (new[0] in ('y','t')): + if (new == 'on') or (new[0] in ('y', 't')): return True - if (new=='off') or (new[0] in ('n','f')): + if (new == 'off') or (new[0] in ('n', 'f')): return False else: try: return typ(new) except: pass - print ("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new)) + print("Problem setting parameter (now %s) to %s; incorrect type?" % (current, new)) return current + class Statekeeper(object): def __init__(self, obj, attribs): self.obj = obj self.attribs = attribs if self.obj: self.save() + def save(self): for attrib in self.attribs: setattr(self, attrib, getattr(self.obj, attrib)) + def restore(self): if self.obj: for attrib in self.attribs: setattr(self.obj, attrib, getattr(self, attrib)) + class Borg(object): '''All instances of any Borg subclass will share state. from Python Cookbook, 2nd Ed., recipe 6.16''' _shared_state = {} + def __new__(cls, *a, **k): obj = object.__new__(cls, *a, **k) obj.__dict__ = cls._shared_state return obj + class OutputTrap(Borg): '''Instantiate an OutputTrap to divert/capture ALL stdout output. For use in unit testing. Call `tearDown()` to return to normal output.''' + def __init__(self): self.contents = '' self.old_stdout = sys.stdout sys.stdout = self + def write(self, txt): self.contents += txt + def read(self): result = self.contents self.contents = '' return result + def tearDown(self): sys.stdout = self.old_stdout self.contents = '' @@ -1516,6 +1600,7 @@ class Cmd2TestCase(unittest.TestCase): that will execute the commands in a transcript file and expect the results shown. See example.py''' CmdApp = None + def fetchTranscripts(self): self.transcripts = {} for fileset in self.CmdApp.testfiles: @@ -1524,86 +1609,92 @@ class Cmd2TestCase(unittest.TestCase): self.transcripts[fname] = iter(tfile.readlines()) tfile.close() if not len(self.transcripts): - raise StandardError("No test files found - nothing to test.") + raise Exception("No test files found - nothing to test.") + def setUp(self): if self.CmdApp: self.outputTrap = OutputTrap() self.cmdapp = self.CmdApp() self.fetchTranscripts() - def runTest(self): # was testall + + def runTest(self): # was testall if self.CmdApp: its = sorted(self.transcripts.items()) for (fname, transcript) in its: self._test_transcript(fname, transcript) + regexPattern = pyparsing.QuotedString(quoteChar=r'/', escChar='\\', multiline=True, unquoteResults=True) regexPattern.ignore(pyparsing.cStyleComment) notRegexPattern = pyparsing.Word(pyparsing.printables) notRegexPattern.setParseAction(lambda t: re.escape(t[0])) expectationParser = regexPattern | notRegexPattern anyWhitespace = re.compile(r'\s', re.DOTALL | re.MULTILINE) + def _test_transcript(self, fname, transcript): lineNum = 0 finished = False - line = transcript.next() + line = next(transcript) lineNum += 1 tests_run = 0 while not finished: # Scroll forward to where actual commands begin while not line.startswith(self.cmdapp.prompt): try: - line = transcript.next() + line = next(transcript) except StopIteration: finished = True break lineNum += 1 command = [line[len(self.cmdapp.prompt):]] - line = transcript.next() + line = next(transcript) # Read the entirety of a multi-line command while line.startswith(self.cmdapp.continuation_prompt): command.append(line[len(self.cmdapp.continuation_prompt):]) try: - line = transcript.next() + line = next(transcript) except StopIteration: raise (StopIteration, - 'Transcript broke off while reading command beginning at line %d with\n%s' - % (command[0])) + 'Transcript broke off while reading command beginning at line {} with\n{}'.format(lineNum, + command[0]) + ) lineNum += 1 command = ''.join(command) # Send the command into the application and capture the resulting output stop = self.cmdapp.onecmd_plus_hooks(command) - #TODO: should act on ``stop`` + # TODO: should act on ``stop`` result = self.outputTrap.read() # Read the expected result from transcript if line.startswith(self.cmdapp.prompt): - message = '\nFile %s, line %d\nCommand was:\n%r\nExpected: (nothing)\nGot:\n%r\n'%\ - (fname, lineNum, command, result) - self.assert_(not(result.strip()), message) + message = '\nFile %s, line %d\nCommand was:\n%r\nExpected: (nothing)\nGot:\n%r\n' % \ + (fname, lineNum, command, result) + self.assert_(not (result.strip()), message) continue expected = [] while not line.startswith(self.cmdapp.prompt): expected.append(line) try: - line = transcript.next() + line = next(transcript) except StopIteration: finished = True break lineNum += 1 expected = ''.join(expected) # Compare actual result to expected - message = '\nFile %s, line %d\nCommand was:\n%s\nExpected:\n%s\nGot:\n%s\n'%\ - (fname, lineNum, command, expected, result) + message = '\nFile %s, line %d\nCommand was:\n%s\nExpected:\n%s\nGot:\n%s\n' % \ + (fname, lineNum, command, expected, result) expected = self.expectationParser.transformString(expected) # checking whitespace is a pain - let's skip it expected = self.anyWhitespace.sub('', expected) result = self.anyWhitespace.sub('', result) - self.assert_(re.match(expected, result, re.MULTILINE | re.DOTALL), message) + self.assertTrue(re.match(expected, result, re.MULTILINE | re.DOTALL), message) def tearDown(self): if self.CmdApp: self.outputTrap.tearDown() + if __name__ == '__main__': - doctest.testmod(optionflags = doctest.NORMALIZE_WHITESPACE) + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) ''' To make your application transcript-testable, replace @@ -1627,5 +1718,3 @@ into a file, ``transcript.test``, and invoke the test like:: Wildcards can be used to test against multiple transcript files. ''' - - diff --git a/docs/conf.py b/docs/conf.py index ee6b5e6a..32d9243c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,12 +11,12 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +# import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) +# sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- @@ -31,7 +31,7 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' @@ -51,40 +51,40 @@ release = '0.6.9a' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -96,26 +96,26 @@ html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -124,74 +124,73 @@ html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'cmd2doc' - # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +# latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' +# latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'cmd2.tex', u'cmd2 Documentation', - u'Catherine Devlin', 'manual'), + ('index', 'cmd2.tex', u'cmd2 Documentation', + u'Catherine Devlin', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst index 87952611..43b39b29 100644 --- a/docs/freefeatures.rst +++ b/docs/freefeatures.rst @@ -51,8 +51,8 @@ Commands at invocation You can send commands to your app as you invoke it by including them as extra arguments to the program. -``cmd2`` interprets each argument as a separate -command, so you should enclose each command in +``cmd2`` interprets each argument as a separate +command, so you should enclose each command in quotation marks if it is more than a one-word command. :: @@ -60,9 +60,9 @@ quotation marks if it is more than a one-word command. cat@eee:~/proj/cmd2/example$ python example.py "say hello" "say Gracie" quit hello Gracie - cat@eee:~/proj/cmd2/example$ + cat@eee:~/proj/cmd2/example$ + - Output redirection ================== @@ -75,7 +75,7 @@ As in a Unix shell, output of a command can be redirected: ending with a bare ``>``, as in ``mycommand args >``.. Redirecting to paste buffer requires software to be installed on the operating system, pywin32_ on Windows or xclip_ on \*nix. - + If your application depends on mathematical syntax, ``>`` may be a bad choice for redirecting output - it will prevent you from using the greater-than sign in your actual user commands. You can override your @@ -83,7 +83,7 @@ app's value of ``self.redirector`` to use a different string for output redirect class MyApp(cmd2.Cmd): redirector = '->' - + :: (Cmd) say line1 -> out.txt @@ -94,7 +94,7 @@ app's value of ``self.redirector`` to use a different string for output redirect .. _pywin32: http://sourceforge.net/projects/pywin32/ .. _xclip: http://www.cyberciti.biz/faq/xclip-linux-insert-files-command-output-intoclipboard/ - + Python ====== @@ -109,34 +109,34 @@ See see :ref:`parameters`) :: - (Cmd) py print("-".join("spelling")) - s-p-e-l-l-i-n-g - (Cmd) py - Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15) - [GCC 4.4.1] on linux2 - Type "help", "copyright", "credits" or "license" for more information. - (CmdLineApp) - - py <command>: Executes a Python command. - py: Enters interactive Python mode. - End with `Ctrl-D` (Unix) / `Ctrl-Z` (Windows), `quit()`, 'exit()`. - Non-python commands can be issued with `cmd("your command")`. - - >>> import os - >>> os.uname() - ('Linux', 'eee', '2.6.31-19-generic', '#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010', 'i686') - >>> cmd("say --piglatin {os}".format(os=os.uname()[0])) - inuxLay - >>> self.prompt - '(Cmd) ' - >>> self.prompt = 'Python was here > ' - >>> quit() - Python was here > + (Cmd) py print("-".join("spelling")) + s-p-e-l-l-i-n-g + (Cmd) py + Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15) + [GCC 4.4.1] on linux2 + Type "help", "copyright", "credits" or "license" for more information. + (CmdLineApp) + + py <command>: Executes a Python command. + py: Enters interactive Python mode. + End with `Ctrl-D` (Unix) / `Ctrl-Z` (Windows), `quit()`, 'exit()`. + Non-python commands can be issued with `cmd("your command")`. + + >>> import os + >>> os.uname() + ('Linux', 'eee', '2.6.31-19-generic', '#56-Ubuntu SMP Thu Jan 28 01:26:53 UTC 2010', 'i686') + >>> cmd("say --piglatin {os}".format(os=os.uname()[0])) + inuxLay + >>> self.prompt + '(Cmd) ' + >>> self.prompt = 'Python was here > ' + >>> quit() + Python was here > Searchable command history ========================== -All cmd_-based applications have access to previous commands with +All cmd_-based applications have access to previous commands with the up- and down- cursor keys. All cmd_-based applications on systems with the ``readline`` module @@ -155,7 +155,7 @@ also provide `bash-like history list editing`_. Quitting the application ======================== -``cmd2`` pre-defines a ``quit`` command for you (with +``cmd2`` pre-defines a ``quit`` command for you (with synonyms ``exit`` and simply ``q``). It's trivial, but it's one less thing for you to remember. @@ -164,14 +164,14 @@ Abbreviated commands ==================== ``cmd2`` apps will accept shortened command names -so long as there is no ambiguity. Thus, if +so long as there is no ambiguity. Thus, if ``do_divide`` is defined, then ``divid``, ``div``, or even ``d`` will suffice, so long as there are no other commands defined beginning with *divid*, *div*, or *d*. This behavior can be turned off with ``app.abbrev`` (see :ref:`parameters`) - + Misc. pre-defined commands ========================== @@ -196,12 +196,12 @@ a ``cmd2``-based app is copied from the screen and pasted into a text file, ``transcript.txt``, then a transcript test can be run against it:: python app.py --test transcript.txt - + Any non-whitespace deviations between the output prescribed in ``transcript.txt`` and the actual output from a fresh run of the application will be reported as a unit test failure. (Whitespace is ignored during the comparison.) -Regular expressions can be embedded in the transcript inside paired ``/`` +Regular expressions can be embedded in the transcript inside paired ``/`` slashes. These regular expressions should not include any whitespace expressions. diff --git a/docs/index.rst b/docs/index.rst index 40fb8c81..f98ebb4a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,11 +20,11 @@ The basic use of ``cmd2`` is identical to that of cmd_. 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``:: - + from cmd2 import Cmd class App(Cmd): # customized attributes and methods here - + 2. Instantiate ``App`` and start the command loop:: app = App() @@ -36,21 +36,20 @@ Resources * cmd_ * `project bug tracker`_ * `cmd2 project page`_ -* `PyCon 2010 presentation <http://us.pycon.org/2010/conference/talks/#proposal_link_153>`_, +* `PyCon 2010 presentation <https://github.com/python-cmd2/cmd2/blob/master/docs/pycon2010/pycon2010.rst>`_, *Easy Command-Line Applications with cmd and cmd2*: - :doc:`slides <pycon2010/pycon2010>`, - `video <http://python.mirocommunity.com/video/1533/easy-command-line-applications>`_ - + :doc:`slides <pycon2010/pycon2010>`, + `video <http://pyvideo.org/pycon-us-2010/pycon-2010--easy-command-line-applications-with-c.html>`_ 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: .. toctree:: :maxdepth: 2 - + overview example freefeatures @@ -61,7 +60,7 @@ Contents: Compatibility ============= -Tested and working with Python 2.5, 2.6, 2.7, 3.1; Jython 2.5 +Tested and working with Python 2.7, 3.5, 3.6 Indices and tables ================== diff --git a/docs/pycon2010/fileutil.py b/docs/pycon2010/fileutil.py deleted file mode 100644 index 5e754b5a..00000000 --- a/docs/pycon2010/fileutil.py +++ /dev/null @@ -1,12 +0,0 @@ -import glob -import os.path - -for fullfilename in glob.glob('/home/cat/proj/cmd2/*.py'): - (dirpath, fname) = os.path.split(fullfilename) - stats = os.stat(fullfilename) - binds['path'] = dirpath - binds['name'] = fname - binds['bytes'] = stats.st_size - cmd("""INSERT INTO cat.files (path, name, bytes) - VALUES (%(path)s, %(name)s, %(bytes)s)""") -quit() diff --git a/docs/pycon2010/graph.py b/docs/pycon2010/graph.py deleted file mode 100644 index 96ffde72..00000000 --- a/docs/pycon2010/graph.py +++ /dev/null @@ -1,41 +0,0 @@ -from turtle import * -pu() -goto(-400,-400) - -def label(txt): - write(txt, font=('Arial', 20, 'italic')) -hideturtle() -width(6) - -def line(len, _label): - start = pos() - pd() - forward(len) - pu() - forward(30) - pd() - label(_label) - pu() - goto(start) - -def tech(x, y, _label): - pu() - goto(x, y) - pd() - write(_label, font=('Arial', 40, 'bold')) - pu() - -line(600, "Easy to write") -left(90) -line(600, "Easy to use") - -tech(-360, 160, 'GUI') -tech(-390, 100, 'AJAX') -tech(-300, -10, 'webapp') -tech(190, -380, 'CLU') -tech(60, -320, 'TUI') -tech(100, -210, 'cmd') -tech(80, -80, 'cmd2') - -while True: - pass
\ No newline at end of file diff --git a/docs/pycon2010/pirate.py b/docs/pycon2010/pirate.py index 98db50ea..bd8b5170 100644 --- a/docs/pycon2010/pirate.py +++ b/docs/pycon2010/pirate.py @@ -1,7 +1,10 @@ +# coding=utf-8 from cmd import Cmd + class Pirate(Cmd): pass + pirate = Pirate() -pirate.cmdloop()
\ No newline at end of file +pirate.cmdloop() diff --git a/docs/pycon2010/pirate2.py b/docs/pycon2010/pirate2.py index e2c49609..343f94ff 100644 --- a/docs/pycon2010/pirate2.py +++ b/docs/pycon2010/pirate2.py @@ -1,18 +1,24 @@ +# coding=utf-8 from cmd import Cmd + + # using ``do_`` methods class Pirate(Cmd): gold = 3 + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 print('Now we gots {0} doubloons' .format(self.gold)) + def do_drink(self, arg): 'Drown your sorrrows in rrrum.' self.gold -= 1 print('Now we gots {0} doubloons' .format(self.gold)) + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pirate3.py b/docs/pycon2010/pirate3.py index 7977a8dc..ecc70f3f 100644 --- a/docs/pycon2010/pirate3.py +++ b/docs/pycon2010/pirate3.py @@ -1,21 +1,29 @@ +# coding=utf-8 from cmd import Cmd + + # using hook class Pirate(Cmd): gold = 3 + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): - 'Drown your sorrrows in rrrum.' + 'Drown your sorrrows in rrrum.' self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pirate4.py b/docs/pycon2010/pirate4.py index 5de9c212..a4e4816d 100644 --- a/docs/pycon2010/pirate4.py +++ b/docs/pycon2010/pirate4.py @@ -1,27 +1,35 @@ +# coding=utf-8 from cmd import Cmd + + # using arguments class Pirate(Cmd): gold = 3 + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.'''.format(arg)) - self.gold -= 1 + self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons'.format(self.gold)) + pirate = Pirate() -pirate.cmdloop()
\ No newline at end of file +pirate.cmdloop() diff --git a/docs/pycon2010/pirate5.py b/docs/pycon2010/pirate5.py index 7add4635..2167c7f4 100644 --- a/docs/pycon2010/pirate5.py +++ b/docs/pycon2010/pirate5.py @@ -1,25 +1,32 @@ +# coding=utf-8 from cmd import Cmd + + # quitting class Pirate(Cmd): gold = 3 + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.'''.format(arg)) - self.gold -= 1 + self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) @@ -27,9 +34,11 @@ class Pirate(Cmd): print("Off to debtorrr's prison.") stop = True return stop + def do_quit(self, arg): print("Quiterrr!") - return True + return True + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pirate6.py b/docs/pycon2010/pirate6.py index 4a03fed4..a90c2b52 100644 --- a/docs/pycon2010/pirate6.py +++ b/docs/pycon2010/pirate6.py @@ -1,29 +1,37 @@ +# coding=utf-8 from cmd2 import Cmd + + # prompts and defaults class Pirate(Cmd): gold = 3 prompt = 'arrr> ' + def default(self, line): print('What mean ye by "{0}"?' .format(line)) + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.'''.format(arg)) - self.gold -= 1 + self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) @@ -31,9 +39,11 @@ class Pirate(Cmd): print("Off to debtorrr's prison.") stop = True return stop + def do_quit(self, arg): print("Quiterrr!") - return True + return True + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pirate7.py b/docs/pycon2010/pirate7.py index 25ff5822..a333070c 100644 --- a/docs/pycon2010/pirate7.py +++ b/docs/pycon2010/pirate7.py @@ -1,28 +1,36 @@ +# coding=utf-8 from cmd2 import Cmd + + # prompts and defaults class Pirate(Cmd): gold = 3 prompt = 'arrr> ' + def default(self, line): print('What mean ye by "{0}"?'.format(line)) + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.'''.format(arg)) - self.gold -= 1 + self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) @@ -30,17 +38,21 @@ class Pirate(Cmd): print("Off to debtorrr's prison.") stop = True return stop + def do_quit(self, arg): print("Quiterrr!") - return True + return True + default_to_shell = True multilineCommands = ['sing'] terminators = Cmd.terminators + ['...'] songcolor = 'blue' settable = Cmd.settable + 'songcolor Color to ``sing`` in (red/blue/green/cyan/magenta, bold, underline)' Cmd.shortcuts.update({'~': 'sing'}) + def do_sing(self, arg): print(self.colorize(arg, self.songcolor)) + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pirate8.py b/docs/pycon2010/pirate8.py index 3e80b241..55d6df5c 100644 --- a/docs/pycon2010/pirate8.py +++ b/docs/pycon2010/pirate8.py @@ -1,28 +1,36 @@ +# coding=utf-8 from cmd2 import Cmd, options, make_option + + # prompts and defaults class Pirate(Cmd): gold = 3 prompt = 'arrr> ' + def default(self, line): print('What mean ye by "{0}"?'.format(line)) + def do_loot(self, arg): 'Seize booty from a passing ship.' self.gold += 1 + def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.'''.format(arg)) - self.gold -= 1 + self.gold -= 1 + def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) @@ -30,21 +38,25 @@ class Pirate(Cmd): print("Off to debtorrr's prison.") stop = True return stop + def do_quit(self, arg): print("Quiterrr!") - return True + return True + default_to_shell = True multilineCommands = ['sing'] terminators = Cmd.terminators + ['...'] songcolor = 'blue' settable = Cmd.settable + 'songcolor Color to ``sing`` in (red/blue/green/cyan/magenta, bold, underline)' Cmd.shortcuts.update({'~': 'sing'}) + def do_sing(self, arg): print(self.colorize(arg, self.songcolor)) + @options([make_option('--ho', type='int', default=2, help="How often to chant 'ho'"), make_option('-c', '--commas', - action="store_true", + action="store_true", help="Intersperse commas")]) def do_yo(self, arg, opts): chant = ['yo'] + ['ho'] * opts.ho @@ -53,5 +65,6 @@ class Pirate(Cmd): print('{0} and a bottle of {1}' .format(chant, arg)) + pirate = Pirate() pirate.cmdloop() diff --git a/docs/pycon2010/pycon2010.rst b/docs/pycon2010/pycon2010.rst index 0b3b7a46..6c3af676 100644 --- a/docs/pycon2010/pycon2010.rst +++ b/docs/pycon2010/pycon2010.rst @@ -11,7 +11,7 @@ Web 2.0 .. image:: web-2-0-logos.gif :height: 350px - + But first... ============ @@ -20,10 +20,10 @@ But first... .. image:: akkad.png :height: 250px - + Sargon the Great Founder of Akkadian Empire - + .. twenty-third century BC In between @@ -31,16 +31,16 @@ In between .. image:: apple.jpg :height: 250px - + Command-Line Interface - Unlike the Akkadian Empire, + Unlike the Akkadian Empire, the CLI will never die. Defining CLI ============ Also known as - + - "Line-oriented command interpreter" - "Command-line interface" - "Shell" @@ -85,24 +85,24 @@ Examples .. image:: urwid.png :height: 250px - + Decide your priorities ====================== .. image:: strategy.png :height: 350px - + A ``cmd`` app: pirate.py ======================== :: from cmd import Cmd - + class Pirate(Cmd): pass - + pirate = Pirate() pirate.cmdloop() @@ -114,9 +114,9 @@ Fundamental prrrinciple ======================= .. class:: huge - - ``(Cmd) foo a b c`` - + + ``(Cmd) foo a b c`` + becomes ``self.do_foo('a b c')`` @@ -139,7 +139,7 @@ Fundamental prrrinciple print('Now we gots {0} doubloons' .format(self.gold)) -.. do_methods; more help +.. do_methods; more help Hooks ===== @@ -163,16 +163,16 @@ Hooks: pirate3.py 'Seize booty from a passing ship.' self.gold += 1 def do_drink(self, arg): - 'Drown your sorrrows in rrrum.' + 'Drown your sorrrows in rrrum.' self.gold -= 1 def precmd(self, line): self.initial_gold = self.gold return line - def postcmd(self, stop, line): + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) - + Arguments: pirate4.py ===================== @@ -180,22 +180,22 @@ Arguments: pirate4.py def do_drink(self, arg): '''Drown your sorrrows in rrrum. - - drink [n] - drink [n] barrel[s] o' rum.''' + + drink [n] - drink [n] barrel[s] o' rum.''' try: self.gold -= int(arg) except: if arg: print('''What's "{0}"? I'll take rrrum.''' .format(arg)) - self.gold -= 1 - + self.gold -= 1 + quitting: pirate5.py ==================== :: - def postcmd(self, stop, line): + def postcmd(self, stop, line): if self.gold != self.initial_gold: print('Now we gots {0} doubloons' .format(self.gold)) @@ -205,7 +205,7 @@ quitting: pirate5.py return stop def do_quit(self, arg): print("Quiterrr!") - return True + return True prompts, defaults: pirate6.py ============================= @@ -227,7 +227,7 @@ Other CLI packages * CMdO * pycopia * cmdlin - * cmd2 + * cmd2 Demo ==== @@ -258,7 +258,7 @@ Script files Commands at invocation -Output redirection +Output redirection Python @@ -273,9 +273,9 @@ But wait, there's more * Timing * Echo * Debug - + Minor changes: pirate7.py -========================= +========================= :: @@ -287,18 +287,18 @@ Minor changes: pirate7.py Cmd.shortcuts.update({'~': 'sing'}) def do_sing(self, arg): print(self.colorize(arg, self.songcolor)) - + Now how much would you pay? =========================== options / flags -Quiet (suppress feedback) +Quiet (suppress feedback) BASH-style ``select`` Parsing: terminators, suffixes - + Options: pirate8.py =================== @@ -307,13 +307,13 @@ Options: pirate8.py @options([make_option('--ho', type='int', default=2, help="How often to chant 'ho'"), make_option('-c', '--commas', - action="store_true", + action="store_true", help="Intersperse commas")]) def do_yo(self, arg, opts): chant = ['yo'] + ['ho'] * opts.ho separator = ', ' if opts.commas else ' ' chant = separator.join(chant) - print('{0} and a bottle of {1}' + print('{0} and a bottle of {1}' .format(chant, arg)) Serious example: sqlpython diff --git a/docs/pycon2010/schematic.py b/docs/pycon2010/schematic.py deleted file mode 100644 index 80774859..00000000 --- a/docs/pycon2010/schematic.py +++ /dev/null @@ -1,32 +0,0 @@ -from turtle import * -hideturtle() -width(6) -pensize = 10 -pu() -goto(0,-400) - -def rectangle(x, y, _label): - pu() - seth(0) - backward(x / 2) - fontsize = 40 - pd() - for i in range(2): - forward(x) - left(90) - forward(y) - left(90) - pu() - forward(x / 2) - left(90) - forward(y / 2 - fontsize) - pd() - write(_label, align='center', font=('Arial', fontsize, 'bold')) - -rectangle(800, 80, 'cmd') -pu() -forward(80) -rectangle(200, 400, 'cmd2') - -while True: - pass diff --git a/docs/unfreefeatures.rst b/docs/unfreefeatures.rst index 1cd0081c..c9149409 100644 --- a/docs/unfreefeatures.rst +++ b/docs/unfreefeatures.rst @@ -6,13 +6,13 @@ Multiline commands ================== Command input may span multiple lines for the -commands whose names are listed in the +commands whose names are listed in the parameter ``app.multilineCommands``. These commands will be executed only after the user has entered a *terminator*. By default, the command terminators is ``;``; replacing or appending to the list -``app.terminators`` allows different +``app.terminators`` allows different terminators. A blank line is *always* considered a command terminator (cannot be overridden). @@ -21,17 +21,17 @@ Parsed statements ================= ``cmd2`` passes ``arg`` to a ``do_`` method (or -``default`) as a ParsedString, a subclass of +``default`) as a ParsedString, a subclass of string that includes an attribute ``parsed``. ``parsed`` is a ``pyparsing.ParseResults`` -object produced by applying a pyparsing_ +object produced by applying a pyparsing_ grammar applied to ``arg``. It may include: command Name of the command called raw - Full input exactly as typed. + Full input exactly as typed. terminator Character used to end a multiline command @@ -68,9 +68,9 @@ process are painfully complex and should not be considered stable; future ``cmd2`` releases may change it somewhat (hopefully reducing complexity). -(Getting ``arg`` as a ``ParsedString`` is +(Getting ``arg`` as a ``ParsedString`` is technically "free", in that it requires no application -changes from the cmd_ standard, but there will +changes from the cmd_ standard, but there will be no result unless you change your application to *use* ``arg.parsed``.) @@ -83,7 +83,7 @@ to *use* ``arg.parsed``.) Environment parameters ====================== -Your application can define user-settable parameters +Your application can define user-settable parameters which your code can reference. Create them as class attributes with their default values, and add them (with optional documentation) to ``settable``. @@ -111,7 +111,7 @@ documentation) to ``settable``. (Cmd) set --long degrees_c: 22 # temperature in Celsius - sunny: False # + sunny: False # (Cmd) sunbathe Too dim. (Cmd) set sunny yes @@ -135,7 +135,7 @@ a ``do_`` methods accept Unix-style *flags*. It uses optparse_ to parse the flags, and they work the same way as for that module. -Flags are defined with the ``options`` decorator, +Flags are defined with the ``options`` decorator, which is passed a list of optparse_-style options, each created with ``make_option``. The method should accept a second argument, ``opts``, in @@ -180,19 +180,19 @@ the option's online help. arg_desc='(from city) (to city)') def do_travel(self, arg, opts=None): 'Gets you from (from city) to (to city).' - + :: (Cmd) help travel Gets you from (from city) to (to city). Usage: travel [options] (from-city) (to-city) - + Options: -h, --help show this help message and exit -t, --train by train - -.. _optparse: + +.. _optparse: .. _outputters: @@ -200,17 +200,17 @@ poutput, pfeedback, perror ========================== Standard ``cmd`` applications produce their output with ``self.stdout.write('output')`` (or with ``print``, -but ``print`` decreases output flexibility). ``cmd2`` applications can use +but ``print`` decreases output flexibility). ``cmd2`` applications can use ``self.poutput('output')``, ``self.pfeedback('message')``, and ``self.perror('errmsg')`` instead. These methods have these advantages: - More concise - ``.pfeedback()`` destination is controlled by :ref:`quiet` parameter. - + color ===== -Text output can be colored by wrapping it in the ``colorize`` method. +Text output can be colored by wrapping it in the ``colorize`` method. .. automethod:: cmd2.Cmd.colorize @@ -221,7 +221,7 @@ quiet Controls whether ``self.pfeedback('message')`` output is suppressed; useful for non-essential feedback that the user may not always want -to read. ``quiet`` is only relevant if +to read. ``quiet`` is only relevant if ``app.pfeedback`` is sometimes used. ``select`` @@ -234,13 +234,13 @@ Presents numbered options to user, as bash ``select``. .. automethod:: cmd2.Cmd.select :: - + def do_eat(self, arg): sauce = self.select('sweet salty', 'Sauce? ') result = '{food} with {sauce} sauce, yum!' result = result.format(food=arg, sauce=sauce) self.stdout.write(result + '\n') - + :: (Cmd) eat wheaties @@ -248,4 +248,4 @@ Presents numbered options to user, as bash ``select``. 2. salty Sauce? 2 wheaties with salty sauce, yum! -
\ No newline at end of file + diff --git a/example/example.py b/example/example.py index 9d17bbb7..863084cd 100755 --- a/example/example.py +++ b/example/example.py @@ -1,4 +1,4 @@ - +# coding=utf-8 """A sample application for cmd2. """ @@ -15,7 +15,7 @@ class CmdLineApp(Cmd): @options([make_option('-p', '--piglatin', action="store_true", help="atinLay"), make_option('-s', '--shout', action="store_true", help="N00B EMULATION MODE"), make_option('-r', '--repeat', type="int", help="output [n] times") - ], arg_desc = '(text to say)') + ], arg_desc='(text to say)') def do_speak(self, arg, opts=None): """Repeats what you tell me to.""" arg = ''.join(arg) @@ -30,8 +30,9 @@ class CmdLineApp(Cmd): # self.stdout.write is better than "print", because Cmd can be # initialized with a non-standard output destination - do_say = do_speak # now "say" is a synonym for "speak" - do_orate = do_speak # another synonym, but this one takes multi-line input + do_say = do_speak # now "say" is a synonym for "speak" + do_orate = do_speak # another synonym, but this one takes multi-line input + c = CmdLineApp() c.cmdloop() diff --git a/ignoreBug.py b/ignoreBug.py deleted file mode 100644 index 37819336..00000000 --- a/ignoreBug.py +++ /dev/null @@ -1,11 +0,0 @@ -from pyparsing import * - -teststr = 'please /* ignoreme: | oops */ findme: | kthx' -parser = Word(printables)('leadWord') + SkipTo('|')('statement') -print parser.parseString(teststr).statement -parser.ignore(cStyleComment) -print parser.parseString(teststr).statement -parser = Combine(parser) -print parser.parseString(teststr).statement -parser.ignore(cStyleComment) -print parser.parseString(teststr).statement @@ -1,24 +1,27 @@ #!/usr/bin/python -from setuptools import setup, find_packages -import sys +# coding=utf-8 +from setuptools import setup -install_requires = ['pyparsing >= 2.0.1'] +install_requires = ['pyparsing >= 2.0.1', 'six'] + +tests_require = ['mock', 'pytest'] setup( name="cmd2", - version="0.6.9", + version="0.7.0", py_modules=["cmd2"], - use_2to3=True, + use_2to3=False, # metadata for upload to PyPI - author = 'Catherine Devlin', - author_email = 'catherine.devlin@gmail.com', - description = "Extra features for standard library's cmd module", - license = 'MIT', - keywords = 'command prompt console cmd', - url = 'http://packages.python.org/cmd2/', - install_requires = install_requires, - long_description = """Enhancements for standard library's cmd module. + author='Catherine Devlin', + author_email='catherine.devlin@gmail.com', + description="Extra features for standard library's cmd module", + license='MIT', + keywords='command prompt console cmd', + url='http://packages.python.org/cmd2/', + install_requires=install_requires, + tests_require=tests_require, + long_description="""Enhancements for standard library's cmd module. Drop-in replacement adds several features for command-prompt tools: diff --git a/tests/conftest.py b/tests/conftest.py index 55700914..e1204086 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,4 @@ +# coding=utf-8 # # Cmd2 unit/functional testing # diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 8435ac1a..a7795bb6 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -1,3 +1,4 @@ +# coding=utf-8 # # Cmd2 unit/functional testing # @@ -6,18 +7,14 @@ import mock import pytest - from conftest import run_cmd, _normalize -import cmd2 +from six import StringIO -try: - from StringIO import StringIO -except ImportError: - from io import StringIO +import cmd2 def test_ver(): - assert cmd2.__version__ == '0.6.9a' + assert cmd2.__version__ == '0.7.0' def test_base_help(base_app): diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 06cf50b6..dfb37da7 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -1,3 +1,4 @@ +# coding=utf-8 # # Cmd2 functional testing based on transcript # @@ -37,7 +38,7 @@ class CmdLineApp(Cmd): # self.stdout.write is better than "print", because Cmd can be # initialized with a non-standard output destination - do_say = do_speak # now "say" is a synonym for "speak" + do_say = do_speak # now "say" is a synonym for "speak" do_orate = do_speak # another synonym, but this one takes multi-line input @@ -45,7 +46,7 @@ class CmdLineApp(Cmd): def _cmdline_app(): c = CmdLineApp() c.stdout = StdOut() - #c.shortcuts.update({'&': 'speak', 'h': 'hello'}) + # c.shortcuts.update({'&': 'speak', 'h': 'hello'}) c.settable.append('maxrepeats Max number of `--repeat`s allowed') return c @@ -6,6 +6,7 @@ deps = mock pyparsing pytest + six commands= py.test -v --basetemp={envtmpdir} {posargs} {envpython} cmd2.py |