summaryrefslogtreecommitdiff
path: root/cmd2/parsing.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/parsing.py')
-rw-r--r--cmd2/parsing.py108
1 files changed, 70 insertions, 38 deletions
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 8d59aedb..b220f1c4 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -5,7 +5,7 @@
import os
import re
import shlex
-from typing import List, Tuple
+from typing import List, Tuple, Dict
from . import constants
from . import utils
@@ -21,6 +21,10 @@ class Statement(str):
need a place to capture the additional output of the command parsing, so we add
our own attributes to this subclass.
+ Instances of this class should not be created by anything other than the
+ `StatementParser.parse()` method, nor should any of the attributes be modified
+ once the object is created.
+
The string portion of the class contains the arguments, but not the command, nor
the output redirection clauses.
@@ -54,18 +58,39 @@ class Statement(str):
:type output_to: str or None
"""
- def __init__(self, obj):
- super().__init__()
- self.raw = str(obj)
- self.command = None
- self.multiline_command = None
- self.args = None
- self.argv = None
- self.terminator = None
- self.suffix = None
- self.pipe_to = None
- self.output = None
- self.output_to = None
+ def __new__(cls,
+ obj: object,
+ *,
+ raw: str = None,
+ command: str = None,
+ args: str = None,
+ argv: List[str] = None,
+ multiline_command: str = None,
+ terminator: str = None,
+ suffix: str = None,
+ pipe_to: str = None,
+ output: str = None,
+ output_to:str = None
+ ):
+ """Create a new instance of Statement
+
+ We must override __new__ because we are subclassing `str` which is
+ immutable.
+ """
+ stmt = str.__new__(cls, obj)
+ object.__setattr__(stmt, "raw", raw)
+ object.__setattr__(stmt, "command", command)
+ object.__setattr__(stmt, "args", args)
+ if argv is None:
+ argv = []
+ object.__setattr__(stmt, "argv", argv)
+ object.__setattr__(stmt, "multiline_command", multiline_command)
+ object.__setattr__(stmt, "terminator", terminator)
+ object.__setattr__(stmt, "suffix", suffix)
+ object.__setattr__(stmt, "pipe_to", pipe_to)
+ object.__setattr__(stmt, "output", output)
+ object.__setattr__(stmt, "output_to", output_to)
+ return stmt
@property
def command_and_args(self):
@@ -82,6 +107,13 @@ class Statement(str):
rtn = None
return rtn
+ def __setattr__(self, name, value):
+ """Statement instances should feel immutable; raise ValueError"""
+ raise ValueError
+
+ def __delattr__(self, name):
+ """Statement instances should feel immutable; raise ValueError"""
+ raise ValueError
class StatementParser:
"""Parse raw text into command components.
@@ -90,11 +122,11 @@ class StatementParser:
"""
def __init__(
self,
- allow_redirection=True,
- terminators=None,
- multiline_commands=None,
- aliases=None,
- shortcuts=None,
+ allow_redirection: bool = True,
+ terminators: List[str] = None,
+ multiline_commands: List[str] = None,
+ aliases: Dict[str, str] = None,
+ shortcuts: Dict[str, str] = None,
):
self.allow_redirection = allow_redirection
if terminators is None:
@@ -228,7 +260,7 @@ class StatementParser:
tokens = self._split_on_punctuation(list(lexer))
return tokens
- def parse(self, rawinput: str) -> Statement:
+ def parse(self, line: str) -> Statement:
"""Tokenize the input and parse it into a Statement object, stripping
comments, expanding aliases and shortcuts, and extracting output
redirection directives.
@@ -240,7 +272,7 @@ class StatementParser:
# we have to do this before we tokenize because tokenizing
# destroys all unquoted whitespace in the input
terminator = None
- if rawinput[-1:] == LINE_FEED:
+ if line[-1:] == LINE_FEED:
terminator = LINE_FEED
command = None
@@ -248,7 +280,7 @@ class StatementParser:
argv = None
# lex the input into a list of tokens
- tokens = self.tokenize(rawinput)
+ tokens = self.tokenize(line)
# of the valid terminators, find the first one to occur in the input
terminator_pos = len(tokens) + 1
@@ -360,19 +392,18 @@ class StatementParser:
# build the statement
# string representation of args must be an empty string instead of
# None for compatibility with standard library cmd
- statement = Statement('' if args is None else args)
- statement.raw = rawinput
- statement.command = command
- # if there are no args we will use None since we don't have to worry
- # about compatibility with standard library cmd
- statement.args = args
- statement.argv = list(map(lambda x: utils.strip_quotes(x), argv))
- statement.terminator = terminator
- statement.output = output
- statement.output_to = output_to
- statement.pipe_to = pipe_to
- statement.suffix = suffix
- statement.multiline_command = multiline_command
+ statement = Statement('' if args is None else args,
+ raw=line,
+ command=command,
+ args=args,
+ argv=list(map(lambda x: utils.strip_quotes(x), argv)),
+ multiline_command=multiline_command,
+ terminator=terminator,
+ suffix=suffix,
+ pipe_to=pipe_to,
+ output=output,
+ output_to=output_to,
+ )
return statement
def parse_command_only(self, rawinput: str) -> Statement:
@@ -422,10 +453,11 @@ class StatementParser:
# build the statement
# string representation of args must be an empty string instead of
# None for compatibility with standard library cmd
- statement = Statement('' if args is None else args)
- statement.raw = rawinput
- statement.command = command
- statement.args = args
+ statement = Statement('' if args is None else args,
+ raw=rawinput,
+ command=command,
+ args=args,
+ )
return statement
def _expand(self, line: str) -> str: