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,164 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Dict, List, Optional, Tuple
5
+
6
+
7
+ __all__: Tuple[str, ...] = (
8
+ "implicit_bool",
9
+ "helper_parse_if",
10
+ "helper_split",
11
+ "easier_helper_split",
12
+ "helper_parse_list_if",
13
+ )
14
+
15
+ SPLIT_REGEX: re.Pattern[str] = re.compile(r"(?<!\\)\|")
16
+ EASIER_SPLIT_REGEX: re.Pattern[str] = re.compile(r"/[\~;]/")
17
+ BOOL_LOOKUP: Dict[str, bool] = {
18
+ "true": True,
19
+ "false": False,
20
+ "enable": True,
21
+ "disable": False,
22
+ "yes": True,
23
+ "no": False,
24
+ "on": True,
25
+ "off": False,
26
+ "y": True,
27
+ "n": False,
28
+ "t": True,
29
+ "f": False,
30
+ "1": True,
31
+ "0": False,
32
+ }
33
+
34
+
35
+ def implicit_bool(string: str) -> Optional[bool]:
36
+ """
37
+ Parse a string to a boolean.
38
+
39
+ >>> implicit_bool("true")
40
+ True
41
+ >>> implicit_bool("FALSE")
42
+ False
43
+ >>> implicit_bool("abc")
44
+ None
45
+
46
+ Parameters
47
+ ----------
48
+ string: str
49
+ The string to convert.
50
+
51
+ Returns
52
+ -------
53
+ bool
54
+ The boolean value of the string.
55
+ None
56
+ The string failed to parse.
57
+ """
58
+ return BOOL_LOOKUP.get(string.lower())
59
+
60
+
61
+ def helper_parse_if(string: str) -> Optional[bool]:
62
+ """
63
+ Parse an expression string to a boolean.
64
+
65
+ >>> helper_parse_if("this == this")
66
+ True
67
+ >>> helper_parse_if("2>3")
68
+ False
69
+ >>> helper_parse_if("40 >= 40")
70
+ True
71
+ >>> helper_parse_if("False")
72
+ False
73
+ >>> helper_parse_if("1")
74
+ None
75
+
76
+ Parameters
77
+ ----------
78
+ string: str
79
+ The string to convert.
80
+
81
+ Returns
82
+ -------
83
+ bool
84
+ The boolean value of the expression.
85
+ None
86
+ The expression failed to parse.
87
+ """
88
+ value = implicit_bool(string)
89
+ if value is not None:
90
+ return value
91
+ try:
92
+ if "!=" in string:
93
+ spl = string.split("!=")
94
+ return spl[0].strip() != spl[1].strip()
95
+ if "==" in string:
96
+ spl = string.split("==")
97
+ return spl[0].strip() == spl[1].strip()
98
+ if ">=" in string:
99
+ spl = string.split(">=")
100
+ return float(spl[0].strip()) >= float(spl[1].strip())
101
+ if "<=" in string:
102
+ spl = string.split("<=")
103
+ return float(spl[0].strip()) <= float(spl[1].strip())
104
+ if ">" in string:
105
+ spl = string.split(">")
106
+ return float(spl[0].strip()) > float(spl[1].strip())
107
+ if "<" in string:
108
+ spl = string.split("<")
109
+ return float(spl[0].strip()) < float(spl[1].strip())
110
+ except Exception:
111
+ pass
112
+
113
+
114
+ def helper_split(
115
+ split_string: str, easy: bool = True, *, maxsplit: Optional[int] = None
116
+ ) -> Optional[List[str]]:
117
+ """
118
+ A helper method to universalize the splitting logic used in multiple
119
+ blocks and adapters. Please use this wherever a verb needs content to
120
+ be chopped at | , or ~!
121
+
122
+ >>> helper_split("this, should|work")
123
+ ["this, should", "work"]
124
+ """
125
+ args = (maxsplit,) if maxsplit is not None else ()
126
+ if "|" in split_string:
127
+ return SPLIT_REGEX.split(split_string, *args)
128
+ if easy:
129
+ if "~" in split_string:
130
+ return split_string.split("~", *args)
131
+ if "," in split_string:
132
+ return split_string.split(",", *args)
133
+ return
134
+
135
+
136
+ def easier_helper_split(
137
+ split_string: str, *, maxsplit: Optional[int] = None
138
+ ) -> Optional[List[str]]:
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
158
+
159
+
160
+ def helper_parse_list_if(if_string):
161
+ split = helper_split(if_string, False)
162
+ if split is None:
163
+ return [helper_parse_if(if_string)]
164
+ return [helper_parse_if(item) for item in split]
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, Tuple
4
+
5
+ from ..interface import Block
6
+ from ..interpreter import Context
7
+
8
+
9
+ __all__: Tuple[str, ...] = ("LooseVariableGetterBlock",)
10
+
11
+
12
+ class LooseVariableGetterBlock(Block):
13
+ """
14
+ The loose variable block represents the adapters for any seeded or defined variables.
15
+ This variable implementation is considered "loose" since it checks whether the variable is
16
+ valid during :meth:`process`, rather than :meth:`will_accept`.
17
+
18
+ **Usage:** ``{<variable_name>([parameter]):[payload]}``
19
+
20
+ **Aliases:** This block is valid for any inputted declaration.
21
+
22
+ **Payload:** Depends on the variable's underlying adapter.
23
+
24
+ **Parameter:** Depends on the variable's underlying adapter.
25
+
26
+ **Examples:** ::
27
+
28
+ {=(var):This is my variable.}
29
+ {var}
30
+ # This is my variable.
31
+ """
32
+
33
+ def will_accept(self, ctx: Context) -> bool: # type: ignore
34
+ return True
35
+
36
+ def process(self, ctx: Context) -> Optional[str]:
37
+ if ctx.verb.declaration in ctx.response.variables:
38
+ return ctx.response.variables[ctx.verb.declaration].get_value(ctx.verb)
39
+ else:
40
+ return None
@@ -0,0 +1,164 @@
1
+ from __future__ import division, annotations
2
+
3
+ import math
4
+ import operator
5
+ from typing import Any, Callable, Dict, List, Tuple, cast, Optional as TypingOptional
6
+
7
+ from pyparsing import (
8
+ CaselessLiteral,
9
+ Combine,
10
+ Forward,
11
+ Group,
12
+ Literal,
13
+ Optional,
14
+ ParserElement,
15
+ Word,
16
+ ZeroOrMore,
17
+ alphas,
18
+ nums,
19
+ oneOf,
20
+ )
21
+
22
+ from ..interface import Block
23
+ from ..interpreter import Context
24
+
25
+
26
+ __all__: Tuple[str, ...] = ("MathBlock",)
27
+
28
+
29
+ class NumericStringParser(object):
30
+ """
31
+ Most of this code comes from the fourFn.py pyparsing example
32
+
33
+ """
34
+
35
+ def pushFirst(self, strg: Any, loc: Any, toks: Any) -> Any:
36
+ self.exprStack.append(toks[0])
37
+
38
+ def pushUMinus(self, strg: Any, loc: Any, toks: Any) -> Any:
39
+ if toks and toks[0] == "-":
40
+ self.exprStack.append("unary -")
41
+
42
+ def __init__(self) -> None:
43
+ """
44
+ expop :: '^'
45
+ multop :: '*' | '/'
46
+ addop :: '+' | '-'
47
+ integer :: ['+' | '-'] '0'..'9'+
48
+ atom :: PI | E | real | fn '(' expr ')' | '(' expr ')'
49
+ factor :: atom [ expop factor ]*
50
+ term :: factor [ multop factor ]*
51
+ expr :: term [ addop term ]*
52
+ """
53
+ point: Literal = Literal(".")
54
+ e: CaselessLiteral = CaselessLiteral("E")
55
+ fnumber: Combine = Combine(
56
+ Word("+-" + nums, nums)
57
+ + Optional(point + Optional(Word(nums)))
58
+ + Optional(e + Word("+-" + nums, nums))
59
+ )
60
+ ident: Word = Word(alphas, alphas + nums + "_$")
61
+ mod: Literal = Literal("%")
62
+ plus: Literal = Literal("+")
63
+ minus: Literal = Literal("-")
64
+ mult: Literal = Literal("*")
65
+ iadd: Literal = Literal("+=")
66
+ imult: Literal = Literal("*=")
67
+ idiv: Literal = Literal("/=")
68
+ isub: Literal = Literal("-=")
69
+ div: Literal = Literal("/")
70
+ lpar: ParserElement = Literal("(").suppress()
71
+ rpar: ParserElement = Literal(")").suppress()
72
+ addop: ParserElement = plus | minus
73
+ multop: ParserElement = mult | div | mod
74
+ iop: ParserElement = iadd | isub | imult | idiv
75
+ expop: Literal = Literal("^")
76
+ pi: CaselessLiteral = CaselessLiteral("PI")
77
+ expr: Forward = Forward()
78
+ atom: ParserElement = (
79
+ (
80
+ Optional(oneOf("- +"))
81
+ + (ident + lpar + expr + rpar | pi | e | fnumber).setParseAction(self.pushFirst)
82
+ )
83
+ | Optional(oneOf("- +")) + Group(lpar + expr + rpar)
84
+ ).setParseAction(self.pushUMinus)
85
+ # by defining exponentiation as "atom [ ^ factor ]..." instead of
86
+ # "atom [ ^ atom ]...", we get right-to-left exponents, instead of left-to-right
87
+ # that is, 2^3^2 = 2^(3^2), not (2^3)^2.
88
+ factor: Forward = Forward()
89
+ factor << atom + ZeroOrMore((expop + factor).setParseAction(self.pushFirst)) # type: ignore
90
+ term: ParserElement = factor + ZeroOrMore((multop + factor).setParseAction(self.pushFirst))
91
+ expr << term + ZeroOrMore((addop + term).setParseAction(self.pushFirst)) # type: ignore
92
+ final: ParserElement = expr + ZeroOrMore((iop + expr).setParseAction(self.pushFirst))
93
+ # addop_term = ( addop + term ).setParseAction( self.pushFirst )
94
+ # general_term = term + ZeroOrMore( addop_term ) | OneOrMore( addop_term)
95
+ # expr << general_term
96
+ self.bnf: ParserElement = final
97
+ # map operator symbols to corresponding arithmetic operations
98
+ epsilon: float = 1e-12
99
+ self.opn: Dict[str, Callable[[Any, Any], Any]] = {
100
+ "+": operator.add,
101
+ "-": operator.sub,
102
+ "+=": operator.iadd,
103
+ "-=": operator.isub,
104
+ "*": operator.mul,
105
+ "*=": operator.imul,
106
+ "/": operator.truediv,
107
+ "/=": operator.itruediv,
108
+ "^": operator.pow,
109
+ "%": operator.mod,
110
+ }
111
+ self.fn: Dict[str, Any] = {
112
+ "sin": math.sin,
113
+ "cos": math.cos,
114
+ "tan": math.tan,
115
+ "sinh": math.sinh,
116
+ "cosh": math.cosh,
117
+ "tanh": math.tanh,
118
+ "exp": math.exp,
119
+ "abs": abs,
120
+ "trunc": lambda a: int(a),
121
+ "round": round,
122
+ "sgn": lambda a: abs(a) > epsilon and ((a > 0) - (a < 0)) or 0,
123
+ "log": lambda a: math.log(a, 10),
124
+ "ln": math.log,
125
+ "log2": math.log2,
126
+ "sqrt": math.sqrt,
127
+ }
128
+
129
+ def evaluateStack(self, s: List[Any]) -> Any:
130
+ op = s.pop()
131
+ if op == "unary -":
132
+ return -self.evaluateStack(s)
133
+ if op in self.opn:
134
+ op2 = self.evaluateStack(s)
135
+ op1 = self.evaluateStack(s)
136
+ return self.opn[op](op1, op2)
137
+ elif op == "PI":
138
+ return math.pi # 3.1415926535
139
+ elif op == "E":
140
+ return math.e # 2.718281828
141
+ elif op in self.fn:
142
+ return self.fn[op](self.evaluateStack(s))
143
+ elif op[0].isalpha():
144
+ return 0
145
+ else:
146
+ return float(op)
147
+
148
+ def eval(self, num_string: str, parseAll: bool = True) -> Any:
149
+ self.exprStack = []
150
+ results = self.bnf.parseString(num_string, parseAll) # noqa: F841
151
+ return self.evaluateStack(self.exprStack[:])
152
+
153
+
154
+ NSP: NumericStringParser = NumericStringParser()
155
+
156
+
157
+ class MathBlock(Block):
158
+ ACCEPTED_NAMES: Tuple[str, ...] = ("math", "m", "+", "calc")
159
+
160
+ def process(self, ctx: Context) -> TypingOptional[str]:
161
+ try:
162
+ return str(NSP.eval(cast(str, ctx.verb.payload).strip(" ")))
163
+ except Exception:
164
+ return None
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from typing import Optional, Tuple, cast
5
+
6
+ from ..interface import verb_required_block
7
+ from ..interpreter import Context
8
+
9
+
10
+ __all__: Tuple[str, ...] = ("RandomBlock",)
11
+
12
+
13
+ class RandomBlock(verb_required_block(True, payload=True)): # type: ignore
14
+ """
15
+ Pick a random item from a list of strings, split by either ``~``
16
+ or ``,``. An optional seed can be provided to the parameter to
17
+ always choose the same item when using that seed.
18
+
19
+ **Usage:** ``{random([seed]):<list>}``
20
+
21
+ **Aliases:** ``#, rand``
22
+
23
+ **Payload:** list
24
+
25
+ **Parameter:** seed, None
26
+
27
+ **Examples:** ::
28
+
29
+ {random:Carl,Harold,Josh} attempts to pick the lock!
30
+ # Possible Outputs:
31
+ # Josh attempts to pick the lock!
32
+ # Carl attempts to pick the lock!
33
+ # Harold attempts to pick the lock!
34
+
35
+ {=(insults):You're so ugly that you went to the salon and it took 3 hours just to get an estimate.~I'll never forget the first time we met, although I'll keep trying.~You look like a before picture.}
36
+ {=(insult):{#:{insults}}}
37
+ {insult}
38
+ # Assigns a random insult to the insult variable
39
+ """
40
+
41
+ ACCEPTED_NAMES: Tuple[str, ...] = ("random", "#", "rand")
42
+
43
+ def process(self, ctx: Context) -> Optional[str]:
44
+ spl = []
45
+ if "~" in (payload := cast(str, ctx.verb.payload)):
46
+ spl = payload.split("~")
47
+ else:
48
+ spl = payload.split(",")
49
+ random.seed(ctx.verb.parameter)
50
+
51
+ return random.choice(spl)
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from typing import Optional, Tuple, cast
5
+
6
+ from ..interface import verb_required_block
7
+ from ..interpreter import Context
8
+
9
+
10
+ __all__: Tuple[str, ...] = ("RangeBlock",)
11
+
12
+
13
+ class RangeBlock(verb_required_block(True, payload=True)): # type: ignore
14
+ """
15
+ The range block picks a random number from a range of numbers seperated by ``-``.
16
+ The number range is inclusive, so it can pick the starting/ending number as well.
17
+ Using the rangef block will pick a number to the tenth decimal place.
18
+
19
+ An optional seed can be provided to the parameter to always choose the same item when using that seed.
20
+
21
+ **Usage:** ``{range([seed]):<lowest-highest>}``
22
+
23
+ **Aliases:** ``rangef``
24
+
25
+ **Payload:** number
26
+
27
+ **Parameter:** seed, None
28
+
29
+ **Examples:** ::
30
+
31
+ Your lucky number is {range:10-30}!
32
+ # Your lucky number is 14!
33
+ # Your lucky number is 25!
34
+
35
+ {=(height):{rangef:5-7}}
36
+ I am guessing your height is {height}ft.
37
+ # I am guessing your height is 5.3ft.
38
+ """
39
+
40
+ ACCEPTED_NAMES: Tuple[str, ...] = ("rangef", "range")
41
+
42
+ def process(self, ctx: Context) -> Optional[str]:
43
+ try:
44
+ spl = cast(str, ctx.verb.payload).split("-")
45
+ random.seed(ctx.verb.parameter)
46
+ if cast(str, ctx.verb.declaration).lower() == "rangef":
47
+ lower: float = float(spl[0])
48
+ upper: float = float(spl[1])
49
+ base: float = random.randint(int(lower) * 10, int(upper) * 10) / 10
50
+ return str(base)
51
+ else:
52
+ lower = int(float(spl[0]))
53
+ upper = int(float(spl[1]))
54
+ return str(random.randint(lower, upper))
55
+ except Exception:
56
+ return None
@@ -0,0 +1,42 @@
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, ...] = ("RedirectBlock",)
10
+
11
+
12
+ class RedirectBlock(verb_required_block(True, parameter=True)): # type: ignore
13
+ """
14
+ Redirects the tag response to either the given channel, the author's DMs,
15
+ or uses a reply based on what is passed to the parameter.
16
+
17
+ **Usage:** ``{redirect(<"dm"|"reply"|channel>)}``
18
+
19
+ **Payload:** None
20
+
21
+ **Parameter:** "dm", "reply", channel
22
+
23
+ **Examples:** ::
24
+
25
+ {redirect(dm)}
26
+ {redirect(reply)}
27
+ {redirect(#general)}
28
+ {redirect(626861902521434160)}
29
+ """
30
+
31
+ ACCEPTED_NAMES = ("redirect",)
32
+
33
+ def process(self, ctx: Context) -> Optional[str]:
34
+ param: str = cast(str, ctx.verb.parameter).strip()
35
+ if param.lower() == "dm":
36
+ target: str = "dm"
37
+ elif param.lower() == "reply":
38
+ target: str = "reply"
39
+ else:
40
+ target = param
41
+ ctx.response.actions["target"] = target
42
+ return ""
@@ -0,0 +1,110 @@
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, ...] = ("ReplaceBlock", "PythonBlock")
10
+
11
+
12
+ class ReplaceBlock(verb_required_block(True, payload=True, parameter=True)): # type: ignore
13
+ """
14
+ The replace block will replace specific characters in a string.
15
+ The parameter should split by a ``,``, containing the characters to find
16
+ before the command and the replacements after.
17
+
18
+ **Usage:** ``{replace(<original,new>):<message>}``
19
+
20
+ **Aliases:** ``None``
21
+
22
+ **Payload:** message
23
+
24
+ **Parameter:** original, new
25
+
26
+ **Examples:** ::
27
+
28
+ {replace(o,i):welcome to the server}
29
+ # welcime ti the server
30
+
31
+ {replace(1,6):{args}}
32
+ # if {args} is 1637812
33
+ # 6637862
34
+
35
+ {replace(, ):Test}
36
+ # T e s t
37
+ """
38
+
39
+ ACCEPTED_NAMES: Tuple[str, ...] = ("replace",)
40
+
41
+ def process(self, ctx: Context) -> Optional[str]:
42
+ try:
43
+ before, after = cast(str, ctx.verb.parameter).split(",", 1)
44
+ except ValueError:
45
+ return
46
+
47
+ return cast(str, ctx.verb.payload).replace(before, after)
48
+
49
+
50
+ class PythonBlock(verb_required_block(True, payload=True, parameter=True)): # type: ignore
51
+ """
52
+ The in block serves three different purposes depending on the alias that is used.
53
+
54
+ The ``in`` alias checks if the parameter is anywhere in the payload.
55
+
56
+ ``contain`` strictly checks if the parameter is the payload, split by whitespace.
57
+
58
+ ``index`` finds the location of the parameter in the payload, split by whitespace.
59
+ If the parameter string is not found in the payload, it returns 1.
60
+
61
+ index is used to return the value of the string form the given list of
62
+
63
+ **Usage:** ``{in(<string>):<payload>}``
64
+
65
+ **Aliases:** ``index``, ``contains``
66
+
67
+ **Payload:** payload
68
+
69
+ **Parameter:** string
70
+
71
+ **Examples:** ::
72
+
73
+ {in(apple pie):banana pie apple pie and other pie}
74
+ # true
75
+ {in(mute):How does it feel to be muted?}
76
+ # true
77
+ {in(a):How does it feel to be muted?}
78
+ # false
79
+
80
+ {contains(mute):How does it feel to be muted?}
81
+ # false
82
+ {contains(muted?):How does it feel to be muted?}
83
+ # false
84
+
85
+ {index(food):I love to eat food. everyone does.}
86
+ # 4
87
+ {index(pie):I love to eat food. everyone does.}
88
+ # -1
89
+ """
90
+
91
+ def will_accept(self, ctx: Context) -> bool: # type: ignore
92
+ dec = cast(str, ctx.verb.declaration).lower()
93
+ return dec in ("contains", "in", "index")
94
+
95
+ def process(self, ctx: Context) -> str:
96
+ dec: str = cast(str, ctx.verb.declaration).lower()
97
+ if dec == "contains":
98
+ return str(bool(ctx.verb.parameter in cast(str, ctx.verb.payload).split())).lower()
99
+ elif dec == "in":
100
+ return str(bool(cast(str, ctx.verb.parameter) in cast(str, ctx.verb.payload))).lower()
101
+ else:
102
+ try:
103
+ return str(
104
+ cast(str, ctx.verb.payload)
105
+ .strip()
106
+ .split()
107
+ .index(cast(str, ctx.verb.parameter))
108
+ )
109
+ except ValueError:
110
+ return "-1"