Skip to content

Commit 2cc3d21

Browse files
committed
Extract submenu code to new project
1 parent 6a3dbec commit 2cc3d21

File tree

4 files changed

+0
-675
lines changed

4 files changed

+0
-675
lines changed

cmd2/cmd2.py

Lines changed: 0 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
594330
class Cmd(cmd.Cmd):
595331
"""An easy but powerful framework for writing line-oriented command interpreters.
596332

examples/submenus.py

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)