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
@@ -290,30 +291,6 @@ class EmptyStatement(Exception):
290291 pass
291292
292293
293- class HistoryItem (str ):
294- """Class used to represent an item in the History list.
295-
296- Thin wrapper around str class which adds a custom format for printing. It
297- also keeps track of its index in the list as well as a lowercase
298- representation of itself for convenience/efficiency.
299-
300- """
301- listformat = '-------------------------[{}]\n {}\n '
302-
303- # noinspection PyUnusedLocal
304- def __init__ (self , instr : str ) -> None :
305- str .__init__ (self )
306- self .lowercase = self .lower ()
307- self .idx = None
308-
309- def pr (self ) -> str :
310- """Represent a HistoryItem in a pretty fashion suitable for printing.
311-
312- :return: pretty print string version of a HistoryItem
313- """
314- return self .listformat .format (self .idx , str (self ).rstrip ())
315-
316-
317294class Cmd (cmd .Cmd ):
318295 """An easy but powerful framework for writing line-oriented command interpreters.
319296
@@ -325,7 +302,7 @@ class Cmd(cmd.Cmd):
325302 # Attributes used to configure the StatementParser, best not to change these at runtime
326303 multiline_commands = []
327304 shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
328- terminators = [';' ]
305+ terminators = [constants . MULTILINE_TERMINATOR ]
329306
330307 # Attributes which are NOT dynamically settable at runtime
331308 allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
@@ -2007,7 +1984,7 @@ def onecmd(self, statement: Union[Statement, str]) -> bool:
20071984 if func :
20081985 # Since we have a valid command store it in the history
20091986 if statement .command not in self .exclude_from_history :
2010- self .history .append (statement . raw )
1987+ self .history .append (statement )
20111988
20121989 stop = func (statement )
20131990
@@ -2070,7 +2047,7 @@ def default(self, statement: Statement) -> Optional[bool]:
20702047 """
20712048 if self .default_to_shell :
20722049 if 'shell' not in self .exclude_from_history :
2073- self .history .append (statement . raw )
2050+ self .history .append (statement )
20742051
20752052 return self .do_shell (statement .command_and_args )
20762053 else :
@@ -3188,18 +3165,27 @@ def load_ipy(app):
31883165 load_ipy (bridge )
31893166
31903167 history_parser = ACArgumentParser ()
3191- history_parser_group = history_parser .add_mutually_exclusive_group ()
3192- history_parser_group .add_argument ('-r' , '--run' , action = 'store_true' , help = 'run selected history items' )
3193- history_parser_group .add_argument ('-e' , '--edit' , action = 'store_true' ,
3168+ history_action_group = history_parser .add_mutually_exclusive_group ()
3169+ history_action_group .add_argument ('-r' , '--run' , action = 'store_true' , help = 'run selected history items' )
3170+ history_action_group .add_argument ('-e' , '--edit' , action = 'store_true' ,
31943171 help = 'edit and then run selected history items' )
3195- history_parser_group .add_argument ('-s' , '--script' , action = 'store_true' , help = 'output commands in script format' )
3196- setattr (history_parser_group .add_argument ('-o' , '--output-file' , metavar = 'FILE' ,
3197- help = 'output commands to a script file' ),
3172+ setattr (history_action_group .add_argument ('-o' , '--output-file' , metavar = 'FILE' ,
3173+ help = 'output commands to a script file, implies -s' ),
31983174 ACTION_ARG_CHOICES , ('path_complete' ,))
3199- setattr (history_parser_group .add_argument ('-t' , '--transcript' ,
3200- help = 'output commands and results to a transcript file' ),
3175+ setattr (history_action_group .add_argument ('-t' , '--transcript' ,
3176+ help = 'output commands and results to a transcript file, implies -s ' ),
32013177 ACTION_ARG_CHOICES , ('path_complete' ,))
3202- history_parser_group .add_argument ('-c' , '--clear' , action = "store_true" , help = 'clear all history' )
3178+ history_action_group .add_argument ('-c' , '--clear' , action = 'store_true' , help = 'clear all history' )
3179+
3180+ history_format_group = history_parser .add_argument_group (title = 'formatting' )
3181+ history_script_help = 'output commands in script format, i.e. without command numbers'
3182+ history_format_group .add_argument ('-s' , '--script' , action = 'store_true' , help = history_script_help )
3183+ history_expand_help = 'output expanded commands instead of entered command'
3184+ history_format_group .add_argument ('-x' , '--expanded' , action = 'store_true' , help = history_expand_help )
3185+ history_format_group .add_argument ('-v' , '--verbose' , action = 'store_true' ,
3186+ help = 'display history and include expanded commands if they'
3187+ ' differ from the typed command' )
3188+
32033189 history_arg_help = ("empty all history items\n "
32043190 "a one history item by number\n "
32053191 "a..b, a:b, a:, ..b items by indices (inclusive)\n "
@@ -3211,6 +3197,19 @@ def load_ipy(app):
32113197 def do_history (self , args : argparse .Namespace ) -> None :
32123198 """View, run, edit, save, or clear previously entered commands"""
32133199
3200+ # -v must be used alone with no other options
3201+ if args .verbose :
3202+ if args .clear or args .edit or args .output_file or args .run or args .transcript or args .expanded or args .script :
3203+ self .poutput ("-v can not be used with any other options" )
3204+ self .poutput (self .history_parser .format_usage ())
3205+ return
3206+
3207+ # -s and -x can only be used if none of these options are present: [-c -r -e -o -t]
3208+ if (args .script or args .expanded ) and (args .clear or args .edit or args .output_file or args .run or args .transcript ):
3209+ self .poutput ("-s and -x can not be used with -c, -r, -e, -o, or -t" )
3210+ self .poutput (self .history_parser .format_usage ())
3211+ return
3212+
32143213 if args .clear :
32153214 # Clear command and readline history
32163215 self .history .clear ()
@@ -3257,7 +3256,10 @@ def do_history(self, args: argparse.Namespace) -> None:
32573256 fd , fname = tempfile .mkstemp (suffix = '.txt' , text = True )
32583257 with os .fdopen (fd , 'w' ) as fobj :
32593258 for command in history :
3260- fobj .write ('{}\n ' .format (command ))
3259+ if command .statement .multiline_command :
3260+ fobj .write ('{}\n ' .format (command .expanded .rstrip ()))
3261+ else :
3262+ fobj .write ('{}\n ' .format (command ))
32613263 try :
32623264 self .do_edit (fname )
32633265 self .do_load (fname )
@@ -3269,7 +3271,10 @@ def do_history(self, args: argparse.Namespace) -> None:
32693271 try :
32703272 with open (os .path .expanduser (args .output_file ), 'w' ) as fobj :
32713273 for command in history :
3272- fobj .write ('{}\n ' .format (command ))
3274+ if command .statement .multiline_command :
3275+ fobj .write ('{}\n ' .format (command .expanded .rstrip ()))
3276+ else :
3277+ fobj .write ('{}\n ' .format (command ))
32733278 plural = 's' if len (history ) > 1 else ''
32743279 self .pfeedback ('{} command{} saved to {}' .format (len (history ), plural , args .output_file ))
32753280 except Exception as e :
@@ -3279,10 +3284,7 @@ def do_history(self, args: argparse.Namespace) -> None:
32793284 else :
32803285 # Display the history items retrieved
32813286 for hi in history :
3282- if args .script :
3283- self .poutput (hi )
3284- else :
3285- self .poutput (hi .pr ())
3287+ self .poutput (hi .pr (script = args .script , expanded = args .expanded , verbose = args .verbose ))
32863288
32873289 def _generate_transcript (self , history : List [HistoryItem ], transcript_file : str ) -> None :
32883290 """Generate a transcript file from a given history of commands."""
@@ -3807,113 +3809,6 @@ def register_cmdfinalization_hook(self, func: Callable[[plugin.CommandFinalizati
38073809 self ._cmdfinalization_hooks .append (func )
38083810
38093811
3810- class History (list ):
3811- """ A list of HistoryItems that knows how to respond to user requests. """
3812-
3813- # noinspection PyMethodMayBeStatic
3814- def _zero_based_index (self , onebased : int ) -> int :
3815- """Convert a one-based index to a zero-based index."""
3816- result = onebased
3817- if result > 0 :
3818- result -= 1
3819- return result
3820-
3821- def _to_index (self , raw : str ) -> Optional [int ]:
3822- if raw :
3823- result = self ._zero_based_index (int (raw ))
3824- else :
3825- result = None
3826- return result
3827-
3828- spanpattern = re .compile (r'^\s*(?P<start>-?\d+)?\s*(?P<separator>:|(\.{2,}))?\s*(?P<end>-?\d+)?\s*$' )
3829-
3830- def span (self , raw : str ) -> List [HistoryItem ]:
3831- """Parses the input string search for a span pattern and if if found, returns a slice from the History list.
3832-
3833- :param raw: string potentially containing a span of the forms a..b, a:b, a:, ..b
3834- :return: slice from the History list
3835- """
3836- if raw .lower () in ('*' , '-' , 'all' ):
3837- raw = ':'
3838- results = self .spanpattern .search (raw )
3839- if not results :
3840- raise IndexError
3841- if not results .group ('separator' ):
3842- return [self [self ._to_index (results .group ('start' ))]]
3843- start = self ._to_index (results .group ('start' )) or 0 # Ensure start is not None
3844- end = self ._to_index (results .group ('end' ))
3845- reverse = False
3846- if end is not None :
3847- if end < start :
3848- (start , end ) = (end , start )
3849- reverse = True
3850- end += 1
3851- result = self [start :end ]
3852- if reverse :
3853- result .reverse ()
3854- return result
3855-
3856- rangePattern = re .compile (r'^\s*(?P<start>[\d]+)?\s*-\s*(?P<end>[\d]+)?\s*$' )
3857-
3858- def append (self , new : str ) -> None :
3859- """Append a HistoryItem to end of the History list
3860-
3861- :param new: command line to convert to HistoryItem and add to the end of the History list
3862- """
3863- new = HistoryItem (new )
3864- list .append (self , new )
3865- new .idx = len (self )
3866-
3867- def get (self , getme : Optional [Union [int , str ]] = None ) -> List [HistoryItem ]:
3868- """Get an item or items from the History list using 1-based indexing.
3869-
3870- :param getme: optional item(s) to get (either an integer index or string to search for)
3871- :return: list of HistoryItems matching the retrieval criteria
3872- """
3873- if not getme :
3874- return self
3875- try :
3876- getme = int (getme )
3877- if getme < 0 :
3878- return self [:(- 1 * getme )]
3879- else :
3880- return [self [getme - 1 ]]
3881- except IndexError :
3882- return []
3883- except ValueError :
3884- range_result = self .rangePattern .search (getme )
3885- if range_result :
3886- start = range_result .group ('start' ) or None
3887- end = range_result .group ('start' ) or None
3888- if start :
3889- start = int (start ) - 1
3890- if end :
3891- end = int (end )
3892- return self [start :end ]
3893-
3894- getme = getme .strip ()
3895-
3896- if getme .startswith (r'/' ) and getme .endswith (r'/' ):
3897- finder = re .compile (getme [1 :- 1 ], re .DOTALL | re .MULTILINE | re .IGNORECASE )
3898-
3899- def isin (hi ):
3900- """Listcomp filter function for doing a regular expression search of History.
3901-
3902- :param hi: HistoryItem
3903- :return: bool - True if search matches
3904- """
3905- return finder .search (hi )
3906- else :
3907- def isin (hi ):
3908- """Listcomp filter function for doing a case-insensitive string search of History.
3909-
3910- :param hi: HistoryItem
3911- :return: bool - True if search matches
3912- """
3913- return utils .norm_fold (getme ) in utils .norm_fold (hi )
3914- return [itm for itm in self if isin (itm )]
3915-
3916-
39173812class Statekeeper (object ):
39183813 """Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
39193814 def __init__ (self , obj : Any , attribs : Iterable ) -> None :
0 commit comments