diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48458a5..e43028b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -First off, thank you for considering contributing to Exam-Creator. It’s people like you that make Exam-Creator such a great tool. +First off, thank you for considering contributing to Unique-Exams. It’s people like you that make Unique-Exams such a great tool. # How to report a bug diff --git a/README.md b/README.md index abfd6fc..b828be0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Exam-Creator +# Unique-Exams -Exam-Creator is a simple program to create randomized exams so each student receives a unique set of questions. +Unique-Exams is a simple program to create randomized exams so each student receives a unique set of questions. ## Why? @@ -24,9 +24,13 @@ Run `poetry install` ### Usage -Put your template files in the `input` folder, following the "Exam Layout guide" +Have a look at the sample template files provided in the `input` folder to see the expected format. -Run `python exam_creator/main.py` +You can Follow the TEMPLATE_DESIGN.md guide to design your own templates. Remove the sample files and add your own template files to the `input` folder. + +Modify config.py to reflect the text you want to use for placeholders for the Alternate questions. + +Run `python unique_exams/main.py` Generated exams will be placed in the `output` folder diff --git a/pyproject.toml b/pyproject.toml index b467709..108f8e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "exam-creator" +name = "unique-exams" version = "0.1.0" description = "" authors = ["AJ laptop "] diff --git a/exam_creator/__init__.py b/unique_exams/__init__.py similarity index 100% rename from exam_creator/__init__.py rename to unique_exams/__init__.py diff --git a/exam_creator/config.py b/unique_exams/config.py similarity index 54% rename from exam_creator/config.py rename to unique_exams/config.py index 2fdd581..2b39260 100644 --- a/exam_creator/config.py +++ b/unique_exams/config.py @@ -2,7 +2,10 @@ ### Text strings you will use in the template exams to indicate the structure of the exam # To indicate that the following paragraphs decribe one of the alternative questions, the preceeding paragraph should include this text -ALTERNATIVE_TEXT= "Alternatif Soru" +ALTERNATIVE_TEXT= "Question Alternate" # To indicate the block of alternative questions is now over -END_ALTERNATIVES= "Alternatif Sonu" \ No newline at end of file +END_ALTERNATIVES= "END ALTERNATES" + +# Used to obfuscate the tokens used for unique exams. Not that important, but if you want to modify it, each number should be less than 5. +SEED = [0,2,1,3,0,1,2,0,3] diff --git a/unique_exams/input/Sample Examiner Version.docx b/unique_exams/input/Sample Examiner Version.docx new file mode 100644 index 0000000..7565c9e Binary files /dev/null and b/unique_exams/input/Sample Examiner Version.docx differ diff --git a/unique_exams/input/Sample Student Version.docx b/unique_exams/input/Sample Student Version.docx new file mode 100644 index 0000000..89bcc71 Binary files /dev/null and b/unique_exams/input/Sample Student Version.docx differ diff --git a/exam_creator/main.py b/unique_exams/main.py similarity index 86% rename from exam_creator/main.py rename to unique_exams/main.py index 4a5297e..d992a2c 100644 --- a/exam_creator/main.py +++ b/unique_exams/main.py @@ -23,7 +23,6 @@ def load_in_template(filename)->dict: mode = 'common' for i, paragraph in enumerate(template_doc.paragraphs): - # if paragraph text contains `Alternatif Soru`... print(f"{i}:{mode}: {paragraph.text[:45]}") if ALTERNATIVE_TEXT.lower() in paragraph.text.lower(): if mode == 'common': @@ -34,7 +33,7 @@ def load_in_template(filename)->dict: else: # we save the current alternative alternatives.append(alternative_content) - print(f"Detected next Alternatif Soru") + print(f"Detected next alternative choice") mode = 'choice' alternative_content = [] # we are starting a new alternative continue # we don't save the header @@ -190,30 +189,30 @@ def add_paragraph(target_document, source_paragraph): # new_exams = int(input("How many new exams do you want to generate? ")) new_exams = 3 print(os.getcwd()) - if "exam_creator" in os.getcwd(): + if "unique_exams" in os.getcwd(): INPUT_FOLDER = os.path.join(os.getcwd(), "input") OUTPUT_FOLDER = os.path.join(os.getcwd(), "output") else: - # add to path exam_creator - INPUT_FOLDER = os.path.join(os.getcwd(), "exam_creator", "input") - OUTPUT_FOLDER = os.path.join(os.getcwd(), "exam_creator", "output") - examiner_path = os.path.join(INPUT_FOLDER, "JRSPA - Examiner Version - simplified.docx") - student_path = os.path.join(INPUT_FOLDER, "JRSPA - Student Version.docx") - examiner_loaded_content = load_in_template(examiner_path) - student_loaded_content = load_in_template(student_path) - - examiner_structure = [len(choice['alternatives']) for choice in examiner_loaded_content['choices']] - student_structure = [len(choice['alternatives']) for choice in student_loaded_content['choices']] - if examiner_structure != student_structure: - raise Exception("The structure of the examiner and student versions are not the same. Please check the files") + INPUT_FOLDER = os.path.join(os.getcwd(), "unique_exams", "input") + OUTPUT_FOLDER = os.path.join(os.getcwd(), "unique_exams", "output") + exam_names = [f for f in os.listdir(INPUT_FOLDER) if f.endswith(".docx")] + exam_paths = [os.path.join(INPUT_FOLDER, f) for f in exam_names] + # remove file endings + exam_names = [os.path.splitext(f)[0] for f in exam_names] + loaded_contents = [load_in_template(os.path.join(INPUT_FOLDER, f)) for f in exam_paths] + exam_structures = [] + for loaded_content in loaded_contents: + exam_structures.append([len(choice['alternatives']) for choice in loaded_content['choices']]) + for i in range(len(exam_structures)): + if exam_structures[i] != exam_structures[0]: + raise Exception("The structure of the exam versions are not the same. Please check the files") for i in range(new_exams): - choices = make_choices(student_loaded_content) + choices = make_choices(loaded_contents[0]) hash = to_hash(choices) reversed_from_hash = from_hash(hash) print (f"Choices: {choices} -> {hash} -> {reversed_from_hash}") - # choices is an array like [3,0,2,5], we now convert this into a reversable hash - examiner_output_path = os.path.join(OUTPUT_FOLDER, f"Examiner version {hash}.docx") - student_output_path = os.path.join(OUTPUT_FOLDER, f"Student version {hash}.docx") - create_new_document(student_loaded_content, choices, student_output_path, original=student_path) - create_new_document(examiner_loaded_content, choices, examiner_output_path, original=examiner_path) + for j, loaded_content in enumerate(loaded_contents): + exam_name = exam_names[j] + output_path = os.path.join(OUTPUT_FOLDER, f"{exam_name} - version {hash}.docx") + create_new_document(loaded_content, choices, output_path, original=exam_paths[j]) diff --git a/exam_creator/token_hash.py b/unique_exams/token_hash.py similarity index 98% rename from exam_creator/token_hash.py rename to unique_exams/token_hash.py index c1676bd..d47fd5f 100644 --- a/exam_creator/token_hash.py +++ b/unique_exams/token_hash.py @@ -1,10 +1,10 @@ import random +from config import SEED HEX_ENCODING1 = "aeiouAEIOU" HEX_ENCODING2 = "bcdghjklmnprstvwxyz" HEX_ENCODING3 = "BCDGHJKLMNPRSTVWXYZ-#$%&*+./0123456789:<=>?@^_~!'(){}[]|\\öäüÖÄÜßÖÄÜĞğİıŞşÇç" HEX_ENCODINGS = [HEX_ENCODING1, HEX_ENCODING2, HEX_ENCODING3] -SEED = [0,2,1,3,0,1,2,0,3] def value_to_char(value:int)->str: # each HEX_ENCODING substring is unique. We select an encoding string randomly, weighted towards the earlier strings.