Skip to content

Commit

Permalink
Refactored to move all cmd2.Cmd class member variables to beginning.
Browse files Browse the repository at this point in the history
Also:
- Added locals_in_py to settable parameters to match documentation.
- Added pycon2010 slides back into doc build since they are referenced with :doc:
- Fixed some references in the documentation
- Updated unit tests accordingly
  • Loading branch information
tleonhardt committed Feb 12, 2017
1 parent e637637 commit f9d2414
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 97 deletions.
140 changes: 71 additions & 69 deletions cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,45 +496,95 @@ class EmptyStatement(Exception):


class Cmd(cmd.Cmd):
echo = False
case_insensitive = True # Commands recognized regardless of case
continuation_prompt = '> '
timing = False # Prints elapsed time for each command
# TODO: Move all instance member initializations inside __init__()

# Attributes which are NOT dynamically settable at runtime
_STOP_AND_EXIT = True # distinguish end of script file from actual exit
_STOP_SCRIPT_NO_EXIT = -999
blankLinesAllowed = False
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'},
'yellow': {True: '\x1b[33m', False: '\x1b[39m'},
}
commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment])
commentGrammars.addParseAction(lambda x: '')
commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/')
current_script_dir = None
default_to_shell = False
defaultExtension = 'txt' # For ``save``, ``load``, etc.
excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
kept_state = None
# 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
multilineCommands = []
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
current_script_dir = None
prefixParser = pyparsing.Empty()
redirector = '>' # for sending output to file
reserved_words = []
feedback_to_output = False # Do include nonessentials in >, | output
quiet = False # Do not suppress nonessential output
saveparser = (pyparsing.Optional(pyparsing.Word(pyparsing.nums) ^ '*')("idx") +
pyparsing.Optional(pyparsing.Word(legalChars + '/\\'))("fname") +
pyparsing.stringEnd)
shortcuts = {'?': 'help', '!': 'shell', '@': 'load', '@@': '_relative_load'}
terminators = [';']
urlre = re.compile('(https?://[-\\w\\./]+)')

# Attributes which ARE dynamicaly settable at runtime
abbrev = True # Abbreviated commands recognized
autorun_on_edit = True # Should files automatically run after editing (doesn't apply to commands)
case_insensitive = True # Commands recognized regardless of case
colors = (platform.system() != 'Windows')
continuation_prompt = '> '
debug = False
default_file_name = 'command.txt' # For ``save``, ``load``, etc.
echo = False
editor = os.environ.get('EDITOR')
if not editor:
if sys.platform[:3] == 'win':
editor = 'notepad'
else:
# Favor command-line editors first so we don't leave the terminal to edit
for editor in ['vim', 'vi', 'emacs', 'nano', 'pico', 'gedit', 'kate', 'subl', 'geany', 'atom']:
if _which(editor):
break
feedback_to_output = False # Do include nonessentials in >, | output
locals_in_py = True
kept_state = None
redirector = '>' # for sending output to file
autorun_on_edit = True # Should files automatically run after editing (doesn't apply to commands)
quiet = False # Do not suppress nonessential output
timing = False # Prints elapsed time for each command

# To make an attribute settable with the "do_set" command, add it to this ...
settable = stubbornDict('''
prompt
abbrev Accept abbreviated commands
autorun_on_edit Automatically run files after editing
case_insensitive upper- and lower-case both OK
colors Colorized output (*nix only)
continuation_prompt On 2nd+ line of input
debug Show full error stack on error
default_file_name for ``save``, ``load``, etc.
echo Echo command issued into output
editor Program used by ``edit``
case_insensitive upper- and lower-case both OK
feedback_to_output include nonessentials in `|`, `>` results
locals_in_py Allow access to your application in py via self
prompt The prompt issued to solicit input
quiet Don't print nonessential feedback
echo Echo command issued into output
timing Report execution times
abbrev Accept abbreviated commands
autorun_on_edit Automatically run files after editing
''')

def __init__(self, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)
self.initial_stdout = sys.stdout
self.history = History()
self.pystate = {}
self.shortcuts = sorted(self.shortcuts.items(), reverse=True)
self.keywords = self.reserved_words + [fname[3:] for fname in dir(self)
if fname.startswith('do_')]
self._init_parser()
self._temp_filename = None

def poutput(self, msg):
'''Convenient shortcut for self.stdout.write(); adds newline if necessary.'''
if msg:
Expand Down Expand Up @@ -573,29 +623,6 @@ def pfeedback(self, msg):
else:
print(msg)

_STOP_AND_EXIT = True # distinguish end of script file from actual exit
_STOP_SCRIPT_NO_EXIT = -999
editor = os.environ.get('EDITOR')
if not editor:
if sys.platform[:3] == 'win':
editor = 'notepad'
else:
# Favor command-line editors first so we don't leave the terminal to edit
for editor in ['vim', 'vi', 'emacs', 'nano', 'pico', 'gedit', 'kate', 'subl', 'geany', 'atom']:
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'},
'yellow': {True: '\x1b[33m', False: '\x1b[39m'},
}
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.
Expand Down Expand Up @@ -631,30 +658,11 @@ def do_help(self, arg):
else:
cmd.Cmd.do_help(self, arg)

def __init__(self, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)
self.initial_stdout = sys.stdout
self.history = History()
self.pystate = {}
self.shortcuts = sorted(self.shortcuts.items(), reverse=True)
self.keywords = self.reserved_words + [fname[3:] for fname in dir(self)
if fname.startswith('do_')]
self._init_parser()
self._temp_filename = None

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{}\n".format(result))

