bTagScript 5.0.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.
Files changed (47) hide show
  1. bTagScript/__init__.py +66 -0
  2. bTagScript/adapter/__init__.py +16 -0
  3. bTagScript/adapter/discord_adapters.py +275 -0
  4. bTagScript/adapter/function_adapter.py +33 -0
  5. bTagScript/adapter/int_adapter.py +30 -0
  6. bTagScript/adapter/object_adapter.py +41 -0
  7. bTagScript/adapter/string_adapter.py +69 -0
  8. bTagScript/block/__init__.py +67 -0
  9. bTagScript/block/break_block.py +41 -0
  10. bTagScript/block/case_block.py +62 -0
  11. bTagScript/block/comment_block.py +33 -0
  12. bTagScript/block/control_block.py +161 -0
  13. bTagScript/block/counting_blocks.py +88 -0
  14. bTagScript/block/digitshorthand_block.py +38 -0
  15. bTagScript/block/discord_blocks/__init__.py +20 -0
  16. bTagScript/block/discord_blocks/command_block.py +52 -0
  17. bTagScript/block/discord_blocks/cooldown_block.py +101 -0
  18. bTagScript/block/discord_blocks/delete_block.py +47 -0
  19. bTagScript/block/discord_blocks/embed_block.py +248 -0
  20. bTagScript/block/discord_blocks/override_block.py +61 -0
  21. bTagScript/block/discord_blocks/react_block.py +49 -0
  22. bTagScript/block/discord_blocks/redirect_block.py +42 -0
  23. bTagScript/block/discord_blocks/requirement_blocks.py +83 -0
  24. bTagScript/block/helpers.py +121 -0
  25. bTagScript/block/math_blocks.py +257 -0
  26. bTagScript/block/random_block.py +65 -0
  27. bTagScript/block/range_block.py +54 -0
  28. bTagScript/block/replace_block.py +110 -0
  29. bTagScript/block/stop_block.py +38 -0
  30. bTagScript/block/strf_block.py +70 -0
  31. bTagScript/block/url_blocks.py +76 -0
  32. bTagScript/block/util_blocks/__init__.py +3 -0
  33. bTagScript/block/util_blocks/debug_block.py +107 -0
  34. bTagScript/block/var_block.py +49 -0
  35. bTagScript/block/vargetter_blocks.py +91 -0
  36. bTagScript/exceptions.py +139 -0
  37. bTagScript/interface/__init__.py +4 -0
  38. bTagScript/interface/adapter.py +43 -0
  39. bTagScript/interface/block.py +136 -0
  40. bTagScript/interpreter.py +614 -0
  41. bTagScript/utils.py +65 -0
  42. bTagScript/verb.py +129 -0
  43. btagscript-5.0.0.dist-info/METADATA +109 -0
  44. btagscript-5.0.0.dist-info/RECORD +47 -0
  45. btagscript-5.0.0.dist-info/WHEEL +5 -0
  46. btagscript-5.0.0.dist-info/licenses/LICENSE +1 -0
  47. btagscript-5.0.0.dist-info/top_level.txt +1 -0
