From 79c718ffc851095dddcd02c8380a6d9c17efa370 Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Wed, 20 Apr 2022 18:16:47 +0300 Subject: [PATCH 01/13] Fix the shebang: the script requires Python 3 Signed-off-by: Paul Wolneykien --- quizgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quizgen.py b/quizgen.py index a56261d..89c4e6c 100755 --- a/quizgen.py +++ b/quizgen.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python3 # -*- coding: utf-8 -*- # The MIT License (MIT) From d22a4ca076d6df7fcd28e5eb8a3b4aa00fa9fdad Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Wed, 20 Apr 2022 18:24:06 +0300 Subject: [PATCH 02/13] Added GNU Gettext invocations and a message template .pot file --- po/quizgen.pot | 47 +++++++++++++++++++++++++++++++++++++++++++++++ quizgen.py | 31 ++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 po/quizgen.pot diff --git a/po/quizgen.pot b/po/quizgen.pot new file mode 100644 index 0000000..d29a63e --- /dev/null +++ b/po/quizgen.pot @@ -0,0 +1,47 @@ +# Translations for Quizgen by Karanveer Mohan. +# Copyright (C) 2022 +# This file is distributed under the same license as the Quizgen package. +# Paul Wolneykien , 2022. +# +# +msgid "" +msgstr "" +"Project-Id-Version: Quizgen 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-04-20 17:57+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: quizgen.py:234 +msgid "Correct! " +msgstr "" + +#: quizgen.py:238 +msgid "Incorrect. " +msgstr "" + +#: quizgen.py:294 +msgid "This option is correct. " +msgstr "" + +#: quizgen.py:297 +msgid "This option is incorrect. " +msgstr "" + +#: quizgen.py:327 quizgen.py:419 +msgid "Submit" +msgstr "" + +#: quizgen.py:420 +msgid "Hide" +msgstr "" + +#: quizgen.py:508 +#, python-format +msgid "Page generated using %s" +msgstr "" diff --git a/quizgen.py b/quizgen.py index 89c4e6c..b0017e9 100755 --- a/quizgen.py +++ b/quizgen.py @@ -30,6 +30,12 @@ import glob import itertools +import gettext +import os.path +if os.path.isdir('./mo'): + gettext.install('quizgen', './mo') +else: + gettext.install('quizgen') """ Parse the Quiz to create a python dict @@ -229,11 +235,11 @@ def create_single_choice_dom_from_option(option): if option['correct']: span.attributes['class'] = 'right' response_div.attributes['class'] = 'response right' - span.appendChild(doc.createTextNode('Correct! ')) + span.appendChild(doc.createTextNode(_('Correct! '))) else: span.attributes['class'] = 'wrong' response_div.attributes['class'] = 'response wrong' - span.appendChild(doc.createTextNode('Incorrect. ')) + span.appendChild(doc.createTextNode(_('Incorrect. '))) response_div.appendChild(span) response_div.appendChild(doc.createTextNode(option['explanation'])) @@ -289,10 +295,10 @@ def create_multiple_choice_dom_from_option(option): explanation = '' if option['correct']: response_div.attributes['class'] = 'response right' - explanation = 'This option is correct. ' + explanation = _('This option is correct. ') else: response_div.attributes['class'] = 'response wrong' - explanation = 'This option is incorrect. ' + explanation = _('This option is incorrect. ') response_div.appendChild(doc.createTextNode(explanation + option['explanation'])) @@ -322,7 +328,7 @@ def create_multiple_choice_dom_from_question(question): div.appendChild(elem) button = doc.createElement('button') - button.appendChild(doc.createTextNode('Submit')) + button.appendChild(doc.createTextNode(_('Submit'))) div.appendChild(button) return div @@ -414,6 +420,8 @@ def add_dom_to_template(dom, html_file_name, quiz): # Add the header. By replacing this early, we allow the header to # contain IMG and LINK tags (or even CODE), though it would typically # be pure HTML + content = content.replace('[SUBMIT_LABEL]', _('Submit')) + content = content.replace('[HIDE_LABEL]', _('Hide')) content = content.replace('[HEADER]', get_header()) content = content.replace('[TITLE]', quiz['title']) content = content.replace('[BODY]', dom.toprettyxml()) @@ -501,7 +509,7 @@ def get_footer(): try: footer_file = open('footer.html') except IOError: - footer = 'Page generated using Quizgen' + footer = _('Page generated using %s') % 'Quizgen' else: footer = footer_file.read() return footer @@ -598,6 +606,11 @@ def main(): + + From b9094ed17331fa0d05c176a25d44cd191bb95415 Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Wed, 20 Apr 2022 21:55:58 +0300 Subject: [PATCH 05/13] Pass-through some HTML tags Signed-off-by: Paul Wolneykien --- quizgen.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/quizgen.py b/quizgen.py index be90da3..e67e231 100755 --- a/quizgen.py +++ b/quizgen.py @@ -441,6 +441,36 @@ def add_dom_to_template(dom, html_file_name, quiz): content = re.sub('\|\|CODE:(\S+):\s?(.*?)\|\|', r'
\2
', content, flags=re.DOTALL) + content = re.sub('<br\s*/>', r'
', content); + content = re.sub('<hr\s*/>', r'
', content); + content = re.sub('<center>', r'', content); + content = re.sub('</center>', r'', content); + content = re.sub('<blockquote>', r'
', content); + content = re.sub('</blockquote>', r'
', content); + content = re.sub('<p>', r'

