Compare View

switch
from
...
to
 
Commits (3)
demo/demo.yaml
@@ -75,9 +75,7 @@ questions: @@ -75,9 +75,7 @@ questions:
75 - ref: tut-information 75 - ref: tut-information
76 - ref: tut-success 76 - ref: tut-success
77 - ref: tut-warning 77 - ref: tut-warning
78 -  
79 - # choose one from the list:  
80 - - ref: [tut-alert1, tut-alert2] 78 + - ref: tut-alert
81 79
82 - ref: tut-generator 80 - ref: tut-generator
83 - ref: tut-yamllint 81 - ref: tut-yamllint
demo/questions/generate-question.py
1 #!/usr/bin/env python3 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 import sys 11 import sys
10 12
11 # read two arguments from the field `args` specified in the question yaml file 13 # read two arguments from the field `args` specified in the question yaml file
12 a, b = (int(n) for n in sys.argv[1:]) 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 r = x + y 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,7 +10,7 @@
10 ficheiros de perguntas a importar e uma selecção de perguntas e respectivas 10 ficheiros de perguntas a importar e uma selecção de perguntas e respectivas
11 cotações. 11 cotações.
12 12
13 - Exemplo: 13 + Exemplo de um ficheiro `test.yaml`:
14 14
15 ```yaml 15 ```yaml
16 --- 16 ---
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 ref: tutorial # referência, pode ser reusada em vários turnos 18 ref: tutorial # referência, pode ser reusada em vários turnos
19 title: Demonstração # título da prova 19 title: Demonstração # título da prova
20 database: students.db # base de dados previamente criada com initdb 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 # opcionais 23 # opcionais
24 duration: 60 # duração da prova em minutos (default: inf) 24 duration: 60 # duração da prova em minutos (default: inf)
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 # não normaliza por defeito (default: None) 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 files: 32 files:
33 - tabelas.yaml 33 - tabelas.yaml
34 - topic1/questions.yaml 34 - topic1/questions.yaml
@@ -46,19 +46,19 @@ @@ -46,19 +46,19 @@
46 - ref: pergunta2 46 - ref: pergunta2
47 points: 2.0 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 - ref: pergunta3 50 - ref: pergunta3
51 51
52 # escolhe aleatoriamente uma das variantes da pergunta 52 # escolhe aleatoriamente uma das variantes da pergunta
53 - - ref: [pergunta3a, pergunta3b] 53 + - ref: [pergunta4a, pergunta4b]
54 points: 0.5 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 necessário alterar nada. 62 necessário alterar nada.
63 63
64 # ---------------------------------------------------------------------------- 64 # ----------------------------------------------------------------------------
@@ -278,8 +278,8 @@ @@ -278,8 +278,8 @@
278 mantêm-se inalterados. 278 mantêm-se inalterados.
279 * `remove_space` remove todos os espaços (início, meio e fim). 279 * `remove_space` remove todos os espaços (início, meio e fim).
280 * `normalize_space` remove espaços do início e fim (trim), e substitui 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 transform: ['trim', 'lower'] 283 transform: ['trim', 'lower']
284 correct: ['azul'] 284 correct: ['azul']
285 solution: | 285 solution: |
@@ -314,15 +314,12 @@ @@ -314,15 +314,12 @@
314 - '[Vv]erde' 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 correct: '(VERDE|[Vv]erde)' 324 correct: '(VERDE|[Vv]erde)'
328 solution: | 325 solution: |
@@ -333,7 +330,7 @@ @@ -333,7 +330,7 @@
333 ref: tut-numeric-interval 330 ref: tut-numeric-interval
334 title: Resposta numérica em linha de texto 331 title: Resposta numérica em linha de texto
335 text: | 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 O resultado é considerado correcto se estiver dentro do intervalo fechado 334 O resultado é considerado correcto se estiver dentro do intervalo fechado
338 indicado. 335 indicado.
339 336
@@ -354,7 +351,7 @@ @@ -354,7 +351,7 @@
354 351
355 **Atenção:** as respostas têm de usar o ponto como separador decimal. 352 **Atenção:** as respostas têm de usar o ponto como separador decimal.
356 Em geral são aceites números inteiros, como `123`, 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 correct: [3.14, 3.15] 355 correct: [3.14, 3.15]
359 solution: | 356 solution: |
360 Sabemos que $\pi\approx 3.14159265359$. 357 Sabemos que $\pi\approx 3.14159265359$.
@@ -366,14 +363,14 @@ @@ -366,14 +363,14 @@
366 title: Resposta em múltiplas linhas de texto 363 title: Resposta em múltiplas linhas de texto
367 text: | 364 text: |
368 Este tipo de perguntas permitem respostas em múltiplas linhas de texto e 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 Este programa externo recebe a resposta submetida pelo aluno via `stdin` e 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 Exemplo: 374 Exemplo:
378 375
379 ```yaml 376 ```yaml
@@ -390,7 +387,7 @@ @@ -390,7 +387,7 @@
390 resposta contém as três palavras red, green e blue, e calcula uma nota no 387 resposta contém as três palavras red, green e blue, e calcula uma nota no
391 intervalo 0.0 a 1.0. 388 intervalo 0.0 a 1.0.
392 O programa externo é executado num processo separado e a interacção faz-se 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 Se o programa externo exceder o `timeout` indicado (em segundos), 392 Se o programa externo exceder o `timeout` indicado (em segundos),
396 este é automaticamente terminado e é atribuída a classificação de 0.0 393 este é automaticamente terminado e é atribuída a classificação de 0.0
@@ -405,18 +402,29 @@ @@ -405,18 +402,29 @@
405 0.75 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 O comentário é mostrado na revisão de prova. 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 answer: | 428 answer: |
421 Aqui o aluno escreve a resposta. 429 Aqui o aluno escreve a resposta.
422 Esta caixa aumenta de tamanho automaticamente e 430 Esta caixa aumenta de tamanho automaticamente e
@@ -425,13 +433,14 @@ @@ -425,13 +433,14 @@
425 timeout: 5 433 timeout: 5
426 tests_right: 434 tests_right:
427 - 'red green blue' 435 - 'red green blue'
428 - # tests_wrong:  
429 - # - 'blue gray yellow' 436 + tests_wrong:
  437 + - ''
  438 + - 'blue gray yellow'