bTagScript/__init__.py ADDED
@@ -0,0 +1,66 @@
1
+ """
2
+ This work is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license,
3
+ visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
4
+
5
+ This fork was made by _Leg3ndary and contains everything he thought should be added to tagscript. Hope you enjoy!
6
+ """
7
+
8
+ from collections import namedtuple
9
+
10
+ from .adapter import *
11
+ from .block import *
12
+ from .exceptions import *
13
+ from .interface import *
14
+ from .interpreter import *
15
+ from .utils import *
16
+ from .verb import Verb
17
+
18
+ __version__ = "5.0.0"
19
+
20
+
21
+ class VersionInfo(namedtuple("VersionInfo", "major minor micro")):
22
+ """
23
+ Version information.
24
+
25
+ Attributes
26
+ ----------
27
+ major: int
28
+ Major version number.
29
+ minor: int
30
+ Minor version number.
31
+ micro: int
32
+ Micro version number.
33
+ """
34
+
35
+ __slots__ = ()
36
+
37
+ def __str__(self):
38
+ """
39
+ Returns a string representation of the version information.
40
+
41
+ Returns
42
+ -------
43
+ str
44
+ String representation of the version information.
45
+ """
46
+ return "{major}.{minor}.{micro}".format(**self._asdict())
47
+
48
+ @classmethod
49
+ def from_str(cls, version):
50
+ """
51
+ Returns a VersionInfo instance from a string.
52
+
53
+ Parameters
54
+ ----------
55
+ version: str
56
+ String representation of the version information.
57
+
58
+ Returns
59
+ -------
60
+ VersionInfo
61
+ Version information.
62
+ """
63
+ return cls(*map(int, version.split(".")))
64
+
65
+
66
+ version_info = VersionInfo.from_str(__version__)
@@ -0,0 +1,16 @@
1
+ from .discord_adapters import *
2
+ from .function_adapter import FunctionAdapter
3
+ from .int_adapter import IntAdapter
4
+ from .object_adapter import SafeObjectAdapter
5
+ from .string_adapter import StringAdapter
6
+
7
+ __all__ = (
8
+ "SafeObjectAdapter",
9
+ "StringAdapter",
10
+ "IntAdapter",
11
+ "FunctionAdapter",
12
+ "AttributeAdapter",
13
+ "MemberAdapter",
14
+ "ChannelAdapter",
15
+ "GuildAdapter",
16
+ )
@@ -0,0 +1,275 @@
1
+ from random import choice
2
+ from typing import Union
3
+
4
+ from discord import Guild, Member, TextChannel, Thread
5
+
6
+ from ..interface import Adapter
7
+ from ..utils import escape_content
8
+ from ..verb import Verb
9
+
10
+ __all__ = (
11
+ "AttributeAdapter",
12
+ "MemberAdapter",
13
+ "ChannelAdapter",
14
+ "GuildAdapter",
15
+ )
16
+
17
+
18
+ class AttributeAdapter(Adapter):
19
+ """
20
+ Base attribute adapter for discord.py objects
21
+ """
22
+
23
+ __slots__ = ("object", "_attributes", "_methods")
24
+
25
+ def __init__(self, base: Union[TextChannel, Member, Guild]) -> None:
26
+ """
27
+ Init for the attribute adapter
28
+ """
29
+ self.object = base
30
+ self._attributes = {
31
+ "id": base.id,
32
+ "created_at": base.created_at,
33
+ "timestamp": int(base.created_at.timestamp()),
34
+ "name": getattr(base, "name", str(base)),
35
+ }
36
+ self._methods = {}
37
+ self.update_attributes()
38
+ self.update_methods()
39
+
40
+ def __repr__(self) -> str:
41
+ """
42
+ printable repr
43
+ """
44
+ return f"<{type(self).__qualname__} object={self.object!r}>"
45
+
46
+ def update_attributes(self) -> None:
47
+ """
48
+ Update attributes for the block
49
+ """
50
+
51
+ def update_methods(self) -> None:
52
+ """
53
+ Update methods for the block
54
+ """
55
+
56
+ def get_value(self, ctx: Verb) -> str:
57
+ """
58
+ Get the value for the adapter
59
+ """
60
+ should_escape = False
61
+
62
+ if ctx.parameter is None:
63
+ return_value = str(self.object)
64
+ else:
65
+ try:
66
+ value = self._attributes[ctx.parameter]
67
+ except KeyError:
68
+ if method := self._methods.get(ctx.parameter):
69
+ value = method()
70
+ else:
71
+ return
72
+
73
+ if isinstance(value, tuple):
74
+ value, should_escape = value
75
+
76
+ return_value = str(value) if value is not None else None
77
+
78
+ return escape_content(return_value) if should_escape else return_value
79
+
80
+
81
+ class MemberAdapter(AttributeAdapter):
82
+ """
83
+ The ``{author}`` block with no parameters returns the tag invoker's full username
84
+ and discriminator, but passing the attributes listed below to the block payload
85
+ will return that attribute instead.
86
+
87
+ **Aliases:** ``user``
88
+
89
+ **Usage:** ``{author([attribute])``
90
+
91
+ **Payload:** None
92
+
93
+ **Parameter:** attribute, None
94
+
95
+ Attributes
96
+ ----------
97
+ id
98
+ The author's Discord ID.
99
+ name
100
+ The author's username.
101
+ nick
102
+ The author's nickname, if they have one, else their username.
103
+ avatar
104
+ A link to the author's avatar, which can be used in embeds.
105
+ discriminator
106
+ The author's discriminator.
107
+ created_at
108
+ The author's account creation date.
109
+ timestamp
110
+ The author's account creation date as a UTC timestamp.
111
+ joined_at
112
+ The date the author joined the server.
113
+ mention
114
+ A formatted text that pings the author.
115
+ bot
116
+ Whether or not the author is a bot.
117
+ color
118
+ The author's top role's color as a hex code.
119
+ top_role
120
+ The author's top role.
121
+ boost
122
+ If the user boosted, this will be the the UTC timestamp of when they did, if not, this will be empty.
123
+ timed_out
124
+ If the user is timed out, this will be the the UTC timestamp of when they'll be "untimed-out", if not timed out, this will be empty.
125
+ banner
126
+ The users banner url
127
+ roleids
128
+ A list of the author's role IDs, split by spaces.
129
+ """
130
+
131
+ def update_attributes(self) -> None:
132
+ """
133
+ Update the adapter with all it's needed attributes
134
+ """
135
+ additional_attributes = {
136
+ "color": self.object.color,
137
+ "colour": self.object.color,
138
+ "nick": self.object.display_name,
139
+ "avatar": (self.object.display_avatar.url, False),
140
+ "discriminator": self.object.discriminator,
141
+ "joined_at": getattr(self.object, "joined_at", ""),
142
+ "mention": self.object.mention,
143
+ "bot": self.object.bot,
144
+ "top_role": getattr(self.object, "top_role", ""),
145
+ "boost": getattr(self.object, "premium_since", ""),
146
+ "timed_out": getattr(self.object, "timed_out_until", ""),
147
+ "banner": self.object.banner.url if self.object.banner else "",
148
+ }
149
+ if roleids := getattr(self.object, "_roles", None):
150
+ additional_attributes["roleids"] = " ".join(str(r) for r in roleids)
151
+ self._attributes.update(additional_attributes)
152
+
153
+
154
+ class ChannelAdapter(AttributeAdapter):
155
+ """
156
+ The ``{channel}`` block with no parameters returns the channel's full name
157
+ but passing the attributes listed below to the block payload
158
+ will return that attribute instead.
159
+
160
+ **Usage:** ``{channel([attribute])``
161
+
162
+ **Payload:** None
163
+
164
+ **Parameter:** attribute, None
165
+
166
+ Attributes
167
+ ----------
168
+ id
169
+ The channel's ID.
170
+ name
171
+ The channel's name.
172
+ created_at
173
+ The channel's creation date.
174
+ timestamp
175
+ The channel's creation date as a UTC timestamp.
176
+ nsfw
177
+ Whether the channel is nsfw.
178
+ mention
179
+ A formatted text that pings the channel.
180
+ topic
181
+ The channel's topic.
182
+ slowmode
183
+ The channel's slowmode in seconds, 0 if disabled
184
+ """
185
+
186
+ def update_attributes(self) -> None:
187
+ """
188
+ Update block attributes
189
+ """
190
+ if isinstance(self.object, TextChannel):
191
+ additional_attributes = {
192
+ "channel_type": "textchannel",
193
+ "nsfw": self.object.nsfw,
194
+ "mention": self.object.mention,
195
+ "topic": self.object.topic or None,
196
+ "slowmode": self.object.slowmode_delay,
197
+ }
198
+ self._attributes.update(additional_attributes)
199
+ elif isinstance(self.object, Thread):
200
+ pass
201
+
202
+
203
+ class GuildAdapter(AttributeAdapter):
204
+ """
205
+ The ``{server}`` block with no parameters returns the server's name
206
+ but passing the attributes listed below to the block payload
207
+ will return that attribute instead.
208
+
209
+ **Aliases:** ``guild``
210
+
211
+ **Usage:** ``{server([attribute])``
212
+
213
+ **Payload:** None
214
+
215
+ **Parameter:** attribute, None
216
+
217
+ Attributes
218
+ ----------
219
+ id
220
+ The server's ID.
221
+ name
222
+ The server's name.
223
+ icon
224
+ A link to the server's icon, which can be used in embeds.
225
+ created_at
226
+ The server's creation date.
227
+ timestamp
228
+ The server's creation date as a UTC timestamp.
229
+ member_count
230
+ The server's member count.
231
+ bots
232
+ The number of bots in the server.
233
+ humans
234
+ The number of humans in the server.
235
+ description
236
+ The server's description if one is set, or "No description".
237
+ random
238
+ A random member from the server.
239
+ """
240
+
241
+ def update_attributes(self) -> None:
242
+ """
243
+ Update block attributes
244
+ """
245
+ guild = self.object
246
+ bots = 0
247
+ humans = 0
248
+ for m in guild.members:
249
+ if m.bot:
250
+ bots += 1
251
+ else:
252
+ humans += 1
253
+ member_count = guild.member_count
254
+ additional_attributes = {
255
+ "icon": guild.icon.url if guild.icon else "",
256
+ "member_count": member_count,
257
+ "members": member_count,
258
+ "bots": bots,
259
+ "humans": humans,
260
+ "description": guild.description or "No description",
261
+ }
262
+ self._attributes.update(additional_attributes)
263
+
264
+ def update_methods(self) -> None:
265
+ """
266
+ Update methods for the block
267
+ """
268
+ additional_methods = {"random": self.random_member}
269
+ self._methods.update(additional_methods)
270
+
271
+ def random_member(self) -> None:
272
+ """
273
+ Return a random member
274
+ """
275
+ return choice(self.object.members)
@@ -0,0 +1,33 @@
1
+ from typing import Callable
2
+
3
+ from ..interface import Adapter
4
+ from ..verb import Verb
5
+
6
+
7
+ class FunctionAdapter(Adapter):
8
+ """
9
+ Function adapter...
10
+
11
+ Would be cool to have functions in tagscript
12
+ """
13
+
14
+ __slots__ = ("fn",)
15
+
16
+ def __init__(self, function_pointer: Callable[[], str]) -> None:
17
+ """
18
+ Construct the adapter
19
+ """
20
+ self.fn = function_pointer
21
+ super().__init__()
22
+
23
+ def __repr__(self) -> str:
24
+ """
25
+ String repr
26
+ """
27
+ return f"<{type(self).__qualname__} fn={self.fn!r}>"
28
+
29
+ def get_value(self, ctx: Verb) -> str:
30
+ """
31
+ Run the function and get the value
32
+ """
33
+ return str(self.fn())
@@ -0,0 +1,30 @@
1
+ from ..interface import Adapter
2
+ from ..verb import Verb
3
+
4
+
5
+ class IntAdapter(Adapter):
6
+ """
7
+ IntAdapter
8
+
9
+ This will be useful in the future if types are ever introduced.
10
+ """
11
+
12
+ __slots__ = ("integer",)
13
+
14
+ def __init__(self, integer: int) -> None:
15
+ """
16
+ Construct the int adapter
17
+ """
18
+ self.integer: int = int(integer)
19
+
20
+ def __repr__(self) -> str:
21
+ """
22
+ String repr
23
+ """
24
+ return f"<{type(self).__qualname__} integer={repr(self.integer)}>"
25
+
26
+ def get_value(self, ctx: Verb) -> str:
27
+ """
28
+ Get the value of the int into string, not sure why this even exists
29
+ """
30
+ return str(self.integer)
@@ -0,0 +1,41 @@
1
+ from inspect import ismethod
2
+
3
+ from ..interface import Adapter
4
+ from ..verb import Verb
5
+
6
+
7
+ class SafeObjectAdapter(Adapter):
8
+ """
9
+ Object adapter, though again this will likely not be used
10
+ """
11
+
12
+ __slots__ = ("object",)
13
+
14
+ def __init__(self, base) -> None:
15
+ """
16
+ Construct the safe object adapter.
17
+ """
18
+ self.object = base
19
+
20
+ def __repr__(self) -> str:
21
+ """
22
+ String repr"""
23
+ return f"<{type(self).__qualname__} object={repr(self.object)}>"
24
+
25
+ def get_value(self, ctx: Verb) -> str:
26
+ """
27
+ Get the value safely
28
+ """
29
+ if ctx.parameter is None:
30
+ return str(self.object)
31
+ if ctx.parameter.startswith("_") or "." in ctx.parameter:
32
+ return None
33
+ try:
34
+ attribute = getattr(self.object, ctx.parameter)
35
+ except AttributeError:
36
+ return None
37
+ if ismethod(attribute):
38
+ return None
39
+ if isinstance(attribute, float):
40
+ attribute = int(attribute)
41
+ return str(attribute)
@@ -0,0 +1,69 @@
1
+ from ..interface import Adapter
2
+ from ..utils import escape_content
3
+ from ..verb import Verb
4
+
5
+
6
+ class StringAdapter(Adapter):
7
+ """
8
+ String adapter, allows blocks to be parsed, used basically only for variables
9
+ """
10
+
11
+ __slots__ = ("string", "escape_content")
12
+
13
+ def __init__(self, string: str, *, escape: bool = False) -> None:
14
+ """
15
+ Construction for string adapter
16
+ """
17
+ self.string: str = str(string)
18
+ self.escape_content = escape
19
+
20
+ def __repr__(self) -> str:
21
+ """
22
+ String repr
23
+ """
24
+ return f"<{type(self).__qualname__} string={repr(self.string)}>"
25
+
26
+ def get_value(self, ctx: Verb) -> str:
27
+ """
28
+ Get the value given the verb
29
+ """
30
+ return self.return_value(self.handle_ctx(ctx))
31
+
32
+ def handle_ctx(self, ctx: Verb) -> str:
33
+ """
34
+ Transform any parsing data the block may have
35
+ """
36
+ if ctx.parameter is None:
37
+ return self.string
38
+
39
+ try:
40
+ index = None
41
+ splitter = " " if ctx.payload is None else ctx.payload
42
+ if ctx.parameter.isdigit():
43
+ index = int(ctx.parameter) - 1
44
+
45
+ if ctx.parameter.startswith("-") and ctx.parameter.split("-", 1)[-1].isdigit():
46
+ index = int(ctx.parameter)
47
+
48
+ if index is not None:
49
+ return self.string.split(splitter)[index]
50
+
51
+ index = (
52
+ int(ctx.parameter.replace("+", "")) - 1
53
+ if int(ctx.parameter.replace("+", "")) > 0
54
+ else int(ctx.parameter.replace("+", ""))
55
+ )
56
+ splitter = " " if ctx.payload is None else ctx.payload
57
+ if ctx.parameter.startswith("+"):
58
+ return splitter.join(self.string.split(splitter)[: index + 1])
59
+ if ctx.parameter.endswith("+"):
60
+ return splitter.join(self.string.split(splitter)[index:])
61
+ return self.string.split(splitter)[index]
62
+ except: # pylint: disable=bare-except
63
+ return self.string
64
+
65
+ def return_value(self, string: str) -> str:
66
+ """
67
+ Return the value, escaped
68
+ """
69
+ return escape_content(string) if self.escape_content else string
@@ -0,0 +1,67 @@
1
+ # isort: off
2
+ from .helpers import *
3
+
4
+ # isort: on
5
+ from .break_block import BreakBlock
6
+ from .case_block import LowerBlock, UpperBlock
7
+ from .comment_block import CommentBlock
8
+ from .control_block import AllBlock, AnyBlock, IfBlock
9
+ from .counting_blocks import CountBlock, LengthBlock
10
+ from .digitshorthand_block import DigitShorthandBlock
11
+ from .discord_blocks import (
12
+ BlacklistBlock,
13
+ CommandBlock,
14
+ CooldownBlock,
15
+ DeleteBlock,
16
+ EmbedBlock,
17
+ OverrideBlock,
18
+ ReactBlock,
19
+ RedirectBlock,
20
+ RequireBlock,
21
+ )
22
+ from .math_blocks import MathBlock, OrdinalAbbreviationBlock
23
+ from .random_block import RandomBlock
24
+ from .range_block import RangeBlock
25
+ from .replace_block import PythonBlock, ReplaceBlock
26
+ from .stop_block import StopBlock
27
+ from .strf_block import StrfBlock
28
+ from .url_blocks import URLDecodeBlock, URLEncodeBlock
29
+ from .util_blocks.debug_block import DebugBlock
30
+ from .var_block import VarBlock
31
+ from .vargetter_blocks import LooseVariableGetterBlock, StrictVariableGetterBlock
32
+
33
+ __all__ = (
34
+ "BreakBlock",
35
+ "CommentBlock",
36
+ "AllBlock",
37
+ "AnyBlock",
38
+ "IfBlock",
39
+ "CountBlock",
40
+ "LengthBlock",
41
+ "BlacklistBlock",
42
+ "CommandBlock",
43
+ "CooldownBlock",
44
+ "DeleteBlock",
45
+ "EmbedBlock",
46
+ "OverrideBlock",
47
+ "ReactBlock",
48
+ "RedirectBlock",
49
+ "RequireBlock",
50
+ "MathBlock",
51
+ "OrdinalAbbreviationBlock",
52
+ "RandomBlock",
53
+ "RangeBlock",
54
+ "PythonBlock",
55
+ "ReplaceBlock",
56
+ "StopBlock",
57
+ "StrfBlock",
58
+ "URLDecodeBlock",
59
+ "URLEncodeBlock",
60
+ "DebugBlock",
61
+ "VarBlock",
62
+ "LooseVariableGetterBlock",
63
+ "StrictVariableGetterBlock",
64
+ "DigitShorthandBlock",
65
+ "UpperBlock",
66
+ "LowerBlock",
67
+ )
@@ -0,0 +1,41 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import Block
4
+ from ..interpreter import Context
5
+ from . import helper_parse_if
6
+
7
+
8
+ class BreakBlock(Block):
9
+ """
10
+ The break block will force the tag output to only be the payload of this block, if the passed
11
+ expresssion evaluates true.
12
+ If no message is provided to the payload, the tag output will be empty.
13
+
14
+ This differs from the StopBlock as the stop block stops all tagscript processing and returns
15
+ its message while the break block continues to process blocks. If command blocks exist after
16
+ the break block, they will still execute.
17
+
18
+ **Usage:** ``{break(<expression>):[message]}``
19
+
20
+ **Aliases:** ``short, shortcircuit``
21
+
22
+ **Payload:** ``message``
23
+
24
+ **Parameter:** ``expression``
25
+
26
+ **Examples:**
27
+
28
+ .. tagscript::
29
+
30
+ {break(=={args}):You did not provide any input.}
31
+ """
32
+
33
+ ACCEPTED_NAMES = ("break", "shortcircuit", "short")
34
+
35
+ def process(self, ctx: Context) -> Optional[str]:
36
+ """
37
+ Process the block and break the tag.
38
+ """
39
+ if helper_parse_if(ctx.verb.parameter):
40
+ ctx.response.body = ctx.verb.payload if ctx.verb.payload is not None else ""
41
+ return ""
@@ -0,0 +1,62 @@
1
+ from typing import Optional
2
+
3
+ from ..interface import verb_required_block
4
+ from ..interpreter import Context
5
+
6
+
7
+ class UpperBlock(verb_required_block(True, payload=True)):
8
+ """
9
+ The Upper block will do exactly as it and will make the payload uppercase. The
10
+ block alone will not do anything.
11
+
12
+ **Usage:** ``{upper:<string>}``
13
+
14
+ **Aliases:** ``upper``
15
+
16
+ **Payload:** ``string``
17
+
18
+ **Parameter:** ``None``
19
+
20
+ **Examples:**
21
+
22
+ .. tagscript::
23
+
24
+ {upper:I love strawberries!} -> I LOVE STRAWBERRIES!
25
+ """
26
+
27
+ ACCEPTED_NAMES = ("upper",)
28
+
29
+ def process(self, ctx: Context) -> Optional[str]:
30
+ """
31
+ Process the block and break the tag.
32
+ """
33
+ return ctx.verb.payload.upper()
34
+
35
+
36
+ class LowerBlock(verb_required_block(True, payload=True)):
37
+ """
38
+ The Lower block will make the payload lowercase. The block alone will
39
+ not do anything.
40
+
41
+ **Usage:** ``{lower:<string>}``
42
+
43
+ **Aliases:** ``lower``
44
+
45
+ **Payload:** ``string``
46
+
47
+ **Parameter:** ``None``
48
+
49
+ **Examples:**
50
+
51
+ .. tagscript::
52
+
53
+ {lower:I LOVE STRAWBERRIES!} -> i love strawberries!
54
+ """
55
+
56
+ ACCEPTED_NAMES = ("lower",)
57
+
58
+ def process(self, ctx: Context) -> Optional[str]:
59
+ """
60
+ Process the block and break the tag.
61
+ """
62
+ return ctx.verb.payload.lower()