avrae-ls 0.4.1__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/METADATA +31 -11
- avrae_ls-0.5.0.dist-info/RECORD +6 -0
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/WHEEL +1 -2
- avrae_ls/__init__.py +0 -3
- avrae_ls/__main__.py +0 -108
- avrae_ls/alias_preview.py +0 -346
- avrae_ls/api.py +0 -2014
- avrae_ls/argparser.py +0 -430
- avrae_ls/argument_parsing.py +0 -67
- avrae_ls/code_actions.py +0 -282
- avrae_ls/codes.py +0 -3
- avrae_ls/completions.py +0 -1391
- avrae_ls/config.py +0 -496
- avrae_ls/context.py +0 -229
- avrae_ls/cvars.py +0 -115
- avrae_ls/diagnostics.py +0 -751
- avrae_ls/dice.py +0 -33
- avrae_ls/parser.py +0 -44
- avrae_ls/runtime.py +0 -661
- avrae_ls/server.py +0 -399
- avrae_ls/signature_help.py +0 -252
- avrae_ls/symbols.py +0 -266
- avrae_ls-0.4.1.dist-info/RECORD +0 -34
- avrae_ls-0.4.1.dist-info/top_level.txt +0 -2
- draconic/__init__.py +0 -4
- draconic/exceptions.py +0 -157
- draconic/helpers.py +0 -236
- draconic/interpreter.py +0 -1091
- draconic/string.py +0 -100
- draconic/types.py +0 -364
- draconic/utils.py +0 -78
- draconic/versions.py +0 -4
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/entry_points.txt +0 -0
- {avrae_ls-0.4.1.dist-info → avrae_ls-0.5.0.dist-info}/licenses/LICENSE +0 -0
avrae_ls/argparser.py
DELETED
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
import collections
|
|
2
|
-
import itertools
|
|
3
|
-
import re
|
|
4
|
-
import string
|
|
5
|
-
from typing import ClassVar, Iterator
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class BadArgument(Exception):
|
|
9
|
-
pass
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ExpectedClosingQuoteError(Exception):
|
|
13
|
-
def __init__(self, quote):
|
|
14
|
-
super().__init__(quote)
|
|
15
|
-
self.quote = quote
|
|
16
|
-
|
|
17
|
-
def __str__(self):
|
|
18
|
-
return f"Expected closing quote {self.quote}"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class InvalidArgument(Exception):
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class StringView:
|
|
26
|
-
"""Minimal stand-in for disnake.ext.commands.view.StringView used by argparser."""
|
|
27
|
-
|
|
28
|
-
def __init__(self, buffer: str):
|
|
29
|
-
self.buffer = buffer
|
|
30
|
-
self.index = 0
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def current(self):
|
|
34
|
-
if self.eof:
|
|
35
|
-
return None
|
|
36
|
-
return self.buffer[self.index]
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def eof(self) -> bool:
|
|
40
|
-
return self.index >= len(self.buffer)
|
|
41
|
-
|
|
42
|
-
def skip_ws(self):
|
|
43
|
-
while not self.eof and self.buffer[self.index].isspace():
|
|
44
|
-
self.index += 1
|
|
45
|
-
|
|
46
|
-
def get(self):
|
|
47
|
-
if self.eof:
|
|
48
|
-
return None
|
|
49
|
-
ch = self.buffer[self.index]
|
|
50
|
-
self.index += 1
|
|
51
|
-
return ch
|
|
52
|
-
|
|
53
|
-
def undo(self):
|
|
54
|
-
if self.index > 0:
|
|
55
|
-
self.index -= 1
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
EPHEMERAL_ARG_RE = re.compile(r"(\S+)(\d+)")
|
|
59
|
-
SINGLE_ARG_RE = re.compile(r"([a-zA-Z]\S*(?<!\d))(\d+)?") # g1: flag name g2: ephem?
|
|
60
|
-
FLAG_ARG_RE = re.compile(r"-+([a-zA-Z]\S*(?<!\d))(\d+)?") # g1: flag name g2: ephem?
|
|
61
|
-
SINGLE_ARG_EXCEPTIONS = {"-i", "-h", "-v"}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def argsplit(args: str):
|
|
65
|
-
view = CustomStringView(args.strip())
|
|
66
|
-
args_out = []
|
|
67
|
-
while not view.eof:
|
|
68
|
-
view.skip_ws()
|
|
69
|
-
word = view.get_quoted_word()
|
|
70
|
-
if word is not None:
|
|
71
|
-
args_out.append(word)
|
|
72
|
-
return args_out
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
# ==== argparse ====
|
|
76
|
-
class Argument:
|
|
77
|
-
def __init__(self, name: str, value, pos: int):
|
|
78
|
-
self.name = name
|
|
79
|
-
self.value = value
|
|
80
|
-
self.pos = pos
|
|
81
|
-
|
|
82
|
-
def __repr__(self):
|
|
83
|
-
return f"<{type(self).__name__} name={self.name!r} value={self.value!r} pos={self.pos}>"
|
|
84
|
-
|
|
85
|
-
def __eq__(self, other):
|
|
86
|
-
return self.name == other.name and self.value == other.value and self.pos == other.pos
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class EphemeralArgument(Argument):
|
|
90
|
-
def __init__(self, name: str, value, pos: int, uses: int):
|
|
91
|
-
super().__init__(name, value, pos)
|
|
92
|
-
self.uses = uses
|
|
93
|
-
self.used = 0
|
|
94
|
-
|
|
95
|
-
def has_remaining_uses(self):
|
|
96
|
-
return self.used < self.uses
|
|
97
|
-
|
|
98
|
-
def __repr__(self):
|
|
99
|
-
return (
|
|
100
|
-
f"<{type(self).__name__} name={self.name!r} value={self.value!r} pos={self.pos} uses={self.uses} "
|
|
101
|
-
f"used={self.used}>"
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
def __eq__(self, other):
|
|
105
|
-
return (
|
|
106
|
-
self.name == other.name and self.value == other.value and self.pos == other.pos and self.uses == other.uses
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _argparse_arg(name: str, ephem: str | None, value, idx: int, parse_ephem: bool) -> Argument:
|
|
111
|
-
if ephem and parse_ephem:
|
|
112
|
-
return EphemeralArgument(name=name, value=value, pos=idx, uses=int(ephem))
|
|
113
|
-
elif ephem:
|
|
114
|
-
return Argument(name=name + ephem, value=value, pos=idx)
|
|
115
|
-
else:
|
|
116
|
-
return Argument(name=name, value=value, pos=idx)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def _argparse_iterator(args: list[str], parse_ephem: bool) -> Iterator[Argument]:
|
|
120
|
-
flag_arg_state = None # state: name, ephem?
|
|
121
|
-
idx = 0
|
|
122
|
-
for idx, arg in enumerate(args):
|
|
123
|
-
# prio: single arg exceptions, flag args, values, single args
|
|
124
|
-
if arg in SINGLE_ARG_EXCEPTIONS:
|
|
125
|
-
if flag_arg_state is not None:
|
|
126
|
-
name, ephem = flag_arg_state
|
|
127
|
-
yield _argparse_arg(name, ephem, True, idx - 1, parse_ephem)
|
|
128
|
-
flag_arg_state = None
|
|
129
|
-
yield Argument(name=arg.lstrip("-"), value=True, pos=idx)
|
|
130
|
-
elif match := FLAG_ARG_RE.fullmatch(arg):
|
|
131
|
-
if flag_arg_state is not None:
|
|
132
|
-
name, ephem = flag_arg_state
|
|
133
|
-
yield _argparse_arg(name, ephem, True, idx - 1, parse_ephem)
|
|
134
|
-
flag_arg_state = match.group(1), match.group(2)
|
|
135
|
-
elif flag_arg_state is not None:
|
|
136
|
-
name, ephem = flag_arg_state
|
|
137
|
-
yield _argparse_arg(name, ephem, arg, idx - 1, parse_ephem)
|
|
138
|
-
flag_arg_state = None
|
|
139
|
-
elif match := SINGLE_ARG_RE.fullmatch(arg):
|
|
140
|
-
name = match.group(1)
|
|
141
|
-
ephem = match.group(2)
|
|
142
|
-
yield _argparse_arg(name, ephem, True, idx, parse_ephem)
|
|
143
|
-
# else: the current element at the head is junk
|
|
144
|
-
|
|
145
|
-
if flag_arg_state is not None:
|
|
146
|
-
name, ephem = flag_arg_state
|
|
147
|
-
yield _argparse_arg(name, ephem, True, idx, parse_ephem)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
# --- main entrypoint ---
|
|
151
|
-
def argparse(args, character=None, splitter=argsplit, parse_ephem=True) -> "ParsedArguments":
|
|
152
|
-
"""
|
|
153
|
-
Given an argument string, returns the parsed arguments using the argument nondeterministic finite automaton.
|
|
154
|
-
If *character* is given, evaluates {}-style math inside the string before parsing.
|
|
155
|
-
If the argument is a string, uses *splitter* to split the string into args.
|
|
156
|
-
If *parse_ephem* is False, arguments like ``-d1`` are saved literally rather than as an ephemeral argument.
|
|
157
|
-
"""
|
|
158
|
-
if isinstance(args, str):
|
|
159
|
-
args = splitter(args)
|
|
160
|
-
|
|
161
|
-
if character:
|
|
162
|
-
from aliasing.evaluators import MathEvaluator
|
|
163
|
-
|
|
164
|
-
evaluator = MathEvaluator.with_character(character)
|
|
165
|
-
args = [evaluator.transformed_str(a) for a in args]
|
|
166
|
-
|
|
167
|
-
parsed_args = list(_argparse_iterator(args, parse_ephem))
|
|
168
|
-
return ParsedArguments(parsed_args)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class ParsedArguments:
|
|
172
|
-
ATTRS: ClassVar[list[str]] = []
|
|
173
|
-
METHODS: ClassVar[list[str]] = [
|
|
174
|
-
"get",
|
|
175
|
-
"last",
|
|
176
|
-
"adv",
|
|
177
|
-
"join",
|
|
178
|
-
"ignore",
|
|
179
|
-
"update",
|
|
180
|
-
"update_nx",
|
|
181
|
-
"set_context",
|
|
182
|
-
"add_context",
|
|
183
|
-
]
|
|
184
|
-
|
|
185
|
-
def __init__(self, args: list[Argument]):
|
|
186
|
-
self._parsed = collections.defaultdict(lambda: [])
|
|
187
|
-
for arg in args:
|
|
188
|
-
self._parsed[arg.name].append(arg)
|
|
189
|
-
self._current_context = None
|
|
190
|
-
self._contexts = {} # type: dict[..., ParsedArguments]
|
|
191
|
-
|
|
192
|
-
@classmethod
|
|
193
|
-
def from_dict(cls, d):
|
|
194
|
-
inst = cls([])
|
|
195
|
-
for key, value in d.items():
|
|
196
|
-
inst[key] = value
|
|
197
|
-
return inst
|
|
198
|
-
|
|
199
|
-
@classmethod
|
|
200
|
-
def empty_args(cls):
|
|
201
|
-
return cls([])
|
|
202
|
-
|
|
203
|
-
def get(self, arg, default=None, type_=str, ephem=False):
|
|
204
|
-
if default is None:
|
|
205
|
-
default = []
|
|
206
|
-
parsed = list(self._get_values(arg, ephem=ephem))
|
|
207
|
-
if not parsed:
|
|
208
|
-
return default
|
|
209
|
-
try:
|
|
210
|
-
return [type_(v) for v in parsed]
|
|
211
|
-
except (ValueError, TypeError):
|
|
212
|
-
raise InvalidArgument(f"One or more arguments cannot be cast to {type_.__name__} (in `{arg}`)")
|
|
213
|
-
|
|
214
|
-
def last(self, arg, default=None, type_=str, ephem=False):
|
|
215
|
-
last_arg = self._get_last(arg, ephem=ephem)
|
|
216
|
-
if last_arg is None:
|
|
217
|
-
return default
|
|
218
|
-
try:
|
|
219
|
-
return type_(last_arg)
|
|
220
|
-
except (ValueError, TypeError):
|
|
221
|
-
raise InvalidArgument(f"{last_arg} cannot be cast to {type_.__name__} (in `{arg}`)")
|
|
222
|
-
|
|
223
|
-
def adv(self, eadv=False, boolwise=False, ephem=False, custom: dict = None):
|
|
224
|
-
adv_str, dis_str, ea_str = "adv", "dis", "eadv"
|
|
225
|
-
if custom is not None:
|
|
226
|
-
if "adv" in custom:
|
|
227
|
-
adv_str = custom["adv"]
|
|
228
|
-
if "dis" in custom:
|
|
229
|
-
dis_str = custom["dis"]
|
|
230
|
-
if "eadv" in custom:
|
|
231
|
-
ea_str = custom["eadv"]
|
|
232
|
-
|
|
233
|
-
adv_arg = self.last(adv_str, default=False, type_=bool, ephem=ephem)
|
|
234
|
-
dis_arg = self.last(dis_str, default=False, type_=bool, ephem=ephem)
|
|
235
|
-
ea_arg = eadv and self.last(ea_str, default=False, type_=bool, ephem=ephem)
|
|
236
|
-
|
|
237
|
-
if ea_arg and not dis_arg:
|
|
238
|
-
out = 2
|
|
239
|
-
elif dis_arg and not (adv_arg or ea_arg):
|
|
240
|
-
out = -1
|
|
241
|
-
elif adv_arg and not dis_arg:
|
|
242
|
-
out = 1
|
|
243
|
-
else:
|
|
244
|
-
out = 0
|
|
245
|
-
|
|
246
|
-
if not boolwise:
|
|
247
|
-
return out
|
|
248
|
-
else:
|
|
249
|
-
return {-1: False, 0: None, 1: True}.get(out)
|
|
250
|
-
|
|
251
|
-
def join(self, arg, connector: str, default=None, ephem=False):
|
|
252
|
-
return connector.join(self.get(arg, ephem=ephem)) or default
|
|
253
|
-
|
|
254
|
-
def ignore(self, arg):
|
|
255
|
-
del self[arg]
|
|
256
|
-
for context in self._contexts.values():
|
|
257
|
-
del context[arg]
|
|
258
|
-
|
|
259
|
-
def update(self, new):
|
|
260
|
-
for k, v in new.items():
|
|
261
|
-
self[k] = v
|
|
262
|
-
|
|
263
|
-
def update_nx(self, new):
|
|
264
|
-
for k, v in new.items():
|
|
265
|
-
if k not in self and v is not None:
|
|
266
|
-
self[k] = v
|
|
267
|
-
|
|
268
|
-
@staticmethod
|
|
269
|
-
def _yield_from_iterable(iterable: Iterator[Argument], ephem: bool):
|
|
270
|
-
for value in iterable:
|
|
271
|
-
if not ephem and isinstance(value, EphemeralArgument):
|
|
272
|
-
continue
|
|
273
|
-
elif isinstance(value, EphemeralArgument):
|
|
274
|
-
if not value.has_remaining_uses():
|
|
275
|
-
continue
|
|
276
|
-
value.used += 1
|
|
277
|
-
yield value.value
|
|
278
|
-
|
|
279
|
-
def _get_values(self, arg, ephem=False):
|
|
280
|
-
iterable = self._parsed[arg]
|
|
281
|
-
if self._current_context in self._contexts:
|
|
282
|
-
iterable = itertools.chain(self._parsed[arg], self._contexts[self._current_context]._parsed[arg])
|
|
283
|
-
yield from self._yield_from_iterable(iterable, ephem)
|
|
284
|
-
|
|
285
|
-
def _get_last(self, arg, ephem=False):
|
|
286
|
-
iterable = reversed(self._parsed[arg])
|
|
287
|
-
if self._current_context in self._contexts:
|
|
288
|
-
iterable = itertools.chain(
|
|
289
|
-
reversed(self._contexts[self._current_context]._parsed[arg]), reversed(self._parsed[arg])
|
|
290
|
-
)
|
|
291
|
-
return next((self._yield_from_iterable(iterable, ephem)), None)
|
|
292
|
-
|
|
293
|
-
def set_context(self, context):
|
|
294
|
-
self._current_context = context
|
|
295
|
-
|
|
296
|
-
def add_context(self, context, args):
|
|
297
|
-
if isinstance(args, dict):
|
|
298
|
-
if all(
|
|
299
|
-
isinstance(k, (collections.UserString, str))
|
|
300
|
-
and isinstance(v, (collections.UserList, list))
|
|
301
|
-
and all(isinstance(i, (collections.UserString, str)) for i in v)
|
|
302
|
-
for k, v in args.items()
|
|
303
|
-
):
|
|
304
|
-
args = ParsedArguments.from_dict(args)
|
|
305
|
-
else:
|
|
306
|
-
raise InvalidArgument(f"Argument is not in the format dict[str, list[str]] (in {args})")
|
|
307
|
-
elif not isinstance(args, ParsedArguments):
|
|
308
|
-
raise InvalidArgument(f"Argument is not a dict or ParsedArguments (in {args})")
|
|
309
|
-
|
|
310
|
-
self._contexts[context] = args
|
|
311
|
-
|
|
312
|
-
def __contains__(self, item):
|
|
313
|
-
return item in self._parsed and self._parsed[item]
|
|
314
|
-
|
|
315
|
-
def __len__(self) -> int:
|
|
316
|
-
return len(self._parsed)
|
|
317
|
-
|
|
318
|
-
def __setitem__(self, key, value):
|
|
319
|
-
if isinstance(value, (collections.UserList, list)):
|
|
320
|
-
true_val = [Argument(key, v, idx) for idx, v in enumerate(value)]
|
|
321
|
-
else:
|
|
322
|
-
true_val = [Argument(key, value, 0)]
|
|
323
|
-
self._parsed[key] = true_val
|
|
324
|
-
if match := EPHEMERAL_ARG_RE.fullmatch(key):
|
|
325
|
-
arg, num = match.group(1), match.group(2)
|
|
326
|
-
ephem_val = [EphemeralArgument(arg, v.value, v.pos, int(num)) for v in true_val]
|
|
327
|
-
self._parsed[arg] = ephem_val
|
|
328
|
-
|
|
329
|
-
def __delitem__(self, arg):
|
|
330
|
-
if arg in self._parsed:
|
|
331
|
-
del self._parsed[arg]
|
|
332
|
-
|
|
333
|
-
def __iter__(self) -> Iterator[str]:
|
|
334
|
-
return iter(self._parsed.keys())
|
|
335
|
-
|
|
336
|
-
def __repr__(self):
|
|
337
|
-
return f"<ParsedArguments parsed={dict.__repr__(self._parsed)} context={self._current_context}>"
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
# ==== other helpers ====
|
|
341
|
-
def argquote(arg: str):
|
|
342
|
-
if any(char in arg for char in string.whitespace):
|
|
343
|
-
arg = arg.replace('"', '\\"')
|
|
344
|
-
arg = f'"{arg}"'
|
|
345
|
-
return arg
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
QUOTE_PAIRS = {
|
|
349
|
-
'"': '"',
|
|
350
|
-
"'": "'",
|
|
351
|
-
"‘": "’",
|
|
352
|
-
"‚": "‛",
|
|
353
|
-
"“": "”",
|
|
354
|
-
"„": "‟",
|
|
355
|
-
"⹂": "⹂",
|
|
356
|
-
"「": "」",
|
|
357
|
-
"『": "』",
|
|
358
|
-
"〝": "〞",
|
|
359
|
-
"﹁": "﹂",
|
|
360
|
-
"﹃": "﹄",
|
|
361
|
-
""": """,
|
|
362
|
-
"「": "」",
|
|
363
|
-
"«": "»",
|
|
364
|
-
"‹": "›",
|
|
365
|
-
"《": "》",
|
|
366
|
-
"〈": "〉",
|
|
367
|
-
}
|
|
368
|
-
ALL_QUOTES = set(QUOTE_PAIRS.keys()) | set(QUOTE_PAIRS.values())
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
class CustomStringView(StringView):
|
|
372
|
-
def get_quoted_word(self):
|
|
373
|
-
current = self.current
|
|
374
|
-
if current is None:
|
|
375
|
-
return None
|
|
376
|
-
|
|
377
|
-
close_quote = QUOTE_PAIRS.get(current)
|
|
378
|
-
is_quoted = bool(close_quote)
|
|
379
|
-
if is_quoted:
|
|
380
|
-
result = []
|
|
381
|
-
_escaped_quotes = (current, close_quote)
|
|
382
|
-
else:
|
|
383
|
-
result = [current]
|
|
384
|
-
_escaped_quotes = ALL_QUOTES
|
|
385
|
-
|
|
386
|
-
while not self.eof:
|
|
387
|
-
current = self.get()
|
|
388
|
-
if not current:
|
|
389
|
-
if is_quoted:
|
|
390
|
-
raise ExpectedClosingQuoteError(close_quote)
|
|
391
|
-
return "".join(result)
|
|
392
|
-
|
|
393
|
-
if current == "\\":
|
|
394
|
-
next_char = self.get()
|
|
395
|
-
if next_char in _escaped_quotes:
|
|
396
|
-
result.append(next_char)
|
|
397
|
-
else:
|
|
398
|
-
self.undo()
|
|
399
|
-
result.append(current)
|
|
400
|
-
continue
|
|
401
|
-
|
|
402
|
-
if not is_quoted and current in ALL_QUOTES and current not in {"'", "’"}:
|
|
403
|
-
close_quote = QUOTE_PAIRS.get(current)
|
|
404
|
-
is_quoted = True
|
|
405
|
-
_escaped_quotes = (current, close_quote)
|
|
406
|
-
continue
|
|
407
|
-
|
|
408
|
-
if is_quoted and current == close_quote:
|
|
409
|
-
next_char = self.get()
|
|
410
|
-
valid_eof = not next_char or next_char.isspace()
|
|
411
|
-
if not valid_eof:
|
|
412
|
-
self.undo()
|
|
413
|
-
close_quote = None
|
|
414
|
-
is_quoted = False
|
|
415
|
-
_escaped_quotes = ALL_QUOTES
|
|
416
|
-
continue
|
|
417
|
-
return "".join(result)
|
|
418
|
-
|
|
419
|
-
if current.isspace() and not is_quoted:
|
|
420
|
-
return "".join(result)
|
|
421
|
-
|
|
422
|
-
result.append(current)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if __name__ == "__main__": # pragma: no cover
|
|
426
|
-
while True:
|
|
427
|
-
try:
|
|
428
|
-
print(argsplit(input(">>> ")))
|
|
429
|
-
except BadArgument as e:
|
|
430
|
-
print(e)
|
avrae_ls/argument_parsing.py
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import List, Sequence
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
_NUM_PLACEHOLDER_RE = re.compile(r"%(\d+)%|&(\d+)&")
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _escape_quotes(arg: str) -> str:
|
|
11
|
-
"""Escape quotes/backslashes to mimic Avrae's quote-escaping behavior."""
|
|
12
|
-
return arg.replace("\\", "\\\\").replace('"', '\\"').replace("'", "\\'")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def _ensure_args(args: Sequence[str] | None, count: int) -> List[str]:
|
|
16
|
-
out = list(args or [])
|
|
17
|
-
while len(out) < count:
|
|
18
|
-
out.append(f"arg{len(out) + 1}")
|
|
19
|
-
return out
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def apply_argument_parsing(text: str, args: Sequence[str] | None = None) -> str:
|
|
23
|
-
"""
|
|
24
|
-
Apply Avrae's argument placeholder replacement rules to an alias body.
|
|
25
|
-
|
|
26
|
-
Supports:
|
|
27
|
-
- %N% : non-code replacement (quotes added if arg contains spaces)
|
|
28
|
-
- %*% : full arg string
|
|
29
|
-
- &N& : in-code replacement with quote escaping
|
|
30
|
-
- &*& : full arg string with quote escaping
|
|
31
|
-
- &ARGS&: Python-style list literal of args
|
|
32
|
-
"""
|
|
33
|
-
max_idx = 0
|
|
34
|
-
for match in _NUM_PLACEHOLDER_RE.finditer(text):
|
|
35
|
-
groups = [g for g in match.groups() if g]
|
|
36
|
-
if groups:
|
|
37
|
-
max_idx = max(max_idx, int(groups[0]))
|
|
38
|
-
|
|
39
|
-
args_list = _ensure_args(args, max_idx)
|
|
40
|
-
|
|
41
|
-
def get_arg(idx: int) -> str:
|
|
42
|
-
zero = idx - 1
|
|
43
|
-
if zero < 0 or zero >= len(args_list):
|
|
44
|
-
return ""
|
|
45
|
-
return str(args_list[zero])
|
|
46
|
-
|
|
47
|
-
def percent_repl(match: re.Match) -> str:
|
|
48
|
-
idx = int(match.group(1))
|
|
49
|
-
val = get_arg(idx)
|
|
50
|
-
return f'"{val}"' if " " in val else val
|
|
51
|
-
|
|
52
|
-
def amp_repl(match: re.Match) -> str:
|
|
53
|
-
idx = int(match.group(1))
|
|
54
|
-
val = get_arg(idx)
|
|
55
|
-
return _escape_quotes(val)
|
|
56
|
-
|
|
57
|
-
full_args = " ".join(args_list)
|
|
58
|
-
escaped_full_args = _escape_quotes(full_args)
|
|
59
|
-
list_literal = "[" + ", ".join(repr(a) for a in args_list) + "]"
|
|
60
|
-
|
|
61
|
-
# order: numeric placeholders first, then multi-arg macros
|
|
62
|
-
text = re.sub(r"%(\d+)%", percent_repl, text)
|
|
63
|
-
text = text.replace("%*%", full_args)
|
|
64
|
-
text = re.sub(r"&(\d+)&", amp_repl, text)
|
|
65
|
-
text = text.replace("&*&", escaped_full_args)
|
|
66
|
-
text = text.replace("&ARGS&", list_literal)
|
|
67
|
-
return text
|