1#!/usr/bin/env python3
2"""
3
4"""
5
6# Imports:
7from __future__ import annotations
8
9# ##-- stdlib imports
10import builtins
11import datetime
12import enum
13import functools as ftz
14import importlib
15import itertools as itz
16import logging as logmod
17import pathlib as pl
18import re
19import time
20import types
21import typing
22import weakref
23from dataclasses import InitVar, dataclass, field
24from uuid import UUID, uuid1
25
26# ##-- end stdlib imports
27
28# ##-- 1st party imports
29from jgdv import Maybe
30from jgdv.mixins.annotate.annotate import SubAnnotate_m
31from jgdv.structs.chainguard import ChainGuard
32
33# ##-- end 1st party imports
34
35from jgdv import Proto
36from jgdv.cli.errors import ArgParseError
37from .. import _interface as API # noqa: N812
38from .param_spec import ParamSpec
39from .._interface import ParamSpec_p
40
41# ##-- types
42# isort: off
43import abc
44import collections.abc
45from typing import TYPE_CHECKING, cast, assert_type, assert_never
46from typing import Generic, NewType, Any
47from collections.abc import Callable
48# Protocols:
49from typing import Protocol, runtime_checkable
50# Typing Decorators:
51from typing import no_type_check, final, override, overload
52if TYPE_CHECKING:
53 from jgdv import Maybe
54 from typing import Final
55 from typing import ClassVar, LiteralString
56 from typing import Never, Self, Any, Literal
57 from typing import TypeGuard
58 from collections.abc import Iterable, Iterator, Callable, Generator
59 from collections.abc import Sequence, Mapping, MutableMapping, Hashable
60
61##--|
62# isort: on
63# ##-- end types
64
65##-- logging
66logging = logmod.getLogger(__name__)
67##-- end logging
68
[docs]
69class ToggleParam(ParamSpec, annotation=bool, default=True):
70 """ A bool of -param or -not-param """
71
72 desc : str = "A Toggle"
73
74 def __init__(self, *args:Any, **kwargs:Any) -> None: # noqa: ANN401
75 super().__init__(*args, **kwargs)
76 if self.type_ is not bool:
77 msg = "Toggle Params can only be boolean"
78 raise TypeError(msg, self.name)
79
[docs]
80 def _toggle(self) -> Literal[True]:
81 return True
82
[docs]
83 def next_value(self, args:list) -> tuple[str, list, int]:
84 head, *_ = args
85 if self.inverse in head:
86 value = self.default_value
87 else:
88 value = not self.default_value
89
90 return self.name, [value], 1
91
[docs]
92class KeyParam(ParamSpec[str]):
93 """ a param that is specified by a prefix key
94
95 eg: -key val
96 """
97 desc : str = "A Key"
98
[docs]
99 def _keyval(self) -> Literal[True]:
100 return True
101
[docs]
102 def matches_head(self, val:str) -> bool:
103 return val in self.key_strs
104
[docs]
105 def next_value(self, args:list) -> tuple[str, list, int]:
106 """ get the value for a -key val """
107 logging.debug("Getting Key/Value: %s : %s", self.name, args)
108 match args:
109 case [x, y, *_] if self.matches_head(x):
110 return self.name, [y], 2
111 case _:
112 msg = "Failed to parse key"
113 raise ArgParseError(msg)
114
[docs]
115class AssignParam(ParamSpec):
116 """ a joined --key=val param """
117
118 desc : str = "An Assignment Param"
119
120 def __init__(self, *args:Any, **kwargs:Any) -> None: # noqa: ANN401
121 kwargs.setdefault("type", str)
122 super().__init__(*args, **kwargs)
123 if self.type_ is bool:
124 msg = "A boolean assignment param is pointless, use a toggle"
125 raise TypeError(msg, self.name)
126 match self.prefix:
127 case str() as x if bool(x):
128 pass
129 case x:
130 msg = "Bad prefix value for an assignment param"
131 raise ValueError(msg, x)
132 match self.separator:
133 case str() as x if bool(x):
134 pass
135 case x:
136 msg = "Bad separator value for an assignment param"
137 raise ValueError(msg, x)
138
[docs]
139 def _assignment(self) -> Literal[True]:
140 return True
141
[docs]
142 def next_value(self, args:list) -> tuple[str, list, int]:
143 """ get the value for a --key=val """
144 logging.debug("Getting Key Assignment: %s : %s", self.name, args)
145 if self.separator not in args[0]:
146 msg = "Assignment param has no assignment"
147 raise ArgParseError(msg, self.separator, args[0])
148 key,val = self._processor.split_assignment(self, args[0])
149 return self.name, [val], 1
150
[docs]
151class PositionalParam(ParamSpec):
152 """ A param that is specified by its position in the arg list
153
154 Positional Params are formatted as <int>name.
155 eg: <2>blah
156
157 The integer is the *relative* sort of the parameter.
158 As the full parameter list can be accumulated at runtime,
159 ParamSpec sorts them ready for use.
160 See ParamSpec.key_func.
161
162 Suffice to say, at specification time: <2>blah <50>aweg <-2>qqqq
163 Results in a run time positional param list of: qqqq, blah, aweg
164
165 """
166
167 desc : str = "A Positional Param"
168
[docs]
169 def _positional(self) -> Literal[True]:
170 return True
171
[docs]
172 @override
173 @ftz.cached_property
174 def key_str(self) -> str:
175 return cast("str", self.name)
176
[docs]
177 def matches_head(self, val:str) -> bool: # noqa: ARG002
178 return True
179
[docs]
180 def next_value(self, args:list) -> tuple[str, list, int]:
181 match self.count:
182 case API.DEFAULT_COUNT:
183 return self.name, [args[0]], 1
184 case API.UNRESTRICTED_COUNT if self._processor.end_sep in args:
185 idx = args.index(self._processor.end_sep)
186 claimed = args[max(idx, len(args))]
187 return self.name, claimed, len(claimed)
188 case API.UNRESTRICTED_COUNT:
189 return self.name, args[:], len(args)
190 case int() as x if x < len(args):
191 return self.name, args[:x], x
192 case x:
193 msg = "Bad positional count"
194 raise ArgParseError(msg, x)
195
196
[docs]
197class LiteralParam(ToggleParam):
198 """
199 Match on a Literal Parameter.
200 For command/subcmd names etc
201 """
202 desc : str = "A Literal"
203
[docs]
204 def matches_head(self, val:str) -> bool:
205 """ test to see if a cli argument matches this param """
206 match val:
207 case x if x == self.key_str:
208 return True
209 case _:
210 return False