AdvancedTagScript 3.2.5__py3-none-any.whl → 3.3.1__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 +7 -3
- TagScriptEngine/adapter/redbotadapters.py +11 -9
- TagScriptEngine/block/__init__.py +22 -12
- TagScriptEngine/block/assign.py +176 -2
- TagScriptEngine/block/case.py +2 -0
- TagScriptEngine/block/command.py +1 -1
- TagScriptEngine/block/cooldown.py +3 -0
- TagScriptEngine/block/cycleblock.py +74 -0
- TagScriptEngine/block/embedblock.py +58 -27
- TagScriptEngine/block/helpers.py +35 -56
- TagScriptEngine/block/joinblock.py +2 -2
- TagScriptEngine/block/listblock.py +66 -0
- TagScriptEngine/block/mathblock.py +77 -12
- TagScriptEngine/block/ordblock.py +62 -0
- TagScriptEngine/block/strf.py +1 -1
- TagScriptEngine/block/substr.py +55 -0
- TagScriptEngine/interpreter.py +5 -5
- {advancedtagscript-3.2.5.dist-info → advancedtagscript-3.3.1.dist-info}/METADATA +1 -1
- {advancedtagscript-3.2.5.dist-info → advancedtagscript-3.3.1.dist-info}/RECORD +22 -19
- {advancedtagscript-3.2.5.dist-info → advancedtagscript-3.3.1.dist-info}/WHEEL +0 -0
- {advancedtagscript-3.2.5.dist-info → advancedtagscript-3.3.1.dist-info}/licenses/LICENSE +0 -0
- {advancedtagscript-3.2.5.dist-info → advancedtagscript-3.3.1.dist-info}/top_level.txt +0 -0
TagScriptEngine/__init__.py
CHANGED
|
@@ -24,7 +24,6 @@ from .block import (
|
|
|
24
24
|
helper_parse_if as helper_parse_if,
|
|
25
25
|
helper_parse_list_if as helper_parse_list_if,
|
|
26
26
|
helper_split as helper_split,
|
|
27
|
-
easier_helper_split as easier_helper_split,
|
|
28
27
|
AllowedMentionsBlock as AllowedMentionsBlock,
|
|
29
28
|
AllBlock as AllBlock,
|
|
30
29
|
AnyBlock as AnyBlock,
|
|
@@ -57,6 +56,9 @@ from .block import (
|
|
|
57
56
|
LengthBlock as LengthBlock,
|
|
58
57
|
CooldownBlock as CooldownBlock,
|
|
59
58
|
JoinBlock as JoinBlock,
|
|
59
|
+
ListBlock as ListBlock,
|
|
60
|
+
CycleBlock as CycleBlock,
|
|
61
|
+
OrdinalBlock as OrdinalBlock,
|
|
60
62
|
|
|
61
63
|
)
|
|
62
64
|
from .interface import (
|
|
@@ -100,7 +102,6 @@ __all__: Tuple[str, ...] = (
|
|
|
100
102
|
"helper_parse_if",
|
|
101
103
|
"helper_parse_list_if",
|
|
102
104
|
"helper_split",
|
|
103
|
-
"easier_helper_split",
|
|
104
105
|
"AllowedMentionsBlock",
|
|
105
106
|
"AllBlock",
|
|
106
107
|
"AnyBlock",
|
|
@@ -133,6 +134,9 @@ __all__: Tuple[str, ...] = (
|
|
|
133
134
|
"CountBlock",
|
|
134
135
|
"LengthBlock",
|
|
135
136
|
"JoinBlock",
|
|
137
|
+
"ListBlock",
|
|
138
|
+
"CycleBlock",
|
|
139
|
+
"OrdinalBlock",
|
|
136
140
|
|
|
137
141
|
"SafeObjectAdapter",
|
|
138
142
|
"StringAdapter",
|
|
@@ -177,7 +181,7 @@ __all__: Tuple[str, ...] = (
|
|
|
177
181
|
)
|
|
178
182
|
|
|
179
183
|
|
|
180
|
-
__version__: Final[str] = "3.
|
|
184
|
+
__version__: Final[str] = "3.3.1"
|
|
181
185
|
|
|
182
186
|
|
|
183
187
|
class VersionNamedTuple(NamedTuple):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
from typing import Any, Dict, Optional, Tuple, cast
|
|
3
5
|
|
|
@@ -23,12 +25,13 @@ __all__: Tuple[str, ...] = ("RedCommandAdapter", "RedBotAdapter")
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
class RedCommandAdapter(SimpleAdapter["Command"]):
|
|
26
|
-
if not _has_redbot:
|
|
27
|
-
raise ImportError("A Red-DiscordBot instance is required to use this.", name="redbot")
|
|
28
|
-
|
|
29
28
|
def __init__(self, base: Command, *, signature: Optional[str] = None) -> None:
|
|
30
|
-
|
|
29
|
+
if not _has_redbot:
|
|
30
|
+
raise ImportError(
|
|
31
|
+
"A Red-DiscordBot instance is required to use this.", name="redbot"
|
|
32
|
+
)
|
|
31
33
|
self.signature: Optional[str] = signature
|
|
34
|
+
super().__init__(base=base)
|
|
32
35
|
|
|
33
36
|
def update_attributes(self) -> None:
|
|
34
37
|
command: Command = self.object
|
|
@@ -103,15 +106,14 @@ class RedBotAdapter(SimpleAdapter["Red"]):
|
|
|
103
106
|
Percentage of chunked guilds the bot has.
|
|
104
107
|
|
|
105
108
|
.. warning::
|
|
106
|
-
Attributes denoting
|
|
109
|
+
Attributes denoting ``(*)`` can only be used by the bot owner.
|
|
107
110
|
"""
|
|
108
111
|
|
|
109
|
-
if not _has_redbot:
|
|
110
|
-
raise ImportError("A Red-DiscordBot instance is required to use this.")
|
|
111
|
-
|
|
112
112
|
def __init__(self, base: Red, *, owner: bool = True) -> None:
|
|
113
|
-
|
|
113
|
+
if not _has_redbot:
|
|
114
|
+
raise ImportError("A Red-DiscordBot instance is required to use this.")
|
|
114
115
|
self.is_owner: bool = owner
|
|
116
|
+
super().__init__(base=base)
|
|
115
117
|
|
|
116
118
|
def update_attributes(self) -> None:
|
|
117
119
|
self.user: discord.ClientUser = cast(discord.ClientUser, self.object.user)
|
|
@@ -3,13 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import Tuple
|
|
4
4
|
|
|
5
5
|
# isort: off
|
|
6
|
-
from .helpers import (
|
|
7
|
-
implicit_bool as implicit_bool,
|
|
8
|
-
helper_parse_if as helper_parse_if,
|
|
9
|
-
helper_parse_list_if as helper_parse_list_if,
|
|
10
|
-
helper_split as helper_split,
|
|
11
|
-
|
|
12
|
-
)
|
|
6
|
+
from .helpers import (
|
|
7
|
+
implicit_bool as implicit_bool,
|
|
8
|
+
helper_parse_if as helper_parse_if,
|
|
9
|
+
helper_parse_list_if as helper_parse_list_if,
|
|
10
|
+
helper_split as helper_split,
|
|
11
|
+
)
|
|
13
12
|
|
|
14
13
|
# isort: on
|
|
15
14
|
from .allowedmentions import (
|
|
@@ -92,14 +91,22 @@ from .count import (
|
|
|
92
91
|
from .joinblock import (
|
|
93
92
|
JoinBlock as JoinBlock,
|
|
94
93
|
)
|
|
94
|
+
from .listblock import (
|
|
95
|
+
ListBlock as ListBlock,
|
|
96
|
+
)
|
|
97
|
+
from .cycleblock import (
|
|
98
|
+
CycleBlock as CycleBlock,
|
|
99
|
+
)
|
|
100
|
+
from .ordblock import (
|
|
101
|
+
OrdinalBlock as OrdinalBlock,
|
|
102
|
+
)
|
|
95
103
|
|
|
96
104
|
__all__: Tuple[str, ...] = (
|
|
97
105
|
"implicit_bool",
|
|
98
|
-
"helper_parse_if",
|
|
99
|
-
"helper_parse_list_if",
|
|
100
|
-
"helper_split",
|
|
101
|
-
"
|
|
102
|
-
"AllowedMentionsBlock",
|
|
106
|
+
"helper_parse_if",
|
|
107
|
+
"helper_parse_list_if",
|
|
108
|
+
"helper_split",
|
|
109
|
+
"AllowedMentionsBlock",
|
|
103
110
|
"AllBlock",
|
|
104
111
|
"AnyBlock",
|
|
105
112
|
"AssignmentBlock",
|
|
@@ -131,4 +138,7 @@ __all__: Tuple[str, ...] = (
|
|
|
131
138
|
"CountBlock",
|
|
132
139
|
"LengthBlock",
|
|
133
140
|
"JoinBlock",
|
|
141
|
+
"ListBlock",
|
|
142
|
+
"CycleBlock",
|
|
143
|
+
"OrdinalBlock",
|
|
134
144
|
)
|
TagScriptEngine/block/assign.py
CHANGED
|
@@ -12,9 +12,15 @@ __all__: Tuple[str, ...] = ("AssignmentBlock",)
|
|
|
12
12
|
|
|
13
13
|
class AssignmentBlock(verb_required_block(False, parameter=True)): # type: ignore
|
|
14
14
|
"""
|
|
15
|
-
Variables are useful for
|
|
15
|
+
Variables are useful for storing a value and referencing it later in a tag.
|
|
16
16
|
Variables can be referenced using brackets as any other block.
|
|
17
17
|
|
|
18
|
+
.. note::
|
|
19
|
+
- Variables are not parsed by default. You must use the ``parse`` method to parse a variable.
|
|
20
|
+
- They are one of the most versatile and powerful features of TagScript.
|
|
21
|
+
- By default, a tag cannot store data between invocations. This is where ``variables`` come in.
|
|
22
|
+
- They are the only way to **store** data. And later **retrieve** it within the same invocation.
|
|
23
|
+
|
|
18
24
|
**Usage:** ``{=(<name>):<value>}``
|
|
19
25
|
|
|
20
26
|
**Aliases:** ``assign, let, var``
|
|
@@ -23,8 +29,20 @@ class AssignmentBlock(verb_required_block(False, parameter=True)): # type: igno
|
|
|
23
29
|
|
|
24
30
|
**Parameter:** name
|
|
25
31
|
|
|
26
|
-
**Examples:**
|
|
32
|
+
**Examples:**
|
|
33
|
+
|
|
34
|
+
.. code-block:: yaml
|
|
35
|
+
|
|
36
|
+
{=(message1):Hi there! How are you?}
|
|
37
|
+
{=(message2):It's a beautiful day today!}
|
|
38
|
+
{=(message3):Did you know that TagScript is a powerful tool?}
|
|
27
39
|
|
|
40
|
+
Now, call the variables by their names:
|
|
41
|
+
{message1} # Hi there! How are you?
|
|
42
|
+
{message2} # It's a beautiful day today!
|
|
43
|
+
{message3} # Did you know that TagScript is a powerful tool?
|
|
44
|
+
|
|
45
|
+
More example:
|
|
28
46
|
{=(prefix):!}
|
|
29
47
|
The prefix here is `{prefix}`.
|
|
30
48
|
# The prefix here is `!`.
|
|
@@ -32,6 +50,162 @@ class AssignmentBlock(verb_required_block(False, parameter=True)): # type: igno
|
|
|
32
50
|
{assign(day):Monday}
|
|
33
51
|
{if({day}==Wednesday):It's Wednesday my dudes!|The day is {day}.}
|
|
34
52
|
# The day is Monday.
|
|
53
|
+
|
|
54
|
+
.. caution::
|
|
55
|
+
- You can name variables with **anything** ``except`` existing block names or aliases.
|
|
56
|
+
- They will ``not`` reference the value in payload, if the name is same as an existing block name or alias.
|
|
57
|
+
|
|
58
|
+
----
|
|
59
|
+
|
|
60
|
+
.. important:: How Argument Parsing Works - In Detail
|
|
61
|
+
|
|
62
|
+
- A variable is essentially a string that can be treated as a sequence of elements (words, numbers, etc.) when accessed.
|
|
63
|
+
These **elements** are split using ``delimiters`` (spaces by default) and are indexed sequentially starting from ``1``.
|
|
64
|
+
- A delimiter is a sequence of one or more characters that are used to split a string into a sequence of elements.
|
|
65
|
+
- Once a variable is assigned, its value can be referenced and ``parsed`` (split and indexed)
|
|
66
|
+
to extract ``specific`` parts. Let's take a look at **how** it works.
|
|
67
|
+
- Parsing out of bounds index will return the whole string.
|
|
68
|
+
|
|
69
|
+
----
|
|
70
|
+
|
|
71
|
+
.. rubric:: **Basic Argument Parsing**
|
|
72
|
+
|
|
73
|
+
Example
|
|
74
|
+
- "Coolaid is setting up the table. So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves"
|
|
75
|
+
|
|
76
|
+
- So, our ``argument`` or ``args`` in short, would be:
|
|
77
|
+
|
|
78
|
+
.. code-block:: yaml
|
|
79
|
+
|
|
80
|
+
{=(args):Coolaid is setting up the table. So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves}
|
|
81
|
+
|
|
82
|
+
.. note:: ``args`` is just a variable name, perhaps the most common name, but you can name it anything.
|
|
83
|
+
|
|
84
|
+
- Since, the default delimiter is ``space``, so you can access the ``each element`` as follows:
|
|
85
|
+
|
|
86
|
+
.. code-block:: yaml
|
|
87
|
+
|
|
88
|
+
{args(1)} -> Coolaid
|
|
89
|
+
{args(2)} -> is
|
|
90
|
+
{args(3)} -> setting
|
|
91
|
+
{args(4)} -> up
|
|
92
|
+
{args(5)} -> the
|
|
93
|
+
{args(6)} -> table.
|
|
94
|
+
|
|
95
|
+
{args(31)} -> Would return the whole string since it doesn't exist (indexing 31st element is out of bounds).
|
|
96
|
+
|
|
97
|
+
- ``0`` is special and returns the ``last`` element:
|
|
98
|
+
|
|
99
|
+
.. code-block:: yaml
|
|
100
|
+
|
|
101
|
+
{args(0)} -> gloves
|
|
102
|
+
|
|
103
|
+
- Negative indices allow you to access elements from the end of the sequence:
|
|
104
|
+
|
|
105
|
+
.. code-block:: yaml
|
|
106
|
+
|
|
107
|
+
{args(-1)} -> work
|
|
108
|
+
{args(-2)} -> of
|
|
109
|
+
{args(-3)} -> pair
|
|
110
|
+
{args(-4)} -> a
|
|
111
|
+
{args(-5)} -> and
|
|
112
|
+
{args(-6)} -> level,
|
|
113
|
+
|
|
114
|
+
.. rubric:: Prefix Range Access (``+n``)
|
|
115
|
+
|
|
116
|
+
- Prefixing an index with ``+`` returns all elements from the start up to and including that position:
|
|
117
|
+
|
|
118
|
+
.. code-block:: yaml
|
|
119
|
+
|
|
120
|
+
{args(+3)} -> Coolaid is setting
|
|
121
|
+
{args(+7)} -> Coolaid is setting up the table. So,
|
|
122
|
+
{args(+13)} -> Coolaid is setting up the table. So, he grabbed - a cordless drill,
|
|
123
|
+
|
|
124
|
+
.. rubric:: Suffix Range Access (``n+``)
|
|
125
|
+
|
|
126
|
+
- Suffixing an index with ``+`` returns all elements from that position (counting from the start) to the end:
|
|
127
|
+
|
|
128
|
+
.. code-block:: yaml
|
|
129
|
+
|
|
130
|
+
{args(3+)} -> setting up the table. So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves
|
|
131
|
+
{args(7+)} -> So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves
|
|
132
|
+
{args(13+)} -> drill, some screws, a spirit level, and a pair of work gloves
|
|
133
|
+
|
|
134
|
+
.. rubric:: Negative Range Access (``-n+``)
|
|
135
|
+
|
|
136
|
+
- Appending ``+`` to an negative-index returns a range — all elements from that position to the end:
|
|
137
|
+
- Negative indices are **first** resolved from the end of the sequence,
|
|
138
|
+
then range access continues forward to the end.
|
|
139
|
+
|
|
140
|
+
.. code-block:: yaml
|
|
141
|
+
|
|
142
|
+
{args(-1+)} -> work gloves # Since {args(0)} == gloves
|
|
143
|
+
{args(-11+)} -> drill, some screws, a spirit level, and a pair of work gloves
|
|
144
|
+
|
|
145
|
+
.. tip::
|
|
146
|
+
- ``+n`` → from start → n
|
|
147
|
+
- ``n+`` → from n → end (index resolved first)
|
|
148
|
+
- ``-n`` → nth element from end
|
|
149
|
+
- ``-n+`` → nth element from end → then forward to end (index resolved first)
|
|
150
|
+
|
|
151
|
+
-----
|
|
152
|
+
|
|
153
|
+
.. rubric:: **Advanced Argument Parsing**
|
|
154
|
+
|
|
155
|
+
A **custom delimiter** can be passed as the payload to change how
|
|
156
|
+
the value is split. The syntax is ``{variable(index):delimiter}``:
|
|
157
|
+
|
|
158
|
+
.. code-block:: yaml
|
|
159
|
+
|
|
160
|
+
# Using the same argument as before.
|
|
161
|
+
{=(args):Coolaid is setting up the table. So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves}
|
|
162
|
+
|
|
163
|
+
1st Example:
|
|
164
|
+
{args(1):.} -> Coolaid is setting up the table
|
|
165
|
+
{args(2):.} -> So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves
|
|
166
|
+
{args(3):.} -> Would return the entire string since there is no 3rd element.
|
|
167
|
+
|
|
168
|
+
2nd Example:
|
|
169
|
+
{args(1):-} -> Coolaid is setting up the table. So, he grabbed
|
|
170
|
+
{args(2):-} -> a cordless drill, some screws, a spirit level, and a pair of work gloves
|
|
171
|
+
|
|
172
|
+
.. note::
|
|
173
|
+
- In the 1st example, the custom delimiter is ``.``, hence the string is split by ``.`` leaving 2 elements.
|
|
174
|
+
- In the 2nd example, the custom delimiter is ``-``, hence the string is split by ``-`` leaving 2 elements.
|
|
175
|
+
- Since in both the examples, there are ``2 elements``, so ``args(3)`` would return the entire string.
|
|
176
|
+
Because the index ``3rd`` element is out of bounds.
|
|
177
|
+
|
|
178
|
+
.. rubric:: **Nested Variables**
|
|
179
|
+
|
|
180
|
+
- Variables can be **nested** to perform multi-level parsing:
|
|
181
|
+
|
|
182
|
+
.. code-block:: yaml
|
|
183
|
+
|
|
184
|
+
{=(raw):A - B, C, D}
|
|
185
|
+
{=(part):{raw(2):-}}
|
|
186
|
+
|
|
187
|
+
# "{raw(2):-}" splits "raw" by "-" and returns the 2nd element -> "B, C, D" (1st element is "A")
|
|
188
|
+
# Therefore, "part" == "B, C, D"
|
|
189
|
+
|
|
190
|
+
{part(1):,} -> B
|
|
191
|
+
{part(2):,} -> C
|
|
192
|
+
|
|
193
|
+
Another Example:
|
|
194
|
+
# What if you want to parse through the things that Coolaid grabbed?
|
|
195
|
+
# If you look closely the "-" delimiter is placed conveniently to separate the items. So, we'll use it:
|
|
196
|
+
|
|
197
|
+
{=(args):Coolaid is setting up the table. So, he grabbed - a cordless drill, some screws, a spirit level, and a pair of work gloves}
|
|
198
|
+
{=(items):{args(2):-}}
|
|
199
|
+
|
|
200
|
+
# "{args(2):-}" splits "args" by "-" and returns the 2nd element -> "a cordless drill, ... and a pair of work gloves"
|
|
201
|
+
# Therefore, "items" == "a cordless drill, some screws, a spirit level, and a pair of work gloves"
|
|
202
|
+
|
|
203
|
+
Items:
|
|
204
|
+
{items(1):,} -> a cordless drill
|
|
205
|
+
{items(2):,} -> some screws
|
|
206
|
+
{items(3):,} -> a spirit level
|
|
207
|
+
{items(4):,} -> and a pair of work gloves
|
|
208
|
+
|
|
35
209
|
"""
|
|
36
210
|
|
|
37
211
|
ACCEPTED_NAMES: Tuple[str, ...] = ("=", "assign", "let", "var")
|
TagScriptEngine/block/case.py
CHANGED
|
@@ -25,6 +25,7 @@ class UpperBlock(Block):
|
|
|
25
25
|
The text is {upper(ThIs Is A TeXt)}!
|
|
26
26
|
# The text is THIS IS A TEXT!
|
|
27
27
|
|
|
28
|
+
{=(args):Hello World}
|
|
28
29
|
You have entered {upper({args})}!
|
|
29
30
|
# You have entered HELLO WORLD!
|
|
30
31
|
"""
|
|
@@ -52,6 +53,7 @@ class LowerBlock(Block):
|
|
|
52
53
|
The text is {lower(ThIs Is A TeXt)}!
|
|
53
54
|
# The text is this is a text!
|
|
54
55
|
|
|
56
|
+
{=(args):HELLO WORLD}
|
|
55
57
|
You have entered {lower({args})}!
|
|
56
58
|
# You have entered hello world!
|
|
57
59
|
"""
|
TagScriptEngine/block/command.py
CHANGED
|
@@ -28,6 +28,9 @@ class CooldownBlock(verb_required_block(True, payload=True, parameter=True)): #
|
|
|
28
28
|
cooldown is exceeded. If no message is passed, the default message will be sent instead.
|
|
29
29
|
The cooldown message supports 2 blocks: ``key`` and ``retry_after``.
|
|
30
30
|
|
|
31
|
+
.. note::
|
|
32
|
+
- Delimiter for the parameter (``<rate>`` and ``<per>``): ``|`` or ``~``. Where ``|`` takes priority over ``~``.
|
|
33
|
+
|
|
31
34
|
**Usage:** ``{cooldown(<rate>|<per>):<key>|[message]}``
|
|
32
35
|
|
|
33
36
|
**Payload:** key, message
|
|
@@ -0,0 +1,74 @@
|
|
|
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, ...] = ("CycleBlock",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CycleBlock(verb_required_block(True, payload=True, parameter=True)): # type: ignore
|
|
13
|
+
"""
|
|
14
|
+
The cycle block returns the element in the payload that corresponds to the
|
|
15
|
+
index value in the parameter. If the index is out of bounds, it loops around
|
|
16
|
+
using modulo (``index % list_length``).
|
|
17
|
+
|
|
18
|
+
List and Cycle blocks are another way to parse through a list of values in
|
|
19
|
+
TagScript. They both strictly use either commas ``,`` or tildes ``~`` as the
|
|
20
|
+
delimiters for the list placed in the block's payload. Use tildes when elements
|
|
21
|
+
contain commas. These blocks only function in Tags.
|
|
22
|
+
|
|
23
|
+
Cycles use ``0`` as the index for the first element. Negative values allow
|
|
24
|
+
for backward parsing. The block will return an error message if the value in
|
|
25
|
+
the parameter is not a number.
|
|
26
|
+
|
|
27
|
+
**Usage:** ``{cycle(<index>):<elem>,<elem2>,...}``
|
|
28
|
+
|
|
29
|
+
**Aliases:** ``None``
|
|
30
|
+
|
|
31
|
+
**Payload:** list of elements (comma or tilde separated)
|
|
32
|
+
|
|
33
|
+
**Parameter:** index
|
|
34
|
+
|
|
35
|
+
**Examples:**
|
|
36
|
+
|
|
37
|
+
.. code-block:: yaml
|
|
38
|
+
|
|
39
|
+
{cycle(1):Cake,Candy,Chips,Cookies,Donut}
|
|
40
|
+
# Candy
|
|
41
|
+
|
|
42
|
+
{cycle(13):Cake,Candy,Chips,Cookies,Donut}
|
|
43
|
+
# Cookies
|
|
44
|
+
# (The list has 5 elements. 13 modulo 5 = 3. Index 3 is "Cookies".)
|
|
45
|
+
|
|
46
|
+
{cycle(3):0,1,2}
|
|
47
|
+
# 0
|
|
48
|
+
# (3 modulo 3 = 0. Index 0 is "0".)
|
|
49
|
+
|
|
50
|
+
Negative indices:
|
|
51
|
+
{cycle(-1):Apple,Banana,Cherry}
|
|
52
|
+
# Cherry
|
|
53
|
+
|
|
54
|
+
{cycle(-69):Charlie,Aid,Bob,Dave,Eve,Phen,Steve,Tom,Wendy,Xavier}
|
|
55
|
+
# Aid
|
|
56
|
+
# (-69 modulo 10 = 1. Index 1 is "Aid".)
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("cycle",)
|
|
61
|
+
|
|
62
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
63
|
+
try:
|
|
64
|
+
index = int(cast(str, ctx.verb.parameter))
|
|
65
|
+
except (ValueError, TypeError):
|
|
66
|
+
return "Invalid index: parameter must be a number."
|
|
67
|
+
|
|
68
|
+
payload = cast(str, ctx.verb.payload)
|
|
69
|
+
if "~" in payload:
|
|
70
|
+
items = payload.split("~")
|
|
71
|
+
else:
|
|
72
|
+
items = payload.split(",")
|
|
73
|
+
|
|
74
|
+
return items[index % len(items)]
|
|
@@ -8,10 +8,9 @@ from discord import Colour, Embed
|
|
|
8
8
|
|
|
9
9
|
from ..interface import Block
|
|
10
10
|
from ..interpreter import Context
|
|
11
|
-
from .helpers import helper_split,
|
|
11
|
+
from .helpers import helper_split, implicit_bool
|
|
12
12
|
from ..utils import truncate
|
|
13
13
|
from ..exceptions import BadColourArgument, EmbedParseError
|
|
14
|
-
from .._warnings import removal
|
|
15
14
|
|
|
16
15
|
try:
|
|
17
16
|
import orjson # noqa: F401
|
|
@@ -59,8 +58,10 @@ def set_dynamic_url(embed: Embed, attribute: str, value: str) -> None:
|
|
|
59
58
|
|
|
60
59
|
|
|
61
60
|
def add_field(embed: Embed, _: str, payload: str) -> None:
|
|
62
|
-
if (data :=
|
|
63
|
-
raise EmbedParseError(
|
|
61
|
+
if (data := helper_split(payload, maxsplit=2, double_semicolon=True)) is None: # type: ignore
|
|
62
|
+
raise EmbedParseError(
|
|
63
|
+
"`add_field` payload was not split by `;;` (double semicolon), `|` (pipe), or `~` (tilde)"
|
|
64
|
+
)
|
|
64
65
|
try:
|
|
65
66
|
name, value, _inline = data
|
|
66
67
|
inline = implicit_bool(_inline)
|
|
@@ -69,7 +70,10 @@ def add_field(embed: Embed, _: str, payload: str) -> None:
|
|
|
69
70
|
"`inline` argument for `add_field` is not a boolean value (_inline)"
|
|
70
71
|
)
|
|
71
72
|
except ValueError:
|
|
72
|
-
name, value = cast(
|
|
73
|
+
name, value = cast(
|
|
74
|
+
List[str],
|
|
75
|
+
helper_split(payload, maxsplit=1, double_semicolon=True),
|
|
76
|
+
)
|
|
73
77
|
inline = False
|
|
74
78
|
name = truncate(name, max=FIELD_LIMITS["field.name"])
|
|
75
79
|
value = truncate(value, max=FIELD_LIMITS["field.value"])
|
|
@@ -77,7 +81,7 @@ def add_field(embed: Embed, _: str, payload: str) -> None:
|
|
|
77
81
|
|
|
78
82
|
|
|
79
83
|
def set_footer(embed: Embed, _: str, payload: str) -> None:
|
|
80
|
-
data =
|
|
84
|
+
data = helper_split(payload, maxsplit=1, double_semicolon=True) # type: ignore
|
|
81
85
|
if data is None:
|
|
82
86
|
embed.set_footer(text=truncate(payload, max=FIELD_LIMITS["footer.text"]))
|
|
83
87
|
else:
|
|
@@ -85,6 +89,15 @@ def set_footer(embed: Embed, _: str, payload: str) -> None:
|
|
|
85
89
|
embed.set_footer(text=truncate(text, max=FIELD_LIMITS["footer.text"]), icon_url=icon_url)
|
|
86
90
|
|
|
87
91
|
|
|
92
|
+
def set_author(embed: Embed, _: str, payload: str) -> None:
|
|
93
|
+
data = helper_split(payload, maxsplit=1, double_semicolon=True) # type: ignore
|
|
94
|
+
if data is None:
|
|
95
|
+
embed.set_author(name=truncate(payload, max=FIELD_LIMITS["author.name"]))
|
|
96
|
+
else:
|
|
97
|
+
name, icon_url = data
|
|
98
|
+
embed.set_author(name=truncate(name, max=FIELD_LIMITS["author.name"]), icon_url=icon_url)
|
|
99
|
+
|
|
100
|
+
|
|
88
101
|
# Discord embed field character limits
|
|
89
102
|
# https://docs.discord.com/developers/resources/message#embed-object-embed-limits
|
|
90
103
|
FIELD_LIMITS: Dict[str, int] = {
|
|
@@ -110,19 +123,41 @@ class EmbedBlock(Block):
|
|
|
110
123
|
Multiple embed generators are available online to visualize and generate
|
|
111
124
|
embed JSON.
|
|
112
125
|
|
|
126
|
+
.. important::
|
|
127
|
+
- `Embed Limits are: <https://docs.discord.com/developers/resources/message#embed-object-embed-limits>`_
|
|
128
|
+
- Title: 256 characters
|
|
129
|
+
- Description: 4096 characters
|
|
130
|
+
- Footer: 2048 characters
|
|
131
|
+
- Author: 256 characters
|
|
132
|
+
- Field name: 256 characters
|
|
133
|
+
- Field value: 1024 characters
|
|
134
|
+
- The total length of the embed must not exceed 6000 characters.
|
|
135
|
+
|
|
113
136
|
**Usage:** ``{embed(<json>)}``
|
|
114
137
|
|
|
115
138
|
**Payload:** None
|
|
116
139
|
|
|
117
140
|
**Parameter:** json
|
|
118
141
|
|
|
119
|
-
**Examples:**
|
|
142
|
+
**Examples:**
|
|
143
|
+
|
|
144
|
+
.. code-block:: yaml
|
|
120
145
|
|
|
146
|
+
Example 1:
|
|
121
147
|
{embed({"title":"Hello!", "description":"This is a test embed."})}
|
|
148
|
+
|
|
149
|
+
Example 2:
|
|
122
150
|
{embed({
|
|
123
151
|
"title":"Here's a random duck!",
|
|
124
152
|
"image":{"url":"https://random-d.uk/api/randomimg"},
|
|
125
|
-
"color":15194415
|
|
153
|
+
"color":15194415,
|
|
154
|
+
"fields": [
|
|
155
|
+
{
|
|
156
|
+
"name": "Fun Fact",
|
|
157
|
+
"value": "Ducks are birds that are well-adapted to life in and around water.",
|
|
158
|
+
"inline": false
|
|
159
|
+
}
|
|
160
|
+
]
|
|
126
161
|
})}
|
|
127
162
|
|
|
128
163
|
**Manual**
|
|
@@ -135,11 +170,12 @@ class EmbedBlock(Block):
|
|
|
135
170
|
* ``url``
|
|
136
171
|
* ``thumbnail``
|
|
137
172
|
* ``image``
|
|
173
|
+
* ``author``
|
|
138
174
|
* ``footer``
|
|
139
175
|
* ``field`` - (See below)
|
|
140
176
|
|
|
141
|
-
Adding a field to an embed requires the payload to be split by
|
|
142
|
-
|
|
177
|
+
Adding a field to an embed requires the payload to be split by ``;;``,
|
|
178
|
+
``|`` or ``~`` into either 2 or 3 parts. The first part is the name
|
|
143
179
|
of the field, the second is the text of the field, and the third
|
|
144
180
|
optionally specifies whether the field should be inline.
|
|
145
181
|
|
|
@@ -155,16 +191,22 @@ class EmbedBlock(Block):
|
|
|
155
191
|
{embed(title):Rules}
|
|
156
192
|
{embed(description):Follow these rules to ensure a good experience in our server!}
|
|
157
193
|
{embed(field):Rule 1|Respect everyone you speak to.|false}
|
|
194
|
+
{embed(author):Mod Team|{author(avatar)}}
|
|
158
195
|
{embed(footer):Thanks for reading!|{guild(icon)}}
|
|
159
196
|
|
|
160
197
|
Both methods can be combined to create an embed in a tag.
|
|
161
198
|
The following tagscript uses JSON to create an embed with fields and later
|
|
162
199
|
set the embed title.
|
|
163
200
|
|
|
164
|
-
::
|
|
201
|
+
.. caution::
|
|
202
|
+
- The ``JSON`` block acts as a base for the embed.
|
|
203
|
+
- Since blocks are processed in **order**, the ``JSON`` block **must** come **before** any manual attribute blocks.
|
|
204
|
+
- The manual attributes are used to ``modify`` or ``add`` to the embed created by the ``JSON`` block.
|
|
165
205
|
|
|
166
|
-
|
|
167
|
-
|
|
206
|
+
.. code-block:: yaml
|
|
207
|
+
|
|
208
|
+
{embed({
|
|
209
|
+
"description": "This is a test description.",
|
|
168
210
|
"fields": [
|
|
169
211
|
{
|
|
170
212
|
"name": "Field 1",
|
|
@@ -173,6 +215,8 @@ class EmbedBlock(Block):
|
|
|
173
215
|
}
|
|
174
216
|
]
|
|
175
217
|
})}
|
|
218
|
+
{embed(title):My embed title}
|
|
219
|
+
|
|
176
220
|
"""
|
|
177
221
|
|
|
178
222
|
ACCEPTED_NAMES: Tuple[str, ...] = ("embed",)
|
|
@@ -186,23 +230,10 @@ class EmbedBlock(Block):
|
|
|
186
230
|
"thumbnail": set_dynamic_url,
|
|
187
231
|
"image": set_dynamic_url,
|
|
188
232
|
"field": add_field,
|
|
233
|
+
"author": set_author,
|
|
189
234
|
"footer": set_footer,
|
|
190
235
|
}
|
|
191
236
|
|
|
192
|
-
@removal(
|
|
193
|
-
name="EmbedBlock",
|
|
194
|
-
reason=(
|
|
195
|
-
"One of EmbedBlock's trait is scheduled to be removed in the next minor release, "
|
|
196
|
-
"A minor exception handling which would restrict the embed from getting sent and "
|
|
197
|
-
"would raise TagScriptEngine.exceptions.EmbedParseError incase it had more than "
|
|
198
|
-
"6000 characters, to know more about the limitations of discord embeds refer to the "
|
|
199
|
-
"[Official Discord API Docs](https://discord.com/developers/docs/resources/channel#embed-object-embed-limits)."
|
|
200
|
-
),
|
|
201
|
-
version="3.2.0",
|
|
202
|
-
)
|
|
203
|
-
def __init__(self) -> None:
|
|
204
|
-
super().__init__()
|
|
205
|
-
|
|
206
237
|
@staticmethod
|
|
207
238
|
def get_embed(ctx: Context) -> Embed:
|
|
208
239
|
return ctx.response.actions.get("embed", Embed())
|
TagScriptEngine/block/helpers.py
CHANGED
|
@@ -4,17 +4,15 @@ import re
|
|
|
4
4
|
from typing import Dict, List, Optional, Tuple
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
__all__: Tuple[str, ...] = (
|
|
8
|
-
"implicit_bool",
|
|
9
|
-
"helper_parse_if",
|
|
10
|
-
"helper_split",
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
EASIER_SPLIT_REGEX: re.Pattern[str] = re.compile(r"/[\~;]/")
|
|
17
|
-
BOOL_LOOKUP: Dict[str, bool] = {
|
|
7
|
+
__all__: Tuple[str, ...] = (
|
|
8
|
+
"implicit_bool",
|
|
9
|
+
"helper_parse_if",
|
|
10
|
+
"helper_split",
|
|
11
|
+
"helper_parse_list_if",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
SPLIT_REGEX: re.Pattern[str] = re.compile(r"(?<!\\)\|")
|
|
15
|
+
BOOL_LOOKUP: Dict[str, bool] = {
|
|
18
16
|
"true": True,
|
|
19
17
|
"false": False,
|
|
20
18
|
"enable": True,
|
|
@@ -110,51 +108,32 @@ def helper_parse_if(string: str) -> Optional[bool]:
|
|
|
110
108
|
except Exception:
|
|
111
109
|
pass
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
def helper_split(
|
|
115
|
-
split_string: str,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"""
|
|
140
|
-
A helper method to universalize the splitting logic used in blocks
|
|
141
|
-
and adapters. Please use this wherever a verb needs content to be
|
|
142
|
-
chopped at `|`, `,` or `;`.
|
|
143
|
-
|
|
144
|
-
>>> easier_helper_split("this, should|work")
|
|
145
|
-
["this, should", "work"]
|
|
146
|
-
|
|
147
|
-
>>> easier_helper_split("this, should;work~as well")
|
|
148
|
-
["this, should", "work", "as well"]
|
|
149
|
-
"""
|
|
150
|
-
args = (maxsplit,) if maxsplit is not None else ()
|
|
151
|
-
if "|" in split_string:
|
|
152
|
-
return SPLIT_REGEX.split(split_string, *args)
|
|
153
|
-
if "~" in split_string:
|
|
154
|
-
return EASIER_SPLIT_REGEX.split(split_string, *args)
|
|
155
|
-
if ";" in split_string:
|
|
156
|
-
return EASIER_SPLIT_REGEX.split(split_string, *args)
|
|
157
|
-
return
|
|
111
|
+
|
|
112
|
+
def helper_split(
|
|
113
|
+
split_string: str,
|
|
114
|
+
easy: bool = True,
|
|
115
|
+
*,
|
|
116
|
+
maxsplit: Optional[int] = None,
|
|
117
|
+
double_semicolon: bool = False,
|
|
118
|
+
) -> Optional[List[str]]:
|
|
119
|
+
"""
|
|
120
|
+
A helper method to universalize the splitting logic used in multiple
|
|
121
|
+
blocks and adapters. Please use this wherever a verb needs content to
|
|
122
|
+
be chopped at ``|`` or ``~``. Embed parsing can also opt into ``;;``
|
|
123
|
+
taking priority over the other delimiters.
|
|
124
|
+
|
|
125
|
+
>>> helper_split("this, should|work")
|
|
126
|
+
["this, should", "work"]
|
|
127
|
+
"""
|
|
128
|
+
args = (maxsplit,) if maxsplit is not None else ()
|
|
129
|
+
if double_semicolon and ";;" in split_string:
|
|
130
|
+
return split_string.split(";;", *args)
|
|
131
|
+
if "|" in split_string:
|
|
132
|
+
return SPLIT_REGEX.split(split_string, *args)
|
|
133
|
+
if easy:
|
|
134
|
+
if "~" in split_string:
|
|
135
|
+
return split_string.split("~", *args)
|
|
136
|
+
return
|
|
158
137
|
|
|
159
138
|
|
|
160
139
|
def helper_parse_list_if(if_string):
|
|
@@ -33,8 +33,8 @@ class JoinBlock(verb_required_block(False, payload=True, parameter=True)): # ty
|
|
|
33
33
|
{join():an example sentence}
|
|
34
34
|
# anexamplesentence
|
|
35
35
|
|
|
36
|
-
{join(-):
|
|
37
|
-
#
|
|
36
|
+
{join(-):cool aid man}
|
|
37
|
+
# cool-aid-man
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
40
|
ACCEPTED_NAMES: Tuple[str, ...] = ("join",)
|
|
@@ -0,0 +1,66 @@
|
|
|
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, ...] = ("ListBlock",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ListBlock(verb_required_block(True, payload=True, parameter=True)): # type: ignore
|
|
13
|
+
"""
|
|
14
|
+
The list block returns the element in the payload that corresponds to the
|
|
15
|
+
index value in the parameter. The block returns null if the index is out of bounds.
|
|
16
|
+
|
|
17
|
+
List and Cycle blocks are another way to parse through a list of values in
|
|
18
|
+
TagScript. They both strictly use either commas ``,`` or tildes ``~`` as the
|
|
19
|
+
delimiters for the list placed in the block's payload. Use tildes when elements
|
|
20
|
+
contain commas. These blocks only function in Tags.
|
|
21
|
+
|
|
22
|
+
Lists use ``0`` as the index for the first element. Negative values allow
|
|
23
|
+
for backward parsing. The block will return an error message if the value in
|
|
24
|
+
the parameter is not a number.
|
|
25
|
+
|
|
26
|
+
**Usage:** ``{list(<index>):<elem>,<elem2>,...}``
|
|
27
|
+
|
|
28
|
+
**Aliases:** ``None``
|
|
29
|
+
|
|
30
|
+
**Payload:** list of elements (comma or tilde separated)
|
|
31
|
+
|
|
32
|
+
**Parameter:** index
|
|
33
|
+
|
|
34
|
+
**Examples:** ::
|
|
35
|
+
|
|
36
|
+
{list(0):Pizza~Burger~Pie~Chips~Lasagna}
|
|
37
|
+
# Pizza
|
|
38
|
+
|
|
39
|
+
{list(3):Pizza~Burger~Pie~Chips~Lasagna}
|
|
40
|
+
# Chips
|
|
41
|
+
|
|
42
|
+
{list(-1):Apple,Banana,Cherry}
|
|
43
|
+
# Cherry
|
|
44
|
+
|
|
45
|
+
{list(10):Apple,Banana,Cherry}
|
|
46
|
+
# (returns null — index out of bounds)
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("list",)
|
|
50
|
+
|
|
51
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
52
|
+
try:
|
|
53
|
+
index = int(cast(str, ctx.verb.parameter))
|
|
54
|
+
except (ValueError, TypeError):
|
|
55
|
+
return "Invalid index: parameter must be a number."
|
|
56
|
+
|
|
57
|
+
payload = cast(str, ctx.verb.payload)
|
|
58
|
+
if "~" in payload:
|
|
59
|
+
items = payload.split("~")
|
|
60
|
+
else:
|
|
61
|
+
items = payload.split(",")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
return items[index]
|
|
65
|
+
except IndexError:
|
|
66
|
+
return None
|
|
@@ -16,7 +16,7 @@ from pyparsing import (
|
|
|
16
16
|
ZeroOrMore,
|
|
17
17
|
alphas,
|
|
18
18
|
nums,
|
|
19
|
-
|
|
19
|
+
one_of,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
22
|
from ..interface import Block
|
|
@@ -77,20 +77,20 @@ class NumericStringParser(object):
|
|
|
77
77
|
expr: Forward = Forward()
|
|
78
78
|
atom: ParserElement = (
|
|
79
79
|
(
|
|
80
|
-
Optional(
|
|
81
|
-
+ (ident + lpar + expr + rpar | pi | e | fnumber).
|
|
80
|
+
Optional(one_of("- +"))
|
|
81
|
+
+ (ident + lpar + expr + rpar | pi | e | fnumber).set_parse_action(self.pushFirst)
|
|
82
82
|
)
|
|
83
|
-
| Optional(
|
|
84
|
-
).
|
|
83
|
+
| Optional(one_of("- +")) + Group(lpar + expr + rpar)
|
|
84
|
+
).set_parse_action(self.pushUMinus)
|
|
85
85
|
# by defining exponentiation as "atom [ ^ factor ]..." instead of
|
|
86
86
|
# "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
|
|
87
87
|
# that is, 2^3^2 = 2^(3^2), not (2^3)^2.
|
|
88
88
|
factor: Forward = Forward()
|
|
89
|
-
factor << atom + ZeroOrMore((expop + factor).
|
|
90
|
-
term: ParserElement = factor + ZeroOrMore((multop + factor).
|
|
91
|
-
expr << term + ZeroOrMore((addop + term).
|
|
92
|
-
final: ParserElement = expr + ZeroOrMore((iop + expr).
|
|
93
|
-
# addop_term = ( addop + term ).
|
|
89
|
+
factor << atom + ZeroOrMore((expop + factor).set_parse_action(self.pushFirst)) # type: ignore
|
|
90
|
+
term: ParserElement = factor + ZeroOrMore((multop + factor).set_parse_action(self.pushFirst))
|
|
91
|
+
expr << term + ZeroOrMore((addop + term).set_parse_action(self.pushFirst)) # type: ignore
|
|
92
|
+
final: ParserElement = expr + ZeroOrMore((iop + expr).set_parse_action(self.pushFirst))
|
|
93
|
+
# addop_term = ( addop + term ).set_parse_action( self.pushFirst )
|
|
94
94
|
# general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
|
|
95
95
|
# expr << general_term
|
|
96
96
|
self.bnf: ParserElement = final
|
|
@@ -145,9 +145,9 @@ class NumericStringParser(object):
|
|
|
145
145
|
else:
|
|
146
146
|
return float(op)
|
|
147
147
|
|
|
148
|
-
def eval(self, num_string: str,
|
|
148
|
+
def eval(self, num_string: str, parse_all: bool = True) -> Any:
|
|
149
149
|
self.exprStack = []
|
|
150
|
-
results = self.bnf.
|
|
150
|
+
results = self.bnf.parse_string(num_string, parse_all) # noqa: F841
|
|
151
151
|
return self.evaluateStack(self.exprStack[:])
|
|
152
152
|
|
|
153
153
|
|
|
@@ -155,6 +155,71 @@ NSP: NumericStringParser = NumericStringParser()
|
|
|
155
155
|
|
|
156
156
|
|
|
157
157
|
class MathBlock(Block):
|
|
158
|
+
"""
|
|
159
|
+
The math block performs mathematical calculations from the given payload expression.
|
|
160
|
+
|
|
161
|
+
Supports standard arithmetic operators, exponentiation, modulo, in-place operators,
|
|
162
|
+
mathematical functions, and constants.
|
|
163
|
+
|
|
164
|
+
**Supported Operators:**
|
|
165
|
+
|
|
166
|
+
+----------+---------------------+
|
|
167
|
+
| Operator | Description |
|
|
168
|
+
+==========+=====================+
|
|
169
|
+
| ``+`` | Addition |
|
|
170
|
+
+----------+---------------------+
|
|
171
|
+
| ``-`` | Subtraction |
|
|
172
|
+
+----------+---------------------+
|
|
173
|
+
| ``*`` | Multiplication |
|
|
174
|
+
+----------+---------------------+
|
|
175
|
+
| ``/`` | Division |
|
|
176
|
+
+----------+---------------------+
|
|
177
|
+
| ``^`` | Exponentiation |
|
|
178
|
+
+----------+---------------------+
|
|
179
|
+
| ``%`` | Modulo |
|
|
180
|
+
+----------+---------------------+
|
|
181
|
+
| ``+=`` | In-place addition |
|
|
182
|
+
+----------+---------------------+
|
|
183
|
+
| ``-=`` | In-place subtraction|
|
|
184
|
+
+----------+---------------------+
|
|
185
|
+
| ``*=`` | In-place multiply |
|
|
186
|
+
+----------+---------------------+
|
|
187
|
+
| ``/=`` | In-place division |
|
|
188
|
+
+----------+---------------------+
|
|
189
|
+
|
|
190
|
+
**Supported Functions:**
|
|
191
|
+
``sin``, ``cos``, ``tan``, ``sinh``, ``cosh``, ``tanh``,
|
|
192
|
+
``exp``, ``abs``, ``trunc``, ``round``, ``sgn``,
|
|
193
|
+
``log`` (base 10), ``ln`` (natural), ``log2``, ``sqrt``
|
|
194
|
+
|
|
195
|
+
**Constants:** ``PI``, ``E``
|
|
196
|
+
|
|
197
|
+
**Usage:** ``{math:<expression>}``
|
|
198
|
+
|
|
199
|
+
**Aliases:** ``m, +, calc``
|
|
200
|
+
|
|
201
|
+
**Payload:** expression
|
|
202
|
+
|
|
203
|
+
**Parameter:** None
|
|
204
|
+
|
|
205
|
+
**Examples:** ::
|
|
206
|
+
|
|
207
|
+
{math:2+3}
|
|
208
|
+
# 5
|
|
209
|
+
|
|
210
|
+
{m:round(7/3)}
|
|
211
|
+
# 2
|
|
212
|
+
|
|
213
|
+
{calc:sin(PI/2)}
|
|
214
|
+
# 1.0
|
|
215
|
+
|
|
216
|
+
{+:7*6}
|
|
217
|
+
# 42
|
|
218
|
+
|
|
219
|
+
{m:sqrt(144)}
|
|
220
|
+
# 12.0
|
|
221
|
+
"""
|
|
222
|
+
|
|
158
223
|
ACCEPTED_NAMES: Tuple[str, ...] = ("math", "m", "+", "calc")
|
|
159
224
|
|
|
160
225
|
def process(self, ctx: Context) -> TypingOptional[str]:
|
|
@@ -0,0 +1,62 @@
|
|
|
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, ...] = ("OrdBlock",)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _ordinal(n: int) -> str:
|
|
13
|
+
"""Return the ordinal string for an integer (e.g. 1 -> '1st')."""
|
|
14
|
+
if 11 <= abs(n) % 100 <= 13:
|
|
15
|
+
suffix = "th"
|
|
16
|
+
else:
|
|
17
|
+
suffix = {1: "st", 2: "nd", 3: "rd"}.get(abs(n) % 10, "th")
|
|
18
|
+
return f"{n}{suffix}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OrdinalBlock(verb_required_block(True, payload=True)): # type: ignore
|
|
22
|
+
"""
|
|
23
|
+
The ordinal block returns the number in the payload with the correct ordinal
|
|
24
|
+
abbreviation following it.
|
|
25
|
+
|
|
26
|
+
**Usage:** ``{ordinal:<number>}``
|
|
27
|
+
|
|
28
|
+
**Aliases:** ``ord``
|
|
29
|
+
|
|
30
|
+
**Payload:** number
|
|
31
|
+
|
|
32
|
+
**Parameter:** None
|
|
33
|
+
|
|
34
|
+
**Examples:** ::
|
|
35
|
+
|
|
36
|
+
{ordinal:101}
|
|
37
|
+
# 101st
|
|
38
|
+
|
|
39
|
+
{ord:22}
|
|
40
|
+
# 22nd
|
|
41
|
+
|
|
42
|
+
{ordinal:3}
|
|
43
|
+
# 3rd
|
|
44
|
+
|
|
45
|
+
{ord:456}
|
|
46
|
+
# 456th
|
|
47
|
+
|
|
48
|
+
{ordinal:11}
|
|
49
|
+
# 11th
|
|
50
|
+
|
|
51
|
+
{ord:12}
|
|
52
|
+
# 12th
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
ACCEPTED_NAMES: Tuple[str, ...] = ("ordinal", "ord")
|
|
56
|
+
|
|
57
|
+
def process(self, ctx: Context) -> Optional[str]:
|
|
58
|
+
try:
|
|
59
|
+
number = int(cast(str, ctx.verb.payload).strip())
|
|
60
|
+
except (ValueError, TypeError):
|
|
61
|
+
return None
|
|
62
|
+
return _ordinal(number)
|
TagScriptEngine/block/strf.py
CHANGED
|
@@ -16,7 +16,7 @@ class StrfBlock(Block):
|
|
|
16
16
|
Two types of timestamps are supported: ISO and epoch.
|
|
17
17
|
If a timestamp isn't passed, the current UTC time is used.
|
|
18
18
|
|
|
19
|
-
Invoking this block with
|
|
19
|
+
Invoking this block with :ref:`unix` will return the current Unix timestamp.
|
|
20
20
|
|
|
21
21
|
**Usage:** ``{strf([timestamp]):<format>}``
|
|
22
22
|
|
TagScriptEngine/block/substr.py
CHANGED
|
@@ -10,6 +10,61 @@ __all__: Tuple[str, ...] = ("SubstringBlock",)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class SubstringBlock(verb_required_block(True, parameter=True)): # type: ignore
|
|
13
|
+
"""
|
|
14
|
+
The substring block extracts a specific portion of the payload text using ``0``-based indexing.
|
|
15
|
+
It supports both a single starting index and a specific range of characters.
|
|
16
|
+
|
|
17
|
+
.. important::
|
|
18
|
+
|
|
19
|
+
**Behavior:**
|
|
20
|
+
|
|
21
|
+
- If a single ``index`` is provided, it returns all characters from that position to the end
|
|
22
|
+
(counting from the start).
|
|
23
|
+
- From ``nth`` position (index) to end.
|
|
24
|
+
|
|
25
|
+
- If a range is provided using ``-`` (e.g., ``0-5``), it returns the characters from
|
|
26
|
+
the ``start`` index to the ``end`` index but **without** including the ``end`` th index.
|
|
27
|
+
|
|
28
|
+
**Usage:** ``{substr(<start[-end]>):<text>}``
|
|
29
|
+
|
|
30
|
+
**Aliases:** ``substring``
|
|
31
|
+
|
|
32
|
+
**Payload:** text (to extract from)
|
|
33
|
+
|
|
34
|
+
**Parameter:** A single integer for start, or a hyphenated range (``start-end``).
|
|
35
|
+
|
|
36
|
+
**Examples:**
|
|
37
|
+
|
|
38
|
+
.. code-block:: yaml
|
|
39
|
+
|
|
40
|
+
{substr(7):Hello, World!}
|
|
41
|
+
# World!
|
|
42
|
+
Explanation:
|
|
43
|
+
# - Skips up to index 7 ("Hello, ") and starts at "W" (index 7). As 7th index is inclusive.
|
|
44
|
+
|
|
45
|
+
{substr(1-4):Hello}
|
|
46
|
+
# ell
|
|
47
|
+
Explanation:
|
|
48
|
+
# - Skips the first character ("H" at index 0) and starts at "e" (index 1).
|
|
49
|
+
# - Stops at index 4 (exclusive), so it doesn't include "o" (index 4).
|
|
50
|
+
|
|
51
|
+
{substr(7-12):Hello, World!}
|
|
52
|
+
# World
|
|
53
|
+
|
|
54
|
+
{substr(7):TagScript is powerful}
|
|
55
|
+
# pt is powerful
|
|
56
|
+
Explanation:
|
|
57
|
+
# - Skips up to index 7 ("TagScri") and starts at "pt" (index 7).
|
|
58
|
+
# - Indexing: T(0)a(1)g(2)S(3)c(4)r(5)i(6)p(7). So at 7 it starts at pt.
|
|
59
|
+
|
|
60
|
+
.. note::
|
|
61
|
+
|
|
62
|
+
- For Single Index: Starting index is ``inclusive`` to the end of the string.
|
|
63
|
+
- For Range: Starting index is ``inclusive``, while the ending index is ``exclusive``.
|
|
64
|
+
- Negative indices are not supported.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
13
68
|
ACCEPTED_NAMES: Tuple[str, ...] = ("substr", "substring")
|
|
14
69
|
|
|
15
70
|
def process(self, ctx: Context) -> Optional[str]:
|
TagScriptEngine/interpreter.py
CHANGED
|
@@ -416,11 +416,11 @@ class Interpreter(_Interpreter):
|
|
|
416
416
|
|
|
417
417
|
class AsyncInterpreter(Interpreter):
|
|
418
418
|
"""
|
|
419
|
-
An asynchronous subclass of
|
|
419
|
+
An asynchronous subclass of :class:`Interpreter` that allows blocks to implement asynchronous methods.
|
|
420
420
|
Synchronous blocks are still supported.
|
|
421
421
|
|
|
422
|
-
This subclass has no additional attributes from the
|
|
423
|
-
See
|
|
422
|
+
This subclass has no additional attributes from the :class:`Interpreter` class.
|
|
423
|
+
See :class:`Interpreter` for full documentation.
|
|
424
424
|
"""
|
|
425
425
|
|
|
426
426
|
async def _get_acceptors(self, ctx: Context) -> List[Block]: # type: ignore
|
|
@@ -482,8 +482,8 @@ class AsyncInterpreter(Interpreter):
|
|
|
482
482
|
"""
|
|
483
483
|
Asynchronously process a given TagScript string.
|
|
484
484
|
|
|
485
|
-
This method has no additional attributes from the
|
|
486
|
-
See
|
|
485
|
+
This method has no additional attributes from the :class:`Interpreter` class.
|
|
486
|
+
See :meth:`Interpreter.process` for full documentation.
|
|
487
487
|
"""
|
|
488
488
|
response = Response(variables=seed_variables, extra_kwargs=kwargs)
|
|
489
489
|
node_ordered_list = build_node_tree(message)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
TagScriptEngine/__init__.py,sha256=
|
|
1
|
+
TagScriptEngine/__init__.py,sha256=qXJ4KzdO5ibSYFDW6viRcN50OBzlVvHdxMn3RRCfESs,6144
|
|
2
2
|
TagScriptEngine/_warnings.py,sha256=pfMXaEVGpIue0eqgzZ6HkLXEka1UGl61-yuH9uEnVe4,2662
|
|
3
3
|
TagScriptEngine/exceptions.py,sha256=lhvskKi2AySE5pul_iAA251jdMv-92QkjodR4X-G27Y,2833
|
|
4
|
-
TagScriptEngine/interpreter.py,sha256=
|
|
4
|
+
TagScriptEngine/interpreter.py,sha256=5kF0kEqaZf5Hyu8tkmDDLEy7y-qvd6xiUJ3GwpDDh44,16638
|
|
5
5
|
TagScriptEngine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
TagScriptEngine/utils.py,sha256=Kq7_w6nuJizPYZDJIzXZ51b1ea92_7Vm_YDKbQci7F4,1697
|
|
7
7
|
TagScriptEngine/verb.py,sha256=nk5IenRRGuwzIY7cMyGQn7aVYvmGcNTszztAQTSHP54,5047
|
|
@@ -10,23 +10,26 @@ TagScriptEngine/adapter/discordadapters.py,sha256=l_6HijmmGDRX7lhahskH6mTteJFpS7
|
|
|
10
10
|
TagScriptEngine/adapter/functionadapter.py,sha256=lQlWm0dO-41bTQ2etFpWRaGnG22V1HMy9aiB8FnFZOU,591
|
|
11
11
|
TagScriptEngine/adapter/intadapter.py,sha256=jENx5ah8k0U1PjJccV0NiYFsWFU9ZXTFgz3f-qx30SA,548
|
|
12
12
|
TagScriptEngine/adapter/objectadapter.py,sha256=7bW-Qd6gujq2ALdK3p0ONoCSPWCTv6srkkDNjzkvDEU,1017
|
|
13
|
-
TagScriptEngine/adapter/redbotadapters.py,sha256=
|
|
13
|
+
TagScriptEngine/adapter/redbotadapters.py,sha256=CIHZPYtB_W3Tz9Q-FuSsGWuWvtmQ-0cgZIRVg_CETKc,6018
|
|
14
14
|
TagScriptEngine/adapter/stringadapter.py,sha256=VaJzkm1b8b7w1T90DjR3gOH0DIsZlWtcFe1YG7DOqBQ,1710
|
|
15
|
-
TagScriptEngine/block/__init__.py,sha256=
|
|
15
|
+
TagScriptEngine/block/__init__.py,sha256=zfoPTCTBDIuOkcjKnNL8gYIRKOi4c4gkYNwNJPvYctQ,3223
|
|
16
16
|
TagScriptEngine/block/allowedmentions.py,sha256=WMGWi1Fe0U_vUBsc4uyiX1XfQjGkhpdlyvq3HhCCLd4,2219
|
|
17
|
-
TagScriptEngine/block/assign.py,sha256=
|
|
17
|
+
TagScriptEngine/block/assign.py,sha256=lgJY_hUIjrKPm4N9vWr_rxhrKejGrMRbBG3oasb0hAI,8657
|
|
18
18
|
TagScriptEngine/block/breakblock.py,sha256=nt3T7fDhTxH0jpO8lu0_3oC4XwVuKKg9cmkdLsZwzL8,1288
|
|
19
|
-
TagScriptEngine/block/case.py,sha256=
|
|
20
|
-
TagScriptEngine/block/command.py,sha256=
|
|
19
|
+
TagScriptEngine/block/case.py,sha256=SF4uaAWyC9AiBzoI5SPDCaRx6wrY-_0IQDI1T_mZFvk,1503
|
|
20
|
+
TagScriptEngine/block/command.py,sha256=3xKo5H3K8F7BpxeHJp9I80n0QWvk_GHQCQpKnvQ7ZxQ,4309
|
|
21
21
|
TagScriptEngine/block/control.py,sha256=tALrHN0vbEXheMhoQOI_C0X_WjL3c-Qqd2zEd0rAZrE,5699
|
|
22
|
-
TagScriptEngine/block/cooldown.py,sha256=
|
|
22
|
+
TagScriptEngine/block/cooldown.py,sha256=7s7oXy2euN_w7TcfytHdNSt4duYowrqNcWuTJC3oxL4,4161
|
|
23
23
|
TagScriptEngine/block/count.py,sha256=URa5xadQ56X7uaBkhMew6KyMdlZDwuj3xKjIH_PK37U,2135
|
|
24
|
-
TagScriptEngine/block/
|
|
24
|
+
TagScriptEngine/block/cycleblock.py,sha256=61AhV3aNMVFGgymOuoWm79WzKjPvu_S8MU9f172lb9U,2308
|
|
25
|
+
TagScriptEngine/block/embedblock.py,sha256=57DUs_DXHygvWflh-ReqvyrujQGQdzv5rXtkCSn4KGk,12110
|
|
25
26
|
TagScriptEngine/block/fiftyfifty.py,sha256=QjQP96vpQPRVpuVcF5iI1kTCkM54vUBpKgK2TEZU6vQ,827
|
|
26
|
-
TagScriptEngine/block/helpers.py,sha256=
|
|
27
|
-
TagScriptEngine/block/joinblock.py,sha256
|
|
27
|
+
TagScriptEngine/block/helpers.py,sha256=DD71FBptjtSTlt-mHd4EqnLNn8x7gS3jCpqRudVIbdI,3596
|
|
28
|
+
TagScriptEngine/block/joinblock.py,sha256=gD4NdvJJ7YN8SIu3QrOw6hABAt-cHDQNNQkmiLMKqAg,1094
|
|
29
|
+
TagScriptEngine/block/listblock.py,sha256=r90w1oeJjH_t39cKBT96xW8mnRXLfvyLe8KOz2p6bHw,2045
|
|
28
30
|
TagScriptEngine/block/loosevariablegetter.py,sha256=ym6u9HTWKN-Cgk6OQH5FVIiGnxsND2g5o4r3kRBGH9M,1231
|
|
29
|
-
TagScriptEngine/block/mathblock.py,sha256=
|
|
31
|
+
TagScriptEngine/block/mathblock.py,sha256=g15LG_KEh9w8BiXNRkWcx-8K73Ic2pbiZEVZ7WrHyms,7616
|
|
32
|
+
TagScriptEngine/block/ordblock.py,sha256=_FOe4eI5gBm_8Ek1ZTW25lO-2lasOh8oT4Temp4WmiI,1361
|
|
30
33
|
TagScriptEngine/block/randomblock.py,sha256=udC7mCGYrtN_Exg1XZZvqvvi8KkGNnRyTkuh-6rbeSo,1594
|
|
31
34
|
TagScriptEngine/block/range.py,sha256=3dz6jgTIBloZyXgN7eXGsB7SBjK3Y-OJROmHOeOaJT4,1836
|
|
32
35
|
TagScriptEngine/block/redirect.py,sha256=BobWl6xKqVV4FZlihmJoLPwI_i0P6Sn6G6fWFf9d8CE,1140
|
|
@@ -34,15 +37,15 @@ TagScriptEngine/block/replaceblock.py,sha256=NXx6wdgI4r3MOw-E5oIbtrMEc9BdxpM9Rip
|
|
|
34
37
|
TagScriptEngine/block/require_blacklist.py,sha256=3jFK8Ap_ataHZrqF3MWrmXrBI5CkIXq8G6QMEq0QQXY,2810
|
|
35
38
|
TagScriptEngine/block/shortcutredirect.py,sha256=2F20L_7hYMxJY9s02XwxJQIWXaBQM_dwBNZKsOJQsk8,683
|
|
36
39
|
TagScriptEngine/block/stopblock.py,sha256=C10JCaajiVetEwnP_1T7SwhT8UmBQzWCPpdtDF16nZY,1091
|
|
37
|
-
TagScriptEngine/block/strf.py,sha256=
|
|
40
|
+
TagScriptEngine/block/strf.py,sha256=VfSqKYzXA6zdpX1hOncqpFOBaSi5Tq4emF_rsLj7AxQ,2053
|
|
38
41
|
TagScriptEngine/block/strictvariablegetter.py,sha256=XMwqGad3iy5esB2aTiD5v1EaCLGDFMvB1RF11G0-l2M,1251
|
|
39
|
-
TagScriptEngine/block/substr.py,sha256=
|
|
42
|
+
TagScriptEngine/block/substr.py,sha256=50bRdll_7WTlCgmLlo9pr6nyvsqNq0xZyc3dhLCXKVg,2724
|
|
40
43
|
TagScriptEngine/block/urlencodeblock.py,sha256=sWqdSBk-uTZZjebKhAFmlROvnQvLcjvyISPTiWb4nwM,1396
|
|
41
44
|
TagScriptEngine/interface/__init__.py,sha256=37lAOMONKmWDLiC5Ox9Y3TNQUQbzW54oUugbQe0e9gc,341
|
|
42
45
|
TagScriptEngine/interface/adapter.py,sha256=4jBBz7hc-8fsRSZ5DWOrUxrhXqS0SvJCdW57TZl8sQo,1967
|
|
43
46
|
TagScriptEngine/interface/block.py,sha256=S0hWRH6uqBz_cYRIh3LGMyo63UK6gNidtf6EiLUKL0c,3653
|
|
44
|
-
advancedtagscript-3.
|
|
45
|
-
advancedtagscript-3.
|
|
46
|
-
advancedtagscript-3.
|
|
47
|
-
advancedtagscript-3.
|
|
48
|
-
advancedtagscript-3.
|
|
47
|
+
advancedtagscript-3.3.1.dist-info/licenses/LICENSE,sha256=NvdimESU3jrNgd_8E4i-eTecQ_jGegRrrWk1v2gOnHY,252
|
|
48
|
+
advancedtagscript-3.3.1.dist-info/METADATA,sha256=9itEFRENyeoFYtftKMRRtBW1iCqjGTF6dOEnfLKR-zg,3565
|
|
49
|
+
advancedtagscript-3.3.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
50
|
+
advancedtagscript-3.3.1.dist-info/top_level.txt,sha256=6lY4lZDvWxraERrit1lvDsCCDdfo9e83kk_tBemAJCo,16
|
|
51
|
+
advancedtagscript-3.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|