diff options
author | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-02-08 21:48:00 -0500 |
---|---|---|
committer | Todd Leonhardt <todd.leonhardt@gmail.com> | 2018-02-08 21:48:00 -0500 |
commit | 303e9cb734d217fe4f142040518e3dd7a2a2b3ee (patch) | |
tree | cb069a30f8b1410f27bfc1a39b8bcc39547997ed | |
parent | 4895d5d8db4e57e2ef9a062473a8536f4f07f213 (diff) | |
download | cmd2-git-303e9cb734d217fe4f142040518e3dd7a2a2b3ee.tar.gz |
Added optional persistent readline history feature
- Including an example and info in the Sphinx docs
Also:
- Created CHANGELOG entry for 0.8.1 release
- Added info to README about new sub-menu feature
- Bumped version to 0.8.1
TODO:
- Added a unit test for the persistent readline history feature
-rw-r--r-- | CHANGELOG.md | 9 | ||||
-rwxr-xr-x | README.md | 3 | ||||
-rwxr-xr-x | cmd2.py | 28 | ||||
-rw-r--r-- | docs/conf.py | 2 | ||||
-rw-r--r-- | docs/freefeatures.rst | 15 | ||||
-rwxr-xr-x | examples/persistent_history.py | 22 | ||||
-rwxr-xr-x[-rw-r--r--] | examples/submenus.py | 6 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | tests/test_cmd2.py | 2 |
9 files changed, 69 insertions, 20 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad98d0d..110dec68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.8.1 (TBD, 2018) + +* Enhancements + * Added support for sub-menus. + * See [submenus.py](https://github.com/python-cmd2/cmd2/blob/master/examples/submenus.py) for an example of how to use it + * Added option for persistent readline history + * See [persistent_history.py](https://github.com/python-cmd2/cmd2/blob/master/examples/persistent_history.py) for an example + * See the [Searchable command history](http://cmd2.readthedocs.io/en/latest/freefeatures.html#searchable-command-history) section of the documentation for more info + ## 0.8.0 (February 1, 2018) * Bug Fixes * Fixed unit tests on Python 3.7 due to changes in how re.escape() behaves in Python 3.7 @@ -18,7 +18,7 @@ when using cmd. Main Features ------------- -- Searchable command history (`history` command and `<Ctrl>+r`) +- Searchable command history (`history` command and `<Ctrl>+r`) - optionally persistent - Text file scripting of your application with `load` (`@`) and `_relative_load` (`@@`) - Python scripting of your application with ``pyscript`` - Run shell commands with ``!`` @@ -30,6 +30,7 @@ Main Features - Special-character command shortcuts (beyond cmd's `@` and `!`) - Settable environment parameters - Parsing commands with arguments using `argparse`, including support for sub-commands +- Sub-menu support via the ``AddSubmenu`` decorator - Unicode character support (*Python 3 only*) - Good tab-completion of commands, sub-commands, file system paths, and shell commands - Python 2.7 and 3.4+ support @@ -24,6 +24,8 @@ is used in place of `print`. Git repository on GitHub at https://github.com/python-cmd2/cmd2 """ +import argparse +import atexit import cmd import codecs import collections @@ -31,7 +33,6 @@ import datetime import glob import io import optparse -import argparse import os import platform import re @@ -112,7 +113,7 @@ if six.PY2 and sys.platform.startswith('lin'): except ImportError: pass -__version__ = '0.8.0' +__version__ = '0.8.1' # Pyparsing enablePackrat() can greatly speed up parsing, but problems have been seen in Python 3 in the past pyparsing.ParserElement.enablePackrat() @@ -549,6 +550,7 @@ def strip_ansi(text): def _pop_readline_history(clear_history=True): """Returns a copy of readline's history and optionally clears it (default)""" + # noinspection PyArgumentList history = [ readline.get_history_item(i) for i in range(1, 1 + readline.get_current_history_length()) @@ -689,6 +691,7 @@ class AddSubmenu(object): ) submenu.cmdloop() if self.reformat_prompt is not None: + # noinspection PyUnboundLocalVariable self.submenu.prompt = prompt _push_readline_history(history) finally: @@ -761,12 +764,12 @@ class AddSubmenu(object): _Cmd.complete_help = _complete_submenu_help # Create bindings in the parent command to the submenus commands. - setattr(_Cmd, 'do_' + self.command, enter_submenu) + setattr(_Cmd, 'do_' + self.command, enter_submenu) setattr(_Cmd, 'complete_' + self.command, complete_submenu) # Create additional bindings for aliases for _alias in self.aliases: - setattr(_Cmd, 'do_' + _alias, enter_submenu) + setattr(_Cmd, 'do_' + _alias, enter_submenu) setattr(_Cmd, 'complete_' + _alias, complete_submenu) return _Cmd @@ -833,12 +836,15 @@ class Cmd(cmd.Cmd): 'quiet': "Don't print nonessential feedback", 'timing': 'Report execution times'} - def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False, transcript_files=None): + def __init__(self, completekey='tab', stdin=None, stdout=None, persistent_history_file='', + persistent_history_length=1000, use_ipython=False, transcript_files=None): """An easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package. :param completekey: str - (optional) readline name of a completion key, default to Tab :param stdin: (optional) alternate input file object, if not specified, sys.stdin is used :param stdout: (optional) alternate output file object, if not specified, sys.stdout is used + :param persistent_history_file: str - (optional) file path to load a persistent readline history from + :param persistent_history_length: int - (optional) max number of lines which will be written to the history file :param use_ipython: (optional) should the "ipy" command be included for an embedded IPython shell :param transcript_files: str - (optional) allows running transcript tests when allow_cli_args is False """ @@ -849,6 +855,17 @@ class Cmd(cmd.Cmd): except AttributeError: pass + # If persistent readline history is enabled, then read history from file and register to write to file at exit + if persistent_history_file: + persistent_history_file = os.path.expanduser(persistent_history_file) + try: + readline.read_history_file(persistent_history_file) + # default history len is -1 (infinite), which may grow unruly + readline.set_history_length(persistent_history_length) + except FileNotFoundError: + pass + atexit.register(readline.write_history_file, persistent_history_file) + # Call super class constructor. Need to do it in this way for Python 2 and 3 compatibility cmd.Cmd.__init__(self, completekey=completekey, stdin=stdin, stdout=stdout) @@ -2901,6 +2918,7 @@ def namedtuple_with_two_defaults(typename, field_names, default_values=('', '')) :return: namedtuple type """ T = collections.namedtuple(typename, field_names) + # noinspection PyUnresolvedReferences T.__new__.__defaults__ = default_values return T diff --git a/docs/conf.py b/docs/conf.py index d4ef14bf..09d68b9c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -62,7 +62,7 @@ author = 'Catherine Devlin and Todd Leonhardt' # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. -release = '0.8.0' +release = '0.8.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/freefeatures.rst b/docs/freefeatures.rst index ea40c87c..a439db56 100644 --- a/docs/freefeatures.rst +++ b/docs/freefeatures.rst @@ -13,7 +13,7 @@ Script files ============ Text files can serve as scripts for your ``cmd2``-based -application, with the ``load``, ``_relative_load``, ``edit`` and ``history`` commands. +application, with the ``load``, ``_relative_load``, and ``edit`` commands. Both ASCII and UTF-8 encoded unicode text files are supported. @@ -25,8 +25,6 @@ Simply include one command per line, typed exactly as you would inside a ``cmd2` .. automethod:: cmd2.Cmd.do_edit -.. automethod:: cmd2.Cmd.do_history - Comments ======== @@ -250,17 +248,22 @@ Searchable command history ========================== All cmd_-based applications have access to previous commands with -the up- and down- cursor keys. +the up- and down- arrow keys. All cmd_-based applications on systems with the ``readline`` module -also provide `bash-like history list editing`_. +also provide `Readline Emacs editing mode`_. With this you can, for example, use **Ctrl-r** to search backward through +the readline history. -.. _`bash-like history list editing`: http://www.talug.org/events/20030709/cmdline_history.html +``cmd2`` adds the option of making this readline history persistent via optional arguments to ``cmd2.Cmd.__init__()``: + +.. automethod:: cmd2.Cmd.__init__ ``cmd2`` makes a third type of history access available with the **history** command: .. automethod:: cmd2.Cmd.do_history +.. _`Readline Emacs editing mode`: http://readline.kablamo.org/emacs.html + Quitting the application ======================== diff --git a/examples/persistent_history.py b/examples/persistent_history.py new file mode 100755 index 00000000..21a2cbf3 --- /dev/null +++ b/examples/persistent_history.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# coding=utf-8 +"""This example demonstrates how to enable persistent readline history in your cmd2 application. + +This will allow end users of your cmd2-based application to use the arrow keys and Ctrl+r in a manner which persists +across invocations of your cmd2 application. This can make it much easier for them to use your application. +""" +import cmd2 + + +class Cmd2PersistentHistory(cmd2.Cmd): + """Basic example of how to enable persistent readline history within your cmd2 app.""" + def __init__(self): + """""" + cmd2.Cmd.__init__(self, persistent_history_file='~/.persistent_history.cmd2', persistent_history_length=500) + + # ... your class code here ... + + +if __name__ == '__main__': + app = Cmd2PersistentHistory() + app.cmdloop() diff --git a/examples/submenus.py b/examples/submenus.py index 52f26e08..1e3da0da 100644..100755 --- a/examples/submenus.py +++ b/examples/submenus.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 """ Create a CLI with a nested command structure as follows. The commands 'second' and 'third' navigate the CLI to the scope of the submenu. Nesting of the submenus is done with the cmd2.AddSubmenu() decorator. @@ -6,9 +7,6 @@ of the submenu. Nesting of the submenus is done with the cmd2.AddSubmenu() decor (Top Level)----second----->(2nd Level)----third----->(3rd Level) | | | ---> say ---> say ---> say - - - """ from __future__ import print_function import sys @@ -71,7 +69,6 @@ class SecondLevel(cmd2.Cmd): return [s for s in ['qwe', 'asd', 'zxc'] if s.startswith(text)] - @cmd2.AddSubmenu(SecondLevel(), command='second', aliases=('second_alias',), @@ -105,7 +102,6 @@ class TopLevel(cmd2.Cmd): return [s for s in ['qwe', 'asd', 'zxc'] if s.startswith(text)] - if __name__ == '__main__': root = TopLevel() @@ -6,7 +6,7 @@ Setuptools setup file, used to install or test 'cmd2' import sys from setuptools import setup -VERSION = '0.8.0' +VERSION = '0.8.1' DESCRIPTION = "cmd2 - a tool for building interactive command line applications in Python" LONG_DESCRIPTION = """cmd2 is a tool for building interactive command line applications in Python. Its goal is to make it quick and easy for developers to build feature-rich and user-friendly interactive command line applications. It diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 186def65..5f56803d 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -25,7 +25,7 @@ from conftest import run_cmd, normalize, BASE_HELP, HELP_HISTORY, SHORTCUTS_TXT, def test_ver(): - assert cmd2.__version__ == '0.8.0' + assert cmd2.__version__ == '0.8.1' def test_empty_statement(base_app): |