in2lambda 0.3.1__tar.gz → 0.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {in2lambda-0.3.1 → in2lambda-0.3.3}/PKG-INFO +5 -4
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/api/module.py +39 -5
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/api/question.py +37 -17
- in2lambda-0.3.3/in2lambda/figures/O2_potential.png +0 -0
- in2lambda-0.3.3/in2lambda/figures/activated_complex.png +0 -0
- in2lambda-0.3.3/in2lambda/figures/rocket-momentum.png +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsSepSol/filter.py +5 -3
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/markdown.py +7 -4
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/json_convert/json_convert.py +13 -12
- in2lambda-0.3.3/in2lambda/json_convert/minimal_template.json +23 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/main.py +55 -14
- {in2lambda-0.3.1 → in2lambda-0.3.3}/pyproject.toml +10 -10
- in2lambda-0.3.1/in2lambda/example.docx +0 -0
- in2lambda-0.3.1/in2lambda/filters/Table_PartSolPartSol/__init__.py +0 -4
- in2lambda-0.3.1/in2lambda/filters/Table_PartSolPartSol/example.md +0 -195
- in2lambda-0.3.1/in2lambda/filters/Table_PartSolPartSol/filter.py +0 -61
- in2lambda-0.3.1/in2lambda/json_convert/minimal_template.json +0 -26
- {in2lambda-0.3.1 → in2lambda-0.3.3}/LICENSE +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/README.md +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/api/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/api/part.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/example_questions.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/example_solutions.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartPartSolSol/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartPartSolSol/example.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartPartSolSol/filter.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartSolPartSol/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartSolPartSol/example.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartSolPartSol/filter.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsOneSol/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsOneSol/example.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsOneSol/filter.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsSepSol/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/PartsSepSol/example.tex +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/filters/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/json_convert/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/katex_convert/__init__.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/katex_convert/delete_list.txt +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/katex_convert/katex_convert.py +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/katex_convert/replace_list.txt +0 -0
- {in2lambda-0.3.1 → in2lambda-0.3.3}/in2lambda/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: in2lambda
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Converts content ready for import into Lambda Feedback
|
|
5
5
|
Home-page: https://lambda-feedback.github.io/in2lambda
|
|
6
6
|
License: MIT
|
|
@@ -12,11 +12,12 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
17
|
Classifier: Typing :: Typed
|
|
17
|
-
Requires-Dist: beartype (>=0.
|
|
18
|
-
Requires-Dist: panflute (>=2.3.
|
|
19
|
-
Requires-Dist: rich-click (>=1.7.
|
|
18
|
+
Requires-Dist: beartype (>=0.17.2,<0.18.0)
|
|
19
|
+
Requires-Dist: panflute (>=2.3.1,<3.0.0)
|
|
20
|
+
Requires-Dist: rich-click (>=1.7.4,<2.0.0)
|
|
20
21
|
Project-URL: Bug Tracker, https://github.com/lambda-feedback/in2lambda/issues
|
|
21
22
|
Project-URL: Documentation, https://lambda-feedback.github.io/in2lambda
|
|
22
23
|
Project-URL: Repository, https://github.com/lambda-feedback/in2lambda
|
|
@@ -6,6 +6,7 @@ from typing import Union
|
|
|
6
6
|
import panflute as pf
|
|
7
7
|
|
|
8
8
|
from in2lambda.api.question import Question
|
|
9
|
+
from in2lambda.json_convert import json_convert
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@dataclass
|
|
@@ -28,11 +29,11 @@ class Module:
|
|
|
28
29
|
Examples:
|
|
29
30
|
>>> from in2lambda.api.module import Module
|
|
30
31
|
>>> Module().current_question
|
|
31
|
-
Question(title='INVALID', parts=[], images=[],
|
|
32
|
+
Question(title='INVALID', parts=[], images=[], main_text='')
|
|
32
33
|
>>> module = Module()
|
|
33
34
|
>>> module.add_question()
|
|
34
35
|
>>> module.current_question
|
|
35
|
-
Question(title='', parts=[], images=[],
|
|
36
|
+
Question(title='', parts=[], images=[], main_text='')
|
|
36
37
|
"""
|
|
37
38
|
return (
|
|
38
39
|
self.questions[self._current_question_index]
|
|
@@ -56,7 +57,7 @@ class Module:
|
|
|
56
57
|
>>> module = Module()
|
|
57
58
|
>>> module.add_question("Some title", pf.Para(pf.Str("hello"), pf.Space, pf.Str("there")))
|
|
58
59
|
>>> module
|
|
59
|
-
Module(questions=[Question(title='Some title', parts=[], images=[],
|
|
60
|
+
Module(questions=[Question(title='Some title', parts=[], images=[], main_text='hello there')])
|
|
60
61
|
>>> module.add_question(main_text="Normal string text")
|
|
61
62
|
>>> module.questions[1].main_text
|
|
62
63
|
'Normal string text'
|
|
@@ -85,7 +86,40 @@ class Module:
|
|
|
85
86
|
>>> module.increment_current_question()
|
|
86
87
|
>>> module.current_question.add_solution("Question 2 answer")
|
|
87
88
|
>>> module.questions
|
|
88
|
-
[Question(title='Question 1', parts=[Part(text='', worked_solution='Question 1 answer')], images=[],
|
|
89
|
-
Question(title='Question 2', parts=[Part(text='', worked_solution='Question 2 answer')], images=[],
|
|
89
|
+
[Question(title='Question 1', parts=[Part(text='', worked_solution='Question 1 answer')], images=[], main_text=''),\
|
|
90
|
+
Question(title='Question 2', parts=[Part(text='', worked_solution='Question 2 answer')], images=[], main_text='')]
|
|
90
91
|
"""
|
|
91
92
|
self._current_question_index += 1
|
|
93
|
+
|
|
94
|
+
def to_json(self, output_dir: str) -> None:
|
|
95
|
+
"""Turns this module into Lambda Feedback JSON/ZIP files.
|
|
96
|
+
|
|
97
|
+
WARNING: This will overwrite any existing files in the directory.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
output_dir: Where to output the final Lambda Feedback JSON/ZIP files.
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
>>> import tempfile
|
|
104
|
+
>>> import os
|
|
105
|
+
>>> import json
|
|
106
|
+
>>> # Create a module with two questions
|
|
107
|
+
>>> module = Module()
|
|
108
|
+
>>> module.add_question("Question 1")
|
|
109
|
+
>>> module.add_question("Question 2")
|
|
110
|
+
>>> with tempfile.TemporaryDirectory() as temp_dir:
|
|
111
|
+
... # Write the JSON files to the temporary directory
|
|
112
|
+
... module.to_json(temp_dir)
|
|
113
|
+
... # Check the contents of the directory
|
|
114
|
+
... sorted(os.listdir(temp_dir))
|
|
115
|
+
... # Check the contents of the set directory
|
|
116
|
+
... sorted(os.listdir(f"{temp_dir}/set"))
|
|
117
|
+
... # Check the title of the first question
|
|
118
|
+
... with open(f"{temp_dir}/set/question_1.json") as file:
|
|
119
|
+
... print(f"Question 1's title: {json.load(file)['title']}")
|
|
120
|
+
['set', 'set.zip']
|
|
121
|
+
['media', 'question_1.json', 'question_2.json']
|
|
122
|
+
Question 1's title: Question 1
|
|
123
|
+
|
|
124
|
+
"""
|
|
125
|
+
json_convert.main(self.questions, output_dir)
|
|
@@ -16,8 +16,8 @@ class Question:
|
|
|
16
16
|
|
|
17
17
|
Examples:
|
|
18
18
|
>>> from in2lambda.api.question import Question
|
|
19
|
-
>>> Question(title="Some title",
|
|
20
|
-
Question(title='Some title', parts=[], images=[],
|
|
19
|
+
>>> Question(title="Some title", main_text="Some text")
|
|
20
|
+
Question(title='Some title', parts=[], images=[], main_text='Some text')
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
title: str = ""
|
|
@@ -25,7 +25,11 @@ class Question:
|
|
|
25
25
|
parts: list[Part] = field(default_factory=list)
|
|
26
26
|
images: list[str] = field(default_factory=list)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
# Use getter/setter methods for main_text without exposing private attribute.
|
|
29
|
+
# See https://stackoverflow.com/a/61480946
|
|
30
|
+
main_text: str = ""
|
|
31
|
+
_main_text: str = field(init=False, repr=False, default="")
|
|
32
|
+
|
|
29
33
|
_last_part: dict[str, int] = field(
|
|
30
34
|
default_factory=lambda: {"solution": 0, "text": 0}, repr=False
|
|
31
35
|
)
|
|
@@ -34,15 +38,10 @@ class Question:
|
|
|
34
38
|
|
|
35
39
|
@property
|
|
36
40
|
def main_text(self) -> str:
|
|
37
|
-
"""Main top-level question text.
|
|
38
|
-
return self._main_text
|
|
41
|
+
r"""Main top-level question text.
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
r"""Appends to the top-level main text, which starts off as an empty string.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
value: A panflute element or string denoting what to append to the main text.
|
|
43
|
+
Setting the attribute multiple times appends to the current value with a newline.
|
|
44
|
+
This allows question text to be dynamically appended.
|
|
46
45
|
|
|
47
46
|
Examples:
|
|
48
47
|
>>> from in2lambda.api.question import Question
|
|
@@ -52,7 +51,28 @@ class Question:
|
|
|
52
51
|
>>> question.main_text
|
|
53
52
|
'hello\nthere'
|
|
54
53
|
"""
|
|
55
|
-
|
|
54
|
+
return self._main_text
|
|
55
|
+
|
|
56
|
+
@main_text.setter
|
|
57
|
+
def main_text(self, value: Union[pf.Element, str, property]) -> None:
|
|
58
|
+
r"""Appends to the top-level main text, which starts off as an empty string.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
value: A panflute element or string denoting what to append to the main text.
|
|
62
|
+
|
|
63
|
+
See example in main_text property.
|
|
64
|
+
"""
|
|
65
|
+
# Converts the inputted value into a string, stored in text_value
|
|
66
|
+
match value:
|
|
67
|
+
case str():
|
|
68
|
+
text_value = value
|
|
69
|
+
case property():
|
|
70
|
+
# Use default value when no value set at initialisation.
|
|
71
|
+
# See https://stackoverflow.com/a/61480946
|
|
72
|
+
text_value = self.main_text
|
|
73
|
+
case pf.Element():
|
|
74
|
+
text_value = pf.stringify(value, False)
|
|
75
|
+
|
|
56
76
|
if self._main_text:
|
|
57
77
|
self._main_text += "\n"
|
|
58
78
|
self._main_text += text_value
|
|
@@ -69,7 +89,7 @@ class Question:
|
|
|
69
89
|
>>> question.add_part_text("part a")
|
|
70
90
|
>>> question.add_solution("part a solution")
|
|
71
91
|
>>> question
|
|
72
|
-
Question(title='', parts=[Part(text='part a', worked_solution='part a solution')], images=[],
|
|
92
|
+
Question(title='', parts=[Part(text='part a', worked_solution='part a solution')], images=[], main_text='')
|
|
73
93
|
>>> question.add_part_text("part b")
|
|
74
94
|
>>> question.add_part_text("part c")
|
|
75
95
|
>>> question.add_solution("Solution for b")
|
|
@@ -77,12 +97,12 @@ class Question:
|
|
|
77
97
|
>>> question
|
|
78
98
|
Question(title='', parts=[Part(text='part a', worked_solution='part a solution'), \
|
|
79
99
|
Part(text='part b', worked_solution='Solution for b'), \
|
|
80
|
-
Part(text='part c', worked_solution='Solution for b')], images=[],
|
|
100
|
+
Part(text='part c', worked_solution='Solution for b')], images=[], main_text='')
|
|
81
101
|
>>> question.add_solution("We now have a solution for c!")
|
|
82
102
|
>>> question
|
|
83
103
|
Question(title='', parts=[Part(text='part a', worked_solution='part a solution'), \
|
|
84
104
|
Part(text='part b', worked_solution='Solution for b'), \
|
|
85
|
-
Part(text='part c', worked_solution='We now have a solution for c!')], images=[],
|
|
105
|
+
Part(text='part c', worked_solution='We now have a solution for c!')], images=[], main_text='')
|
|
86
106
|
"""
|
|
87
107
|
elem_text = elem if isinstance(elem, str) else pf.stringify(elem)
|
|
88
108
|
|
|
@@ -111,13 +131,13 @@ Part(text='part c', worked_solution='We now have a solution for c!')], images=[]
|
|
|
111
131
|
>>> question.add_part_text("part a")
|
|
112
132
|
>>> question.add_solution("part a solution")
|
|
113
133
|
>>> question
|
|
114
|
-
Question(title='', parts=[Part(text='part a', worked_solution='part a solution')], images=[],
|
|
134
|
+
Question(title='', parts=[Part(text='part a', worked_solution='part a solution')], images=[], main_text='')
|
|
115
135
|
>>> # Supports adding the answer first.
|
|
116
136
|
>>> question.add_solution("part b solution")
|
|
117
137
|
>>> question.add_part_text("part b")
|
|
118
138
|
>>> question
|
|
119
139
|
Question(title='', parts=[Part(text='part a', worked_solution='part a solution'), \
|
|
120
|
-
Part(text='part b', worked_solution='part b solution')], images=[],
|
|
140
|
+
Part(text='part b', worked_solution='part b solution')], images=[], main_text='')
|
|
121
141
|
"""
|
|
122
142
|
elem_text = elem if isinstance(elem, str) else pf.stringify(elem)
|
|
123
143
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -72,7 +72,9 @@ def pandoc_filter(
|
|
|
72
72
|
# Add each part solution/text
|
|
73
73
|
# For the solution, prepend any top level answer text to each part answer
|
|
74
74
|
for part in lettered_parts:
|
|
75
|
-
|
|
76
|
-
spaced_blurb + part
|
|
77
|
-
|
|
75
|
+
(
|
|
76
|
+
module.current_question.add_solution(spaced_blurb + part)
|
|
77
|
+
if parsing_answers
|
|
78
|
+
else module.current_question.add_part_text(part)
|
|
79
|
+
)
|
|
78
80
|
return None
|
|
@@ -32,12 +32,12 @@ def image_directories(tex_file: str) -> list[str]:
|
|
|
32
32
|
>>> temp_dir = tempfile.mkdtemp()
|
|
33
33
|
>>> tex_file = os.path.join(temp_dir, 'test.tex')
|
|
34
34
|
>>> with open(tex_file, 'w') as f:
|
|
35
|
-
... f.write("
|
|
35
|
+
... f.write("\\graphicspath{{subdir1/}{subdir2/}{subdir3/}}")
|
|
36
36
|
45
|
|
37
37
|
>>> image_directories(tex_file)
|
|
38
38
|
['subdir1/', 'subdir2/', 'subdir3/']
|
|
39
39
|
>>> with open(tex_file, 'w') as f:
|
|
40
|
-
... f.write("
|
|
40
|
+
... f.write("\\graphicspath{ { subdir1/ }, { subdir2/ }, { subdir3/ } }")
|
|
41
41
|
57
|
|
42
42
|
>>> image_directories.cache_clear()
|
|
43
43
|
>>> image_directories(tex_file)
|
|
@@ -78,7 +78,7 @@ def image_path(image_name: str, tex_file: str) -> Optional[str]:
|
|
|
78
78
|
>>> temp_dir = tempfile.mkdtemp()
|
|
79
79
|
>>> tex_file = os.path.join(temp_dir, 'test.tex')
|
|
80
80
|
>>> with open(tex_file, 'w') as f:
|
|
81
|
-
... f.write("
|
|
81
|
+
... f.write("\\graphicspath{{./subdir1/}{./subdir2/}{./subdir3/}}")
|
|
82
82
|
51
|
|
83
83
|
>>> # Example image in a relative subdirectory
|
|
84
84
|
>>> sub_dir = os.path.join(temp_dir, 'subdir3')
|
|
@@ -120,7 +120,10 @@ def filter(
|
|
|
120
120
|
[pf.Element, pf.elements.Doc, Module, bool],
|
|
121
121
|
Optional[pf.Str],
|
|
122
122
|
]
|
|
123
|
-
) -> Callable[
|
|
123
|
+
) -> Callable[
|
|
124
|
+
[pf.Element, pf.elements.Doc, Module, str, bool],
|
|
125
|
+
Optional[pf.Str],
|
|
126
|
+
]:
|
|
124
127
|
"""Python decorator to make generic LaTeX elements markdown readable.
|
|
125
128
|
|
|
126
129
|
As an example, part of the process involves putting dollar signs around maths
|
|
@@ -24,6 +24,15 @@ def converter(
|
|
|
24
24
|
"""
|
|
25
25
|
# Create output by copying template
|
|
26
26
|
|
|
27
|
+
# create directory to put the questions
|
|
28
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
29
|
+
output_question = os.path.join(output_dir, "set")
|
|
30
|
+
os.makedirs(output_question, exist_ok=True)
|
|
31
|
+
|
|
32
|
+
# create directory to put images - should be in set
|
|
33
|
+
output_image = os.path.join(output_question, "media")
|
|
34
|
+
os.makedirs(output_image, exist_ok=True)
|
|
35
|
+
|
|
27
36
|
for i in range(len(ListQuestions)):
|
|
28
37
|
output = deepcopy(template)
|
|
29
38
|
|
|
@@ -39,27 +48,19 @@ def converter(
|
|
|
39
48
|
# add parts to the question file
|
|
40
49
|
if ListQuestions[i].parts:
|
|
41
50
|
output["parts"][0]["content"] = ListQuestions[i].parts[0].text
|
|
42
|
-
output["parts"][0]["workedSolution"][
|
|
51
|
+
output["parts"][0]["workedSolution"]["content"] = (
|
|
43
52
|
ListQuestions[i].parts[0].worked_solution
|
|
44
53
|
)
|
|
45
54
|
for j in range(1, len(ListQuestions[i].parts)):
|
|
46
55
|
output["parts"].append(deepcopy(template["parts"][0]))
|
|
47
56
|
output["parts"][j]["content"] = ListQuestions[i].parts[j].text
|
|
48
|
-
output["parts"][j]["
|
|
57
|
+
output["parts"][j]["orderNumber"] = j
|
|
58
|
+
output["parts"][j]["workedSolution"]["content"] = (
|
|
49
59
|
ListQuestions[i].parts[j].worked_solution
|
|
50
60
|
)
|
|
51
61
|
|
|
52
62
|
# Output file
|
|
53
|
-
filename = "
|
|
54
|
-
|
|
55
|
-
# create directory to put the questions
|
|
56
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
57
|
-
output_question = os.path.join(output_dir, filename)
|
|
58
|
-
os.makedirs(output_question, exist_ok=True)
|
|
59
|
-
|
|
60
|
-
# create directory to put image
|
|
61
|
-
output_image = os.path.join(output_question, "media")
|
|
62
|
-
os.makedirs(output_image, exist_ok=True)
|
|
63
|
+
filename = "question_" + str(i + 1)
|
|
63
64
|
|
|
64
65
|
# write questions into directory
|
|
65
66
|
with open(f"{output_question}/{filename}.json", "w") as file:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"displayFinalAnswer": true,
|
|
3
|
+
"displayStructuredTutorial": true,
|
|
4
|
+
"displayWorkedSolution": true,
|
|
5
|
+
"masterContent": "Top level question here",
|
|
6
|
+
"parts": [
|
|
7
|
+
{
|
|
8
|
+
"answer": "",
|
|
9
|
+
"content": "Part text here",
|
|
10
|
+
"orderNumber": 0,
|
|
11
|
+
"responseAreas": [],
|
|
12
|
+
"tutorial": [],
|
|
13
|
+
"universalPartId": "N/A",
|
|
14
|
+
"workedSolution": {
|
|
15
|
+
"content": "Part worked solution here",
|
|
16
|
+
"id": "N/A",
|
|
17
|
+
"title": ""
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"publish": false,
|
|
22
|
+
"title": "Question title here"
|
|
23
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"""The main input for in2lambda, defining both the CLT and main library function."""
|
|
2
2
|
|
|
3
|
+
# This commented block makes it run the local files rather than the pip library (I think, I don't understand it. Kevin wrote it.)
|
|
4
|
+
#
|
|
5
|
+
# import sys
|
|
6
|
+
# import os
|
|
7
|
+
# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
8
|
+
|
|
3
9
|
import importlib
|
|
4
10
|
import pkgutil
|
|
11
|
+
import subprocess
|
|
5
12
|
from typing import Optional
|
|
6
13
|
|
|
7
14
|
import panflute as pf
|
|
@@ -9,7 +16,19 @@ import rich_click as click
|
|
|
9
16
|
|
|
10
17
|
import in2lambda.filters
|
|
11
18
|
from in2lambda.api.module import Module
|
|
12
|
-
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def docx_to_md(docx_file: str) -> str:
|
|
22
|
+
"""Converts .docx files to markdown.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
docx_file: A file path with the file extension included.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
the contents of the .docx file in markdown formatting
|
|
29
|
+
"""
|
|
30
|
+
md_output = subprocess.check_output(["pandoc", docx_file, "-t", "markdown"])
|
|
31
|
+
return md_output.decode("utf-8")
|
|
13
32
|
|
|
14
33
|
|
|
15
34
|
def file_type(file: str) -> str:
|
|
@@ -40,10 +59,20 @@ def file_type(file: str) -> str:
|
|
|
40
59
|
match (extension := file.split(".")[-1].lower()):
|
|
41
60
|
case "tex" | "latex" | "ltx":
|
|
42
61
|
return "latex"
|
|
43
|
-
case
|
|
62
|
+
case (
|
|
63
|
+
"md"
|
|
64
|
+
| "rmd"
|
|
65
|
+
| "markdown"
|
|
66
|
+
| "mdown"
|
|
67
|
+
| "mdwn"
|
|
68
|
+
| "mkd"
|
|
69
|
+
| "mkdn"
|
|
70
|
+
| "text"
|
|
71
|
+
| "txt"
|
|
72
|
+
):
|
|
44
73
|
return "markdown"
|
|
45
74
|
case "docx":
|
|
46
|
-
return "docx" # Pandoc doesn't seem to support doc
|
|
75
|
+
return "docx" # Pandoc doesn't seem to support .doc, and panflute doesn't like .docx.
|
|
47
76
|
raise RuntimeError(f"Unsupported file extension: .{extension}")
|
|
48
77
|
|
|
49
78
|
|
|
@@ -71,9 +100,9 @@ def runner(
|
|
|
71
100
|
>>> from in2lambda.main import runner
|
|
72
101
|
>>> # Retrieve an example TeX file and run the given filter.
|
|
73
102
|
>>> runner(f"{os.path.dirname(in2lambda.__file__)}/filters/PartsSepSol/example.tex", "PartsSepSol") # doctest: +ELLIPSIS
|
|
74
|
-
Module(questions=[Question(title='', parts=[Part(text=..., worked_solution=''), ...], images=[],
|
|
103
|
+
Module(questions=[Question(title='', parts=[Part(text=..., worked_solution=''), ...], images=[], main_text='This is a sample question\n\n'), ...])
|
|
75
104
|
>>> runner(f"{os.path.dirname(in2lambda.__file__)}/filters/PartsOneSol/example.tex", "PartsOneSol") # doctest: +ELLIPSIS
|
|
76
|
-
Module(questions=[Question(title='', parts=[Part(text='This is part (a)\n\n', worked_solution=''), ...], images=[],
|
|
105
|
+
Module(questions=[Question(title='', parts=[Part(text='This is part (a)\n\n', worked_solution=''), ...], images=[], main_text='Here is some preliminary question information that might be useful.'), ...)
|
|
77
106
|
"""
|
|
78
107
|
# The list of questions for Lambda Feedback as a Python API.
|
|
79
108
|
module = Module()
|
|
@@ -81,15 +110,20 @@ def runner(
|
|
|
81
110
|
# Dynamically import the correct pandoc filter depending on the subject.
|
|
82
111
|
filter_module = importlib.import_module(f"in2lambda.filters.{chosen_filter}.filter")
|
|
83
112
|
|
|
84
|
-
|
|
85
|
-
|
|
113
|
+
if file_type(question_file) == "docx":
|
|
114
|
+
# Convert .docx to md using Pandoc and proceed
|
|
115
|
+
text = docx_to_md(question_file)
|
|
116
|
+
input_format = "markdown"
|
|
117
|
+
else:
|
|
118
|
+
with open(question_file, "r", encoding="utf-8") as file:
|
|
119
|
+
text = file.read()
|
|
120
|
+
|
|
121
|
+
input_format = file_type(question_file)
|
|
86
122
|
|
|
87
123
|
# Parse the Pandoc AST using the relevant panflute filter.
|
|
88
124
|
pf.run_filter(
|
|
89
125
|
filter_module.pandoc_filter,
|
|
90
|
-
doc=pf.convert_text(
|
|
91
|
-
text, input_format=file_type(question_file), standalone=True
|
|
92
|
-
),
|
|
126
|
+
doc=pf.convert_text(text, input_format=input_format, standalone=True),
|
|
93
127
|
module=module,
|
|
94
128
|
tex_file=question_file,
|
|
95
129
|
parsing_answers=False,
|
|
@@ -97,13 +131,20 @@ def runner(
|
|
|
97
131
|
|
|
98
132
|
# If separate answer TeX file provided, parse that as well.
|
|
99
133
|
if answer_file:
|
|
100
|
-
|
|
101
|
-
|
|
134
|
+
|
|
135
|
+
if file_type(answer_file) == "docx":
|
|
136
|
+
|
|
137
|
+
answer_text = docx_to_md(answer_file)
|
|
138
|
+
answer_format = "markdown"
|
|
139
|
+
else:
|
|
140
|
+
with open(answer_file, "r", encoding="utf-8") as file:
|
|
141
|
+
answer_text = file.read()
|
|
142
|
+
answer_format = file_type(answer_file)
|
|
102
143
|
|
|
103
144
|
pf.run_filter(
|
|
104
145
|
filter_module.pandoc_filter,
|
|
105
146
|
doc=pf.convert_text(
|
|
106
|
-
answer_text, input_format=
|
|
147
|
+
answer_text, input_format=answer_format, standalone=True
|
|
107
148
|
),
|
|
108
149
|
module=module,
|
|
109
150
|
tex_file=answer_file,
|
|
@@ -112,7 +153,7 @@ def runner(
|
|
|
112
153
|
|
|
113
154
|
# Read the Python API format and convert to JSON.
|
|
114
155
|
if output_dir is not None:
|
|
115
|
-
|
|
156
|
+
module.to_json(output_dir)
|
|
116
157
|
|
|
117
158
|
return module
|
|
118
159
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "in2lambda"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.3"
|
|
4
4
|
description = "Converts content ready for import into Lambda Feedback"
|
|
5
5
|
authors = []
|
|
6
6
|
license = "MIT"
|
|
@@ -23,25 +23,25 @@ documentation = "https://lambda-feedback.github.io/in2lambda"
|
|
|
23
23
|
|
|
24
24
|
[tool.poetry.dependencies]
|
|
25
25
|
python = "^3.10"
|
|
26
|
-
panflute = "^2.3.
|
|
27
|
-
rich-click = "^1.7.
|
|
28
|
-
beartype = "^0.
|
|
26
|
+
panflute = "^2.3.1"
|
|
27
|
+
rich-click = "^1.7.4"
|
|
28
|
+
beartype = "^0.17.2"
|
|
29
29
|
|
|
30
30
|
[tool.poetry.scripts]
|
|
31
31
|
in2lambda = "in2lambda.main:cli"
|
|
32
32
|
|
|
33
33
|
[tool.poetry.group.dev.dependencies]
|
|
34
|
-
black = "^
|
|
34
|
+
black = "^24.3.0"
|
|
35
35
|
isort = "^5.13.2"
|
|
36
36
|
pydocstyle = "^6.3.0"
|
|
37
|
-
mypy = "^1.
|
|
38
|
-
pytest = "^
|
|
37
|
+
mypy = "^1.9.0"
|
|
38
|
+
pytest = "^8.1.1"
|
|
39
39
|
pytest-cov = "^4.1.0"
|
|
40
|
-
pytest-sugar = "^0.
|
|
41
|
-
pre-commit = "^3.6.
|
|
40
|
+
pytest-sugar = "^1.0.0"
|
|
41
|
+
pre-commit = "^3.6.2"
|
|
42
42
|
|
|
43
43
|
[tool.poetry.group.docs.dependencies]
|
|
44
|
-
furo = "^
|
|
44
|
+
furo = "^2024.1.29"
|
|
45
45
|
myst-parser = "^2.0.0"
|
|
46
46
|
sphinx = "^7.2.6"
|
|
47
47
|
sphinx-copybutton = "^0.5.2"
|
|
Binary file
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
+-----+----------------------------------------------------------------+
|
|
2
|
-
| Q | A Frahm\'s tachometer for measuring the rotational speed of |
|
|
3
|
-
| 1.1 | machinery consists of many small cantilever spring steel |
|
|
4
|
-
| | strips, each being rigidly fastened to the frame of the |
|
|
5
|
-
| | instrument at one end and having a mass at the other (free) |
|
|
6
|
-
| | end. A strip will vibrate (and so give a visual indication) |
|
|
7
|
-
| | when the speed of the machine is equal to the strip\'s natural |
|
|
8
|
-
| | frequency. One such strip is 1 mm thick, 7 mm wide and 50 mm |
|
|
9
|
-
| | long. Calculate the mass required at the free end so that the |
|
|
10
|
-
| | natural frequency will correspond to 1800 rev/min. |
|
|
11
|
-
| | |
|
|
12
|
-
| | **(0.082 kg)** |
|
|
13
|
-
+=====+================================================================+
|
|
14
|
-
| | Hint: Obtain the cantilever stiffness from data book. |
|
|
15
|
-
+-----+----------------------------------------------------------------+
|
|
16
|
-
| | |
|
|
17
|
-
+-----+----------------------------------------------------------------+
|
|
18
|
-
| | {width="3.9776432633420824in" |
|
|
19
|
-
| | height="2.075301837270341in"} |
|
|
20
|
-
| | |
|
|
21
|
-
| | a\) Cantilever steel strip and b) equivalent mass and spring |
|
|
22
|
-
| | system |
|
|
23
|
-
| | |
|
|
24
|
-
| | Calculate the effective stiffness of the beam by considering |
|
|
25
|
-
| | the standard formula of deflection (Engineering Data and |
|
|
26
|
-
| | Formulae Handbook). |
|
|
27
|
-
| | |
|
|
28
|
-
| | ------------------------- |
|
|
29
|
-
| | -------------------------------------------------------------- |
|
|
30
|
-
| | -------------------------------------------------------------- |
|
|
31
|
-
| | |
|
|
32
|
-
| | $$\mathbf{Stiffness}\mathbf{\ }\left( \mathbf{k} \rig |
|
|
33
|
-
| | ht)\mathbf{=}\frac{\mathbf{Force}}{\mathbf{Displacement}}$$ |
|
|
34
|
-
| | ------------------------- |
|
|
35
|
-
| | ------------------------------------------------------------- |
|
|
36
|
-
| | ----------------------------------------------------- -------- |
|
|
37
|
-
| | |
|
|
38
|
-
| | $\delta = \frac{FL^{3}}{3EI}$ where $I = \frac{bd^{3} |
|
|
39
|
-
| | }{12}$ (from Data and Formulae Handbook) |
|
|
40
|
-
| | |
|
|
41
|
-
| | $$\mathbf{\theref |
|
|
42
|
-
| | ore}$$ $$\ \frac{F}{\delta} = k = \frac{Ebd^{3}}{4L^{3}}$$ |
|
|
43
|
-
| | |
|
|
44
|
-
| | ------------------------- |
|
|
45
|
-
| | -------------------------------------------------------------- |
|
|
46
|
-
| | -------------------------------------------------------------- |
|
|
47
|
-
| | |
|
|
48
|
-
| | The natural frequency is given by, |
|
|
49
|
-
| | |
|
|
50
|
-
| | ------------------------------------ |
|
|
51
|
-
| | -------------------------------------------------------------- |
|
|
52
|
-
| | $$\mathbf{\omega}_{ |
|
|
53
|
-
| | \mathbf{n}}\mathbf{=}\sqrt{\frac{\mathbf{k}}{\mathbf{m}}}$$ |
|
|
54
|
-
| | -------- --------------------------- |
|
|
55
|
-
| | ----------------------------------------------------- -------- |
|
|
56
|
-
| | |
|
|
57
|
-
| | ------------------------------------ |
|
|
58
|
-
| | -------------------------------------------------------------- |
|
|
59
|
-
| | |
|
|
60
|
-
| | so, |
|
|
61
|
-
| | |
|
|
62
|
-
| | ------------------------------------------------------ |
|
|
63
|
-
| | -------------------------------------------------------------- |
|
|
64
|
-
| | -------------------------------------------------------------- |
|
|
65
|
-
| | $$\mathbf{\omega}_{\ |
|
|
66
|
-
| | mathbf{n}}^{\mathbf{2}}\mathbf{=}\frac{\mathbf{Eb}\mathbf{d}^{ |
|
|
67
|
-
| | \mathbf{3}}}{\mathbf{4}\mathbf{L}^{\mathbf{3}}\mathbf{m}}$$ |
|
|
68
|
-
| | ------------------------- ---------------------------- |
|
|
69
|
-
| | -------------------------------------------------------------- |
|
|
70
|
-
| | ----------------------------------------------------- -------- |
|
|
71
|
-
| | $$\mathbf{\therefore}$$ $$m = \frac{Edb^{3}} |
|
|
72
|
-
| | {4L^{3}\omega_{n}^{2}}$$ |
|
|
73
|
-
| | |
|
|
74
|
-
| | |
|
|
75
|
-
| | ------------------------------------------------------ |
|
|
76
|
-
| | -------------------------------------------------------------- |
|
|
77
|
-
| | -------------------------------------------------------------- |
|
|
78
|
-
| | |
|
|
79
|
-
| | Remember to convert from rpm to rad/s: ($\omega_{n}$ = |
|
|
80
|
-
| | 188.5 rad/s) |
|
|
81
|
-
| | |
|
|
82
|
-
| | ------------------- |
|
|
83
|
-
| | -------------------------------------------------------------- |
|
|
84
|
-
| | -------------------------------------------------------------- |
|
|
85
|
-
| | -------------------------------------------------------------- |
|
|
86
|
-
| | -------------------------------------------------------------- |
|
|
87
|
-
| | -------------------------------------------------------------- |
|
|
88
|
-
| | $$\mathbf{\ |
|
|
89
|
-
| | therefore}$$ $$\mathbf{m =}\frac{\mathbf{207 \times 1}\mathb |
|
|
90
|
-
| | f{0}^{\mathbf{9}}\mathbf{\times 7 \times}\mathbf{10}^{\mathbf{ |
|
|
91
|
-
| | - 3}}\mathbf{\times}\left( \mathbf{1 \times 1}\mathbf{0}^{\mat |
|
|
92
|
-
| | hbf{- 3}} \right)^{\mathbf{3}}}{\mathbf{4 \times 0.0}\mathbf{5 |
|
|
93
|
-
| | }^{\mathbf{3}}\mathbf{\times}\mathbf{188.5}^{\mathbf{2}}}$$ |
|
|
94
|
-
| | ------------------- |
|
|
95
|
-
| | ------ ------------------------------------------------------- |
|
|
96
|
-
| | -------------------------------------------------------------- |
|
|
97
|
-
| | -------------------------------------------------------------- |
|
|
98
|
-
| | -------------------------------------------------------------- |
|
|
99
|
-
| | ----------------------------------------------------- -------- |
|
|
100
|
-
| | $$\mathbf{\ |
|
|
101
|
-
| | therefore}$$ $$m\mathbf{=}\boxed{\mathbf{0.082\ kg}}$$ |
|
|
102
|
-
| | |
|
|
103
|
-
| | |
|
|
104
|
-
| | |
|
|
105
|
-
| | |
|
|
106
|
-
| | |
|
|
107
|
-
| | ------------------- |
|
|
108
|
-
| | -------------------------------------------------------------- |
|
|
109
|
-
| | -------------------------------------------------------------- |
|
|
110
|
-
| | -------------------------------------------------------------- |
|
|
111
|
-
| | -------------------------------------------------------------- |
|
|
112
|
-
| | -------------------------------------------------------------- |
|
|
113
|
-
+-----+----------------------------------------------------------------+
|
|
114
|
-
| | |
|
|
115
|
-
+-----+----------------------------------------------------------------+
|
|
116
|
-
| Q | A circular diaphragm, fixed all round its edge, has a natural |
|
|
117
|
-
| 1.2 | frequency of 5000 Hz. When an additional mass of 0.5 kg is |
|
|
118
|
-
| | attached to the centre of the diaphragm, the natural frequency |
|
|
119
|
-
| | is reduced to 4900 Hz. What are the effective mass and |
|
|
120
|
-
| | effective stiffness of the diaphragm, both referred to the |
|
|
121
|
-
| | central point |
|
|
122
|
-
| | |
|
|
123
|
-
| | **(12.14 kg,12.0 GN/m)** |
|
|
124
|
-
+-----+----------------------------------------------------------------+
|
|
125
|
-
| | Hint: Write the natural frequency formula twice, considering |
|
|
126
|
-
| | the cases of with and without added mass. |
|
|
127
|
-
+-----+----------------------------------------------------------------+
|
|
128
|
-
| | The diaphragm has effective mass, $m$ and effective stiffness, |
|
|
129
|
-
| | $k$ which are to be determined. The original natural frequency |
|
|
130
|
-
| | is simply given by $\sqrt{\frac{k}{m}}$ . When a 0.5 kg mass |
|
|
131
|
-
| | is added to the centre of the diaphragm, which is the point of |
|
|
132
|
-
| | maximum motion, the effective mass is increased by 0.5 kg. |
|
|
133
|
-
| | |
|
|
134
|
-
| | {width="2.2402777777777776in" |
|
|
135
|
-
| | height="1.4996205161854768in"} |
|
|
136
|
-
| | |
|
|
137
|
-
| | > Therefore, |
|
|
138
|
-
| | |
|
|
139
|
-
| | ---- |
|
|
140
|
-
| | -------------------------------------------------------------- |
|
|
141
|
-
| | |
|
|
142
|
-
| | $$\omega_{n1} = \sqrt{\frac{k}{m_{1}}}$$ \(1\) |
|
|
143
|
-
| | ---- |
|
|
144
|
-
| | ---- ------------------------------------------------ -------- |
|
|
145
|
-
| | |
|
|
146
|
-
| | $$\omega_{n2} = \sqrt{\frac{k}{m_{1} + 0.5}}$$ \(2\) |
|
|
147
|
-
| | |
|
|
148
|
-
| | ---- |
|
|
149
|
-
| | -------------------------------------------------------------- |
|
|
150
|
-
| | |
|
|
151
|
-
| | The two equations are used to solve for two unknowns, |
|
|
152
|
-
| | |
|
|
153
|
-
| | From equ (1), |
|
|
154
|
-
| | |
|
|
155
|
-
| | ------------------------------------------------------------ |
|
|
156
|
-
| | $$k = \omega_{n1}^{2}m_{1}$$ \(3\) |
|
|
157
|
-
| | -------- ------------------------------------------ -------- |
|
|
158
|
-
| | |
|
|
159
|
-
| | ------------------------------------------------------------ |
|
|
160
|
-
| | |
|
|
161
|
-
| | Substitute equ (3) into equ (2), |
|
|
162
|
-
| | |
|
|
163
|
-
| | ----------------------------------------------- |
|
|
164
|
-
| | -------------------------------------------------------------- |
|
|
165
|
-
| | $$\omega_{n2}^{2} = \frac{\ome |
|
|
166
|
-
| | ga_{n1}^{2}m_{1}}{m_{1} + 0.5}$$ |
|
|
167
|
-
| | -------- -------------------------------------- |
|
|
168
|
-
| | ----------------------------------------------------- -------- |
|
|
169
|
-
| | $$m_{1}\left( \omega_{n1}^{2} |
|
|
170
|
-
| | - \omega_{n2}^{2} \right) + \frac{\omega_{n2}^{2}}{2} = 0$$ |
|
|
171
|
-
| | |
|
|
172
|
-
| | ----------------------------------------------- |
|
|
173
|
-
| | -------------------------------------------------------------- |
|
|
174
|
-
| | |
|
|
175
|
-
| | > Remember to convert Hz to rad/s. |
|
|
176
|
-
| | |
|
|
177
|
-
| | +-------+-----------------------------------------+-------+ |
|
|
178
|
-
| | | | $$m_{1} | | |
|
|
179
|
-
| | | | = \frac{\omega_{n2}^{2}}{2\left( \omeg | | |
|
|
180
|
-
| | | | a_{n1}^{2} - \omega_{n2}^{2} \right)}$$ | | |
|
|
181
|
-
| | | | | | |
|
|
182
|
-
| | | | $$\mat | | |
|
|
183
|
-
| | | | hbf{\ \ \ \ \ \ \ =}\frac{(4900 \times | | |
|
|
184
|
-
| | | | 2\pi)^{2}}{2\left( (5000 \times 2\pi)^ | | |
|
|
185
|
-
| | | | {2} - (4900 \times 2\pi)^{2} \right)}$$ | | |
|
|
186
|
-
| | | | | | |
|
|
187
|
-
| | | | $$\mathbf{\ \ \ | | |
|
|
188
|
-
| | | | \ \ \ \ =}\boxed{\mathbf{12.13\ kg}}$$ | | |
|
|
189
|
-
| | +=======+=========================================+=======+ |
|
|
190
|
-
| | +-------+-----------------------------------------+-------+ |
|
|
191
|
-
| | |
|
|
192
|
-
| | > And from equ (3), $k = \boxed{\mathbf{12.0\ GN/m}}$ |
|
|
193
|
-
+-----+----------------------------------------------------------------+
|
|
194
|
-
| | |
|
|
195
|
-
+-----+----------------------------------------------------------------+
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""A solution appears after each individual part."""
|
|
2
|
-
|
|
3
|
-
from collections import deque
|
|
4
|
-
from typing import Optional
|
|
5
|
-
|
|
6
|
-
import panflute as pf
|
|
7
|
-
|
|
8
|
-
from in2lambda.api.module import Module
|
|
9
|
-
from in2lambda.filters.markdown import filter
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@filter
|
|
13
|
-
def pandoc_filter(
|
|
14
|
-
elem: pf.Element,
|
|
15
|
-
doc: pf.elements.Doc,
|
|
16
|
-
module: Module,
|
|
17
|
-
parsing_answers: bool,
|
|
18
|
-
) -> Optional[pf.Str]:
|
|
19
|
-
"""A Pandoc filter that parses and translates various TeX elements.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
elem: The current TeX element being processed. This could be a paragraph,
|
|
23
|
-
ordered list, etc.
|
|
24
|
-
doc: A Pandoc document container - essentially the Pandoc AST.
|
|
25
|
-
module: The Python API that is used to store the result after processing
|
|
26
|
-
the TeX file.
|
|
27
|
-
parsing_answers: Whether an answers-only document is currently being parsed.
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
Converted TeX elements for the AST where required
|
|
31
|
-
e.g. replaces math equations so that they are surrounded by $.
|
|
32
|
-
"""
|
|
33
|
-
# Question text (ListItem -> List -> Doc)
|
|
34
|
-
if isinstance(elem, pf.Table):
|
|
35
|
-
match type(elem):
|
|
36
|
-
case pf.Para:
|
|
37
|
-
pandoc_filter.solutions = deque()
|
|
38
|
-
if hasattr(pandoc_filter, "question"):
|
|
39
|
-
pandoc_filter.question.append(pf.stringify(elem))
|
|
40
|
-
else:
|
|
41
|
-
pandoc_filter.question = [pf.stringify(elem)]
|
|
42
|
-
case pf.OrderedList:
|
|
43
|
-
for listItem in elem.content:
|
|
44
|
-
part = [
|
|
45
|
-
pf.stringify(item)
|
|
46
|
-
for item in listItem.content
|
|
47
|
-
if not isinstance(item, pf.Div)
|
|
48
|
-
]
|
|
49
|
-
module.current_question.add_part_text("\n".join(part))
|
|
50
|
-
module.current_question.add_solution(
|
|
51
|
-
pandoc_filter.solutions.popleft()
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if isinstance(elem, pf.Div):
|
|
55
|
-
pandoc_filter.solutions.append(pf.stringify(elem))
|
|
56
|
-
if pandoc_filter.question:
|
|
57
|
-
module.add_question(main_text="\n".join(pandoc_filter.question))
|
|
58
|
-
module.current_question.add_solution(pf.stringify(elem))
|
|
59
|
-
module.current_question._last_part["solution"] -= 1
|
|
60
|
-
pandoc_filter.question = []
|
|
61
|
-
return None
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"isPublished": false,
|
|
3
|
-
"masterContent": "Top level question here",
|
|
4
|
-
"parts": [
|
|
5
|
-
{
|
|
6
|
-
"answer": "",
|
|
7
|
-
"content": "Part text here",
|
|
8
|
-
"responseArea": [],
|
|
9
|
-
"tutorial": [],
|
|
10
|
-
"universalPartId": "N/A",
|
|
11
|
-
"workedSolution": [
|
|
12
|
-
{
|
|
13
|
-
"content": "Part worked solution here",
|
|
14
|
-
"id": "N/A",
|
|
15
|
-
"title": ""
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|
|
19
|
-
],
|
|
20
|
-
"questionSettings": {
|
|
21
|
-
"displayFinalAnswer": true,
|
|
22
|
-
"displayStructuredTutorial": true,
|
|
23
|
-
"displayWorkedSolution": true
|
|
24
|
-
},
|
|
25
|
-
"title": "Question title here"
|
|
26
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|