summaryrefslogtreecommitdiff
path: root/cmd2/parsing.py
diff options
context:
space:
mode:
Diffstat (limited to 'cmd2/parsing.py')
-rw-r--r--cmd2/parsing.py73
1 files changed, 64 insertions, 9 deletions
diff --git a/cmd2/parsing.py b/cmd2/parsing.py
index 1d22ccb8..82e8ee39 100644
--- a/cmd2/parsing.py
+++ b/cmd2/parsing.py
@@ -12,6 +12,51 @@ import attr
from . import constants
from . import utils
+# Pattern used to find normal argument
+# Match strings like: {5}, {{{{{4}, {2}}}}}
+macro_normal_arg_pattern = re.compile(r'(?<!\{)\{\d+\}|\{\d+\}(?!\})')
+
+# Pattern used to find escaped arguments (2 or more braces on each side of digit)
+# Match strings like: {{5}}, {{{{{4}}, {{2}}}}}, {{{4}}}
+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 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), type=int)
+
+ # The number that appears between the braces
+ number = attr.ib(validator=attr.validators.instance_of(int), type=int)
+
+ # Tells if this argument is escaped and therefore needs to be unescaped
+ is_escaped = attr.ib(validator=attr.validators.instance_of(bool), type=bool)
+
+
+@attr.s(frozen=True)
+class Macro:
+ """Defines a cmd2 macro"""
+
+ # Name of the macro
+ name = attr.ib(validator=attr.validators.instance_of(str), type=str)
+
+ # The string the macro resolves to
+ value = attr.ib(validator=attr.validators.instance_of(str), type=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), type=int)
+
+ # Used to fill in argument placeholders in the macro
+ arg_list = attr.ib(factory=list, validator=attr.validators.instance_of(list), type=List[MacroArg])
+
@attr.s(frozen=True)
class Statement(str):
@@ -246,24 +291,34 @@ class StatementParser:
expr = r'\A\s*(\S*?)({})'.format(second_group)
self._command_pattern = re.compile(expr)
- def is_valid_command(self, word: str) -> Tuple[bool, str]:
- """Determine whether a word is a valid alias.
+ def is_valid_command(self, word: str, allow_shortcut: bool) -> Tuple[bool, str]:
+ """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 = "Aliases {}".format(errmsg)
"""
valid = False
- errmsg = 'whitespace, quotes, '
+ if not word:
+ return False, 'cannot be an empty string'
+
+ if not allow_shortcut:
+ errmsg = 'cannot start with a shortcut: '
+ errmsg += ', '.join(shortcut for (shortcut, expansion) in self.shortcuts)
+ for (shortcut, expansion) in self.shortcuts:
+ if word.startswith(shortcut):
+ return False, errmsg
+
+ errmsg = 'cannot contain: whitespace, quotes, '
errchars = []
errchars.extend(constants.REDIRECTION_CHARS)
errchars.extend(self.terminators)