From b0a0b0848bc7592bafc3ec75e3c9a918f299a800 Mon Sep 17 00:00:00 2001 From: Elaina Date: Wed, 25 Sep 2024 17:50:54 +0800 Subject: [PATCH] sistana: feat: option header separators --- src/arclet/alconna/sistana/analyzer.py | 20 ++++++++++++++ src/arclet/alconna/sistana/model/pattern.py | 29 +++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/arclet/alconna/sistana/analyzer.py b/src/arclet/alconna/sistana/analyzer.py index 5abb8e23..6a042550 100644 --- a/src/arclet/alconna/sistana/analyzer.py +++ b/src/arclet/alconna/sistana/analyzer.py @@ -238,6 +238,25 @@ def loopflow(self, snapshot: AnalyzeSnapshot[T], buffer: Buffer[T]) -> LoopflowD continue # else: 进了 track process. + if context.separator_optbind is not None: + opt_matched = False + + for opt_keyword, separators in context.separator_optbind.items(): + option = context.options[opt_keyword] + + keyword_part, *tail = token.val.split(separators, 1) + if keyword_part == opt_keyword or keyword_part in option.aliases: + opt_matched = True + token.apply() + buffer.add_to_ahead(keyword_part) + if tail: + buffer.pushleft(tail[0]) + + break + + if opt_matched: + continue + if context.compact_keywords is not None: prefix = context.compact_keywords.get_closest_prefix(token.val) if prefix: @@ -257,6 +276,7 @@ def loopflow(self, snapshot: AnalyzeSnapshot[T], buffer: Buffer[T]) -> LoopflowD # 但这里有个有趣的例子,比如说我们有 `-v -vv`,这里 v 是一个 duplicate,而 duplicate 仍然可以重入并继续分配,而其 assignable 则成为无关变量。 # 还有一个有趣的例子:如果一个 duplicate 的 option,他具备有几个 default=Value(...) 的 fragment,则多次触发会怎么样? # 答案是不会怎么样:还记得吗?duplicate 会在回退到 subcommand 时被 pop_track,也就会将 Track 换成另外一个,所以不会引发任何问题(比如你担心的行为不一致)。 + # 顺便一提,我们在这方面做了特殊处理:该 Option 对应的 Track 上,所有 Fragment 的 Rx 调用 rx_prev 都将能获取到之前的 assignes。 # else: 你是不是手动构造了 TrieHard? # 由于默认 redirect 是 False,所以这里不会准许跳转。 diff --git a/src/arclet/alconna/sistana/model/pattern.py b/src/arclet/alconna/sistana/model/pattern.py index 96c0a8a7..9ff7c433 100644 --- a/src/arclet/alconna/sistana/model/pattern.py +++ b/src/arclet/alconna/sistana/model/pattern.py @@ -32,6 +32,8 @@ class SubcommandPattern: compact_header: bool = False satisfy_previous: bool = True header_fragment: _Fragment | None = None + separator_optbind: dict[str, str] | None = None + # opt_keyword -> header_separators @classmethod def build( @@ -102,7 +104,7 @@ def subcommand( satisfy_previous: bool = True, header_fragment: _Fragment | None = None, ): - pattern = SubcommandPattern( + pattern = self.subcommands[header] = SubcommandPattern( header=header, preset=Preset(), soft_keyword=soft_keyword, @@ -128,20 +130,29 @@ def option( keyword: str, *fragments: _Fragment, aliases: Iterable[str] = (), + separators: str | None = None, + hybrid_separators: bool = False, soft_keyword: bool = False, allow_duplicate: bool = False, compact_header: bool = False, compact_aliases: bool = False, header_fragment: _Fragment | None = None, + header_separators: str | None = None, ): - pattern = OptionPattern( + if separators is None: + separators = self.separators + elif hybrid_separators: + separators = separators + self.separators + + pattern = self.options[keyword] = OptionPattern( keyword, - separators=self.separators, + aliases=list(aliases), + separators=separators, allow_duplicate=allow_duplicate, soft_keyword=soft_keyword, - header_fragment=header_fragment + header_fragment=header_fragment, + header_separators=header_separators, ) - self.options[keyword] = pattern for alias in aliases: self.options[alias] = pattern @@ -151,15 +162,23 @@ def option( if compact_header: self.compact_keywords = TrieHard([keyword, *aliases, *(self.compact_keywords or []), *(aliases if compact_aliases else [])]) + if header_separators: + if self.separator_optbind is None: + self.separator_optbind = {keyword: header_separators} + else: + self.separator_optbind[keyword] = header_separators + return self @dataclass(**safe_dcls_kw(slots=True)) class OptionPattern: keyword: str + aliases: list[str] = field(default_factory=list) separators: str = SEPARATORS soft_keyword: bool = False allow_duplicate: bool = False keep_previous_assignes: bool = False header_fragment: _Fragment | None = None + header_separators: str | None = None