Compare View

switch
from
...
to
 
Commits (3)
demo/demo.yaml
... ... @@ -75,9 +75,7 @@ questions:
75 75 - ref: tut-information
76 76 - ref: tut-success
77 77 - ref: tut-warning
78   -
79   - # choose one from the list:
80   - - ref: [tut-alert1, tut-alert2]
  78 + - ref: tut-alert
81 79  
82 80 - ref: tut-generator
83 81 - ref: tut-yamllint
... ...
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   - ![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 556 ![planetas](planets.png "Planetas do Sistema Solar")
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',
... ...