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.
Files changed (48) hide show
  1. TagScriptEngine/__init__.py +227 -0
  2. TagScriptEngine/_warnings.py +88 -0
  3. TagScriptEngine/adapter/__init__.py +50 -0
  4. TagScriptEngine/adapter/discordadapters.py +596 -0
  5. TagScriptEngine/adapter/functionadapter.py +23 -0
  6. TagScriptEngine/adapter/intadapter.py +22 -0
  7. TagScriptEngine/adapter/objectadapter.py +35 -0
  8. TagScriptEngine/adapter/redbotadapters.py +161 -0
  9. TagScriptEngine/adapter/stringadapter.py +47 -0
  10. TagScriptEngine/block/__init__.py +130 -0
  11. TagScriptEngine/block/allowedmentions.py +60 -0
  12. TagScriptEngine/block/assign.py +43 -0
  13. TagScriptEngine/block/breakblock.py +41 -0
  14. TagScriptEngine/block/case.py +63 -0
  15. TagScriptEngine/block/command.py +141 -0
  16. TagScriptEngine/block/comment.py +29 -0
  17. TagScriptEngine/block/control.py +149 -0
  18. TagScriptEngine/block/cooldown.py +95 -0
  19. TagScriptEngine/block/count.py +68 -0
  20. TagScriptEngine/block/embedblock.py +306 -0
  21. TagScriptEngine/block/fiftyfifty.py +34 -0
  22. TagScriptEngine/block/helpers.py +164 -0
  23. TagScriptEngine/block/loosevariablegetter.py +40 -0
  24. TagScriptEngine/block/mathblock.py +164 -0
  25. TagScriptEngine/block/randomblock.py +51 -0
  26. TagScriptEngine/block/range.py +56 -0
  27. TagScriptEngine/block/redirect.py +42 -0
  28. TagScriptEngine/block/replaceblock.py +110 -0
  29. TagScriptEngine/block/require_blacklist.py +79 -0
  30. TagScriptEngine/block/shortcutredirect.py +23 -0
  31. TagScriptEngine/block/stopblock.py +38 -0
  32. TagScriptEngine/block/strf.py +70 -0
  33. TagScriptEngine/block/strictvariablegetter.py +38 -0
  34. TagScriptEngine/block/substr.py +25 -0
  35. TagScriptEngine/block/urlencodeblock.py +41 -0
  36. TagScriptEngine/exceptions.py +105 -0
  37. TagScriptEngine/interface/__init__.py +14 -0
  38. TagScriptEngine/interface/adapter.py +75 -0
  39. TagScriptEngine/interface/block.py +124 -0
  40. TagScriptEngine/interpreter.py +502 -0
  41. TagScriptEngine/py.typed +0 -0
  42. TagScriptEngine/utils.py +71 -0
  43. TagScriptEngine/verb.py +160 -0
  44. advancedtagscript-3.2.3.dist-info/METADATA +99 -0
  45. advancedtagscript-3.2.3.dist-info/RECORD +48 -0
  46. advancedtagscript-3.2.3.dist-info/WHEEL +5 -0
  47. advancedtagscript-3.2.3.dist-info/licenses/LICENSE +1 -0
  48. 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