summaryrefslogtreecommitdiff
path: root/cmd2.py
diff options
context:
space:
mode:
authorCatherine Devlin <catherine.devlin@gsa.gov>2017-05-28 16:21:58 -0700
committerCatherine Devlin <catherine.devlin@gsa.gov>2017-05-30 06:54:28 -0500
commit6af4b54f52e55d0739b4cc8aef005b09e59ea461 (patch)
tree86ec30dd931eb41e5186fa08232c72e52519862f /cmd2.py
parent387eaa2b4c98b2e986b24736ee4404f67921b2ef (diff)
downloadcmd2-git-6af4b54f52e55d0739b4cc8aef005b09e59ea461.tar.gz
Moved parser creation into ParserManager
Diffstat (limited to 'cmd2.py')
-rwxr-xr-xcmd2.py180
1 files changed, 105 insertions, 75 deletions
diff --git a/cmd2.py b/cmd2.py
index 56f8df61..d8941a61 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -576,15 +576,19 @@ class Cmd(cmd.Cmd):
Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
"""
# Attributes which are NOT dynamically settable at runtime
+
+
allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
allow_redirection = True # Should output redirection and pipes be allowed
blankLinesAllowed = False
commentGrammars = pyparsing.Or([pyparsing.pythonStyleComment, pyparsing.cStyleComment])
commentGrammars.addParseAction(lambda x: '')
commentInProgress = pyparsing.Literal('/*') + pyparsing.SkipTo(pyparsing.stringEnd ^ '*/')
+
default_to_shell = False
defaultExtension = 'txt' # For ``save``, ``load``, etc.
excludeFromHistory = '''run r list l history hi ed edit li eof'''.split()
+
# make sure your terminators are not in legalChars!
legalChars = u'!#$%.:?@_-' + pyparsing.alphanums + pyparsing.alphas8bit
multilineCommands = [] # NOTE: Multiline commands can never be abbreviated, even if abbrev is True
@@ -662,7 +666,10 @@ class Cmd(cmd.Cmd):
self.shortcuts = sorted(self.shortcuts.items(), reverse=True)
self.keywords = self.reserved_words + [fname[3:] for fname in dir(self)
if fname.startswith('do_')]
- self._init_parser()
+ self.parser_manager = ParserManager(redirector=self.redirector, terminators=self.terminators, multilineCommands=self.multilineCommands,
+ legalChars=self.legalChars, commentGrammars=self.commentGrammars,
+ commentInProgress=self.commentInProgress, case_insensitive=self.case_insensitive,
+ blankLinesAllowed=self.blankLinesAllowed, prefixParser=self.prefixParser)
self._transcript_files = transcript_files
# Used to enable the ability for a Python script to quit the application
@@ -749,77 +756,6 @@ class Cmd(cmd.Cmd):
return self._colorcodes[color][True] + val + self._colorcodes[color][False]
return val
- # ----- Methods related to pyparsing parsing logic -----
-
- def _init_parser(self):
- """ Initializes everything related to pyparsing. """
- # output_parser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
- output_parser = (pyparsing.Literal(self.redirector * 2) |
- (pyparsing.WordStart() + self.redirector) |
- pyparsing.Regex('[^=]' + self.redirector))('output')
-
- terminator_parser = pyparsing.Or(
- [(hasattr(t, 'parseString') and t) or pyparsing.Literal(t) for t in self.terminators])('terminator')
- string_end = pyparsing.stringEnd ^ '\nEOF'
- self.multilineCommand = pyparsing.Or(
- [pyparsing.Keyword(c, caseless=self.case_insensitive) for c in self.multilineCommands])('multilineCommand')
- oneline_command = (~self.multilineCommand + pyparsing.Word(self.legalChars))('command')
- pipe = pyparsing.Keyword('|', identChars='|')
- self.commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '')
- do_not_parse = self.commentGrammars | self.commentInProgress | pyparsing.quotedString
- after_elements = \
- pyparsing.Optional(pipe + pyparsing.SkipTo(output_parser ^ string_end, ignore=do_not_parse)('pipeTo')) + \
- pyparsing.Optional(output_parser +
- pyparsing.SkipTo(string_end,
- ignore=do_not_parse).setParseAction(lambda x: x[0].strip())('outputTo'))
- if self.case_insensitive:
- self.multilineCommand.setParseAction(lambda x: x[0].lower())
- oneline_command.setParseAction(lambda x: x[0].lower())
- if self.blankLinesAllowed:
- self.blankLineTerminationParser = pyparsing.NoMatch
- else:
- self.blankLineTerminator = (pyparsing.lineEnd + pyparsing.lineEnd)('terminator')
- self.blankLineTerminator.setResultsName('terminator')
- self.blankLineTerminationParser = ((self.multilineCommand ^ oneline_command) +
- pyparsing.SkipTo(self.blankLineTerminator,
- ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('args') +
- self.blankLineTerminator)('statement')
- self.multilineParser = (((self.multilineCommand ^ oneline_command) +
- pyparsing.SkipTo(terminator_parser,
- ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('args') + terminator_parser)('statement') +
- pyparsing.SkipTo(output_parser ^ pipe ^ string_end, ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('suffix') + after_elements)
- self.multilineParser.ignore(self.commentInProgress)
- self.singleLineParser = ((oneline_command +
- pyparsing.SkipTo(terminator_parser ^ string_end ^ pipe ^ output_parser,
- ignore=do_not_parse).setParseAction(
- lambda x: x[0].strip())('args'))('statement') +
- pyparsing.Optional(terminator_parser) + after_elements)
- # self.multilineParser = self.multilineParser.setResultsName('multilineParser')
- # self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser')
- self.blankLineTerminationParser = self.blankLineTerminationParser.setResultsName('statement')
- self.parser = self.prefixParser + (
- string_end |
- self.multilineParser |
- self.singleLineParser |
- self.blankLineTerminationParser |
- self.multilineCommand + pyparsing.SkipTo(string_end, ignore=do_not_parse)
- )
- self.parser.ignore(self.commentGrammars)
-
- input_mark = pyparsing.Literal('<')
- input_mark.setParseAction(lambda x: '')
- file_name = pyparsing.Word(self.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"
- self.inputParser = input_mark + pyparsing.Optional(input_from) + pyparsing.Optional('>') + \
- pyparsing.Optional(file_name) + (pyparsing.stringEnd | '|')
- self.inputParser.ignore(self.commentInProgress)
-
# ----- Methods which override stuff in cmd -----
def precmd(self, statement):
@@ -972,17 +908,17 @@ class Cmd(cmd.Cmd):
else:
# preparse is an overridable hook; default makes no changes
s = self.preparse(raw)
- s = self.inputParser.transformString(s.lstrip())
+ s = self.parser_manager.input_source_parser.transformString(s.lstrip())
s = self.commentGrammars.transformString(s)
for (shortcut, expansion) in self.shortcuts:
if s.lower().startswith(shortcut):
s = s.replace(shortcut, expansion + ' ', 1)
break
try:
- result = self.parser.parseString(s)
+ result = self.parser_manager.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.parser.parseString('')
+ result = self.parser_manager.main_parser.parseString('')
result['raw'] = raw
result['command'] = result.multilineCommand or result.command
result = self.postparse(result)
@@ -2011,6 +1947,100 @@ Script should contain one command per line, just like command would be typed in
self.postloop()
+class ParserManager:
+
+ def __init__(self, redirector, terminators, multilineCommands, legalChars, commentGrammars,
+ commentInProgress, case_insensitive, blankLinesAllowed, prefixParser):
+ "Creates and uses parsers for user input according to app's paramters."
+
+ self.main_parser = self._build_main_parser(
+ redirector=redirector, terminators=terminators, multilineCommands=multilineCommands,
+ legalChars=legalChars, commentGrammars=commentGrammars,
+ commentInProgress=commentInProgress, case_insensitive=case_insensitive,
+ 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, commentGrammars,
+ commentInProgress, case_insensitive, 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=case_insensitive) for c in multilineCommands])('multilineCommand')
+ oneline_command = (~multilineCommand + pyparsing.Word(legalChars))('command')
+ pipe = pyparsing.Keyword('|', identChars='|')
+ commentGrammars.ignore(pyparsing.quotedString).setParseAction(lambda x: '')
+ do_not_parse = 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: x[0].strip())('outputTo'))
+ if case_insensitive:
+ multilineCommand.setParseAction(lambda x: x[0].lower())
+ oneline_command.setParseAction(lambda x: x[0].lower())
+ 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(commentGrammars)
+ return parser
+
+ def _build_input_source_parser(self, legalChars, commentInProgress):
+ "Builds a PyParsing parser for alternate user input sources (from file, pipe, etc.)"
+
+ input_mark = pyparsing.Literal('<')
+ input_mark.setParseAction(lambda x: '')
+ 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
+
+
+
+
class HistoryItem(str):
"""Class used to represent an item in the History list.