Skip to content

Commit

Permalink
sistana: feat: option header separators
Browse files Browse the repository at this point in the history
  • Loading branch information
GreyElaina committed Sep 25, 2024
1 parent a0f67a3 commit b0a0b08
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 5 deletions.
20 changes: 20 additions & 0 deletions src/arclet/alconna/sistana/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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,所以这里不会准许跳转。
Expand Down
29 changes: 24 additions & 5 deletions src/arclet/alconna/sistana/model/pattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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

Expand All @@ -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

0 comments on commit b0a0b08

Please sign in to comment.