Source code for exam_generator.main

# -*- coding: utf-8 -*-

import os
import random
import json
import argparse as ap
import textwrap as tw
from addict import Dict

from .classes import *
from .funcs import *
from .customExceptions import *


[docs]def exam_generator(args): """ Main function which combines all modules of this program. Parameters: args.create_test: Would the user like to create a test args.make_all: Would the user like to create a preview for all problems args.make_pool Pool which the user would like to create a preview for args.make_specific Name of the problem the user would like to create a preview for """ # main directory root_directory = os.getcwd() if check_directory(root_directory) == False: return # ================================== # --- Make-Specific --- # ================================== if args.make_all or args.make_pool or args.make_specific: make_specific(args.make_all, args.make_pool, args.make_specific, root_directory) return # ================================== # --- Create-Test --- # ================================== # ================ Settings ============================= # Settings are adjusted in the settings json files in the settings directory and then loaded into Python # No change of settings in this program! if args.random_seed is not None: seed = args.random_seed initialize_random_number_generator(seed) else: initialize_random_number_generator() settings_path = args.create_test path_settings = os.path.join(root_directory, settings_path) if not os.path.isfile(path_settings): path_settings = settings_path settings_name = os.path.normpath(settings_path).split(os.sep)[-1] if not os.path.isfile(path_settings): raise MissingFileError( f"{errorInfo()} File {settings_name} does not exist. \ Please make sure your directory structure follows the instructions." ) # Loading the json settings into a Python dictionary with open(path_settings, "r", encoding="utf8") as json_datei: settings_dictionary = json.load(json_datei) # converts Python dict into addict Dic settings = Dict(settings_dictionary) check_settings(settings, settings_name) # assigning int values to string decleration of pages_per_sheet for usage in sumo func if settings.page_format_exam == "A4": settings.page_format_exam = 2 elif settings.page_format_exam == "A5": settings.page_format_exam = 4 if settings.page_format_solution == "A4": settings.page_format_solution = 2 elif settings.page_format_solution == "A5": settings.page_format_solution = 4 # ensuring variant name does not create problems while compiling settings.variant_name = settings.variant_name.replace(" ", "") # ================================== # --- Configuration --- # ================================== # Working directory for the LaTeX compiler latex_directory = os.path.join(root_directory, "pool_data") if not os.path.isdir(latex_directory): raise MissingDirectoryError( f"{errorInfo()} {latex_directory} does not exist. \ Please make sure all types match the ones given in the instructions." ) # Template directory template_directory = os.path.join(root_directory, "templates") if not os.path.isdir(template_directory): raise MissingDirectoryError( f"{errorInfo()} {latex_directory} does not exist. \ Please make sure all types match the ones given in the instructions." ) # Directory where the tests will be saved in (for example: Exams-ET1-WS201920) test_directory = os.path.join( root_directory, "Exams-{}-{}".format(settings.variant_name, settings.semester) .replace(" ", "") .replace("/", ""), ) pool_files = pull_pool_data(latex_directory) file_names_tex = combine_file_names(pool_files) # ------------Custom Tests----------------# custom_test_list = create_custom_test_list(settings.exams, pool_files) # for debugging pool_all = Pool(".*", file_names_tex) test_all = TestType("VX", *[pool_all for i in range(len(pool_all.stack_available))]) test_list_all = [test_all] # ================================ # --- Combining problems --- # ================================ tests_per_group = combining_problems(settings.number_of_groups, custom_test_list) # ================================== # --- Generating the TeX-Files --- # ================================== copies_per_group = determine_copies_per_group( settings.number_of_groups, settings.copies ) generate_tex_files( latex_directory, template_directory, settings.number_of_groups, custom_test_list, settings.variant_name, settings.title, settings.semester, tests_per_group, copies_per_group, ) # =================== # --- Compiling --- # =================== compile( test_directory, latex_directory, settings.options.delete_temp_data, ) combine_group_files( test_directory, latex_directory, settings.number_of_groups, custom_test_list, settings.variant_name, settings.page_format_exam, settings.page_format_solution, ) # ================================== # --- Sumo-Files --- # ================================== if settings.options.generate_sumo_pdf: # list of all exams and solution pdf files all_exam_files = os.listdir(test_directory) # only exam pdf files problem_files = [ file for file in all_exam_files if not file.endswith("Solution.pdf") ] problem_files.sort() sumo_name_problems = f"Sumo-{settings.variant_name}-Problems.pdf" build_sumo( test_directory, sumo_name_problems, problem_files, settings.page_format_exam, settings.sumo_options.exam_copies, ) # only solution pdf files solution_files = [ file for file in all_exam_files if file.endswith("Solution.pdf") ] solution_files.sort() sumo_name_solutions = f"Sumo-{settings.variant_name}-Solutions.pdf" build_sumo( test_directory, sumo_name_solutions, solution_files, settings.page_format_solution, settings.sumo_options.solution_copies, ) if not settings.options.generate_single_pdfs: files = os.listdir(test_directory) os.chdir(test_directory) for file in files: if "Sumo" not in file: delete_pdf(file) os.chdir(root_directory)
[docs]def main(): """ Calls parser and delivers arguments to exam_generator. """ Descr = tw.dedent( """ Exam_generator is a script which is designed to create exams/ tests from pools of problems while ensuring that there will be no repetition amongst different groups. The exams/ tests are based off of two major components: the LaTeX basis (problems, solutions, templates) and user defined settings. """ ) parser = ap.ArgumentParser(description=Descr) parser.add_argument( "-ct", "--create_test", metavar="SETTINGSPATH", help="Creates a test based on the provided json settings file. Provide the path to the settings file of your liking.", ) parser.add_argument( "-ma", "--make_all", action="store_true", help="Creates a preview for all problems", ) parser.add_argument( "-mp", "--make_pool", metavar="PATH", help="Creates a preview for all problems of the given pool. Provide the path to the pool.", ) parser.add_argument( "-ms", "--make_specific", metavar="PATH", help="Creates a preview for only the given problem you will need to provide the path to the problem", ) parser.add_argument( "-rs", "--random_seed", metavar="SEED", type=int, help="Set a new random seed, allowing the same exam to be created, yet with different problems pulled. Provide a positive integer of your liking.", ) parser.add_argument( "--bootstrap", action="store_true", help="bootstrap the example content in the current working directory", ) args = parser.parse_args() if args.bootstrap: bootstrap_app() elif ( (args.create_test is None) and (args.make_all == False) and (args.make_pool is None) and (args.make_specific is None) ): parser.error("Please choose at least one of the options. For help type: -h") elif args.random_seed is not None and args.create_test is None: parser.error("You can only select a random seed when creating an exam.") elif args.random_seed is not None: if args.random_seed <= 0: parser.error("Please select a positive integer as your random seed.") else: exam_generator(args)
[docs]def bootstrap_app(): import sys import shutil package_abs_path = os.path.dirname(os.path.abspath(sys.modules.get(__name__).__file__)) copy_dirs = ["examples", "templates"] destination_root = os.path.abspath(os.getcwd()) existing = [] for dirname in copy_dirs: dst = os.path.join(destination_root, dirname) if os.path.exists(dst): existing.append(dst) print(f"The directory ./{dirname} already exists. Please delete/rename it.") if existing: print("Bootstrapping canceled") sys.exit() print("copying ... ") for dirname in copy_dirs: src = os.path.join(package_abs_path, dirname) dst = os.path.join(destination_root, dirname) shutil.copytree(src, dst) print(" ✓", src) print("done")
if __name__ == "__main__": main()