verifica 1.0.0.dev2__tar.gz → 1.0.2__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.
- {verifica-1.0.0.dev2/src/verifica.egg-info → verifica-1.0.2}/PKG-INFO +3 -6
- {verifica-1.0.0.dev2 → verifica-1.0.2}/README.md +1 -4
- {verifica-1.0.0.dev2 → verifica-1.0.2}/pyproject.toml +2 -2
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/__main__.py +20 -2
- verifica-1.0.2/src/verifica/builder.py +447 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/check.py +123 -66
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/config.py +8 -16
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/fetcher.py +8 -1
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/imports_tester.py +1 -1
- verifica-1.0.2/src/verifica/tui.py +104 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2/src/verifica.egg-info}/PKG-INFO +3 -6
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica.egg-info/SOURCES.txt +2 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/LICENSE +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/setup.cfg +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica/__init__.py +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica.egg-info/dependency_links.txt +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica.egg-info/entry_points.txt +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica.egg-info/requires.txt +0 -0
- {verifica-1.0.0.dev2 → verifica-1.0.2}/src/verifica.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: verifica
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Uma ferramenta simples para correção de atividades em python via CLI.
|
|
5
5
|
Author-email: Théo Modeneis Ruela <theo.ruela@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/espinafr/Verifica
|
|
|
8
8
|
Project-URL: Issues, https://github.com/espinafr/Verifica/issues
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.9
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: colorama>=0.4.6
|
|
@@ -24,9 +24,6 @@ Dynamic: license-file
|
|
|
24
24
|
<td><a href="https://pypi.org/project/verifica/">Página no Pypi</a></td>
|
|
25
25
|
<td align="right"><a href="https://github.com/espinafr/verifica/issues/new?template=denúncia-de-bug.yml">Reportar bug</a></td>
|
|
26
26
|
</tr>
|
|
27
|
-
<tr>
|
|
28
|
-
<td colspan="2" align="center"><a href="https://espinafr.github.io/verifica">Gerador <code>correcao.json</code></a></td>
|
|
29
|
-
</tr>
|
|
30
27
|
<tr>
|
|
31
28
|
<td colspan="2" align="center"><a href="https://github.com/espinafr/verifica/tree/master/docs">Como configurar</a></td>
|
|
32
29
|
</tr>
|
|
@@ -74,7 +71,7 @@ pip install dist/{NOME DO ARQUIVO GERADO}
|
|
|
74
71
|
|
|
75
72
|
## Reconhecimentos
|
|
76
73
|
|
|
77
|
-
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md)
|
|
74
|
+
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md).
|
|
78
75
|
|
|
79
76
|
## Licença
|
|
80
77
|
|
|
@@ -7,9 +7,6 @@
|
|
|
7
7
|
<td><a href="https://pypi.org/project/verifica/">Página no Pypi</a></td>
|
|
8
8
|
<td align="right"><a href="https://github.com/espinafr/verifica/issues/new?template=denúncia-de-bug.yml">Reportar bug</a></td>
|
|
9
9
|
</tr>
|
|
10
|
-
<tr>
|
|
11
|
-
<td colspan="2" align="center"><a href="https://espinafr.github.io/verifica">Gerador <code>correcao.json</code></a></td>
|
|
12
|
-
</tr>
|
|
13
10
|
<tr>
|
|
14
11
|
<td colspan="2" align="center"><a href="https://github.com/espinafr/verifica/tree/master/docs">Como configurar</a></td>
|
|
15
12
|
</tr>
|
|
@@ -57,7 +54,7 @@ pip install dist/{NOME DO ARQUIVO GERADO}
|
|
|
57
54
|
|
|
58
55
|
## Reconhecimentos
|
|
59
56
|
|
|
60
|
-
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md)
|
|
57
|
+
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md).
|
|
61
58
|
|
|
62
59
|
## Licença
|
|
63
60
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "verifica"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"colorama>=0.4.6",
|
|
10
10
|
"platformdirs>=2.5.2"
|
|
@@ -14,7 +14,7 @@ authors = [
|
|
|
14
14
|
]
|
|
15
15
|
description = "Uma ferramenta simples para correção de atividades em python via CLI."
|
|
16
16
|
readme = "README.md"
|
|
17
|
-
requires-python = ">=3.9
|
|
17
|
+
requires-python = ">=3.9"
|
|
18
18
|
classifiers = [
|
|
19
19
|
"Programming Language :: Python :: 3",
|
|
20
20
|
"Operating System :: OS Independent",
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from colorama import init
|
|
3
4
|
import logging
|
|
4
5
|
import sys
|
|
5
6
|
|
|
6
7
|
from .config import settings
|
|
7
8
|
from .check import Checker
|
|
8
9
|
from .fetcher import Fetcher
|
|
10
|
+
from .builder import Builder
|
|
9
11
|
|
|
10
12
|
logging.basicConfig(
|
|
11
13
|
level=logging.ERROR,
|
|
12
14
|
format='%(levelname)s - %(message)s',
|
|
13
15
|
handlers=[logging.FileHandler(f'{settings.config_dir}/app.log', mode='w', encoding='utf-8'), logging.StreamHandler(stream=sys.stdout)]
|
|
14
16
|
)
|
|
17
|
+
init(autoreset=True)
|
|
15
18
|
|
|
16
19
|
def main():
|
|
17
20
|
parser = argparse.ArgumentParser(description="Uma ferramenta simples para correção de atividades em python via CLI.", add_help=False)
|
|
@@ -24,7 +27,9 @@ def main():
|
|
|
24
27
|
options.add_argument("-c", "--config", action="store_true", help="Mostra o caminho do arquivo de configuração.")
|
|
25
28
|
options.add_argument("-d", "--debug", action="store_true", help="Ativa o modo debug, mostrando mais informações durante a execução.")
|
|
26
29
|
options.add_argument("-l", "--local", action="store_true", help="Caminho local para a pasta com o arquivo de respostas, caso não queira baixar do github.")
|
|
27
|
-
|
|
30
|
+
options.add_argument("--version", action="store_true", help="Mostra a versão do programa.")
|
|
31
|
+
options.add_argument("-b", "--builder", action="store_true", help="TUI para criar arquivos de correção de atividades.")
|
|
32
|
+
|
|
28
33
|
args = parser.parse_args()
|
|
29
34
|
|
|
30
35
|
if args.debug:
|
|
@@ -35,6 +40,15 @@ def main():
|
|
|
35
40
|
print(settings.config_path)
|
|
36
41
|
return
|
|
37
42
|
|
|
43
|
+
if args.version:
|
|
44
|
+
print(settings.get_version())
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if args.builder:
|
|
48
|
+
builder = Builder()
|
|
49
|
+
builder.start()
|
|
50
|
+
return
|
|
51
|
+
|
|
38
52
|
if not args.atividade:
|
|
39
53
|
parser.error("o seguinte argumento é obrigatório: atividade")
|
|
40
54
|
|
|
@@ -55,7 +69,11 @@ def main():
|
|
|
55
69
|
logging.error(f"Não foi possível localizar o arquivo de correção em '{args.atividade}'")
|
|
56
70
|
sys.exit(1)
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
try:
|
|
73
|
+
decoded_answers = answers.get_decoded_json()
|
|
74
|
+
except Exception as _:
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
|
|
59
77
|
answers.cleanup()
|
|
60
78
|
|
|
61
79
|
checker = Checker(args.files, decoded_answers)
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
from colorama import Style, Fore
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import subprocess
|
|
5
|
+
import copy
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from .tui import TUI, ANSI_PATTERN
|
|
11
|
+
from .config import settings
|
|
12
|
+
|
|
13
|
+
# Código ANSI para limpar a linha atual no terminal
|
|
14
|
+
CLEAR_LINE = "\r\033[K"
|
|
15
|
+
|
|
16
|
+
class Controller:
|
|
17
|
+
@staticmethod
|
|
18
|
+
def clear_command():
|
|
19
|
+
if os.name == 'nt':
|
|
20
|
+
subprocess.run(['cmd', '/c', 'cls'])
|
|
21
|
+
else:
|
|
22
|
+
subprocess.run(['clear'])
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def clear_terminal(func):
|
|
26
|
+
@wraps(func)
|
|
27
|
+
def wrapper(*args, **kwargs):
|
|
28
|
+
Controller.clear_command()
|
|
29
|
+
return func(*args, **kwargs)
|
|
30
|
+
return wrapper
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def loop_breaker(func):
|
|
34
|
+
@wraps(func)
|
|
35
|
+
def wrapper(*args, **kwargs):
|
|
36
|
+
try:
|
|
37
|
+
return func(*args, **kwargs)
|
|
38
|
+
except KeyboardInterrupt:
|
|
39
|
+
return None
|
|
40
|
+
return wrapper
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def sequential_question(input_text):
|
|
44
|
+
answers = []
|
|
45
|
+
try:
|
|
46
|
+
while True:
|
|
47
|
+
result = input(input_text)
|
|
48
|
+
answers.append(result)
|
|
49
|
+
|
|
50
|
+
# Altera a linha anterior para explicitar que foi salvo
|
|
51
|
+
sys.stdout.write("\033[F\033[K")
|
|
52
|
+
sys.stdout.flush()
|
|
53
|
+
print(f"{Fore.GREEN}{ANSI_PATTERN.sub('', input_text)}{result}{Style.RESET_ALL}")
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
return answers
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def required_question(func):
|
|
59
|
+
@wraps(func)
|
|
60
|
+
def wrapper(*args, **kwargs):
|
|
61
|
+
while True:
|
|
62
|
+
result = func(*args, **kwargs)
|
|
63
|
+
if not isinstance(result, str) or result.strip() == "":
|
|
64
|
+
print(f"{Style.BRIGHT}{Fore.RED}Este campo é obrigatório. Por favor, insira um valor válido.{Style.RESET_ALL}")
|
|
65
|
+
else:
|
|
66
|
+
return result
|
|
67
|
+
return wrapper
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def required_text(text) -> str:
|
|
71
|
+
return f"{Fore.RED}{text}{Style.RESET_ALL}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def optional_text(text) -> str:
|
|
75
|
+
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def sequence_text(text) -> str:
|
|
79
|
+
return f"{Fore.GREEN}{text}{Style.RESET_ALL}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Selector:
|
|
83
|
+
def __init__(self, name: str, action, selected: bool = False):
|
|
84
|
+
self.name = name
|
|
85
|
+
self.action = action
|
|
86
|
+
self.selected = selected
|
|
87
|
+
|
|
88
|
+
class FileBuilder(Controller):
|
|
89
|
+
def __init__(self, file: str):
|
|
90
|
+
self.file = file
|
|
91
|
+
self.selectors = [
|
|
92
|
+
Selector("Classe", self.create_classes),
|
|
93
|
+
Selector("Função", self.create_functions),
|
|
94
|
+
Selector("CLI", self.create_cli),
|
|
95
|
+
Selector("Inputs", self.create_inputs),
|
|
96
|
+
]
|
|
97
|
+
self.current_file = {}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@Controller.clear_terminal
|
|
101
|
+
def show_selector(self):
|
|
102
|
+
if not hasattr(self, 'tui'):
|
|
103
|
+
self.tui = TUI([self.file, {"":f"Selecione o tipo de funcionalidade que você quer testar no arquivo {Style.BRIGHT}{Fore.CYAN}{self.file}{Style.RESET_ALL}. Digite o número correspondente e aperte ENTER."}, {}], max_line=50)
|
|
104
|
+
|
|
105
|
+
for index, selector in enumerate(self.selectors):
|
|
106
|
+
self.tui.contents[2][f"{Style.BRIGHT}{Fore.CYAN}[{index + 1}]"] = f"{Fore.GREEN if selector.selected else Fore.WHITE}{selector.name}"
|
|
107
|
+
|
|
108
|
+
self.tui.show()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def index_is_selectable(self, index: int) -> bool:
|
|
112
|
+
try:
|
|
113
|
+
index = int(index)
|
|
114
|
+
available_selectors = len(self.selectors)
|
|
115
|
+
if index <= available_selectors and index >= 1:
|
|
116
|
+
return True
|
|
117
|
+
else:
|
|
118
|
+
return False
|
|
119
|
+
except ValueError:
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_selectors(self):
|
|
124
|
+
self.show_selector()
|
|
125
|
+
selecteds = 0
|
|
126
|
+
while True:
|
|
127
|
+
index = Controller.required_question(input)(Controller.required_text("> "))
|
|
128
|
+
if self.index_is_selectable(index):
|
|
129
|
+
self.selectors[int(index) - 1].selected = True
|
|
130
|
+
selecteds += 1
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
self.show_selector()
|
|
134
|
+
while True:
|
|
135
|
+
index = input(Controller.optional_text("> "))
|
|
136
|
+
if index.strip() != "":
|
|
137
|
+
if self.index_is_selectable(index):
|
|
138
|
+
index = int(index)
|
|
139
|
+
self.selectors[index - 1].selected = not self.selectors[index - 1].selected
|
|
140
|
+
if self.selectors[index - 1].selected:
|
|
141
|
+
selecteds += 1
|
|
142
|
+
else:
|
|
143
|
+
selecteds -= 1
|
|
144
|
+
self.show_selector()
|
|
145
|
+
else:
|
|
146
|
+
print(Controller.required_text("Indice inválido."))
|
|
147
|
+
else:
|
|
148
|
+
if selecteds == 0:
|
|
149
|
+
print(Controller.required_text("Pelo menos um item deve ser selecionado."))
|
|
150
|
+
else:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def create_structure(self):
|
|
155
|
+
if not "STRUCTURE" in self.current_file:
|
|
156
|
+
self.current_file["STRUCTURE"] = {}
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@Controller.clear_terminal
|
|
161
|
+
def create_classes(self):
|
|
162
|
+
self.create_structure()
|
|
163
|
+
if not "CLASSES" in self.current_file["STRUCTURE"]:
|
|
164
|
+
self.current_file["STRUCTURE"]["CLASSES"] = []
|
|
165
|
+
|
|
166
|
+
print(f"Iniciando registro de classes para o arquivo {Style.BRIGHT}{Fore.CYAN}{self.file}{Style.RESET_ALL}.")
|
|
167
|
+
while True:
|
|
168
|
+
current_class = {}
|
|
169
|
+
class_name = Controller.required_question(input)(Controller.required_text("Nome da classe: "))
|
|
170
|
+
current_class["name"] = class_name
|
|
171
|
+
|
|
172
|
+
is_initialized = Controller.required_question(input)(Controller.required_text("A classe possui um método __init__? (s/n): "))
|
|
173
|
+
if is_initialized.strip()[0].lower() == "s":
|
|
174
|
+
current_class["initializer"] = {}
|
|
175
|
+
current_class["initialized"] = True
|
|
176
|
+
while True:
|
|
177
|
+
print(f"Digite os parâmetros do método __init__ (um por vez). {Style.DIM}Aperte CTRL-C para parar.")
|
|
178
|
+
inputs = Controller.sequential_question(Controller.optional_text("> "))
|
|
179
|
+
current_class["initializer"]["input"] = inputs if inputs else []
|
|
180
|
+
|
|
181
|
+
class_info = input(Controller.optional_text(f"{CLEAR_LINE}Descrição da validação (instancialização da classe): "))
|
|
182
|
+
if class_info.strip() != "":
|
|
183
|
+
current_class["initializer"]["info"] = class_info
|
|
184
|
+
break
|
|
185
|
+
|
|
186
|
+
while True:
|
|
187
|
+
add_method = Controller.required_question(input)(Controller.required_text("Deseja adicionar um método à classe? (s/n): "))
|
|
188
|
+
if add_method[0].lower() == "s":
|
|
189
|
+
if not "methods" in current_class:
|
|
190
|
+
current_class["methods"] = []
|
|
191
|
+
method_data = {}
|
|
192
|
+
|
|
193
|
+
method_name = Controller.required_question(input)(Controller.required_text("Nome do método: "))
|
|
194
|
+
method_data["name"] = method_name
|
|
195
|
+
|
|
196
|
+
print(f"Digite os parâmetros do método {Style.DIM}Aperte CTRL-C para parar.")
|
|
197
|
+
method_inputs = Controller.sequential_question(Controller.optional_text("> "))
|
|
198
|
+
method_data["input"] = method_inputs if method_inputs else []
|
|
199
|
+
|
|
200
|
+
is_static = Controller.required_question(input)(Controller.required_text(f"{CLEAR_LINE}O método é estático? (s/n): "))
|
|
201
|
+
method_data["static"] = True if is_static.strip()[0].lower() == "s" else False
|
|
202
|
+
|
|
203
|
+
expected_output = input(Controller.optional_text("Output esperado: "))
|
|
204
|
+
method_data["expected"] = expected_output if expected_output else ""
|
|
205
|
+
|
|
206
|
+
method_info = input(Controller.optional_text("Descrição da validação: "))
|
|
207
|
+
if method_info.strip() != "":
|
|
208
|
+
method_data["info"] = method_info
|
|
209
|
+
current_class["methods"].append(method_data)
|
|
210
|
+
else:
|
|
211
|
+
break
|
|
212
|
+
|
|
213
|
+
self.current_file["STRUCTURE"]["CLASSES"].append(current_class)
|
|
214
|
+
print(f"Classe {Style.BRIGHT}{Fore.CYAN}{class_name}{Style.RESET_ALL} adicionada com sucesso!")
|
|
215
|
+
new_class = Controller.required_question(input)(Controller.required_text("Deseja adicionar outra classe? (s/n): "))
|
|
216
|
+
if new_class.strip()[0].lower() != "s":
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@Controller.clear_terminal
|
|
222
|
+
def create_functions(self):
|
|
223
|
+
self.create_structure()
|
|
224
|
+
if not "FUNCTIONS" in self.current_file["STRUCTURE"]:
|
|
225
|
+
self.current_file["STRUCTURE"]["FUNCTIONS"] = []
|
|
226
|
+
|
|
227
|
+
print(f"Iniciando registro de funções para o arquivo {Style.BRIGHT}{Fore.CYAN}{self.file}{Style.RESET_ALL}.")
|
|
228
|
+
while True:
|
|
229
|
+
current_function = {}
|
|
230
|
+
function_name = Controller.required_question(input)(Controller.required_text("Nome da função: "))
|
|
231
|
+
current_function["name"] = function_name
|
|
232
|
+
|
|
233
|
+
print("Bateria de testes da função")
|
|
234
|
+
current_function["runs"] = []
|
|
235
|
+
while True:
|
|
236
|
+
current_run = {}
|
|
237
|
+
|
|
238
|
+
print(f"Digite os inputs necessários para testar a função. {Style.DIM}Aperte CTRL-C para parar.")
|
|
239
|
+
inputs = Controller.sequential_question(Controller.optional_text("> "))
|
|
240
|
+
current_run["input"] = inputs if inputs else []
|
|
241
|
+
|
|
242
|
+
expected = input(Controller.optional_text(f"{CLEAR_LINE}Output esperado: "))
|
|
243
|
+
current_run["expected"] = expected if expected else ""
|
|
244
|
+
|
|
245
|
+
info = input(Controller.optional_text("Descrição da validação: "))
|
|
246
|
+
if info.strip() != "":
|
|
247
|
+
current_run["info"] = info
|
|
248
|
+
|
|
249
|
+
current_function["runs"].append(current_run)
|
|
250
|
+
new_run = Controller.required_question(input)(Controller.required_text("Deseja adicionar outra bateria de testes? (s/n): "))
|
|
251
|
+
if new_run.strip()[0].lower() != "s":
|
|
252
|
+
break
|
|
253
|
+
|
|
254
|
+
self.current_file["STRUCTURE"]["FUNCTIONS"].append(current_function)
|
|
255
|
+
print(f"Função {Style.BRIGHT}{Fore.CYAN}{function_name}{Style.RESET_ALL} adicionada com sucesso!")
|
|
256
|
+
|
|
257
|
+
new_class = Controller.required_question(input)(Controller.required_text("Deseja adicionar outra função? (s/n): "))
|
|
258
|
+
if new_class.strip()[0].lower() != "s":
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@Controller.clear_terminal
|
|
264
|
+
def create_cli(self):
|
|
265
|
+
self.current_file["CLI"] = []
|
|
266
|
+
print(f"Iniciando registro de CLI para o arquivo {Style.BRIGHT}{Fore.CYAN}{self.file}{Style.RESET_ALL}.")
|
|
267
|
+
while True:
|
|
268
|
+
current_cli = {}
|
|
269
|
+
inputs = input(Controller.optional_text("Argumentos do comando: "))
|
|
270
|
+
current_cli["input"] = inputs if inputs else ""
|
|
271
|
+
|
|
272
|
+
expected = input(Controller.optional_text("Output esperado: "))
|
|
273
|
+
current_cli["expected"] = expected if expected else ""
|
|
274
|
+
|
|
275
|
+
info = input(Controller.optional_text("Descrição da validação: "))
|
|
276
|
+
if info.strip() != "":
|
|
277
|
+
current_cli["info"] = info
|
|
278
|
+
|
|
279
|
+
self.current_file["CLI"].append(current_cli)
|
|
280
|
+
print(f"{Style.BRIGHT}{Fore.CYAN}Comando registrado com sucesso!")
|
|
281
|
+
new_run = Controller.required_question(input)(Controller.required_text("Deseja adicionar outro comando CLI? (s/n): "))
|
|
282
|
+
if new_run.strip()[0].lower() != "s":
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@Controller.clear_terminal
|
|
287
|
+
def create_inputs(self):
|
|
288
|
+
print(f"Iniciando registro de inputs para o arquivo {Style.BRIGHT}{Fore.CYAN}{self.file}{Style.RESET_ALL}.")
|
|
289
|
+
self.current_file["INPUTS"] = []
|
|
290
|
+
while True:
|
|
291
|
+
current_seqinput = {}
|
|
292
|
+
print(f"Digite os inputs. {Style.DIM}Aperte CTRL-C para parar.")
|
|
293
|
+
inputs = Controller.sequential_question(Controller.optional_text("> "))
|
|
294
|
+
current_seqinput["input"] = inputs if inputs else []
|
|
295
|
+
|
|
296
|
+
print(f"{CLEAR_LINE}Digite os outputs esperados. {Style.DIM}Aperte CTRL-C para parar.")
|
|
297
|
+
expected = Controller.sequential_question(Controller.optional_text("> "))
|
|
298
|
+
current_seqinput["expected"] = expected if expected else []
|
|
299
|
+
|
|
300
|
+
info = input(Controller.optional_text(f"{CLEAR_LINE}Descrição da validação: "))
|
|
301
|
+
if info.strip() != "":
|
|
302
|
+
current_seqinput["info"] = info
|
|
303
|
+
|
|
304
|
+
self.current_file["INPUTS"].append(current_seqinput)
|
|
305
|
+
print(f"{Style.BRIGHT}{Fore.CYAN}Input registrado com sucesso!")
|
|
306
|
+
new_run = Controller.required_question(input)(Controller.required_text("Deseja adicionar outro input? (s/n): "))
|
|
307
|
+
if new_run.strip()[0].lower() != "s":
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def purge_unselected(self):
|
|
312
|
+
selected = []
|
|
313
|
+
for selector in self.selectors:
|
|
314
|
+
if selector.selected:
|
|
315
|
+
selected.append(selector)
|
|
316
|
+
self.selectors = selected
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def run_selected(self):
|
|
320
|
+
for selector in self.selectors:
|
|
321
|
+
selector.action()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def show_progress(self):
|
|
325
|
+
if not hasattr(self, 'progress_visualizer'):
|
|
326
|
+
self.progress_visualizer = copy.deepcopy(self.current_file)
|
|
327
|
+
visualizer = self.progress_visualizer
|
|
328
|
+
if "STRUCTURE" in visualizer:
|
|
329
|
+
for key in visualizer["STRUCTURE"]:
|
|
330
|
+
visualizer[key] = visualizer["STRUCTURE"][key]
|
|
331
|
+
del visualizer["STRUCTURE"]
|
|
332
|
+
|
|
333
|
+
TUI([f"{Fore.CYAN}{self.file}", *({f"{Fore.CYAN}[{index+1}]": f"{Fore.GREEN}{key}{Style.RESET_ALL} {self.current_file[key]}"} for index, key in enumerate(self.current_file.keys()))], max_line=50).show()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def start(self):
|
|
337
|
+
self.get_selectors()
|
|
338
|
+
self.purge_unselected()
|
|
339
|
+
self.run_selected()
|
|
340
|
+
self.show_progress()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Builder:
|
|
344
|
+
default_info =[
|
|
345
|
+
f"{Fore.YELLOW}{Style.BRIGHT}Gerador de {settings.get_config('answers_file_name')}",
|
|
346
|
+
f"{Style.DIM}Seu progresso vai ficar aqui.",
|
|
347
|
+
{
|
|
348
|
+
f"{Style.BRIGHT}{Fore.RED}Informações importantes:": "",
|
|
349
|
+
f"{Style.BRIGHT}{Fore.CYAN}1.": "Leia atentamente a todos os avisos e instruções antes de prosseguir.",
|
|
350
|
+
f"{Style.BRIGHT}{Fore.CYAN}2.": f"Inputs {Fore.RED}VERMELHOS{Style.RESET_ALL} são obrigatórios, enquanto inputs {Fore.YELLOW}AMARELOS{Style.RESET_ALL} são opcionais.",
|
|
351
|
+
f"{Style.BRIGHT}{Fore.CYAN}3.": f"Alguns inputs exigem uma sequência de informções. Esses serão indicados com {Style.BRIGHT}setas (>){Style.RESET_ALL}.\nPara esse tipo de input, digite cada informação e aperte ENTER para confirmar. Inputs registrados com sucesso ficarão {Fore.GREEN}verdes{Style.RESET_ALL}.\nPara parar de adicionar informações, aperte {Style.BRIGHT}CTRL-C{Style.RESET_ALL} (salvo em situações que explicitamente indiquem o contrário).",
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
|
|
355
|
+
@staticmethod
|
|
356
|
+
def confirm_yield():
|
|
357
|
+
input("Pressione ENTER para continuar...")
|
|
358
|
+
|
|
359
|
+
def __init__(self):
|
|
360
|
+
self.build = {}
|
|
361
|
+
self.info = TUI(self.default_info, max_line=80)
|
|
362
|
+
|
|
363
|
+
@Controller.clear_terminal
|
|
364
|
+
def show_info(self):
|
|
365
|
+
self.info.show()
|
|
366
|
+
|
|
367
|
+
def add_block(self, name: str, content: str):
|
|
368
|
+
self.build[name] = content
|
|
369
|
+
|
|
370
|
+
def show_progress(self, name: str, text: str):
|
|
371
|
+
progress = self.info.contents[1]
|
|
372
|
+
if not isinstance(progress, dict):
|
|
373
|
+
progress = {}
|
|
374
|
+
|
|
375
|
+
progress[f"{Style.BRIGHT}{name}"] = text
|
|
376
|
+
self.info.contents[1] = progress
|
|
377
|
+
|
|
378
|
+
self.show_info()
|
|
379
|
+
|
|
380
|
+
def get_files(self):
|
|
381
|
+
files = []
|
|
382
|
+
|
|
383
|
+
def format_filename(filename: str) -> str:
|
|
384
|
+
if not filename.endswith(".py"):
|
|
385
|
+
filename += ".py"
|
|
386
|
+
return filename
|
|
387
|
+
|
|
388
|
+
def get_extra_files() -> list:
|
|
389
|
+
extra_files = []
|
|
390
|
+
while True:
|
|
391
|
+
extra_file = input(f"{Controller.optional_text('> ')}")
|
|
392
|
+
if extra_file.strip() != "":
|
|
393
|
+
extra_files.append(format_filename(extra_file))
|
|
394
|
+
else:
|
|
395
|
+
break
|
|
396
|
+
return extra_files
|
|
397
|
+
|
|
398
|
+
print(f"\nNomeie os arquivos que você quer analisar. O primeiro arquivo é obrigatório.\n{Fore.YELLOW}OBSERVAÇÃO: A extensão .py será adicionada automaticamente.")
|
|
399
|
+
files.append(format_filename(Controller.required_question(input)(f"{Controller.required_text('Nome do primeiro arquivo: ')}")))
|
|
400
|
+
print(f"\nSe quiser adicionar mais arquivos, digite o nome deles um por vez.\n{Style.DIM}Para parar aperte ENTER sem digitar nada.")
|
|
401
|
+
files.extend(get_extra_files())
|
|
402
|
+
|
|
403
|
+
return list(dict.fromkeys(files))
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@Controller.clear_terminal
|
|
407
|
+
def save_file(self):
|
|
408
|
+
assignment_name = Controller.required_question(input)(f"{Controller.required_text('Nome da atividade: ')}")
|
|
409
|
+
save_path = Path.cwd() / assignment_name
|
|
410
|
+
|
|
411
|
+
print(f"O arquivo \"correcao.json\" será salvo em {save_path}.")
|
|
412
|
+
change_path = input(f"{Controller.optional_text('Deseja alterar o caminho? (s/n): ')}")
|
|
413
|
+
|
|
414
|
+
if change_path and change_path.strip()[0].lower() == "s":
|
|
415
|
+
user_input = Controller.required_question(input)(f"{Controller.required_text('Novo caminho: ')}")
|
|
416
|
+
save_path = Path(user_input)
|
|
417
|
+
|
|
418
|
+
file_path = save_path / "correcao.json"
|
|
419
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
420
|
+
|
|
421
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
|
422
|
+
json.dump(self.build, file, indent=4, ensure_ascii=False)
|
|
423
|
+
|
|
424
|
+
print(f"Arquivo salvo em {file_path}.")
|
|
425
|
+
|
|
426
|
+
def start(self):
|
|
427
|
+
try:
|
|
428
|
+
self.show_info()
|
|
429
|
+
|
|
430
|
+
self.confirm_yield()
|
|
431
|
+
files = self.get_files()
|
|
432
|
+
self.add_block("files", files)
|
|
433
|
+
self.show_progress("Arquivos selecionados: ", f"{'; '.join(files)}.")
|
|
434
|
+
self.confirm_yield()
|
|
435
|
+
|
|
436
|
+
Controller.clear_command()
|
|
437
|
+
for file in files:
|
|
438
|
+
fb = FileBuilder(file)
|
|
439
|
+
fb.start()
|
|
440
|
+
self.add_block(file, fb.current_file)
|
|
441
|
+
|
|
442
|
+
self.confirm_yield()
|
|
443
|
+
|
|
444
|
+
self.save_file()
|
|
445
|
+
except KeyboardInterrupt:
|
|
446
|
+
Controller.clear_command()
|
|
447
|
+
print(f"{Fore.RED}Operação cancelada pelo usuário. Nenhum arquivo foi salvo.")
|
|
@@ -7,32 +7,45 @@ import sys
|
|
|
7
7
|
from types import SimpleNamespace
|
|
8
8
|
|
|
9
9
|
from . import imports_tester
|
|
10
|
-
from .config import settings
|
|
11
10
|
|
|
12
11
|
class Checker:
|
|
12
|
+
"""Classe responsável por validar exercícios baseado em um arquivo de correção"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, exercises_path: str, answers: dict):
|
|
14
|
+
def __init__(self, exercises_path: str, answers: dict) -> None:
|
|
15
15
|
"""Atribui os parâmetros passados para o objeto
|
|
16
|
-
|
|
17
|
-
:
|
|
18
|
-
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
exercises_path (str): Caminho da pasta com arquivos do exercício
|
|
19
|
+
answers (dict): Dicionário de respostas no formato apropriado
|
|
19
20
|
"""
|
|
20
21
|
self.exercises_path = exercises_path
|
|
21
22
|
self.answers = answers
|
|
22
23
|
self.roadmap = []
|
|
23
24
|
self.logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
def __file_exists(self, file_path: str) -> bool:
|
|
26
28
|
"""Checa se um arquivo existe em determinado caminho
|
|
27
29
|
|
|
28
|
-
:
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
Args:
|
|
31
|
+
file_path (str): Caminho do arquivo
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
bool: Booleano indicando se o arquivo existe
|
|
31
35
|
"""
|
|
32
36
|
return Path(self.exercises_path / file_path).is_file()
|
|
33
37
|
|
|
38
|
+
|
|
34
39
|
def setup_roadmap(self) -> bool:
|
|
35
|
-
"""Popula a lista roadmap com uma sequência de testes a serem realizados
|
|
40
|
+
"""Popula a lista roadmap com uma sequência de testes a serem realizados
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ValueError: Arquivo de correção não possui características a serem testadas
|
|
44
|
+
ValueError: Característica desconhecida no arquivo de correção
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
bool: Booleano indicando se o roadmap foi configurado com sucesso
|
|
48
|
+
"""
|
|
36
49
|
try:
|
|
37
50
|
self.logger.info(f"Configurando roadmap")
|
|
38
51
|
for file in self.answers["files"]:
|
|
@@ -70,7 +83,8 @@ class Checker:
|
|
|
70
83
|
for class_info in subsequent_steps["CLASSES"]:
|
|
71
84
|
self.logger.info(f"ADICIONANDO CLASSE {class_info['name']}")
|
|
72
85
|
is_initialized = class_info.get("initialized", False)
|
|
73
|
-
|
|
86
|
+
has_methods = class_info.get("methods", []) != []
|
|
87
|
+
currentClass = imports_tester.ClassTester(importedFile, class_info["name"], class_info["methods"] if has_methods else [], is_initialized)
|
|
74
88
|
self.roadmap.append({
|
|
75
89
|
"info": class_info.get("info", f"classe {class_info['name']} existe e possui os métodos esperados"),
|
|
76
90
|
"args": [currentClass],
|
|
@@ -79,15 +93,16 @@ class Checker:
|
|
|
79
93
|
if is_initialized:
|
|
80
94
|
self.roadmap.append({
|
|
81
95
|
"info": class_info.get("info", f"classe {class_info['name']} pode ser instanciada"),
|
|
82
|
-
"args": [currentClass, *class_info["initializer"]["
|
|
96
|
+
"args": [currentClass, *class_info["initializer"]["input"]],
|
|
83
97
|
"action": imports_tester.ClassTester.initialize_instance
|
|
84
98
|
})
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
if has_methods:
|
|
100
|
+
for method_info in class_info["methods"]:
|
|
101
|
+
self.roadmap.append({
|
|
102
|
+
"info": method_info.get("info", f"{method_info['name']}({', '.join(map(str, method_info['input']))}) retorna {method_info['expected']}"),
|
|
103
|
+
"args": [method_info["name"], method_info.get("static", False), method_info["input"], method_info["expected"]],
|
|
104
|
+
"action": currentClass.test_method
|
|
105
|
+
})
|
|
91
106
|
if subsequent_steps.get("FUNCTIONS"):
|
|
92
107
|
self.logger.info(f"FUNÇÕES DETECTADAS")
|
|
93
108
|
for function_info in subsequent_steps["FUNCTIONS"]:
|
|
@@ -105,21 +120,22 @@ class Checker:
|
|
|
105
120
|
"args": [currentFunction, run["input"], run["expected"]],
|
|
106
121
|
"action": imports_tester.FunctionTester.test
|
|
107
122
|
})
|
|
108
|
-
elif check_step == "INPUTS":
|
|
109
|
-
for input_info in subsequent_steps:
|
|
110
|
-
self.logger.info(f"ADICIONANDO INPUT {input_info['input']}")
|
|
111
|
-
self.roadmap.append({
|
|
112
|
-
"info": input_info.get("info", f"input '{input_info['input']}' retorna '{input_info['expected']}'"),
|
|
113
|
-
"args": [current_file_path, input_info["input"], input_info["expected"]],
|
|
114
|
-
"action": self.test_INPUT
|
|
115
|
-
})
|
|
116
|
-
elif check_step == "SEQUENCE_INPUTS":
|
|
123
|
+
elif check_step == "INPUTS" or check_step == "SEQUENCE_INPUTS":
|
|
117
124
|
for sequence_input_info in subsequent_steps:
|
|
118
|
-
self.logger.info(f"ADICIONANDO
|
|
125
|
+
self.logger.info(f"ADICIONANDO INPUTS {sequence_input_info['input']}")
|
|
126
|
+
|
|
127
|
+
expected = sequence_input_info['expected']
|
|
128
|
+
input_data = sequence_input_info['input']
|
|
129
|
+
|
|
130
|
+
if isinstance(expected, str):
|
|
131
|
+
expected = [expected]
|
|
132
|
+
if isinstance(input_data, str):
|
|
133
|
+
input_data = [input_data]
|
|
134
|
+
|
|
119
135
|
self.roadmap.append({
|
|
120
|
-
"info": sequence_input_info.get("info", f"input '{', '.join(
|
|
121
|
-
"args": [current_file_path,
|
|
122
|
-
"action": self.
|
|
136
|
+
"info": sequence_input_info.get("info", f"input '{', '.join(input_data)}' retorna '{', '.join(expected)}'"),
|
|
137
|
+
"args": [current_file_path, input_data, expected],
|
|
138
|
+
"action": self.test_INPUTS
|
|
123
139
|
})
|
|
124
140
|
else:
|
|
125
141
|
raise ValueError(f"Característica desconhecida '{check_step}' no arquivo de correção")
|
|
@@ -135,65 +151,104 @@ class Checker:
|
|
|
135
151
|
|
|
136
152
|
|
|
137
153
|
def test_CLI(self, file_path: str, input_args: list[str], expected_output: str) -> bool:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
self.logger.warning(f"Detalhes do erro: {e.stderr}")
|
|
145
|
-
return False
|
|
154
|
+
"""Testa um comando CLI de um programa Python
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
file_path (str): Caminho do arquivo a ser testado
|
|
158
|
+
input_data (list[str]): Lista de inputs a serem fornecidos ao programa
|
|
159
|
+
expected_output (str): Saída esperada
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
Returns:
|
|
162
|
+
bool: Booleano indicando se a saída do programa contém a saída esperada
|
|
163
|
+
"""
|
|
148
164
|
try:
|
|
149
165
|
result = subprocess.run(
|
|
150
|
-
[sys.executable,
|
|
151
|
-
input=input_data,
|
|
166
|
+
[sys.executable, file_path] + input_args,
|
|
152
167
|
capture_output=True,
|
|
153
168
|
text=True
|
|
154
169
|
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
170
|
+
|
|
171
|
+
if result.returncode != 0:
|
|
172
|
+
self.logger.warning(
|
|
173
|
+
f"[CLI] O programa terminou com código {result.returncode}"
|
|
174
|
+
)
|
|
175
|
+
self.logger.warning(f"[CLI] stderr: {result.stderr}")
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
output = result.stdout.strip()
|
|
179
|
+
self.logger.debug(f"[CLI] Saída do comando '{' '.join(input_args)}': {output}")
|
|
180
|
+
|
|
181
|
+
return expected_output in output
|
|
182
|
+
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.logger.warning(
|
|
185
|
+
f"[CLI] Falha ao executar o comando '{file_path} {' '.join(input_args)}': {e}"
|
|
186
|
+
)
|
|
159
187
|
return False
|
|
160
188
|
|
|
161
|
-
def
|
|
189
|
+
def test_INPUTS(self, file_path: str, input_data: list[str], expected_output: list[str]) -> bool:
|
|
190
|
+
"""Testa uma sequência de inputs
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
file_path (str): Caminho do arquivo a ser testado
|
|
194
|
+
input_data (list[str]): Lista de inputs a serem fornecidos ao programa
|
|
195
|
+
expected_output (list[str]): Lista de saídas esperadas
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
bool: Booleano indicando se a saída do programa contém a saída esperada
|
|
199
|
+
"""
|
|
162
200
|
try:
|
|
163
201
|
result = subprocess.run(
|
|
164
|
-
[sys.executable,
|
|
165
|
-
input=
|
|
166
|
-
capture_output=True,
|
|
202
|
+
[sys.executable, file_path],
|
|
203
|
+
input="\n".join(input_data) + "\n",
|
|
204
|
+
capture_output=True,
|
|
167
205
|
text=True
|
|
168
206
|
)
|
|
207
|
+
|
|
208
|
+
if result.returncode != 0:
|
|
209
|
+
self.logger.warning(
|
|
210
|
+
f"[INPUT] O programa terminou com código {result.returncode}"
|
|
211
|
+
)
|
|
212
|
+
self.logger.warning(f"[INPUT] stderr: {result.stderr}")
|
|
213
|
+
return False
|
|
214
|
+
|
|
169
215
|
output = result.stdout.strip()
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
216
|
+
self.logger.debug(f"[INPUT] Saída do input: {output}")
|
|
217
|
+
|
|
218
|
+
return all(
|
|
219
|
+
expected in output
|
|
220
|
+
for expected in expected_output
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
self.logger.warning(
|
|
225
|
+
f"[INPUT] Falha ao executar o input '{input_data}': {e}"
|
|
226
|
+
)
|
|
174
227
|
return False
|
|
175
228
|
|
|
229
|
+
|
|
176
230
|
def make_result_message(self, result: bool, info: str) -> str:
|
|
177
|
-
"""
|
|
231
|
+
"""Forma uma mensagem de resultado com cores e estilo apropriados
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
result (bool): Resultado do teste
|
|
235
|
+
info (str): Informação sobre o teste
|
|
178
236
|
|
|
179
|
-
:
|
|
180
|
-
|
|
181
|
-
:returns: Mensagem formatada
|
|
182
|
-
:rtype: str
|
|
237
|
+
Returns:
|
|
238
|
+
str: Mensagem formatada
|
|
183
239
|
"""
|
|
184
240
|
colors = {
|
|
185
|
-
True: Fore.GREEN
|
|
186
|
-
False: Fore.RED
|
|
187
|
-
"bold": Style.BRIGHT if settings.enviroment_supports_colors else "",
|
|
188
|
-
"reset": Style.RESET_ALL if settings.enviroment_supports_colors else ""
|
|
241
|
+
True: Fore.GREEN,
|
|
242
|
+
False: Fore.RED
|
|
189
243
|
}
|
|
190
|
-
return f"{colors[result]}{
|
|
244
|
+
return f"{colors[result]}{Style.BRIGHT}{':)' if result else ':('}{Style.RESET_ALL} {colors[result]}{info}{Style.RESET_ALL}"
|
|
245
|
+
|
|
191
246
|
|
|
192
247
|
def run_roadmap(self) -> list:
|
|
193
248
|
"""Executa os testes do roadmap e retorna uma lista de resultados
|
|
194
249
|
|
|
195
|
-
:
|
|
196
|
-
|
|
250
|
+
Returns:
|
|
251
|
+
list: Lista de resultados dos testes
|
|
197
252
|
"""
|
|
198
253
|
results = []
|
|
199
254
|
for step in self.roadmap:
|
|
@@ -208,10 +263,12 @@ class Checker:
|
|
|
208
263
|
|
|
209
264
|
return results
|
|
210
265
|
|
|
266
|
+
|
|
211
267
|
def show_results(self, results: list) -> None:
|
|
212
268
|
"""Exibe os resultados dos testes no console
|
|
213
269
|
|
|
214
|
-
:
|
|
270
|
+
Args:
|
|
271
|
+
results (list): Lista de resultados dos testes
|
|
215
272
|
"""
|
|
216
273
|
if self.answers.get("description"):
|
|
217
274
|
print(f"{self.answers['description']}")
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
from platformdirs import user_config_dir
|
|
2
2
|
from importlib.metadata import version, PackageNotFoundError
|
|
3
|
-
from colorama import init
|
|
4
3
|
from time import time
|
|
5
4
|
import json
|
|
6
|
-
import sys
|
|
7
5
|
import os
|
|
8
6
|
|
|
9
|
-
def setup_colors():
|
|
10
|
-
if not sys.stdout.isatty():
|
|
11
|
-
init(strip=True, convert=False)
|
|
12
|
-
return False
|
|
13
|
-
|
|
14
|
-
init(autoreset=True)
|
|
15
|
-
return True
|
|
16
7
|
|
|
17
8
|
class Config:
|
|
18
9
|
_instance = None
|
|
@@ -26,7 +17,6 @@ class Config:
|
|
|
26
17
|
if hasattr(self, "_initialized"):
|
|
27
18
|
return
|
|
28
19
|
self._initialized = True
|
|
29
|
-
self.enviroment_supports_colors = setup_colors()
|
|
30
20
|
|
|
31
21
|
config_dir = user_config_dir("verifica")
|
|
32
22
|
os.makedirs(config_dir, exist_ok=True)
|
|
@@ -49,13 +39,8 @@ class Config:
|
|
|
49
39
|
return {}
|
|
50
40
|
|
|
51
41
|
def __check_config_state(self):
|
|
52
|
-
try:
|
|
53
|
-
current_version = version("verifica")
|
|
54
|
-
except PackageNotFoundError:
|
|
55
|
-
current_version = f"indev-{time()}"
|
|
56
|
-
|
|
57
42
|
defaultConfig = {
|
|
58
|
-
"version":
|
|
43
|
+
"version": self.get_version(),
|
|
59
44
|
"url": "https://raw.githubusercontent.com",
|
|
60
45
|
"answers_file_name": "correcao.json"
|
|
61
46
|
}
|
|
@@ -67,6 +52,13 @@ class Config:
|
|
|
67
52
|
if current_config.get("version") != defaultConfig.get("version"):
|
|
68
53
|
self.update_config(defaultConfig, defaultConfig.get("version"))
|
|
69
54
|
|
|
55
|
+
def get_version(self):
|
|
56
|
+
try:
|
|
57
|
+
__version__ = version("verifica")
|
|
58
|
+
except PackageNotFoundError:
|
|
59
|
+
__version__ = f"indev-{time()}"
|
|
60
|
+
return __version__
|
|
61
|
+
|
|
70
62
|
def update_config(self, new_data: dict, version: str = None):
|
|
71
63
|
current_config = self.__read_keys()
|
|
72
64
|
if version:
|
|
@@ -68,7 +68,14 @@ class Fetcher:
|
|
|
68
68
|
if not self.file:
|
|
69
69
|
raise ValueError("o arquivo de correção não existe")
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
try:
|
|
72
|
+
decoded = json.loads(self.get_content())
|
|
73
|
+
except json.JSONDecodeError as error:
|
|
74
|
+
self.logger.error(f"Falha ao decodificar o arquivo de correção '{self.exercise}': {error}")
|
|
75
|
+
raise RuntimeError(f"Falha ao decodificar o arquivo de correção '{self.exercise}'") from error
|
|
76
|
+
|
|
77
|
+
return decoded
|
|
78
|
+
|
|
72
79
|
|
|
73
80
|
def cleanup(self):
|
|
74
81
|
if self.file != None:
|
|
@@ -39,7 +39,7 @@ class Imported:
|
|
|
39
39
|
raise ImportError(f"Falha ao importar o módulo '{module_path}'") from e
|
|
40
40
|
|
|
41
41
|
class ClassTester:
|
|
42
|
-
def __init__(self, import_info: Imported, class_name: str, methods: list, initialized: bool):
|
|
42
|
+
def __init__(self, import_info: Imported, class_name: str, methods: list = [], initialized: bool = False):
|
|
43
43
|
self.import_info = import_info
|
|
44
44
|
self.class_name = class_name
|
|
45
45
|
self.methods = methods
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from colorama import Style
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
# Regex para códigos ANSI de cores e formatação
|
|
5
|
+
ANSI_PATTERN = re.compile(r"(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]")
|
|
6
|
+
|
|
7
|
+
class TUI:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def visible_len(text: str) -> int:
|
|
10
|
+
"""Retorna o tamanho de uma string sem caracteres de formatação ANSI"""
|
|
11
|
+
return len(ANSI_PATTERN.sub("", text))
|
|
12
|
+
|
|
13
|
+
def __init__(self, contents: list, show: bool = False, max_line: int = -1, padding: int = 1):
|
|
14
|
+
self.contents = contents
|
|
15
|
+
self.padding = padding
|
|
16
|
+
self.max_line = max_line
|
|
17
|
+
self.min_size = self.__get_longest_message_length() + (padding * 2)
|
|
18
|
+
self.row_size = self.min_size + self.padding
|
|
19
|
+
if show:
|
|
20
|
+
self.show()
|
|
21
|
+
|
|
22
|
+
def __get_longest_message_length(self):
|
|
23
|
+
longest = 0
|
|
24
|
+
for block in self.contents:
|
|
25
|
+
if isinstance(block, dict):
|
|
26
|
+
for k, v in block.items():
|
|
27
|
+
for line in self.__get_paragraphs(k, self.__create_paragraphs(k, v)):
|
|
28
|
+
length = self.visible_len(line)
|
|
29
|
+
if length > longest:
|
|
30
|
+
longest = length
|
|
31
|
+
else:
|
|
32
|
+
length = self.visible_len(block)
|
|
33
|
+
if length > longest:
|
|
34
|
+
longest = length
|
|
35
|
+
return longest
|
|
36
|
+
|
|
37
|
+
def __get_paragraphs(self, key: str, text: str) -> list:
|
|
38
|
+
lines = []
|
|
39
|
+
paragraphs = text.split('\n')
|
|
40
|
+
|
|
41
|
+
lines.append(f"{key}{Style.RESET_ALL} {paragraphs[0]}")
|
|
42
|
+
for paragraph in paragraphs[1:]:
|
|
43
|
+
lines.append(f"{' ' * (self.visible_len(key) + 1)}{paragraph}{Style.RESET_ALL}")
|
|
44
|
+
|
|
45
|
+
return lines
|
|
46
|
+
|
|
47
|
+
def __create_paragraphs(self, key: str, text: str) -> str:
|
|
48
|
+
if self.max_line >= 1:
|
|
49
|
+
paragraphs = []
|
|
50
|
+
unformatted_paragraphs = text.split('\n')
|
|
51
|
+
|
|
52
|
+
for paragraph in unformatted_paragraphs:
|
|
53
|
+
words = paragraph.split()
|
|
54
|
+
current_line = ""
|
|
55
|
+
for word in words:
|
|
56
|
+
if self.visible_len(f"{key} {current_line}") + self.visible_len(word) + 1 <= self.max_line:
|
|
57
|
+
current_line += f"{word} "
|
|
58
|
+
else:
|
|
59
|
+
paragraphs.append(current_line.strip())
|
|
60
|
+
current_line = f"{word} "
|
|
61
|
+
|
|
62
|
+
if current_line:
|
|
63
|
+
paragraphs.append(current_line.strip())
|
|
64
|
+
|
|
65
|
+
return '\n'.join(paragraphs)
|
|
66
|
+
return [text]
|
|
67
|
+
|
|
68
|
+
def __create_top_border(self):
|
|
69
|
+
return f"╔{'═' * self.row_size}╗"
|
|
70
|
+
|
|
71
|
+
def __create_internal_border(self):
|
|
72
|
+
return f"╠{'═' * self.row_size}╣"
|
|
73
|
+
|
|
74
|
+
def __create_bottom_border(self):
|
|
75
|
+
return f"╚{'═' * self.row_size}╝"
|
|
76
|
+
|
|
77
|
+
def __create_left_border(self):
|
|
78
|
+
return f"║"
|
|
79
|
+
|
|
80
|
+
def __create_right_border(self):
|
|
81
|
+
return f"║"
|
|
82
|
+
|
|
83
|
+
def __create_center_row(self, text: str):
|
|
84
|
+
return f"{self.__create_left_border()}{" "*self.padding}{text.center(self.min_size - self.padding + (len(text) - self.visible_len(text)))}{Style.RESET_ALL}{" "*self.padding}{self.__create_right_border()}"
|
|
85
|
+
|
|
86
|
+
def __create_row(self, text: str):
|
|
87
|
+
return f"{self.__create_left_border()}{" "*self.padding}{text.ljust(self.min_size - self.padding + (len(text) - self.visible_len(text)))}{" "*self.padding}{Style.RESET_ALL}{self.__create_right_border()}"
|
|
88
|
+
|
|
89
|
+
def show(self):
|
|
90
|
+
last_element = len(self.contents) - 1
|
|
91
|
+
|
|
92
|
+
print(f"{self.__create_top_border()}")
|
|
93
|
+
for index, block in enumerate(self.contents):
|
|
94
|
+
if isinstance(block, dict):
|
|
95
|
+
for key, message in self.contents[index].items():
|
|
96
|
+
for line in self.__get_paragraphs(key, self.__create_paragraphs(key, message)):
|
|
97
|
+
print(f"{self.__create_row(line)}")
|
|
98
|
+
else:
|
|
99
|
+
print(f"{self.__create_center_row(block)}")
|
|
100
|
+
|
|
101
|
+
if index != last_element:
|
|
102
|
+
print(f"{self.__create_internal_border()}")
|
|
103
|
+
else:
|
|
104
|
+
print(f"{self.__create_bottom_border()}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: verifica
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Uma ferramenta simples para correção de atividades em python via CLI.
|
|
5
5
|
Author-email: Théo Modeneis Ruela <theo.ruela@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -8,7 +8,7 @@ Project-URL: Homepage, https://github.com/espinafr/Verifica
|
|
|
8
8
|
Project-URL: Issues, https://github.com/espinafr/Verifica/issues
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.9
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: colorama>=0.4.6
|
|
@@ -24,9 +24,6 @@ Dynamic: license-file
|
|
|
24
24
|
<td><a href="https://pypi.org/project/verifica/">Página no Pypi</a></td>
|
|
25
25
|
<td align="right"><a href="https://github.com/espinafr/verifica/issues/new?template=denúncia-de-bug.yml">Reportar bug</a></td>
|
|
26
26
|
</tr>
|
|
27
|
-
<tr>
|
|
28
|
-
<td colspan="2" align="center"><a href="https://espinafr.github.io/verifica">Gerador <code>correcao.json</code></a></td>
|
|
29
|
-
</tr>
|
|
30
27
|
<tr>
|
|
31
28
|
<td colspan="2" align="center"><a href="https://github.com/espinafr/verifica/tree/master/docs">Como configurar</a></td>
|
|
32
29
|
</tr>
|
|
@@ -74,7 +71,7 @@ pip install dist/{NOME DO ARQUIVO GERADO}
|
|
|
74
71
|
|
|
75
72
|
## Reconhecimentos
|
|
76
73
|
|
|
77
|
-
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md)
|
|
74
|
+
Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md).
|
|
78
75
|
|
|
79
76
|
## Licença
|
|
80
77
|
|
|
@@ -3,10 +3,12 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/verifica/__init__.py
|
|
5
5
|
src/verifica/__main__.py
|
|
6
|
+
src/verifica/builder.py
|
|
6
7
|
src/verifica/check.py
|
|
7
8
|
src/verifica/config.py
|
|
8
9
|
src/verifica/fetcher.py
|
|
9
10
|
src/verifica/imports_tester.py
|
|
11
|
+
src/verifica/tui.py
|
|
10
12
|
src/verifica.egg-info/PKG-INFO
|
|
11
13
|
src/verifica.egg-info/SOURCES.txt
|
|
12
14
|
src/verifica.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|