summaryrefslogtreecommitdiff
path: root/cmd2/cmd2.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/cmd2.py')
-rwxr-xr-xcmd2/cmd2.py197
1 files changed, 8 insertions, 189 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 43973b64..41f46d5c 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -52,7 +52,7 @@ import pyperclip
from .rl_utils import rl_force_redisplay, readline, rl_type, RlType
from .argparse_completer import AutoCompleter, ACArgumentParser
-from cmd2.parsing import CommandParser
+from cmd2.parsing import StatementParser
if rl_type == RlType.PYREADLINE:
@@ -348,23 +348,6 @@ def write_to_paste_buffer(txt: str) -> None:
"""
pyperclip.copy(txt)
-def replace_with_file_contents(fname: str) -> str:
- """Action to perform when successfully matching parse element definition for inputFrom parser.
-
- :param fname: filename
- :return: contents of file "fname"
- """
- try:
- # Any outer quotes are not part of the filename
- unquoted_file = strip_quotes(fname[0])
- with open(os.path.expanduser(unquoted_file)) as source_file:
- result = source_file.read()
- except IOError:
- result = '< %s' % fname[0] # wasn't a file after all
-
- # TODO: IF pyparsing input parser logic gets fixed to support empty file, add support to get from paste buffer
- return result
-
class EmbeddedConsoleExit(SystemExit):
"""Custom exception class for use with the py command."""
@@ -646,7 +629,7 @@ class Cmd(cmd.Cmd):
Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
"""
- # Attributes used to configure the ParserManager (all are not dynamically settable at runtime)
+ # Attributes used to configure the StatementParser, best not to change these at runtime
blankLinesAllowed = False
multilineCommands = []
redirector = '>' # for sending output to file
@@ -739,7 +722,7 @@ class Cmd(cmd.Cmd):
self.history = History()
self.pystate = {}
self.keywords = self.reserved_words + [fname[3:] for fname in dir(self) if fname.startswith('do_')]
- self.command_parser = CommandParser(
+ self.statement_parser = StatementParser(
quotes=QUOTES,
allow_redirection=self.allow_redirection,
redirection_chars=REDIRECTION_CHARS,
@@ -2121,10 +2104,7 @@ class Cmd(cmd.Cmd):
pipe runs out. We can't refactor it because we need to retain
backwards compatibility with the standard library version of cmd.
"""
- #if not line or (not pyparsing.Or(self.commentGrammars).setParseAction(lambda x: '').transformString(line)):
- # raise EmptyStatement()
- # statement = self.parser_manager.parsed(line) # deleteme
- statement = self.command_parser.parseString(line)
+ statement = self.statement_parser.parse(line)
while statement.multilineCommand and not statement.terminator:
if not self.quit_on_sigint:
try:
@@ -2139,7 +2119,7 @@ class Cmd(cmd.Cmd):
line = '{}\n{}'.format(statement.raw, newline)
except KeyboardInterrupt:
self.poutput('^C')
- statement = self.command_parser.parseString('')
+ statement = self.statement_parser.parse('')
break
else:
newline = self.pseudo_raw_input(self.continuation_prompt)
@@ -2151,7 +2131,7 @@ class Cmd(cmd.Cmd):
newline = '\n'
self.poutput(newline)
line = '{}\n{}'.format(statement.raw, newline)
- statement = self.command_parser.parseString(line)
+ statement = self.statement_parser.parse(line)
if not statement.command:
raise EmptyStatement()
@@ -2160,7 +2140,7 @@ class Cmd(cmd.Cmd):
def _redirect_output(self, statement):
"""Handles output redirection for >, >>, and |.
- :param statement: ParsedString - subclass of str which also contains pyparsing ParseResults instance
+ :param statement: Statement - a parsed statement from the user
"""
if statement.pipeTo:
self.kept_state = Statekeeper(self, ('stdout',))
@@ -2208,7 +2188,7 @@ class Cmd(cmd.Cmd):
def _restore_output(self, statement):
"""Handles restoring state after output redirection as well as the actual pipe operation if present.
- :param statement: ParsedString - subclass of str which also contains pyparsing ParseResults instance
+ :param statement: Statement object which contains the parsed input from the user
"""
# If we have redirected output to a file or the clipboard or piped it to a shell command, then restore state
if self.kept_state is not None:
@@ -3312,167 +3292,6 @@ Script should contain one command per line, just like command would be typed in
self.postloop()
-# noinspection PyPep8Naming
-class ParserManager:
- """
- Class which encapsulates all of the pyparsing parser functionality for cmd2 in a single location.
- """
- def __init__(self, redirector, terminators, multilineCommands, legalChars, commentGrammars, commentInProgress,
- blankLinesAllowed, prefixParser, preparse, postparse, aliases, shortcuts):
- """Creates and uses parsers for user input according to app's parameters."""
-
- self.commentGrammars = commentGrammars
- self.preparse = preparse
- self.postparse = postparse
- self.aliases = aliases
- self.shortcuts = shortcuts
-
- self.main_parser = self._build_main_parser(redirector=redirector, terminators=terminators,
- multilineCommands=multilineCommands, legalChars=legalChars,
- commentInProgress=commentInProgress,
- blankLinesAllowed=blankLinesAllowed, prefixParser=prefixParser)
- self.input_source_parser = self._build_input_source_parser(legalChars=legalChars,
- commentInProgress=commentInProgress)
-
- def _build_main_parser(self, redirector, terminators, multilineCommands, legalChars, commentInProgress,
- blankLinesAllowed, prefixParser):
- """Builds a PyParsing parser for interpreting user commands."""
-
- # Build several parsing components that are eventually compiled into overall parser
- output_destination_parser = (pyparsing.Literal(redirector * 2) |
- (pyparsing.WordStart() + redirector) |
- pyparsing.Regex('[^=]' + redirector))('output')
-
- terminator_parser = pyparsing.Or(
- [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in terminators])('terminator')
- string_end = pyparsing.stringEnd ^ '\nEOF'
- multilineCommand = pyparsing.Or(
- [pyparsing.Keyword(c, caseless=False) for c in multilineCommands])('multilineCommand')
- oneline_command = (~multilineCommand + pyparsing.Word(legalChars))('command')
- pipe = pyparsing.Keyword('|', identChars='|')
- do_not_parse = self.commentGrammars | commentInProgress | pyparsing.quotedString
- after_elements = \
- pyparsing.Optional(pipe + pyparsing.SkipTo(output_destination_parser ^ string_end,
- ignore=do_not_parse)('pipeTo')) + \
- pyparsing.Optional(output_destination_parser +
- pyparsing.SkipTo(string_end, ignore=do_not_parse).
- setParseAction(lambda x: strip_quotes(x[0].strip()))('outputTo'))
-
- multilineCommand.setParseAction(lambda x: x[0])
- oneline_command.setParseAction(lambda x: x[0])
-
- if blankLinesAllowed:
- blankLineTerminationParser = pyparsing.NoMatch
- else:
- blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator')
- blankLineTerminator.setResultsName('terminator')
- blankLineTerminationParser = ((multilineCommand ^ oneline_command) +
- pyparsing.SkipTo(blankLineTerminator, ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('args') + blankLineTerminator)('statement')
-
- multilineParser = (((multilineCommand ^ oneline_command) +
- pyparsing.SkipTo(terminator_parser,
- ignore=do_not_parse).setParseAction(lambda x: x[0].strip())('args') +
- terminator_parser)('statement') +
- pyparsing.SkipTo(output_destination_parser ^ pipe ^ string_end,
- ignore=do_not_parse).setParseAction(lambda x: x[0].strip())('suffix') +
- after_elements)
- multilineParser.ignore(commentInProgress)
-
- singleLineParser = ((oneline_command +
- pyparsing.SkipTo(terminator_parser ^ string_end ^ pipe ^ output_destination_parser,
- ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('args'))('statement') +
- pyparsing.Optional(terminator_parser) + after_elements)
-
- blankLineTerminationParser = blankLineTerminationParser.setResultsName('statement')
-
- parser = prefixParser + (
- string_end |
- multilineParser |
- singleLineParser |
- blankLineTerminationParser |
- multilineCommand + pyparsing.SkipTo(string_end, ignore=do_not_parse)
- )
- parser.ignore(self.commentGrammars)
- return parser
-
- @staticmethod
- def _build_input_source_parser(legalChars, commentInProgress):
- """Builds a PyParsing parser for alternate user input sources (from file, pipe, etc.)"""
-
- input_mark = pyparsing.Literal('<')
- input_mark.setParseAction(lambda x: '')
-
- # Also allow spaces, slashes, and quotes
- file_name = pyparsing.Word(legalChars + ' /\\"\'')
-
- input_from = file_name('inputFrom')
- input_from.setParseAction(replace_with_file_contents)
- # a not-entirely-satisfactory way of distinguishing < as in "import from" from <
- # as in "lesser than"
- inputParser = input_mark + pyparsing.Optional(input_from) + pyparsing.Optional('>') + \
- pyparsing.Optional(file_name) + (pyparsing.stringEnd | '|')
- inputParser.ignore(commentInProgress)
- return inputParser
-
- def parsed(self, raw):
- """ This function is where the actual parsing of each line occurs.
-
- :param raw: str - the line of text as it was entered
- :return: ParsedString - custom subclass of str with extra attributes
- """
- if isinstance(raw, ParsedString):
- p = raw
- else:
- # preparse is an overridable hook; default makes no changes
- s = self.preparse(raw)
- s = self.input_source_parser.transformString(s.lstrip())
- s = self.commentGrammars.transformString(s)
-
- # Make a copy of aliases so we can edit it
- tmp_aliases = list(self.aliases.keys())
- keep_expanding = len(tmp_aliases) > 0
-
- # Expand aliases
- while keep_expanding:
- for cur_alias in tmp_aliases:
- keep_expanding = False
-
- if s == cur_alias or s.startswith(cur_alias + ' '):
- s = s.replace(cur_alias, self.aliases[cur_alias], 1)
-
- # Do not expand the same alias more than once
- tmp_aliases.remove(cur_alias)
- keep_expanding = len(tmp_aliases) > 0
- break
-
- # Expand command shortcut to its full command name
- for (shortcut, expansion) in self.shortcuts:
- if s.startswith(shortcut):
- # If the next character after the shortcut isn't a space, then insert one
- shortcut_len = len(shortcut)
- if len(s) == shortcut_len or s[shortcut_len] != ' ':
- expansion += ' '
-
- # Expand the shortcut
- s = s.replace(shortcut, expansion, 1)
- break
-
- try:
- result = self.main_parser.parseString(s)
- except pyparsing.ParseException:
- # If we have a parsing failure, treat it is an empty command and move to next prompt
- result = self.main_parser.parseString('')
- result['raw'] = raw
- result['command'] = result.multilineCommand or result.command
- result = self.postparse(result)
- p = ParsedString(result.args)
- p.parsed = result
- p.parser = self.parsed
- return p
-
-
class HistoryItem(str):
"""Class used to represent an item in the History list.