prefixParser = pyparsing.Empty()
commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment])
commentGrammars.addParseAction(lambda x: '')
commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/')
terminators = [';']
blankLinesAllowed = False
multilineCommands = []

def _init_parser(self):
r'''
>>> c = Cmd()
Expand Down Expand Up @@ -1388,10 +1396,6 @@ def do_edit(self, arg):
if self.autorun_on_edit or buffer:
self.do_load(filename)

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]`
Expand Down Expand Up @@ -1450,8 +1454,6 @@ def do__relative_load(self, arg=None):
targetname = os.path.join(self.current_script_dir or '', targetname)
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 or arg is an empty string, use the default filename
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'pycon2010']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
Expand Down
52 changes: 27 additions & 25 deletions docs/settingchanges.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ Features requiring only parameter changes

Several aspects of a ``cmd2`` application's behavior
can be controlled simply by setting attributes of ``App``.
A parameter can also be changed at runtime by the user *if*
A parameter can also be changed at runtime by the user *if*
its name is included in the dictionary ``app.settable``.
(To define your own user-settable parameters, see :ref:`parameters`)

Case-insensitivity
==================

By default, all ``cmd2`` command names are case-insensitive;
``sing the blues`` and ``SiNg the blues`` are equivalent. To change this,
By default, all ``cmd2`` command names are case-insensitive;
``sing the blues`` and ``SiNg the blues`` are equivalent. To change this,
set ``App.case_insensitive`` to False.

Whether or not you set ``case_insensitive``, *please do not* define
command method names with any uppercase letters. ``cmd2`` will probably
do something evil if you do.

Shortcuts
=========

Expand All @@ -28,16 +28,16 @@ like ``!ls``. By default, the following shortcuts are defined:

``?``
help
``!``

``!``
shell: run as OS-level command

``@``
load script file

``@@``
load script file; filename is relative to current script location

To define more shortcuts, update the dict ``App.shortcuts`` with the
{'shortcut': 'command_name'} (omit ``do_``)::

Expand All @@ -56,7 +56,7 @@ shortcut::
(Cmd) !which python
/usr/bin/python

However, if the parameter ``default_to_shell`` is
However, if the parameter ``default_to_shell`` is
``True``, then *every* command will be attempted on
the operating system. Only if that attempt fails
(i.e., produces a nonzero return value) will the
Expand Down Expand Up @@ -90,9 +90,10 @@ Setting ``App.debug`` to ``True`` will produce detailed error stacks
whenever the application generates an error. |settable|

.. |settable| replace:: The user can ``set`` this parameter
during application execution.
during application execution.
(See :ref:`parameters`)

.. _parameters:

Other user-settable parameters
==============================
Expand All @@ -101,18 +102,19 @@ A list of all user-settable parameters, with brief
comments, is viewable from within a running application
with::

(Cmd) set --long
abbrev: True # Accept abbreviated commands
case_insensitive: True # upper- and lower-case both OK
colors: True # Colorized output (*nix only)
continuation_prompt: > # On 2nd+ line of input
debug: False # Show full error stack on error
default_file_name: command.txt # for ``save``, ``load``, etc.
echo: False # Echo command issued into output
editor: gedit # Program used by ``edit``
feedback_to_output: False # include nonessentials in `|`, `>` results
prompt: (Cmd) #
quiet: False # Don't print nonessential feedback
timing: False # Report execution times
(Cmd) set --long
abbrev: True # Accept abbreviated commands
autorun_on_edit: True # Automatically run files after editing
case_insensitive: True # upper- and lower-case both OK
colors: True # Colorized output (*nix only)
continuation_prompt: > # On 2nd+ line of input
debug: False # Show full error stack on error
default_file_name: command.txt # for ``save``, ``load``, etc.
echo: False # Echo command issued into output
editor: vim # Program used by ``edit``
feedback_to_output: False # include nonessentials in `|`, `>` results
locals_in_py: True # Allow access to your application in py via self
prompt: (Cmd) # The prompt issued to solicit input
quiet: False # Don't print nonessential feedback
timing: False # Report execution times

4 changes: 3 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
echo: False
editor: vim
feedback_to_output: False
locals_in_py: True
prompt: (Cmd)
quiet: False
timing: False
Expand All @@ -74,7 +75,8 @@
echo: False # Echo command issued into output
editor: vim # Program used by ``edit``
feedback_to_output: False # include nonessentials in `|`, `>` results
prompt: (Cmd) #
locals_in_py: True # Allow access to your application in py via self
prompt: (Cmd) # The prompt issued to solicit input
quiet: False # Don't print nonessential feedback
timing: False # Report execution times
""".format(color_str)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_cmd2.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ def test_base_cmdenvironment(base_app):

# Settable parameters can be listed in any order, so need to validate carefully using unordered sets
settable_params = {'continuation_prompt', 'default_file_name', 'prompt', 'abbrev', 'quiet', 'case_insensitive',
'colors', 'echo', 'timing', 'editor', 'feedback_to_output', 'debug', 'autorun_on_edit'}
'colors', 'echo', 'timing', 'editor', 'feedback_to_output', 'debug', 'autorun_on_edit',
'locals_in_py'}
out_params = set(out[2].split("Settable parameters: ")[1].split())
assert settable_params == out_params

Expand Down

0 comments on commit f9d2414

Please sign in to comment.