Skip to content

Commit 8cc5fd6

Browse files
committed
implement: Build only after a clean validate (t35)
1 parent f7c8fad commit 8cc5fd6

6 files changed

Lines changed: 47 additions & 7 deletions

File tree

‎in2lambda/draft/export.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ def as_set(draft: dict[str, Any], directory: str = ".") -> Set:
5959
question was written without parts, it becomes a part of its own holding
6060
nothing but that solution. That is the rule
6161
:meth:`~in2lambda.api.question.Question.add_solution` applies, so a draft
62-
exports as the same sheet converted by `in2lambda convert` does. A block marked
63-
ignore is in no question: it is the source's, not the set's.
62+
exports as the same sheet converted by `in2lambda convert` does. A question
63+
written with neither parts nor a solution holds one part with nothing in it,
64+
which is the question as the draft has it. A block marked ignore is in no
65+
question: it is the source's, not the set's.
6466
6567
Examples:
6668
>>> from in2lambda.draft.export import as_set
@@ -100,6 +102,12 @@ def as_set(draft: dict[str, Any], directory: str = ".") -> Set:
100102
for part_of in question.parts:
101103
if not part_of.worked_solution:
102104
part_of.worked_solution = fields[written]["value"]
105+
if not question.parts:
106+
# A question whose parts are yet to be written is still exported, and an
107+
# empty part is what it holds: `json_convert` leaves a question with no
108+
# parts at all carrying the template's own placeholder wording, which is
109+
# wording no field of the draft holds.
110+
question.parts.append(Part())
103111
# As the export refers to them: beside the draft, since that is where a command
104112
# naming a file names one. Whether the file is there is `build`'s question, not
105113
# asked here, so that a draft can be rendered while its figures are being found.

‎tests/fixtures/drafts/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ into `media/` - the only folder here with a file the fields point at.
2828
`question_solution_beside_part_solutions` is the only one filled in by a spec rather than by
2929
commands one at a time, and the only one whose question has two parts: both are answered by the
3030
spec's own solutions, so the `question solution` after it answers nothing, and the export carries
31-
it as a part of its own rather than dropping the wording.
31+
it as a part of its own rather than dropping the wording. `question_without_parts` is a question
32+
and nothing else, which the checks have nothing to say about: it is here because a question with
33+
no parts is what the export has to write out as an empty part rather than as the template's.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
{
3+
"args": {
4+
"text": "b1"
5+
},
6+
"by": "tests",
7+
"command": "question add"
8+
}
9+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"q1.text": {
3+
"by": "tests",
4+
"edited": false,
5+
"layer": 3,
6+
"ranges": [
7+
[
8+
1,
9+
1
10+
]
11+
],
12+
"value": "Find the pressure at the bottom of a tank of water $h$ deep."
13+
}
14+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Find the pressure at the bottom of a tank of water $h$ deep.

‎tests/test_draft.py‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,18 @@ def _expected_parts(fields: dict[str, Any], number: int) -> int:
5959
6060
One per ``qN.pM.text``, and one more where ``qN.solution`` is written beside a
6161
solution for every part there is: nothing is left for it to answer, so it is a part
62-
of its own, as `in2lambda convert` writes one.
62+
of its own, as `in2lambda convert` writes one. A question with no parts written for
63+
it at all is one empty part, since a question exported holding none carries the
64+
template's placeholder wording instead.
6365
"""
6466
written = [
6567
int(found[2])
6668
for key in fields
6769
if (found := PART.fullmatch(key)) and int(found[1]) == number
6870
]
6971
answered = all(f"q{number}.p{part}.solution" in fields for part in written)
70-
return len(written) + (answered and f"q{number}.solution" in fields)
72+
parts = len(written) + (answered and f"q{number}.solution" in fields)
73+
return parts or 1
7174

7275

7376
def _reported(folder: Path) -> list[dict[str, Any]]:
@@ -568,9 +571,12 @@ def test_build_follows_the_report(folder: Path, tmp_path: Path, monkeypatch) ->
568571
for index, part in enumerate(question.parts, start=1):
569572
if f"q{number}.p{index}.text" not in fields:
570573
# The question's own solution, written where every part is answered
571-
# already: last, and holding nothing but that solution.
574+
# already: last, and holding nothing but that solution. Or, where the
575+
# question has no solution either, the empty part a question with no
576+
# parts written for it exports as.
572577
assert part.text == ""
573-
assert part.worked_solution == fields[f"q{number}.solution"]["value"]
578+
solution = fields.get(f"q{number}.solution")
579+
assert part.worked_solution == (solution["value"] if solution else "")
574580
continue
575581
assert part.text == fields[f"q{number}.p{index}.text"]["value"]
576582
# A part's own solution, or the question's where it has none of its own.

0 commit comments

Comments
 (0)