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', | ... | ... |