Compare View
Commits (3)
Showing
5 changed files
Show diff stats
demo/demo.yaml
demo/questions/generate-question.py
1 | 1 | #!/usr/bin/env python3 |
2 | 2 | |
3 | 3 | ''' |
4 | -Example of a question generator. | |
5 | -Arguments are read from stdin. | |
4 | +Example of a parameterized question generator. | |
5 | +Parameters are read from stdin. | |
6 | +The generated question is written to stdout in JSON format. | |
6 | 7 | ''' |
7 | 8 | |
8 | -from random import randint | |
9 | +import json | |
10 | +import random | |
9 | 11 | import sys |
10 | 12 | |
11 | 13 | # read two arguments from the field `args` specified in the question yaml file |
12 | 14 | a, b = (int(n) for n in sys.argv[1:]) |
13 | 15 | |
14 | -x = randint(a, b) | |
15 | -y = randint(a, b) | |
16 | +x = random.randint(a, b) | |
17 | +y = random.randint(a, b) | |
16 | 18 | r = x + y |
17 | 19 | |
18 | -print(f"""--- | |
19 | -type: text | |
20 | -title: Geradores de perguntas | |
21 | -text: | | |
22 | - | |
23 | - As perguntas podem ser estáticas (como as que vimos até aqui), ou serem | |
24 | - geradas dinâmicamente por um programa externo. Para gerar uma pergunta, o | |
25 | - programa deve escrever texto no `stdout` em formato `yaml` tal como os | |
26 | - exemplos das perguntas estáticas dos tipos apresentados anteriormente. Pode | |
27 | - também receber argumentos de linha de comando para parametrizar a pergunta. | |
28 | - Aqui está um exemplo de uma pergunta gerada por um script python: | |
29 | - | |
30 | - ```python | |
31 | - #!/usr/bin/env python3 | |
32 | - | |
33 | - from random import randint | |
34 | - import sys | |
35 | - | |
36 | - a, b = (int(n) for n in sys.argv[1:]) # argumentos da linha de comando | |
37 | - | |
38 | - x = randint(a, b) # número inteiro no intervalo a..b | |
39 | - y = randint(a, b) # número inteiro no intervalo a..b | |
40 | - r = x + y # calcula resultado correcto | |
41 | - | |
42 | - print(f'''--- | |
43 | - type: text | |
44 | - title: Contas de somar | |
45 | - text: | | |
46 | - Calcule o resultado de ${{x}} + {{y}}$. | |
47 | - correct: '{{r}}' | |
48 | - solution: | | |
49 | - A solução é {{r}}.''') | |
50 | - ``` | |
51 | - | |
52 | - Este script deve ter permissões para poder ser executado no terminal. | |
53 | - Podemos testar o programa no terminal `./gen-somar.py 1 100` e verificar que | |
54 | - o output é uma pergunta válida em formato `yaml`. Agora é necessário indicar | |
55 | - que este script deve ser usado para gerar uma pergunta. | |
56 | - | |
57 | - Uma pergunta gerada por um programa externo é declarada com | |
58 | - | |
59 | - ```yaml | |
60 | - - type: generator | |
61 | - ref: gen-somar | |
62 | - script: gen-somar.py | |
63 | - # argumentos opcionais | |
64 | - args: [1, 100] | |
65 | - ``` | |
66 | - | |
67 | - O programa pode receber uma lista de argumentos de linha de comando | |
68 | - declarados em `args`. | |
69 | - | |
70 | - --- | |
71 | - | |
72 | - Calcule o resultado de ${x} + {y}$. | |
73 | - | |
74 | - Os números foram gerados aleatoriamente no intervalo de {a} a {b}. | |
75 | -correct: '{r}' | |
76 | -solution: | | |
77 | - A solução é {r}.""") | |
20 | +# ---------------------------------------------------------------------------- | |
21 | +text = ''' | |
22 | +As perguntas podem ser estáticas (como as que vimos até aqui), ou serem | |
23 | +geradas dinâmicamente por um programa externo. Para gerar uma pergunta, o | |
24 | +programa deve escrever texto no `stdout` em formato `yaml` tal como os | |
25 | +exemplos das perguntas estáticas dos tipos apresentados anteriormente. Pode | |
26 | +também receber argumentos de linha de comando para parametrizar a pergunta. | |
27 | +Aqui está um exemplo de uma pergunta gerada por um script python: | |
28 | + | |
29 | +```python | |
30 | + #!/usr/bin/env python3 | |
31 | + | |
32 | + import json | |
33 | + import random | |
34 | + import sys | |
35 | + | |
36 | + a, b = (int(n) for n in sys.argv[1:]) # argumentos da linha de comando | |
37 | + | |
38 | + x = random.randint(a, b) # número inteiro no intervalo a..b | |
39 | + y = random.randint(a, b) # número inteiro no intervalo a..b | |
40 | + r = x + y # calcula o resultado correcto | |
41 | + | |
42 | + question = { | |
43 | + 'type': 'text', | |
44 | + 'title': 'Geradores de perguntas', | |
45 | + 'text': f'Quanto é {x} + {y}?', | |
46 | + 'transform': ['trim'], | |
47 | + 'correct': f'{r}', | |
48 | + 'solution': f'A resposta correcta é {r}.' | |
49 | + } | |
50 | + json.dump(question, sys.stdout) | |
51 | +``` | |
52 | + | |
53 | +Este script deve ter permissões para poder ser executado. | |
54 | +Podemos testar o programa no terminal `./gen-somar.py 1 100` e verificar que | |
55 | +o output é uma pergunta válida no formato JSON. Agora é necessário indicar | |
56 | +que este script deve ser usado para gerar uma pergunta. | |
57 | + | |
58 | +Uma pergunta gerada por um programa externo é declarada com | |
59 | + | |
60 | +```yaml | |
61 | +- type: generator | |
62 | + ref: gen-somar | |
63 | + script: gen-somar.py | |
64 | + # argumentos opcionais | |
65 | + args: [1, 100] | |
66 | +``` | |
67 | + | |
68 | +O script vai receber a lista de argumentos declarados em `args`. | |
69 | +A pergunta vai ser modificada, sendo acrescentados os campos gerados pelo | |
70 | +script. O tipo da pergunta vai também ser preenchido pelo tipo correcto. | |
71 | +Apenas a referência mantém-se. | |
72 | + | |
73 | +--- | |
74 | + | |
75 | +''' | |
76 | +# ---------------------------------------------------------------------------- | |
77 | + | |
78 | +question = { | |
79 | + 'type': 'text', | |
80 | + 'title': 'Geradores de perguntas', | |
81 | + 'text': text + f'Quanto é {x} + {y}?', | |
82 | + 'transform': ['trim'], | |
83 | + 'correct': f'{r}', | |
84 | + 'solution': f'A resposta correcta é {r}.' | |
85 | +} | |
86 | + | |
87 | +json.dump(question, sys.stdout) | ... | ... |
demo/questions/tutorial.yaml
... | ... | @@ -10,7 +10,7 @@ |
10 | 10 | ficheiros de perguntas a importar e uma selecção de perguntas e respectivas |
11 | 11 | cotações. |
12 | 12 | |
13 | - Exemplo: | |
13 | + Exemplo de um ficheiro `test.yaml`: | |
14 | 14 | |
15 | 15 | ```yaml |
16 | 16 | --- |
... | ... | @@ -18,7 +18,7 @@ |
18 | 18 | ref: tutorial # referência, pode ser reusada em vários turnos |
19 | 19 | title: Demonstração # título da prova |
20 | 20 | database: students.db # base de dados previamente criada com initdb |
21 | - answers_dir: ans # directório onde ficam os testes dos alunos | |
21 | + answers_dir: ans # directório onde ficam as submissões dos alunos | |
22 | 22 | |
23 | 23 | # opcionais |
24 | 24 | duration: 60 # duração da prova em minutos (default: inf) |
... | ... | @@ -28,7 +28,7 @@ |
28 | 28 | # não normaliza por defeito (default: None) |
29 | 29 | |
30 | 30 | # -------------------------------------------------------------------------- |
31 | - # Ficheiros de perguntas a importar (relativamente a `questions_dir`) | |
31 | + # Ficheiros de perguntas a importar | |
32 | 32 | files: |
33 | 33 | - tabelas.yaml |
34 | 34 | - topic1/questions.yaml |
... | ... | @@ -46,19 +46,19 @@ |
46 | 46 | - ref: pergunta2 |
47 | 47 | points: 2.0 |
48 | 48 | |
49 | - # por defeinto, a cotação da pergunta é 1.0 valor | |
49 | + # por defeito, a pergunta tem a cotação de 1.0 valor | |
50 | 50 | - ref: pergunta3 |
51 | 51 | |
52 | 52 | # escolhe aleatoriamente uma das variantes da pergunta |
53 | - - ref: [pergunta3a, pergunta3b] | |
53 | + - ref: [pergunta4a, pergunta4b] | |
54 | 54 | points: 0.5 |
55 | 55 | |
56 | 56 | # -------------------------------------------------------------------------- |
57 | 57 | ``` |
58 | 58 | |
59 | - A ordem das perguntas é mantida quando apresentada no teste. | |
59 | + A ordem das perguntas é mantida. | |
60 | 60 | |
61 | - O mesmo teste pode ser realizado várias vezes em turnos diferentes, não é | |
61 | + O mesmo teste pode ser repetido várias vezes em turnos diferentes, não é | |
62 | 62 | necessário alterar nada. |
63 | 63 | |
64 | 64 | # ---------------------------------------------------------------------------- |
... | ... | @@ -278,8 +278,8 @@ |
278 | 278 | mantêm-se inalterados. |
279 | 279 | * `remove_space` remove todos os espaços (início, meio e fim). |
280 | 280 | * `normalize_space` remove espaços do início e fim (trim), e substitui |
281 | - múltiplos espaços por um único espaço (no meio). | |
282 | - * `lower` e `upper` convertem respectivamente para minúsculas e maiúsculas. | |
281 | + múltiplos espaços por um único espaço no meio. | |
282 | + * `lower` e `upper` convertem para minúsculas e maiúsculas, respectivamente. | |
283 | 283 | transform: ['trim', 'lower'] |
284 | 284 | correct: ['azul'] |
285 | 285 | solution: | |
... | ... | @@ -314,15 +314,12 @@ |
314 | 314 | - '[Vv]erde' |
315 | 315 | ``` |
316 | 316 | |
317 | - --- | |
318 | - | |
319 | - **Atenção:** A expressão regular deve seguir as convenções da suportadas em | |
320 | - python (ver | |
321 | - [Regular expression operations](https://docs.python.org/3/library/re.html)). | |
322 | - Em particular, a expressão regular acima também aceita a resposta | |
323 | - `verde, azul`. | |
324 | - Deve marcar-se o início e final `^(VERDE|[Vv]erde)$` para evitar estas | |
325 | - situações. | |
317 | + **Atenção:** | |
318 | + A expressão regular deve seguir as convenções suportadas em python, ver | |
319 | + [Regular expression operations](https://docs.python.org/3/library/re.html). | |
320 | + Em particular, a expressão regular acima também aceita a resposta `verde, | |
321 | + azul`. Deve marcar-se o início e final da string `^(VERDE|[Vv]erde)$` para | |
322 | + evitar estas situações. | |
326 | 323 | |
327 | 324 | correct: '(VERDE|[Vv]erde)' |
328 | 325 | solution: | |
... | ... | @@ -333,7 +330,7 @@ |
333 | 330 | ref: tut-numeric-interval |
334 | 331 | title: Resposta numérica em linha de texto |
335 | 332 | text: | |
336 | - Este tipo de perguntas esperam uma resposta numérica (vírgula flutuante). | |
333 | + Este tipo de perguntas esperam uma resposta numérica em vírgula flutuante. | |
337 | 334 | O resultado é considerado correcto se estiver dentro do intervalo fechado |
338 | 335 | indicado. |
339 | 336 | |
... | ... | @@ -354,7 +351,7 @@ |
354 | 351 | |
355 | 352 | **Atenção:** as respostas têm de usar o ponto como separador decimal. |
356 | 353 | Em geral são aceites números inteiros, como `123`, |
357 | - ou em vírgula flutuante, como em `0.23`, `1e-3`. | |
354 | + ou em vírgula flutuante, como em `0.23`, `1e-3`, `1.2e4`. | |
358 | 355 | correct: [3.14, 3.15] |
359 | 356 | solution: | |
360 | 357 | Sabemos que $\pi\approx 3.14159265359$. |
... | ... | @@ -366,14 +363,14 @@ |
366 | 363 | title: Resposta em múltiplas linhas de texto |
367 | 364 | text: | |
368 | 365 | Este tipo de perguntas permitem respostas em múltiplas linhas de texto e |
369 | - são as mais flexíveis. | |
366 | + são as mais flexíveis, sendo a resposta enviada para um programa externo | |
367 | + para ser avaliada. | |
370 | 368 | |
371 | - A resposta é enviada para um programa externo para ser avaliada. | |
372 | - O programa externo é um programa que tem de ser executável pelo pelo | |
373 | - sistema operativo (pode ser um binário ou script desde que o respectivo | |
374 | - interpretador instalado). | |
369 | + O programa externo é um programa que tem de ser executável pelo sistema | |
370 | + operativo (pode ser um binário ou script desde que o respectivo | |
371 | + interpretador esteja instalado). | |
375 | 372 | Este programa externo recebe a resposta submetida pelo aluno via `stdin` e |
376 | - devolve a classificação via `stdout`. | |
373 | + devolve a classificação em formato JSON via `stdout`. | |
377 | 374 | Exemplo: |
378 | 375 | |
379 | 376 | ```yaml |
... | ... | @@ -390,7 +387,7 @@ |
390 | 387 | resposta contém as três palavras red, green e blue, e calcula uma nota no |
391 | 388 | intervalo 0.0 a 1.0. |
392 | 389 | O programa externo é executado num processo separado e a interacção faz-se |
393 | - via stdin/stdout. | |
390 | + via *stdin/stdout*. | |
394 | 391 | |
395 | 392 | Se o programa externo exceder o `timeout` indicado (em segundos), |
396 | 393 | este é automaticamente terminado e é atribuída a classificação de 0.0 |
... | ... | @@ -405,18 +402,29 @@ |
405 | 402 | 0.75 |
406 | 403 | ``` |
407 | 404 | |
408 | - ou opcionalmente escrever em formato json ou yaml, eventualmente com um | |
409 | - comentário que será arquivado com o teste. | |
410 | - Exemplo: | |
405 | + ou escrever em formato JSON, eventualmente com um comentário que será | |
406 | + arquivado com o teste. Exemplo: | |
411 | 407 | |
412 | - ```yaml | |
413 | - grade: 0.5 | |
414 | - comments: | | |
415 | - Esqueceu-se de algumas cores. | |
416 | - A resposta correcta era `red green blue`. | |
408 | + ```json | |
409 | + { "grade": 0.5, "comments": "Esqueceu-se de algumas cores." } | |
417 | 410 | ``` |
418 | 411 | |
419 | 412 | O comentário é mostrado na revisão de prova. |
413 | + | |
414 | + Como este tipo de perguntas é mais complexo, incluem-se *unit tests* para | |
415 | + testar os scripts de correcção das perguntas antes do início da prova. | |
416 | + As opções seguintes mostram casos em que a resposta está correcta e casos | |
417 | + em que a resposta está errada. O script de correcção é testado sobre todos | |
418 | + estes casos e para cada pergunta de cada aluno: | |
419 | + | |
420 | + ```yaml | |
421 | + tests_right: | |
422 | + - 'red green blue' | |
423 | + - 'green blue red' | |
424 | + tests_wrong: | |
425 | + - '' | |
426 | + - 'blue yellow black' | |
427 | + ``` | |
420 | 428 | answer: | |
421 | 429 | Aqui o aluno escreve a resposta. |
422 | 430 | Esta caixa aumenta de tamanho automaticamente e |
... | ... | @@ -425,13 +433,14 @@ |
425 | 433 | timeout: 5 |
426 | 434 | tests_right: |
427 | 435 | - 'red green blue' |
428 | - # tests_wrong: | |
429 | - # - 'blue gray yellow' | |
436 | + tests_wrong: | |
437 | + - '' | |
438 | + - 'blue gray yellow' | |
430 | 439 | |
431 | 440 | # --------------------------------------------------------------------------- |
432 | 441 | - type: information |
433 | 442 | ref: tut-information |
434 | - title: Texto informativo | |
443 | + title: Texto informativo (genérico) | |
435 | 444 | text: | |
436 | 445 | As perguntas deste tipo não contam para avaliação. O objectivo é fornecer |
437 | 446 | instruções para os alunos, por exemplo tabelas para consulta, fórmulas, etc. |
... | ... | @@ -467,7 +476,7 @@ |
467 | 476 | ref: tut-success |
468 | 477 | title: Texto informativo (sucesso) |
469 | 478 | text: | |
470 | - Não conta para avaliação. É apenas o aspecto gráfico que muda. | |
479 | + Semelhante ao anterior, mas com cores diferentes. | |
471 | 480 | |
472 | 481 | Um pedaço de código em linha, por exemplo `x = sqrt(z)` é marcado com uma |
473 | 482 | fonte e cor diferente. |
... | ... | @@ -508,7 +517,7 @@ |
508 | 517 | ref: tut-warning |
509 | 518 | title: Texto informativo (aviso) |
510 | 519 | text: | |
511 | - Não conta para avaliação. | |
520 | + Semelhante aos anteriores. | |
512 | 521 | |
513 | 522 | Neste exemplo mostramos como se pode construir uma tabela como a seguinte: |
514 | 523 | |
... | ... | @@ -539,26 +548,10 @@ |
539 | 548 | |
540 | 549 | # ---------------------------------------------------------------------------- |
541 | 550 | - type: alert |
542 | - ref: tut-alert1 | |
543 | - title: Texto informativo (perigo) - versão 1 | |
551 | + ref: tut-alert | |
552 | + title: Texto informativo (perigo) | |
544 | 553 | text: | |
545 | - Não conta para avaliação. Texto importante. | |
546 | - | |
547 | -  | |
548 | - | |
549 | - As imagens podem ser adicionadas usando a notação standard em markdown. Há | |
550 | - duas possibilidads: | |
551 | - | |
552 | - - Imagens inline: não têm título definido e podem ser incluídas no meio de | |
553 | - uma linha de texto usando``. | |
554 | - - Imagens centradas com título: ``. | |
555 | - O título é colocado por baixo da imagem. Pode ser uma string vazia. | |
556 | - | |
557 | -- type: alert | |
558 | - ref: tut-alert2 | |
559 | - title: Texto informativo (perigo) - versão 2 | |
560 | - text: | | |
561 | - Não conta para avaliação. Texto importante. | |
554 | + Semelhante aos anteriores. | |
562 | 555 | |
563 | 556 |  |
564 | 557 | |
... | ... | @@ -599,11 +592,3 @@ |
599 | 592 | yamllint test.yaml |
600 | 593 | yamllint questions.yaml |
601 | 594 | ``` |
602 | - | |
603 | - No caso de programas geradores de perguntas e programas de correcção de | |
604 | - respostas pode usar-se um *pipe*: | |
605 | - | |
606 | - ```sh | |
607 | - generate-question | yamllint - | |
608 | - correct-answer | yamllint - | |
609 | - ``` | ... | ... |
package.json
... | ... | @@ -2,12 +2,12 @@ |
2 | 2 | "description": "Javascript libraries required to run the server", |
3 | 3 | "email": "mjsb@uevora.pt", |
4 | 4 | "dependencies": { |
5 | - "bootstrap": "^5.1.0", | |
6 | - "bootstrap-icons": "^1.8.0", | |
5 | + "bootstrap": "^5.3.3", | |
6 | + "bootstrap-icons": "^1.11.3", | |
7 | 7 | "codemirror": "^5.65.2", |
8 | - "datatables": "^1.10.18", | |
9 | - "jquery": "^3.6.0", | |
10 | - "mathjax": "^3.2.0", | |
11 | - "underscore": "^1.13.2" | |
8 | + "datatables": "<2.0.0", | |
9 | + "jquery": "^3.7.1", | |
10 | + "mathjax": "^3.2.2", | |
11 | + "underscore": "^1.13.6" | |
12 | 12 | } |
13 | 13 | } | ... | ... |
setup.py
... | ... | @@ -7,19 +7,19 @@ from setuptools import setup, find_packages |
7 | 7 | from perguntations import (__author__, __license__, |
8 | 8 | APP_NAME, APP_VERSION, APP_DESCRIPTION) |
9 | 9 | |
10 | -with open("README.md", "r") as f: | |
10 | +with open('README.md', 'r') as f: | |
11 | 11 | long_description = f.read() |
12 | 12 | |
13 | 13 | setup( |
14 | 14 | name=APP_NAME, |
15 | 15 | version=APP_VERSION, |
16 | 16 | author=__author__, |
17 | - author_email="mjsb@uevora.pt", | |
17 | + author_email='mjsb@uevora.pt', | |
18 | 18 | license=__license__, |
19 | 19 | description=APP_DESCRIPTION.split('\n')[0], |
20 | 20 | long_description=APP_DESCRIPTION, |
21 | - long_description_content_type="text/markdown", | |
22 | - url="https://git.xdi.uevora.pt/mjsb/perguntations.git", | |
21 | + long_description_content_type='text/markdown', | |
22 | + url='https://git.xdi.uevora.pt/mjsb/perguntations.git', | |
23 | 23 | packages=find_packages(), |
24 | 24 | include_package_data=True, # install files from MANIFEST.in |
25 | 25 | python_requires='>=3.9', | ... | ... |