AdvancedTagscript 3.2.3__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.
- TagScriptEngine/__init__.py +227 -0
- TagScriptEngine/_warnings.py +88 -0
- TagScriptEngine/adapter/__init__.py +50 -0
- TagScriptEngine/adapter/discordadapters.py +596 -0
- TagScriptEngine/adapter/functionadapter.py +23 -0
- TagScriptEngine/adapter/intadapter.py +22 -0
- TagScriptEngine/adapter/objectadapter.py +35 -0
- TagScriptEngine/adapter/redbotadapters.py +161 -0
- TagScriptEngine/adapter/stringadapter.py +47 -0
- TagScriptEngine/block/__init__.py +130 -0
- TagScriptEngine/block/allowedmentions.py +60 -0
- TagScriptEngine/block/assign.py +43 -0
- TagScriptEngine/block/breakblock.py +41 -0
- TagScriptEngine/block/case.py +63 -0
- TagScriptEngine/block/command.py +141 -0
- TagScriptEngine/block/comment.py +29 -0
- TagScriptEngine/block/control.py +149 -0
- TagScriptEngine/block/cooldown.py +95 -0
- TagScriptEngine/block/count.py +68 -0
- TagScriptEngine/block/embedblock.py +306 -0
- TagScriptEngine/block/fiftyfifty.py +34 -0
- TagScriptEngine/block/helpers.py +164 -0
- TagScriptEngine/block/loosevariablegetter.py +40 -0
- TagScriptEngine/block/mathblock.py +164 -0
- TagScriptEngine/block/randomblock.py +51 -0
- TagScriptEngine/block/range.py +56 -0
- TagScriptEngine/block/redirect.py +42 -0
- TagScriptEngine/block/replaceblock.py +110 -0
- TagScriptEngine/block/require_blacklist.py +79 -0
- TagScriptEngine/block/shortcutredirect.py +23 -0
- TagScriptEngine/block/stopblock.py +38 -0
- TagScriptEngine/block/strf.py +70 -0
- TagScriptEngine/block/strictvariablegetter.py +38 -0
- TagScriptEngine/block/substr.py +25 -0
- TagScriptEngine/block/urlencodeblock.py +41 -0
- TagScriptEngine/exceptions.py +105 -0
- TagScriptEngine/interface/__init__.py +14 -0
- TagScriptEngine/interface/adapter.py +75 -0
- TagScriptEngine/interface/block.py +124 -0
- TagScriptEngine/interpreter.py +502 -0
- TagScriptEngine/py.typed +0 -0
- TagScriptEngine/utils.py +71 -0
- TagScriptEngine/verb.py +160 -0
- advancedtagscript-3.2.3.dist-info/METADATA +99 -0
- advancedtagscript-3.2.3.dist-info/RECORD +48 -0
- advancedtagscript-3.2.3.dist-info/WHEEL +5 -0
- advancedtagscript-3.2.3.dist-info/licenses/LICENSE +1 -0
- advancedtagscript-3.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple, cast
|
|
4
|
+
|
|
5
|
+
from ..interface import verb_required_block
|
|
6
|
+
from ..interpreter import Context
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__: Tuple[str, ...] = ("RequireBlock", "BlacklistBlock")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RequireBlock(verb_required_block(True, parameter=True)): # type: ignore
|
|
13
|
+
"""
|
|
14
|
+
The require block will attempt to convert the given parameter into a channel
|
|
15
|
+
or role, using name or ID. If the user running the tag is not in the targeted
|
|
16
|
+
channel or doesn't have the targeted role, the tag will stop processing and
|
|
17
|
+
it will send the response if one is given. Multiple role or channel
|
|
18
|
+
requirements can be given, and should be split by a ",".
|
|
19
|
+
|
|
20
|
+
**Usage:** ``{require(<role,channel>):[response]}``
|
|
21
|
+
|
|
22
|
+
**Aliases:** ``whitelist``
|
|
23
|
+
|
|
24
|
+
**Payload:** response, None
|
|
25
|
+
|
|
26
|
+
**Parameter:** role, channel
|
|
27
|
+
|
|
28
|
+
**Examples:** ::
|
|
29
|
+
|
|
30
|
+
{require(Moderator)}
|
|
31
|
+
{require(#general, #bot-cmds):This tag can only be run in #general and #bot-cmds.}
|
|
32
|
+
{require(757425366209134764, 668713062186090506, 737961895356792882):You aren't allowed to use this tag.}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("require", "whitelist")
|
|
36
|
+
|
|
37
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
38
|
+
actions = ctx.response.actions.get("requires")
|
|
39
|
+
if actions:
|
|
40
|
+
return None
|
|
41
|
+
ctx.response.actions["requires"] = {
|
|
42
|
+
"items": [i.strip() for i in cast(str, ctx.verb.parameter).split(",")],
|
|
43
|
+
"response": ctx.verb.payload,
|
|
44
|
+
}
|
|
45
|
+
return ""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BlacklistBlock(verb_required_block(True, parameter=True)): # type: ignore
|
|
49
|
+
"""
|
|
50
|
+
The blacklist block will attempt to convert the given parameter into a channel
|
|
51
|
+
or role, using name or ID. If the user running the tag is in the targeted
|
|
52
|
+
channel or has the targeted role, the tag will stop processing and
|
|
53
|
+
it will send the response if one is given. Multiple role or channel
|
|
54
|
+
requirements can be given, and should be split by a ",".
|
|
55
|
+
|
|
56
|
+
**Usage:** ``{blacklist(<role,channel>):[response]}``
|
|
57
|
+
|
|
58
|
+
**Payload:** response, None
|
|
59
|
+
|
|
60
|
+
**Parameter:** role, channel
|
|
61
|
+
|
|
62
|
+
**Examples:** ::
|
|
63
|
+
|
|
64
|
+
{blacklist(Muted)}
|
|
65
|
+
{blacklist(#support):This tag is not allowed in #support.}
|
|
66
|
+
{blacklist(Tag Blacklist, 668713062186090506):You are blacklisted from using tags.}
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("blacklist",)
|
|
70
|
+
|
|
71
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
72
|
+
actions = ctx.response.actions.get("blacklist")
|
|
73
|
+
if actions:
|
|
74
|
+
return None
|
|
75
|
+
ctx.response.actions["blacklist"] = {
|
|
76
|
+
"items": [i.strip() for i in cast(str, ctx.verb.parameter).split(",")],
|
|
77
|
+
"response": ctx.verb.payload,
|
|
78
|
+
}
|
|
79
|
+
return ""
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Optional, Tuple, cast
|
|
2
|
+
|
|
3
|
+
from ..interface import Block
|
|
4
|
+
from ..interpreter import Context
|
|
5
|
+
from ..verb import Verb
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__: Tuple[str, ...] = ("ShortCutRedirectBlock",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ShortCutRedirectBlock(Block):
|
|
12
|
+
def __init__(self, var_name: str) -> None:
|
|
13
|
+
self.redirect_name: str = var_name
|
|
14
|
+
|
|
15
|
+
def will_accept(self, ctx: Context) -> bool: # type: ignore
|
|
16
|
+
return cast(str, ctx.verb.declaration).isdigit()
|
|
17
|
+
|
|
18
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
19
|
+
blank: Verb = Verb()
|
|
20
|
+
blank.declaration = self.redirect_name
|
|
21
|
+
blank.parameter = ctx.verb.declaration
|
|
22
|
+
ctx.verb = blank
|
|
23
|
+
return None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple, cast
|
|
4
|
+
|
|
5
|
+
from ..exceptions import StopError
|
|
6
|
+
from ..interface import verb_required_block
|
|
7
|
+
from ..interpreter import Context
|
|
8
|
+
from . import helper_parse_if
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__: Tuple[str, ...] = ("StopBlock",)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class StopBlock(verb_required_block(True, parameter=True)): # type: ignore
|
|
15
|
+
"""
|
|
16
|
+
The stop block stops tag processing if the given parameter is true.
|
|
17
|
+
If a message is passed to the payload it will return that message.
|
|
18
|
+
|
|
19
|
+
**Usage:** ``{stop(<bool>):[string]}``
|
|
20
|
+
|
|
21
|
+
**Aliases:** ``halt, error``
|
|
22
|
+
|
|
23
|
+
**Payload:** string, None
|
|
24
|
+
|
|
25
|
+
**Parameter:** bool
|
|
26
|
+
|
|
27
|
+
**Example:** ::
|
|
28
|
+
|
|
29
|
+
{stop({args}==):You must provide arguments for this tag.}
|
|
30
|
+
# enforces providing arguments for a tag
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("stop", "halt", "error")
|
|
34
|
+
|
|
35
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
36
|
+
if helper_parse_if(cast(str, ctx.verb.parameter)):
|
|
37
|
+
raise StopError("" if ctx.verb.payload is None else ctx.verb.payload)
|
|
38
|
+
return ""
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Optional, Tuple, cast
|
|
5
|
+
|
|
6
|
+
from ..interface import Block
|
|
7
|
+
from ..interpreter import Context
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__: Tuple[str, ...] = ("StrfBlock",)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StrfBlock(Block):
|
|
14
|
+
"""
|
|
15
|
+
The strf block converts and formats timestamps based on `strftime formatting spec <https://strftime.org/>`_.
|
|
16
|
+
Two types of timestamps are supported: ISO and epoch.
|
|
17
|
+
If a timestamp isn't passed, the current UTC time is used.
|
|
18
|
+
|
|
19
|
+
Invoking this block with `unix` will return the current Unix timestamp.
|
|
20
|
+
|
|
21
|
+
**Usage:** ``{strf([timestamp]):<format>}``
|
|
22
|
+
|
|
23
|
+
**Aliases:** ``unix``
|
|
24
|
+
|
|
25
|
+
**Payload:** format, None
|
|
26
|
+
|
|
27
|
+
**Parameter:** timestamp
|
|
28
|
+
|
|
29
|
+
**Example:** ::
|
|
30
|
+
|
|
31
|
+
{strf:%Y-%m-%d}
|
|
32
|
+
# 2021-07-11
|
|
33
|
+
|
|
34
|
+
{strf({user(timestamp)}):%c}
|
|
35
|
+
# Fri Jun 29 21:10:28 2018
|
|
36
|
+
|
|
37
|
+
{strf(1420070400):%A %d, %B %Y}
|
|
38
|
+
# Thursday 01, January 2015
|
|
39
|
+
|
|
40
|
+
{strf(2019-10-09T01:45:00.805000):%H:%M %d-%B-%Y}
|
|
41
|
+
# 01:45 09-October-2019
|
|
42
|
+
|
|
43
|
+
{unix}
|
|
44
|
+
# 1629182008
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("strf", "unix")
|
|
48
|
+
|
|
49
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
50
|
+
if cast(str, ctx.verb.declaration).lower() == "unix":
|
|
51
|
+
return str(int(datetime.now(timezone.utc).timestamp()))
|
|
52
|
+
if not ctx.verb.payload:
|
|
53
|
+
return None
|
|
54
|
+
if ctx.verb.parameter:
|
|
55
|
+
if ctx.verb.parameter.isdigit():
|
|
56
|
+
try:
|
|
57
|
+
t = datetime.fromtimestamp(int(ctx.verb.parameter))
|
|
58
|
+
except Exception:
|
|
59
|
+
return
|
|
60
|
+
else:
|
|
61
|
+
try:
|
|
62
|
+
t = datetime.fromisoformat(ctx.verb.parameter)
|
|
63
|
+
# converts datetime.__str__ to datetime
|
|
64
|
+
except ValueError:
|
|
65
|
+
return
|
|
66
|
+
else:
|
|
67
|
+
t = datetime.now()
|
|
68
|
+
if not t.tzinfo:
|
|
69
|
+
t = t.replace(tzinfo=timezone.utc)
|
|
70
|
+
return t.strftime(ctx.verb.payload)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple, cast
|
|
4
|
+
|
|
5
|
+
from ..interface import Block
|
|
6
|
+
from ..interpreter import Context
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__: Tuple[str, ...] = ("StrictVariableGetterBlock",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StrictVariableGetterBlock(Block):
|
|
13
|
+
"""
|
|
14
|
+
The strict variable block represents the adapters for any seeded or defined variables.
|
|
15
|
+
This variable implementation is considered "strict" since it checks whether the variable is
|
|
16
|
+
valid during :meth:`will_accept` and is only processed if the declaration refers to a valid
|
|
17
|
+
variable.
|
|
18
|
+
|
|
19
|
+
**Usage:** ``{<variable_name>([parameter]):[payload]}``
|
|
20
|
+
|
|
21
|
+
**Aliases:** This block is valid for any variable name in `Response.variables`.
|
|
22
|
+
|
|
23
|
+
**Payload:** Depends on the variable's underlying adapter.
|
|
24
|
+
|
|
25
|
+
**Parameter:** Depends on the variable's underlying adapter.
|
|
26
|
+
|
|
27
|
+
**Examples:** ::
|
|
28
|
+
|
|
29
|
+
{=(var):This is my variable.}
|
|
30
|
+
{var}
|
|
31
|
+
# This is my variable.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def will_accept(self, ctx: Context) -> bool: # type: ignore
|
|
35
|
+
return ctx.verb.declaration in ctx.response.variables
|
|
36
|
+
|
|
37
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
38
|
+
return ctx.response.variables[cast(str, ctx.verb.declaration)].get_value(ctx.verb)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple, cast
|
|
4
|
+
|
|
5
|
+
from ..interface import verb_required_block
|
|
6
|
+
from ..interpreter import Context
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__: Tuple[str, ...] = ("SubstringBlock",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SubstringBlock(verb_required_block(True, parameter=True)): # type: ignore
|
|
13
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("substr", "substring")
|
|
14
|
+
|
|
15
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
16
|
+
try:
|
|
17
|
+
if "-" not in cast(str, ctx.verb.parameter):
|
|
18
|
+
return cast(str, ctx.verb.payload)[int(float(cast(str, ctx.verb.parameter))) :]
|
|
19
|
+
|
|
20
|
+
spl = cast(str, ctx.verb.parameter).split("-")
|
|
21
|
+
start = int(float(spl[0]))
|
|
22
|
+
end = int(float(spl[1]))
|
|
23
|
+
return cast(str, ctx.verb.payload)[start:end]
|
|
24
|
+
except Exception:
|
|
25
|
+
return
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Tuple, cast
|
|
2
|
+
from urllib.parse import quote, quote_plus
|
|
3
|
+
|
|
4
|
+
from ..interface import verb_required_block
|
|
5
|
+
from ..interpreter import Context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__: Tuple[str, ...] = ("URLEncodeBlock",)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class URLEncodeBlock(verb_required_block(True, payload=True)): # type: ignore
|
|
12
|
+
"""
|
|
13
|
+
This block will encode a given string into a properly formatted url
|
|
14
|
+
with non-url compliant characters replaced. Using ``+`` as the parameter
|
|
15
|
+
will replace spaces with ``+`` rather than ``%20``.
|
|
16
|
+
|
|
17
|
+
**Usage:** ``{urlencode(["+"]):<string>}``
|
|
18
|
+
|
|
19
|
+
**Payload:** string
|
|
20
|
+
|
|
21
|
+
**Parameter:** "+", None
|
|
22
|
+
|
|
23
|
+
**Examples:** ::
|
|
24
|
+
|
|
25
|
+
{urlencode:covid-19 sucks}
|
|
26
|
+
# covid-19%20sucks
|
|
27
|
+
|
|
28
|
+
{urlencode(+):im stuck at home writing docs}
|
|
29
|
+
# im+stuck+at+home+writing+docs
|
|
30
|
+
|
|
31
|
+
# the following tagscript can be used to search up tag blocks
|
|
32
|
+
# assume {args} = "command block"
|
|
33
|
+
# <https://seina-cogs.readthedocs.io/en/latest/search.html?q={urlencode(+):{args}}&check_keywords=yes&area=default>
|
|
34
|
+
# <https://seina-cogs.readthedocs.io/en/latest/search.html?q=command+block&check_keywords=yes&area=default>
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("urlencode",)
|
|
38
|
+
|
|
39
|
+
def process(self, ctx: Context) -> str:
|
|
40
|
+
method = quote_plus if ctx.verb.parameter == "+" else quote
|
|
41
|
+
return method(cast(str, ctx.verb.payload))
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Tuple
|
|
4
|
+
|
|
5
|
+
from discord.ext.commands import Cooldown
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .interpreter import Interpreter, Response
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__: Tuple[str, ...] = (
|
|
12
|
+
"TagScriptError",
|
|
13
|
+
"WorkloadExceededError",
|
|
14
|
+
"ProcessError",
|
|
15
|
+
"EmbedParseError",
|
|
16
|
+
"BadColourArgument",
|
|
17
|
+
"StopError",
|
|
18
|
+
"CooldownExceeded",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TagScriptError(Exception):
|
|
23
|
+
"""Base class for all module errors."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WorkloadExceededError(TagScriptError):
|
|
27
|
+
"""Raised when the interpreter goes over its passed character limit."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProcessError(TagScriptError):
|
|
31
|
+
"""
|
|
32
|
+
Raised when an exception occurs during interpreter processing.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
original: Exception
|
|
37
|
+
The original exception that occurred during processing.
|
|
38
|
+
response: Response
|
|
39
|
+
The incomplete response that was being processed when the exception occurred.
|
|
40
|
+
interpreter: Interpreter
|
|
41
|
+
The interpreter used for processing.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, error: Exception, response: Response, interpreter: Interpreter) -> None:
|
|
45
|
+
self.original: Exception = error
|
|
46
|
+
self.response: Response = response
|
|
47
|
+
self.interpreter: Interpreter = interpreter
|
|
48
|
+
super().__init__(error)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class EmbedParseError(TagScriptError):
|
|
52
|
+
"""Raised if an exception occurs while attempting to parse an embed."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BadColourArgument(EmbedParseError):
|
|
56
|
+
"""
|
|
57
|
+
Raised when the passed input fails to convert to `discord.Colour`.
|
|
58
|
+
|
|
59
|
+
Attributes
|
|
60
|
+
----------
|
|
61
|
+
argument: str
|
|
62
|
+
The invalid input.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, argument: str) -> None:
|
|
66
|
+
self.argument: str = argument
|
|
67
|
+
super().__init__(f'Colour "{argument}" is invalid.')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class StopError(TagScriptError):
|
|
71
|
+
"""
|
|
72
|
+
Raised by the StopBlock to stop processing.
|
|
73
|
+
|
|
74
|
+
Attributes
|
|
75
|
+
----------
|
|
76
|
+
message: str
|
|
77
|
+
The stop error message.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, message: str) -> None:
|
|
81
|
+
self.message: str = message
|
|
82
|
+
super().__init__(message)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class CooldownExceeded(StopError):
|
|
86
|
+
"""
|
|
87
|
+
Raised by the cooldown block when a cooldown is exceeded.
|
|
88
|
+
|
|
89
|
+
Attributes
|
|
90
|
+
----------
|
|
91
|
+
message: str
|
|
92
|
+
The cooldown error message.
|
|
93
|
+
cooldown: discord.ext.commands.Cooldown
|
|
94
|
+
The cooldown bucket with information on the cooldown.
|
|
95
|
+
key: str
|
|
96
|
+
The cooldown key that reached its cooldown.
|
|
97
|
+
retry_after: float
|
|
98
|
+
The seconds left til the cooldown ends.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, message: str, cooldown: Cooldown, key: str, retry_after: float) -> None:
|
|
102
|
+
self.cooldown: Cooldown = cooldown
|
|
103
|
+
self.key: str = key
|
|
104
|
+
self.retry_after: float = retry_after
|
|
105
|
+
super().__init__(message)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
|
|
5
|
+
from .adapter import (
|
|
6
|
+
Adapter as Adapter,
|
|
7
|
+
SimpleAdapter as SimpleAdapter,
|
|
8
|
+
)
|
|
9
|
+
from .block import (
|
|
10
|
+
Block as Block,
|
|
11
|
+
verb_required_block as verb_required_block,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__: Tuple[str, ...] = ("Adapter", "SimpleAdapter", "Block", "verb_required_block")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Protocol, Tuple, TypeVar
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..verb import Verb
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_T = TypeVar("_T", bound=object)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__: Tuple[str, ...] = ("Adapter", "SimpleAdapter")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _Adapter(Protocol):
|
|
16
|
+
def __repr__(self) -> str: ...
|
|
17
|
+
|
|
18
|
+
def get_value(self, ctx: Verb) -> Optional[str]: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Adapter(_Adapter):
|
|
22
|
+
"""
|
|
23
|
+
The base class for TagScript blocks.
|
|
24
|
+
|
|
25
|
+
Implementations must subclass this to create adapters.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __repr__(self) -> str:
|
|
29
|
+
return f"<{type(self).__qualname__} at {hex(id(self))}>"
|
|
30
|
+
|
|
31
|
+
def get_value(self, ctx: Verb) -> Optional[str]:
|
|
32
|
+
"""
|
|
33
|
+
Processes the adapter's actions for a given :class:`~TagScriptEngine.interpreter.Context`.
|
|
34
|
+
|
|
35
|
+
Subclasses must implement this.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
ctx: Verb
|
|
40
|
+
The context object containing the TagScript :class:`~TagScriptEngine.verb.Verb`.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
Optional[str]
|
|
45
|
+
The adapters's processed value.
|
|
46
|
+
|
|
47
|
+
Raises
|
|
48
|
+
------
|
|
49
|
+
NotImplementedError
|
|
50
|
+
The subclass did not implement this required method.
|
|
51
|
+
"""
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SimpleAdapter(Adapter, Generic[_T]):
|
|
56
|
+
__slots__: Tuple[str, ...] = ("object", "attributes", "_methods")
|
|
57
|
+
|
|
58
|
+
def __init__(self, *, base: _T) -> None:
|
|
59
|
+
self.object: _T = base
|
|
60
|
+
self._attributes: Dict[str, Any] = {}
|
|
61
|
+
self._methods: Dict[str, Any] = {}
|
|
62
|
+
self.update_attributes()
|
|
63
|
+
self.update_methods()
|
|
64
|
+
|
|
65
|
+
def __repr__(self) -> str:
|
|
66
|
+
return f"<{type(self).__qualname__} object={self.object!r}>"
|
|
67
|
+
|
|
68
|
+
def update_attributes(self) -> None:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def update_methods(self) -> None:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def get_value(self, ctx: Verb) -> Optional[str]:
|
|
75
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, Protocol, Tuple, Type, cast
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..interpreter import Context
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__: Tuple[str, ...] = ("Block", "verb_required_block")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Block(Protocol):
|
|
14
|
+
ACCEPTED_NAMES: Tuple[str, ...]
|
|
15
|
+
|
|
16
|
+
def __repr__(self) -> str: ...
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def will_accept(cls, ctx: Context) -> bool: ...
|
|
20
|
+
|
|
21
|
+
def pre_process(self, ctx: Context) -> Any: ...
|
|
22
|
+
|
|
23
|
+
def process(self, ctx: Context) -> Optional[str]: ...
|
|
24
|
+
|
|
25
|
+
def post_process(self, ctx: "Context") -> Any: ...
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Block(_Block):
|
|
29
|
+
"""
|
|
30
|
+
The base class for TagScript blocks.
|
|
31
|
+
|
|
32
|
+
Implementations must subclass this to create new blocks.
|
|
33
|
+
|
|
34
|
+
Attributes
|
|
35
|
+
----------
|
|
36
|
+
ACCEPTED_NAMES: Tuple[str, ...]
|
|
37
|
+
The accepted names for this block. This ideally should be set as a class attribute.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ()
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return f"<{type(self).__qualname__} at {hex(id(self))}>"
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def will_accept(cls, ctx: Context) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
Describes whether the block is valid for the given :class:`~TagScriptEngine.interpreter.Context`.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
ctx: Context
|
|
53
|
+
The context object containing the TagScript :class:`~TagScriptEngine.verb.Verb`.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
bool
|
|
58
|
+
Whether the block should be processed for this :class:`~TagScriptEngine.interpreter.Context`.
|
|
59
|
+
"""
|
|
60
|
+
dec: str = cast(str, ctx.verb.declaration).lower()
|
|
61
|
+
return dec in cls.ACCEPTED_NAMES
|
|
62
|
+
|
|
63
|
+
def pre_process(self, ctx: Context) -> Any:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
67
|
+
"""
|
|
68
|
+
Processes the block's actions for a given :class:`~TagScriptEngine.interpreter.Context`.
|
|
69
|
+
|
|
70
|
+
Subclasses must implement this.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
ctx: Context
|
|
75
|
+
The context object containing the TagScript :class:`~TagScriptEngine.verb.Verb`.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
Optional[str]
|
|
80
|
+
The block's processed value.
|
|
81
|
+
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
NotImplementedError
|
|
85
|
+
The subclass did not implement this required method.
|
|
86
|
+
"""
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
def post_process(self, ctx: "Context") -> Any:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@lru_cache(maxsize=None)
|
|
94
|
+
def verb_required_block(
|
|
95
|
+
implicit: bool,
|
|
96
|
+
*,
|
|
97
|
+
parameter: bool = False,
|
|
98
|
+
payload: bool = False,
|
|
99
|
+
) -> Type[Block]:
|
|
100
|
+
"""
|
|
101
|
+
Get a Block subclass that requires a verb to implicitly or explicitly have a parameter or payload passed.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
implicit: bool
|
|
106
|
+
Specifies whether the value is required to be passed implicitly or explicitly.
|
|
107
|
+
``{block()}`` would be allowed if implicit is False.
|
|
108
|
+
parameter: bool
|
|
109
|
+
Passing True will cause the block to require a parameter to be passed.
|
|
110
|
+
payload: bool
|
|
111
|
+
Passing True will cause the block to require the payload to be passed.
|
|
112
|
+
"""
|
|
113
|
+
check = (lambda x: x) if implicit else (lambda x: x is not None)
|
|
114
|
+
|
|
115
|
+
class VerbRequiredBlock(Block):
|
|
116
|
+
@classmethod
|
|
117
|
+
def will_accept(cls, ctx: Context) -> bool:
|
|
118
|
+
verb = ctx.verb
|
|
119
|
+
if payload and not check(verb.payload):
|
|
120
|
+
return False
|
|
121
|
+
if parameter and not check(verb.parameter):
|
|
122
|
+
return False
|
|
123
|
+
return super().will_accept(ctx)
|
|
124
|
+
return VerbRequiredBlock
|