summaryrefslogtreecommitdiff
path: root/cmd2/parsing.py
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
committerTodd Leonhardt <todd.leonhardt@gmail.com>2018-09-30 11:17:04 -0400
commit81ad085cffc8f8af7fb7884bcc5db6fcede09df6 (patch)
treea197a6c81767f10b62d6ac6acfd46e42357a8289 /cmd2/parsing.py
parent85a21ef39467dc24a6f6f8c4f3ac656f24d6ff90 (diff)
parent61d5703cd3586b3460669a6260cf903c9863b240 (diff)
downloadcmd2-git-81ad085cffc8f8af7fb7884bcc5db6fcede09df6.tar.gz
Merged master into transcript_fixes branch and resolved conflicts
Diffstat (limited to 'cmd2/parsing.py')
-rw-r--r--cmd2/parsing.py95
1 files changed, 77 insertions, 18 deletions
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 8edfacb9..e90eac43 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -14,6 +14,55 @@ from . import utils
@attr.s(frozen=True)
+class MacroArg:
+ """
+ Information used to replace or unescape arguments in a macro value when the macro is resolved
+ Normal argument syntax : {5}
+ Escaped argument syntax: {{5}}
+ """
+ # The starting index of this argument in the macro value
+ start_index = attr.ib(validator=attr.validators.instance_of(int))
+
+ # The number string that appears between the braces
+ # This is a string instead of an int because we support unicode digits and must be able
+ # to reproduce this string later
+ number_str = attr.ib(validator=attr.validators.instance_of(str))
+
+ # Tells if this argument is escaped and therefore needs to be unescaped
+ is_escaped = attr.ib(validator=attr.validators.instance_of(bool))
+
+ # Pattern used to find normal argument
+ # Digits surrounded by exactly 1 brace on a side and 1 or more braces on the opposite side
+ # Match strings like: {5}, {{{{{4}, {2}}}}}
+ macro_normal_arg_pattern = re.compile(r'(?<!{){\d+}|{\d+}(?!})')
+
+ # Pattern used to find escaped arguments
+ # Digits surrounded by 2 or more braces on both sides
+ # Match strings like: {{5}}, {{{{{4}}, {{2}}}}}
+ macro_escaped_arg_pattern = re.compile(r'{{2}\d+}{2}')
+
+ # Finds a string of digits
+ digit_pattern = re.compile(r'\d+')
+
+
+@attr.s(frozen=True)
+class Macro:
+ """Defines a cmd2 macro"""
+
+ # Name of the macro
+ name = attr.ib(validator=attr.validators.instance_of(str))
+
+ # The string the macro resolves to
+ value = attr.ib(validator=attr.validators.instance_of(str))
+
+ # The required number of args the user has to pass to this macro
+ required_arg_count = attr.ib(validator=attr.validators.instance_of(int))
+
+ # Used to fill in argument placeholders in the macro
+ arg_list = attr.ib(default=attr.Factory(list), validator=attr.validators.instance_of(list))
+
+
+@attr.s(frozen=True)
class Statement(str):
"""String subclass with additional attributes to store the results of parsing.
@@ -81,34 +130,34 @@ class Statement(str):
argv[1:], which strips them all off for you.
"""
# the arguments, but not the command, nor the output redirection clauses.
- args = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ args = attr.ib(default='', validator=attr.validators.instance_of(str))
# string containing exactly what we input by the user
- raw = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ raw = attr.ib(default='', validator=attr.validators.instance_of(str))
# the command, i.e. the first whitespace delimited word
- command = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ command = attr.ib(default='', validator=attr.validators.instance_of(str))
# list of arguments to the command, not including any output redirection or terminators; quoted args remain quoted
- arg_list = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[str])
+ arg_list = attr.ib(default=attr.Factory(list), validator=attr.validators.instance_of(list))
# if the command is a multiline command, the name of the command, otherwise empty
- multiline_command = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ multiline_command = attr.ib(default='', validator=attr.validators.instance_of(str))
# the character which terminated the multiline command, if there was one
- terminator = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ terminator = attr.ib(default='', validator=attr.validators.instance_of(str))
# characters appearing after the terminator but before output redirection, if any
- suffix = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ suffix = attr.ib(default='', validator=attr.validators.instance_of(str))
# if output was piped to a shell command, the shell command as a list of tokens
- pipe_to = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[str])
+ pipe_to = attr.ib(default=attr.Factory(list), validator=attr.validators.instance_of(list))
# if output was redirected, the redirection token, i.e. '>>'
- output = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ output = attr.ib(default='', validator=attr.validators.instance_of(str))
# if output was redirected, the destination file
- output_to = attr.ib(default='', validator=attr.validators.instance_of(str), type=str)
+ output_to = attr.ib(default='', validator=attr.validators.instance_of(str))
def __new__(cls, value: object, *pos_args, **kw_args):
"""Create a new instance of Statement.
@@ -247,23 +296,33 @@ class StatementParser:
self._command_pattern = re.compile(expr)
def is_valid_command(self, word: str) -> Tuple[bool, str]:
- """Determine whether a word is a valid alias.
+ """Determine whether a word is a valid name for a command.
- Aliases can not include redirection characters, whitespace,
- or termination characters.
+ Commands can not include redirection characters, whitespace,
+ or termination characters. They also cannot start with a
+ shortcut.
- If word is not a valid command, return False and a comma
- separated string of characters that can not appear in a command.
+ If word is not a valid command, return False and error text
This string is suitable for inclusion in an error message of your
choice:
- valid, invalidchars = statement_parser.is_valid_command('>')
+ valid, errmsg = statement_parser.is_valid_command('>')
if not valid:
- errmsg = "Aliases can not contain: {}".format(invalidchars)
+ errmsg = "Alias {}".format(errmsg)
"""
valid = False
- errmsg = 'whitespace, quotes, '
+ if not word:
+ return False, 'cannot be an empty string'
+
+ for (shortcut, _) in self.shortcuts:
+ if word.startswith(shortcut):
+ # Build an error string with all shortcuts listed
+ errmsg = 'cannot start with a shortcut: '
+ errmsg += ', '.join(shortcut for (shortcut, _) in self.shortcuts)
+ return False, errmsg
+
+ errmsg = 'cannot contain: whitespace, quotes, '
errchars = []
errchars.extend(constants.REDIRECTION_CHARS)
errchars.extend(self.terminators)