Skip to content

Commit

Permalink
💥 version 1.3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
RF-Tar-Railt committed Oct 15, 2022
1 parent a2c83bd commit d9de7ca
Show file tree
Hide file tree
Showing 26 changed files with 587 additions and 319 deletions.
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Alconna 1.3.x:

## Alconna 1.3.2 ~ 1.3.2.1
1. 添加通过"+"以组合字符串与选项或者子命令等隐式构建命令的方法
2. 允许自定义内建选项的名称,比如改"--help"为"帮助"
3. `Arpamar.find` 现在可以用 query_path 了
4. `Arpamar.query` 现在返回的是MappingProxyType, 若需要修改path的值请用`Arpamar.update`
5. 修复 bugs

## Alconna 1.3.1
1. 调整lang config
2. 修改 help text 的 bug
Expand Down
539 changes: 315 additions & 224 deletions pdm.lock

Large diffs are not rendered by default.

19 changes: 10 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ build-backend = "pdm.pep517.api"

[project]
name = "arclet-alconna"
version = "1.3.1"
version = "1.3.2.1"
description = "A High-performance, Generality, Humane Command Line Arguments Parser Library."
authors = [
{name = "RF-Tar-Railt", email = "[email protected]"},
]
dependencies = [
"typing-extensions~=4.3.0",
"nepattern>=0.2.0",
"typing-extensions~=4.4.0",
"nepattern>=0.3.0",
]
requires-python = ">=3.8"
readme = "README-EN.md"
Expand All @@ -27,6 +27,7 @@ keywords = [
"optparse"
]
classifiers=[
"Typing :: Typed",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.8",
Expand All @@ -40,10 +41,10 @@ documentation = "https://arcletproject.github.io/docs/alconna/tutorial"
repository = "https://github.com/ArcletProject/Alconna"

[project.optional-dependencies]
full = ["arclet-alconna-tools"]
cli = ["arclet-alconna-cli"]
graia = ["arclet-alconna-graia"]
all = ["arclet-alconna-cli", "arclet-alconna-graia", "arclet-alconna-tools"]
full = ["arclet-alconna-tools>=0.2.0"]
cli = ["arclet-alconna-cli>=0.3.0"]
graia = ["arclet-alconna-graia>=0.8.0"]
all = ["arclet-alconna-cli>=0.3.0", "arclet-alconna-graia>=0.8.0", "arclet-alconna-tools>=0.2.0"]

[tool.pdm]

Expand All @@ -53,8 +54,8 @@ includes = ["src/arclet"]
[tool.pdm.dev-dependencies]
dev = [
"pytest~=7.1.3",
"coverage~=6.4.4",
"pydeps~=1.10.12"
"coverage~=6.5.0",
"pydeps~=1.10.24"
]

[tool.pylint.BASIC]
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
typing_extensions~=4.3.0
nepattern>=0.2.0
typing_extensions~=4.4.0
nepattern>=0.3.0
4 changes: 2 additions & 2 deletions requirements_test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pytest~=7.1.3
coverage~=6.4.4
pydeps~=1.10.22
coverage~=6.5.0
pydeps~=1.10.24
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
url="https://github.com/ArcletProject/Alconna",
package_dir={"": "src"},
packages=find_namespace_packages(where="src"),
install_requires=["typing-extensions~=4.3.0", "nepattern~=0.1.2"],
install_requires=["typing-extensions>=4.3,<4.5", "nepattern~=0.1.2"],
extras_require={
'graia': [
'arclet-alconna-graia',
Expand Down
16 changes: 8 additions & 8 deletions src/arclet/alconna/analysis/analyser.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ def __init__(self, alconna: "Alconna"):
self.message_cache = alconna.namespace_config.enable_message_cache
self.param_ids = set()
self.command_params = {}
self._special = {}
self._special.update(
[(i, handle_help) for i in alconna.namespace_config.builtin_option_name['help']] +
[(i, handle_completion) for i in alconna.namespace_config.builtin_option_name['completion']] +
[(i, handle_shortcut) for i in alconna.namespace_config.builtin_option_name['shortcut']]
)
self.__handle_main_args__(alconna.args, alconna.nargs)
self.__init_header__(alconna.command, alconna.headers)
self.__init_actions__()
Expand Down Expand Up @@ -300,12 +306,6 @@ def process(self, data: DataCollection[Union[str, Any]]) -> 'Analyser':
self.temp_token = self.generate_token(raw_data)
return self

_special = {
"--help": handle_help, "-h": handle_help,
"--comp": handle_completion, "-cp": handle_completion,
"--shortcut": handle_shortcut, "-sct": handle_shortcut
}

def analyse(
self,
message: Union[DataCollection[Union[str, Any]], None] = None,
Expand Down Expand Up @@ -380,7 +380,7 @@ def analyse(
return handle_completion(self, comp.args[0])
except (ParamsUnmatched, ArgumentMissing) as e1:
if rest := self.release():
if rest[-1] in ("--comp", "-cp"):
if rest[-1] in self.alconna.namespace_config.builtin_option_name['completion']:
return handle_completion(self, self.context)
if handler := self._special.get(rest[-1]):
return handler(self)
Expand All @@ -401,7 +401,7 @@ def analyse(

rest = self.release()
if len(rest) > 0:
if rest[-1] in ("--comp", "-cp"):
if rest[-1] in self.alconna.namespace_config.builtin_option_name['completion']:
return handle_completion(self, rest[-2])
exc = ParamsUnmatched(config.lang.analyser_param_unmatched.format(target=self.popitem(move=False)[0]))
else:
Expand Down
2 changes: 2 additions & 0 deletions src/arclet/alconna/analysis/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ..typing import DataCollection, TDataCollection
from ..base import Option, Subcommand, Sentence
from ..args import Args
from ..config import config

if TYPE_CHECKING:
from ..arpamar import Arpamar
Expand Down Expand Up @@ -72,6 +73,7 @@ class _DummyAnalyser(Analyser):
class _DummyALC:
options = []
meta = namedtuple("Meta", ["keep_crlf", "fuzzy_match"])(False, False)
namespace_config = config.default_namespace

def __new__(cls, *args, **kwargs):
cls.alconna = cls._DummyALC() # type: ignore
Expand Down
4 changes: 2 additions & 2 deletions src/arclet/alconna/analysis/parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def analyse_args(analyser: 'Analyser', args: Args) -> Dict[str, Any]:
default_val = arg['field'].default_gen
optional = arg['optional']
may_arg, _str = analyser.popitem(seps)
if may_arg in ("--comp", "-cp"):
if may_arg in analyser.alconna.namespace_config.builtin_option_name['completion']:
raise CompletionTriggered(arg)
if not may_arg or (_str and may_arg in analyser.param_ids):
analyser.pushback(may_arg)
Expand Down Expand Up @@ -274,7 +274,7 @@ def analyse_subcommand(analyser: 'Analyser', param: Subcommand) -> Tuple[str, Su
args = False
for _ in param.sub_part_len:
_text, _str = analyser.popitem(param.separators, move=False)
if _text in ("--comp", "-cp"):
if _text in analyser.alconna.namespace_config.builtin_option_name['completion']:
raise CompletionTriggered(param)
_param = _param if (_param := (param.sub_params.get(_text) if _str and _text else Ellipsis)) else (
analyse_unmatch_params(param.sub_params.values(), _text, analyser.fuzzy_match)
Expand Down
14 changes: 8 additions & 6 deletions src/arclet/alconna/analysis/special.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
from nepattern import Empty
from ..components.output import output_manager
from ..base import Subcommand, Option
from ..args import ArgUnit
from ..builtin import ShortcutOption
from ..args import ArgUnit, Args
from ..config import config
from ..exceptions import ParamsUnmatched
from .parts import analyse_option
from .parts import analyse_args

if TYPE_CHECKING:
from .analyser import Analyser


def handle_help(analyser: "Analyser"):
analyser.current_index, analyser.content_index = analyser.head_pos
_help_param = [str(i) for i in analyser.release() if i not in {"-h", "--help"}]
_help_param = [
str(i) for i in analyser.release() if i not in analyser.alconna.namespace_config.builtin_option_name['help']
]

def _get_help():
formatter = analyser.alconna.formatter_type(analyser.alconna)
Expand All @@ -27,7 +28,8 @@ def _get_help():


def handle_shortcut(analyser: "Analyser"):
opt_v = analyse_option(analyser, ShortcutOption)[1]["args"]
analyser.popitem()
opt_v = analyse_args(analyser, Args["delete;O", "delete"]["name", str]["command", str, "_"])
try:
msg = analyser.alconna.shortcut(
opt_v["name"],
Expand Down Expand Up @@ -89,7 +91,7 @@ def _handle_none(analyser: "Analyser", got: List[str]):
f"{unit['value']}{'' if default is None else f' ({None if default is Empty else default})'}"
)
for opt in filter(
lambda x: x.name not in ("--shortcut", "--comp"),
lambda x: x.name not in analyser.alconna.namespace_config.builtin_option_name['completion'],
analyser.alconna.options,
):
if opt.requires and all(opt.requires[0] not in i for i in got):
Expand Down
5 changes: 4 additions & 1 deletion src/arclet/alconna/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def from_callable(cls, target: Callable, extra: Literal["allow", "ignore", "reje
de = inspect.Signature.empty
if param.kind == param.KEYWORD_ONLY:
if anno == bool:
anno = BasePattern(f"(?:-*no)?-*{name}", 3, bool, lambda x: not x.lstrip("-").startswith('no'))
anno = BasePattern(f"(?:-*no)?-*{name}", 3, bool, lambda _, x: not x.lstrip("-").startswith('no'))
else:
_args.add_argument(f"${name}_key", value=f"-*{name}")
_args.keyword_only.append(name)
Expand Down Expand Up @@ -352,6 +352,9 @@ def __merge__(self, other) -> "Args":
def __add__(self, other) -> "Args":
return self.__merge__(other)

def __iadd__(self, other) -> "Args":
return self.__merge__(other)

def __lshift__(self, other) -> "Args":
return self.__merge__(other)

Expand Down
23 changes: 12 additions & 11 deletions src/arclet/alconna/arpamar.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Union, Dict, List, Any, Optional, TYPE_CHECKING, Type, TypeVar, Tuple, overload, Generic
from typing import Union, Dict, List, Any, Optional, TYPE_CHECKING, Type, TypeVar, Tuple, overload, Generic, Mapping
from types import MappingProxyType
from contextlib import suppress
from nepattern import Empty
from .typing import TDataCollection
Expand Down Expand Up @@ -194,20 +195,20 @@ def _r_opt(_p: str, _s: List[str], _opts: Dict[str, OptionResult]):
return None, prefix

@overload
def query(self, path: str) -> Union[Dict[str, Any], Any, None]:
def query(self, path: str) -> Union[Mapping[str, Any], Any, None]:
...

@overload
def query(self, path: str, default: T) -> Union[T, Dict[str, Any], Any]:
def query(self, path: str, default: T) -> Union[T, Mapping[str, Any], Any]:
...

def query(self, path: str, default: Optional[T] = None) -> Union[Any, Dict[str, Any], T, None]:
def query(self, path: str, default: Optional[T] = None) -> Union[Any, Mapping[str, Any], T, None]:
"""根据path查询值"""
cache, endpoint = self.__require__(path.split('.'))
if cache is None:
return default
if not endpoint:
return cache if cache is not None else default
return MappingProxyType(cache) if cache is not None else default
return cache.get(endpoint, default)

def update(self, path: str, value: Any):
Expand All @@ -223,17 +224,17 @@ def update(self, path: str, value: Any):
cache.update(value)
self._record.update([f"{path}.{k}" for k in value])

def query_with(self, arg_type: Type[T], name: Optional[str] = None, default: Optional[T] = None) -> Optional[T]:
def query_with(self, arg_type: Type[T], path: Optional[str] = None, default: Optional[T] = None) -> Optional[T]:
"""根据类型查询参数"""
if name:
return res if isinstance(res := self.query(name, Empty), arg_type) else default
if path:
return res if isinstance(res := self.query(path, Empty), arg_type) else default
with suppress(IndexError):
return [v for v in self.all_matched_args.values() if isinstance(v, arg_type)][0]
return default

def find(self, name: str) -> bool:
"""查询是否存在"""
return any([name in self.components, name in self.main_args, name in self.other_args])
def find(self, path: str) -> bool:
"""查询路径是否存在"""
return self.query(path, Empty) != Empty

def clean(self):
if not self._record:
Expand Down
47 changes: 46 additions & 1 deletion src/arclet/alconna/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import re
from dataclasses import dataclass, field
from typing import Union, Dict, Callable, Any, Optional, Sequence, List, TypedDict, Set, FrozenSet
from typing import Union, Dict, Callable, Any, Optional, Sequence, List, TypedDict, Set, FrozenSet, overload

from .args import Args
from .exceptions import InvalidParam
Expand Down Expand Up @@ -116,6 +116,33 @@ def __init__(
" ".join(rest) + (" " if rest else "") + name, args, dest, action, separators, help_text, requires
)

@overload
def __add__(self, other: "Option") -> "Subcommand":
...

@overload
def __add__(self, other: Args) -> "Option":
...

def __add__(self, other):
if isinstance(other, Option):
return Subcommand(
self.name, [other], self.args, self.dest, self.action,
self.separators, self.help_text, self.requires
)
if isinstance(other, Args):
self.args += other
self.nargs = len(self.args)
self._hash = self._calc_hash()
return self
raise TypeError(f"unsupported operand type(s) for +: 'Option' and '{other.__class__.__name__}'")

def __radd__(self, other):
if isinstance(other, str):
from .core import Alconna
return Alconna(other, self)
raise TypeError(f"unsupported operand type(s) for +: '{other.__class__.__name__}' and 'Option'")


class Subcommand(CommandNode):
"""子命令, 次于主命令, 可解析 SubOption"""
Expand All @@ -137,6 +164,24 @@ def __init__(
self.sub_params = {}
self.sub_part_len = range(self.nargs)

def __add__(self, other):
if isinstance(other, (Option, str)):
self.options.append(Option(other) if isinstance(other, str) else other)
self._hash = self._calc_hash()
return self
if isinstance(other, Args):
self.args += other
self.nargs = len(self.args)
self._hash = self._calc_hash()
return self
raise TypeError(f"unsupported operand type(s) for +: 'Subcommand' and '{other.__class__.__name__}'")

def __radd__(self, other):
if isinstance(other, str):
from .core import Alconna
return Alconna(other, self)
raise TypeError(f"unsupported operand type(s) for +: '{other.__class__.__name__}' and 'Subcommand'")


@dataclass
class Sentence:
Expand Down
15 changes: 5 additions & 10 deletions src/arclet/alconna/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,8 @@
from .components.action import ArgAction
from .components.behavior import ArpamarBehavior
from .exceptions import BehaveCancelled
from .base import Option
from .args import Args

__all__ = ["HelpOption", "ShortcutOption", "CompletionOption", "set_default", "store_value", "version"]

HelpOption = Option("--help|-h", help_text="显示帮助信息")
ShortcutOption = Option(
'--shortcut|-sct', Args["delete;O", "delete"]["name", str]["command", str, "_"],
help_text='设置快捷命令'
)
CompletionOption = Option("--comp|-cp", help_text="补全当前命令")
__all__ = ["set_default", "store_value", "version", "store_true", "store_false"]


class _StoreValue(ArgAction):
Expand All @@ -31,6 +22,10 @@ def store_value(value: Any):
return _StoreValue(value)


store_true = store_value(True)
store_false = store_value(False)


if TYPE_CHECKING:
from arclet.alconna import alconna_version
from arclet.alconna.arpamar import Arpamar
Expand Down
Loading

0 comments on commit d9de7ca

Please sign in to comment.