Skip to content

Commit d792ed8

Browse files
committed
🍻 mount Argv to Analyser
1 parent dbeeb66 commit d792ed8

File tree

7 files changed

+80
-97
lines changed

7 files changed

+80
-97
lines changed

src/arclet/alconna/_internal/_analyser.py

+11-11
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ def reset(self):
139139
self.value_result = None
140140
self.header_result = None
141141

142-
def process(self, argv: Argv[TDC], name_validated: bool = True) -> Self:
142+
def process(self, argv: Argv, name_validated: bool = True) -> Self:
143143
"""处理传入的参数集合
144144
145145
Args:
146-
argv (Argv[TDC]): 命令行参数
146+
argv (Argv): 命令行参数
147147
name_validated (bool, optional): 是否已经验证过名称. Defaults to True.
148148
149149
Returns:
@@ -187,31 +187,31 @@ class Analyser(SubAnalyser):
187187

188188
command: Alconna
189189
"""命令实例"""
190+
argv: Argv
191+
"""命令行参数"""
190192

191-
def __init__(self, alconna: Alconna, compiler: TCompile | None = None):
193+
def __init__(self, alconna: Alconna, argv: Argv, compiler: TCompile | None = None):
192194
"""初始化解析器
193195
194196
Args:
195197
alconna (Alconna): 命令实例
198+
argv (Argv): 命令行参数
196199
compiler (TCompile | None, optional): 编译器方法
197200
"""
198201
super().__init__(alconna)
199-
self._compiler = compiler or default_compiler
200-
201-
def compile(self):
202+
self.argv = argv
202203
self.extra_allow = not self.command.meta.strict or not self.command.namespace_config.strict
203-
self._compiler(self)
204-
command_manager.resolve(self.command).stack_params.base = self.compile_params
205-
return self
204+
(compiler or default_compiler)(self)
205+
self.argv.stack_params.base = self.compile_params
206206

207207
def __repr__(self):
208208
return f"<{self.__class__.__name__} of {self.command.path}>"
209209

210-
def process(self, argv: Argv[TDC], name_validated: bool = True) -> Exception | None:
210+
def process(self, argv: Argv, name_validated: bool = True) -> Exception | None:
211211
"""主体解析函数, 应针对各种情况进行解析
212212
213213
Args:
214-
argv (Argv[TDC]): 命令行参数
214+
argv (Argv): 命令行参数
215215
name_validated (bool, optional): 是否已经验证过名称. Defaults to True.
216216
"""
217217
if not self.header_result or not name_validated:

src/arclet/alconna/_internal/_handlers.py

+19-9
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from ..base import Option, Header
1212
from ..config import config
1313
from ..exceptions import (
14-
AlconnaException,
14+
AnalyseException,
1515
ArgumentMissing,
1616
FuzzyMatchSuccess,
1717
InvalidHeader,
1818
InvalidParam,
19-
PauseTriggered, ParamsUnmatched,
19+
PauseTriggered,
20+
ParamsUnmatched,
2021
)
2122
from ..model import HeadResult, OptionResult
2223
from ..typing import KWBool, MultiKeyWordVar, MultiVar, _AllParamPattern, _StrMulti
@@ -353,13 +354,12 @@ def analyse_option(analyser: SubAnalyser, argv: Argv, opt: Option, name_validate
353354
analyser.options_result[opt_n] = handle_action(opt, analyser.options_result[opt_n], opt_v)
354355

355356

356-
def analyse_compact_params(analyser: SubAnalyser, argv: Argv, prefix: str):
357+
def analyse_compact_params(analyser: SubAnalyser, argv: Argv):
357358
"""分析紧凑参数
358359
359360
Args:
360361
analyser (SubAnalyser): 当前解析器
361362
argv (Argv): 命令行参数
362-
prefix (str): 参数前缀
363363
"""
364364
exc = None
365365
for param in analyser.compact_params:
@@ -381,7 +381,7 @@ def analyse_compact_params(analyser: SubAnalyser, argv: Argv, prefix: str):
381381
else:
382382
analyser.subcommands_result[sparam.command.dest] = sparam.result()
383383
raise
384-
except AlconnaException:
384+
except AnalyseException:
385385
analyser.subcommands_result[sparam.command.dest] = sparam.result()
386386
raise
387387
else:
@@ -417,17 +417,22 @@ def analyse_param(analyser: SubAnalyser, argv: Argv, seps: str | None = None):
417417
argv (Argv): 命令行参数
418418
seps (str, optional): 指定的分隔符.
419419
"""
420+
# 每次调用都会尝试解析一个参数
420421
_text, _str = argv.next(seps)
422+
# analyser.compile_params 有命中,说明在当前子命令内有对应的选项/子命令
421423
if _str and _text and (_param := analyser.compile_params.get(_text)):
422-
if Option in _param.__class__.__mro__:
424+
# Help 之类的选项是 Option 子类, 得加上 __base__ 判断
425+
if _param.__class__ is Option or _param.__class__.__base__ is Option:
423426
oparam: Option = _param # type: ignore
424427
try:
428+
# 因为 _text 已经被确定为选项名,所以 name_validated 为 True
425429
analyse_option(analyser, argv, oparam, True)
426-
except AlconnaException as e:
430+
except AnalyseException as e:
427431
if not argv.error:
428432
argv.error = e
429433
return True
430434
sparam: SubAnalyser = _param # type: ignore
435+
# 禁止子命令重复解析
431436
if sparam.command.dest not in analyser.subcommands_result:
432437
try:
433438
sparam.process(argv)
@@ -441,26 +446,31 @@ def analyse_param(analyser: SubAnalyser, argv: Argv, seps: str | None = None):
441446
analyser.subcommands_result[sparam.command.dest] = sparam.result()
442447
if not argv.error:
443448
argv.error = e
444-
except AlconnaException as e1:
449+
except AnalyseException as e1:
445450
analyser.subcommands_result[sparam.command.dest] = sparam.result()
446451
if not argv.error:
447452
argv.error = e1
448453
else:
449454
analyser.subcommands_result[sparam.command.dest] = sparam.result()
450455
return True
456+
# 如果没有命中,则说明当前参数可能存在自定义分隔符,或者属于子命令的主参数,那么需要重新解析
451457
argv.rollback(_text)
452-
if _str and _text and analyser.compact_params and analyse_compact_params(analyser, argv, _text):
458+
# 尝试以紧凑参数解析
459+
if _str and _text and analyser.compact_params and analyse_compact_params(analyser, argv):
453460
return True
461+
# 主参数同样只允许解析一次
454462
if analyser.command.nargs and not analyser.args_result:
455463
analyser.args_result = analyse_args(argv, analyser.self_args)
456464
if analyser.args_result:
457465
return True
466+
# 若参数属于该子命令的同级/上级选项或子命令,则终止解析
458467
if _str and _text and _text in argv.stack_params.parents():
459468
return False
460469
if analyser.extra_allow:
461470
analyser.args_result.setdefault("$extra", []).append(_text)
462471
argv.next()
463472
return True
473+
# 给 Completion 打的洞,若此时 analyser 属于主命令, 则让其先解析完主命令
464474
elif _str and _text and not argv.stack_params.stack:
465475
if not argv.error:
466476
argv.error = ParamsUnmatched(lang.require("analyser", "param_unmatched").format(target=_text))

