Skip to content

Commit 9e6d138

Browse files
committed
- for the sake of efficiency, it keeps track of files already compiled/converted and/or transferred
- dropped "make directory" messages when running in "local" mode
1 parent 3a4ded7 commit 9e6d138

File tree

4 files changed

+74
-22
lines changed

4 files changed

+74
-22
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ The output will be a text file in GIFT format with the same name as the input on
5656

5757
### Questions
5858

59-
Questions are specified through another *YAML* file. The first parameter, `pictures base directory`, refers to the base directory that will be created in the remote host for all your embedded images. It is meant to separate different question banks (so that you can have, e.g., directories `quiz 1` and `quiz 2`). The remaining of the file is a **list of categories**, and inside each one there is a **list of questions**. Hopefully, the format is clear from either the name of the settings and/or its companion comments. You are probably better of taking a look at the [provided example](bank.yaml).
59+
Questions are specified through another *YAML* file. The first parameter, `pictures base directory`, refers to the base directory that will be created in the remote host for all your embedded images. It is meant to separate different question banks (so that you can have, e.g., directories `quiz 1` and `quiz 2`). The remaining of the file is a **list of categories**, and inside each one there is a **list of questions**. Hopefully, the format is clear from either the name of the settings and/or its companion comments. You are probably better off taking a look at the [provided example](bank.yaml).
6060

6161
### Example
6262

63-
If you run the program inside the `gift-wrapper` directory as is, it will process the sample `bank.yaml` which includes a `.tex`, a `.svg` and some mathematical formulas, and will generate a `bank.gift` file which you can import from Moodle (choosing the GIFT format when asked).
63+
If you run the program inside the `gift-wrapper` directory as is, it will process the sample `bank.yaml` which includes a `.tex`, a `.svg` and some mathematical formulas, and will generate a `bank.gift.txt` file which you can import from Moodle (choosing the GIFT format when asked).
6464

6565
## Including images
6666

@@ -73,6 +73,10 @@ In any case, you just need to write the path to the file inside the text of the
7373

7474
Images (*svg*s) are then copied to a remote host, and properly linked in the output GIFT file.
7575

76+
### Browser compatibility
77+
78+
It seems (it has been reported) not every browser properly handles svg images (maybe other types too) embedded in a question as an URL. My experience so far is both *Firefox* and *Chromium* (at the time of writing this) work just fine.
79+
7680
### Do I really need pdf2svg?
7781

7882
Only if you want automatic conversion from `.tex` (passing through `.pdf`) to `.svg`, i.e., only if you embed a `.tex` somewhere.

question.py

+41-11
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class HtmlQuestion(metaclass=abc.ABCMeta):
1414
Abstract class implementing an html-based question.
1515
"""
1616

17-
def __init__(self, name: str, statement: str, images_settings: dict, feedback: Optional[str] = None):
17+
def __init__(self, name: str, statement: str, images_settings: dict, history: dict, feedback: Optional[str] = None):
1818
"""
1919
Initializer.
2020
@@ -33,6 +33,7 @@ def __init__(self, name: str, statement: str, images_settings: dict, feedback: O
3333
self.name = name
3434
self.statement = statement.rstrip()
3535
self.feedback = feedback
36+
self.history = history
3637

3738
assert ('width' in images_settings) and ('height' in images_settings),\
3839
'"width" and/or "height" missing in "image_settings"'
@@ -115,9 +116,10 @@ class Numerical(HtmlQuestion):
115116
"""
116117

117118
def __init__(
118-
self, name: str, statement: str, images_settings: dict, solution: dict, feedback: Optional[str] = None):
119+
self, name: str, statement: str, images_settings: dict, history: dict, solution: dict,
120+
feedback: Optional[str] = None):
119121

120-
super().__init__(name, statement, images_settings, feedback)
122+
super().__init__(name, statement, images_settings, history, feedback)
121123

122124
assert ('value' in solution), '"value" missing in "solution"'
123125

@@ -141,9 +143,11 @@ class MultipleChoice(HtmlQuestion):
141143
Class implementing a multiple-choice question.
142144
"""
143145

144-
def __init__(self, name: str, statement: str, images_settings: dict, answers: dict, feedback: Optional[str] = None):
146+
def __init__(
147+
self, name: str, statement: str, images_settings: dict, history: dict, answers: dict,
148+
feedback: Optional[str] = None):
145149

146-
super().__init__(name, statement, images_settings, feedback)
150+
super().__init__(name, statement, images_settings, history, feedback)
147151

148152
self.answers = answers
149153

@@ -199,8 +203,6 @@ def transform_files(
199203
# all the matching files in the given text
200204
files = re.findall(pattern, text)
201205

202-
# breakpoint()
203-
204206
# every one of them...
205207
for file in files:
206208

@@ -217,11 +219,25 @@ def __init__(self, decorated: Union[HtmlQuestion, QuestionDecorator]):
217219

218220
super().__init__(decorated)
219221

222+
def process_match(f):
223+
224+
# if this file has not been already compiled-converted...
225+
if f not in self.history['already compiled']:
226+
227+
# ...it is...
228+
image.pdf_to_svg(image.compile_tex(f))
229+
230+
# ...and a note is made of it
231+
self.history['already compiled'].add(f)
232+
233+
# else:
234+
#
235+
# print(f'{f} already compiled-converted...')
236+
220237
# a new pre-processing function is attached to the corresponding list
221238
# (the "\1" in `replacement` refers to matches in `pattern`)
222239
self.pre_processing_functions.append(functools.partial(
223-
self.transform_files, pattern='(\S+)\.tex', process_match=lambda x: image.pdf_to_svg(image.compile_tex(x)),
224-
replacement=r'\1.svg'))
240+
self.transform_files, pattern='(\S+)\.tex', process_match=process_match, replacement=r'\1.svg'))
225241