430 439
431 # --------------------------------------------------------------------------- 440 # ---------------------------------------------------------------------------
432 - type: information 441 - type: information
433 ref: tut-information 442 ref: tut-information
434 - title: Texto informativo 443 + title: Texto informativo (genérico)
435 text: | 444 text: |
436 As perguntas deste tipo não contam para avaliação. O objectivo é fornecer 445 As perguntas deste tipo não contam para avaliação. O objectivo é fornecer
437 instruções para os alunos, por exemplo tabelas para consulta, fórmulas, etc. 446 instruções para os alunos, por exemplo tabelas para consulta, fórmulas, etc.
@@ -467,7 +476,7 @@ @@ -467,7 +476,7 @@
467 ref: tut-success 476 ref: tut-success
468 title: Texto informativo (sucesso) 477 title: Texto informativo (sucesso)
469 text: | 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 Um pedaço de código em linha, por exemplo `x = sqrt(z)` é marcado com uma 481 Um pedaço de código em linha, por exemplo `x = sqrt(z)` é marcado com uma
473 fonte e cor diferente. 482 fonte e cor diferente.
@@ -508,7 +517,7 @@ @@ -508,7 +517,7 @@
508 ref: tut-warning 517 ref: tut-warning
509 title: Texto informativo (aviso) 518 title: Texto informativo (aviso)
510 text: | 519 text: |
511 - Não conta para avaliação. 520 + Semelhante aos anteriores.
512 521
513 Neste exemplo mostramos como se pode construir uma tabela como a seguinte: 522 Neste exemplo mostramos como se pode construir uma tabela como a seguinte:
514 523
@@ -539,26 +548,10 @@ @@ -539,26 +548,10 @@
539 548
540 # ---------------------------------------------------------------------------- 549 # ----------------------------------------------------------------------------
541 - type: alert 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 text: | 553 text: |
545 - Não conta para avaliação. Texto importante.  
546 -  
547 - ![planetas](planets.png "Planetas do Sistema Solar")  
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`![alt text](image.jpg)`.  
554 - - Imagens centradas com título: `![alt text](image.jpg "Título da imagem")`.  
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 ![planetas](planets.png "Planetas do Sistema Solar") 556 ![planetas](planets.png "Planetas do Sistema Solar")
564 557
@@ -599,11 +592,3 @@ @@ -599,11 +592,3 @@
599 yamllint test.yaml 592 yamllint test.yaml
600 yamllint questions.yaml 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 - ```  
@@ -2,12 +2,12 @@ @@ -2,12 +2,12 @@
2 "description": "Javascript libraries required to run the server", 2 "description": "Javascript libraries required to run the server",
3 "email": "mjsb@uevora.pt", 3 "email": "mjsb@uevora.pt",
4 "dependencies": { 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 "codemirror": "^5.65.2", 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 }
@@ -7,19 +7,19 @@ from setuptools import setup, find_packages @@ -7,19 +7,19 @@ from setuptools import setup, find_packages
7 from perguntations import (__author__, __license__, 7 from perguntations import (__author__, __license__,
8 APP_NAME, APP_VERSION, APP_DESCRIPTION) 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 long_description = f.read() 11 long_description = f.read()
12 12
13 setup( 13 setup(
14 name=APP_NAME, 14 name=APP_NAME,
15 version=APP_VERSION, 15 version=APP_VERSION,
16 author=__author__, 16 author=__author__,
17 - author_email="mjsb@uevora.pt", 17 + author_email='mjsb@uevora.pt',
18 license=__license__, 18 license=__license__,
19 description=APP_DESCRIPTION.split('\n')[0], 19 description=APP_DESCRIPTION.split('\n')[0],
20 long_description=APP_DESCRIPTION, 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 packages=find_packages(), 23 packages=find_packages(),
24 include_package_data=True, # install files from MANIFEST.in 24 include_package_data=True, # install files from MANIFEST.in
25 python_requires='>=3.9', 25 python_requires='>=3.9',