49
49
from .argparse_completer import AutoCompleter , ACArgumentParser , ACTION_ARG_CHOICES
50
50
from .clipboard import can_clip , get_paste_buffer , write_to_paste_buffer
51
51
from .parsing import StatementParser , Statement , Macro , MacroArg
52
+ from .history import History , HistoryItem
52
53
53
54
# Set up readline
54
55
from .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):
290
291
pass
291
292
292
293
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
-
317
294
class Cmd (cmd .Cmd ):
318
295
"""An easy but powerful framework for writing line-oriented command interpreters.
319
296
@@ -325,7 +302,7 @@ class Cmd(cmd.Cmd):
325
302
# Attributes used to configure the StatementParser, best not to change these at runtime
326
303
multiline_commands = []
327
304
shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
328
- terminators = [';' ]
305
+ terminators = [constants . MULTILINE_TERMINATOR ]
329
306
330
307
# Attributes which are NOT dynamically settable at runtime
331
308
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:
2007
1984
if func :
2008
1985
# Since we have a valid command store it in the history
2009
1986
if statement .command not in self .exclude_from_history :
2010
- self .history .append (statement . raw )
1987
+ self .history .append (statement )
2011
1988
2012
1989
stop = func (statement )
2013
1990
@@ -2070,7 +2047,7 @@ def default(self, statement: Statement) -> Optional[bool]:
2070
2047
"""
2071
2048
if self .default_to_shell :
2072
2049
if 'shell' not in self .exclude_from_history :
2073
- self .history .append (statement . raw )
2050
+ self .history .append (statement )
2074
2051
2075
2052
return self .do_shell (statement .command_and_args )
2076
2053
else :
@@ -3188,18 +3165,27 @@ def load_ipy(app):
3188
3165
load_ipy (bridge )
3189
3166
3190
3167
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' ,
3194
3171
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' ),
3198
3174
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 ' ),
3201
3177
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
+
3203
3189
history_arg_help = ("empty all history items\n "
3204
3190
"a one history item by number\n "
3205
3191
"a..b, a:b, a:, ..b items by indices (inclusive)\n "
@@ -3211,6 +3197,19 @@ def load_ipy(app):
3211
3197
def do_history (self , args : argparse .Namespace ) -> None :
3212
3198
"""View, run, edit, save, or clear previously entered commands"""
3213
3199
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
+
3214
3213
if args .clear :
3215
3214
# Clear command and readline history
3216
3215
self .history .clear ()
@@ -3257,7 +3256,10 @@ def do_history(self, args: argparse.Namespace) -> None:
3257
3256
fd , fname = tempfile .mkstemp (suffix = '.txt' , text = True )
3258
3257
with os .fdopen (fd , 'w' ) as fobj :
3259
3258
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 ))
3261
3263
try :
3262
3264
self .do_edit (fname )
3263
3265
self .do_load (fname )
@@ -3269,7 +3271,10 @@ def do_history(self, args: argparse.Namespace) -> None:
3269
3271
try :
3270
3272
with open (os .path .expanduser (args .output_file ), 'w' ) as fobj :
3271
3273
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 ))
3273
3278
plural = 's' if len (history ) > 1 else ''
3274
3279
self .pfeedback ('{} command{} saved to {}' .format (len (history ), plural , args .output_file ))
3275
3280
except Exception as e :
@@ -3279,10 +3284,7 @@ def do_history(self, args: argparse.Namespace) -> None:
3279
3284
else :
3280
3285
# Display the history items retrieved
3281
3286
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 ))
3286
3288
3287
3289
def _generate_transcript (self , history : List [HistoryItem ], transcript_file : str ) -> None :
3288
3290
"""Generate a transcript file from a given history of commands."""
@@ -3807,113 +3809,6 @@ def register_cmdfinalization_hook(self, func: Callable[[plugin.CommandFinalizati
3807
3809
self ._cmdfinalization_hooks .append (func )
3808
3810
3809
3811
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
-
3917
3812
class Statekeeper (object ):
3918
3813
"""Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
3919
3814
def __init__ (self , obj : Any , attribs : Iterable ) -> None :
0 commit comments