dshellInterpreter 0.1.14__tar.gz → 0.1.16__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.
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_message.py +4 -4
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellInterpreteur/dshell_interpreter.py +45 -8
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellParser/ast_nodes.py +29 -1
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellParser/dshell_parser.py +27 -8
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_keywords.py +1 -1
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/PKG-INFO +1 -1
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/PKG-INFO +1 -1
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/setup.py +1 -1
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/__init__.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_channel.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_member.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellInterpreteur/__init__.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellParser/__init__.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/__init__.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_token_type.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_tokenizer.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/__init__.py +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/LICENSE +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/README.md +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/SOURCES.txt +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/dependency_links.txt +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/requires.txt +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/top_level.txt +0 -0
- {dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/setup.cfg +0 -0
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_message.py
RENAMED
|
@@ -116,11 +116,11 @@ async def dshell_research_regex_in_content(ctx: Message, regex, content):
|
|
|
116
116
|
|
|
117
117
|
return True
|
|
118
118
|
|
|
119
|
-
async def dshell_add_reactions(ctx: Message, reactions,
|
|
119
|
+
async def dshell_add_reactions(ctx: Message, reactions, message=None):
|
|
120
120
|
"""
|
|
121
121
|
Adds reactions to a message
|
|
122
122
|
"""
|
|
123
|
-
message = ctx
|
|
123
|
+
message = ctx if message is None else ctx.channel.get_partial_message(message) # builds a reference to the message (even if it doesn't exist)
|
|
124
124
|
|
|
125
125
|
if isinstance(reactions, str):
|
|
126
126
|
reactions = [reactions]
|
|
@@ -130,11 +130,11 @@ async def dshell_add_reactions(ctx: Message, reactions, message_id=None):
|
|
|
130
130
|
|
|
131
131
|
return message.id
|
|
132
132
|
|
|
133
|
-
async def dshell_remove_reactions(ctx: Message, reactions,
|
|
133
|
+
async def dshell_remove_reactions(ctx: Message, reactions, message=None):
|
|
134
134
|
"""
|
|
135
135
|
Removes reactions from a message
|
|
136
136
|
"""
|
|
137
|
-
message = ctx
|
|
137
|
+
message = ctx if message is None else ctx.channel.get_partial_message(message) # builds a reference to the message (even if it doesn't exist)
|
|
138
138
|
|
|
139
139
|
if isinstance(reactions, str):
|
|
140
140
|
reactions = [reactions]
|
|
@@ -2,9 +2,8 @@ from asyncio import sleep
|
|
|
2
2
|
from re import findall
|
|
3
3
|
from typing import TypeVar, Union, Any, Optional, Callable
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
from discord import AutoShardedBot, Embed, Colour, PermissionOverwrite, Permissions, Guild, Member, Role, Message
|
|
7
|
-
from discord.abc import
|
|
6
|
+
from discord.abc import PrivateChannel
|
|
8
7
|
|
|
9
8
|
from .._DshellParser.ast_nodes import *
|
|
10
9
|
from .._DshellParser.dshell_parser import parse
|
|
@@ -24,7 +23,7 @@ class DshellInterpreteur:
|
|
|
24
23
|
Make what you want with Dshell code to interact with Discord !
|
|
25
24
|
"""
|
|
26
25
|
|
|
27
|
-
def __init__(self, code: str, ctx: context, debug: bool = False, vars: Optional[
|
|
26
|
+
def __init__(self, code: str, ctx: context, debug: bool = False, vars: Optional[str] = None):
|
|
28
27
|
"""
|
|
29
28
|
Interpreter Dshell code
|
|
30
29
|
:param code: The code to interpret. Each line must end with a newline character, except SEPARATOR and SUB_SEPARATOR tokens.
|
|
@@ -33,11 +32,12 @@ class DshellInterpreteur:
|
|
|
33
32
|
:param vars: Optional dictionary of variables to initialize in the interpreter's environment.
|
|
34
33
|
"""
|
|
35
34
|
self.ast: list[ASTNode] = parse(DshellTokenizer(code).start(), StartNode([]))[0]
|
|
36
|
-
self.env: dict[str, Any] =
|
|
35
|
+
self.env: dict[str, Any] = {
|
|
36
|
+
'__ret__': None} # environment variables, '__ret__' is used to store the return value of commands
|
|
37
|
+
self.vars = vars if vars is not None else ''
|
|
37
38
|
self.ctx: context = ctx
|
|
38
39
|
if debug:
|
|
39
40
|
print_ast(self.ast)
|
|
40
|
-
self.env['__ret__'] = None
|
|
41
41
|
|
|
42
42
|
async def execute(self, ast: Optional[list[All_nodes]] = None):
|
|
43
43
|
"""
|
|
@@ -63,6 +63,10 @@ class DshellInterpreteur:
|
|
|
63
63
|
if isinstance(node, CommandNode):
|
|
64
64
|
self.env['__ret__'] = await call_function(dshell_commands[node.name], node.body, self)
|
|
65
65
|
|
|
66
|
+
elif isinstance(node, ParamNode):
|
|
67
|
+
params = get_params(node, self)
|
|
68
|
+
self.env.update(params) # update the environment
|
|
69
|
+
|
|
66
70
|
elif isinstance(node, IfNode):
|
|
67
71
|
elif_valid = False
|
|
68
72
|
if eval_expression(node.condition, self):
|
|
@@ -153,6 +157,37 @@ class DshellInterpreteur:
|
|
|
153
157
|
return token.value # fallback
|
|
154
158
|
|
|
155
159
|
|
|
160
|
+
def get_params(node: ParamNode, interpreter: DshellInterpreteur) -> dict[str, Any]:
|
|
161
|
+
"""
|
|
162
|
+
Get the parameters from a ParamNode.
|
|
163
|
+
:param node: The ParamNode to get the parameters from.
|
|
164
|
+
:param interpreter: The Dshell interpreter instance.
|
|
165
|
+
:return: A dictionary of parameters.
|
|
166
|
+
"""
|
|
167
|
+
regrouped_args: dict[str, list] = regroupe_commandes(node.body, interpreter)[
|
|
168
|
+
0] # just regroup the commands, no need to do anything else
|
|
169
|
+
regrouped_args.pop('*', ())
|
|
170
|
+
obligate = [i for i in regrouped_args.keys() if regrouped_args[i] == '*'] # get the obligatory parameters
|
|
171
|
+
|
|
172
|
+
g: list[list[Token]] = DshellTokenizer(interpreter.vars).start()
|
|
173
|
+
env_give_variables = regroupe_commandes(g[0], interpreter)[0] if g else {}
|
|
174
|
+
|
|
175
|
+
gived_variables = env_give_variables.pop('*', ()) # get the variables given in the environment
|
|
176
|
+
for key, value in zip(regrouped_args.keys(), gived_variables):
|
|
177
|
+
regrouped_args[key] = value
|
|
178
|
+
|
|
179
|
+
for key, value in env_give_variables.items():
|
|
180
|
+
if key in regrouped_args:
|
|
181
|
+
regrouped_args[key] = value # update the regrouped args with the env variables
|
|
182
|
+
else:
|
|
183
|
+
raise Exception(f"'{key}' is not a valid parameter, but was given in the environment.")
|
|
184
|
+
|
|
185
|
+
for key in obligate:
|
|
186
|
+
if regrouped_args[key] == '*':
|
|
187
|
+
raise Exception(f"'{key}' is an obligatory parameter, but no value was given for it.")
|
|
188
|
+
|
|
189
|
+
return regrouped_args
|
|
190
|
+
|
|
156
191
|
def eval_expression_inline(if_node: IfNode, interpreter: DshellInterpreteur) -> Token:
|
|
157
192
|
"""
|
|
158
193
|
Eval a conditional expression inline.
|
|
@@ -231,7 +266,7 @@ def regroupe_commandes(body: list[Token], interpreter: DshellInterpreteur) -> li
|
|
|
231
266
|
Non-mandatory parameters will be stored in a list in the form of tokens with the key ‘*’.
|
|
232
267
|
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.
|
|
233
268
|
If two parameters have the same name, the last one will overwrite the previous one.
|
|
234
|
-
To accept duplicates, use the SUB_SEPARATOR (~~) to create a sub-dictionary for parameters with the same name.
|
|
269
|
+
To accept duplicates, use the SUB_SEPARATOR (~~) to create a sub-dictionary for parameters with the same name (sub-dictionary is added to the list returned).
|
|
235
270
|
|
|
236
271
|
:param body: The list of tokens to group.
|
|
237
272
|
:param interpreter: The Dshell interpreter instance.
|
|
@@ -404,7 +439,8 @@ class DshellPermissions:
|
|
|
404
439
|
|
|
405
440
|
if 'members' in target_keys:
|
|
406
441
|
for member_id in (
|
|
407
|
-
|
|
442
|
+
self.target['members'] if isinstance(self.target['members'], ListNode) else [
|
|
443
|
+
self.target['members']]): # allow a single ID
|
|
408
444
|
member = self.get_member(guild, member_id)
|
|
409
445
|
permissions[member] = PermissionOverwrite.from_pair(
|
|
410
446
|
allow=Permissions(permissions=self.target.get('allow', 0)),
|
|
@@ -413,7 +449,8 @@ class DshellPermissions:
|
|
|
413
449
|
|
|
414
450
|
elif 'roles' in target_keys:
|
|
415
451
|
for role_id in (
|
|
416
|
-
|
|
452
|
+
self.target['roles'] if isinstance(self.target['roles'], ListNode) else [
|
|
453
|
+
self.target['roles']]): # allow a single ID
|
|
417
454
|
role = self.get_role(guild, role_id)
|
|
418
455
|
permissions[role] = PermissionOverwrite.from_pair(
|
|
419
456
|
allow=Permissions(permissions=self.target.get('allow', 0)),
|
|
@@ -19,7 +19,8 @@ __all__ = [
|
|
|
19
19
|
'SleepNode',
|
|
20
20
|
'IdentOperationNode',
|
|
21
21
|
'ListNode',
|
|
22
|
-
'PermissionNode'
|
|
22
|
+
'PermissionNode',
|
|
23
|
+
'ParamNode'
|
|
23
24
|
]
|
|
24
25
|
|
|
25
26
|
|
|
@@ -395,6 +396,33 @@ class IdentOperationNode(ASTNode):
|
|
|
395
396
|
"args": self.args.to_dict()
|
|
396
397
|
}
|
|
397
398
|
|
|
399
|
+
class ParamNode(ASTNode):
|
|
400
|
+
"""
|
|
401
|
+
Node representing a parameter in the AST.
|
|
402
|
+
This is used to define parameters for variables passed to the dshell interpreter.
|
|
403
|
+
"""
|
|
404
|
+
|
|
405
|
+
def __init__(self, body: list[Token]):
|
|
406
|
+
"""
|
|
407
|
+
:param name: Token representing the parameter name
|
|
408
|
+
:param body: list of tokens representing the body of the parameter
|
|
409
|
+
"""
|
|
410
|
+
self.body = body
|
|
411
|
+
|
|
412
|
+
def __repr__(self):
|
|
413
|
+
return f"<PARAM> - {self.name} *- {self.body}"
|
|
414
|
+
|
|
415
|
+
def to_dict(self):
|
|
416
|
+
"""
|
|
417
|
+
Convert the ParamNode to a dictionary representation.
|
|
418
|
+
:return: Dictionary representation of the ParamNode.
|
|
419
|
+
"""
|
|
420
|
+
return {
|
|
421
|
+
"type": "ParamNode",
|
|
422
|
+
"name": self.name.to_dict(),
|
|
423
|
+
"body": [token.to_dict() for token in self.body]
|
|
424
|
+
}
|
|
425
|
+
|
|
398
426
|
class ListNode(ASTNode):
|
|
399
427
|
"""
|
|
400
428
|
Node representing a list structure in the AST.
|
|
@@ -23,6 +23,7 @@ from .ast_nodes import (ASTNode,
|
|
|
23
23
|
EmbedNode,
|
|
24
24
|
FieldEmbedNode,
|
|
25
25
|
PermissionNode,
|
|
26
|
+
ParamNode,
|
|
26
27
|
StartNode)
|
|
27
28
|
from .._DshellTokenizer.dshell_token_type import DshellTokenType as DTT
|
|
28
29
|
from .._DshellTokenizer.dshell_token_type import Token
|
|
@@ -66,7 +67,7 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
66
67
|
|
|
67
68
|
elif first_token_line.value == '#if':
|
|
68
69
|
if not isinstance(last_block, (IfNode, ElseNode, ElifNode)):
|
|
69
|
-
raise SyntaxError(f'[#IF]
|
|
70
|
+
raise SyntaxError(f'[#IF] No conditional bloc open on line {first_token_line.position} !')
|
|
70
71
|
|
|
71
72
|
if isinstance(last_block, (ElifNode, ElseNode)):
|
|
72
73
|
|
|
@@ -78,7 +79,7 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
78
79
|
|
|
79
80
|
elif first_token_line.value == 'elif':
|
|
80
81
|
if not isinstance(last_block, (IfNode, ElifNode)):
|
|
81
|
-
raise SyntaxError(f'[ELIF]
|
|
82
|
+
raise SyntaxError(f'[ELIF] No conditional bloc open on line {first_token_line.position} !')
|
|
82
83
|
elif_node = ElifNode(condition=tokens_by_line[1:], body=[],
|
|
83
84
|
parent=last_block if isinstance(last_block, IfNode) else last_block.parent)
|
|
84
85
|
|
|
@@ -94,10 +95,10 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
94
95
|
|
|
95
96
|
elif first_token_line.value == 'else':
|
|
96
97
|
if not isinstance(last_block, (IfNode, ElifNode)):
|
|
97
|
-
raise SyntaxError(f'[ELSE]
|
|
98
|
+
raise SyntaxError(f'[ELSE] No conditional bloc open on line {first_token_line.position} !')
|
|
98
99
|
|
|
99
100
|
if isinstance(last_block, ElseNode) and last_block.else_body is not None:
|
|
100
|
-
raise SyntaxError(f'[ELSE]
|
|
101
|
+
raise SyntaxError(f'[ELSE] already define !')
|
|
101
102
|
|
|
102
103
|
else_node = ElseNode(body=[])
|
|
103
104
|
|
|
@@ -116,7 +117,7 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
116
117
|
|
|
117
118
|
elif first_token_line.value == '#loop': # si rencontré
|
|
118
119
|
if not isinstance(last_block, LoopNode):
|
|
119
|
-
raise SyntaxError(f'[#LOOP]
|
|
120
|
+
raise SyntaxError(f'[#LOOP] No loop open on line {first_token_line.position} !')
|
|
120
121
|
blocks.pop()
|
|
121
122
|
return blocks, pointeur # on renvoie les informations parsé à la dernière loop ouverte
|
|
122
123
|
|
|
@@ -135,6 +136,18 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
135
136
|
sleep_node = SleepNode(tokens_by_line[1:])
|
|
136
137
|
last_block.body.append(sleep_node)
|
|
137
138
|
|
|
139
|
+
elif first_token_line.value == 'param':
|
|
140
|
+
param_node = ParamNode(body=[])
|
|
141
|
+
last_block.body.append(param_node)
|
|
142
|
+
_, p = parse(token_lines[pointeur + 1:], param_node)
|
|
143
|
+
pointeur += p + 1 # on avance le pointeur de la ligne suivante
|
|
144
|
+
|
|
145
|
+
elif first_token_line.value == '#param':
|
|
146
|
+
if not isinstance(last_block, ParamNode):
|
|
147
|
+
raise SyntaxError(f'[#PARAM] No parameters open on line {first_token_line.position} !')
|
|
148
|
+
blocks.pop() # on supprime le dernier bloc (le paramètre)
|
|
149
|
+
return blocks, pointeur # on renvoie les informations parsé à la dernière paramètre ouverte
|
|
150
|
+
|
|
138
151
|
elif first_token_line.value == '#end': # node pour arrêter le programme si elle est rencontré
|
|
139
152
|
end_node = EndNode()
|
|
140
153
|
last_block.body.append(end_node)
|
|
@@ -152,13 +165,13 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
152
165
|
|
|
153
166
|
elif first_token_line.value == '#embed':
|
|
154
167
|
if not isinstance(last_block, EmbedNode):
|
|
155
|
-
raise SyntaxError(f'[#EMBED]
|
|
168
|
+
raise SyntaxError(f'[#EMBED] No embed open on line {first_token_line.position} !')
|
|
156
169
|
blocks.pop()
|
|
157
170
|
return blocks, pointeur
|
|
158
171
|
|
|
159
172
|
elif first_token_line.value == 'field':
|
|
160
173
|
if not isinstance(last_block, EmbedNode):
|
|
161
|
-
raise SyntaxError(f'[FIELD]
|
|
174
|
+
raise SyntaxError(f'[FIELD] No embed open on line {first_token_line.position} !')
|
|
162
175
|
last_block.fields.append(FieldEmbedNode(tokens_by_line[1:]))
|
|
163
176
|
|
|
164
177
|
elif first_token_line.value in ('perm', 'permission'):
|
|
@@ -170,7 +183,7 @@ def parse(token_lines: list[list[Token]], start_node: ASTNode) -> tuple[list[AST
|
|
|
170
183
|
|
|
171
184
|
elif first_token_line.value in ('#perm', '#permission'):
|
|
172
185
|
if not isinstance(last_block, PermissionNode):
|
|
173
|
-
raise SyntaxError(f'[#PERM]
|
|
186
|
+
raise SyntaxError(f'[#PERM] No permission open on line {first_token_line.position} !')
|
|
174
187
|
blocks.pop()
|
|
175
188
|
return blocks, pointeur
|
|
176
189
|
|
|
@@ -345,3 +358,9 @@ def print_ast(ast: list[ASTNode], decalage: int = 0):
|
|
|
345
358
|
elif isinstance(i, FieldEmbedNode):
|
|
346
359
|
for field in i.body:
|
|
347
360
|
print(f"{' ' * decalage}FIELD -> {field.value}")
|
|
361
|
+
|
|
362
|
+
elif isinstance(i, PermissionNode):
|
|
363
|
+
print(f"{' ' * decalage}PERMISSION -> {i.body}")
|
|
364
|
+
|
|
365
|
+
elif isinstance(i, ParamNode):
|
|
366
|
+
print(f"{' ' * decalage}PARAM -> {i.body}")
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_keywords.py
RENAMED
|
@@ -14,7 +14,7 @@ from ..DISCORD_COMMANDS.dshell_message import *
|
|
|
14
14
|
from ..DISCORD_COMMANDS.dshell_member import *
|
|
15
15
|
|
|
16
16
|
dshell_keyword: set[str] = {
|
|
17
|
-
'if', 'else', 'elif', 'loop', '#end', 'var', '#loop', '#if', 'sleep'
|
|
17
|
+
'if', 'else', 'elif', 'loop', '#end', 'var', '#loop', '#if', 'sleep', 'param', '#param'
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
dshell_discord_keyword: set[str] = {
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="dshellInterpreter",
|
|
8
|
-
version="0.1.
|
|
8
|
+
version="0.1.16",
|
|
9
9
|
author="Chronos",
|
|
10
10
|
author_email="vagabonwalybi@gmail.com",
|
|
11
11
|
description="A Discord bot interpreter for creating custom commands and automations.",
|
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_channel.py
RENAMED
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/DISCORD_COMMANDS/dshell_member.py
RENAMED
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellInterpreteur/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_token_type.py
RENAMED
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/Dshell/_DshellTokenizer/dshell_tokenizer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/requires.txt
RENAMED
|
File without changes
|
{dshellinterpreter-0.1.14 → dshellinterpreter-0.1.16}/dshellInterpreter.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|