4949from .argparse_completer import AutoCompleter , ACArgumentParser , ACTION_ARG_CHOICES
5050from .clipboard import can_clip , get_paste_buffer , write_to_paste_buffer
5151from .parsing import StatementParser , Statement , Macro , MacroArg
52+ from .history import History , HistoryItem
5253
5354# Set up readline
5455from .rl_utils import rl_type , RlType , rl_get_point , rl_set_prompt , vt100_support , rl_make_safe_prompt
@@ -295,30 +296,6 @@ class EmptyStatement(Exception):
295296 pass
296297
297298
298- class HistoryItem (str ):
299- """Class used to represent an item in the History list.
300-
301- Thin wrapper around str class which adds a custom format for printing. It
302- also keeps track of its index in the list as well as a lowercase
303- representation of itself for convenience/efficiency.
304-
305- """
306- listformat = '-------------------------[{}]\n {}\n '
307-
308- # noinspection PyUnusedLocal
309- def __init__ (self , instr : str ) -> None :
310- str .__init__ (self )
311- self .lowercase = self .lower ()
312- self .idx = None
313-
314- def pr (self ) -> str :
315- """Represent a HistoryItem in a pretty fashion suitable for printing.
316-
317- :return: pretty print string version of a HistoryItem
318- """
319- return self .listformat .format (self .idx , str (self ).rstrip ())
320-
321-
322299class Cmd (cmd .Cmd ):
323300 """An easy but powerful framework for writing line-oriented command interpreters.
324301
@@ -330,7 +307,7 @@ class Cmd(cmd.Cmd):
330307 # Attributes used to configure the StatementParser, best not to change these at runtime
331308 multiline_commands = []
332309 shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
333- terminators = [';' ]
310+ terminators = [constants . MULTILINE_TERMINATOR ]
334311
335312 # Attributes which are NOT dynamically settable at runtime
336313 allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
@@ -2012,7 +1989,7 @@ def onecmd(self, statement: Union[Statement, str]) -> bool:
20121989 if func :
20131990 # Since we have a valid command store it in the history
20141991 if statement .command not in self .exclude_from_history :
2015- self .history .append (statement . raw )
1992+ self .history .append (statement )
20161993
20171994 stop = func (statement )
20181995
@@ -2075,7 +2052,7 @@ def default(self, statement: Statement) -> Optional[bool]:
20752052 """
20762053 if self .default_to_shell :
20772054 if 'shell' not in self .exclude_from_history :
2078- self .history .append (statement . raw )
2055+ self .history .append (statement )
20792056
20802057 return self .do_shell (statement .command_and_args )
20812058 else :
@@ -3193,18 +3170,27 @@ def load_ipy(app):
31933170 load_ipy (bridge )
31943171
31953172 history_parser = ACArgumentParser ()
3196- history_parser_group = history_parser .add_mutually_exclusive_group ()
3197- history_parser_group .add_argument ('-r' , '--run' , action = 'store_true' , help = 'run selected history items' )
3198- history_parser_group .add_argument ('-e' , '--edit' , action = 'store_true' ,
3173+ history_action_group = history_parser .add_mutually_exclusive_group ()
3174+ history_action_group .add_argument ('-r' , '--run' , action = 'store_true' , help = 'run selected history items' )
3175+ history_action_group .add_argument ('-e' , '--edit' , action = 'store_true' ,
31993176 help = 'edit and then run selected history items' )
3200- history_parser_group .add_argument ('-s' , '--script' , action = 'store_true' , help = 'output commands in script format' )
3201- setattr (history_parser_group .add_argument ('-o' , '--output-file' , metavar = 'FILE' ,
3202- help = 'output commands to a script file' ),
3177+ setattr (history_action_group .add_argument ('-o' , '--output-file' , metavar = 'FILE' ,
3178+ help = 'output commands to a script file, implies -s' ),
32033179 ACTION_ARG_CHOICES , ('path_complete' ,))
3204- setattr (history_parser_group .add_argument ('-t' , '--transcript' ,
3205- help = 'output commands and results to a transcript file' ),
3180+ setattr (history_action_group .add_argument ('-t' , '--transcript' ,
3181+ help = 'output commands and results to a transcript file, implies -s ' ),
32063182 ACTION_ARG_CHOICES , ('path_complete' ,))
3207- history_parser_group .add_argument ('-c' , '--clear' , action = "store_true" , help = 'clear all history' )
3183+ history_action_group .add_argument ('-c' , '--clear' , action = 'store_true' , help = 'clear all history' )
3184+
3185+ history_format_group = history_parser .add_argument_group (title = 'formatting' )
3186+ history_script_help = 'output commands in script format, i.e. without command numbers'
3187+ history_format_group .add_argument ('-s' , '--script' , action = 'store_true' , help = history_script_help )
3188+ history_expand_help = 'output expanded commands instead of entered command'
3189+ history_format_group .add_argument ('-x' , '--expanded' , action = 'store_true' , help = history_expand_help )
3190+ history_format_group .add_argument ('-v' , '--verbose' , action = 'store_true' ,
3191+ help = 'display history and include expanded commands if they'
3192+ ' differ from the typed command' )
3193+
32083194 history_arg_help = ("empty all history items\n "
32093195 "a one history item by number\n "
32103196 "a..b, a:b, a:, ..b items by indices (inclusive)\n "
@@ -3216,6 +3202,19 @@ def load_ipy(app):
32163202 def do_history (self , args : argparse .Namespace ) -> None :
32173203 """View, run, edit, save, or clear previously entered commands"""
32183204
3205+ # -v must be used alone with no other options
3206+ if args .verbose :
3207+ if args .clear or args .edit or args .output_file or args .run or args .transcript or args .expanded or args .script :
3208+ self .poutput ("-v can not be used with any other options" )
3209+ self .poutput (self .history_parser .format_usage ())
3210+ return
3211+
3212+ # -s and -x can only be used if none of these options are present: [-c -r -e -o -t]
3213+ if (args .script or args .expanded ) and (args .clear or args .edit or args .output_file or args .run or args .transcript ):
3214+ self .poutput ("-s and -x can not be used with -c, -r, -e, -o, or -t" )
3215+ self .poutput (self .history_parser .format_usage ())
3216+ return
3217+
32193218 if args .clear :
32203219 # Clear command and readline history
32213220 self .history .clear ()
@@ -3262,7 +3261,10 @@ def do_history(self, args: argparse.Namespace) -> None:
32623261 fd , fname = tempfile .mkstemp (suffix = '.txt' , text = True )
32633262 with os .fdopen (fd , 'w' ) as fobj :
32643263 for command in history :
3265- fobj .write ('{}\n ' .format (command ))
3264+ if command .statement .multiline_command :
3265+ fobj .write ('{}\n ' .format (command .expanded .rstrip ()))
3266+ else :
3267+ fobj .write ('{}\n ' .format (command ))
32663268 try :
32673269 self .do_edit (fname )
32683270 self .do_load (fname )
@@ -3274,7 +3276,10 @@ def do_history(self, args: argparse.Namespace) -> None:
32743276 try :
32753277 with open (os .path .expanduser (args .output_file ), 'w' ) as fobj :
32763278 for command in history :
3277- fobj .write ('{}\n ' .format (command ))
3279+ if command .statement .multiline_command :
3280+ fobj .write ('{}\n ' .format (command .expanded .rstrip ()))
3281+ else :
3282+ fobj .write ('{}\n ' .format (command ))
32783283 plural = 's' if len (history ) > 1 else ''
32793284 self .pfeedback ('{} command{} saved to {}' .format (len (history ), plural , args .output_file ))
32803285 except Exception as e :
@@ -3284,10 +3289,7 @@ def do_history(self, args: argparse.Namespace) -> None:
32843289 else :
32853290 # Display the history items retrieved
32863291 for hi in history :
3287- if args .script :
3288- self .poutput (hi )
3289- else :
3290- self .poutput (hi .pr ())
3292+ self .poutput (hi .pr (script = args .script , expanded = args .expanded , verbose = args .verbose ))
32913293
32923294 def _generate_transcript (self , history : List [HistoryItem ], transcript_file : str ) -> None :
32933295 """Generate a transcript file from a given history of commands."""
@@ -3812,113 +3814,6 @@ def register_cmdfinalization_hook(self, func: Callable[[plugin.CommandFinalizati
38123814 self ._cmdfinalization_hooks .append (func )
38133815
38143816
3815- class History (list ):
3816- """ A list of HistoryItems that knows how to respond to user requests. """
3817-
3818- # noinspection PyMethodMayBeStatic
3819- def _zero_based_index (self , onebased : int ) -> int :
3820- """Convert a one-based index to a zero-based index."""
3821- result = onebased
3822- if result > 0 :
3823- result -= 1
3824- return result
3825-
3826- def _to_index (self , raw : str ) -> Optional [int ]:
3827- if raw :
3828- result = self ._zero_based_index (int (raw ))
3829- else :
3830- result = None
3831- return result
3832-
3833- spanpattern = re .compile (r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$' )
3834-
3835- def span (self , raw : str ) -> List [HistoryItem ]:
3836- """Parses the input string search for a span pattern and if if found, returns a slice from the History list.
3837-
3838- :param raw: string potentially containing a span of the forms a..b, a:b, a:, ..b
3839- :return: slice from the History list
3840- """
3841- if raw .lower () in ('*' , '-' , 'all' ):
3842- raw = ':'
3843- results = self .spanpattern .search (raw )
3844- if not results :
3845- raise IndexError
3846- if not results .group ('separator' ):
3847- return [self [self ._to_index (results .group ('start' ))]]
3848- start = self ._to_index (results .group ('start' )) or 0 # Ensure start is not None
3849- end = self ._to_index (results .group ('end' ))
3850- reverse = False
3851- if end is not None :
3852- if end < start :
3853- (start , end ) = (end , start )
3854- reverse = True
3855- end += 1
3856- result = self [start :end ]
3857- if reverse :
3858- result .reverse ()
3859- return result
3860-
3861- rangePattern = re .compile (r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$' )
3862-
3863- def append (self , new : str ) -> None :
3864- """Append a HistoryItem to end of the History list
3865-
3866- :param new: command line to convert to HistoryItem and add to the end of the History list
3867- """
3868- new = HistoryItem (new )
3869- list .append (self , new )
3870- new .idx = len (self )
3871-
3872- def get (self , getme : Optional [Union [int , str ]] = None ) -> List [HistoryItem ]:
3873- """Get an item or items from the History list using 1-based indexing.
3874-
3875- :param getme: optional item(s) to get (either an integer index or string to search for)
3876- :return: list of HistoryItems matching the retrieval criteria
3877- """
3878- if not getme :
3879- return self
3880- try :
3881- getme = int (getme )
3882- if getme < 0 :
3883- return self [:(- 1 * getme )]
3884- else :
3885- return [self [getme - 1 ]]
3886- except IndexError :
3887- return []
3888- except ValueError :
3889- range_result = self .rangePattern .search (getme )
3890- if range_result :
3891- start = range_result .group ('start' ) or None
3892- end = range_result .group ('start' ) or None
3893- if start :
3894- start = int (start ) - 1
3895- if end :
3896- end = int (end )
3897- return self [start :end ]
3898-
3899- getme = getme .strip ()
3900-
3901- if getme .startswith (r'/' ) and getme .endswith (r'/' ):
3902- finder = re .compile (getme [1 :- 1 ], re .DOTALL | re .MULTILINE | re .IGNORECASE )
3903-
3904- def isin (hi ):
3905- """Listcomp filter function for doing a regular expression search of History.
3906-
3907- :param hi: HistoryItem
3908- :return: bool - True if search matches
3909- """
3910- return finder .search (hi )
3911- else :
3912- def isin (hi ):
3913- """Listcomp filter function for doing a case-insensitive string search of History.
3914-
3915- :param hi: HistoryItem
3916- :return: bool - True if search matches
3917- """
3918- return utils .norm_fold (getme ) in utils .norm_fold (hi )
3919- return [itm for itm in self if isin (itm )]
3920-
3921-
39223817class Statekeeper (object ):
39233818 """Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
39243819 def __init__ (self , obj : Any , attribs : Iterable ) -> None :
0 commit comments