forked from ucbds-infra/otter-grader
/
blocks.py
84 lines (62 loc) · 2.34 KB
/
blocks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Assignment block parsing for Otter Assign"""
import re
import yaml
from enum import Enum
from .utils import get_source, is_cell_type
class BlockType(Enum):
"""
An enum of allowed block types.
"""
QUESTION = "question"
PROMPT = "prompt"
SOLUTION = "solution"
TESTS = "tests"
def is_block_boundary_cell(cell, block_type, end=False):
"""
Determine whether ``cell`` is a boundary cell for a ``block_type`` block. If ``end`` is true,
the block should be an end block; otherwise, it should be a begin block.
Args:
cell (``nbformat.NotebookNode``): the cell to check
block_type (``BlockType``): the block type to check for
end (``bool``, optional): whether to check for an end boundary instead of a begin
Returns:
``bool``: whether the cell is a boundary cell of type ``block_type``
"""
begin_or_end = 'end' if end else 'begin'
regex = fr"#\s+{ begin_or_end }\s+{ block_type.value }\s*"
source = get_source(cell)
return is_cell_type(cell, "raw") and bool(re.match(regex, source[0], flags=re.IGNORECASE))
def is_assignment_config_cell(cell):
"""
Determine whether ``cell`` is an assignment configuration cell.
An assignment configuration cell is a raw cell starting with the line ``# ASSIGNMENT CONFIG``,
e.g.
.. code-block:: yaml
# ASSIGNMENT CONFIG
requirements: requirements.txt
files:
- data.csv
Args:
cell (``nbformat.NotebookNode``): the cell to check
Returns:
``bool``: whether the cell is an assignment config cell
"""
regex = r"#\s+assignment\s+config\s*"
source = get_source(cell)
return is_cell_type(cell, "raw") and bool(re.match(regex, source[0], flags=re.IGNORECASE))
def get_cell_config(cell):
"""
Parse a cell's contents as YAML and return the resulting dictionary.
Args:
cell (``nbformat.NotebookNode``): the cell to check
Returns:
``dict[str, object]``: the parsed configurations
Raises:
``TypeError``: if parsing the YAML does not return a dictionary
"""
source = get_source(cell)
config = yaml.full_load("\n".join(source))
if not isinstance(config, dict):
# TODO: make this error nicer?
raise TypeError(f"Found a begin cell configuration that is not a dictionary: {cell}")
return config