dshellInterpreter 0.1.10__py3-none-any.whl → 0.1.12__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.
Potentially problematic release.
This version of dshellInterpreter might be problematic. Click here for more details.
- Dshell/DISCORD_COMMANDS/__init__.py +1 -0
- Dshell/DISCORD_COMMANDS/dshell_channel.py +44 -16
- Dshell/DISCORD_COMMANDS/dshell_member.py +67 -0
- Dshell/DISCORD_COMMANDS/dshell_message.py +68 -15
- Dshell/_DshellInterpreteur/dshell_interpreter.py +91 -66
- Dshell/_DshellParser/ast_nodes.py +115 -12
- Dshell/_DshellParser/dshell_parser.py +3 -1
- Dshell/_DshellTokenizer/dshell_keywords.py +13 -8
- Dshell/_DshellTokenizer/dshell_token_type.py +1 -1
- Dshell/_DshellTokenizer/dshell_tokenizer.py +3 -2
- {dshellinterpreter-0.1.10.dist-info → dshellinterpreter-0.1.12.dist-info}/METADATA +1 -1
- dshellinterpreter-0.1.12.dist-info/RECORD +19 -0
- dshellinterpreter-0.1.10.dist-info/RECORD +0 -18
- {dshellinterpreter-0.1.10.dist-info → dshellinterpreter-0.1.12.dist-info}/WHEEL +0 -0
- {dshellinterpreter-0.1.10.dist-info → dshellinterpreter-0.1.12.dist-info}/licenses/LICENSE +0 -0
- {dshellinterpreter-0.1.10.dist-info → dshellinterpreter-0.1.12.dist-info}/top_level.txt +0 -0
|
@@ -2,24 +2,29 @@ from asyncio import sleep
|
|
|
2
2
|
from re import search
|
|
3
3
|
from typing import Union
|
|
4
4
|
|
|
5
|
-
from discord import MISSING, PermissionOverwrite, Member, Role
|
|
6
|
-
from discord.abc import GuildChannel
|
|
5
|
+
from discord import MISSING, PermissionOverwrite, Member, Role, Message
|
|
7
6
|
|
|
8
7
|
__all__ = [
|
|
9
8
|
'dshell_create_text_channel',
|
|
10
9
|
'dshell_delete_channel',
|
|
11
|
-
'dshell_delete_channels'
|
|
10
|
+
'dshell_delete_channels',
|
|
11
|
+
'dshell_create_voice_channel'
|
|
12
12
|
]
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
async def dshell_create_text_channel(ctx:
|
|
15
|
+
async def dshell_create_text_channel(ctx: Message,
|
|
16
|
+
name,
|
|
17
|
+
category=None,
|
|
18
|
+
position=MISSING,
|
|
19
|
+
slowmode=MISSING,
|
|
16
20
|
topic=MISSING, nsfw=MISSING,
|
|
17
|
-
permission: dict[Union[Member, Role], PermissionOverwrite] = MISSING
|
|
21
|
+
permission: dict[Union[Member, Role], PermissionOverwrite] = MISSING,
|
|
22
|
+
reason=None):
|
|
18
23
|
"""
|
|
19
|
-
|
|
24
|
+
Creates a text channel on the server
|
|
20
25
|
"""
|
|
21
26
|
|
|
22
|
-
channel_category = ctx.guild.get_channel(category)
|
|
27
|
+
channel_category = ctx.channel.guild.get_channel(category)
|
|
23
28
|
|
|
24
29
|
created_channel = await ctx.guild.create_text_channel(name,
|
|
25
30
|
category=channel_category,
|
|
@@ -27,18 +32,41 @@ async def dshell_create_text_channel(ctx: GuildChannel, name, category=None, pos
|
|
|
27
32
|
slowmode_delay=slowmode,
|
|
28
33
|
topic=topic,
|
|
29
34
|
nsfw=nsfw,
|
|
30
|
-
overwrites=permission
|
|
35
|
+
overwrites=permission,
|
|
36
|
+
reason=reason)
|
|
37
|
+
|
|
38
|
+
return created_channel.id
|
|
39
|
+
|
|
40
|
+
async def dshell_create_voice_channel(ctx: Message,
|
|
41
|
+
name,
|
|
42
|
+
category=None,
|
|
43
|
+
position=MISSING,
|
|
44
|
+
bitrate=MISSING,
|
|
45
|
+
permission: dict[Union[Member, Role], PermissionOverwrite] = MISSING,
|
|
46
|
+
reason=None):
|
|
47
|
+
"""
|
|
48
|
+
Creates a voice channel on the server
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
channel_category = ctx.channel.guild.get_channel(category)
|
|
52
|
+
|
|
53
|
+
created_channel = await ctx.guild.create_voice_channel(name,
|
|
54
|
+
category=channel_category,
|
|
55
|
+
position=position,
|
|
56
|
+
bitrate=bitrate,
|
|
57
|
+
overwrites=permission,
|
|
58
|
+
reason=reason)
|
|
31
59
|
|
|
32
60
|
return created_channel.id
|
|
33
61
|
|
|
34
62
|
|
|
35
|
-
async def dshell_delete_channel(ctx:
|
|
63
|
+
async def dshell_delete_channel(ctx: Message, channel=None, reason=None, timeout=0):
|
|
36
64
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
Deletes a channel.
|
|
66
|
+
You can add a waiting time before it is deleted (in seconds)
|
|
39
67
|
"""
|
|
40
68
|
|
|
41
|
-
channel_to_delete = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
69
|
+
channel_to_delete = ctx.channel if channel is None else ctx.channel.guild.get_channel(channel)
|
|
42
70
|
|
|
43
71
|
if channel_to_delete is None:
|
|
44
72
|
raise Exception(f"Le channel {channel} n'existe pas !")
|
|
@@ -50,12 +78,12 @@ async def dshell_delete_channel(ctx: GuildChannel, channel=None, reason=None, ti
|
|
|
50
78
|
return channel_to_delete.id
|
|
51
79
|
|
|
52
80
|
|
|
53
|
-
async def dshell_delete_channels(ctx:
|
|
81
|
+
async def dshell_delete_channels(ctx: Message, name=None, regex=None, reason=None):
|
|
54
82
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
83
|
+
Deletes all channels with the same name and/or matching the same regex.
|
|
84
|
+
If neither is set, it will delete all channels with the same name as the one where the command was executed.
|
|
57
85
|
"""
|
|
58
|
-
for channel in ctx.guild.channels:
|
|
86
|
+
for channel in ctx.channel.guild.channels:
|
|
59
87
|
|
|
60
88
|
if name is not None and channel.name == str(name):
|
|
61
89
|
await channel.delete(reason=reason)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from discord import MISSING, Message
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"dshell_ban_member",
|
|
6
|
+
"dshell_unban_member",
|
|
7
|
+
"dshell_kick_member",
|
|
8
|
+
"dshell_rename_member"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
async def dshell_ban_member(ctx: Message, member: int, reason: str = MISSING):
|
|
12
|
+
"""
|
|
13
|
+
Bans a member from the server.
|
|
14
|
+
"""
|
|
15
|
+
banned_member = ctx.channel.guild.get_member(member)
|
|
16
|
+
|
|
17
|
+
if not banned_member:
|
|
18
|
+
return 1 # Member not found in the server
|
|
19
|
+
|
|
20
|
+
await ctx.channel.guild.ban(banned_member, reason=reason)
|
|
21
|
+
|
|
22
|
+
return banned_member.id
|
|
23
|
+
|
|
24
|
+
async def dshell_unban_member(ctx: Message, user: int, reason: str = MISSING):
|
|
25
|
+
"""
|
|
26
|
+
Unbans a user from the server.
|
|
27
|
+
"""
|
|
28
|
+
banned_users = ctx.channel.guild.bans()
|
|
29
|
+
user_to_unban = None
|
|
30
|
+
|
|
31
|
+
async for ban_entry in banned_users:
|
|
32
|
+
if ban_entry.user.id == user:
|
|
33
|
+
user_to_unban = ban_entry.user
|
|
34
|
+
break
|
|
35
|
+
|
|
36
|
+
if not user_to_unban:
|
|
37
|
+
return 1 # User not found in the banned list
|
|
38
|
+
|
|
39
|
+
await ctx.channel.guild.unban(user_to_unban, reason=reason)
|
|
40
|
+
|
|
41
|
+
return user_to_unban.id
|
|
42
|
+
|
|
43
|
+
async def dshell_kick_member(ctx: Message, member: int, reason: str = MISSING):
|
|
44
|
+
"""
|
|
45
|
+
Kicks a member from the server.
|
|
46
|
+
"""
|
|
47
|
+
kicked_member = ctx.channel.guild.get_member(member)
|
|
48
|
+
|
|
49
|
+
if not kicked_member:
|
|
50
|
+
return 1 # Member not found in the server
|
|
51
|
+
|
|
52
|
+
await ctx.channel.guild.kick(kicked_member, reason=reason)
|
|
53
|
+
|
|
54
|
+
return kicked_member.id
|
|
55
|
+
|
|
56
|
+
async def dshell_rename_member(ctx: Message, new_name, member=None):
|
|
57
|
+
"""
|
|
58
|
+
Renames a member in the server.
|
|
59
|
+
"""
|
|
60
|
+
renamed_member = ctx.channel.guild.get_member(member)
|
|
61
|
+
|
|
62
|
+
if not renamed_member:
|
|
63
|
+
return 1 # Member not found in the server
|
|
64
|
+
|
|
65
|
+
await renamed_member.edit(nick=new_name)
|
|
66
|
+
|
|
67
|
+
return renamed_member.id
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
from discord import Embed
|
|
2
|
-
from discord.
|
|
1
|
+
from discord import Embed, Message
|
|
2
|
+
from discord.ext import commands
|
|
3
|
+
from .._DshellParser.ast_nodes import ListNode
|
|
4
|
+
from re import search
|
|
3
5
|
|
|
4
6
|
__all__ = [
|
|
5
7
|
'dshell_send_message',
|
|
6
8
|
'dshell_delete_message',
|
|
7
|
-
'dshell_purge_message'
|
|
9
|
+
'dshell_purge_message',
|
|
10
|
+
'dshell_edit_message',
|
|
11
|
+
'dshell_research_regex_message',
|
|
12
|
+
'dshell_research_regex_in_content'
|
|
8
13
|
]
|
|
9
14
|
|
|
10
15
|
|
|
11
|
-
async def dshell_send_message(ctx:
|
|
16
|
+
async def dshell_send_message(ctx: Message, message=None, delete=None, channel=None, embeds=None, embed=None):
|
|
12
17
|
from .._DshellParser.ast_nodes import ListNode
|
|
13
18
|
"""
|
|
14
|
-
|
|
19
|
+
Sends a message on Discord
|
|
15
20
|
"""
|
|
16
|
-
channel_to_send = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
21
|
+
channel_to_send = ctx.channel if channel is None else ctx.channel.guild.get_channel(channel)
|
|
17
22
|
|
|
18
23
|
if channel_to_send is None:
|
|
19
|
-
raise Exception(f'
|
|
24
|
+
raise Exception(f'Channel {channel} not found!')
|
|
20
25
|
|
|
21
26
|
if embeds is None:
|
|
22
27
|
embeds = ListNode([])
|
|
@@ -34,27 +39,75 @@ async def dshell_send_message(ctx: GuildChannel, message=None, delete=None, chan
|
|
|
34
39
|
return sended_message.id
|
|
35
40
|
|
|
36
41
|
|
|
37
|
-
async def dshell_delete_message(ctx:
|
|
42
|
+
async def dshell_delete_message(ctx: Message, message, reason=None, delay=0):
|
|
38
43
|
"""
|
|
39
|
-
|
|
44
|
+
Deletes a message
|
|
40
45
|
"""
|
|
41
46
|
|
|
42
|
-
delete_message = ctx.get_partial_message(message) #
|
|
47
|
+
delete_message = ctx.channel.get_partial_message(message) # builds a reference to the message (even if it doesn't exist)
|
|
43
48
|
|
|
44
49
|
if delay > 3600:
|
|
45
|
-
raise Exception(f'
|
|
50
|
+
raise Exception(f'The message deletion delay is too long! ({delay} seconds)')
|
|
46
51
|
|
|
47
52
|
await delete_message.delete(delay=delay, reason=reason)
|
|
48
53
|
|
|
49
54
|
|
|
50
|
-
async def dshell_purge_message(ctx:
|
|
55
|
+
async def dshell_purge_message(ctx: Message, message_number, channel=None, reason=None):
|
|
51
56
|
"""
|
|
52
|
-
|
|
57
|
+
Purges messages from a channel
|
|
53
58
|
"""
|
|
54
59
|
|
|
55
|
-
purge_channel = ctx if channel is None else ctx.guild.get_channel(channel)
|
|
60
|
+
purge_channel = ctx.channel if channel is None else ctx.channel.guild.get_channel(channel)
|
|
56
61
|
|
|
57
62
|
if purge_channel is None:
|
|
58
|
-
raise Exception(f"
|
|
63
|
+
raise Exception(f"Channel {channel} to purge not found!")
|
|
59
64
|
|
|
60
65
|
await purge_channel.purge(limit=message_number, reason=reason)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def dshell_edit_message(ctx: Message, message, new_content=None, embeds=None):
|
|
69
|
+
"""
|
|
70
|
+
Edits a message
|
|
71
|
+
"""
|
|
72
|
+
edit_message = ctx.channel.get_partial_message(message) # builds a reference to the message (even if it doesn't exist)
|
|
73
|
+
|
|
74
|
+
if embeds is None:
|
|
75
|
+
embeds = []
|
|
76
|
+
|
|
77
|
+
elif isinstance(embeds, Embed):
|
|
78
|
+
embeds = [embeds]
|
|
79
|
+
|
|
80
|
+
await edit_message.edit(content=new_content, embeds=embeds)
|
|
81
|
+
|
|
82
|
+
return edit_message.id
|
|
83
|
+
|
|
84
|
+
async def dshell_research_regex_message(ctx: Message, regex, channel=None):
|
|
85
|
+
"""
|
|
86
|
+
Searches for messages matching a regex in a channel
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
search_channel = ctx.channel if channel is None else ctx.channel.guild.get_channel(channel)
|
|
90
|
+
|
|
91
|
+
if search_channel is None:
|
|
92
|
+
raise Exception(f"Channel {channel} to search not found!")
|
|
93
|
+
|
|
94
|
+
messages = ListNode([])
|
|
95
|
+
async for message in search_channel.history(limit=100):
|
|
96
|
+
if search(regex, message.content):
|
|
97
|
+
messages.add(message)
|
|
98
|
+
|
|
99
|
+
if not messages:
|
|
100
|
+
raise commands.CommandError(f"No messages found matching the regex '{regex}'.")
|
|
101
|
+
|
|
102
|
+
return messages
|
|
103
|
+
|
|
104
|
+
async def dshell_research_regex_in_content(ctx: Message, regex, content):
|
|
105
|
+
"""
|
|
106
|
+
Searches for a regex in a specific message content
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
if not search(regex, content):
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
return False
|
|
113
|
+
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from asyncio import sleep
|
|
2
2
|
from re import findall
|
|
3
3
|
from typing import TypeVar, Union, Any, Optional, Callable
|
|
4
|
-
from logging import Logger
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
from discord import AutoShardedBot, Embed, Colour, PermissionOverwrite, Permissions, Guild, Member, Role
|
|
6
|
+
from discord import AutoShardedBot, Embed, Colour, PermissionOverwrite, Permissions, Guild, Member, Role, Message
|
|
8
7
|
from discord.abc import GuildChannel, PrivateChannel
|
|
9
8
|
|
|
10
9
|
from .._DshellParser.ast_nodes import *
|
|
@@ -16,16 +15,21 @@ from .._DshellTokenizer.dshell_token_type import Token
|
|
|
16
15
|
from .._DshellTokenizer.dshell_tokenizer import DshellTokenizer
|
|
17
16
|
|
|
18
17
|
All_nodes = TypeVar('All_nodes', IfNode, LoopNode, ElseNode, ElifNode, ArgsCommandNode, VarNode, IdentOperationNode)
|
|
19
|
-
context = TypeVar('context', AutoShardedBot,
|
|
18
|
+
context = TypeVar('context', AutoShardedBot, Message, PrivateChannel)
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class DshellInterpreteur:
|
|
22
|
+
"""
|
|
23
|
+
Discord Dshell interpreter.
|
|
24
|
+
Make what you want with Dshell code to interact with Discord !
|
|
25
|
+
"""
|
|
23
26
|
|
|
24
|
-
def __init__(self,
|
|
27
|
+
def __init__(self, code: str, ctx: context, debug: bool = False):
|
|
25
28
|
"""
|
|
26
|
-
Interpreter Dshell code
|
|
29
|
+
Interpreter Dshell code
|
|
30
|
+
:param code: The code to interpret. Each line must end with a newline character, except SEPARATOR and SUB_SEPARATOR tokens.
|
|
27
31
|
"""
|
|
28
|
-
self.ast: list[ASTNode] = parse(DshellTokenizer(
|
|
32
|
+
self.ast: list[ASTNode] = parse(DshellTokenizer(code).start(), StartNode([]))[0]
|
|
29
33
|
self.env: dict[str, Any] = {}
|
|
30
34
|
self.ctx: context = ctx
|
|
31
35
|
if debug:
|
|
@@ -33,7 +37,16 @@ class DshellInterpreteur:
|
|
|
33
37
|
|
|
34
38
|
async def execute(self, ast: Optional[list[All_nodes]] = None):
|
|
35
39
|
"""
|
|
36
|
-
|
|
40
|
+
Executes the abstract syntax tree (AST) generated from the Dshell code.
|
|
41
|
+
|
|
42
|
+
This asynchronous method traverses and interprets each node in the AST, executing commands,
|
|
43
|
+
handling control flow structures (such as if, elif, else, and loops), managing variables,
|
|
44
|
+
and interacting with Discord through the provided context. It supports command execution,
|
|
45
|
+
variable assignment, sleep operations, and permission handling, among other features.
|
|
46
|
+
|
|
47
|
+
:param ast: Optional list of AST nodes to execute. If None, uses the interpreter's main AST.
|
|
48
|
+
:raises RuntimeError: If an EndNode is encountered, indicating execution should be stopped.
|
|
49
|
+
:raises Exception: If sleep duration is out of allowed bounds.
|
|
37
50
|
"""
|
|
38
51
|
if ast is None:
|
|
39
52
|
ast = self.ast
|
|
@@ -92,19 +105,20 @@ class DshellInterpreteur:
|
|
|
92
105
|
elif isinstance(node, SleepNode):
|
|
93
106
|
sleep_time = eval_expression(node.body, self)
|
|
94
107
|
if sleep_time > 3600:
|
|
95
|
-
raise Exception(f
|
|
108
|
+
raise Exception(f"Sleep time is too long! ({sleep_time} seconds) - maximum is 3600 seconds)")
|
|
96
109
|
elif sleep_time < 1:
|
|
97
|
-
raise Exception(f
|
|
110
|
+
raise Exception(f"Sleep time is too short! ({sleep_time} seconds) - minimum is 1 second)")
|
|
98
111
|
|
|
99
112
|
await sleep(sleep_time)
|
|
100
113
|
|
|
101
114
|
|
|
102
115
|
elif isinstance(node, EndNode):
|
|
103
|
-
raise RuntimeError(
|
|
116
|
+
raise RuntimeError("Execution stopped - EndNode encountered")
|
|
104
117
|
|
|
105
118
|
def eval_data_token(self, token: Token):
|
|
106
119
|
"""
|
|
107
|
-
|
|
120
|
+
Eval a data token and returns its value in Python.
|
|
121
|
+
:param token: The token to evaluate.
|
|
108
122
|
"""
|
|
109
123
|
|
|
110
124
|
if not hasattr(token, 'type'):
|
|
@@ -137,7 +151,9 @@ class DshellInterpreteur:
|
|
|
137
151
|
|
|
138
152
|
def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) -> Token:
|
|
139
153
|
"""
|
|
140
|
-
|
|
154
|
+
Eval a conditional expression inline.
|
|
155
|
+
:param if_node: The IfNode to evaluate.
|
|
156
|
+
:param interpreter: The Dshell interpreter instance.
|
|
141
157
|
"""
|
|
142
158
|
if eval_expression(if_node.condition, interpreter):
|
|
143
159
|
return eval_expression(if_node.body, interpreter)
|
|
@@ -147,7 +163,9 @@ def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) ->
|
|
|
147
163
|
|
|
148
164
|
def eval_expression(tokens: list[Token], interpreter: DshellInterpreteur) -> Any:
|
|
149
165
|
"""
|
|
150
|
-
|
|
166
|
+
Evaluates an arithmetic and logical expression.
|
|
167
|
+
:param tokens: A list of tokens representing the expression.
|
|
168
|
+
:param interpreter: The Dshell interpreter instance.
|
|
151
169
|
"""
|
|
152
170
|
postfix = to_postfix(tokens)
|
|
153
171
|
stack = []
|
|
@@ -172,24 +190,28 @@ def eval_expression(tokens: list[Token], interpreter: DshellInterpreteur) -> Any
|
|
|
172
190
|
stack.append(result)
|
|
173
191
|
|
|
174
192
|
else:
|
|
175
|
-
raise SyntaxError(f"
|
|
193
|
+
raise SyntaxError(f"Unexpected token type: {token.type} - {token.value}")
|
|
176
194
|
|
|
177
195
|
if len(stack) != 1:
|
|
178
|
-
raise SyntaxError("
|
|
196
|
+
raise SyntaxError("Invalid expression: stack should contain exactly one element after evaluation.")
|
|
179
197
|
|
|
180
198
|
return stack[0]
|
|
181
199
|
|
|
182
200
|
|
|
183
201
|
async def call_function(function: Callable, args: ArgsCommandNode, interpreter: DshellInterpreteur):
|
|
184
202
|
"""
|
|
185
|
-
|
|
203
|
+
Call the function with the given arguments.
|
|
204
|
+
It can be an async function !
|
|
205
|
+
:param function: The function to call.
|
|
206
|
+
:param args: The arguments to pass to the function.
|
|
207
|
+
:param interpreter: The Dshell interpreter instance.
|
|
186
208
|
"""
|
|
187
209
|
reformatted = regroupe_commandes(args.body, interpreter)[0]
|
|
188
210
|
|
|
189
211
|
# conversion des args en valeurs Python
|
|
190
212
|
absolute_args = reformatted.pop('*', list())
|
|
191
213
|
|
|
192
|
-
reformatted: dict[str, Token]
|
|
214
|
+
reformatted: dict[str, Token]
|
|
193
215
|
|
|
194
216
|
absolute_args.insert(0, interpreter.ctx)
|
|
195
217
|
keyword_args = {
|
|
@@ -200,64 +222,69 @@ async def call_function(function: Callable, args: ArgsCommandNode, interpreter:
|
|
|
200
222
|
|
|
201
223
|
def regroupe_commandes(body: list[Token], interpreter: DshellInterpreteur) -> list[dict[str, list[Any]]]:
|
|
202
224
|
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
225
|
+
Groups the command arguments in the form of a python dictionary.
|
|
226
|
+
Note that you can specify the parameter you wish to pass via -- followed by the parameter name. But this is not mandatory!
|
|
227
|
+
Non-mandatory parameters will be stored in a list in the form of tokens with the key ‘*’.
|
|
228
|
+
The others, having been specified via a separator, will be in the form of a list of tokens with the IDENT token as key, following the separator for each argument.
|
|
229
|
+
If two parameters have the same name, the last one will overwrite the previous one.
|
|
230
|
+
To accept duplicates, use the SUB_SEPARATOR (~~) to create a sub-dictionary for parameters with the same name.
|
|
231
|
+
|
|
232
|
+
:param body: The list of tokens to group.
|
|
233
|
+
:param interpreter: The Dshell interpreter instance.
|
|
207
234
|
"""
|
|
208
|
-
tokens = {'*': []} #
|
|
209
|
-
current_arg = '*' #
|
|
235
|
+
tokens = {'*': []} # tokens to return
|
|
236
|
+
current_arg = '*' # the argument keys are the types they belong to. '*' is for all arguments not explicitly specified by a separator and an IDENT
|
|
210
237
|
n = len(body)
|
|
211
238
|
list_tokens: list[dict] = [tokens]
|
|
212
239
|
|
|
213
240
|
i = 0
|
|
214
241
|
while i < n:
|
|
215
242
|
if body[i].type == DTT.SEPARATOR and body[
|
|
216
|
-
i + 1].type == DTT.IDENT: #
|
|
217
|
-
current_arg = body[i + 1].value #
|
|
218
|
-
tokens[current_arg] = '' #
|
|
219
|
-
i += 2 #
|
|
243
|
+
i + 1].type == DTT.IDENT: # Check if it's a separator and if the next token is an IDENT
|
|
244
|
+
current_arg = body[i + 1].value # change the current argument. It will be impossible to return to '*'
|
|
245
|
+
tokens[current_arg] = '' # create a key/value pair for it
|
|
246
|
+
i += 2 # skip the IDENT after the separator since it has just been processed
|
|
220
247
|
|
|
221
248
|
elif body[
|
|
222
|
-
i].type == DTT.SUB_SEPARATOR: #
|
|
249
|
+
i].type == DTT.SUB_SEPARATOR: # allows to delimit parameters and to have several with the same name
|
|
223
250
|
list_tokens += regroupe_commandes(
|
|
224
251
|
[Token(
|
|
225
252
|
type_=DTT.SEPARATOR, value=body[i].value, position=body[i].position)
|
|
226
253
|
] + body[i + 1:], interpreter
|
|
227
|
-
) #
|
|
254
|
+
) # add a sub-dictionary for sub-commands
|
|
228
255
|
return list_tokens
|
|
229
256
|
|
|
230
257
|
else:
|
|
231
258
|
if current_arg == '*':
|
|
232
259
|
tokens[current_arg].append(interpreter.eval_data_token(body[i]))
|
|
233
260
|
else:
|
|
234
|
-
tokens[current_arg] = interpreter.eval_data_token(body[i]) #
|
|
261
|
+
tokens[current_arg] = interpreter.eval_data_token(body[i]) # add the token to the current argument
|
|
235
262
|
i += 1
|
|
236
263
|
return list_tokens
|
|
237
264
|
|
|
238
265
|
|
|
239
266
|
def build_embed(body: list[Token], fields: list[FieldEmbedNode], interpreter: DshellInterpreteur) -> Embed:
|
|
240
267
|
"""
|
|
241
|
-
|
|
268
|
+
Builds an embed from the command information.
|
|
242
269
|
"""
|
|
243
270
|
args_main_embed: dict[str, list[Any]] = regroupe_commandes(body, interpreter)[0]
|
|
244
|
-
args_main_embed.pop('*') #
|
|
245
|
-
args_main_embed: dict[str, Token] #
|
|
271
|
+
args_main_embed.pop('*') # remove unspecified parameters for the embed
|
|
272
|
+
args_main_embed: dict[str, Token] # specify what it contains from now on
|
|
246
273
|
|
|
247
274
|
args_fields: list[dict[str, Token]] = []
|
|
248
|
-
for field in fields: #
|
|
275
|
+
for field in fields: # do the same for the fields
|
|
249
276
|
a = regroupe_commandes(field.body, interpreter)[0]
|
|
250
277
|
a.pop('*')
|
|
251
278
|
a: dict[str, Token]
|
|
252
279
|
args_fields.append(a)
|
|
253
280
|
|
|
254
281
|
if 'color' in args_main_embed and isinstance(args_main_embed['color'],
|
|
255
|
-
ListNode): #
|
|
282
|
+
ListNode): # if color is a ListNode, convert it to Colour
|
|
256
283
|
args_main_embed['color'] = Colour.from_rgb(*args_main_embed['color'])
|
|
257
284
|
|
|
258
|
-
embed = Embed(**args_main_embed) #
|
|
285
|
+
embed = Embed(**args_main_embed) # build the main embed
|
|
259
286
|
for field in args_fields:
|
|
260
|
-
embed.add_field(**field) #
|
|
287
|
+
embed.add_field(**field) # add all fields
|
|
261
288
|
|
|
262
289
|
return embed
|
|
263
290
|
|
|
@@ -265,21 +292,21 @@ def build_embed(body: list[Token], fields: list[FieldEmbedNode], interpreter: Ds
|
|
|
265
292
|
def build_permission(body: list[Token], interpreter: DshellInterpreteur) -> dict[
|
|
266
293
|
Union[Member, Role], PermissionOverwrite]:
|
|
267
294
|
"""
|
|
268
|
-
|
|
295
|
+
Builds a dictionary of PermissionOverwrite objects from the command information.
|
|
269
296
|
"""
|
|
270
297
|
args_permissions: list[dict[str, list[Any]]] = regroupe_commandes(body, interpreter)
|
|
271
298
|
permissions: dict[Union[Member, Role], PermissionOverwrite] = {}
|
|
272
299
|
|
|
273
300
|
for i in args_permissions:
|
|
274
301
|
i.pop('*')
|
|
275
|
-
permissions.update(DshellPermissions(i).get_permission_overwrite(interpreter.ctx.guild))
|
|
302
|
+
permissions.update(DshellPermissions(i).get_permission_overwrite(interpreter.ctx.channel.guild))
|
|
276
303
|
|
|
277
304
|
return permissions
|
|
278
305
|
|
|
279
306
|
|
|
280
307
|
class DshellIterator:
|
|
281
308
|
"""
|
|
282
|
-
|
|
309
|
+
Used to transform anything into an iterable
|
|
283
310
|
"""
|
|
284
311
|
|
|
285
312
|
def __init__(self, data):
|
|
@@ -303,20 +330,20 @@ class DshellPermissions:
|
|
|
303
330
|
|
|
304
331
|
def __init__(self, target: dict[str, list[int]]):
|
|
305
332
|
"""
|
|
306
|
-
|
|
307
|
-
:param target:
|
|
308
|
-
|
|
309
|
-
|
|
333
|
+
Creates a Dshell permissions object.
|
|
334
|
+
:param target: A dictionary containing parameters and their values.
|
|
335
|
+
Expected parameters: “allow”, “deny”, ‘members’, “roles”.
|
|
336
|
+
For “members” and “roles”, values must be ID ListNodes.
|
|
310
337
|
"""
|
|
311
338
|
self.target: dict[str, Union[ListNode, int]] = target
|
|
312
339
|
|
|
313
340
|
@staticmethod
|
|
314
341
|
def get_instance(guild: Guild, target_id: int) -> Union[Member, Role]:
|
|
315
342
|
"""
|
|
316
|
-
|
|
317
|
-
:param guild:
|
|
318
|
-
:param target_id:
|
|
319
|
-
:return:
|
|
343
|
+
Returns the instance corresponding to the given id. Only a Member or Role.
|
|
344
|
+
:param guild: The Discord server in which to search
|
|
345
|
+
:param target_id: The ID of the member or role
|
|
346
|
+
:return: An instance of Member or Role
|
|
320
347
|
"""
|
|
321
348
|
try:
|
|
322
349
|
member = DshellPermissions.get_member(guild, target_id)
|
|
@@ -334,48 +361,46 @@ class DshellPermissions:
|
|
|
334
361
|
if role is not None:
|
|
335
362
|
return role
|
|
336
363
|
|
|
337
|
-
raise ValueError(f"Aucun membre ou rôle trouvé avec l'ID {target_id} dans le serveur {guild.name}.")
|
|
338
|
-
|
|
339
364
|
@staticmethod
|
|
340
365
|
def get_member(guild: Guild, target_id: int) -> Member:
|
|
341
366
|
"""
|
|
342
|
-
|
|
343
|
-
:param guild:
|
|
344
|
-
:param target_id:
|
|
345
|
-
:return:
|
|
367
|
+
Returns the Member instance corresponding to the given id.
|
|
368
|
+
:param guild: The Discord server to search
|
|
369
|
+
:param target_id: The member ID
|
|
370
|
+
:return: A Member instance
|
|
346
371
|
"""
|
|
347
372
|
member = guild.get_member(target_id)
|
|
348
373
|
if member is not None:
|
|
349
374
|
return member
|
|
350
375
|
|
|
351
|
-
raise ValueError(f"
|
|
376
|
+
raise ValueError(f"No member found with ID {target_id} in guild {guild.name}.")
|
|
352
377
|
|
|
353
378
|
@staticmethod
|
|
354
379
|
def get_role(guild: Guild, target_id: int) -> Role:
|
|
355
380
|
"""
|
|
356
|
-
|
|
357
|
-
:param guild:
|
|
358
|
-
:param target_id:
|
|
359
|
-
:return:
|
|
381
|
+
Returns the Role instance corresponding to the given id.
|
|
382
|
+
:param guild: The Discord server to search
|
|
383
|
+
:param target_id: The role ID
|
|
384
|
+
:return: A Role instance
|
|
360
385
|
"""
|
|
361
386
|
role = guild.get_role(target_id)
|
|
362
387
|
if role is not None:
|
|
363
388
|
return role
|
|
364
389
|
|
|
365
|
-
raise ValueError(f"
|
|
390
|
+
raise ValueError(f"No role found with ID {target_id} in guild {guild.name}.")
|
|
366
391
|
|
|
367
392
|
def get_permission_overwrite(self, guild: Guild) -> dict[Union[Member, Role], PermissionOverwrite]:
|
|
368
393
|
"""
|
|
369
|
-
|
|
370
|
-
:param guild:
|
|
371
|
-
:return:
|
|
394
|
+
Returns a PermissionOverwrite object with member and role permissions.
|
|
395
|
+
:param guild: The Discord server
|
|
396
|
+
:return: A dictionary of PermissionOverwrite objects with members and roles as keys
|
|
372
397
|
"""
|
|
373
398
|
permissions: dict[Union[Member, Role], PermissionOverwrite] = {}
|
|
374
399
|
target_keys = self.target.keys()
|
|
375
400
|
|
|
376
401
|
if 'members' in target_keys:
|
|
377
402
|
for member_id in (
|
|
378
|
-
self.target['members'] if isinstance(self.target['members'], ListNode) else [self.target['members']]): #
|
|
403
|
+
self.target['members'] if isinstance(self.target['members'], ListNode) else [self.target['members']]): # allow a single ID
|
|
379
404
|
member = self.get_member(guild, member_id)
|
|
380
405
|
permissions[member] = PermissionOverwrite.from_pair(
|
|
381
406
|
allow=Permissions(permissions=self.target.get('allow', 0)),
|
|
@@ -384,13 +409,13 @@ class DshellPermissions:
|
|
|
384
409
|
|
|
385
410
|
elif 'roles' in target_keys:
|
|
386
411
|
for role_id in (
|
|
387
|
-
self.target['roles'] if isinstance(self.target['roles'], ListNode) else [self.target['roles']]):
|
|
412
|
+
self.target['roles'] if isinstance(self.target['roles'], ListNode) else [self.target['roles']]): # allow a single ID
|
|
388
413
|
role = self.get_role(guild, role_id)
|
|
389
414
|
permissions[role] = PermissionOverwrite.from_pair(
|
|
390
415
|
allow=Permissions(permissions=self.target.get('allow', 0)),
|
|
391
416
|
deny=Permissions(permissions=self.target.get('deny', 0))
|
|
392
417
|
)
|
|
393
418
|
else:
|
|
394
|
-
raise ValueError("
|
|
419
|
+
raise ValueError("No members or roles specified in the permissions target.")
|
|
395
420
|
|
|
396
421
|
return permissions
|
|
@@ -23,10 +23,16 @@ __all__ = [
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class ASTNode:
|
|
26
|
+
"""
|
|
27
|
+
Base class for all AST nodes.
|
|
28
|
+
"""
|
|
26
29
|
pass
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
class StartNode(ASTNode):
|
|
33
|
+
"""
|
|
34
|
+
Node representing the start of the AST.
|
|
35
|
+
"""
|
|
30
36
|
def __init__(self, body: list):
|
|
31
37
|
self.body = body
|
|
32
38
|
|
|
@@ -35,7 +41,13 @@ class StartNode(ASTNode):
|
|
|
35
41
|
|
|
36
42
|
|
|
37
43
|
class ElseNode(ASTNode):
|
|
44
|
+
"""
|
|
45
|
+
Node representing the 'else' part of an if statement.
|
|
46
|
+
"""
|
|
38
47
|
def __init__(self, body: list[Token]):
|
|
48
|
+
"""
|
|
49
|
+
:param body: list of tokens representing the body of the else statement
|
|
50
|
+
"""
|
|
39
51
|
self.body = body
|
|
40
52
|
|
|
41
53
|
def __repr__(self):
|
|
@@ -43,7 +55,15 @@ class ElseNode(ASTNode):
|
|
|
43
55
|
|
|
44
56
|
|
|
45
57
|
class ElifNode(ASTNode):
|
|
58
|
+
"""
|
|
59
|
+
Node representing an 'elif' part of an if statement.
|
|
60
|
+
"""
|
|
46
61
|
def __init__(self, condition: list[Token], body: list[Token], parent: "IfNode"):
|
|
62
|
+
"""
|
|
63
|
+
:param condition: list of tokens representing the condition for the elif
|
|
64
|
+
:param body: list of tokens representing the body of the elif
|
|
65
|
+
:param parent: the if node that this elif belongs to
|
|
66
|
+
"""
|
|
47
67
|
self.condition = condition
|
|
48
68
|
self.body = body
|
|
49
69
|
self.parent = parent
|
|
@@ -53,8 +73,17 @@ class ElifNode(ASTNode):
|
|
|
53
73
|
|
|
54
74
|
|
|
55
75
|
class IfNode(ASTNode):
|
|
76
|
+
"""
|
|
77
|
+
Node representing an 'if' statement, which can contain 'elif' and 'else' parts.
|
|
78
|
+
"""
|
|
56
79
|
def __init__(self, condition: list[Token], body: list[Token], elif_nodes: Optional[list[ElifNode]] = None,
|
|
57
80
|
else_body: Optional[ElseNode] = None):
|
|
81
|
+
"""
|
|
82
|
+
:param condition: list of tokens representing the condition for the if statement
|
|
83
|
+
:param body: list of tokens representing the body of the if statement
|
|
84
|
+
:param elif_nodes: optional list of ElifNode instances representing 'elif' parts
|
|
85
|
+
:param else_body: optional ElseNode instance representing the 'else' part
|
|
86
|
+
"""
|
|
58
87
|
self.condition = condition
|
|
59
88
|
self.body = body
|
|
60
89
|
self.elif_nodes = elif_nodes
|
|
@@ -65,8 +94,15 @@ class IfNode(ASTNode):
|
|
|
65
94
|
|
|
66
95
|
|
|
67
96
|
class LoopNode(ASTNode):
|
|
97
|
+
"""
|
|
98
|
+
Node representing a loop structure in the AST.
|
|
99
|
+
"""
|
|
68
100
|
def __init__(self, variable: "VarNode", body: list):
|
|
69
|
-
|
|
101
|
+
"""
|
|
102
|
+
:param variable: VarNode representing the loop variable. This variable will be used to iterate over the body. Can contain a ListNode, string or integer.
|
|
103
|
+
:param body: list of tokens representing the body of the loop
|
|
104
|
+
"""
|
|
105
|
+
self.variable = variable
|
|
70
106
|
self.body = body
|
|
71
107
|
|
|
72
108
|
def __repr__(self):
|
|
@@ -74,7 +110,13 @@ class LoopNode(ASTNode):
|
|
|
74
110
|
|
|
75
111
|
|
|
76
112
|
class ArgsCommandNode(ASTNode):
|
|
113
|
+
"""
|
|
114
|
+
Node representing the arguments of a command in the AST.
|
|
115
|
+
"""
|
|
77
116
|
def __init__(self, body: list[Token]):
|
|
117
|
+
"""
|
|
118
|
+
:param body: list of tokens representing the arguments of the command
|
|
119
|
+
"""
|
|
78
120
|
self.body: list[Token] = body
|
|
79
121
|
|
|
80
122
|
def __repr__(self):
|
|
@@ -82,7 +124,14 @@ class ArgsCommandNode(ASTNode):
|
|
|
82
124
|
|
|
83
125
|
|
|
84
126
|
class CommandNode(ASTNode):
|
|
127
|
+
"""
|
|
128
|
+
Node representing a command in the AST.
|
|
129
|
+
"""
|
|
85
130
|
def __init__(self, name: str, body: ArgsCommandNode):
|
|
131
|
+
"""
|
|
132
|
+
:param name: The command name (e.g., "sm", "cc")
|
|
133
|
+
:param body: ArgsCommandNode containing the arguments of the command
|
|
134
|
+
"""
|
|
86
135
|
self.name = name
|
|
87
136
|
self.body = body
|
|
88
137
|
|
|
@@ -91,7 +140,14 @@ class CommandNode(ASTNode):
|
|
|
91
140
|
|
|
92
141
|
|
|
93
142
|
class VarNode(ASTNode):
|
|
143
|
+
"""
|
|
144
|
+
Node representing a variable declaration in the AST.
|
|
145
|
+
"""
|
|
94
146
|
def __init__(self, name: Token, body: list[Token]):
|
|
147
|
+
"""
|
|
148
|
+
:param name: Token representing the variable name
|
|
149
|
+
:param body: list of tokens representing the body of the variable
|
|
150
|
+
"""
|
|
95
151
|
self.name = name
|
|
96
152
|
self.body = body
|
|
97
153
|
|
|
@@ -100,6 +156,9 @@ class VarNode(ASTNode):
|
|
|
100
156
|
|
|
101
157
|
|
|
102
158
|
class EndNode(ASTNode):
|
|
159
|
+
"""
|
|
160
|
+
Node representing the end of the AST.
|
|
161
|
+
"""
|
|
103
162
|
def __init__(self):
|
|
104
163
|
pass
|
|
105
164
|
|
|
@@ -108,7 +167,13 @@ class EndNode(ASTNode):
|
|
|
108
167
|
|
|
109
168
|
|
|
110
169
|
class FieldEmbedNode(ASTNode):
|
|
170
|
+
"""
|
|
171
|
+
Node representing a field in an embed structure.
|
|
172
|
+
"""
|
|
111
173
|
def __init__(self, body: list[Token]):
|
|
174
|
+
"""
|
|
175
|
+
:param body: list of tokens representing the field content
|
|
176
|
+
"""
|
|
112
177
|
self.body: list[Token] = body
|
|
113
178
|
|
|
114
179
|
def __repr__(self):
|
|
@@ -116,7 +181,14 @@ class FieldEmbedNode(ASTNode):
|
|
|
116
181
|
|
|
117
182
|
|
|
118
183
|
class EmbedNode(ASTNode):
|
|
184
|
+
"""
|
|
185
|
+
Node representing an embed structure in the AST.
|
|
186
|
+
"""
|
|
119
187
|
def __init__(self, body: list[Token], fields: list[FieldEmbedNode]):
|
|
188
|
+
"""
|
|
189
|
+
:param body: list of tokens representing the embed content
|
|
190
|
+
:param fields: list of FieldEmbedNode instances representing the fields of the embed
|
|
191
|
+
"""
|
|
120
192
|
self.body = body
|
|
121
193
|
self.fields = fields
|
|
122
194
|
|
|
@@ -125,7 +197,13 @@ class EmbedNode(ASTNode):
|
|
|
125
197
|
|
|
126
198
|
|
|
127
199
|
class PermissionNode(ASTNode):
|
|
200
|
+
"""
|
|
201
|
+
Node representing a permission structure in the AST.
|
|
202
|
+
"""
|
|
128
203
|
def __init__(self, body: list[Token]):
|
|
204
|
+
"""
|
|
205
|
+
:param body: list of tokens representing the permission content
|
|
206
|
+
"""
|
|
129
207
|
self.body = body
|
|
130
208
|
|
|
131
209
|
def __repr__(self):
|
|
@@ -133,7 +211,13 @@ class PermissionNode(ASTNode):
|
|
|
133
211
|
|
|
134
212
|
|
|
135
213
|
class SleepNode(ASTNode):
|
|
214
|
+
"""
|
|
215
|
+
Node representing a sleep command in the AST.
|
|
216
|
+
"""
|
|
136
217
|
def __init__(self, body: list[Token]):
|
|
218
|
+
"""
|
|
219
|
+
:param body: list of tokens representing the sleep duration
|
|
220
|
+
"""
|
|
137
221
|
self.body = body
|
|
138
222
|
|
|
139
223
|
def __repr__(self):
|
|
@@ -142,14 +226,20 @@ class SleepNode(ASTNode):
|
|
|
142
226
|
|
|
143
227
|
class IdentOperationNode(ASTNode):
|
|
144
228
|
"""
|
|
145
|
-
|
|
146
|
-
|
|
229
|
+
Node representing an operation on an identifier in the AST.
|
|
230
|
+
Manages operations on idendifiers (function calls)
|
|
231
|
+
Ensure that the function call returns the associated class to allow nesting. Not mandatory in itself if it returns something
|
|
147
232
|
"""
|
|
148
233
|
|
|
149
234
|
def __init__(self, ident: Token, function: Token, args: Token):
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
235
|
+
"""
|
|
236
|
+
:param ident: Token representing the identifier (e.g., a class or object)
|
|
237
|
+
:param function: Token representing the function to be called on the identifier
|
|
238
|
+
:param args: Token representing the arguments passed to the function
|
|
239
|
+
"""
|
|
240
|
+
self.ident = ident
|
|
241
|
+
self.function = function
|
|
242
|
+
self.args = args
|
|
153
243
|
|
|
154
244
|
def __repr__(self):
|
|
155
245
|
return f"<IDENT OPERATION> - {self.ident}.{self.function}({self.args})"
|
|
@@ -157,33 +247,42 @@ class IdentOperationNode(ASTNode):
|
|
|
157
247
|
|
|
158
248
|
class ListNode(ASTNode):
|
|
159
249
|
"""
|
|
160
|
-
|
|
161
|
-
|
|
250
|
+
Node representing a list structure in the AST.
|
|
251
|
+
Iterable class for browsing lists created from Dshell code.
|
|
252
|
+
This class also lets you interact with the list via specific methods not built in by python.
|
|
162
253
|
"""
|
|
163
254
|
|
|
164
255
|
def __init__(self, body: list[Any]):
|
|
256
|
+
"""
|
|
257
|
+
:param body: list of elements to initialize the ListNode with
|
|
258
|
+
"""
|
|
165
259
|
self.iterable: list[Any] = body
|
|
166
260
|
self.len_iterable: int = len(body)
|
|
167
261
|
self.iterateur_count: int = 0
|
|
168
262
|
|
|
169
263
|
def add(self, value: Any):
|
|
170
264
|
"""
|
|
171
|
-
|
|
265
|
+
Add a value to the list.
|
|
172
266
|
"""
|
|
173
267
|
if self.len_iterable > 10000:
|
|
174
|
-
raise PermissionError('
|
|
268
|
+
raise PermissionError('The list is too long, it must not exceed 10,000 elements !')
|
|
175
269
|
|
|
176
270
|
self.iterable.append(value)
|
|
177
271
|
self.len_iterable += 1
|
|
178
272
|
|
|
179
273
|
def remove(self, value: Any, number: int = 1):
|
|
180
274
|
"""
|
|
181
|
-
|
|
275
|
+
Remove a value from the list.
|
|
182
276
|
"""
|
|
183
277
|
if number < 1:
|
|
184
|
-
raise Exception(f"
|
|
278
|
+
raise Exception(f"The number of elements to remove must be at least 1, not {number} !")
|
|
185
279
|
|
|
186
280
|
def __add__(self, other: "ListNode"):
|
|
281
|
+
"""
|
|
282
|
+
Add another ListNode to this one.
|
|
283
|
+
:param other: Another ListNode to add to this one.
|
|
284
|
+
:return: Instance of ListNode with combined elements.
|
|
285
|
+
"""
|
|
187
286
|
for i in other:
|
|
188
287
|
self.add(i)
|
|
189
288
|
return self
|
|
@@ -192,6 +291,10 @@ class ListNode(ASTNode):
|
|
|
192
291
|
return self
|
|
193
292
|
|
|
194
293
|
def __next__(self):
|
|
294
|
+
"""
|
|
295
|
+
Iterate over the elements of the list.
|
|
296
|
+
:return: an element from the list.
|
|
297
|
+
"""
|
|
195
298
|
|
|
196
299
|
if self.iterateur_count >= self.len_iterable:
|
|
197
300
|
self.iterateur_count = 0
|
|
@@ -32,7 +32,9 @@ if TYPE_CHECKING:
|
|
|
32
32
|
|
|
33
33
|
def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[ASTNode], int]:
|
|
34
34
|
"""
|
|
35
|
-
|
|
35
|
+
Parse the list of tokens and return a list of AST nodes.
|
|
36
|
+
:param token_lines: table of tokens
|
|
37
|
+
:param start_node: the node where to start the parsing
|
|
36
38
|
"""
|
|
37
39
|
pointeur = 0 # pointeur sur les listes de tokens pour savoir ou parser
|
|
38
40
|
blocks: list[Union[ASTNode, EndNode]] = [start_node] # liste d'imbrication des blocks pour gérer l'imbrication
|
|
@@ -11,6 +11,7 @@ from typing import Callable
|
|
|
11
11
|
|
|
12
12
|
from ..DISCORD_COMMANDS.dshell_channel import *
|
|
13
13
|
from ..DISCORD_COMMANDS.dshell_message import *
|
|
14
|
+
from ..DISCORD_COMMANDS.dshell_member import *
|
|
14
15
|
|
|
15
16
|
dshell_keyword: set[str] = {
|
|
16
17
|
'if', 'else', 'elif', 'loop', '#end', 'var', '#loop', '#if', 'sleep'
|
|
@@ -24,11 +25,15 @@ dshell_commands: dict[str, Callable] = {
|
|
|
24
25
|
"dm": dshell_delete_message,
|
|
25
26
|
"pm": dshell_purge_message,
|
|
26
27
|
"cc": dshell_create_text_channel, # create channel
|
|
28
|
+
"cvc": dshell_create_voice_channel, # create voice channel
|
|
27
29
|
"dc": dshell_delete_channel, # delete channel
|
|
28
|
-
"dcs": dshell_delete_channels,
|
|
29
|
-
"
|
|
30
|
-
#
|
|
31
|
-
"
|
|
30
|
+
"dcs": dshell_delete_channels, # delete several channels by name or regex
|
|
31
|
+
"bm": dshell_ban_member, # ban member
|
|
32
|
+
"um": dshell_unban_member, # unban member
|
|
33
|
+
"km": dshell_kick_member, # kick member
|
|
34
|
+
"em": dshell_edit_message, # edit message
|
|
35
|
+
"srm": dshell_research_regex_message, # research regex in message
|
|
36
|
+
"src": dshell_research_regex_in_content, # research regex in content
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
dshell_mathematical_operators: dict[str, tuple[Callable, int]] = {
|
|
@@ -43,11 +48,11 @@ dshell_mathematical_operators: dict[str, tuple[Callable, int]] = {
|
|
|
43
48
|
r"=>": (lambda a, b: a >= b, 4),
|
|
44
49
|
|
|
45
50
|
r".": (lambda a, b: a.b, 9),
|
|
46
|
-
r"->": (lambda a: a.at, 10), #
|
|
51
|
+
r"->": (lambda a: a.at, 10), # equivalent to calling .at(key)
|
|
47
52
|
|
|
48
53
|
r"+": (lambda a, b: a + b, 6),
|
|
49
54
|
r"-": (lambda a, b=None: -a if b is None else a - b, 6),
|
|
50
|
-
#
|
|
55
|
+
# warning: ambiguity between unary and binary to be handled in your parser
|
|
51
56
|
r"**": (lambda a, b: a ** b, 8),
|
|
52
57
|
r"*": (lambda a, b: a * b, 7),
|
|
53
58
|
r"%": (lambda a, b: a % b, 7),
|
|
@@ -71,7 +76,7 @@ dshell_operators.update(dshell_mathematical_operators)
|
|
|
71
76
|
|
|
72
77
|
'''
|
|
73
78
|
C_create_var = "var"
|
|
74
|
-
C_obligate_var = "ovar" #
|
|
79
|
+
C_obligate_var = "ovar" # makes variables mandatory
|
|
75
80
|
|
|
76
81
|
# guild
|
|
77
82
|
C_create_channel = "cc"
|
|
@@ -120,5 +125,5 @@ C_create_var = "var"
|
|
|
120
125
|
C_clear_emoji = "ce"
|
|
121
126
|
C_remove_reaction = "rre"
|
|
122
127
|
|
|
123
|
-
#
|
|
128
|
+
# button
|
|
124
129
|
C_create_button = "b"'''
|
|
@@ -32,7 +32,7 @@ class DshellTokenType(Enum):
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class Token:
|
|
35
|
-
def __init__(self, type_: "DTT", value: Union[str, list], position: int):
|
|
35
|
+
def __init__(self, type_: "DTT", value: Union[str, list], position: tuple[int, int]):
|
|
36
36
|
self.type = type_
|
|
37
37
|
self.value = value
|
|
38
38
|
self.position = position
|
|
@@ -59,6 +59,7 @@ class DshellTokenizer:
|
|
|
59
59
|
"""
|
|
60
60
|
tokens: list[list[Token]] = []
|
|
61
61
|
|
|
62
|
+
line_number = 1
|
|
62
63
|
for ligne in commandes_lines: # iter chaque ligne du code
|
|
63
64
|
tokens_par_ligne: list[Token] = []
|
|
64
65
|
|
|
@@ -66,7 +67,7 @@ class DshellTokenizer:
|
|
|
66
67
|
|
|
67
68
|
for match in finditer(pattern, ligne): # iter les résultat du match pour avoir leur position
|
|
68
69
|
if token_type != DTT.COMMENT: # si ce n'est pas un commentaire
|
|
69
|
-
token = Token(token_type, match.group(1), match.start()) # on enregistre son token
|
|
70
|
+
token = Token(token_type, match.group(1), (line_number, match.start())) # on enregistre son token
|
|
70
71
|
tokens_par_ligne.append(token)
|
|
71
72
|
|
|
72
73
|
len_match = len(match.group(0))
|
|
@@ -81,7 +82,7 @@ class DshellTokenizer:
|
|
|
81
82
|
result) > 0 else result # gère si la structure de donnée est vide ou non
|
|
82
83
|
|
|
83
84
|
tokens_par_ligne.sort(key=lambda
|
|
84
|
-
token: token.position) # trie la position par rapport aux positions de match des tokens pour les avoir dans l'ordre du code
|
|
85
|
+
token: token.position[1]) # trie la position par rapport aux positions de match des tokens pour les avoir dans l'ordre du code
|
|
85
86
|
if tokens_par_ligne:
|
|
86
87
|
tokens.append(tokens_par_ligne)
|
|
87
88
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Dshell/__init__.py,sha256=UPvXnewe_8FX9aoevMA78UN1k8AY-u8LTY3vEVxaDxw,72
|
|
2
|
+
Dshell/DISCORD_COMMANDS/__init__.py,sha256=unbZE4sxFCbUQ4Qptr1BhWu-nwhvI2oRzcDLtRmX6Ug,92
|
|
3
|
+
Dshell/DISCORD_COMMANDS/dshell_channel.py,sha256=pKN-jzPSk2UI0LgUt31LsOYs5bWLys6LcQclQ-AwbXE,3713
|
|
4
|
+
Dshell/DISCORD_COMMANDS/dshell_member.py,sha256=hV_8tFUa_RLwh-UaoLcHQr4yp0ln2vjvzFW5IJwDGVM,1792
|
|
5
|
+
Dshell/DISCORD_COMMANDS/dshell_message.py,sha256=12Alhl_7qgPcs1NCEWDgUVPSsaKmULHkrbGuGH2LWu8,3406
|
|
6
|
+
Dshell/_DshellInterpreteur/__init__.py,sha256=xy5-J-R3YmY99JF3NBHTRRLsComFxpjnCA5xacISctU,35
|
|
7
|
+
Dshell/_DshellInterpreteur/dshell_interpreter.py,sha256=x1_t-9twq2IaB5aQUJN9XFvXbujMCN1KRX_1uYr9IGA,17289
|
|
8
|
+
Dshell/_DshellParser/__init__.py,sha256=ONDfhZMvClqP_6tE8SLjp-cf3pXL-auQYnfYRrHZxC4,56
|
|
9
|
+
Dshell/_DshellParser/ast_nodes.py,sha256=ZPZAA6yqR62mLAMGVO94wTUVmpYM91qAwSKFiWNAt74,9125
|
|
10
|
+
Dshell/_DshellParser/dshell_parser.py,sha256=SqZRG7i8_vdIFhaQwLi-Q8LWIwyw5FaPFEGMX5vsWwU,14074
|
|
11
|
+
Dshell/_DshellTokenizer/__init__.py,sha256=LIQSRhDx2B9pmPx5ADMwwD0Xr9ybneVLhHH8qrJWw_s,172
|
|
12
|
+
Dshell/_DshellTokenizer/dshell_keywords.py,sha256=Mf48FtTWZN-bgNwFVijFbcQK1bZEEdyutQvu_DXJEVo,3915
|
|
13
|
+
Dshell/_DshellTokenizer/dshell_token_type.py,sha256=p0cR4sxhmq03AqBjd0h_sg2eDzgBmd3dhrrchYkL1Pk,1011
|
|
14
|
+
Dshell/_DshellTokenizer/dshell_tokenizer.py,sha256=DSGiSHj9jLqP7RkC-8WFRqFvitJ7P4b7p0CJn4ek7hE,5831
|
|
15
|
+
dshellinterpreter-0.1.12.dist-info/licenses/LICENSE,sha256=lNgcw1_xb7QENAQi3uHGymaFtbs0RV-ihiCd7AoLQjA,1082
|
|
16
|
+
dshellinterpreter-0.1.12.dist-info/METADATA,sha256=CpdSKbxC4rMdof6AvL_mt_Ae0ZGRmAg8hLb807-uz4w,1096
|
|
17
|
+
dshellinterpreter-0.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
dshellinterpreter-0.1.12.dist-info/top_level.txt,sha256=B4CMhtmchGwPQJLuqUy0GhRG-0cUGxKL4GqEbCiB_vE,7
|
|
19
|
+
dshellinterpreter-0.1.12.dist-info/RECORD,,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Dshell/__init__.py,sha256=UPvXnewe_8FX9aoevMA78UN1k8AY-u8LTY3vEVxaDxw,72
|
|
2
|
-
Dshell/DISCORD_COMMANDS/__init__.py,sha256=s58iMazDXNPDLZ3Hvymh__C5w7lcgEvaYfX0-SHocRo,62
|
|
3
|
-
Dshell/DISCORD_COMMANDS/dshell_channel.py,sha256=RmzqXiuwWTbIUV37KtutFsw2uuXNY7HJFtbJCut5rgg,2388
|
|
4
|
-
Dshell/DISCORD_COMMANDS/dshell_message.py,sha256=cOZzPbm4wkgObuIL42CH1R3qDhPP8ONiP4BA-XTfAMc,1880
|
|
5
|
-
Dshell/_DshellInterpreteur/__init__.py,sha256=xy5-J-R3YmY99JF3NBHTRRLsComFxpjnCA5xacISctU,35
|
|
6
|
-
Dshell/_DshellInterpreteur/dshell_interpreter.py,sha256=V9sF_2k7JU25yS7n2Ms95fMsjV-pCQy6eCCMu74ilVA,16113
|
|
7
|
-
Dshell/_DshellParser/__init__.py,sha256=ONDfhZMvClqP_6tE8SLjp-cf3pXL-auQYnfYRrHZxC4,56
|
|
8
|
-
Dshell/_DshellParser/ast_nodes.py,sha256=dRAYD7ZgNB6q5RGwqgWrM68PtUzSFGMBb6xEwvzaDxo,5687
|
|
9
|
-
Dshell/_DshellParser/dshell_parser.py,sha256=mA8uA-xBhKcfnSKfLfePwI_fD0GrHbj0VBVeJPbcGas,13956
|
|
10
|
-
Dshell/_DshellTokenizer/__init__.py,sha256=LIQSRhDx2B9pmPx5ADMwwD0Xr9ybneVLhHH8qrJWw_s,172
|
|
11
|
-
Dshell/_DshellTokenizer/dshell_keywords.py,sha256=wTV89px9bUY4uf0KCnDr356H_dBIVgcPr55L_6-AWdg,3661
|
|
12
|
-
Dshell/_DshellTokenizer/dshell_token_type.py,sha256=NE-sVloBYtioZpWKnWuO5p0YcObGJZPNaKwc8lWDonQ,999
|
|
13
|
-
Dshell/_DshellTokenizer/dshell_tokenizer.py,sha256=2tto_gXYj94fLFx99p6nBbtplALT3Ysz9oBwatX9tIA,5788
|
|
14
|
-
dshellinterpreter-0.1.10.dist-info/licenses/LICENSE,sha256=lNgcw1_xb7QENAQi3uHGymaFtbs0RV-ihiCd7AoLQjA,1082
|
|
15
|
-
dshellinterpreter-0.1.10.dist-info/METADATA,sha256=1DKZzK2v6ED7iMaKg7H3CLNG4qf62niR3wfBT8_-xbU,1096
|
|
16
|
-
dshellinterpreter-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
-
dshellinterpreter-0.1.10.dist-info/top_level.txt,sha256=B4CMhtmchGwPQJLuqUy0GhRG-0cUGxKL4GqEbCiB_vE,7
|
|
18
|
-
dshellinterpreter-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|