from glxshell.lib.columnize import columnize
import sys # MiroPython doesn't yet have a string module
from glxshell.lib.argparse import ArgumentParser
from glxshell.lib.argparse import WrapperCmdLineArgParser
from glxshell.lib.path import expanduser, exists
import os
try:
import subprocess
except ImportError:
subprocess = None
#
# A generic class to build line-oriented command interpreters.
#
# Interpreters constructed with this class obey the following conventions:
#
# 1. End of file on input is processed as the command 'EOF'.
# 2. A command is parsed out of each line by collecting the prefix composed
# of characters in the identchars member.
# 3. A command `foo' is dispatched to a method 'do_foo()'; the do_ method
# is passed a single argument consisting of the remainder of the line.
# 4. Typing an empty line repeats the last command. (Actually, it calls the
# method `emptyline', which may be overridden in a subclass.)
# 5. There is a predefined `help' method. Given an argument `topic', it
# calls the command `help_topic'. With no arguments, it lists all topics
# with defined help_ functions, broken into up to three topics; documented
# commands, miscellaneous help topics, and undocumented commands.
# 6. The command '?' is a synonym for `help'. The command '!' is a synonym
# for `shell', if a do_shell method exists.
# 7. If completion is enabled, completing commands will be done automatically,
# and completing of commands args is done by calling complete_foo() with
# arguments text, line, begidx, endidx. text is string we are matching
# against, all returned matches must begin with it. line is the current
# input line (lstripped), begidx and endidx are the beginning and end
# indexes of the text being matched, which could be used to provide
# different completion depending upon which position the argument is in.
#
# The `default' method may be overridden to intercept commands for which there
# is no do_ method.
#
# The `completedefault' method may be overridden to intercept completions for
# commands that have no complete_ method.
#
# The data member `self.ruler' sets the character used to draw separator lines
# in the help messages. If empty, no ruler line is drawn. It defaults to "=".
#
# If the value of `self.intro' is nonempty when the cmdloop method is called,
# it is printed out on interpreter startup. This value may be overridden
# via an optional argument to the cmdloop() method.
#
# The data members `self.doc_header', `self.misc_header', and
# `self.undoc_header' set the headers used for the help function's
# listings of documented functions, miscellaneous topics, and undocumented
# functions respectively.
#
# ----------------------------------------------------------------------------
# This is a copy of python's Cmd, but leaves out features that aren't relevant
# or can't currently be implemented for MicroPython.
#
# One of the notable deviations is that since MicroPython strips doc strings,
# this means that that help by doc string feature doesn't work.
#
# completions have also been stripped out.
# import string, sys
parser_man = ArgumentParser(
prog="man", description="The man utility shall write information about each of the name operands.", add_help=True
)
parser_man.add_argument(
"name",
nargs="?",
help="A keyword or the name of a standard utility.",
)
PROMPT = "(Cmd) "
# IDENTCHARS = string.ascii_letters + string.digits + '_'
IDENTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
[docs]
class Cmd:
"""
A simple framework for writing line-oriented command interpreters.
These are often useful for test harnesses, administrative tools, and
prototypes that will later be wrapped in a more sophisticated interface.
A Cmd instance or subclass instance is a line-oriented interpreter
framework. There is no good reason to instantiate Cmd itself; rather,
it's useful as a superclass of an interpreter class you define yourself
in order to inherit Cmd's methods and encapsulate action methods.
"""
prompt = PROMPT
identchars = IDENTCHARS
ruler = "="
lastcmd = ""
intro = None
doc_leader = ""
doc_header = "Documented commands (type man <topic>):"
misc_header = "Miscellaneous help topics:"
undoc_header = "Undocumented commands:"
nohelp = "No manual entry for %s"
use_rawinput = 1
def __init__(self, completekey='tab'):
"""
Instantiate a line-oriented interpreter framework.
"""
self.cmdqueue = []
self.completekey = completekey
self.completion_matches = []
self.exit_code = 0
self.rc_file = ".glxshrc"
self.logout_file = ".glxsh_logout"
self.history_path = expanduser("~/.glxsh_history")
self.history_length = 100
[docs]
def cmdloop(self, intro=None):
"""
Repeatedly issue a prompt, accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
the remainder of the line as argument.
"""
self.preloop()
if self.use_rawinput and self.completekey:
try:
import readline
self.old_completer = readline.get_completer()
readline.set_completer(self.complete)
readline.parse_and_bind(self.completekey + ": complete")
# do not use - as delimiter
self.old_delims = readline.get_completer_delims()
readline.set_completer_delims(self.old_delims.replace("-", ""))
readline.parse_and_bind("set colored-completion-prefix off")
readline.parse_and_bind("set show-all-if-unmodified on")
readline.parse_and_bind("set horizontal-scroll-mode on")
if not exists(self.history_path):
open(self.history_path, 'a+').close()
readline.read_history_file(self.history_path)
readline.set_history_length(self.history_length)
except ImportError:
pass
try:
if intro is not None:
self.intro = intro
if self.intro:
sys.stdout.write(str(self.intro) + "\n")
stop = None
while not stop:
if self.cmdqueue:
line = self.cmdqueue.pop(0)
else:
if self.use_rawinput:
try:
sys.stdout.write(self.prompt)
sys.stdout.flush()
line = input()
except EOFError:
line = "EOF"
except KeyboardInterrupt:
sys.stdout.write("^C")
sys.stdout.flush()
line = ""
else:
try:
sys.stdout.write(self.prompt)
sys.stdout.flush()
line = sys.stdin.readline()
if not len(line):
line = "EOF"
else:
line = line.rstrip("\r\n")
except KeyboardInterrupt:
sys.stdout.write("^C")
sys.stdout.flush()
line = ""
line = self.precmd(line)
stop = self.onecmd(line)
stop = self.postcmd(stop, line)
self.postloop()
finally:
if self.use_rawinput and self.completekey:
try:
import readline
readline.set_completer(self.old_completer)
readline.set_completer_delims(self.old_delims)
readline.write_history_file(self.history_path)
except ImportError:
pass
return self.exit_code
[docs]
def precmd(self, line):
"""
Hook method executed just before the command line is
interpreted, but after the input prompt is generated and issued.
"""
return line
[docs]
def postcmd(self, stop, line):
"""
Hook method executed just after a command dispatch is finished.
"""
return stop
[docs]
def preloop(self):
"""
Hook method executed once when the cmdloop() method is called.
"""
pass
[docs]
def postloop(self):
"""
Hook method executed once when the cmdloop() method is about to
return.
"""
pass
[docs]
def parseline(self, line):
"""
Parse the line into a command name and a string containing
the arguments. Returns a tuple containing (command, args, line).
'command' and 'args' may be None if the line couldn't be parsed.
"""
line = line.strip()
if not line:
return None, None, line
elif line[0] == "?":
line = "man " + line[1:]
elif line[0] == "!":
if hasattr(self, "do_shell"):
line = "shell " + line[1:]
else:
return None, None, line
i, n = 0, len(line)
while i < n and line[i] in self.identchars:
i = i + 1
cmd, arg = line[:i], line[i:].strip()
return cmd, arg, line
[docs]
def onecmd(self, line):
"""
Interpret the argument as though it had been typed in response
to the prompt.
This may be overridden, but should not normally need to be;
see the precmd() and postcmd() methods for useful execution hooks.
The return value is a flag indicating whether interpretation of
commands by the interpreter should stop.
"""
cmd, arg, line = self.parseline(line)
if not line:
return self.emptyline()
if cmd is None:
return self.default(line)
self.lastcmd = line
if line == "EOF":
self.lastcmd = ""
if cmd == "":
return self.default(line)
else:
try:
func = getattr(self, "do_" + cmd)
except AttributeError:
return self.default(line)
return func(arg)
[docs]
def onecmdhooks(self, line):
self.onecmd(line)
return self.exit_code
[docs]
def emptyline(self):
"""
Called when an empty line is entered in response to the prompt.
If this method is not overridden, it repeats the last nonempty
command entered.
"""
if self.lastcmd:
return self.onecmd(self.lastcmd)
[docs]
def default(self, line):
"""
Called on an input line when the command prefix is not recognized.
If this method is not overridden, it prints an error message and
returns.
"""
sys.stdout.write("*** Unknown syntax: %s\n" % line)
[docs]
def default_completer(self, *ignored):
"""
Method called to complete an input line when no command-specific
complete_*() method is available.
By default, it returns an empty list.
"""
return []
[docs]
def completenames(self, text, *ignored):
return ["%s " % a[3:] for a in self.get_names() if a.startswith("do_%s" % text)]
[docs]
def complete(self, text, state):
"""
Return the next possible completion for 'text'.
If a command has not been entered, then complete against command list.
Otherwise try to call complete_<command> to get list of completions.
"""
if state == 0:
import readline
original_line = readline.get_line_buffer()
line = original_line.lstrip()
stripped = len(original_line) - len(line)
start_index = readline.get_begidx() - stripped
end_index = readline.get_endidx() - stripped
if start_index > 0:
cmd, args, foo = self.parseline(line)
if cmd == "":
complete_function = self.default_completer
else:
try:
complete_function = getattr(self, "complete_" + cmd)
except AttributeError:
complete_function = self.default_completer
else:
complete_function = self.completenames
self.completion_matches = complete_function(text, line, start_index, end_index)
try:
return self.completion_matches[state]
except IndexError:
return None
[docs]
def get_names(self):
# This method used to pull in base class attributes
# at a time dir() didn't do it yet.
return dir(self.__class__)
[docs]
def complete_man(self, *args):
commands = set([a[3:] for a in self.get_names() if a.startswith("do_" + args[0])])
topics = set(a[5:] for a in self.get_names() if a.startswith("help_" + args[0]))
return list(commands | topics)
[docs]
@staticmethod
def help_man():
parser_man.print_help()
@WrapperCmdLineArgParser(parser_man)
def do_man(self, arg, parsed):
"""
List available commands with "help" or detailed help with "help cmd".
"""
if parsed.help:
self.help_man()
return 0
if arg:
# XXX check arg syntax
try:
func = getattr(self, "help_" + arg)
except AttributeError:
sys.stdout.write("%s\n" % str(self.nohelp % (arg,)))
return 1
return func()
else:
names = self.get_names()
cmds_doc = []
cmds_undoc = []
help = {}
for name in names:
if name[:5] == "help_":
help[name[5:]] = 1
names.sort()
# There can be duplicates if routines overridden
prevname = ""
for name in names:
if name[:3] == "do_":
if name == prevname:
continue
prevname = name
cmd = name[3:]
if cmd in help:
cmds_doc.append(cmd)
del help[cmd]
else:
cmds_undoc.append(cmd)
sys.stdout.write("%s\n" % self.doc_leader)
self.print_topics(self.doc_header, cmds_doc, 15, 80)
self.print_topics(self.misc_header, list(help.keys()), 15, 80)
if cmds_undoc != ['EOF']:
self.print_topics(self.undoc_header, cmds_undoc, 15, 80)
return 0
[docs]
def print_topics(self, header, cmds, cmdlen, maxcol):
if cmds:
sys.stdout.write("%s\n" % header)
if self.ruler:
sys.stdout.write("%s\n" % str(self.ruler * len(header)))
columnize(cmds, maxcol - 1)
sys.stdout.write("\n")