', content); + content = re.sub('</p>', r'

', content); + content = re.sub('<em>', r'', content); + content = re.sub('</em>', r'', content); + content = re.sub('<strong>', r'', content); + content = re.sub('</strong>', r'', content); + content = re.sub('<code>', r'', content); + content = re.sub('</code>', r'', content); + content = re.sub('<span(\s+[^&]*)>', r'', content); + content = re.sub('</span>', r'', content); + content = re.sub('<div(\s+[^&]*)>', r'', content); + content = re.sub('</div>', r'', content); + content = re.sub('<ul>', r'
    ', content); + content = re.sub('</ul>', r'
', content); + content = re.sub('<ol>', r'
    ', content); + content = re.sub('</ol>', r'
', content); + content = re.sub('<li>', r'
  • ', content); + content = re.sub('</li>', r'
  • ', content); + content = re.sub('<sub>', r'', content); + content = re.sub('</sub>', r'', content); + content = re.sub('<sup>', r'', content); + content = re.sub('</sup>', r'', content); + content = re.sub('<u(nderline)?>', r'', content); + content = re.sub('</u(nderline)?>', r'', content); generated_file.write(content) generated_file.close() From 9f171135c3cda37b112f3fe0e77d282aa5c494be Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Wed, 20 Apr 2022 21:56:10 +0300 Subject: [PATCH 06/13] Allow to make labelled links using ||LINK:URL text...|| --- quizgen.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quizgen.py b/quizgen.py index e67e231..f720670 100755 --- a/quizgen.py +++ b/quizgen.py @@ -435,6 +435,7 @@ def add_dom_to_template(dom, html_file_name, quiz): content = re.sub('\|\|IMG:\s?(\S+)\|\|', r'
    ', content) # For the links + content = re.sub('\|\|LINK:\s?(\S+)\s+(\S.*)\|\|', r'\2', content) content = re.sub('\|\|LINK:\s?(\S+)\|\|', r'\1', content) # For code blocks From c256920ee72f6a2933804e84d8a349a4c3ebb971 Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Wed, 20 Apr 2022 22:10:21 +0300 Subject: [PATCH 07/13] Support -r command line option to turn on quiz randomization Using shuffle was the default and very surprising option in Quizgen. Signed-off-by: Paul Wolneykien --- quizgen.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/quizgen.py b/quizgen.py index f720670..5a42a07 100755 --- a/quizgen.py +++ b/quizgen.py @@ -49,6 +49,8 @@ def __init__(self, filename): else: self.filename = '%s.quiz' % filename + self.randomize = False; + def get_filename(self): return self.filename @@ -206,10 +208,13 @@ def parse(self): quiz['problem_groups'][-1]['questions'].append(question) except: raise Exception('ERROR. Are you sure you started every problem group with "[]"?') - for pg in quiz["problem_groups"]: + + if self.randomize: + for pg in quiz["problem_groups"]: random.shuffle(pg["questions"]) for ql in pg["questions"]: - random.shuffle(ql["options"]) + random.shuffle(ql["options"]) + return quiz @@ -488,7 +493,7 @@ def add_dom_to_template(dom, html_file_name, quiz): def usage(): print (""" - Usage: python quizgen.py SOURCE_QUIZ_FILE. + Usage: python quizgen.py [options] SOURCE_QUIZ_FILE. You may like to: sudo cp quizgen.py /usr/bin/quizgen so that you can simply type quizgen. @@ -504,7 +509,9 @@ def usage(): This file shows all the features of quizgen along with the format. You may provide a footer and/or header to appear on your quizzes by creating - a file named footer.html and/or header.html that contains an html fragment. + a file named footer.html and/or header.html that contains an html fragment. + + In order to shuffle questions and options use the -r option. More information and a lot of sample quizzes file can be found on: https://github.com/karanveerm/quizgen @@ -552,8 +559,13 @@ def main(): elif '-c' in sys.argv[1]: create_sample() else: + randomize = False + if '-r' in sys.argv[1]: + sys.argv.pop(1) + randomize = True for filename in sys.argv[1:]: quiz_parser = QuizParser(filename) + quiz_parser.randomize = randomize quiz = quiz_parser.parse() From 724943b0ab1998e37406f22e8da0df29d16b4d64 Mon Sep 17 00:00:00 2001 From: Paul Wolneykien Date: Thu, 21 Apr 2022 21:28:17 +0300 Subject: [PATCH 08/13] Implemented result counting Signed-off-by: Paul Wolneykien --- quiz.css | 42 ++++++++++++++++++ quizgen.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++-- sample.html | 121 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 247 insertions(+), 33 deletions(-) diff --git a/quiz.css b/quiz.css index 431a259..7de545c 100644 --- a/quiz.css +++ b/quiz.css @@ -135,3 +135,45 @@ button:hover{ button:focus { outline: none; } + +#results { + position: fixed; + bottom: 0px; + right: 0px; + margin: 1em; +} + +#right_counter, #wrong_counter, #results > .delimiter { + display: inline-block; + font-size: 150%; + font-weight: bold; + margin: 0px 0px; + text-align: center; + vertical-align: middle; +} + +#right_counter, #wrong_counter { + border-radius: 50%; + color: white; + padding: 0.3em 0.6em; + transition: padding 0.5s; +} + +#right_counter { + background-color: green; +} + +#results > .delimiter { + background-color: transparent; + padding: 0px 0px; + margin: 0px 0px; +} + +#wrong_counter { + background-color: red; +} + +#right_counter.rise, #wrong_counter.rise { + padding: 0.5em 0.9em; + transition: padding 0.5s; +} \ No newline at end of file diff --git a/quizgen.py b/quizgen.py index 5a42a07..00ba87a 100755 --- a/quizgen.py +++ b/quizgen.py @@ -231,6 +231,7 @@ def create_single_choice_dom_from_option(option): # and a 'response', i.e the response to be shown when that option is selected selector_div = doc.createElement('div') selector_div.attributes['class'] = 'selection' + selector_div.attributes['name'] = 'pg%04d_q%04d' % (option['pg_num'], option['q_num']) selector_div.appendChild(doc.createTextNode(option['description'])) response_div = doc.createElement('div') @@ -264,6 +265,8 @@ def create_single_choice_dom_from_question(question): ol.attributes['type'] = 'a' for option in question['options']: + option['pg_num'] = question['pg_num'] + option['q_num'] = question['num'] elem = create_single_choice_dom_from_option(option) ol.appendChild(elem) return ol @@ -333,6 +336,7 @@ def create_multiple_choice_dom_from_question(question): div.appendChild(elem) button = doc.createElement('button') + button.attributes['name'] = 'pg%04d_q%04d' % (question['pg_num'], question['num']) button.appendChild(doc.createTextNode(_('Submit'))) div.appendChild(button) @@ -380,14 +384,16 @@ def create_dom_from_problem_group(problem_group): div.appendChild(doc.createTextNode(problem_group['problem_intro'])) fieldset.appendChild(div) - first_question = True + q_num = 0 for question in problem_group['questions']: + question['pg_num'] = problem_group['num'] + question['num'] = q_num hr = doc.createElement('hr') - if not first_question or problem_group['problem_intro']: + if q_num > 0 or problem_group['problem_intro']: fieldset.appendChild(hr) - first_question = False elem = create_dom_from_question(question) fieldset.appendChild(elem) + q_num = q_num + 1 return fieldset @@ -403,10 +409,13 @@ def create_dom_from_quiz(quiz): header.appendChild(doc.createTextNode(quiz['title'])) wrapper.appendChild(header) + pg_num = 0 for problem_group in quiz['problem_groups']: + problem_group['num'] = pg_num elem = create_dom_from_problem_group(problem_group) wrapper.appendChild(elem) wrapper.appendChild(doc.createElement('br')) + pg_num = pg_num + 1 return wrapper @@ -548,6 +557,16 @@ def get_footer(): footer_file = open('footer.html') except IOError: footer = _('Page generated using %s') % 'Quizgen' + footer = footer + """ +
    +
    + 0 +
    + / +
    + 0 +
    +
    """ else: footer = footer_file.read() return footer @@ -654,6 +673,43 @@ def main(): const HIDE_LABEL = '[HIDE_LABEL]'; + + @@ -20,6 +20,48 @@ + + + + @@ -914,14 +918,16 @@ def main(): outline: none; } -#results { +#floating_results { position: fixed; bottom: 0px; right: 0px; margin: 1em; } -#right_counter, #wrong_counter, #results > .delimiter { +#floating_results .right_counter, +#floating_results .wrong_counter, +#floating_results .delimiter { display: inline-block; font-size: 150%; font-weight: bold; @@ -930,31 +936,50 @@ def main(): vertical-align: middle; } -#right_counter, #wrong_counter { +#floating_results .right_counter, +#floating_results .wrong_counter { border-radius: 50%; color: white; padding: 0.3em 0.6em; transition: padding 0.5s; } -#right_counter { +#floating_results .right_counter { background-color: green; } -#results > .delimiter { +#floating_results .delimiter { background-color: transparent; padding: 0px 0px; margin: 0px 0px; } -#wrong_counter { +#floating_results .wrong_counter { background-color: red; } -#right_counter.rise, #wrong_counter.rise { +#floating_results .right_counter.rise, +#floating_results .wrong_counter.rise { padding: 0.5em 0.9em; transition: padding 0.5s; } + +#footer_results { + margin: 1em 1em 3em 1em; + text-align: center; + font-size: 150%; +} + +#footer_results .heading { + font-style: italic; + text-decoration: underline; +} + +#footer_results .right_counter, +#footer_results .wrong_counter { + font-style: italic; + font-weight: bold; +} """ diff --git a/sample.html b/sample.html index 0131dbd..6e9f3af 100644 --- a/sample.html +++ b/sample.html @@ -42,22 +42,22 @@ } }; - $('#right_counter').children('.value').each(function () { + $('.right_counter .value').each(function () { this.innerText = right; }); - $('#wrong_counter').children('.value').each(function () { + $('.wrong_counter .value').each(function () { this.innerText = wrong; }); if (right > wrong) { - $('#wrong_counter').toggleClass('rise', false); - $('#right_counter').toggleClass('rise', true); + $('.wrong_counter').toggleClass('rise', false); + $('.right_counter').toggleClass('rise', true); } else if (wrong > right) { - $('#right_counter').toggleClass('rise', false); - $('#wrong_counter').toggleClass('rise', true); + $('.right_counter').toggleClass('rise', false); + $('.wrong_counter').toggleClass('rise', true); } else { - $('#right_counter').toggleClass('rise', false); - $('#wrong_counter').toggleClass('rise', false); + $('.right_counter').toggleClass('rise', false); + $('.wrong_counter').toggleClass('rise', false); } } @@ -282,13 +282,14 @@

    Sample Quiz Title