verifica 1.0.0.dev1__tar.gz → 1.0.1__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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: verifica
3
+ Version: 1.0.1
4
+ Summary: Uma ferramenta simples para correção de atividades em python via CLI.
5
+ Author-email: Théo Modeneis Ruela <theo.ruela@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/espinafr/Verifica
8
+ Project-URL: Issues, https://github.com/espinafr/Verifica/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: colorama>=0.4.6
15
+ Requires-Dist: platformdirs>=2.5.2
16
+ Dynamic: license-file
17
+
18
+ <div align="center">
19
+ <h1 align="center">Verifica</h1>
20
+ <p align="center">Uma ferramenta simples para correção de atividades em python via CLI.<br>
21
+ <table>
22
+ <tbody>
23
+ <tr>
24
+ <td><a href="https://pypi.org/project/verifica/">Página no Pypi</a></td>
25
+ <td align="right"><a href="https://github.com/espinafr/verifica/issues/new?template=denúncia-de-bug.yml">Reportar bug</a></td>
26
+ </tr>
27
+ <tr>
28
+ <td colspan="2" align="center"><a href="https://github.com/espinafr/verifica/tree/master/docs">Como configurar</a></td>
29
+ </tr>
30
+ </tbody>
31
+ </table>
32
+ </div>
33
+
34
+ ## Sobre o projeto
35
+
36
+ **Verifica** é uma ferramenta simples que permite que professores preparem testes automatizados para corrigir atividades em python, fornecendo aos alunos uma correção instantânea para seus programas antes deles serem enviados oficialmente.
37
+
38
+ ## Como usar
39
+ _Quer saber como criar os arquivos de correção? [Clique aqui](https://github.com/espinafr/verifica/tree/master/docs)_
40
+ 1. Baixe a ferramenta:
41
+ ```bash
42
+ pip install verifica
43
+ ```
44
+
45
+ 2. Faça as atividades propostas com base nas orientações recebidas.
46
+
47
+ 3. No diretório com os arquivos `.py`, execute o comando `python -m verifica` seguido da localização do diretório com os arquivos de correção no github
48
+ ```bash
49
+ python -m verifica usuario/repositorio/branch/localizacao/da/pasta
50
+ ```
51
+
52
+ ## Como compilar
53
+
54
+ Para gerar os arquivos de distribuição do projeto, certifique-se de ter o módulo `build` instalado:
55
+
56
+ ```bash
57
+ pip install build
58
+ ```
59
+
60
+ Em seguida, execute o comando de construção na raiz do projeto:
61
+
62
+ ```bash
63
+ python -m build
64
+ ```
65
+
66
+ Os arquivos gerados (arquivos `.tar.gz` e `.whl`) estarão disponíveis no diretório `dist/`. Use o arquivo `.whl` para distribuição e instalação do pacote via pip.
67
+
68
+ ```bash
69
+ pip install dist/{NOME DO ARQUIVO GERADO}
70
+ ```
71
+
72
+ ## Reconhecimentos
73
+
74
+ Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md).
75
+
76
+ ## Licença
77
+
78
+ Veja o arquivo [LICENSE](https://github.com/espinafr/verifica/blob/master/LICENSE).
@@ -0,0 +1,61 @@
1
+ <div align="center">
2
+ <h1 align="center">Verifica</h1>
3
+ <p align="center">Uma ferramenta simples para correção de atividades em python via CLI.<br>
4
+ <table>
5
+ <tbody>
6
+ <tr>
7
+ <td><a href="https://pypi.org/project/verifica/">Página no Pypi</a></td>
8
+ <td align="right"><a href="https://github.com/espinafr/verifica/issues/new?template=denúncia-de-bug.yml">Reportar bug</a></td>
9
+ </tr>
10
+ <tr>
11
+ <td colspan="2" align="center"><a href="https://github.com/espinafr/verifica/tree/master/docs">Como configurar</a></td>
12
+ </tr>
13
+ </tbody>
14
+ </table>
15
+ </div>
16
+
17
+ ## Sobre o projeto
18
+
19
+ **Verifica** é uma ferramenta simples que permite que professores preparem testes automatizados para corrigir atividades em python, fornecendo aos alunos uma correção instantânea para seus programas antes deles serem enviados oficialmente.
20
+
21
+ ## Como usar
22
+ _Quer saber como criar os arquivos de correção? [Clique aqui](https://github.com/espinafr/verifica/tree/master/docs)_
23
+ 1. Baixe a ferramenta:
24
+ ```bash
25
+ pip install verifica
26
+ ```
27
+
28
+ 2. Faça as atividades propostas com base nas orientações recebidas.
29
+
30
+ 3. No diretório com os arquivos `.py`, execute o comando `python -m verifica` seguido da localização do diretório com os arquivos de correção no github
31
+ ```bash
32
+ python -m verifica usuario/repositorio/branch/localizacao/da/pasta
33
+ ```
34
+
35
+ ## Como compilar
36
+
37
+ Para gerar os arquivos de distribuição do projeto, certifique-se de ter o módulo `build` instalado:
38
+
39
+ ```bash
40
+ pip install build
41
+ ```
42
+
43
+ Em seguida, execute o comando de construção na raiz do projeto:
44
+
45
+ ```bash
46
+ python -m build
47
+ ```
48
+
49
+ Os arquivos gerados (arquivos `.tar.gz` e `.whl`) estarão disponíveis no diretório `dist/`. Use o arquivo `.whl` para distribuição e instalação do pacote via pip.
50
+
51
+ ```bash
52
+ pip install dist/{NOME DO ARQUIVO GERADO}
53
+ ```
54
+
55
+ ## Reconhecimentos
56
+
57
+ Veja o arquivo [ACKNOWLEDGEMENTS](https://github.com/espinafr/verifica/blob/master/ACKNOWLEDGEMENTS.md).
58
+
59
+ ## Licença
60
+
61
+ Veja o arquivo [LICENSE](https://github.com/espinafr/verifica/blob/master/LICENSE).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "verifica"
7
- version = "1.0.0.dev1"
7
+ version = "1.0.1"
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.13"
17
+ requires-python = ">=3.9"
18
18
  classifiers = [
19
19
  "Programming Language :: Python :: 3",
20
20
  "Operating System :: OS Independent",
@@ -1,30 +1,35 @@
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)
18
21
  positional = parser.add_argument_group("argumentos posicionais")
19
22
  options = parser.add_argument_group("opções")
20
23
 
21
- positional.add_argument("atividade", nargs="?", help="A URL do github usada para a correção da atividade.")
24
+ positional.add_argument("atividade", nargs="?", help="A URL do github ou caminho local do arquivo usado para a correção da atividade.")
22
25
  options.add_argument("-f", "--files", default=Path.cwd(), help="Diretório com a(s) atividade(s) a ser(em) corrigida(s).")
23
26
  options.add_argument("-h", "--help", "--ajuda", action="help", help="Mostra essa mensagem de ajuda.")
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
- decoded_answers = answers.get_decoded_json()
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.")