226242

227243
class SvgToHttp(QuestionDecorator):
@@ -244,8 +260,22 @@ def replacement_function(m: re.Match) -> str:
244260

245261
return public_url + pictures_base_directory + '/' + file.as_posix()
246262

263+
def process_match(f):
264+
265+
# if this file has not been already transferred...
266+
if f not in self.history['already transferred']:
267+
268+
# ...it is...
269+
connection.copy(f, remote_directory=remote_subdirectory / pathlib.Path(f).parent)
270+
271+
# ...and a note is made of it
272+
self.history['already transferred'].add(f)
273+
274+
# else:
275+
#
276+
# print(f'{f} already transferred...')
277+
247278
# a new pre-processing function is attached to the corresponding list
248279
self.pre_processing_functions.append(functools.partial(
249280
self.transform_files, pattern='(?<!\S)(?!http)(\S+\.svg)\??(?!\S)',
250-
process_match=lambda f: connection.copy(f, remote_directory=remote_subdirectory / pathlib.Path(f).parent),
251-
replacement=replacement_function))
281+
process_match=process_match, replacement=replacement_function))

remote.py

+17-5
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,32 @@ def make_directory_at(self, new: Union[str, pathlib.Path], at: str):
109109

110110
class FakeConnection:
111111
"""
112-
For debugging purposes.
112+
For offline runs.
113113
"""
114114

115+
def __init__(self) -> None:
116+
117+
self.already_copied = set()
118+
115119
@staticmethod
116120
def is_active():
117121

118122
return False
119123

120-
@staticmethod
121-
def copy(source: Union[str, pathlib.Path], remote_directory: str):
124+
def copy(self, source: Union[str, pathlib.Path], remote_directory: str):
125+
126+
source = pathlib.Path(source)
127+
128+
if source.as_posix() not in self.already_copied:
129+
130+
print(
131+
f'{colors.info}you *should* copy {colors.reset}{source}{colors.info} to'
132+
f' {colors.reset}{remote_directory}')
122133

123-
print(f'{colors.info}you *should* copy {colors.reset}{source}{colors.info} to {colors.reset}{remote_directory}')
134+
self.already_copied.add(source.as_posix())
124135

125136
@staticmethod
126137
def make_directory_at(new: str, at: str):
127138

128-
print(f'{colors.info}you *should* make directory {colors.reset}{new}{colors.info} at {colors.reset}{at}')
139+
pass
140+
# print(f'{colors.info}you *should* make directory {colors.reset}{new}{colors.info} at {colors.reset}{at}')

wrap.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@
6161
# ...an actual connection with the requested host is opened
6262
connection = remote.Connection(parameters['images hosting']['copy']['host'], **parameters['images hosting']['ssh'])
6363

64-
# output file has the same name as the input with the ".gift" suffix
64+
# output file has the same name as the input with the ".gift.txt" suffix
6565
output_file = input_file.with_suffix('.gift.txt')
6666

67+
# to keep track of files already compiled/transferred
68+
history = {'already compiled': set(), 'already transferred': set()}
69+
6770
with open(output_file, 'w') as f:
6871

6972
# for every category...
@@ -86,7 +89,7 @@
8689
# for every question in the category...
8790
for q in tqdm.tqdm(cat['questions'], desc='question', leave=False):
8891

89-
# that class that will be instantiated for this particular question
92+
# the class that will be instantiated for this particular question
9093
question_class = getattr(question, q['class'])
9194

9295
# if field `images_settings` is not present...
@@ -98,12 +101,15 @@
98101
# the class is removed from the dictionary so that it doesn't get passed to the initializer
99102
del q['class']
100103

101-
# question is instantiated and decorated
104+
# "history" is passed
105+
q['history'] = history
106+
107+
# question is instantiated and "decorated"
102108
q = question.SvgToHttp(
103109
question.TexToSvg(question_class(**q)), connection,
104110
parameters['images hosting']['copy']['public filesystem root'],
105111
pictures_base_directory, parameters['images hosting']['public URL'])
106112

107113
f.write(f'{q.gift}\n\n')
108114

109-
print(f'file "{output_file}" created')
115+
print(f'{colors.info}file "{colors.reset}{output_file}{colors.info}" created')

0 commit comments

Comments
 (0)