@@ -312,267 +312,6 @@ class EmptyStatement(Exception):
312312 pass
313313
314314
315- def _pop_readline_history (clear_history : bool = True ) -> List [str ]:
316- """Returns a copy of readline's history and optionally clears it (default)"""
317- # noinspection PyArgumentList
318- history = [
319- readline .get_history_item (i )
320- for i in range (1 , 1 + readline .get_current_history_length ())
321- ]
322- if clear_history :
323- readline .clear_history ()
324- return history
325-
326-
327- def _push_readline_history (history , clear_history = True ):
328- """Restores readline's history and optionally clears it first (default)"""
329- if clear_history :
330- readline .clear_history ()
331- for line in history :
332- readline .add_history (line )
333-
334-
335- def _complete_from_cmd (cmd_obj , text , line , begidx , endidx ):
336- """Complete as though the user was typing inside cmd's cmdloop()"""
337- from itertools import takewhile
338- command_subcommand_params = line .split (None , 3 )
339-
340- if len (command_subcommand_params ) < (3 if text else 2 ):
341- n = len (command_subcommand_params [0 ])
342- n += sum (1 for _ in takewhile (str .isspace , line [n :]))
343- return cmd_obj .completenames (text , line [n :], begidx - n , endidx - n )
344-
345- command , subcommand = command_subcommand_params [:2 ]
346- n = len (command ) + sum (1 for _ in takewhile (str .isspace , line ))
347- cfun = getattr (cmd_obj , 'complete_' + subcommand , cmd_obj .complete )
348- return cfun (text , line [n :], begidx - n , endidx - n )
349-
350-
351- class AddSubmenu (object ):
352- """Conveniently add a submenu (Cmd-like class) to a Cmd
353-
354- e.g. given "class SubMenu(Cmd): ..." then
355-
356- @AddSubmenu(SubMenu(), 'sub')
357- class MyCmd(cmd.Cmd):
358- ....
359-
360- will have the following effects:
361- 1. 'sub' will interactively enter the cmdloop of a SubMenu instance
362- 2. 'sub cmd args' will call do_cmd(args) in a SubMenu instance
363- 3. 'sub ... [TAB]' will have the same behavior as [TAB] in a SubMenu cmdloop
364- i.e., autocompletion works the way you think it should
365- 4. 'help sub [cmd]' will print SubMenu's help (calls its do_help())
366- """
367-
368- class _Nonexistent (object ):
369- """
370- Used to mark missing attributes.
371- Disable __dict__ creation since this class does nothing
372- """
373- __slots__ = () #
374-
375- def __init__ (self ,
376- submenu ,
377- command ,
378- aliases = (),
379- reformat_prompt = "{super_prompt}>> {sub_prompt}" ,
380- shared_attributes = None ,
381- require_predefined_shares = True ,
382- create_subclass = False ,
383- preserve_shares = False ,
384- persistent_history_file = None
385- ):
386- """Set up the class decorator
387-
388- submenu (Cmd): Instance of something cmd.Cmd-like
389-
390- command (str): The command the user types to access the SubMenu instance
391-
392- aliases (iterable): More commands that will behave like "command"
393-
394- reformat_prompt (str): Format str or None to disable
395- if it's a string, it should contain one or more of:
396- {super_prompt}: The current cmd's prompt
397- {command}: The command in the current cmd with which it was called
398- {sub_prompt}: The subordinate cmd's original prompt
399- the default is "{super_prompt}{command} {sub_prompt}"
400-
401- shared_attributes (dict): dict of the form {'subordinate_attr': 'parent_attr'}
402- the attributes are copied to the submenu at the last moment; the submenu's
403- attributes are backed up before this and restored afterward
404-
405- require_predefined_shares: The shared attributes above must be independently
406- defined in the subordinate Cmd (default: True)
407-
408- create_subclass: put the modifications in a subclass rather than modifying
409- the existing class (default: False)
410- """
411- self .submenu = submenu
412- self .command = command
413- self .aliases = aliases
414- if persistent_history_file :
415- self .persistent_history_file = os .path .expanduser (persistent_history_file )
416- else :
417- self .persistent_history_file = None
418-
419- if reformat_prompt is not None and not isinstance (reformat_prompt , str ):
420- raise ValueError ("reformat_prompt should be either a format string or None" )
421- self .reformat_prompt = reformat_prompt
422-
423- self .shared_attributes = {} if shared_attributes is None else shared_attributes
424- if require_predefined_shares :
425- for attr in self .shared_attributes .keys ():
426- if not hasattr (submenu , attr ):
427- raise AttributeError ("The shared attribute '{attr}' is not defined in {cmd}. Either define {attr} "
428- "in {cmd} or set require_predefined_shares=False."
429- .format (cmd = submenu .__class__ .__name__ , attr = attr ))
430-
431- self .create_subclass = create_subclass
432- self .preserve_shares = preserve_shares
433-
434- def _get_original_attributes (self ):
435- return {
436- attr : getattr (self .submenu , attr , AddSubmenu ._Nonexistent )
437- for attr in self .shared_attributes .keys ()
438- }
439-
440- def _copy_in_shared_attrs (self , parent_cmd ):
441- for sub_attr , par_attr in self .shared_attributes .items ():
442- setattr (self .submenu , sub_attr , getattr (parent_cmd , par_attr ))
443-
444- def _copy_out_shared_attrs (self , parent_cmd , original_attributes ):
445- if self .preserve_shares :
446- for sub_attr , par_attr in self .shared_attributes .items ():
447- setattr (parent_cmd , par_attr , getattr (self .submenu , sub_attr ))
448- else :
449- for attr , value in original_attributes .items ():
450- if attr is not AddSubmenu ._Nonexistent :
451- setattr (self .submenu , attr , value )
452- else :
453- delattr (self .submenu , attr )
454-
455- def __call__ (self , cmd_obj ):
456- """Creates a subclass of Cmd wherein the given submenu can be accessed via the given command"""
457- def enter_submenu (parent_cmd , statement ):
458- """
459- This function will be bound to do_<submenu> and will change the scope of the CLI to that of the
460- submenu.
461- """
462- submenu = self .submenu
463- original_attributes = self ._get_original_attributes ()
464- history = _pop_readline_history ()
465-
466- if self .persistent_history_file :
467- try :
468- readline .read_history_file (self .persistent_history_file )
469- except FileNotFoundError :
470- pass
471-
472- try :
473- # copy over any shared attributes
474- self ._copy_in_shared_attrs (parent_cmd )
475-
476- if statement .args :
477- # Remove the menu argument and execute the command in the submenu
478- submenu .onecmd_plus_hooks (statement .args )
479- else :
480- if self .reformat_prompt is not None :
481- prompt = submenu .prompt
482- submenu .prompt = self .reformat_prompt .format (
483- super_prompt = parent_cmd .prompt ,
484- command = self .command ,
485- sub_prompt = prompt ,
486- )
487- submenu .cmdloop ()
488- if self .reformat_prompt is not None :
489- # noinspection PyUnboundLocalVariable
490- self .submenu .prompt = prompt
491- finally :
492- # copy back original attributes
493- self ._copy_out_shared_attrs (parent_cmd , original_attributes )
494-
495- # write submenu history
496- if self .persistent_history_file :
497- readline .write_history_file (self .persistent_history_file )
498- # reset main app history before exit
499- _push_readline_history (history )
500-
501- def complete_submenu (_self , text , line , begidx , endidx ):
502- """
503- This function will be bound to complete_<submenu> and will perform the complete commands of the submenu.
504- """
505- submenu = self .submenu
506- original_attributes = self ._get_original_attributes ()
507- try :
508- # copy over any shared attributes
509- self ._copy_in_shared_attrs (_self )
510-
511- # Reset the submenu's tab completion parameters
512- submenu .allow_appended_space = True
513- submenu .allow_closing_quote = True
514- submenu .display_matches = []
515-
516- return _complete_from_cmd (submenu , text , line , begidx , endidx )
517- finally :
518- # copy back original attributes
519- self ._copy_out_shared_attrs (_self , original_attributes )
520-
521- # Pass the submenu's tab completion parameters back up to the menu that called complete()
522- import copy
523- _self .allow_appended_space = submenu .allow_appended_space
524- _self .allow_closing_quote = submenu .allow_closing_quote
525- _self .display_matches = copy .copy (submenu .display_matches )
526-
527- original_do_help = cmd_obj .do_help
528- original_complete_help = cmd_obj .complete_help
529-
530- def help_submenu (_self , line ):
531- """
532- This function will be bound to help_<submenu> and will call the help commands of the submenu.
533- """
534- tokens = line .split (None , 1 )
535- if tokens and (tokens [0 ] == self .command or tokens [0 ] in self .aliases ):
536- self .submenu .do_help (tokens [1 ] if len (tokens ) == 2 else '' )
537- else :
538- original_do_help (_self , line )
539-
540- def _complete_submenu_help (_self , text , line , begidx , endidx ):
541- """autocomplete to match help_submenu()'s behavior"""
542- tokens = line .split (None , 1 )
543- if len (tokens ) == 2 and (
544- not (not tokens [1 ].startswith (self .command ) and not any (
545- tokens [1 ].startswith (alias ) for alias in self .aliases ))
546- ):
547- return self .submenu .complete_help (
548- text ,
549- tokens [1 ],
550- begidx - line .index (tokens [1 ]),
551- endidx - line .index (tokens [1 ]),
552- )
553- else :
554- return original_complete_help (_self , text , line , begidx , endidx )
555-
556- if self .create_subclass :
557- class _Cmd (cmd_obj ):
558- do_help = help_submenu
559- complete_help = _complete_submenu_help
560- else :
561- _Cmd = cmd_obj
562- _Cmd .do_help = help_submenu
563- _Cmd .complete_help = _complete_submenu_help
564-
565- # Create bindings in the parent command to the submenus commands.
566- setattr (_Cmd , 'do_' + self .command , enter_submenu )
567- setattr (_Cmd , 'complete_' + self .command , complete_submenu )
568-
569- # Create additional bindings for aliases
570- for _alias in self .aliases :
571- setattr (_Cmd , 'do_' + _alias , enter_submenu )
572- setattr (_Cmd , 'complete_' + _alias , complete_submenu )
573- return _Cmd
574-
575-
576315class Cmd (cmd .Cmd ):
577316 """An easy but powerful framework for writing line-oriented command interpreters.
578317
0 commit comments