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