@@ -576,20 +576,24 @@ class Cmd(cmd.Cmd):
576576 Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
577577 """
578578 # Attributes which are NOT dynamically settable at runtime
579- allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
580- allow_redirection = True # Should output redirection and pipes be allowed
579+
580+ allow_cli_args = True # Should arguments passed on the command-line be processed as commands?
581+ allow_redirection = True # Should output redirection and pipes be allowed
581582 blankLinesAllowed = False
582- commentGrammars = pyparsing .Or ([pyparsing .pythonStyleComment , pyparsing .cStyleComment ])
583- commentGrammars .addParseAction (lambda x : '' )
583+ commentGrammars = pyparsing .Or (
584+ [pyparsing .pythonStyleComment , pyparsing .cStyleComment ]
585+ )
584586 commentInProgress = pyparsing .Literal ('/*' ) + pyparsing .SkipTo (pyparsing .stringEnd ^ '*/' )
585- default_to_shell = False
586- defaultExtension = 'txt' # For ``save``, ``load``, etc.
587+
588+ default_to_shell = False # Attempt to run unrecognized commands as shell commands
589+ defaultExtension = 'txt' # For ``save``, ``load``, etc.
587590 excludeFromHistory = '''run r list l history hi ed edit li eof''' .split ()
591+
588592 # make sure your terminators are not in legalChars!
589593 legalChars = u'!#$%.:?@_-' + pyparsing .alphanums + pyparsing .alphas8bit
590594 multilineCommands = [] # NOTE: Multiline commands can never be abbreviated, even if abbrev is True
591595 prefixParser = pyparsing .Empty ()
592- redirector = '>' # for sending output to file
596+ redirector = '>' # for sending output to file
593597 reserved_words = []
594598 shortcuts = {'?' : 'help' , '!' : 'shell' , '@' : 'load' , '@@' : '_relative_load' }
595599 terminators = [';' ]
@@ -655,14 +659,18 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False
655659 # Call super class constructor. Need to do it in this way for Python 2 and 3 compatibility
656660 cmd .Cmd .__init__ (self , completekey = completekey , stdin = stdin , stdout = stdout )
657661
662+ self ._finalize_app_parameters ()
658663 self .initial_stdout = sys .stdout
659664 self .history = History ()
660665 self .pystate = {}
661666 # noinspection PyUnresolvedReferences
662- self .shortcuts = sorted (self .shortcuts .items (), reverse = True )
663667 self .keywords = self .reserved_words + [fname [3 :] for fname in dir (self )
664668 if fname .startswith ('do_' )]
665- self ._init_parser ()
669+ self .parser_manager = ParserManager (redirector = self .redirector , terminators = self .terminators , multilineCommands = self .multilineCommands ,
670+ legalChars = self .legalChars , commentGrammars = self .commentGrammars ,
671+ commentInProgress = self .commentInProgress , case_insensitive = self .case_insensitive ,
672+ blankLinesAllowed = self .blankLinesAllowed , prefixParser = self .prefixParser ,
673+ preparse = self .preparse , postparse = self .postparse , shortcuts = self .shortcuts )
666674 self ._transcript_files = transcript_files
667675
668676 # Used to enable the ability for a Python script to quit the application
@@ -700,6 +708,10 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, use_ipython=False
700708
701709 # ----- Methods related to presenting output to the user -----
702710
711+ def _finalize_app_parameters (self ):
712+ self .commentGrammars .ignore (pyparsing .quotedString ).setParseAction (lambda x : '' )
713+ self .shortcuts = sorted (self .shortcuts .items (), reverse = True )
714+
703715 def poutput (self , msg ):
704716 """Convenient shortcut for self.stdout.write(); adds newline if necessary."""
705717 if msg :
@@ -749,77 +761,6 @@ def colorize(self, val, color):
749761 return self ._colorcodes [color ][True ] + val + self ._colorcodes [color ][False ]
750762 return val
751763
752- # ----- Methods related to pyparsing parsing logic -----
753-
754- def _init_parser (self ):
755- """ Initializes everything related to pyparsing. """
756- # output_parser = (pyparsing.Literal('>>') | (pyparsing.WordStart() + '>') | pyparsing.Regex('[^=]>'))('output')
757- output_parser = (pyparsing .Literal (self .redirector * 2 ) |
758- (pyparsing .WordStart () + self .redirector ) |
759- pyparsing .Regex ('[^=]' + self .redirector ))('output' )
760-
761- terminator_parser = pyparsing .Or (
762- [(hasattr (t , 'parseString' ) and t ) or pyparsing .Literal (t ) for t in self .terminators ])('terminator' )
763- string_end = pyparsing .stringEnd ^ '\n EOF'
764- self .multilineCommand = pyparsing .Or (
765- [pyparsing .Keyword (c , caseless = self .case_insensitive ) for c in self .multilineCommands ])('multilineCommand' )
766- oneline_command = (~ self .multilineCommand + pyparsing .Word (self .legalChars ))('command' )
767- pipe = pyparsing .Keyword ('|' , identChars = '|' )
768- self .commentGrammars .ignore (pyparsing .quotedString ).setParseAction (lambda x : '' )
769- do_not_parse = self .commentGrammars | self .commentInProgress | pyparsing .quotedString
770- after_elements = \
771- pyparsing .Optional (pipe + pyparsing .SkipTo (output_parser ^ string_end , ignore = do_not_parse )('pipeTo' )) + \
772- pyparsing .Optional (output_parser +
773- pyparsing .SkipTo (string_end ,
774- ignore = do_not_parse ).setParseAction (lambda x : x [0 ].strip ())('outputTo' ))
775- if self .case_insensitive :
776- self .multilineCommand .setParseAction (lambda x : x [0 ].lower ())
777- oneline_command .setParseAction (lambda x : x [0 ].lower ())
778- if self .blankLinesAllowed :
779- self .blankLineTerminationParser = pyparsing .NoMatch
780- else :
781- self .blankLineTerminator = (pyparsing .lineEnd + pyparsing .lineEnd )('terminator' )
782- self .blankLineTerminator .setResultsName ('terminator' )
783- self .blankLineTerminationParser = ((self .multilineCommand ^ oneline_command ) +
784- pyparsing .SkipTo (self .blankLineTerminator ,
785- ignore = do_not_parse ).setParseAction (
786- lambda x : x [0 ].strip ())('args' ) +
787- self .blankLineTerminator )('statement' )
788- self .multilineParser = (((self .multilineCommand ^ oneline_command ) +
789- pyparsing .SkipTo (terminator_parser ,
790- ignore = do_not_parse ).setParseAction (
791- lambda x : x [0 ].strip ())('args' ) + terminator_parser )('statement' ) +
792- pyparsing .SkipTo (output_parser ^ pipe ^ string_end , ignore = do_not_parse ).setParseAction (
793- lambda x : x [0 ].strip ())('suffix' ) + after_elements )
794- self .multilineParser .ignore (self .commentInProgress )
795- self .singleLineParser = ((oneline_command +
796- pyparsing .SkipTo (terminator_parser ^ string_end ^ pipe ^ output_parser ,
797- ignore = do_not_parse ).setParseAction (
798- lambda x : x [0 ].strip ())('args' ))('statement' ) +
799- pyparsing .Optional (terminator_parser ) + after_elements )
800- # self.multilineParser = self.multilineParser.setResultsName('multilineParser')
801- # self.singleLineParser = self.singleLineParser.setResultsName('singleLineParser')
802- self .blankLineTerminationParser = self .blankLineTerminationParser .setResultsName ('statement' )
803- self .parser = self .prefixParser + (
804- string_end |
805- self .multilineParser |
806- self .singleLineParser |
807- self .blankLineTerminationParser |
808- self .multilineCommand + pyparsing .SkipTo (string_end , ignore = do_not_parse )
809- )
810- self .parser .ignore (self .commentGrammars )
811-
812- input_mark = pyparsing .Literal ('<' )
813- input_mark .setParseAction (lambda x : '' )
814- file_name = pyparsing .Word (self .legalChars + '/\\ ' )
815- input_from = file_name ('inputFrom' )
816- input_from .setParseAction (replace_with_file_contents )
817- # a not-entirely-satisfactory way of distinguishing < as in "import from" from <
818- # as in "lesser than"
819- self .inputParser = input_mark + pyparsing .Optional (input_from ) + pyparsing .Optional ('>' ) + \
820- pyparsing .Optional (file_name ) + (pyparsing .stringEnd | '|' )
821- self .inputParser .ignore (self .commentInProgress )
822-
823764 # ----- Methods which override stuff in cmd -----
824765
825766 def precmd (self , statement ):
@@ -952,45 +893,15 @@ def _complete_statement(self, line):
952893 """Keep accepting lines of input until the command is complete."""
953894 if not line or (not pyparsing .Or (self .commentGrammars ).setParseAction (lambda x : '' ).transformString (line )):
954895 raise EmptyStatement ()
955- statement = self ._parsed (line )
896+ statement = self .parser_manager . parsed (line )
956897 while statement .parsed .multilineCommand and (statement .parsed .terminator == '' ):
957898 statement = '%s\n %s' % (statement .parsed .raw ,
958899 self .pseudo_raw_input (self .continuation_prompt ))
959- statement = self ._parsed (statement )
900+ statement = self .parser_manager . parsed (statement )
960901 if not statement .parsed .command :
961902 raise EmptyStatement ()
962903 return statement
963904
964- def _parsed (self , raw ):
965- """ This function is where the actual parsing of each line occurs.
966-
967- :param raw: str - the line of text as it was entered
968- :return: ParsedString - custom subclass of str with extra attributes
969- """
970- if isinstance (raw , ParsedString ):
971- p = raw
972- else :
973- # preparse is an overridable hook; default makes no changes
974- s = self .preparse (raw )
975- s = self .inputParser .transformString (s .lstrip ())
976- s = self .commentGrammars .transformString (s )
977- for (shortcut , expansion ) in self .shortcuts :
978- if s .lower ().startswith (shortcut ):
979- s = s .replace (shortcut , expansion + ' ' , 1 )
980- break
981- try :
982- result = self .parser .parseString (s )
983- except pyparsing .ParseException :
984- # If we have a parsing failure, treat it is an empty command and move to next prompt
985- result = self .parser .parseString ('' )
986- result ['raw' ] = raw
987- result ['command' ] = result .multilineCommand or result .command
988- result = self .postparse (result )
989- p = ParsedString (result .args )
990- p .parsed = result
991- p .parser = self ._parsed
992- return p
993-
994905 def _redirect_output (self , statement ):
995906 """Handles output redirection for >, >>, and |.
996907
@@ -1086,7 +997,7 @@ def onecmd(self, line):
1086997 :param line: ParsedString - subclass of string including the pyparsing ParseResults
1087998 :return: bool - a flag indicating whether the interpretation of commands should stop
1088999 """
1089- statement = self ._parsed (line )
1000+ statement = self .parser_manager . parsed (line )
10901001 self .lastcmd = statement .parsed .raw
10911002 funcname = self ._func_named (statement .parsed .command )
10921003 if not funcname :
@@ -2011,6 +1922,135 @@ def cmdloop(self, intro=None):
20111922 self .postloop ()
20121923
20131924
1925+ class ParserManager :
1926+
1927+ def __init__ (self , redirector , terminators , multilineCommands , legalChars , commentGrammars ,
1928+ commentInProgress , case_insensitive , blankLinesAllowed , prefixParser ,
1929+ preparse , postparse , shortcuts ):
1930+ "Creates and uses parsers for user input according to app's paramters."
1931+
1932+ self .commentGrammars = commentGrammars
1933+ self .preparse = preparse
1934+ self .postparse = postparse
1935+ self .shortcuts = shortcuts
1936+
1937+ self .main_parser = self ._build_main_parser (
1938+ redirector = redirector , terminators = terminators , multilineCommands = multilineCommands ,
1939+ legalChars = legalChars ,
1940+ commentInProgress = commentInProgress , case_insensitive = case_insensitive ,
1941+ blankLinesAllowed = blankLinesAllowed , prefixParser = prefixParser )
1942+ self .input_source_parser = self ._build_input_source_parser (legalChars = legalChars , commentInProgress = commentInProgress )
1943+
1944+ def _build_main_parser (self , redirector , terminators , multilineCommands , legalChars ,
1945+ commentInProgress , case_insensitive , blankLinesAllowed , prefixParser ):
1946+ "Builds a PyParsing parser for interpreting user commands."
1947+
1948+ # Build several parsing components that are eventually compiled into overall parser
1949+ output_destination_parser = (pyparsing .Literal (redirector * 2 ) |
1950+ (pyparsing .WordStart () + redirector ) |
1951+ pyparsing .Regex ('[^=]' + redirector ))('output' )
1952+
1953+ terminator_parser = pyparsing .Or (
1954+ [(hasattr (t , 'parseString' ) and t ) or pyparsing .Literal (t ) for t in terminators ])('terminator' )
1955+ string_end = pyparsing .stringEnd ^ '\n EOF'
1956+ multilineCommand = pyparsing .Or (
1957+ [pyparsing .Keyword (c , caseless = case_insensitive ) for c in multilineCommands ])('multilineCommand' )
1958+ oneline_command = (~ multilineCommand + pyparsing .Word (legalChars ))('command' )
1959+ pipe = pyparsing .Keyword ('|' , identChars = '|' )
1960+ do_not_parse = self .commentGrammars | commentInProgress | pyparsing .quotedString
1961+ after_elements = \
1962+ pyparsing .Optional (pipe + pyparsing .SkipTo (output_destination_parser ^ string_end , ignore = do_not_parse )('pipeTo' )) + \
1963+ pyparsing .Optional (output_destination_parser +
1964+ pyparsing .SkipTo (string_end ,
1965+ ignore = do_not_parse ).setParseAction (lambda x : x [0 ].strip ())('outputTo' ))
1966+ if case_insensitive :
1967+ multilineCommand .setParseAction (lambda x : x [0 ].lower ())
1968+ oneline_command .setParseAction (lambda x : x [0 ].lower ())
1969+ if blankLinesAllowed :
1970+ blankLineTerminationParser = pyparsing .NoMatch
1971+ else :
1972+ blankLineTerminator = (pyparsing .lineEnd + pyparsing .lineEnd )('terminator' )
1973+ blankLineTerminator .setResultsName ('terminator' )
1974+ blankLineTerminationParser = ((multilineCommand ^ oneline_command ) +
1975+ pyparsing .SkipTo (blankLineTerminator ,
1976+ ignore = do_not_parse ).setParseAction (
1977+ lambda x : x [0 ].strip ())('args' ) +
1978+ blankLineTerminator )('statement' )
1979+
1980+ multilineParser = (((multilineCommand ^ oneline_command ) +
1981+ pyparsing .SkipTo (terminator_parser ,
1982+ ignore = do_not_parse ).setParseAction (
1983+ lambda x : x [0 ].strip ())('args' ) + terminator_parser )('statement' ) +
1984+ pyparsing .SkipTo (output_destination_parser ^ pipe ^ string_end , ignore = do_not_parse ).setParseAction (
1985+ lambda x : x [0 ].strip ())('suffix' ) + after_elements )
1986+ multilineParser .ignore (commentInProgress )
1987+
1988+ singleLineParser = ((oneline_command +
1989+ pyparsing .SkipTo (terminator_parser ^ string_end ^ pipe ^ output_destination_parser ,
1990+ ignore = do_not_parse ).setParseAction (
1991+ lambda x : x [0 ].strip ())('args' ))('statement' ) +
1992+ pyparsing .Optional (terminator_parser ) + after_elements )
1993+
1994+ blankLineTerminationParser = blankLineTerminationParser .setResultsName ('statement' )
1995+
1996+ parser = prefixParser + (
1997+ string_end |
1998+ multilineParser |
1999+ singleLineParser |
2000+ blankLineTerminationParser |
2001+ multilineCommand + pyparsing .SkipTo (string_end , ignore = do_not_parse )
2002+ )
2003+ parser .ignore (self .commentGrammars )
2004+ return parser
2005+
2006+ def _build_input_source_parser (self , legalChars , commentInProgress ):
2007+ "Builds a PyParsing parser for alternate user input sources (from file, pipe, etc.)"
2008+
2009+ input_mark = pyparsing .Literal ('<' )
2010+ input_mark .setParseAction (lambda x : '' )
2011+ file_name = pyparsing .Word (legalChars + '/\\ ' )
2012+ input_from = file_name ('inputFrom' )
2013+ input_from .setParseAction (replace_with_file_contents )
2014+ # a not-entirely-satisfactory way of distinguishing < as in "import from" from <
2015+ # as in "lesser than"
2016+ inputParser = input_mark + pyparsing .Optional (input_from ) + pyparsing .Optional ('>' ) + \
2017+ pyparsing .Optional (file_name ) + (pyparsing .stringEnd | '|' )
2018+ inputParser .ignore (commentInProgress )
2019+ return inputParser
2020+
2021+ def parsed (self , raw ):
2022+ """ This function is where the actual parsing of each line occurs.
2023+
2024+ :param raw: str - the line of text as it was entered
2025+ :return: ParsedString - custom subclass of str with extra attributes
2026+ """
2027+ if isinstance (raw , ParsedString ):
2028+ p = raw
2029+ else :
2030+ # preparse is an overridable hook; default makes no changes
2031+ s = self .preparse (raw )
2032+ s = self .input_source_parser .transformString (s .lstrip ())
2033+ s = self .commentGrammars .transformString (s )
2034+ for (shortcut , expansion ) in self .shortcuts :
2035+ if s .lower ().startswith (shortcut ):
2036+ s = s .replace (shortcut , expansion + ' ' , 1 )
2037+ break
2038+ try :
2039+ result = self .main_parser .parseString (s )
2040+ except pyparsing .ParseException :
2041+ # If we have a parsing failure, treat it is an empty command and move to next prompt
2042+ result = self .main_parser .parseString ('' )
2043+ result ['raw' ] = raw
2044+ result ['command' ] = result .multilineCommand or result .command
2045+ result = self .postparse (result )
2046+ p = ParsedString (result .args )
2047+ p .parsed = result
2048+ p .parser = self .parsed
2049+ return p
2050+
2051+
2052+
2053+
20142054class HistoryItem (str ):
20152055 """Class used to represent an item in the History list.
20162056
0 commit comments