1- """Variant on standard library's cmd with extra features.
2-
3- To use, simply import cmd2.Cmd instead of cmd.Cmd; use precisely as though you
4- were using the standard library's cmd, while enjoying the extra features.
5-
6- Searchable command history (commands: "history")
7- Run commands from file, save to file, edit commands in file
8- Multi-line commands
9- Special-character shortcut commands (beyond cmd's "?" and "!")
10- Settable environment parameters
11- Parsing commands with `argparse` argument parsers (flags)
12- Redirection to file or paste buffer (clipboard) with > or >>
13- Easy transcript-based testing of applications (see examples/transcript_example.py)
14- Bash-style ``select`` available
1+ """cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python.
2+
3+ cmd2 is a tool for building interactive command line applications in Python. Its goal is to make it quick and easy for
4+ developers to build feature-rich and user-friendly interactive command line applications. It provides a simple API which
5+ is an extension of Python's built-in cmd module. cmd2 provides a wealth of features on top of cmd to make your life easier
6+ and eliminates much of the boilerplate code which would be necessary when using cmd.
7+
8+ Extra features include:
9+ - Searchable command history (commands: "history")
10+ - Run commands from file, save to file, edit commands in file
11+ - Multi-line commands
12+ - Special-character shortcut commands (beyond cmd's "?" and "!")
13+ - Settable environment parameters
14+ - Parsing commands with `argparse` argument parsers (flags)
15+ - Redirection to file or paste buffer (clipboard) with > or >>
16+ - Easy transcript-based testing of applications (see examples/transcript_example.py)
17+ - Bash-style ``select`` available
1518
1619Note, if self.stdout is different than sys.stdout, then redirection with > and |
1720will only work if `self.poutput()` is used in place of `print`.
1821
19- - Catherine Devlin, Jan 03 2008 - catherinedevlin.blogspot.com
20-
21- Git repository on GitHub at https://github.com/python-cmd2/cmd2
22+ GitHub: https://github.com/python-cmd2/cmd2
23+ Documentation: https://cmd2.readthedocs.io/
2224"""
2325
2426# This module has many imports, quite a few of which are only
2527# infrequently utilized. To reduce the initial overhead of
2628# import this module, many of these imports are lazy-loaded
2729# i.e. we only import the module when we use it.
2830import argparse
29- import cmd
3031import contextlib
3132import copy
3233import functools
6465)
6566
6667import rich .box
67- from rich .console import Group
68+ from rich .console import Group , RenderableType
6869from rich .highlighter import ReprHighlighter
6970from rich .rule import Rule
7071from rich .style import Style , StyleType
@@ -286,7 +287,7 @@ def remove(self, command_method: CommandFunc) -> None:
286287 del self ._parsers [full_method_name ]
287288
288289
289- class Cmd ( cmd . Cmd ) :
290+ class Cmd :
290291 """An easy but powerful framework for writing line-oriented command interpreters.
291292
292293 Extends the Python Standard Library's cmd package by adding a lot of useful features
@@ -304,6 +305,8 @@ class Cmd(cmd.Cmd):
304305 # List for storing transcript test file names
305306 testfiles : ClassVar [list [str ]] = []
306307
308+ DEFAULT_PROMPT = '(Cmd) '
309+
307310 def __init__ (
308311 self ,
309312 completekey : str = 'tab' ,
@@ -326,6 +329,7 @@ def __init__(
326329 auto_load_commands : bool = False ,
327330 allow_clipboard : bool = True ,
328331 suggest_similar_command : bool = False ,
332+ intro : RenderableType = '' ,
329333 ) -> None :
330334 """Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
331335
@@ -376,6 +380,7 @@ def __init__(
376380 :param suggest_similar_command: If ``True``, ``cmd2`` will attempt to suggest the most
377381 similar command when the user types a command that does
378382 not exist. Default: ``False``.
383+ "param intro: Intro banner to print when starting the application.
379384 """
380385 # Check if py or ipy need to be disabled in this instance
381386 if not include_py :
@@ -384,11 +389,28 @@ def __init__(
384389 setattr (self , 'do_ipy' , None ) # noqa: B010
385390
386391 # initialize plugin system
387- # needs to be done before we call __init__(0)
392+ # needs to be done before we most of the other stuff below
388393 self ._initialize_plugin_system ()
389394
390- # Call super class constructor
391- super ().__init__ (completekey = completekey , stdin = stdin , stdout = stdout )
395+ # Configure a few defaults
396+ self .prompt = Cmd .DEFAULT_PROMPT
397+ self .intro = intro
398+ self .use_rawinput = True
399+
400+ # What to use for standard input
401+ if stdin is not None :
402+ self .stdin = stdin
403+ else :
404+ self .stdin = sys .stdin
405+
406+ # What to use for standard output
407+ if stdout is not None :
408+ self .stdout = stdout
409+ else :
410+ self .stdout = sys .stdout
411+
412+ # Key used for tab completion
413+ self .completekey = completekey
392414
393415 # Attributes which should NOT be dynamically settable via the set command at runtime
394416 self .default_to_shell = False # Attempt to run unrecognized commands as shell commands
@@ -2693,10 +2715,6 @@ def postloop(self) -> None:
26932715 def parseline (self , line : str ) -> tuple [str , str , str ]:
26942716 """Parse the line into a command name and a string containing the arguments.
26952717
2696- NOTE: This is an override of a parent class method. It is only used by other parent class methods.
2697-
2698- Different from the parent class method, this ignores self.identchars.
2699-
27002718 :param line: line read by readline
27012719 :return: tuple containing (command, args, line)
27022720 """
@@ -3086,7 +3104,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
30863104
30873105 # Initialize the redirection saved state
30883106 redir_saved_state = utils .RedirectionSavedState (
3089- cast ( TextIO , self .stdout ) , stdouts_match , self ._cur_pipe_proc_reader , self ._redirecting
3107+ self .stdout , stdouts_match , self ._cur_pipe_proc_reader , self ._redirecting
30903108 )
30913109
30923110 # The ProcReader for this command
@@ -3141,7 +3159,7 @@ def _redirect_output(self, statement: Statement) -> utils.RedirectionSavedState:
31413159 new_stdout .close ()
31423160 raise RedirectionError (f'Pipe process exited with code { proc .returncode } before command could run' )
31433161 redir_saved_state .redirecting = True
3144- cmd_pipe_proc_reader = utils .ProcReader (proc , cast ( TextIO , self .stdout ) , sys .stderr )
3162+ cmd_pipe_proc_reader = utils .ProcReader (proc , self .stdout , sys .stderr )
31453163
31463164 self .stdout = new_stdout
31473165 if stdouts_match :
@@ -3293,6 +3311,15 @@ def default(self, statement: Statement) -> bool | None: # type: ignore[override
32933311 self .perror (err_msg , style = None )
32943312 return None
32953313
3314+ def completedefault (self , * _ignored : list [str ]) -> list [str ]:
3315+ """Call to complete an input line when no command-specific complete_*() method is available.
3316+
3317+ This method is only called for non-argparse-based commands.
3318+
3319+ By default, it returns an empty list.
3320+ """
3321+ return []
3322+
32963323 def _suggest_similar_command (self , command : str ) -> str | None :
32973324 return suggest_similar (command , self .get_visible_commands ())
32983325
@@ -4131,10 +4158,6 @@ def _build_help_parser(cls) -> Cmd2ArgumentParser:
41314158 )
41324159 return help_parser
41334160
4134- # Get rid of cmd's complete_help() functions so ArgparseCompleter will complete the help command
4135- if getattr (cmd .Cmd , 'complete_help' , None ) is not None :
4136- delattr (cmd .Cmd , 'complete_help' )
4137-
41384161 @with_argparser (_build_help_parser )
41394162 def do_help (self , args : argparse .Namespace ) -> None :
41404163 """List available commands or provide detailed help for a specific command."""
@@ -4640,7 +4663,7 @@ def do_shell(self, args: argparse.Namespace) -> None:
46404663 ** kwargs ,
46414664 )
46424665
4643- proc_reader = utils .ProcReader (proc , cast ( TextIO , self .stdout ) , sys .stderr )
4666+ proc_reader = utils .ProcReader (proc , self .stdout , sys .stderr )
46444667 proc_reader .wait ()
46454668
46464669 # Save the return code of the application for use in a pyscript
@@ -5359,7 +5382,7 @@ def _generate_transcript(
53595382 transcript += command
53605383
53615384 # Use a StdSim object to capture output
5362- stdsim = utils .StdSim (cast ( TextIO , self .stdout ) )
5385+ stdsim = utils .StdSim (self .stdout )
53635386 self .stdout = cast (TextIO , stdsim )
53645387
53655388 # then run the command and let the output go into our buffer
@@ -5385,7 +5408,7 @@ def _generate_transcript(
53855408 with self .sigint_protection :
53865409 # Restore altered attributes to their original state
53875410 self .echo = saved_echo
5388- self .stdout = cast ( TextIO , saved_stdout )
5411+ self .stdout = saved_stdout
53895412
53905413 # Check if all commands ran
53915414 if commands_run < len (history ):
@@ -5880,7 +5903,7 @@ def _report_disabled_command_usage(self, *_args: Any, message_to_print: str, **_
58805903 """
58815904 self .perror (message_to_print , style = None )
58825905
5883- def cmdloop (self , intro : str | None = None ) -> int : # type: ignore[override]
5906+ def cmdloop (self , intro : str = '' ) -> int : # type: ignore[override]
58845907 """Deal with extra features provided by cmd2, this is an outer wrapper around _cmdloop().
58855908
58865909 _cmdloop() provides the main loop equivalent to cmd.cmdloop(). This is a wrapper around that which deals with
@@ -5922,11 +5945,11 @@ def cmdloop(self, intro: str | None = None) -> int: # type: ignore[override]
59225945 self ._run_transcript_tests ([os .path .expanduser (tf ) for tf in self ._transcript_files ])
59235946 else :
59245947 # If an intro was supplied in the method call, allow it to override the default
5925- if intro is not None :
5948+ if intro :
59265949 self .intro = intro
59275950
59285951 # Print the intro, if there is one, right after the preloop
5929- if self .intro is not None :
5952+ if self .intro :
59305953 self .poutput (self .intro )
59315954
59325955 # And then call _cmdloop() to enter the main loop
0 commit comments