src/arclet/alconna/base.py

-6
Original file line numberDiff line numberDiff line change
@@ -417,22 +417,16 @@ def add(self, opt: Option | Subcommand) -> Self:
417417

418418

419419
class Help(Option):
420-
soft_keyword = False
421-
422420
def _calc_hash(self):
423421
return hash("$ALCONNA_BUILTIN_OPTION_HELP")
424422

425423

426424
class Shortcut(Option):
427-
soft_keyword = False
428-
429425
def _calc_hash(self):
430426
return hash("$ALCONNA_BUILTIN_OPTION_SHORTCUT")
431427

432428

433429
class Completion(Option):
434-
soft_keyword = False
435-
436430
def _calc_hash(self):
437431
return hash("$ALCONNA_BUILTIN_OPTION_COMPLETION")
438432

src/arclet/alconna/completion.py

+13-23
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def enter(self, content: list | None = None) -> EnterResult:
113113
Raises:
114114
ValueError: 当前没有可用的补全选项, 或者当前补全选项不可用。
115115
"""
116-
argv = command_manager.resolve(self.source.command)
116+
argv = command_manager.require(self.source.command).argv
117117
argv.raw_data = self.raw_data.copy()
118118
argv.bak_data = self.bak_data.copy()
119119
argv.current_index = self.current_index
@@ -226,17 +226,6 @@ def fresh(self, exc: PauseTriggered):
226226
comp_ctx: ContextModel[CompSession] = ContextModel("comp_ctx")
227227

228228

229-
def _prompt_unit(command: Alconna, argv: Argv, trig: Arg):
230-
if not (comp := trig.field.get_completion()):
231-
return [Prompt(command.formatter.param(trig), False)]
232-
if isinstance(comp, str):
233-
return [Prompt(f"{trig.name}: {comp}", False)]
234-
releases = argv.release(recover=True)
235-
target = str(releases[-1]) or str(releases[-2])
236-
o = list(filter(lambda x: target in x, comp)) or comp
237-
return [Prompt(f"{trig.name}: {i}", False, target) for i in o]
238-
239-
240229
def _prompt_none(command: Alconna, args_got: list[str], opts_got: list[str]):
241230
res: list[Prompt] = []
242231
if unit := next((arg for arg in command.args if arg.name not in args_got), None):
@@ -256,21 +245,22 @@ def _prompt_none(command: Alconna, args_got: list[str], opts_got: list[str]):
256245

257246
def prompt(command: Alconna, argv: Argv, args_got: list[str], opts_got: list[str], trigger: str | Arg | Subcommand | None = None):
258247
"""获取补全列表"""
259-
if isinstance(trigger, Arg):
260-
return _prompt_unit(command, argv, trigger)
261-
elif isinstance(trigger, Subcommand):
262-
return [Prompt(i) for i in argv.stack_params.stack[-1]]
263-
elif isinstance(trigger, str):
264-
res = list(filter(lambda x: trigger in x, argv.stack_params.base))
265-
if not res:
266-
return []
267-
out = [i for i in res if i not in opts_got]
268-
return [Prompt(i, True, trigger) for i in (out or res)]
269248
releases = argv.release(recover=True)
270249
target = str(releases[-1])
271250
if isinstance(releases[-1], str) and releases[-1] in command.namespace_config.builtin_option_name["completion"]:
272251
target = str(releases[-2])
273-
if _res := list(filter(lambda x: target in x and target != x, argv.stack_params.base)):
252+
if isinstance(trigger, Arg):
253+
if not (comp := trigger.field.get_completion()):
254+
return [Prompt(command.formatter.param(trigger), False)]
255+
if isinstance(comp, str):
256+
return [Prompt(f"{trigger.name}: {comp}", False)]
257+
o = list(filter(lambda x: target in x, comp)) or comp
258+
return [Prompt(f"{trigger.name}: {i}", False, target) for i in o]
259+
elif isinstance(trigger, Subcommand):
260+
return [Prompt(i) for i in argv.stack_params.stack[-1]]
261+
if isinstance(trigger, str):
262+
target = trigger
263+
if _res := list(filter(lambda x: target in x, argv.stack_params.base)):
274264
out = [i for i in _res if i not in opts_got]
275265
return [Prompt(i, True, target) for i in (out or _res)]
276266
return _prompt_none(command, args_got, opts_got)

src/arclet/alconna/core.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
from dataclasses import dataclass, field
66
from pathlib import Path
7-
from typing import Any, Callable, Generic, Literal, Sequence, TypeVar, cast, overload
7+
from typing import Any, Callable, Generic, Literal, Sequence, TypeVar, cast, overload, TYPE_CHECKING
88
from typing_extensions import Self
99
from weakref import WeakSet
1010

@@ -16,6 +16,7 @@
1616
from ._internal._handlers import handle_head_fuzzy, analyse_header
1717
from ._internal._shortcut import shortcut as _shortcut
1818
from .args import Arg, Args
19+
from .argv import Argv, __argv_type__
1920
from .arparma import Arparma, ArparmaBehavior, requirement_handler
2021
from .base import Completion, Help, Option, Shortcut, Subcommand, Header, SPECIAL_OPTIONS
2122
from .config import Namespace, config
@@ -48,11 +49,11 @@ def handle_argv():
4849

4950
def add_builtin_options(options: list[Option | Subcommand], cmd: Alconna, ns: Namespace) -> None:
5051
if "help" not in ns.disable_builtin_options:
51-
options.append(Help("|".join(ns.builtin_option_name["help"]), dest="$help", help_text=lang.require("builtin", "option_help"))) # noqa: E501
52+
options.append(Help("|".join(ns.builtin_option_name["help"]), dest="$help", help_text=lang.require("builtin", "option_help"), soft_keyword=False)) # noqa: E501
5253

5354
@cmd.route("$help")
5455
def _(command: Alconna, arp: Arparma):
55-
argv = command_manager.resolve(cmd)
56+
argv = command_manager.require(cmd).argv
5657
_help_param = [str(i) for i in argv.release(recover=True) if str(i) not in ns.builtin_option_name["help"]]
5758
arp.output = command.formatter.format_node(_help_param)
5859
return True
@@ -64,6 +65,7 @@ def _(command: Alconna, arp: Arparma):
6465
Args["action?", "delete|list"]["name?", str]["command?", str],
6566
dest="$shortcut",
6667
help_text=lang.require("builtin", "option_shortcut"),
68+
soft_keyword=False,
6769
)
6870
)
6971

@@ -84,11 +86,11 @@ def _(command: Alconna, arp: Arparma):
8486
return True
8587

8688
if "completion" not in ns.disable_builtin_options:
87-
options.append(Completion("|".join(ns.builtin_option_name["completion"]), dest="$completion", help_text=lang.require("builtin", "option_completion"))) # noqa: E501
89+
options.append(Completion("|".join(ns.builtin_option_name["completion"]), dest="$completion", help_text=lang.require("builtin", "option_completion"), soft_keyword=False)) # noqa: E501
8890

8991
@cmd.route("$completion")
9092
def _(command: Alconna, arp: Arparma):
91-
argv = command_manager.resolve(cmd)
93+
argv = command_manager.require(cmd).argv
9294
rest = argv.release()
9395
trigger = None
9496
if rest and isinstance(rest[-1], str) and rest[-1] in ns.builtin_option_name["completion"]:
@@ -193,7 +195,12 @@ class Alconna(Subcommand):
193195

194196
def compile(self, compiler: TCompile | None = None) -> Analyser:
195197
"""编译 `Alconna` 为对应的解析器"""
196-
return Analyser(self, compiler).compile()
198+
if TYPE_CHECKING:
199+
argv_type = Argv
200+
else:
201+
argv_type: type[Argv] = __argv_type__.get()
202+
argv = argv_type(self.meta, self.namespace_config, self.separators)
203+
return Analyser(self, argv, compiler)
197204

198205
def __init__(
199206
self,
@@ -424,7 +431,7 @@ def _parse(self, message: TDC, ctx: dict[str, Any] | None = None) -> Arparma[TDC
424431
if (res := alc._parse(message, ctx)).matched:
425432
return res
426433
analyser = command_manager.require(self)
427-
argv = command_manager.resolve(self)
434+
argv = analyser.argv
428435
argv.enter(ctx).build(message)
429436
if argv.message_cache and (res := command_manager.get_record(argv.token)):
430437
return res

0 commit comments

Comments
 (0)