Skip to content

Commit

Permalink
- for the sake of efficiency, it keeps track of files already compile…
Browse files Browse the repository at this point in the history
…d/converted and/or transferred

- dropped "make directory" messages when running in "local" mode
  • Loading branch information
manuvazquez committed Apr 19, 2020
1 parent 3a4ded7 commit 9e6d138
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 22 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ The output will be a text file in GIFT format with the same name as the input on

### Questions

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).
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).

### Example

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).
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).

## Including images

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

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

### Browser compatibility

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.

### Do I really need pdf2svg?

Only if you want automatic conversion from `.tex` (passing through `.pdf`) to `.svg`, i.e., only if you embed a `.tex` somewhere.
Expand Down
52 changes: 41 additions & 11 deletions question.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class HtmlQuestion(metaclass=abc.ABCMeta):
Abstract class implementing an html-based question.
"""

def __init__(self, name: str, statement: str, images_settings: dict, feedback: Optional[str] = None):
def __init__(self, name: str, statement: str, images_settings: dict, history: dict, feedback: Optional[str] = None):
"""
Initializer.
Expand All @@ -33,6 +33,7 @@ def __init__(self, name: str, statement: str, images_settings: dict, feedback: O
self.name = name
self.statement = statement.rstrip()
self.feedback = feedback
self.history = history

assert ('width' in images_settings) and ('height' in images_settings),\
'"width" and/or "height" missing in "image_settings"'
Expand Down Expand Up @@ -115,9 +116,10 @@ class Numerical(HtmlQuestion):
"""

def __init__(
self, name: str, statement: str, images_settings: dict, solution: dict, feedback: Optional[str] = None):
self, name: str, statement: str, images_settings: dict, history: dict, solution: dict,
feedback: Optional[str] = None):

super().__init__(name, statement, images_settings, feedback)
super().__init__(name, statement, images_settings, history, feedback)

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

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

def __init__(self, name: str, statement: str, images_settings: dict, answers: dict, feedback: Optional[str] = None):
def __init__(
self, name: str, statement: str, images_settings: dict, history: dict, answers: dict,
feedback: Optional[str] = None):

super().__init__(name, statement, images_settings, feedback)
super().__init__(name, statement, images_settings, history, feedback)

self.answers = answers

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

# breakpoint()

# every one of them...
for file in files:

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

super().__init__(decorated)

def process_match(f):

# if this file has not been already compiled-converted...
if f not in self.history['already compiled']:

# ...it is...
image.pdf_to_svg(image.compile_tex(f))

# ...and a note is made of it
self.history['already compiled'].add(f)

# else:
#
# print(f'{f} already compiled-converted...')

# a new pre-processing function is attached to the corresponding list
# (the "\1" in `replacement` refers to matches in `pattern`)
self.pre_processing_functions.append(functools.partial(
self.transform_files, pattern='(\S+)\.tex', process_match=lambda x: image.pdf_to_svg(image.compile_tex(x)),
replacement=r'\1.svg'))
self.transform_files, pattern='(\S+)\.tex', process_match=process_match, replacement=r'\1.svg'))


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

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

def process_match(f):

# if this file has not been already transferred...
if f not in self.history['already transferred']:

# ...it is...
connection.copy(f, remote_directory=remote_subdirectory / pathlib.Path(f).parent)

# ...and a note is made of it
self.history['already transferred'].add(f)

# else:
#
# print(f'{f} already transferred...')

# a new pre-processing function is attached to the corresponding list
self.pre_processing_functions.append(functools.partial(
self.transform_files, pattern='(?<!\S)(?!http)(\S+\.svg)\??(?!\S)',
process_match=lambda f: connection.copy(f, remote_directory=remote_subdirectory / pathlib.Path(f).parent),
replacement=replacement_function))
process_match=process_match, replacement=replacement_function))
22 changes: 17 additions & 5 deletions remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,20 +109,32 @@ def make_directory_at(self, new: Union[str, pathlib.Path], at: str):

class FakeConnection:
"""
For debugging purposes.
For offline runs.
"""

def __init__(self) -> None:

self.already_copied = set()

@staticmethod
def is_active():

return False

@staticmethod
def copy(source: Union[str, pathlib.Path], remote_directory: str):
def copy(self, source: Union[str, pathlib.Path], remote_directory: str):

source = pathlib.Path(source)

if source.as_posix() not in self.already_copied:

print(
f'{colors.info}you *should* copy {colors.reset}{source}{colors.info} to'
f' {colors.reset}{remote_directory}')

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

@staticmethod
def make_directory_at(new: str, at: str):

print(f'{colors.info}you *should* make directory {colors.reset}{new}{colors.info} at {colors.reset}{at}')
pass
# print(f'{colors.info}you *should* make directory {colors.reset}{new}{colors.info} at {colors.reset}{at}')
14 changes: 10 additions & 4 deletions wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@
# ...an actual connection with the requested host is opened
connection = remote.Connection(parameters['images hosting']['copy']['host'], **parameters['images hosting']['ssh'])

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

# to keep track of files already compiled/transferred
history = {'already compiled': set(), 'already transferred': set()}

with open(output_file, 'w') as f:

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

# that class that will be instantiated for this particular question
# the class that will be instantiated for this particular question
question_class = getattr(question, q['class'])

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

# question is instantiated and decorated
# "history" is passed
q['history'] = history

# question is instantiated and "decorated"
q = question.SvgToHttp(
question.TexToSvg(question_class(**q)), connection,
parameters['images hosting']['copy']['public filesystem root'],
pictures_base_directory, parameters['images hosting']['public URL'])

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

print(f'file "{output_file}" created')
print(f'{colors.info}file "{colors.reset}{output_file}{colors.info}" created')

0 comments on commit 9e6d138

Please sign in to comment.