import { LocalQuiz } from "../localQuiz"; import { quizQuestionMarkdownUtils } from "./quizQuestionMarkdownUtils"; const extractLabelValue = (input: string, label: string): string => { const pattern = new RegExp(`${label}: (.*?)\n`); const match = pattern.exec(input); return match ? match[1].trim() : ""; }; const extractDescription = (input: string): string => { const pattern = new RegExp("Description: (.*?)$", "s"); const match = pattern.exec(input); return match ? match[1].trim() : ""; }; const parseBooleanOrThrow = (value: string, label: string): boolean => { if (value.toLowerCase() === "true") return true; if (value.toLowerCase() === "false") return false; throw new Error(`Error with ${label}: ${value}`); }; const parseBooleanOrDefault = ( value: string, label: string, defaultValue: boolean ): boolean => { if (value.toLowerCase() === "true") return true; if (value.toLowerCase() === "false") return false; return defaultValue; }; const parseNumberOrThrow = (value: string, label: string): number => { const parsed = parseInt(value, 10); if (isNaN(parsed)) { throw new Error(`Error with ${label}: ${value}`); } return parsed; }; const parseDateOrThrow = (value: string, label: string): string => { const date = new Date(value); if (isNaN(date.getTime())) { throw new Error(`Error with ${label}: ${value}`); } const day = String(date.getDate()).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-based const year = date.getFullYear(); const hours = String(date.getHours()).padStart(2, "0"); const minutes = String(date.getMinutes()).padStart(2, "0"); const seconds = String(date.getSeconds()).padStart(2, "0"); return `${day}/${month}/${year} ${hours}:${minutes}:${seconds}`; }; const parseDateOrNull = (value: string): string | undefined => { const date = new Date(value); return isNaN(date.getTime()) ? undefined : date.toISOString(); }; const getQuizWithOnlySettings = (settings: string): LocalQuiz => { const name = extractLabelValue(settings, "Name"); const rawShuffleAnswers = extractLabelValue(settings, "ShuffleAnswers"); const shuffleAnswers = parseBooleanOrThrow( rawShuffleAnswers, "ShuffleAnswers" ); const password = extractLabelValue(settings, "Password") || undefined; const rawShowCorrectAnswers = extractLabelValue( settings, "ShowCorrectAnswers" ); const showCorrectAnswers = parseBooleanOrDefault( rawShowCorrectAnswers, "ShowCorrectAnswers", true ); const rawOneQuestionAtATime = extractLabelValue( settings, "OneQuestionAtATime" ); const oneQuestionAtATime = parseBooleanOrThrow( rawOneQuestionAtATime, "OneQuestionAtATime" ); const rawAllowedAttempts = extractLabelValue(settings, "AllowedAttempts"); const allowedAttempts = parseNumberOrThrow( rawAllowedAttempts, "AllowedAttempts" ); const rawDueAt = extractLabelValue(settings, "DueAt"); const dueAt = parseDateOrThrow(rawDueAt, "DueAt"); const rawLockAt = extractLabelValue(settings, "LockAt"); const lockAt = parseDateOrNull(rawLockAt); const description = extractDescription(settings); const localAssignmentGroupName = extractLabelValue( settings, "AssignmentGroup" ); const quiz: LocalQuiz = { name, description, password, lockAt, dueAt, shuffleAnswers, showCorrectAnswers, oneQuestionAtATime, localAssignmentGroupName, allowedAttempts, questions: [], }; return quiz; }; export const quizMarkdownUtils = { toMarkdown(quiz: LocalQuiz): string { const questionMarkdownArray = quiz.questions.map((q) => quizQuestionMarkdownUtils.toMarkdown(q) ); const questionDelimiter = "\n\n---\n\n"; const questionMarkdown = questionMarkdownArray.join(questionDelimiter); return `Name: ${quiz.name} LockAt: ${quiz.lockAt ?? ""} DueAt: ${quiz.dueAt} Password: ${quiz.password ?? ""} ShuffleAnswers: ${quiz.shuffleAnswers.toString().toLowerCase()} ShowCorrectAnswers: ${quiz.showCorrectAnswers.toString().toLowerCase()} OneQuestionAtATime: ${quiz.oneQuestionAtATime.toString().toLowerCase()} AssignmentGroup: ${quiz.localAssignmentGroupName} AllowedAttempts: ${quiz.allowedAttempts} Description: ${quiz.description} --- ${questionMarkdown}`; }, parseMarkdown(input: string): LocalQuiz { const splitInput = input.split("---\n"); const settings = splitInput[0]; const quizWithoutQuestions = getQuizWithOnlySettings(settings); const rawQuestions = splitInput.slice(1); const questions = rawQuestions .filter((str) => str.trim().length > 0) .map((q, i) => quizQuestionMarkdownUtils.parseMarkdown(q, i)); return { ...quizWithoutQuestions, questions, }; }, };