summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd2/ansi.py12
-rw-r--r--cmd2/cmd2.py16
-rw-r--r--docs/api/ansi.rst5
-rw-r--r--docs/api/cmd.rst4
-rw-r--r--docs/api/index.rst1
-rw-r--r--docs/doc_conventions.rst119
-rw-r--r--docs/features/generating_output.rst173
-rw-r--r--docs/features/settings.rst62
-rw-r--r--docs/features/startup_commands.rst2
-rwxr-xr-xexamples/colors.py26
-rwxr-xr-xexamples/plumbum_colors.py2
11 files changed, 332 insertions, 90 deletions
diff --git a/cmd2/ansi.py b/cmd2/ansi.py
index f1b2def8..db828af4 100644
--- a/cmd2/ansi.py
+++ b/cmd2/ansi.py
@@ -91,6 +91,7 @@ def ansi_safe_wcswidth(text: str) -> int:
Wrap wcswidth to make it compatible with strings that contains ANSI escape sequences
:param text: the string being measured
+ :return: the width of the string when printed to the terminal
"""
# Strip ANSI escape sequences since they cause wcswidth to return -1
return wcswidth(strip_ansi(text))
@@ -114,7 +115,7 @@ def fg_lookup(fg_name: str) -> str:
:param fg_name: foreground color name to look up ANSI escape code(s) for
:return: ANSI escape code(s) associated with this color
- :raises ValueError if the color cannot be found
+ :raises ValueError: if the color cannot be found
"""
try:
ansi_escape = FG_COLORS[fg_name.lower()]
@@ -128,7 +129,7 @@ def bg_lookup(bg_name: str) -> str:
:param bg_name: background color name to look up ANSI escape code(s) for
:return: ANSI escape code(s) associated with this color
- :raises ValueError if the color cannot be found
+ :raises ValueError: if the color cannot be found
"""
try:
ansi_escape = BG_COLORS[bg_name.lower()]
@@ -184,8 +185,13 @@ def style(text: Any, *, fg: str = '', bg: str = '', bold: bool = False, underlin
# These can be altered to suit an application's needs and only need to be a
# function with the following structure: func(str) -> str
style_success = functools.partial(style, fg='green', bold=True)
+"""Partial function supplying arguments to style() to generate bold green text"""
+
style_warning = functools.partial(style, fg='bright_yellow')
+"""Partial function supplying arguments to ansi.style() to generate yellow text"""
+
style_error = functools.partial(style, fg='bright_red')
+"""Partial function supplying arguments to ansi.style() to generate bright red text"""
def async_alert_str(*, terminal_columns: int, prompt: str, line: str, cursor_offset: int, alert_msg: str) -> str:
@@ -246,6 +252,6 @@ def set_title_str(title: str) -> str:
"""Get the required string, including ANSI escape codes, for setting window title for the terminal.
:param title: new title for the window
- :return string to write to sys.stderr in order to set the window title to the desired test
+ :return: string to write to sys.stderr in order to set the window title to the desired test
"""
return colorama.ansi.set_title(title)
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index d2ab6bd3..2ed87fdc 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -289,8 +289,8 @@ class Cmd(cmd.Cmd):
# The error that prints when a non-existent command is run
self.default_error = "{} is not a recognized command, alias, or macro"
+ """The error message displayed when a non-existent command is run."""
- # If this string is non-empty, then this warning message will print if a broken pipe error occurs while printing
self.broken_pipe_warning = ''
# Commands that will run at the beginning of the command loop
@@ -413,6 +413,20 @@ class Cmd(cmd.Cmd):
self.perror('Invalid value: {} (valid values: {}, {}, {})'.format(new_val, ansi.ANSI_TERMINAL,
ansi.ANSI_ALWAYS, ansi.ANSI_NEVER))
+ @property
+ def broken_pipe_warning(self) -> str:
+ """Message to display if a BrokenPipeError is raised while writing output.
+
+ :meth:`~cmd2.cmd2.Cmd.poutput()` catches BrokenPipeError exceptions and
+ outputs the contents of `broken_pipe_warning`. The default value is an
+ empty string meaning the BrokenPipeError is silently swallowed.
+ """
+ return self.broken_pipe_error
+
+ @broken_pipe_warning.setter
+ def broken_pipe_warning(self, new_val: str) -> None:
+ self.broken_pipe_error = new_val
+
def _completion_supported(self) -> bool:
"""Return whether tab completion is supported"""
return self.use_rawinput and self.completekey and rl_type != RlType.NONE
diff --git a/docs/api/ansi.rst b/docs/api/ansi.rst
new file mode 100644
index 00000000..e361bd4f
--- /dev/null
+++ b/docs/api/ansi.rst
@@ -0,0 +1,5 @@
+cmd2.ansi
+=========
+
+.. automodule:: cmd2.ansi
+ :members:
diff --git a/docs/api/cmd.rst b/docs/api/cmd.rst
index 4f88101e..ebec17c3 100644
--- a/docs/api/cmd.rst
+++ b/docs/api/cmd.rst
@@ -3,3 +3,7 @@ cmd2.Cmd
.. autoclass:: cmd2.cmd2.Cmd
:members:
+
+ .. attribute:: default_error
+
+
diff --git a/docs/api/index.rst b/docs/api/index.rst
index 94d0fdd4..f0324eab 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -7,5 +7,6 @@ API Reference
cmd
decorators
exceptions
+ ansi
utility_classes
utility_functions
diff --git a/docs/doc_conventions.rst b/docs/doc_conventions.rst
index 39302fc6..226c5edc 100644
--- a/docs/doc_conventions.rst
+++ b/docs/doc_conventions.rst
@@ -22,25 +22,60 @@ In addition:
Naming Files
------------
-- all lower case file names
+All source files in the documentation must:
+
+- have all lower case file names
- if the name has multiple words, separate them with an underscore
-- all documentation file names end in '.rst'
+- end in '.rst'
+
+
+Titles and Headings
+-------------------
+
+reStructuredText allows flexibility in how headings are defined. You only have
+to worry about the heirarchy of headings within a single file. Sphinx magically
+handles the intra-file heirarchy on it's own. This magic means that no matter
+how you style titles and headings in the various files that comprise the
+documentation, Sphinx will render properly structured output. To ensure we have
+a similar consistency when viewing the source files, we use the following
+conventions for titles and headings:
+
+1. When creating a heading for a section, do not use the overline and underline
+syntax. Use the underline syntax only::
+
+ Document Title
+ ==============
+
+2. The string of adornment characters on the line following the heading should
+be the same length as the title.
+
+3. The title of a document should use the '=' adornment character on the next
+line and only one heading of this level should appear in each file.
+
+4. Sections within a document should have their titles adorned with the '-'
+character::
+
+ Section Title
+ -------------
+5. Subsections within a section should have their titles adorned with the '~'
+character::
-Heirarchy of headings
----------------------
+ Subsection Title
+ ~~~~~~~~~~~~~~~~
-show the heirarchy of sphinx headings we use, and the conventions (underline
-only, no overline)
+6. Use two blank lines before every title unless it's the first heading in the
+file. Use one blank line after every heading.
-Use '=', then '-', then '~'. If your document needs more levels than that,
-break it into separate documents.
+7. If your document needs more than three levels of sections, break it into
+separate documents.
-You only have to worry about the heirarchy of headings within a single file.
-Sphinx handles the intra-file heirarchy magically on it's own.
-Use two blank lines before every heading unless it's the first heading in the
-file. Use one blank line after every heading
+Indenting
+---------
+
+In reStructuredText all indenting is significant. Use 2 spaces per indenting
+level.
Code
@@ -85,9 +120,6 @@ and
See :ref:`custom title<features/argument_processing:Help Messages>`
-[TODO what's the right way to link to source code? Can we make it link to the
-tag that the documentation is rendered from?]
-
Autolinking
-----------
@@ -96,6 +128,63 @@ Autolinking
Referencing cmd2 API documentation
----------------------------------
+[TODO what's the right way to link to source code? Can we make it link to the
+tag that the documentation is rendered from?]
+
+It's easy to reference and create a link to the API documentation for classes,
+methods, functions, or attributes.
+
+
+Referencing Methods
+~~~~~~~~~~~~~~~~~~~
+
+To reference a method, use one of the following approaches:
+
+1. Reference the full dotted path of the method::
+
+ The :meth:`cmd2.cmd2.Cmd.poutput` method is similar to the Python built-in
+ print function.
+
+Which renders as: The :meth:`cmd2.cmd2.Cmd.poutput` method is similar to the
+Python built-in print function.
+
+2. Reference the full dotted path to the method, but only display the method
+name::
+
+ The :meth:`~cmd2.cmd2.Cmd.poutput` method is similar to the Python built-in print function.
+
+Which renders as: The :meth:`~cmd2.cmd2.Cmd.poutput` method is similar to the
+Python built-in print function.
+
+3. Reference a portion of the dotted path of the method::
+
+ The :meth:`.cmd2.Cmd.poutput` method is similar to the Python built-in print
+ function.
+
+Which renders as: The :meth:`.cmd2.Cmd.poutput` method is similar to the Python
+built-in print function.
+
+Avoid either of these approaches:
+
+1. Reference just the class name without enough dotted path::
+
+ The :meth:`.Cmd.poutput` method is similar to the Python built-in print
+ function.
+
+Because ``cmd2.Cmd`` subclasses ``cmd.Cmd`` from the standard library, this
+approach does not clarify which class it is referring to.
+
+2. Reference just a method name::
+
+ The :meth:`poutput` method is similar to the Python built-in print
+ function.
+
+While Sphinx may be smart enough to generate the correct output, the potential
+for multiple matching references is high, which causes Sphinx to generate
+warnings. The build pipeline that renders the documentation treats warnings as
+fatal errors. It's best to just be specific about what you are referencing.
+
+
Info and Warning Callouts
-------------------------
diff --git a/docs/features/generating_output.rst b/docs/features/generating_output.rst
index 2ee820f1..58e53962 100644
--- a/docs/features/generating_output.rst
+++ b/docs/features/generating_output.rst
@@ -1,71 +1,148 @@
Generating Output
=================
-how to generate output
+A standard ``cmd`` application can produce output by using either of these
+methods::
+
+ print("Greetings, Professor Falken.", file=self.stdout)
+ self.stdout.write("Shall we play a game?\n")
+
+While you could send output directly to ``sys.stdout``, ``cmd`` can be
+initialized with a ``stdin`` and ``stdout`` variables, which it stores
+as ``self.stdin`` and ``self.stdout``. By using these variables every
+time you produce output, you can trivially change where all the output
+goes by changing how you initialize your class.
+
+``cmd2`` extends this approach in a number of convenient ways. See
+:ref:`features/redirection:Output Redirection And Pipes` for information on how
+users can change where the output of a command is sent. In order for those
+features to work, the output you generate must be sent to ``self.stdout``. You
+can use the methods described above, and everything will work fine. ``cmd2``
+also adds a number of output related methods to ``Cmd2.Cmd`` which you may use
+to enhance the output your application produces.
+
+
+TODO:
+
-- poutput
- perror
-- paging
-- exceptions
-- color support
+- pwarning
+- pexcept
+- ppaging
+
+- column formatting?
+- wcswidth?
-Standard ``cmd`` applications produce their output with
-``self.stdout.write('output')`` (or with ``print``, but ``print`` decreases
-output flexibility). ``cmd2`` applications can use ``self.poutput('output')``,
-``self.pfeedback('message')``, ``self.perror('errmsg')``, and
-``self.ppaged('text')`` instead. These methods have these advantages:
+- exceptions
-- Handle output redirection to file and/or pipe appropriately
-- More concise
- - ``.pfeedback()`` destination is controlled by ``quiet`` parameter.
-- Option to display long output using a pager via ``ppaged()``
+Ordinary Output
+---------------
-.. automethod:: cmd2.cmd2.Cmd.poutput
- :noindex:
-.. automethod:: cmd2.cmd2.Cmd.perror
- :noindex:
-.. automethod:: cmd2.cmd2.Cmd.pfeedback
- :noindex:
-.. automethod:: cmd2.cmd2.Cmd.ppaged
- :noindex:
+The :meth:`.cmd2.Cmd.poutput` method is similar to the Python
+`built-in print function <https://docs.python.org/3/library/functions.html#print>`_. :meth:`~cmd2.cmd2.Cmd.poutput` adds two
+conveniences.
+ 1. Since users can pipe output to a shell command, it catches
+ ``BrokenPipeError`` and outputs the contents of
+ ``self.broken_pipe_warning`` to ``stderr``. ``self.broken_pipe_warning``
+ defaults to an empty string so this method will just swallow the exception.
+ If you want to show an error message, put it in
+ ``self.broken_pipe_warning`` when you initialize ``Cmd2.cmd``.
-Suppressing non-essential output
---------------------------------
+ 2. It examines and honors the :ref:`features/settings:allow_ansi` setting.
+ See :ref:`features/generating_output:Colored Output` below for more details.
-The ``quiet`` setting controls whether ``self.pfeedback()`` actually produces
-any output. If ``quiet`` is ``False``, then the output will be produced. If
-``quiet`` is ``True``, no output will be produced.
+Here's a simple command that shows this method in action::
-This makes ``self.pfeedback()`` useful for non-essential output like status
-messages. Users can control whether they would like to see these messages by
-changing the value of the ``quiet`` setting.
+ def do_echo(self, args):
+ """A simple command showing how poutput() works"""
+ self.poutput(args)
Colored Output
--------------
-The output methods in the previous section all honor the ``allow_ansi``
-setting, which has three possible values:
+You may want to generate output in different colors, which is typically done by
+adding `ANSI escape sequences
+<https://en.wikipedia.org/wiki/ANSI_escape_code#Colors>`_ which tell the
+terminal to change the foreground and background colors. If you want to give
+yourself a headache, you can generate these by hand. You could also use another
+Python color library like `plumbum.colors
+<https://plumbum.readthedocs.io/en/latest/colors.html>`_, `colored
+<https://gitlab.com/dslackw/colored>`_, or `colorama
+<https://github.com/tartley/colorama>`_. Colorama is unique because when it's
+running on Windows, it wraps ``stdout``, looks for ANSI escape sequences, and
+converts them into the appropriate ``win32`` calls to modify the state of the
+terminal.
+
+``cmd2`` imports and uses Colorama and provides a number of convenience methods
+for generating colorized output, measuring the screen width of colorized
+output, setting the window title in the terminal, and removing ANSI escape
+codes from a string. These functions are all documentated in
+:mod:`cmd2.ansi`.
+
+:mod:`cmd2.cmd2.Cmd` includes an :ref:`features/settings:allow_ansi` setting,
+which controls whether ANSI escape sequences that instruct the terminal to
+colorize output are stripped from the output. The recommended approach is to
+construct your application so that it generates colorized output, and then
+allow your users to use this setting to remove the colorization if they do not
+want it.
+
+Output generated by any of these
+methods will honor the :ref:`features/settings:allow_ansi` setting:
+
+- :meth:`cmd2.cmd2.Cmd.poutput`
+- :meth:`cmd2.cmd2.Cmd.perror`
+- :meth:`cmd2.cmd2.Cmd.pwarning`
+- :meth:`cmd2.cmd2.Cmd.pexcept`
+- :meth:`cmd2.cmd2.Cmd.pfeedback`
+- :meth:`cmd2.cmd2.Cmd.ppaged`
+
+
+Error Messages
+--------------
+
-Never
- poutput(), pfeedback(), and ppaged() strip all ANSI escape sequences
- which instruct the terminal to colorize output
-Terminal
- (the default value) poutput(), pfeedback(), and ppaged() do not strip any
- ANSI escape sequences when the output is a terminal, but if the output is a
- pipe or a file the escape sequences are stripped. If you want colorized
- output you must add ANSI escape sequences using either cmd2's internal ansi
- module or another color library such as `plumbum.colors`, `colorama`, or
- `colored`.
+Warning Messages
+----------------
+
+
+Feedback
+--------
+
+You may have the need to display information to the user which is not intended
+to be part of the generated output. This could be debugging information or
+status information about the progress of long running commands. It's not
+output, it's not error messages, it's feedback. If you use the
+:ref:`features/settings:Timing` setting, the output of how long it took the
+command to run will be output as feedback. ``cmd2`` has a ``self.pfeedback()``
+method to produce this type of output, and several
+:ref:`features/settings:Settings` to control how this output is handled.
+
+If the ``quiet`` setting is ``True``, then calling ``self.pfeedback()``
+produces no output. If ``quiet`` is ``False``, then the ``feedback_to_output``
+setting is consulted to determine which file descriptor the feedback will be
+sent to. The default value of ``False`` means all feedback is sent to
+``sys.stderr``. If set to ``True``, then the feedback output will be sent to
+``self.stdout`` along with the rest of the generated output.
+
+
+Exceptions
+----------
+
+
+Paging Output
+-------------
+
+
+Centering Text
+--------------
-Always
- poutput(), pfeedback(), and ppaged() never strip ANSI escape sequences,
- regardless of the output destination
+utils.center_text()
-Colored and otherwise styled output can be generated using the `ansi.style()`
-function:
-.. automethod:: cmd2.ansi.style
+Columnar Output
+---------------
+Using wcswidth() and ansi.ansi_safe_wcswidth()
diff --git a/docs/features/settings.rst b/docs/features/settings.rst
index b0575468..25162aed 100644
--- a/docs/features/settings.rst
+++ b/docs/features/settings.rst
@@ -11,6 +11,26 @@ Built In Settings
``cmd2`` has a number of built in settings, which a developer can set a default
value, and which users can modify to change the behavior of the application.
+A list of all user-settable parameters, with brief comments, is viewable from
+within a running application::
+
+ (Cmd) set --long
+ allow_ansi: Terminal # Allow ANSI escape sequences in output (valid values: Terminal, Always, Never)
+ continuation_prompt: > # On 2nd+ line of input
+ debug: False # Show full error stack on error
+ echo: False # Echo command issued into output
+ editor: vim # Program used by ``edit``
+ feedback_to_output: False # include nonessentials in `|`, `>` results
+ locals_in_py: False # Allow access to your application in py via self
+ max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion
+ prompt: (Cmd) # The prompt issued to solicit input
+ quiet: False # Don't print nonessential feedback
+ timing: False # Report execution times
+
+Any of these user-settable parameters can be set while running your app with
+the ``set`` command like so::
+
+ (Cmd) set allow_ansi Never
Timing
~~~~~~
@@ -38,31 +58,29 @@ the application generates an error. |settable|
.. _parameters:
-Other user-settable parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-A list of all user-settable parameters, with brief
-comments, is viewable from within a running application
-with::
+allow_ansi
+~~~~~~~~~~
- (Cmd) set --long
- allow_ansi: Terminal # Allow ANSI escape sequences in output (valid values: Terminal, Always, Never)
- continuation_prompt: > # On 2nd+ line of input
- debug: False # Show full error stack on error
- echo: False # Echo command issued into output
- editor: vim # Program used by ``edit``
- feedback_to_output: False # include nonessentials in `|`, `>` results
- locals_in_py: False # Allow access to your application in py via self
- max_completion_items: 50 # Maximum number of CompletionItems to display during tab completion
- prompt: (Cmd) # The prompt issued to solicit input
- quiet: False # Don't print nonessential feedback
- timing: False # Report execution times
+The ``allow_ansi`` setting controls the behavior of ANSI escape sequences
+in output generated with any of the following methods:
-Any of these user-settable parameters can be set while running your app with
-the ``set`` command like so::
+- ``poutput()``
+- ``perror()``
+- ``pwarning()``
+- ``pfeedback()``
+- ``ppaged()``
- set allow_ansi Never
+This setting can be one of three values:
+- ``Never`` - all ANSI escape sequences which instruct the terminal to colorize
+ output are stripped from the output.
+
+- ``Terminal`` - (the default value) pass through ANSI escape sequences when
+ the output is being sent to the terminal, but if the output is redirected to
+ a pipe or a file the escape sequences are stripped.
+
+- ``Always`` - ANSI escape sequences are always passed through, regardless
Create New Settings
@@ -100,3 +118,7 @@ changes a setting, and will receive both the old value and the new value.
now: 13
(Cmd) sunbathe
It's 13 C - are you a penguin?
+
+
+Hide Built-in Settings
+----------------------
diff --git a/docs/features/startup_commands.rst b/docs/features/startup_commands.rst
index 93984db6..aaaf7722 100644
--- a/docs/features/startup_commands.rst
+++ b/docs/features/startup_commands.rst
@@ -5,7 +5,7 @@ Startup Commands
after your application starts up:
1. Commands at Invocation
-1. Startup Script
+2. Startup Script
Commands run as part of a startup script are always run immediately after the
application finishes initializing so they are guaranteed to run before any
diff --git a/examples/colors.py b/examples/colors.py
index 8c54dfa4..7a4d15e6 100755
--- a/examples/colors.py
+++ b/examples/colors.py
@@ -24,9 +24,11 @@ Always
regardless of the output destination
"""
import argparse
+from typing import Any
import cmd2
from cmd2 import ansi
+from colorama import Fore, Back, Style
class CmdLineApp(cmd2.Cmd):
@@ -66,9 +68,31 @@ class CmdLineApp(cmd2.Cmd):
repetitions = args.repeat or 1
output_str = ansi.style(' '.join(words), fg=args.fg, bg=args.bg, bold=args.bold, underline=args.underline)
- for i in range(min(repetitions, self.maxrepeats)):
+ for _ in range(min(repetitions, self.maxrepeats)):
# .poutput handles newlines, and accommodates output redirection too
self.poutput(output_str)
+ self.perror('error message at the end')
+
+ @staticmethod
+ def perror(msg: Any, *, end: str = '\n', apply_style: bool = True) -> None:
+ """Override perror() method from `cmd2.Cmd`
+
+ Use colorama native approach for styling the text instead of `cmd2.ansi` methods
+
+ :param msg: message to print (anything convertible to a str with '{}'.format() is OK)
+ :param end: string appended after the end of the message, default a newline
+ :param apply_style: If True, then ansi.style_error will be applied to the message text. Set to False in cases
+ where the message text already has the desired style. Defaults to True.
+ """
+ if apply_style:
+ final_msg = "{}{}{}{}".format(Fore.RED, Back.YELLOW, msg, Style.RESET_ALL)
+ else:
+ final_msg = "{}".format(msg)
+ ansi.ansi_aware_write(sys.stderr, final_msg + end)
+
+ def do_timetravel(self, args):
+ """A command which always generates an error message, to demonstrate custom error colors"""
+ self.perror('Mr. Fusion failed to start. Could not energize flux capacitor.')
if __name__ == '__main__':
diff --git a/examples/plumbum_colors.py b/examples/plumbum_colors.py
index a969c4de..b4f0ad1c 100755
--- a/examples/plumbum_colors.py
+++ b/examples/plumbum_colors.py
@@ -104,7 +104,7 @@ class CmdLineApp(cmd2.Cmd):
repetitions = args.repeat or 1
output_str = ansi.style(' '.join(words), fg=args.fg, bg=args.bg, bold=args.bold, underline=args.underline)
- for i in range(min(repetitions, self.maxrepeats)):
+ for _ in range(min(repetitions, self.maxrepeats)):
# .poutput handles newlines, and accommodates output redirection too
self.poutput(output_str)