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
@@ -295,30 +296,6 @@ class EmptyStatement(Exception):
295
296
pass
296
297
297
298
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
-
322
299
class Cmd (cmd .Cmd ):
323
300
"""An easy but powerful framework for writing line-oriented command interpreters.
324
301
@@ -330,7 +307,7 @@ class Cmd(cmd.Cmd):
330
307
# Attributes used to configure the StatementParser, best not to change these at runtime
331
308
multiline_commands = []
332
309
shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
333
- terminators = [';' ]
310
+ terminators = [constants . MULTILINE_TERMINATOR ]
334
311
335
312
# Attributes which are NOT dynamically settable at runtime
336
313
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:
2012
1989
if func :
2013
1990
# Since we have a valid command store it in the history
2014
1991
if statement .command not in self .exclude_from_history :
2015
- self .history .append (statement . raw )
1992
+ self .history .append (statement )
2016
1993
2017
1994
stop = func (statement )
2018
1995
@@ -2075,7 +2052,7 @@ def default(self, statement: Statement) -> Optional[bool]:
2075
2052
"""
2076
2053
if self .default_to_shell :
2077
2054
if 'shell' not in self .exclude_from_history :
2078
- self .history .append (statement . raw )
2055
+ self .history .append (statement )
2079
2056
2080
2057
return self .do_shell (statement .command_and_args )
2081
2058
else :
@@ -3193,18 +3170,27 @@ def load_ipy(app):
3193
3170
load_ipy (bridge )
3194
3171
3195
3172
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' ,
3199
3176
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' ),
3203
3179
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 ' ),
3206
3182
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
+
3208
3194
history_arg_help = ("empty all history items\n "
3209
3195
"a one history item by number\n "
3210
3196
"a..b, a:b, a:, ..b items by indices (inclusive)\n "
@@ -3216,6 +3202,19 @@ def load_ipy(app):
3216
3202
def do_history (self , args : argparse .Namespace ) -> None :
3217
3203
"""View, run, edit, save, or clear previously entered commands"""
3218
3204
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
+
3219
3218
if args .clear :
3220
3219
# Clear command and readline history
3221
3220
self .history .clear ()
@@ -3262,7 +3261,10 @@ def do_history(self, args: argparse.Namespace) -> None:
3262
3261
fd , fname = tempfile .mkstemp (suffix = '.txt' , text = True )
3263
3262
with os .fdopen (fd , 'w' ) as fobj :
3264
3263
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 ))
3266
3268
try :
3267
3269
self .do_edit (fname )
3268
3270
self .do_load (fname )
@@ -3274,7 +3276,10 @@ def do_history(self, args: argparse.Namespace) -> None:
3274
3276
try :
3275
3277
with open (os .path .expanduser (args .output_file ), 'w' ) as fobj :
3276
3278
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 ))
3278
3283
plural = 's' if len (history ) > 1 else ''
3279
3284
self .pfeedback ('{} command{} saved to {}' .format (len (history ), plural , args .output_file ))
3280
3285
except Exception as e :
@@ -3284,10 +3289,7 @@ def do_history(self, args: argparse.Namespace) -> None:
3284
3289
else :
3285
3290
# Display the history items retrieved
3286
3291
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 ))
3291
3293
3292
3294
def _generate_transcript (self , history : List [HistoryItem ], transcript_file : str ) -> None :
3293
3295
"""Generate a transcript file from a given history of commands."""
@@ -3812,113 +3814,6 @@ def register_cmdfinalization_hook(self, func: Callable[[plugin.CommandFinalizati
3812
3814
self ._cmdfinalization_hooks .append (func )
3813
3815
3814
3816
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
-
3922
3817
class Statekeeper (object ):
3923
3818
"""Class used to save and restore state during load and py commands as well as when redirecting output or pipes."""
3924
3819
def __init__ (self , obj : Any , attribs : Iterable ) -> None :
0 commit comments