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.
- verifica-1.0.1/PKG-INFO +78 -0
- verifica-1.0.1/README.md +61 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/pyproject.toml +2 -2
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/__main__.py +21 -3
- verifica-1.0.1/src/verifica/builder.py +447 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/check.py +123 -66
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/config.py +8 -16
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/fetcher.py +8 -1
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/imports_tester.py +1 -1
- verifica-1.0.1/src/verifica/tui.py +104 -0
- verifica-1.0.1/src/verifica.egg-info/PKG-INFO +78 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica.egg-info/SOURCES.txt +2 -0
- verifica-1.0.0.dev1/PKG-INFO +0 -49
- verifica-1.0.0.dev1/README.md +0 -32
- verifica-1.0.0.dev1/src/verifica.egg-info/PKG-INFO +0 -49
- {verifica-1.0.0.dev1 → verifica-1.0.1}/LICENSE +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/setup.cfg +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica/__init__.py +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica.egg-info/dependency_links.txt +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica.egg-info/entry_points.txt +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica.egg-info/requires.txt +0 -0
- {verifica-1.0.0.dev1 → verifica-1.0.1}/src/verifica.egg-info/top_level.txt +0 -0
verifica-1.0.1/PKG-INFO
ADDED
|
@@ -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).
|
verifica-1.0.1/README.md
ADDED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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.")
|