|
|
@ -32,18 +32,21 @@ class Completion(Exception):
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class CompletionUnavailable(Completion):
|
|
|
|
class CompletionUnavailable(Completion):
|
|
|
|
"""Argument completion unavailable."""
|
|
|
|
"""Argument completion unavailable."""
|
|
|
|
|
|
|
|
|
|
|
|
message: str = 'No completions available.'
|
|
|
|
message: str = 'No completions available.'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class CompletionError(Completion):
|
|
|
|
class CompletionError(Completion):
|
|
|
|
"""Argument completion error."""
|
|
|
|
"""Argument completion error."""
|
|
|
|
|
|
|
|
|
|
|
|
message: t.Optional[str] = None
|
|
|
|
message: t.Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class CompletionSuccess(Completion):
|
|
|
|
class CompletionSuccess(Completion):
|
|
|
|
"""Successful argument completion result."""
|
|
|
|
"""Successful argument completion result."""
|
|
|
|
|
|
|
|
|
|
|
|
list_mode: bool
|
|
|
|
list_mode: bool
|
|
|
|
consumed: str
|
|
|
|
consumed: str
|
|
|
|
continuation: str
|
|
|
|
continuation: str
|
|
|
@ -72,6 +75,7 @@ class CompletionSuccess(Completion):
|
|
|
|
|
|
|
|
|
|
|
|
class ParserMode(enum.Enum):
|
|
|
|
class ParserMode(enum.Enum):
|
|
|
|
"""Mode the parser is operating in."""
|
|
|
|
"""Mode the parser is operating in."""
|
|
|
|
|
|
|
|
|
|
|
|
PARSE = enum.auto()
|
|
|
|
PARSE = enum.auto()
|
|
|
|
COMPLETE = enum.auto()
|
|
|
|
COMPLETE = enum.auto()
|
|
|
|
LIST = enum.auto()
|
|
|
|
LIST = enum.auto()
|
|
|
@ -84,6 +88,7 @@ class ParserError(Exception):
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class ParserBoundary:
|
|
|
|
class ParserBoundary:
|
|
|
|
"""Boundary details for parsing composite input."""
|
|
|
|
"""Boundary details for parsing composite input."""
|
|
|
|
|
|
|
|
|
|
|
|
delimiters: str
|
|
|
|
delimiters: str
|
|
|
|
required: bool
|
|
|
|
required: bool
|
|
|
|
match: t.Optional[str] = None
|
|
|
|
match: t.Optional[str] = None
|
|
|
@ -93,6 +98,7 @@ class ParserBoundary:
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class ParserState:
|
|
|
|
class ParserState:
|
|
|
|
"""State of the composite argument parser."""
|
|
|
|
"""State of the composite argument parser."""
|
|
|
|
|
|
|
|
|
|
|
|
mode: ParserMode
|
|
|
|
mode: ParserMode
|
|
|
|
remainder: str = ''
|
|
|
|
remainder: str = ''
|
|
|
|
consumed: str = ''
|
|
|
|
consumed: str = ''
|
|
|
@ -194,11 +200,13 @@ class ParserState:
|
|
|
|
@dataclasses.dataclass
|
|
|
|
@dataclasses.dataclass
|
|
|
|
class DocumentationState:
|
|
|
|
class DocumentationState:
|
|
|
|
"""State of the composite argument parser's generated documentation."""
|
|
|
|
"""State of the composite argument parser's generated documentation."""
|
|
|
|
|
|
|
|
|
|
|
|
sections: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
|
|
sections: dict[str, str] = dataclasses.field(default_factory=dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Parser(metaclass=abc.ABCMeta):
|
|
|
|
class Parser(metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for all composite argument parsers."""
|
|
|
|
"""Base class for all composite argument parsers."""
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
@abc.abstractmethod
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
@ -210,6 +218,7 @@ class Parser(metaclass=abc.ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
class MatchConditions(enum.Flag):
|
|
|
|
class MatchConditions(enum.Flag):
|
|
|
|
"""Acceptable condition(s) for matching user input to available choices."""
|
|
|
|
"""Acceptable condition(s) for matching user input to available choices."""
|
|
|
|
|
|
|
|
|
|
|
|
CHOICE = enum.auto()
|
|
|
|
CHOICE = enum.auto()
|
|
|
|
"""Match any choice."""
|
|
|
|
"""Match any choice."""
|
|
|
|
ANY = enum.auto()
|
|
|
|
ANY = enum.auto()
|
|
|
@ -220,6 +229,7 @@ class MatchConditions(enum.Flag):
|
|
|
|
|
|
|
|
|
|
|
|
class DynamicChoicesParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
class DynamicChoicesParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for composite argument parsers which use a list of choices that can be generated during completion."""
|
|
|
|
"""Base class for composite argument parsers which use a list of choices that can be generated during completion."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
def __init__(self, conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
self.conditions = conditions
|
|
|
|
self.conditions = conditions
|
|
|
|
|
|
|
|
|
|
|
@ -275,6 +285,7 @@ class DynamicChoicesParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
class ChoicesParser(DynamicChoicesParser):
|
|
|
|
class ChoicesParser(DynamicChoicesParser):
|
|
|
|
"""Composite argument parser which relies on a static list of choices."""
|
|
|
|
"""Composite argument parser which relies on a static list of choices."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, choices: list[str], conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
def __init__(self, choices: list[str], conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
self.choices = choices
|
|
|
|
self.choices = choices
|
|
|
|
|
|
|
|
|
|
|
@ -291,6 +302,7 @@ class ChoicesParser(DynamicChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class EnumValueChoicesParser(ChoicesParser):
|
|
|
|
class EnumValueChoicesParser(ChoicesParser):
|
|
|
|
"""Composite argument parser which relies on a static list of choices derived from the values of an enum."""
|
|
|
|
"""Composite argument parser which relies on a static list of choices derived from the values of an enum."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, enum_type: t.Type[enum.Enum], conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
def __init__(self, enum_type: t.Type[enum.Enum], conditions: MatchConditions = MatchConditions.CHOICE) -> None:
|
|
|
|
self.enum_type = enum_type
|
|
|
|
self.enum_type = enum_type
|
|
|
|
|
|
|
|
|
|
|
@ -304,6 +316,7 @@ class EnumValueChoicesParser(ChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class IntegerParser(DynamicChoicesParser):
|
|
|
|
class IntegerParser(DynamicChoicesParser):
|
|
|
|
"""Composite argument parser for integers."""
|
|
|
|
"""Composite argument parser for integers."""
|
|
|
|
|
|
|
|
|
|
|
|
PATTERN = re.compile('^[1-9][0-9]*$')
|
|
|
|
PATTERN = re.compile('^[1-9][0-9]*$')
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, maximum: t.Optional[int] = None) -> None:
|
|
|
|
def __init__(self, maximum: t.Optional[int] = None) -> None:
|
|
|
@ -341,6 +354,7 @@ class IntegerParser(DynamicChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class BooleanParser(ChoicesParser):
|
|
|
|
class BooleanParser(ChoicesParser):
|
|
|
|
"""Composite argument parser for boolean (yes/no) values."""
|
|
|
|
"""Composite argument parser for boolean (yes/no) values."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
super().__init__(['yes', 'no'])
|
|
|
|
super().__init__(['yes', 'no'])
|
|
|
|
|
|
|
|
|
|
|
@ -352,6 +366,7 @@ class BooleanParser(ChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class AnyParser(ChoicesParser):
|
|
|
|
class AnyParser(ChoicesParser):
|
|
|
|
"""Composite argument parser which accepts any input value."""
|
|
|
|
"""Composite argument parser which accepts any input value."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, nothing: bool = False, no_match_message: t.Optional[str] = None) -> None:
|
|
|
|
def __init__(self, nothing: bool = False, no_match_message: t.Optional[str] = None) -> None:
|
|
|
|
self.no_match_message = no_match_message
|
|
|
|
self.no_match_message = no_match_message
|
|
|
|
|
|
|
|
|
|
|
@ -379,6 +394,7 @@ class AnyParser(ChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class RelativePathNameParser(DynamicChoicesParser):
|
|
|
|
class RelativePathNameParser(DynamicChoicesParser):
|
|
|
|
"""Composite argument parser for relative path names."""
|
|
|
|
"""Composite argument parser for relative path names."""
|
|
|
|
|
|
|
|
|
|
|
|
RELATIVE_NAMES = ['.', '..']
|
|
|
|
RELATIVE_NAMES = ['.', '..']
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, choices: list[str]) -> None:
|
|
|
|
def __init__(self, choices: list[str]) -> None:
|
|
|
@ -400,6 +416,7 @@ class RelativePathNameParser(DynamicChoicesParser):
|
|
|
|
|
|
|
|
|
|
|
|
class FileParser(Parser):
|
|
|
|
class FileParser(Parser):
|
|
|
|
"""Composite argument parser for absolute or relative file paths."""
|
|
|
|
"""Composite argument parser for absolute or relative file paths."""
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, state: ParserState) -> str:
|
|
|
|
def parse(self, state: ParserState) -> str:
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
if state.mode == ParserMode.PARSE:
|
|
|
|
if state.mode == ParserMode.PARSE:
|
|
|
@ -432,6 +449,7 @@ class FileParser(Parser):
|
|
|
|
|
|
|
|
|
|
|
|
class AbsolutePathParser(Parser):
|
|
|
|
class AbsolutePathParser(Parser):
|
|
|
|
"""Composite argument parser for absolute file paths. Paths are only verified for proper syntax, not for existence."""
|
|
|
|
"""Composite argument parser for absolute file paths. Paths are only verified for proper syntax, not for existence."""
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
path = ''
|
|
|
|
path = ''
|
|
|
@ -443,13 +461,14 @@ class AbsolutePathParser(Parser):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
path += ChoicesParser([PATH_DELIMITER]).parse(state)
|
|
|
|
path += ChoicesParser([PATH_DELIMITER]).parse(state)
|
|
|
|
|
|
|
|
|
|
|
|
path += (boundary.match or '')
|
|
|
|
path += boundary.match or ''
|
|
|
|
|
|
|
|
|
|
|
|
return path
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamespaceParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
class NamespaceParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for composite argument parsers that store their results in a namespace."""
|
|
|
|
"""Base class for composite argument parsers that store their results in a namespace."""
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
namespace = state.current_namespace
|
|
|
|
namespace = state.current_namespace
|
|
|
@ -496,6 +515,7 @@ class NamespaceParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
class NamespaceWrappedParser(NamespaceParser):
|
|
|
|
class NamespaceWrappedParser(NamespaceParser):
|
|
|
|
"""Composite argument parser that wraps a non-namespace parser and stores the result in a namespace."""
|
|
|
|
"""Composite argument parser that wraps a non-namespace parser and stores the result in a namespace."""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, dest: str, parser: Parser) -> None:
|
|
|
|
def __init__(self, dest: str, parser: Parser) -> None:
|
|
|
|
self._dest = dest
|
|
|
|
self._dest = dest
|
|
|
|
self.parser = parser
|
|
|
|
self.parser = parser
|
|
|
@ -512,6 +532,7 @@ class NamespaceWrappedParser(NamespaceParser):
|
|
|
|
|
|
|
|
|
|
|
|
class KeyValueParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
class KeyValueParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for key/value composite argument parsers."""
|
|
|
|
"""Base class for key/value composite argument parsers."""
|
|
|
|
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
@abc.abstractmethod
|
|
|
|
def get_parsers(self, state: ParserState) -> dict[str, Parser]:
|
|
|
|
def get_parsers(self, state: ParserState) -> dict[str, Parser]:
|
|
|
|
"""Return a dictionary of key names and value parsers."""
|
|
|
|
"""Return a dictionary of key names and value parsers."""
|
|
|
@ -538,6 +559,7 @@ class KeyValueParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
class PairParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
class PairParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for composite argument parsers consisting of a left and right argument parser, with input separated by a delimiter."""
|
|
|
|
"""Base class for composite argument parsers consisting of a left and right argument parser, with input separated by a delimiter."""
|
|
|
|
|
|
|
|
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
def parse(self, state: ParserState) -> t.Any:
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
"""Parse the input from the given state and return the result."""
|
|
|
|
namespace = self.create_namespace()
|
|
|
|
namespace = self.create_namespace()
|
|
|
@ -577,6 +599,7 @@ class PairParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
|
|
|
|
|
|
|
|
class TypeParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
class TypeParser(Parser, metaclass=abc.ABCMeta):
|
|
|
|
"""Base class for composite argument parsers which parse a type name, a colon and then parse results based on the type given by the type name."""
|
|
|
|
"""Base class for composite argument parsers which parse a type name, a colon and then parse results based on the type given by the type name."""
|
|
|
|
|
|
|
|
|
|
|
|
def get_parsers(self, state: ParserState) -> dict[str, Parser]: # pylint: disable=unused-argument
|
|
|
|
def get_parsers(self, state: ParserState) -> dict[str, Parser]: # pylint: disable=unused-argument
|
|
|
|
"""Return a dictionary of type names and type parsers."""
|
|
|
|
"""Return a dictionary of type names and type parsers."""
|
|
|
|
return self.get_stateless_parsers()
|
|
|
|
return self.get_stateless_parsers()
|
|
|
|