dshellInterpreter 0.1.0__tar.gz

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.

Potentially problematic release.


This version of dshellInterpreter might be problematic. Click here for more details.

@@ -0,0 +1,2 @@
1
+ from .dshell_channel import *
2
+ from .dshell_message import *
@@ -0,0 +1,60 @@
1
+ from asyncio import sleep
2
+ from re import search
3
+
4
+ from discord import MISSING
5
+ from discord.abc import GuildChannel
6
+
7
+ __all__ = [
8
+ 'dshell_create_text_channel',
9
+ 'dshell_delete_channel',
10
+ 'dshell_delete_channels'
11
+ ]
12
+
13
+ async def dshell_create_text_channel(ctx: GuildChannel, name, category=None, position=MISSING, slowmode=MISSING,
14
+ topic=MISSING, nsfw=MISSING):
15
+ """
16
+ Crée un salon textuel sur le serveur
17
+ """
18
+
19
+ channel_category = ctx.guild.get_channel(category)
20
+
21
+ created_channel = await ctx.guild.create_text_channel(name,
22
+ category=channel_category,
23
+ position=position,
24
+ slowmode_delay=slowmode,
25
+ topic=topic,
26
+ nsfw=nsfw)
27
+
28
+ return created_channel.id
29
+
30
+
31
+ async def dshell_delete_channel(ctx: GuildChannel, channel=None, reason=None, timeout=0):
32
+ """
33
+ Supprime un salon.
34
+ Possibilité de lui rajouter un temps d'attente avant qu'il ne le supprime (en seconde)
35
+ """
36
+
37
+ channel_to_delete = ctx if channel is None else ctx.guild.get_channel(channel)
38
+
39
+ if channel_to_delete is None:
40
+ raise Exception(f"Le channel {channel} n'existe pas !")
41
+
42
+ await sleep(timeout)
43
+
44
+ await channel_to_delete.delete(reason=reason)
45
+
46
+ return channel_to_delete.id
47
+
48
+
49
+ async def dshell_delete_channels(ctx: GuildChannel, name=None, regex=None, reason=None):
50
+ """
51
+ Supprime tous les salons ayant le même nom et/ou le même regex.
52
+ Si aucun des deux n'est mis, il supprimera tous les salons comportant le même nom que celui ou a été fait la commande
53
+ """
54
+ for channel in ctx.guild.channels:
55
+
56
+ if name is not None and channel.name == str(name):
57
+ await channel.delete(reason=reason)
58
+
59
+ elif regex is not None and search(regex, channel.name):
60
+ await channel.delete(reason=reason)
@@ -0,0 +1,61 @@
1
+ from discord import Embed
2
+ from discord.abc import GuildChannel
3
+
4
+ from .._DshellParser.ast_nodes import ListNode
5
+
6
+ __all__ = [
7
+ 'dshell_send_message',
8
+ 'dshell_delete_message',
9
+ 'dshell_purge_message'
10
+ ]
11
+
12
+ async def dshell_send_message(ctx: GuildChannel, message=None, delete=None, channel=None, embeds: ListNode = None,
13
+ embed=None):
14
+ """
15
+ Envoie un message sur Discord
16
+ """
17
+ channel_to_send = ctx if channel is None else ctx.guild.get_channel(channel)
18
+
19
+ if channel_to_send is None:
20
+ raise Exception(f'Le channel {channel} est introuvable !')
21
+
22
+ if embeds is None:
23
+ embeds = ListNode([])
24
+
25
+ elif isinstance(embeds, Embed):
26
+ embeds = ListNode([embeds])
27
+
28
+ if embed is not None and isinstance(embed, Embed):
29
+ embeds.add(embed)
30
+
31
+ sended_message = await channel_to_send.send(message,
32
+ delete_after=delete,
33
+ embeds=embeds)
34
+
35
+ return sended_message.id
36
+
37
+
38
+ async def dshell_delete_message(ctx: GuildChannel, message, reason=None, delay=0):
39
+ """
40
+ Supprime un message
41
+ """
42
+
43
+ delete_message = ctx.get_partial_message(message) # construit une référence au message (même s'il n'existe pas)
44
+
45
+ if delay > 3600:
46
+ raise Exception(f'Le délait de suppression du message est trop grand ! ({delay} secondes)')
47
+
48
+ await delete_message.delete(delay=delay, reason=reason)
49
+
50
+
51
+ async def dshell_purge_message(ctx: GuildChannel, message_number, channel=None, reason=None):
52
+ """
53
+ Purge les messages d'un salon
54
+ """
55
+
56
+ purge_channel = ctx if channel is None else ctx.guild.get_channel(channel)
57
+
58
+ if purge_channel is None:
59
+ raise Exception(f"Le salon {channel} à purgé est introuvable !")
60
+
61
+ await purge_channel.purge(limit=message_number, reason=reason)
@@ -0,0 +1 @@
1
+ from .dshell_interpreter import *
@@ -0,0 +1,267 @@
1
+ from asyncio import sleep
2
+ from re import findall
3
+ from typing import TypeVar, Union, Any, Optional, Callable
4
+
5
+ from discord import AutoShardedBot, Embed, Colour
6
+ from discord.abc import GuildChannel, PrivateChannel
7
+
8
+ from .._DshellTokenizer.dshell_keywords import *
9
+ from .._DshellParser.ast_nodes import *
10
+ from .._DshellParser.dshell_parser import parse
11
+ from .._DshellParser.dshell_parser import to_postfix, print_ast
12
+ from .._DshellTokenizer.dshell_token_type import DshellTokenType as DTT
13
+ from .._DshellTokenizer.dshell_token_type import Token
14
+ from .._DshellTokenizer.dshell_tokenizer import DshellTokenizer
15
+
16
+ All_nodes = TypeVar('All_nodes', IfNode, LoopNode, ElseNode, ElifNode, ArgsCommandNode, VarNode, IdentOperationNode)
17
+ context = TypeVar('context', AutoShardedBot, GuildChannel, PrivateChannel)
18
+
19
+
20
+ class DshellInterpreteur:
21
+
22
+ def __init__(self, ast_or_code: Union[list[All_nodes], str], ctx: context, auto=False, debug: bool = False):
23
+ """
24
+ Interpreter Dshell code or AST.
25
+ """
26
+ self.ast: StartNode = parse(DshellTokenizer(ast_or_code).start(), StartNode([]))[0][0]
27
+ self.env: dict[str, Any] = {}
28
+ self.ctx: context = ctx
29
+ if debug:
30
+ print_ast(self.ast)
31
+
32
+ async def execute(self, ast: Optional[list[All_nodes]] = None):
33
+ """
34
+ Execute l'arbre syntaxique.
35
+ """
36
+
37
+ for node in ast:
38
+
39
+ if isinstance(node, StartNode):
40
+ await self.execute(node.body)
41
+
42
+ if isinstance(node, CommandNode):
43
+ self.env['__cr__'] = await call_function(dshell_commands[node.name], node.body, self)
44
+
45
+ elif isinstance(node, IfNode):
46
+ elif_valid = False
47
+ if eval_expression(node.condition, self):
48
+ await self.execute(node.body)
49
+ return
50
+ elif node.elif_nodes:
51
+
52
+ for i in node.elif_nodes:
53
+ if eval_expression(i.condition, self):
54
+ await self.execute(i.body)
55
+ elif_valid = True
56
+ break
57
+
58
+ if not elif_valid and node.else_body is not None:
59
+ await self.execute(node.else_body.body)
60
+
61
+ elif isinstance(node, LoopNode):
62
+ self.env[node.variable.name.value] = 0
63
+ for i in DshellIterator(eval_expression(node.variable.body, self)):
64
+ self.env[node.variable.name.value] = i
65
+ await self.execute(node.body)
66
+
67
+ elif isinstance(node, VarNode):
68
+
69
+ first_node = node.body[0]
70
+ if isinstance(first_node, IfNode):
71
+ self.env[node.name.value] = eval_expression_inline(first_node, self)
72
+
73
+ elif isinstance(first_node, EmbedNode):
74
+ self.env[node.name.value] = build_embed(first_node.body, first_node.fields, self)
75
+
76
+ else:
77
+ self.env[node.name.value] = eval_expression(node.body, self)
78
+
79
+ elif isinstance(node, IdentOperationNode):
80
+ function = self.eval_data_token(node.function)
81
+ listNode = self.eval_data_token(node.ident)
82
+ if hasattr(listNode, function):
83
+ getattr(listNode, function)(self.eval_data_token(node.args))
84
+
85
+ elif isinstance(node, SleepNode):
86
+ sleep_time = eval_expression(node.body, self)
87
+ if sleep_time > 3600:
88
+ raise Exception(f'Le temps maximal de sommeil est de 3600 secondes !')
89
+ elif sleep_time < 1:
90
+ raise Exception(f'Le temps minimal de sommeil est de 1 seconde !')
91
+
92
+ await sleep(sleep_time)
93
+
94
+ elif isinstance(node, EndNode):
95
+ raise RuntimeError(f"Execution interromput -> #end atteint")
96
+
97
+ def eval_data_token(self, token: Token):
98
+ """
99
+ Evalue les tokens de data
100
+ """
101
+
102
+ if not hasattr(token, 'type'):
103
+ return token
104
+
105
+ if token.type in (DTT.INT, DTT.MENTION):
106
+ return int(token.value)
107
+ elif token.type == DTT.FLOAT:
108
+ return float(token.value)
109
+ elif token.type == DTT.BOOL:
110
+ return token.value.lower() == "true"
111
+ elif token.type == DTT.LIST:
112
+ return ListNode(
113
+ [self.eval_data_token(tok) for tok in token.value]) # token.value contient déjà une liste de Token
114
+ elif token.type == DTT.IDENT:
115
+ if token.value in self.env.keys():
116
+ return self.env[token.value]
117
+ return token.value
118
+ elif token.type == DTT.CALL_ARGS:
119
+ return (self.eval_data_token(tok) for tok in token.value)
120
+ elif token.type == DTT.STR:
121
+ for match in findall(rf"\$({'|'.join(self.env.keys())})", token.value):
122
+ token.value = token.value.replace('$' + match, str(self.env[match]))
123
+ return token.value
124
+ else:
125
+ return token.value # fallback
126
+
127
+
128
+ def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) -> Token:
129
+ """
130
+ Evalue une expression en ligne des variables
131
+ """
132
+ if eval_expression(if_node.condition, interpreter):
133
+ return eval_expression(if_node.body, interpreter)
134
+ else:
135
+ return eval_expression(if_node.else_body.body, interpreter)
136
+
137
+
138
+ def eval_expression(tokens: list[Token], interpreter: DshellInterpreteur) -> Any:
139
+ """
140
+ Evalue une expressions arithmétique et logique et renvoie son résultat. Cela peut-être un booléen, un entier, un flottant, une chaîne de caractère ou une liste
141
+ """
142
+ postfix = to_postfix(tokens)
143
+ stack = []
144
+
145
+ for token in postfix:
146
+
147
+ if token.type in {DTT.INT, DTT.FLOAT, DTT.BOOL, DTT.STR, DTT.LIST, DTT.IDENT}:
148
+ stack.append(interpreter.eval_data_token(token))
149
+
150
+ elif token.type in (DTT.MATHS_OPERATOR, DTT.LOGIC_OPERATOR):
151
+ op = token.value
152
+
153
+ if op == "not":
154
+ a = stack.pop()
155
+ result = dshell_operators[op][0](a)
156
+
157
+ else:
158
+ b = stack.pop()
159
+ a = stack.pop()
160
+ result = dshell_operators[op][0](a, b)
161
+
162
+ stack.append(result)
163
+
164
+ else:
165
+ raise SyntaxError(f"Token inattendu en condition: {token}")
166
+
167
+ if len(stack) != 1:
168
+ raise SyntaxError("Condition mal formée")
169
+
170
+ return stack[0]
171
+
172
+
173
+ async def call_function(function: Callable, args: ArgsCommandNode, interpreter: DshellInterpreteur):
174
+ """
175
+ Appelle une fonction avec évaluation des arguments Dshell en valeurs Python
176
+ """
177
+ reformatted = regroupe_commandes(args.body, interpreter)
178
+
179
+ # conversion des args en valeurs Python
180
+ absolute_args = reformatted.pop('*', list())
181
+
182
+ reformatted: dict[str, Token] # ne sert à rien, juste à indiquer ce qu'il contient dorénanvant
183
+
184
+ absolute_args.insert(0, interpreter.ctx)
185
+ keyword_args = {
186
+ key: value for key, value in reformatted.items()
187
+ }
188
+ return await function(*absolute_args, **keyword_args)
189
+
190
+
191
+ def regroupe_commandes(body: list[Token], interpreter: DshellInterpreteur) -> dict[Union[str, Token], list[Token]]:
192
+ """
193
+ Regroupe les arguments de la commande sous la forme d'un dictionnaire python.
194
+ Sachant que l'on peut spécifier le paramètre que l'on souhaite passer via -- suivit du nom du paramètre. Mais ce n'est pas obligatoire !
195
+ Les paramètres non obligatoire seront stocké dans une liste sous la forme de tokens avec comme clé '*'.
196
+ Les autres ayant été spécifié via un séparateur, ils seront sous la forme d'une liste de tokens avec comme clé le token IDENT qui suivra le séparateur pour chaque argument.
197
+ """
198
+ tokens = {'*': []} # les tokens à renvoyer
199
+ current_arg = '*' # les clés des arguments sont les types auquels ils appartiennent. L'* sert à tous les arguments non explicité par un séparateur et un IDENT
200
+ n = len(body)
201
+
202
+ i = 0
203
+ while i < n:
204
+ if body[i].type == DTT.SEPARATOR and body[
205
+ i + 1].type == DTT.IDENT: # On regarde si c'est un séparateur et si le token suivant est un IDENT
206
+ current_arg = body[i + 1].value # on change l'argument actuel. Il sera donc impossible de revenir à l'*
207
+ tokens[current_arg] = '' # on lui crée une paire clé/valeur
208
+ i += 2 # on skip l'IDENT qu'il y a après le séparateur car on vient de le traiter
209
+ else:
210
+ if current_arg == '*':
211
+ tokens[current_arg].append(interpreter.eval_data_token(body[i]))
212
+ else:
213
+ tokens[current_arg] = interpreter.eval_data_token(body[i]) # on ajoute le token à l'argument actuel
214
+ i += 1
215
+ return tokens
216
+
217
+
218
+ def build_embed(body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> Embed:
219
+ """
220
+ Construit un embed à partir des informations de la commande.
221
+ """
222
+ args_main_embed: dict[Union[str, Token], list[Token]] = regroupe_commandes(body, interpreter)
223
+ args_main_embed.pop('*') # on enlève les paramètres non spécifié pour l'embed
224
+ args_main_embed: dict[str, Token] # on précise se qu'il contient dorénavant
225
+
226
+ args_fields: list[dict[str, Token]] = []
227
+ for field in fields: # on fait la même chose pour tous les fields
228
+ a = regroupe_commandes(field.body, interpreter)
229
+ a.pop('*')
230
+ a: dict[str, Token]
231
+ args_fields.append(a)
232
+
233
+ if 'color' in args_main_embed and isinstance(args_main_embed['color'],
234
+ ListNode): # si on passe l'argument de la couleur sous la forme d'une liste RGB
235
+ args_main_embed['color'] = Colour.from_rgb(*args_main_embed['color'])
236
+
237
+ embed = Embed(**args_main_embed) # on construit l'embed principal
238
+ for field in args_fields:
239
+ embed.add_field(**field) # on joute tous les fields
240
+
241
+ return embed
242
+
243
+
244
+ class DshellIterator:
245
+ """
246
+ Utilisé pour transformer n'importe quoi en un iterable
247
+ """
248
+
249
+ def __init__(self, data):
250
+ if isinstance(data, ListNode):
251
+ self.data = data
252
+ else:
253
+ self.data = data if isinstance(data, (str, list)) else range(int(data))
254
+ self.current = 0
255
+
256
+ def __iter__(self):
257
+ return self
258
+
259
+ def __next__(self):
260
+
261
+ if self.current >= len(self.data):
262
+ self.current = 0
263
+ raise StopIteration
264
+
265
+ value = self.data[self.current]
266
+ self.current += 1
267
+ return value
@@ -0,0 +1,2 @@
1
+ from .ast_nodes import *
2
+ from .dshell_parser import *
@@ -0,0 +1,204 @@
1
+ from typing import Optional, Any
2
+
3
+ from .._DshellTokenizer.dshell_token_type import Token
4
+
5
+ __all__ = [
6
+ 'ASTNode',
7
+ 'StartNode',
8
+ 'ElseNode',
9
+ 'ElifNode',
10
+ 'IfNode',
11
+ 'LoopNode',
12
+ 'ArgsCommandNode',
13
+ 'CommandNode',
14
+ 'VarNode',
15
+ 'EndNode',
16
+ 'FieldEmbedNode',
17
+ 'EmbedNode',
18
+ 'SleepNode',
19
+ 'IdentOperationNode',
20
+ 'ListNode'
21
+ ]
22
+
23
+ class ASTNode:
24
+ pass
25
+
26
+
27
+ class StartNode(ASTNode):
28
+ def __init__(self, body: list):
29
+ self.body = body
30
+
31
+ def __repr__(self):
32
+ return f"<Command> - {self.body}"
33
+
34
+
35
+ class ElseNode(ASTNode):
36
+ def __init__(self, body: list[Token]):
37
+ self.body = body
38
+
39
+ def __repr__(self):
40
+ return f"<Else> - {self.body}"
41
+
42
+
43
+ class ElifNode(ASTNode):
44
+ def __init__(self, condition: list[Token], body: list[Token], parent: "IfNode"):
45
+ self.condition = condition
46
+ self.body = body
47
+ self.parent = parent
48
+
49
+ def __repr__(self):
50
+ return f"<Elif> - {self.condition} - {self.body}"
51
+
52
+
53
+ class IfNode(ASTNode):
54
+ def __init__(self, condition: list[Token], body: list[Token], elif_nodes: Optional[list[ElifNode]] = None,
55
+ else_body: Optional[ElseNode] = None):
56
+ self.condition = condition
57
+ self.body = body
58
+ self.elif_nodes = elif_nodes
59
+ self.else_body = else_body
60
+
61
+ def __repr__(self):
62
+ return f"<If> - {self.condition} - {self.body} *- {self.elif_nodes} **- {self.else_body}"
63
+
64
+
65
+ class LoopNode(ASTNode):
66
+ def __init__(self, variable: "VarNode", body: list):
67
+ self.variable = variable # content l'itérable dans son body
68
+ self.body = body
69
+
70
+ def __repr__(self):
71
+ return f"<Loop> - {self.variable.name} -> {self.variable.body} *- {self.body}"
72
+
73
+
74
+ class ArgsCommandNode(ASTNode):
75
+ def __init__(self, body: list[Token]):
76
+ self.body: list[Token] = body
77
+
78
+ def __repr__(self):
79
+ return f"<Args Command> - {self.body}"
80
+
81
+
82
+ class CommandNode(ASTNode):
83
+ def __init__(self, name: str, body: ArgsCommandNode):
84
+ self.name = name
85
+ self.body = body
86
+
87
+ def __repr__(self):
88
+ return f"<{self.name}> - {self.body}"
89
+
90
+
91
+ class VarNode(ASTNode):
92
+ def __init__(self, name: Token, body: list[Token]):
93
+ self.name = name
94
+ self.body = body
95
+
96
+ def __repr__(self):
97
+ return f"<VAR> - {self.name} *- {self.body}"
98
+
99
+
100
+ class EndNode(ASTNode):
101
+ def __init__(self):
102
+ pass
103
+
104
+ def __repr__(self):
105
+ return f"<END>"
106
+
107
+
108
+ class FieldEmbedNode(ASTNode):
109
+ def __init__(self, body: list[Token]):
110
+ self.body: list[Token] = body
111
+
112
+ def __repr__(self):
113
+ return f"<EMBED_FIELD> - {self.body}"
114
+
115
+
116
+ class EmbedNode(ASTNode):
117
+ def __init__(self, body: list[Token], fields: list[FieldEmbedNode]):
118
+ self.body = body
119
+ self.fields = fields
120
+
121
+ def __repr__(self):
122
+ return f"<EMBED> - {self.body}"
123
+
124
+
125
+ class SleepNode(ASTNode):
126
+ def __init__(self, body: list[Token]):
127
+ self.body = body
128
+
129
+ def __repr__(self):
130
+ return f"<SLEEP> - {self.body}"
131
+
132
+
133
+ class IdentOperationNode(ASTNode):
134
+ """
135
+ Gère les opération sur les idendificateur (appel de fonctions)
136
+ Faire en sorte que l'appel de la fonction renvoie la class associé pour permettre les imbrications. Pas obligatoire en soit si elle renvoie quelque chose
137
+ """
138
+
139
+ def __init__(self, ident: Token, function: Token, args: Token):
140
+ self.ident = ident # content la "class"
141
+ self.function = function # contient la méthode appelé
142
+ self.args = args # contient une liste de tokens des arguments passé en paramètre
143
+
144
+ def __repr__(self):
145
+ return f"<IDENT OPERATION> - {self.ident}.{self.function}({self.args})"
146
+
147
+
148
+ class ListNode(ASTNode):
149
+ """
150
+ Class iterable permettant de parcourir les listes créé à partir du code Dshell.
151
+ Cette class permet aussi d'intéragir avec la liste via des méthodes spécifique non built-in par python.
152
+ """
153
+
154
+ def __init__(self, body: list[Any]):
155
+ self.iterable: list[Any] = body
156
+ self.len_iterable: int = len(body)
157
+ self.iterateur_count: int = 0
158
+
159
+ def add(self, value: Any):
160
+ """
161
+ Ajoute un token à la liste
162
+ """
163
+ if self.len_iterable > 10000:
164
+ raise PermissionError('Une liste ne peut dépasser les 10.000 éléments !')
165
+
166
+ self.iterable.append(value)
167
+ self.len_iterable += 1
168
+
169
+ def remove(self, value: Any, number: int = 1):
170
+ """
171
+ Enlève un ou plusieurs token de la liste
172
+ """
173
+ if number < 1:
174
+ raise Exception(f"Le nombre d'élément à retirer doit-être égale ou supperieur à 1 !")
175
+
176
+ def __add__(self, other: "ListNode"):
177
+ for i in other:
178
+ self.add(i)
179
+ return self
180
+
181
+ def __iter__(self):
182
+ return self
183
+
184
+ def __next__(self):
185
+
186
+ if self.iterateur_count >= self.len_iterable:
187
+ self.iterateur_count = 0
188
+ raise StopIteration()
189
+
190
+ v = self.iterable[self.iterateur_count]
191
+ self.iterateur_count += 1
192
+ return v
193
+
194
+ def __len__(self):
195
+ return self.len_iterable
196
+
197
+ def __getitem__(self, item):
198
+ return self.iterable[item]
199
+
200
+ def __bool__(self):
201
+ return bool(self.iterable)
202
+
203
+ def __repr__(self):
204
+ return f"<LIST> - {self.iterable}"