diff --git a/pdm.lock b/pdm.lock index 4033b4f5..d77b14c2 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,24 +5,24 @@ groups = ["default", "dev", "full"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:babb045e66cd5789bd62c59abe769158f98b849965a04c5bc4dd1c3521c66a66" +content_hash = "sha256:bdb7aaf4c6c1e74b7ec58cfb33bc9ee524bac7f8d2f93f42e3fbc67061fd700c" [[metadata.targets]] requires_python = ">=3.9" [[package]] name = "arclet-alconna-tools" -version = "0.7.9" -requires_python = ">=3.8" +version = "0.7.10" +requires_python = ">=3.9" summary = "Builtin Tools for Alconna" groups = ["full"] dependencies = [ - "arclet-alconna>=1.8.21", + "arclet-alconna>=1.8.31", "nepattern<1.0.0,>=0.7.3", ] files = [ - {file = "arclet_alconna_tools-0.7.9-py3-none-any.whl", hash = "sha256:01a3462bb9f8dbe55010b394f7a0ac11e331799d463e326738870dce191aa608"}, - {file = "arclet_alconna_tools-0.7.9.tar.gz", hash = "sha256:bded24c4157e13e2d803fe7b77ee246fda456206451337015513f150d1e4449c"}, + {file = "arclet_alconna_tools-0.7.10-py3-none-any.whl", hash = "sha256:50e8b2f433fbc612dc8b99f4f5410006dcb1ef406c971c795071117a4eab8e20"}, + {file = "arclet_alconna_tools-0.7.10.tar.gz", hash = "sha256:446a63a9c56886c23fb44548bb9a18655e0ba5b5dd80cc87915b858dfb02554c"}, ] [[package]] @@ -147,6 +147,69 @@ files = [ {file = "coverage-7.0.5.tar.gz", hash = "sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45"}, ] +[[package]] +name = "elaina-flywheel" +version = "0.6.0" +requires_python = ">=3.9" +summary = "" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.10.0", +] +files = [ + {file = "elaina_flywheel-0.6.0-py3-none-any.whl", hash = "sha256:d01ff36ba61308c21ff239a709fbaa28288f073a7c8dfe0d993a74193ce48e23"}, + {file = "elaina_flywheel-0.6.0.tar.gz", hash = "sha256:dbc072ad7eeb250df35c638e5365de037f134965bbe374bbeefbc53f18b5c55a"}, +] + +[[package]] +name = "elaina-segment" +version = "0.4.0" +requires_python = ">=3.9" +summary = "Default template for PDM package" +groups = ["default"] +files = [ + {file = "elaina_segment-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06dc2928c68a8de9e829a367e84fbfcd2a2d510cf4e1655e5ba8d03e415dabeb"}, + {file = "elaina_segment-0.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55f2282f79919bed01ec2d30be30023482d741a645c95c5739017cd6849f7300"}, + {file = "elaina_segment-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b77097199bb97e5497063a9e89ac3180b71d52cb58253441d027ec131c6d1ced"}, + {file = "elaina_segment-0.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:06b266b5e1d97a7c52689fc67a149553fe67976c2e62f7598141062f56e3ecc7"}, + {file = "elaina_segment-0.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:108e4ee5a54610eac341f035f61130140733b086c1f2cd4f09892033a6179bf9"}, + {file = "elaina_segment-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bafef61c1f281e952180f5a67ff236685091eb5e4d7f7ccac5b3b51986c2cc7b"}, + {file = "elaina_segment-0.4.0-cp310-cp310-win32.whl", hash = "sha256:25f6d74f4786f2324122ad7fa70a04ec2175de4955041078f64bcbe6f6507fa0"}, + {file = "elaina_segment-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:3a02e90676c8b6fa117f52aa0c08574d82296dfd241732ab2095be30b434d0e5"}, + {file = "elaina_segment-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de558fff394a0f0b5977615d403448e946c34f32db11566c9c2b2c2d351fcb7"}, + {file = "elaina_segment-0.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:921979113cd97ace662a7273498c3ffa945d5f3f30a67e47bbfd1fafa5473215"}, + {file = "elaina_segment-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad163a2259325a6f33d3d001d19e77ea317b5dc80f92fee67c5073bc3b4b0b40"}, + {file = "elaina_segment-0.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4fb3eab83e72c1d8fc91926abf9de5a1283c1e85b12503763c15c1ca7dcf7db"}, + {file = "elaina_segment-0.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a67987b90ae2171752ac32d9d5dd700e2937007616b90b3a9522b6f78e37c4b5"}, + {file = "elaina_segment-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a091f1aecd45fac12d72d23a3f935b7bb22fb7e688a77e7eed499904430b46ad"}, + {file = "elaina_segment-0.4.0-cp311-cp311-win32.whl", hash = "sha256:e8db8ca0387f5429600dbbfbdcb7b791fbd21e3cb60d835b6645a90e2ffe98f8"}, + {file = "elaina_segment-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:7bacb1f41c3adda71043a158fa31b16d454b90cec5c168b5d32117beeb1df5ef"}, + {file = "elaina_segment-0.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0436e858ad3aedf3699bada99b04936d7a4347fe30f271b1840051f92691f118"}, + {file = "elaina_segment-0.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1b24d1c4f9da9bac6377645538388900750f6c57ea3550c3876b065c31512c2"}, + {file = "elaina_segment-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a92b5ba1990183c7ec8e41dd96e4833ed2b9d38890f453b594b3fa53be4723c9"}, + {file = "elaina_segment-0.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84b6518582796f1d663ac9ef301a7cda0d6f00454b9a828503408cfae49268be"}, + {file = "elaina_segment-0.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:959805562561f2cfec95f7006ec7628e74a4fe18619da31796e996ddd70f81f2"}, + {file = "elaina_segment-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a3bde1ccefcaf8dce68101c02f7953913c34afb8d994134ac619070dc4e1027d"}, + {file = "elaina_segment-0.4.0-cp312-cp312-win32.whl", hash = "sha256:b70d87555089c30cb47bcebb9031a58ec865bb8ddfe24c8976236d6ea63965f0"}, + {file = "elaina_segment-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:22cec03f9b8903a4f18e69254d08f6be122a34b5e3dce0d1bdb03978a3da54f6"}, + {file = "elaina_segment-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:23e863f66d0b9840962cf5280e9cd76039b3a01e91c5e08449d3ac94e18bb98c"}, + {file = "elaina_segment-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4753fa111130488df2efd13b95cbd2a78cf1b7d8ddafd4237aca119bffb461f8"}, + {file = "elaina_segment-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37e33e1a142b9c9a6ad117b64157ef090a7418ba4114bf9b573c6e03926ef524"}, + {file = "elaina_segment-0.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a0fc9efc50ec01f9b6393595c5b53c6f1362223c15ea3a0ce7ce96e5494e8c7"}, + {file = "elaina_segment-0.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b67118291a6c8a99d4cabf6f7962de66599e0102df7c1fc2a5cef3a61d98ab30"}, + {file = "elaina_segment-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ec3b3431e59101558dbf07a369e8e4c4d50ed478f95af9ab82438324f3322af4"}, + {file = "elaina_segment-0.4.0-cp313-cp313-win32.whl", hash = "sha256:652b02e5033c51e50f866a0b9777f41dba4cfa0aedec2d0965f96f52d89f1c58"}, + {file = "elaina_segment-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:17ea773ba1e34e9c52fc09cb2d99460b4a2d2c9068cee273e0a110e4caa931d3"}, + {file = "elaina_segment-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a58680070d3d6872a5831c138f06a5384418b9f0bce5177933c1c4f97adef4a2"}, + {file = "elaina_segment-0.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bbd4dfba5d539aa9d855b4fca824b4415b2d64fb80e95a13f487005ff262998b"}, + {file = "elaina_segment-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfb97d9fd2f74c0bec4b7f72412eefe1d3429b3b1192d574eefcc5ec17fed2e6"}, + {file = "elaina_segment-0.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8192df9b47ed13174f844e54e0929d2cf69e7e33e09bd3ec17a97a6c75c76c23"}, + {file = "elaina_segment-0.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:79141efc042942115db562b1603d1bb8bbaaf8ed8769787398d9be9d225bb485"}, + {file = "elaina_segment-0.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f1c99b89a62a8d546584eb0943e112870b6e6ae9ecd919be2c31a1fca0184c90"}, + {file = "elaina_segment-0.4.0-cp39-cp39-win32.whl", hash = "sha256:001c21f189e32a0443b4cc43b0acb916ee98c772516cbc5b310f7ccf7d6467d1"}, + {file = "elaina_segment-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:965d4e7eb12fd35576a1c4324eb4e0203074885c3779d619015e202be856f54a"}, +] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -196,6 +259,44 @@ files = [ {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] +[[package]] +name = "msgspec" +version = "0.18.6" +requires_python = ">=3.8" +summary = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML." +groups = ["dev"] +files = [ + {file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"}, + {file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"}, + {file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"}, + {file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"}, + {file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"}, + {file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"}, + {file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"}, + {file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"}, + {file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"}, + {file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"}, + {file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"}, + {file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"}, + {file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"}, + {file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"}, + {file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"}, + {file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"}, + {file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"}, + {file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"}, +] + [[package]] name = "mypy-extensions" version = "1.0.0" diff --git a/src/arclet/alconna/__init__.py b/src/arclet/alconna/__init__.py index be1f8ad0..c4a5a3f3 100644 --- a/src/arclet/alconna/__init__.py +++ b/src/arclet/alconna/__init__.py @@ -27,7 +27,6 @@ from .config import config as config from .config import namespace as namespace from .core import Alconna as Alconna -from .duplication import Duplication as Duplication from .exceptions import AlconnaException as AlconnaException from .exceptions import InvalidArgs as InvalidArgs from .exceptions import InvalidParam as InvalidParam @@ -40,9 +39,6 @@ from .model import OptionResult as OptionResult from .model import SubcommandResult as SubcommandResult from .output import output_manager as output_manager -from .stub import ArgsStub as ArgsStub -from .stub import OptionStub as OptionStub -from .stub import SubcommandStub as SubcommandStub from .typing import AllParam as AllParam from .typing import CommandMeta as CommandMeta from .typing import KeyWordVar as KeyWordVar diff --git a/src/arclet/alconna/_internal/_analyser.py b/src/arclet/alconna/_internal/_analyser.py index 977772d7..54861072 100644 --- a/src/arclet/alconna/_internal/_analyser.py +++ b/src/arclet/alconna/_internal/_analyser.py @@ -23,7 +23,7 @@ SpecialOptionTriggered, ) from ..manager import command_manager -from ..model import HeadResult, OptionResult, Sentence, SubcommandResult +from ..model import HeadResult, OptionResult, SubcommandResult from ..output import output_manager from ..typing import TDC, InnerShortcutArgs from ._handlers import ( @@ -49,29 +49,6 @@ _SPECIAL = {"help": handle_help, "shortcut": handle_shortcut, "completion": handle_completion} -def _compile_opts(option: Option, data: dict[str, Sentence | Option | list[Option] | SubAnalyser]): - """处理选项 - - Args: - option (Option): 选项 - data (dict[str, Sentence | Option | list[Option] | SubAnalyser]): 编译的节点 - """ - for alias in option.aliases: - if li := data.get(alias): - if isinstance(li, SubAnalyser): - continue - if isinstance(li, list): - li.append(option) - li.sort(key=lambda x: x.priority, reverse=True) - elif isinstance(li, Sentence): - data[alias] = option - continue - else: - data[alias] = sorted([li, option], key=lambda x: x.priority, reverse=True) - else: - data[alias] = option - - def default_compiler(analyser: SubAnalyser, pids: set[str]): """默认的编译方法 @@ -83,7 +60,8 @@ def default_compiler(analyser: SubAnalyser, pids: set[str]): if isinstance(opts, Option) and not isinstance(opts, (Help, Shortcut, Completion)): if opts.compact or opts.action.type == 2 or not set(analyser.command.separators).issuperset(opts.separators): # noqa: E501 analyser.compact_params.append(opts) - _compile_opts(opts, analyser.compile_params) # type: ignore + for alias in opts.aliases: + analyser.compile_params[alias] = opts if opts.default is not Empty: analyser.default_opt_result[opts.dest] = (opts.default, opts.action) pids.update(opts.aliases) @@ -97,10 +75,6 @@ def default_compiler(analyser: SubAnalyser, pids: set[str]): analyser.compact_params.append(sub) if sub.command.default is not Empty: analyser.default_sub_result[opts.dest] = sub.command.default - if opts.requires: - pids.update(opts.requires) - for k in opts.requires: - analyser.compile_params.setdefault(k, Sentence(name=k)) @dataclass @@ -113,7 +87,7 @@ class SubAnalyser(Generic[TDC]): """命令是否只有主参数""" need_main_args: bool = field(default=False) """是否需要主参数""" - compile_params: dict[str, Sentence | Option | list[Option] | SubAnalyser[TDC]] = field(default_factory=dict) + compile_params: dict[str, Option | SubAnalyser[TDC]] = field(default_factory=dict) """编译的节点""" compact_params: list[Option | SubAnalyser[TDC]] = field(default_factory=list) """可能紧凑的需要逐个解析的节点""" @@ -129,8 +103,6 @@ class SubAnalyser(Generic[TDC]): """头部的解析结果""" value_result: Any = field(init=False) """值的解析结果""" - sentences: list[str] = field(init=False) - """暂存传入的所有句段""" default_opt_result: dict[str, tuple[OptionResult, Action]] = field(default_factory=dict) """默认选项的解析结果""" default_sub_result: dict[str, SubcommandResult] = field(default_factory=dict) diff --git a/src/arclet/alconna/_internal/_handlers.py b/src/arclet/alconna/_internal/_handlers.py index 0902210a..06dc8142 100644 --- a/src/arclet/alconna/_internal/_handlers.py +++ b/src/arclet/alconna/_internal/_handlers.py @@ -13,10 +13,10 @@ from ..completion import Prompt, comp_ctx from ..config import config from ..exceptions import AlconnaException, ArgumentMissing, FuzzyMatchSuccess, InvalidParam, PauseTriggered, SpecialOptionTriggered -from ..model import HeadResult, OptionResult, Sentence +from ..model import HeadResult, OptionResult from ..output import output_manager from ..typing import KWBool, MultiKeyWordVar, MultiVar, _ShortcutRegWrapper, _StrMulti, _AllParamPattern -from ._header import Double, Header, Pair +from ._header import Header from ._util import escape, levenshtein, unescape if TYPE_CHECKING: @@ -370,17 +370,9 @@ def analyse_compact_params(analyser: SubAnalyser, argv: Argv): try: if param.__class__ is Option: oparam: Option = param # type: ignore - if oparam.requires and analyser.sentences != oparam.requires: - return lang.require("option", "require_error").format( - source=oparam.name, target=" ".join(analyser.sentences) - ) analyse_option(analyser, argv, oparam) else: sparam: SubAnalyser = param # type: ignore - if sparam.command.requires and analyser.sentences != sparam.command.requires: - return lang.require("subcommand", "require_error").format( - source=sparam.command.name, target=" ".join(analyser.sentences) - ) try: sparam.process(argv) except (FuzzyMatchSuccess, PauseTriggered, SpecialOptionTriggered): @@ -426,87 +418,38 @@ def analyse_param(analyser: SubAnalyser, argv: Argv, seps: str | None = None): seps (str, optional): 指定的分隔符. """ _text, _str = argv.next(seps, move=False) - if _str and _text in argv.special: - if argv.special[_text] not in argv.namespace.disable_builtin_options: + if _str and _text: + if _text in argv.special and argv.special[_text] not in argv.namespace.disable_builtin_options: if _text in argv.completion_names: argv.bak_data[argv.current_index] = argv.bak_data[argv.current_index].replace(_text, "") raise SpecialOptionTriggered(argv.special[_text]) - if not _str or not _text: - _param = None - elif _text in analyser.compile_params: - _param = analyser.compile_params[_text] - elif analyser.compact_params and (res := analyse_compact_params(analyser, argv)): - if res.__class__ is str: - raise InvalidParam(res) - argv.current_node = None - return True - else: - _param = None - if _param.__class__ is Sentence: - if _param.name not in analyser.sentences: # type: ignore - analyser.sentences.append(argv.next()[0]) - return True - if _param.__class__ is Option: - oparam: Option = _param # type: ignore - if oparam.requires and analyser.sentences != oparam.requires: - raise InvalidParam( - lang.require("option", "require_error").format(source=oparam.name, target=" ".join(analyser.sentences)) - ) - analyse_option(analyser, argv, oparam) - analyser.sentences.clear() - return True - if _param.__class__ is list: - exc: Exception | None = None - lparam: list[Option] = _param # type: ignore - for opt in lparam: - _data, _index = argv.data_set() - try: - if opt.requires and analyser.sentences != opt.requires: - raise InvalidParam( - lang.require("option", "require_error").format( - source=opt.name, target=" ".join(analyser.sentences) - ) - ) - analyser.sentences = [] - analyse_option(analyser, argv, opt) - _data.clear() - exc = None - break - except Exception as e: - exc = e - argv.data_reset(_data, _index) - if exc: - raise exc # type: ignore # noqa - analyser.sentences.clear() - return True - if _param is not None: - sparam: SubAnalyser = _param # type: ignore - if sparam.command.dest not in analyser.subcommands_result: - if sparam.command.requires and analyser.sentences != sparam.command.requires: - raise InvalidParam( - lang.require("subcommand", "require_error").format( - source=sparam.command.name, target=" ".join(analyser.sentences) - ) - ) - try: - sparam.process(argv) - except (FuzzyMatchSuccess, PauseTriggered, SpecialOptionTriggered): - sparam.result() - raise - except InvalidParam: - if argv.current_node is sparam.command: + if _param := analyser.compile_params.get(_text): + if _param.__class__ is Option: + oparam: Option = _param # type: ignore + analyse_option(analyser, argv, oparam) + return True + sparam: SubAnalyser = _param # type: ignore + if sparam.command.dest not in analyser.subcommands_result: + try: + sparam.process(argv) + except (FuzzyMatchSuccess, PauseTriggered, SpecialOptionTriggered): sparam.result() + raise + except InvalidParam: + if argv.current_node is sparam.command: + sparam.result() + else: + analyser.subcommands_result[sparam.command.dest] = sparam.result() + raise + except AlconnaException: + analyser.subcommands_result[sparam.command.dest] = sparam.result() + raise else: analyser.subcommands_result[sparam.command.dest] = sparam.result() - raise - except AlconnaException: - analyser.subcommands_result[sparam.command.dest] = sparam.result() - raise - else: - analyser.subcommands_result[sparam.command.dest] = sparam.result() - analyser.sentences.clear() - return True - if _param is None and analyser.command.nargs and not analyser.args_result: + return True + elif analyser.compact_params and analyse_compact_params(analyser, argv): + return True + if analyser.command.nargs and not analyser.args_result: analyser.args_result = analyse_args(argv, analyser.self_args) if analyser.args_result: argv.current_node = None @@ -571,31 +514,10 @@ def _header_handle2(header: "Header[BasePattern, BasePattern]", argv: Argv): _after_analyse_header(header, argv, head_text, may_cmd, _str, _m_str) -def _header_handle3(header: "Header[list[Pair], Any]", argv: Argv): - head_text, _str = argv.next() - may_cmd, _m_str = argv.next() - if _m_str: - for pair in header.content: - if res := pair.match(head_text, may_cmd, argv.rollback, header.compact): - return HeadResult(*res, fixes=header.mapping) - _after_analyse_header(header, argv, head_text, may_cmd, _str, _m_str) - - -def _header_handle4(header: "Header[Double, Any]", argv: Argv): - head_text, _str = argv.next() - may_cmd, _m_str = argv.next() - - if res := header.content.match(head_text, may_cmd, _str, _m_str, argv.rollback, header.compact): - return HeadResult(*res, fixes=header.mapping) - _after_analyse_header(header, argv, head_text, may_cmd, _str, _m_str) - - HEAD_HANDLES: dict[int, Callable[[Header, Argv], HeadResult]] = { 0: _header_handle0, 1: _header_handle1, 2: _header_handle2, - 3: _header_handle3, - 4: _header_handle4, } @@ -790,20 +712,6 @@ def _prompt_unit(analyser: Analyser, argv: Argv, trig: Arg): return [Prompt(f"{trig.name}: {i}", False, target) for i in o] -def _prompt_sentence(analyser: Analyser): - res: list[str] = [] - s_len = len(stc := analyser.sentences) - for opt in filter( - lambda x: len(x.requires) >= s_len and x.requires[s_len - 1] == stc[-1], - analyser.command.options, - ): - if len(opt.requires) > s_len: - res.append(opt.requires[s_len]) - else: - res.extend(opt.aliases if isinstance(opt, Option) else [opt.name]) - return [Prompt(i) for i in res] - - def _prompt_none(analyser: Analyser, argv: Argv, got: list[str]): res: list[Prompt] = [] if not analyser.args_result and analyser.self_args.argument: @@ -818,9 +726,7 @@ def _prompt_none(analyser: Analyser, argv: Argv, got: list[str]): lambda x: x.name not in (argv.special if len(analyser.command.options) > 3 else argv.completion_names), analyser.command.options, ): - if opt.requires and all(opt.requires[0] not in i for i in got): - res.append(Prompt(opt.requires[0])) - elif opt.dest not in got: + if opt.dest not in got: res.extend([Prompt(al) for al in opt.aliases] if isinstance(opt, Option) else [Prompt(opt.name)]) return res @@ -844,7 +750,7 @@ def prompt(analyser: Analyser, argv: Argv, trigger: str | None = None): if _res := list(filter(lambda x: target in x and target != x, analyser.compile_params)): out = [i for i in _res if i not in got] return [Prompt(i, True, target) for i in (out or _res)] - return _prompt_sentence(analyser) if analyser.sentences else _prompt_none(analyser, argv, got) + return _prompt_none(analyser, argv, got) def handle_completion(analyser: Analyser, argv: Argv, trigger: str | None = None): diff --git a/src/arclet/alconna/_internal/_header.py b/src/arclet/alconna/_internal/_header.py index 7f36bc68..ca0d26c2 100644 --- a/src/arclet/alconna/_internal/_header.py +++ b/src/arclet/alconna/_internal/_header.py @@ -1,14 +1,12 @@ from __future__ import annotations import re -from inspect import isclass -from typing import TYPE_CHECKING, Any, Callable, TypeVar, Generic +from typing import TypeVar, Generic -from nepattern import BasePattern, MatchMode, UnionPattern, all_patterns, parser +from nepattern import BasePattern, MatchMode, all_patterns, parser from nepattern.util import TPattern from tarina import lang -from ..typing import TPrefixes from ._util import escape, unescape @@ -63,179 +61,7 @@ def handle_bracket(name: str, mapping: dict): TP = TypeVar("TP", TPattern, str) -class Pair(Generic[TP]): - """用于匹配前缀和命令的配对""" - - __slots__ = ("prefix", "pattern", "is_prefix_pat", "gd_supplier", "_match") - - def _match1(self: "Pair[str]", command: str, rbfn: Callable[..., Any], comp: bool): - if command == self.pattern: - return command, None - if comp and command.startswith(self.pattern): - rbfn(command[len(self.pattern):], replace=True) - return self.pattern, None - return None, None - - def _match2(self: "Pair[TPattern]", command: str, rbfn: Callable[..., Any], comp: bool): - if mat := self.pattern.fullmatch(command): - return command, mat - if comp and (mat := self.pattern.match(command)): - rbfn(command[len(mat[0]):], replace=True) - return mat[0], mat - return None, None - - def __init__(self, prefix: Any, pattern: TP): - self.prefix = prefix - self.pattern: TP = pattern - self.is_prefix_pat = isinstance(self.prefix, BasePattern) - if isinstance(self.pattern, str): - self.gd_supplier = lambda mat: None - self._match = self._match1 # type: ignore - else: - self.gd_supplier = lambda mat: mat.groupdict() - self._match = self._match2 # type: ignore - - def match(self, _pf: Any, command: str, rbfn: Callable[..., Any], comp: bool): - cmd, mat = self._match(command, rbfn, comp) - if cmd is None: - return - if self.is_prefix_pat and (val := self.prefix.validate(_pf)).success: - return (_pf, command), (val._value, command), True, self.gd_supplier(mat) - if not isclass(_pf) and _pf == self.prefix or _pf.__class__ == self.prefix: - return (_pf, command), (_pf, command), True, self.gd_supplier(mat) - - def __repr__(self): - prefix = f"{self.prefix}" if self.is_prefix_pat else self.prefix.__name__ if isinstance(self.prefix, type) else self.prefix.__class__.__name__ # noqa: E501 - pattern = self.pattern if isinstance(self.pattern, str) else self.pattern.pattern - return f"({prefix}{pattern!r})" - - -TC = TypeVar("TC", TPattern, BasePattern) -TP1 = TypeVar("TP1", TPattern, "set[str] | None") - - -class Double(Generic[TC, TP1]): - """用于匹配前缀和命令的组合""" - - command: TC - comp_pattern: TC - prefix: TP1 - flag: int - - def __init__( - self, - prefixes: TPrefixes, - command: str | BasePattern, - ): - patterns = [] - texts = [] - for h in prefixes: - if isinstance(h, str): - texts.append(h) - elif isinstance(h, BasePattern): - patterns.append(h) - else: - patterns.append(parser(h)) - self.patterns: UnionPattern = UnionPattern(patterns) - if isinstance(command, BasePattern): - _self0: Double[BasePattern, set[str] | None] = self # type: ignore - _self0.command = command - _self0.prefix = set(texts) if texts else None - _self0.comp_pattern = prefixed(command) - _self0.flag = 0 - _self0.match = _self0.match0 - elif not texts: - _self1: Double[TPattern, None] = self # type: ignore - _self1.prefix = None - _self1.command = re.compile(command) - _self1.comp_pattern = re.compile(f"^{command}") - _self1.flag = 1 - _self1.match = _self1.match1 - else: - _self2: Double[TPattern, TPattern] = self # type: ignore - prf = "|".join(re.escape(h) for h in texts) - _self2.prefix = re.compile(f"(?:{prf}){command}") - _self2.command = re.compile(command) - _self2.flag = 2 - _self2.comp_pattern = re.compile(f"^(?:{prf}){command}") - _self2.match = _self2.match2 - - def __repr__(self): - if self.flag == 0: - if self.prefix: - return f"[{'│'.join(self.prefix)}]{self.command}" # type: ignore - return str(self.command) - if self.flag == 1: - return self.command.pattern - _self: Double[TPattern, TPattern] = self # type: ignore - cmd = self.command.pattern - prefixes = [str(_self.patterns).replace("|", "│")] - for pf in _self.prefix.pattern[:-len(cmd)][3:-1].split("|"): - for c in '()[]{}?*+-|^$\\.&~# \t\n\r\v\f': - if f"\\{c}" in pf: - pf = pf.replace(f"\\{c}", c) - prefixes.append(pf) - return f"[{'│'.join(prefixes)}]{cmd}" - - def match0(self: "Double[BasePattern, set[str] | None]", pf: Any, cmd: Any, p_str: bool, c_str: bool, rbfn: Callable[..., Any], comp: bool): - if self.prefix and p_str and pf in self.prefix: - if (val := self.command.validate(cmd)).success: - return (pf, cmd), (pf, val._value), True, None - if comp and (val := self.comp_pattern.validate(cmd)).success: - if c_str: - rbfn(cmd[len(str(val._value)):], replace=True) - return (pf, cmd), (pf, cmd[:len(str(val._value))]), True, None - return - if (val := self.patterns.validate(pf)).success: - if (val2 := self.command.validate(cmd)).success: - return (pf, cmd), (val._value, val2._value), True, None - if comp and (val2 := self.comp_pattern.validate(cmd)).success: - if c_str: - rbfn(cmd[len(str(val2._value)):], replace=True) - return (pf, cmd), (val._value, cmd[:len(str(val2._value))]), True, None - return - - def match1(self: "Double[TPattern, None]", pf: Any, cmd: Any, p_str: bool, c_str: bool, rbfn: Callable[..., Any], comp: bool): - if p_str or not c_str: - return - if (val := self.patterns.validate(pf)).success and (mat := self.command.fullmatch(cmd)): - return (pf, cmd), (val._value, cmd), True, mat.groupdict() - if comp and (mat := self.comp_pattern.match(cmd)): - rbfn(cmd[len(mat[0]):], replace=True) - return (pf, cmd), (pf, mat[0]), True, mat.groupdict() - - def match2(self: "Double[TPattern, TPattern]", pf: Any, cmd: Any, p_str: bool, c_str: bool, rbfn: Callable[..., Any], comp: bool): - if not p_str and not c_str: - return - if p_str: - if mat := self.prefix.fullmatch(pf): - rbfn(cmd) - return pf, pf, True, mat.groupdict() - if comp and (mat := self.comp_pattern.match(pf)): - rbfn(cmd) - rbfn(pf[len(mat[0]):], replace=True) - return mat[0], mat[0], True, mat.groupdict() - if not c_str: - return - if mat := self.prefix.fullmatch((name := pf + cmd)): - return name, name, True, mat.groupdict() - if comp and (mat := self.comp_pattern.match(name)): - rbfn(name[len(mat[0]):], replace=True) - return mat[0], mat[0], True, mat.groupdict() - return - if (val := self.patterns.validate(pf)).success: - if mat := self.command.fullmatch(cmd): - return (pf, cmd), (val._value, cmd), True, mat.groupdict() - if comp and (mat := self.command.match(cmd)): - rbfn(cmd[len(mat[0]):], replace=True) - return (pf, cmd), (val._value, mat[0]), True, mat.groupdict() - - if TYPE_CHECKING: - def match(self, pf: Any, cmd: Any, p_str: bool, c_str: bool, rbfn: Callable[..., Any], comp: bool) -> Any: - ... - - -TContent = TypeVar("TContent", TPattern, "set[str]", "list[Pair]", Double, BasePattern) +TContent = TypeVar("TContent", TPattern, set[str], BasePattern) TCompact = TypeVar("TCompact", TPattern, BasePattern, None) @@ -246,8 +72,8 @@ class Header(Generic[TContent, TCompact]): def __init__( self, - origin: tuple[str | BasePattern, TPrefixes], - content: set[str] | TPattern | BasePattern | list[Pair] | Double, + origin: tuple[str | BasePattern, list[str]], + content: set[str] | TPattern | BasePattern, mapping: dict[str, BasePattern] | None = None, compact: bool = False, compact_pattern: TPattern | BasePattern | None = None, @@ -262,12 +88,8 @@ def __init__( self.flag = 0 elif isinstance(self.content, TPattern): # type: ignore self.flag = 1 - elif isinstance(self.content, BasePattern): - self.flag = 2 - elif isinstance(self.content, list): - self.flag = 3 else: - self.flag = 4 + self.flag = 2 def __repr__(self): if isinstance(self.content, set): @@ -290,7 +112,7 @@ def __repr__(self): def generate( cls, command: str | type | BasePattern, - prefixes: TPrefixes, + prefixes: list[str], compact: bool, ): if isinstance(command, str): @@ -303,26 +125,13 @@ def generate( if not prefixes: cmd = re.compile(_cmd) if to_regex else {_cmd} return cls((command, prefixes), cmd, mapping, compact, re.compile(f"^{_cmd}")) - if isinstance(prefixes[0], tuple): # prefixes: List[Tuple[Any, str]] - _prefixes: list[tuple[Any, str]] = prefixes # type: ignore - return cls( - (command, prefixes), - [Pair(h[0], re.compile(re.escape(h[1]) + _cmd)) if to_regex else Pair(h[0], h[1] + _cmd) for h in _prefixes], - mapping, - compact, - ) - if all(isinstance(h, str) for h in prefixes): - _prefixes: list[str] = prefixes # type: ignore - prf = "|".join(re.escape(h) for h in _prefixes) - compp = re.compile(f"^(?:{prf}){_cmd}") - if to_regex: - return cls((command, prefixes), re.compile(f"(?:{prf}){_cmd}"), mapping, compact, compp) - return cls((command, prefixes), {f"{h}{_cmd}" for h in prefixes}, mapping, compact, compp) - return cls((command, prefixes), Double(prefixes, _cmd), mapping, compact) + prf = "|".join(re.escape(h) for h in prefixes) + compp = re.compile(f"^(?:{prf}){_cmd}") + if to_regex: + return cls((command, prefixes), re.compile(f"(?:{prf}){_cmd}"), mapping, compact, compp) + return cls((command, prefixes), {f"{h}{_cmd}" for h in prefixes}, mapping, compact, compp) else: _cmd = parser(command) if not prefixes: return cls((_cmd, prefixes), _cmd, {}, compact, prefixed(_cmd)) - if isinstance(prefixes[0], tuple): - raise TypeError(lang.require("header", "prefix_error")) - return cls((_cmd, prefixes), Double(prefixes, _cmd), {}, compact) + raise TypeError(lang.require("header", "prefix_error")) diff --git a/src/arclet/alconna/base.py b/src/arclet/alconna/base.py index d485ff70..9497a608 100644 --- a/src/arclet/alconna/base.py +++ b/src/arclet/alconna/base.py @@ -1,6 +1,7 @@ """Alconna 的基础内容相关""" from __future__ import annotations +import re from dataclasses import replace from functools import reduce from typing import Any, Iterable, Sequence, overload @@ -63,8 +64,6 @@ class CommandNode: """命令节点响应动作""" help_text: str """命令节点帮助信息""" - requires: list[str] - """命令节点需求前缀""" def __init__( self, @@ -76,7 +75,6 @@ def __init__( action: Action | None = None, separators: str | Sequence[str] | set[str] | None = None, help_text: str | None = None, - requires: str | list[str] | tuple[str, ...] | set[str] | None = None, ): """ 初始化命令节点 @@ -89,30 +87,27 @@ def __init__( action (Action | None, optional): 命令节点响应动作 separators (str | Sequence[str] | Set[str] | None, optional): 命令分隔符 help_text (str | None, optional): 命令帮助信息 - requires (str | list[str] | tuple[str, ...] | set[str] | None, optional): 命令节点需求前缀 """ + self.separators = " " if separators is None else "".join(separators) aliases = list(alias or []) - parts = name.split(" ") - _name = parts[-1] - if "|" in _name: - _aliases = _name.split("|") + name = re.sub(f"[{self.separators}]", "", name) + if "|" in name: + _aliases = name.split("|") _aliases.sort(key=len, reverse=True) - _name = _aliases[0] + name = _aliases[0] aliases.extend(_aliases[1:]) - if not _name: + if not name: raise InvalidArgs(lang.require("common", "name_empty")) - aliases.insert(0, _name) - self.name = _name + aliases.insert(0, name) + self.name = name self.aliases = frozenset(aliases) - self.requires = ([requires] if isinstance(requires, str) else list(requires)) if requires else [] - self.requires.extend(parts[:-1]) self.args = Args() + args self.default = default self.action = action or store _handle_default(self) - self.separators = " " if separators is None else "".join(separators) + self.nargs = len(self.args.argument) - self.dest = (dest or (("_".join(self.requires) + "_") if self.requires else "") + (self.name.lstrip("-") or self.name)) # noqa: E501 + self.dest = dest or self.name self.dest = self.dest.lstrip("-") or self.dest self.help_text = help_text or self.dest self._hash = self._calc_hash() @@ -178,7 +173,6 @@ def __init__( action: Action | None = None, separators: str | Sequence[str] | set[str] | None = None, help_text: str | None = None, - requires: str | list[str] | tuple[str, ...] | set[str] | None = None, compact: bool = False, priority: int = 0, soft_keyword: bool = False @@ -194,7 +188,6 @@ def __init__( action (Action | None, optional): 命令选项响应动作 separators (str | Sequence[str] | Set[str] | None, optional): 命令分隔符 help_text (str | None, optional): 命令选项帮助信息 - requires (str | list[str] | tuple[str, ...] | set[str] | None, optional): 命令选项需求前缀 compact (bool, optional): 是否允许名称与后随参数之间无分隔符 priority (int, optional): 命令选项优先级 soft_keyword (bool, optional): 是否为软关键字;仅 Sistana 支持该特性。 @@ -205,7 +198,7 @@ def __init__( self.soft_keyword = soft_keyword if default is not Empty and not isinstance(default, (OptionResult, SubcommandResult)): default = OptionResult(default) - super().__init__(name, args, alias, dest, default, action, separators, help_text, requires) + super().__init__(name, args, alias, dest, default, action, separators, help_text) if not self.args.empty: if default is not Empty and not self.default.args: self.default.args = {self.args.argument[0].name: self.default.value} if not isinstance(self.default.value, dict) else self.default.value @@ -238,7 +231,7 @@ def __add__(self, other: Option | Args | Arg) -> Subcommand | Option: TypeError: 如果other不是命令选项或命令节点, 则抛出此异常 """ if isinstance(other, Option): - return Subcommand(self.name, other, self.args, dest=self.dest, separators=self.separators, help_text=self.help_text, requires=self.requires) # noqa: E501 + return Subcommand(self.name, other, self.args, dest=self.dest, separators=self.separators, help_text=self.help_text, soft_keyword=self.soft_keyword) # noqa: E501 if isinstance(other, (Arg, Args)): self.args += other self.nargs = len(self.args) @@ -287,7 +280,6 @@ def __init__( default: Any = Empty, separators: str | Sequence[str] | set[str] | None = None, help_text: str | None = None, - requires: str | list[str] | tuple[str, ...] | set[str] | None = None, soft_keyword: bool = False ): """初始化子命令 @@ -300,7 +292,6 @@ def __init__( action (Action | None, optional): 子命令选项响应动作 separators (str | Sequence[str] | Set[str] | None, optional): 子命令分隔符 help_text (str | None, optional): 子命令选项帮助信息 - requires (str | list[str] | tuple[str, ...] | set[str] | None, optional): 子命令选项需求前缀 """ self.options = [i for i in args if isinstance(i, (Option, Subcommand))] for li in args: @@ -311,7 +302,7 @@ def __init__( super().__init__( name, reduce(lambda x, y: x + y, [Args()] + [i for i in args if isinstance(i, (Arg, Args))]), # type: ignore - alias, dest, default, None, separators, help_text, requires, + alias, dest, default, None, separators, help_text ) if not self.args.empty and default is not Empty and not self.default.args: self.default.args = {self.args.argument[0].name: self.default.value} if not isinstance(self.default.value, dict) else self.default.value diff --git a/src/arclet/alconna/builtin.py b/src/arclet/alconna/builtin.py index ecb1dac7..861f090f 100644 --- a/src/arclet/alconna/builtin.py +++ b/src/arclet/alconna/builtin.py @@ -8,34 +8,10 @@ from .config import lang from .arparma import Arparma, ArparmaBehavior from .core import Alconna -from .duplication import Duplication from .exceptions import BehaveCancelled from .model import OptionResult, SubcommandResult -from .stub import ArgsStub, OptionStub, SubcommandStub - -__all__ = ["set_default", "generate_duplication", "conflict"] - - -def generate_duplication(alc: Alconna) -> type[Duplication]: - """依据给定的命令生成一个解析结果的检查类。""" - from .base import Option, Subcommand - - options = filter(lambda x: isinstance(x, Option), alc.options) - subcommands = filter(lambda x: isinstance(x, Subcommand), alc.options) - return cast( - "type[Duplication]", - type( - f"{alc.name.strip('/.-:')}Interface", - (Duplication,), - { - "__annotations__": { - "args": ArgsStub, - **{opt.dest: OptionStub for opt in options}, - **{sub.dest: SubcommandStub for sub in subcommands}, - } - }, - ), - ) + +__all__ = ["set_default", "conflict"] @dataclass diff --git a/src/arclet/alconna/config.py b/src/arclet/alconna/config.py index 28eba41f..1ad985c2 100644 --- a/src/arclet/alconna/config.py +++ b/src/arclet/alconna/config.py @@ -5,7 +5,7 @@ from .i18n import lang as lang -from .typing import DataCollection, TPrefixes +from .typing import DataCollection if TYPE_CHECKING: from .formatter import TextFormatter @@ -26,7 +26,7 @@ class Namespace: name: str """命名空间名称""" - prefixes: TPrefixes = field(default_factory=list) + prefixes: list[str] = field(default_factory=list) """默认前缀配置""" separators: tuple[str, ...] = field(default_factory=lambda: (" ",)) """默认分隔符配置""" diff --git a/src/arclet/alconna/core.py b/src/arclet/alconna/core.py index b0a44ea0..7ca47556 100644 --- a/src/arclet/alconna/core.py +++ b/src/arclet/alconna/core.py @@ -20,7 +20,7 @@ from .exceptions import ExecuteFailed, NullMessage from .formatter import TextFormatter from .manager import ShortcutArgs, command_manager -from .typing import TDC, CommandMeta, DataCollection, InnerShortcutArgs, ShortcutRegWrapper, TPrefixes +from .typing import TDC, CommandMeta, DataCollection, InnerShortcutArgs, ShortcutRegWrapper T = TypeVar("T") TDC1 = TypeVar("TDC1", bound=DataCollection[Any]) @@ -100,7 +100,7 @@ class Alconna(Subcommand, Generic[TDC]): >>> alc.parse("name opt opt_arg") """ - prefixes: TPrefixes + prefixes: list[str] """命令前缀""" command: str | Any """命令名""" @@ -119,7 +119,7 @@ def compile(self, compiler: TCompile | None = None, param_ids: set[str] | None = def __init__( self, - *args: Option | Subcommand | str | TPrefixes | Args | Arg | CommandMeta | ArparmaBehavior | Any, + *args: Option | Subcommand | str | list[str] | Args | Arg | CommandMeta | ArparmaBehavior | Any, meta: CommandMeta | None = None, namespace: str | Namespace | None = None, separators: str | set[str] | Sequence[str] | None = None, @@ -153,7 +153,7 @@ def __init__( self.formatter = (formatter_type or ns_config.formatter_type or TextFormatter)() self.meta = meta or next((i for i in args if isinstance(i, CommandMeta)), CommandMeta()) if self.meta.example: - self.meta.example = self.meta.example.replace("$", str(self.prefixes[0]) if self.prefixes else "") + self.meta.example = self.meta.example.replace("$", self.prefixes[0] if self.prefixes else "") self.meta.fuzzy_match = self.meta.fuzzy_match or ns_config.fuzzy_match self.meta.raise_exception = self.meta.raise_exception or ns_config.raise_exception self.meta.compact = self.meta.compact or ns_config.compact diff --git a/src/arclet/alconna/duplication.py b/src/arclet/alconna/duplication.py deleted file mode 100644 index 2049635a..00000000 --- a/src/arclet/alconna/duplication.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -from inspect import isclass -from typing import cast - -from tarina import Empty - -from .arparma import Arparma -from .stub import ArgsStub, BaseStub, OptionStub, SubcommandStub - - -class Duplication: - """`副本`, 用以更方便的检查、调用解析结果的类。""" - - header: dict[str, str] - - def __init__(self, target: Arparma): - from .base import Option, Subcommand - - source = target.source - self.header = target.header.copy() - for key, value in self.__annotations__.items(): - if isclass(value) and issubclass(value, BaseStub): - if value is ArgsStub: - setattr(self, key, ArgsStub(source.args).set_result(target.main_args)) - elif value is SubcommandStub: - for subcommand in source.options: - if isinstance(subcommand, Subcommand) and subcommand.dest == key: - setattr(self, key, SubcommandStub(subcommand).set_result(target.subcommands.get(key, None))) - elif value is OptionStub: - for option in source.options: - if isinstance(option, Option) and option.dest == key: - setattr(self, key, OptionStub(option).set_result(target.options.get(key, None))) - elif key != "header": - setattr(self, key, target.all_matched_args.get(key, Empty)) - - def __repr__(self): - return f"{self.__class__.__name__}({self.__annotations__})" - - def option(self, name: str) -> OptionStub | None: - """获取指定名称的选项存根。""" - return cast(OptionStub, getattr(self, name, None)) - - def subcommand(self, name: str) -> SubcommandStub | None: - """获取指定名称的子命令存根。""" - return cast(SubcommandStub, getattr(self, name, None)) diff --git a/src/arclet/alconna/formatter.py b/src/arclet/alconna/formatter.py index ef8ff885..326df849 100644 --- a/src/arclet/alconna/formatter.py +++ b/src/arclet/alconna/formatter.py @@ -14,50 +14,20 @@ from .core import Alconna -def resolve_requires(options: list[Option | Subcommand]): - """Resolve the requires of options.""" - reqs: dict[str, dict | Option | Subcommand] = {} - - def _u(target, source): - for k in source: - if k not in target or isinstance(target[k], (Option, Subcommand)): - target.update(source) - break - _u(target[k], source[k]) - - for opt in options: - if not opt.requires: - # reqs.setdefault(opt.name, opt) - [reqs.setdefault(i, opt) for i in opt.aliases] if isinstance(opt, Option) else None - reqs.setdefault(opt.name, resolve_requires(opt.options)) if isinstance(opt, Subcommand) else None - else: - _reqs = _cache = {} - for req in opt.requires: - if not _reqs: - _reqs[req] = {} - _cache = _reqs[req] - else: - _cache[req] = {} - _cache = _cache[req] - # _cache[opt.name] = opt # type: ignore - [_cache.setdefault(i, opt) for i in opt.aliases] if isinstance(opt, Option) else None # type: ignore - _cache.setdefault(opt.name, resolve_requires(opt.options)) if isinstance(opt, Subcommand) else None - _u(reqs, _reqs) - return reqs - - -def ensure_node(targets: list[str], options: list[Option | Subcommand]): +def ensure_node(targets: list[str], options: list[Option | Subcommand], record: list) -> Option | Subcommand | None: if not targets: return None pf = targets.pop(0) for opt in options: if isinstance(opt, Option) and pf in opt.aliases: + record.append(pf) return opt if isinstance(opt, Subcommand) and pf == opt.name: + record.append(pf) if not targets: return opt - return sub if (sub := ensure_node(targets, opt.options)) else opt - return ensure_node(targets, options) + return sub if (sub := ensure_node(targets, opt.options, record)) else opt + return ensure_node(targets, options, record) class TraceHead(TypedDict): @@ -131,38 +101,20 @@ def format_node(self, parts: list | None = None): def _handle(trace: Trace): if not parts or parts == [""]: return self.format(trace) - _cache = resolve_requires(trace.body) - _parts = [] + rec = [] prefix = parts[0] - for text in parts: - if isinstance(_cache, dict) and text in _cache: - _cache = _cache[text] - _parts.append(text) - if not _parts: + end = ensure_node(parts, trace.body, rec) + if not end: return self.format(trace) - if len(_parts) > 1: - prefix += trace.separators[0] + trace.separators[0].join(_parts[:-1]) - if isinstance(_cache, dict): - if ensure := ensure_node(_parts, trace.body): - _cache = ensure - else: - _opts, _visited = [], set() - for k, i in _cache.items(): - if isinstance(i, dict): - _opts.append(Option(k, requires=_parts)) - elif i not in _visited: - _opts.append(i) - _visited.add(i) - return self.format( - Trace({"name": _parts[-1], 'description': _parts[-1], 'example': None, 'usage': None}, Args(), trace.separators, _opts, {}) # noqa: E501 - ) - if isinstance(_cache, Option): + if len(rec) > 1: + prefix += trace.separators[0] + trace.separators[0].join(rec[:-1]) + if isinstance(end, Option): return self.format( - Trace({"name": prefix + trace.separators[0] + "│".join(_cache.aliases), "description": _cache.help_text, 'example': None, 'usage': None}, _cache.args, _cache.separators, [], {}) # noqa: E501 + Trace({"name": prefix + trace.separators[0] + "│".join(end.aliases), "description": end.help_text, 'example': None, 'usage': None}, end.args, end.separators, [], {}) # noqa: E501 ) - if isinstance(_cache, Subcommand): + if isinstance(end, Subcommand): return self.format( - Trace({"name": prefix + trace.separators[0] + "│".join(_cache.aliases), "description": _cache.help_text, 'example': None, 'usage': None}, _cache.args, _cache.separators, _cache.options, {}) # noqa: E501 + Trace({"name": prefix + trace.separators[0] + "│".join(end.aliases), "description": end.help_text, 'example': None, 'usage': None}, end.args, end.separators, end.options, {}) # noqa: E501 ) return self.format(trace) @@ -244,12 +196,12 @@ def header(self, root: TraceHead) -> tuple[str, str, str, str]: def opt(self, node: Option) -> str: """对单个选项的描述""" - alias_text = " ".join(node.requires) + (" " if node.requires else "") + "│".join(node.aliases) + alias_text = "│".join(node.aliases) return f"* {node.help_text}\n" f" {alias_text}{node.separators[0]}{self.parameters(node.args)}\n" def sub(self, node: Subcommand) -> str: """对单个子命令的描述""" - alias_text = " ".join(node.requires) + (" " if node.requires else "") + "│".join(node.aliases) + alias_text = "│".join(node.aliases) opt_string = "".join( [self.opt(opt).replace("\n", "\n ").replace("# ", "* ") for opt in node.options if isinstance(opt, Option)] ) diff --git a/src/arclet/alconna/manager.py b/src/arclet/alconna/manager.py index c6c3bba7..322bcdbc 100644 --- a/src/arclet/alconna/manager.py +++ b/src/arclet/alconna/manager.py @@ -258,12 +258,8 @@ def add_shortcut(self, target: Alconna, key: str | TPattern, source: Arparma | S if isinstance(source, dict): humanize = source.pop("humanized", None) if source.get("prefix", False) and target.prefixes: - prefixes = [] out = [] for prefix in target.prefixes: - if not isinstance(prefix, str): - continue - prefixes.append(prefix) _shortcut[1][f"{re.escape(prefix)}{_key}"] = InnerShortcutArgs( **{**source, "command": argv.converter(prefix + source.get("command", str(target.command)))}, flags=_flags, @@ -272,7 +268,7 @@ def add_shortcut(self, target: Alconna, key: str | TPattern, source: Arparma | S lang.require("shortcut", "add_success").format(shortcut=f"{prefix}{_key}", target=target.path) ) _shortcut[0][humanize or _key] = InnerShortcutArgs( - **{**source, "command": argv.converter(source.get("command", str(target.command))), "prefixes": prefixes}, + **{**source, "command": argv.converter(source.get("command", str(target.command))), "prefixes": target.prefixes}, flags=_flags, ) target.formatter.update_shortcut(target) diff --git a/src/arclet/alconna/model.py b/src/arclet/alconna/model.py index c07fb142..bb3be461 100644 --- a/src/arclet/alconna/model.py +++ b/src/arclet/alconna/model.py @@ -7,27 +7,6 @@ _repr_ = lambda self: "(" + " ".join([f"{k}={getattr(self, k, ...)!r}" for k in self.__slots__]) + ")" -@dataclass(init=False, eq=True) -class Sentence: - """句段 - - 句段由 `Analyser` 编译而来, 代表选项或者子命令的需求前缀。 - - Attributes: - name (str): 句段名称 - """ - - __slots__ = ("name",) - __str__ = lambda self: self.name # type: ignore - __repr__ = lambda self: self.name # type: ignore - - if TYPE_CHECKING: - name: str - - def __init__(self, name: str) -> None: - self.name = name - - @dataclass(init=False, eq=True) class OptionResult: """选项解析结果 diff --git a/src/arclet/alconna/stub.py b/src/arclet/alconna/stub.py deleted file mode 100644 index c4256302..00000000 --- a/src/arclet/alconna/stub.py +++ /dev/null @@ -1,181 +0,0 @@ -from __future__ import annotations - -from abc import ABCMeta, abstractmethod -from dataclasses import dataclass, field -from inspect import isclass -from typing import Any, Generic, TypeVar -from typing_extensions import Self - -from nepattern import ANY, BasePattern - -from .args import Args -from .base import Option, Subcommand -from .model import OptionResult, SubcommandResult -from .typing import AllParam - -T = TypeVar("T") -T_Origin = TypeVar("T_Origin") - - -@dataclass(init=True, eq=True) -class BaseStub(Generic[T_Origin], metaclass=ABCMeta): - """基础的命令组件存根""" - - _origin: T_Origin - """原始的命令组件""" - _value: Any = field(default=None) - """解析结果""" - available: bool = field(default=False, init=False) - """是否可用""" - - @property - def origin(self) -> T_Origin: - """原始的命令组件""" - return self._origin - - @abstractmethod - def set_result(self, result: Any) -> Self: - """设置解析结果与可用性""" - - def __repr__(self): - return f"{{{', '.join([f'{k}={v}' for k, v in vars(self).items() if v and not k.startswith('_')])}}}" - - -@dataclass(init=True) -class ArgsStub(BaseStub[Args]): - """参数存根""" - - _value: dict[str, Any] = field(default_factory=dict) - """解析结果""" - - def __post_init__(self): - for arg in self._origin.argument: - key = arg.name - if arg.value in (AllParam, ANY): - self.__annotations__[key] = Any - elif isinstance(arg.value, BasePattern): - self.__annotations__[key] = arg.value.origin - else: - self.__annotations__[key] = arg.value - setattr(self, key, arg.field.default) - - def set_result(self, result: dict[str, Any]): - if result: - self._value = result.copy() - self.available = True - return self - - @property - def first(self) -> Any: - """第一个参数""" - return self.__getitem__(0) - - def get(self, item: str | type[T], default=None) -> T | Any: - """获取参数结果 - - Args: - item (str | type[T]): 参数名或参数类型 - default (Any, optional): 默认值. Defaults to None. - """ - if isinstance(item, str): - return self._value.get(item, default) - for k, v in self.__annotations__.items(): - if isclass(v) and (item == v or issubclass(v, item)): - return self._value.get(k, default) - return default - - def __contains__(self, item): - return item in self._value - - def __iter__(self): - return iter(self._value) - - def __len__(self): - return len(self._value) - - def __getattribute__(self, item): - if item not in (_cache := super().__getattribute__("_value")): - return super().__getattribute__(item) - return _cache.get(item, None) - - def __getitem__(self, item: int | str) -> Any: - if isinstance(item, int): - return list(self._value.values())[item] - return self._value[item] - - -@dataclass(init=True) -class OptionStub(BaseStub[Option]): - """选项存根""" - - args: ArgsStub = field(init=False) - """选项的参数存根""" - dest: str = field(init=False) - """选项的目标名称""" - aliases: list[str] = field(init=False) - """选项的别名""" - name: str = field(init=False) - """选项的名称""" - - def __post_init__(self): - self.dest = self._origin.dest - self.aliases = [alias.lstrip("-") for alias in self._origin.aliases] - self.name = self._origin.name.lstrip("-") - self.args = ArgsStub(self._origin.args) - - def set_result(self, result: OptionResult | None): - if result: - self._value = result.value - self.args.set_result(result.args) - self.available = True - return self - - -@dataclass(init=True) -class SubcommandStub(BaseStub[Subcommand]): - """子命令存根""" - - args: ArgsStub = field(init=False) - """子命令的参数存根""" - dest: str = field(init=False) - """子命令的目标名称""" - options: list[OptionStub] = field(init=False) - """子命令的子选项存根""" - subcommands: list[SubcommandStub] = field(init=False) - """子命令的子子命令存根""" - name: str = field(init=False) - """子命令的名称""" - - def __post_init__(self): - self.dest = self._origin.dest - self.name = self._origin.name.lstrip("-") - self.args = ArgsStub(self._origin.args) - self.options = [OptionStub(opt) for opt in self._origin.options if isinstance(opt, Option)] - self.subcommands = [SubcommandStub(sub) for sub in self._origin.options if isinstance(sub, Subcommand)] - - def set_result(self, result: SubcommandResult | None): - if result: - self._value = result.value - self.args.set_result(result.args) - for option in self.options: - option.set_result(result.options.get(option.dest, None)) - for subcommand in self.subcommands: - subcommand.set_result(result.subcommands.get(subcommand.dest, None)) - self.available = True - return self - - def option(self, name: str) -> OptionStub: - """获取子选项存根 - - Args: - name (str): 子选项名称 - """ - return next(opt for opt in self.options if opt.name == name) - - def subcommand(self, name: str) -> SubcommandStub: - """获取子子命令存根 - - Args: - name (str): 子子命令名称 - """ - return next(sub for sub in self.subcommands if sub.name == name) diff --git a/src/arclet/alconna/typing.py b/src/arclet/alconna/typing.py index 950e2f6d..1eee9a6f 100644 --- a/src/arclet/alconna/typing.py +++ b/src/arclet/alconna/typing.py @@ -14,7 +14,6 @@ List, Literal, Protocol, - Tuple, Type, TypedDict, TypeVar, @@ -27,7 +26,6 @@ from tarina import generic_isinstance, lang from nepattern import BasePattern, MatchMode, parser, MatchFailed -TPrefixes = List[str] DataUnit = TypeVar("DataUnit", covariant=True) diff --git a/tests/base_test.py b/tests/base_test.py index 2dd5468f..6dbad91f 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -16,14 +16,6 @@ def test_single_args(): assert node1.args == Args["bar", int] -def test_node_requires(): - node2 = CommandNode("foo", requires=["baz", "qux"]) - assert node2.dest == "baz_qux_foo" - node2_1 = CommandNode("baz qux foo") - assert node2_1.name == "foo" - assert node2_1.requires == ["baz", "qux"] - - def test_option_aliases(): opt = Option("test|T|t") assert opt.aliases == {"test", "T", "t"} @@ -33,14 +25,6 @@ def test_option_aliases(): assert opt == Option("T|t|test") -def test_option_requires(): - opt1 = Option("foo bar test|T|t") - assert opt1.aliases == {"test", "T", "t"} - assert opt1.requires == ["foo", "bar"] - opt1_1 = Option("foo bar test| T | t") - assert opt1_1.aliases != {"test", "T", "t"} - - def test_separator(): opt2 = Option("foo", Args["bar", int], separators="|") assert analyse_option(opt2, "foo|123") == OptionResult(None, {"bar": 123}) diff --git a/tests/components_test.py b/tests/components_test.py index ad358b08..5d24d76e 100644 --- a/tests/components_test.py +++ b/tests/components_test.py @@ -1,9 +1,7 @@ from arclet.alconna import Alconna, Args, Arparma, ArparmaBehavior, Option, Subcommand -from arclet.alconna.builtin import generate_duplication, set_default, conflict -from arclet.alconna.duplication import Duplication +from arclet.alconna.builtin import set_default, conflict from arclet.alconna.model import OptionResult from arclet.alconna.output import output_manager -from arclet.alconna.stub import ArgsStub, OptionStub, SubcommandStub def test_behavior(): @@ -59,45 +57,6 @@ def operate(cls, interface: "Arparma"): assert com1_1.parse("comp1_1 -1 -3").matched -def test_duplication(): - class Demo(Duplication): - testArgs: ArgsStub - bar: OptionStub - sub: SubcommandStub - - class Demo1(Duplication): - foo: int - bar: str - baz: str - - com4 = Alconna( - "comp4", - Args["foo", int], - Option("--bar", Args["bar", str]), - Subcommand("sub", Option("--sub1", Args["baz", str])), - ) - res = com4.parse("comp4 123 --bar abc sub --sub1 xyz") - assert res.matched is True - duplication = Demo(com4.parse("comp4 123 --bar abc sub --sub1 xyz")) - assert isinstance(duplication, Demo) - assert duplication.testArgs.available is True - assert duplication.testArgs.foo == 123 - assert duplication.bar.available is True - assert duplication.bar.args.bar == "abc" - assert duplication.sub.available is True - assert duplication.sub.option("sub1").args.first == "xyz" - duplication1 = Demo1(com4.parse("comp4 123 --bar abc sub --sub1 xyz")) - assert isinstance(duplication1, Demo1) - assert isinstance(duplication1.foo, int) - assert isinstance(duplication1.bar, str) - assert isinstance(duplication1.baz, str) - - com4_1 = Alconna(["!", "!"], "yiyu", Args["value;OH", str]) - res = com4_1.parse("!yiyu") - dup = generate_duplication(com4_1)(res) - assert isinstance(dup, Duplication) - - def test_output(): print("") output_manager.set_action(lambda x: {"bar": f"{x}!"}, "foo") diff --git a/tests/config_test.py b/tests/config_test.py index 7799d631..0db08732 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -22,15 +22,15 @@ def test_config(): def test_namespace(): - np = Namespace("xxx", prefixes=[...]) + np = Namespace("xxx", prefixes=["/"]) config.default_namespace = np assert config.default_namespace == np - assert config.default_namespace.prefixes == [...] + assert config.default_namespace.prefixes == ["/"] cfg3 = Alconna("cfg3") assert cfg3.parse("cfg3").matched is False - assert cfg3.parse([..., "cfg3"]).matched is True + assert cfg3.parse(["/cfg3"]).matched is True config.default_namespace = "Alconna" diff --git a/tests/core_test.py b/tests/core_test.py index e8cf9ba1..c4e2cc37 100644 --- a/tests/core_test.py +++ b/tests/core_test.py @@ -201,27 +201,27 @@ class A: assert alc6_1.parse("!core6_1").head_matched is True assert alc6_1.parse("aaa core6_1").head_matched is True assert alc6_1.parse("aaacore6_1").head_matched is False - # 对头 - alc6_2 = Alconna("core6_2", [(a, "/"), (A, "!"), ("c", "."), (NUMBER, "d")]) - assert alc6_2.parse([a, "/core6_2"]).head_matched is True - assert alc6_2.parse([a, "core6_2"]).head_matched is False - assert alc6_2.parse([b, "/core6_2"]).head_matched is False - assert alc6_2.parse([b, "!core6_2"]).head_matched is True - assert alc6_2.parse([a, "!core6_2"]).head_matched is True - assert alc6_2.parse([A, "!core6_2"]).head_matched is False - assert alc6_2.parse(["c", ".core6_2"]).head_matched is True - assert alc6_2.parse(["c", "core6_2"]).head_matched is False - assert alc6_2.parse("c.core6_2").head_matched is False - assert alc6_2.parse([123, "dcore6_2"]).head_matched is True - assert alc6_2.parse(["123.0", "dcore6_2"]).head_matched is True - assert alc6_2.parse(["aaa", "dcore6_2"]).head_matched is False - assert alc6_2.parse("123dcore6_2").head_matched is False - assert alc6_2.parse("/core6_2").head_matched is False - # 只有纯元素类头 - alc6_3 = Alconna(A) - assert alc6_3.parse([a]).head_matched is True - assert alc6_3.parse([b]).head_matched is True - assert alc6_3.parse("a").head_matched is False + # # 对头 + # alc6_2 = Alconna("core6_2", [(a, "/"), (A, "!"), ("c", "."), (NUMBER, "d")]) + # assert alc6_2.parse([a, "/core6_2"]).head_matched is True + # assert alc6_2.parse([a, "core6_2"]).head_matched is False + # assert alc6_2.parse([b, "/core6_2"]).head_matched is False + # assert alc6_2.parse([b, "!core6_2"]).head_matched is True + # assert alc6_2.parse([a, "!core6_2"]).head_matched is True + # assert alc6_2.parse([A, "!core6_2"]).head_matched is False + # assert alc6_2.parse(["c", ".core6_2"]).head_matched is True + # assert alc6_2.parse(["c", "core6_2"]).head_matched is False + # assert alc6_2.parse("c.core6_2").head_matched is False + # assert alc6_2.parse([123, "dcore6_2"]).head_matched is True + # assert alc6_2.parse(["123.0", "dcore6_2"]).head_matched is True + # assert alc6_2.parse(["aaa", "dcore6_2"]).head_matched is False + # assert alc6_2.parse("123dcore6_2").head_matched is False + # assert alc6_2.parse("/core6_2").head_matched is False + # # 只有纯元素类头 + # alc6_3 = Alconna(A) + # assert alc6_3.parse([a]).head_matched is True + # assert alc6_3.parse([b]).head_matched is True + # assert alc6_3.parse("a").head_matched is False # 只有纯文字头 alc6_4 = Alconna(["/dd", "!cd"], Args["a;?", int]) assert alc6_4.parse("/dd").head_matched is True @@ -229,45 +229,45 @@ class A: assert alc6_4.parse("!cd 123").head_matched is True assert alc6_4.parse("/dd !cd").matched is False assert alc6_4.parse("/dd !cd 123").matched is False - # 只有纯元素头 - alc6_5 = Alconna(a) - assert alc6_5.parse([a]).head_matched is True - assert alc6_5.parse([b]).head_matched is False - # 元素类头 - alc6_6 = Alconna("core6_6", [A]) - assert alc6_6.parse([a, "core6_6"]).head_matched is True - assert alc6_6.parse([b, "core6_6"]).head_matched is True - assert alc6_6.parse([A, "core6_6"]).head_matched is False - assert alc6_6.parse("core6_6").head_matched is False - # 表达式头 - alc6_7 = Alconna("core6_7", [NUMBER]) - assert alc6_7.parse([123, "core6_7"]).head_matched is True - assert alc6_7.parse("123core6_7").head_matched is False - # 混合头 - alc6_8 = Alconna("core6_8", [A, "/"]) - assert alc6_8.parse([a, "core6_8"]).head_matched is True - assert alc6_8.parse([b, "core6_8"]).head_matched is True - assert alc6_8.parse("/core6_8").head_matched is True - assert alc6_8.parse([A, "core6_8"]).head_matched is False - assert alc6_8.parse(["/", "core6_8"]).head_matched is True - assert alc6_8.parse("core6_8").head_matched is False - alc6_9 = Alconna("core6_9", ["/", a]) - assert alc6_9.parse("/core6_9").head_matched is True - assert alc6_9.parse([a, "core6_9"]).head_matched is True - assert alc6_9.parse([b, "core6_9"]).head_matched is False - assert alc6_9.parse([A, "core6_9"]).head_matched is False - alc6_10 = Alconna(a, ["/", b]) - assert alc6_10.parse(["/", a]).head_matched is True - assert alc6_10.parse([b, b]).head_matched is False - assert alc6_10.parse([b, a]).head_matched is True - assert alc6_10.parse([b]).head_matched is False - assert alc6_10.parse([b, "abc"]).head_matched is False - alc6_11 = Alconna(A, ["/", b]) - assert alc6_11.parse(["/", a]).head_matched is True - assert alc6_11.parse([b, b]).head_matched is True - assert alc6_11.parse([b, a]).head_matched is True - assert alc6_11.parse([b]).head_matched is False - assert alc6_11.parse([b, "abc"]).head_matched is False + # # 只有纯元素头 + # alc6_5 = Alconna(a) + # assert alc6_5.parse([a]).head_matched is True + # assert alc6_5.parse([b]).head_matched is False + # # 元素类头 + # alc6_6 = Alconna("core6_6", [A]) + # assert alc6_6.parse([a, "core6_6"]).head_matched is True + # assert alc6_6.parse([b, "core6_6"]).head_matched is True + # assert alc6_6.parse([A, "core6_6"]).head_matched is False + # assert alc6_6.parse("core6_6").head_matched is False + # # 表达式头 + # alc6_7 = Alconna("core6_7", [NUMBER]) + # assert alc6_7.parse([123, "core6_7"]).head_matched is True + # assert alc6_7.parse("123core6_7").head_matched is False + # # 混合头 + # alc6_8 = Alconna("core6_8", [A, "/"]) + # assert alc6_8.parse([a, "core6_8"]).head_matched is True + # assert alc6_8.parse([b, "core6_8"]).head_matched is True + # assert alc6_8.parse("/core6_8").head_matched is True + # assert alc6_8.parse([A, "core6_8"]).head_matched is False + # assert alc6_8.parse(["/", "core6_8"]).head_matched is True + # assert alc6_8.parse("core6_8").head_matched is False + # alc6_9 = Alconna("core6_9", ["/", a]) + # assert alc6_9.parse("/core6_9").head_matched is True + # assert alc6_9.parse([a, "core6_9"]).head_matched is True + # assert alc6_9.parse([b, "core6_9"]).head_matched is False + # assert alc6_9.parse([A, "core6_9"]).head_matched is False + # alc6_10 = Alconna(a, ["/", b]) + # assert alc6_10.parse(["/", a]).head_matched is True + # assert alc6_10.parse([b, b]).head_matched is False + # assert alc6_10.parse([b, a]).head_matched is True + # assert alc6_10.parse([b]).head_matched is False + # assert alc6_10.parse([b, "abc"]).head_matched is False + # alc6_11 = Alconna(A, ["/", b]) + # assert alc6_11.parse(["/", a]).head_matched is True + # assert alc6_11.parse([b, b]).head_matched is True + # assert alc6_11.parse([b, a]).head_matched is True + # assert alc6_11.parse([b]).head_matched is False + # assert alc6_11.parse([b, "abc"]).head_matched is False # 开启 compact 后 alc6_12 = Alconna("core6_12", Args["foo", str], meta=CommandMeta(compact=True)) assert alc6_12.parse("core6_12 abc").matched is True @@ -276,9 +276,6 @@ class A: alc6_13 = Alconna("core6_13", ["/", "?"], Args["foo", str], meta=CommandMeta(compact=True)) assert alc6_13.parse("/core6_13 abc").matched is True assert alc6_13.parse("/core6_13abc").matched is True - alc6_14 = Alconna("core6_14", ["/", A], Args["foo", str], meta=CommandMeta(compact=True)) - assert alc6_14.parse("/core6_14 abc").matched is True - assert alc6_14.parse([a, "core6_14abc"]).matched is True def test_alconna_namespace(): @@ -335,30 +332,11 @@ def test_alconna_synthesise(): def test_simple_override(): - alc11 = Alconna("core11") + Option("foo", Args["bar", str]) + Option("foo") - res = alc11.parse("core11 foo abc") + alc11 = Alconna("core11") + Option("bar", Args["bar", str], dest="foo") + Option("foo", dest="foo") + res = alc11.parse("core11 bar abc") res1 = alc11.parse("core11 foo") - assert res.matched is True - assert res1.matched is True - - -def test_requires(): - alc12 = Alconna( - "core12", - Args["target", int], - Option("user perm set", Args["foo", str], help_text="set user permission"), - Option("user perm del", Args["foo", str], help_text="del user permission"), - Option("group perm set", Args["foo", str], help_text="set group permission"), - Option("group perm del", Args["foo", str], help_text="del group permission"), - Option("test"), - ) - - assert alc12.parse("core12 123 user perm set 123").find("user_perm_set") is True - assert alc12.parse("core12 123 user perm del 123").find("user_perm_del") is True - assert alc12.parse("core12 123 group perm set 123").find("group_perm_set") is True - assert alc12.parse("core12 123 group perm del 123 test").find("group_perm_del") is True - print("\n------------------------") - print(alc12.get_help()) + assert res.query("foo") + assert res1.query("foo") def test_wildcard(): @@ -435,18 +413,6 @@ def test_fuzzy(): assert res2.matched is False assert cap["output"] == '无法解析 "@core15_1"。您想要输入的是不是 "/core15_1" ?' - alc15_2 = Alconna([1], "core15_2", meta=CommandMeta(fuzzy_match=True)) - with output_manager.capture("core15_2") as cap: - output_manager.set_action(lambda x: x, "core15_2") - res4 = alc15_2.parse("/core15_2") - assert res4.matched is False - assert cap["output"] == '无法解析 "/core15_2"。您想要输入的是不是 "1 core15_2" ?' - with output_manager.capture("core15_2") as cap: - output_manager.set_action(lambda x: x, "core15_2") - res5 = alc15_2.parse([2, "core15_2"]) - assert res5.matched is False - assert cap["output"] == '无法解析 "2 core15_2"。您想要输入的是不是 "1 core15_2" ?' - alc15_3 = Alconna("core15_3", Option("rank", compact=True), meta=CommandMeta(fuzzy_match=True)) with output_manager.capture("core15_3") as cap: output_manager.set_action(lambda x: x, "core15_3") @@ -502,13 +468,12 @@ def test_shortcut(): assert not alc16_1.parse("echo").matched assert alc16_1.parse("echo1").content == "print('')" - alc16_2 = Alconna([1, 2, "3"], "core16_2", Args["foo", bool]) - alc16_2.shortcut("test", {"command": [1, "core16_2 True"]}) # type: ignore - assert alc16_2.parse([1, "core16_2 True"]).matched + alc16_2 = Alconna(["/", "."], "core16_2", Args["foo", bool]) + alc16_2.shortcut("test", {"command": ["/core16_2 True"]}) # type: ignore + assert alc16_2.parse("/core16_2 True").matched res9 = alc16_2.parse("test") assert res9.foo is True - assert not alc16_2.parse([2, "test"]).matched - assert not alc16_2.parse("3test").matched + assert not alc16_2.parse(".test").matched alc16.parse("core16 --shortcut list") @@ -658,13 +623,6 @@ def test_help(): with output_manager.capture("core17") as cap: alc17.parse("core17 del --help") assert cap["output"] == "core17 del \nDel bar" - alc17_1 = Alconna( - "core17_1", - Option("foo bar abc baz", Args["qux", int]), - Option("foo qux bar", Args["baz", str]), - ) - alc17_1.parse("core17_1 --help") - alc17_1.parse("core17_1 --help aaa") alc17_2 = Alconna( "core17_2", Subcommand( @@ -865,17 +823,17 @@ def test_action(): Option("--flag|-F", Args["flag", str], action=append, compact=True), Option("-v", action=count), Option("-x|--xyz", action=count), - Option("--q", action=count, requires=["foo", "bar"]), + Option("--q", action=count), ) res = alc24_2.parse( - "core24_2 -A --a -vvv -x -x --xyzxyz -Fabc -Fdef --flag xyz --i 4 --i 5 foo bar --q foo bar --qq" + "core24_2 -A --a -vvv -x -x --xyzxyz -Fabc -Fdef --flag xyz --i 4 --i 5 --q --qq" ) assert res.query[int]("i.foo") == 5 assert res.query[List[int]]("a.value") == [1, 1] assert res.query[List[str]]("flag.flag") == ["abc", "def", "xyz"] assert res.query[int]("v.value") == 3 assert res.query[int]("xyz.value") == 4 - assert res.query[int]("foo_bar_q.value") == 3 + assert res.query[int]("q.value") == 3 alc24_3 = Alconna("core24_3", Option("-t", default=False, action=append_value(True))) assert alc24_3.parse("core24_3 -t -t -t").query("t.value") == [True, True, True]