diff --git a/src/moodle_to_vikwikiquiz/helpers.py b/src/moodle_to_vikwikiquiz/helpers.py new file mode 100644 index 0000000..ebec8db --- /dev/null +++ b/src/moodle_to_vikwikiquiz/helpers.py @@ -0,0 +1,19 @@ +from os import system +from pathlib import Path +from time import sleep + +from send2trash import send2trash # type: ignore + + +def clear_terminal() -> None: + system("clear||cls") + + +def wait_for_pastebot_to_recognize_copy() -> None: + print("Waiting 2 seconds for Pastebot to recognize it...") + sleep(2) + print("...done!") + + +def remove_uploaded_files(folder: Path) -> None: + send2trash(folder) diff --git a/src/moodle_to_vikwikiquiz/main.py b/src/moodle_to_vikwikiquiz/main.py index 5d67ec6..4e64794 100644 --- a/src/moodle_to_vikwikiquiz/main.py +++ b/src/moodle_to_vikwikiquiz/main.py @@ -1,20 +1,13 @@ from argparse import ArgumentParser, Namespace import logging from pathlib import Path -from platform import system from sys import version_info -from time import sleep -from urllib.parse import quote, urlencode -from webbrowser import open_new_tab - -# future: delete the comment below when stubs for the package below are available -from pyperclip import copy # type: ignore -from send2trash import send2trash # type: ignore +from .wiki import create_article, get_article_instructions, log_in_to_wiki # type: ignore from .quiz.illustrations.state_of_illustrations import StateOfIllustrations # type: ignore from .quiz.grading_types import GradingType # type: ignore from .quiz.quiz import Quiz # type: ignore -from .quiz.quiz_helpers import clear_terminal # type: ignore +from .helpers import clear_terminal, wait_for_pastebot_to_recognize_copy # type: ignore def main() -> None: @@ -69,108 +62,6 @@ def main() -> None: logging.getLogger(__name__).debug("Program finished!") -def get_article_instructions( - quiz: Quiz, wiki_domain: str -) -> tuple[str, dict[str, str], dict[str, str], dict[str, str]]: - wikitext_instructions = """ -""" - parameters_for_opening_edit = { - "action": "edit", - "summary": "Kvíz bővítése " - "a https://github.com/gy-mate/moodle-to-vikwikiquiz segítségével importált Moodle-kvíz(ek)ből", - "preload": "Sablon:Előbetöltés", - "preloadparams[]": wikitext_instructions, - } - clear_terminal() - return ( - operating_system, - parameters_for_opening_edit, - wiki_editor_keys, - wiki_modifier_keys, - ) - - -def log_in_to_wiki(wiki_domain: str) -> None: - input( - """Let's log in to the wiki! Please... -• if you see the login page, log in -• when you see the main page of the wiki, return here. - -Please press Enter to open the login page...""" - ) - open_new_tab(f"{wiki_domain}/index.php?title=Speciális:Belépés") - input("Please press Enter if you've logged in...") - clear_terminal() - - -def remove_uploaded_files(folder: Path) -> None: - send2trash(folder) - - def parse_arguments() -> Namespace: parser = ArgumentParser( "Convert graded and downloaded Moodle quizzes to a vik.viki quiz wikitext." @@ -258,102 +149,5 @@ def get_grading() -> GradingType: clear_terminal() -def create_article( - args: Namespace, - parameters_for_opening_edit: dict[str, str], - quiz_title: str, - quiz_wikitext: str, - wiki_domain: str, - wiki_modifier_keys: dict[str, str], - wiki_editor_keys: dict[str, str], - operating_system: str, -) -> None: - if args.new: - parameters_for_opening_edit_with_paste = parameters_for_opening_edit.copy() - parameters_for_opening_edit_with_paste.update( - { - "preload": "Sablon:Előbetöltés", - "preloadparams[]": quiz_wikitext, - } - ) - parameters_for_opening_edit_with_paste["summary"] = ( - parameters_for_opening_edit_with_paste["summary"].replace( - "bővítése", "létrehozása" - ) - ) - url = f"{wiki_domain}/{quiz_title}?{urlencode(parameters_for_opening_edit_with_paste)}" - if len(url) < 2048: - return open_article_paste_text(args, quiz_wikitext, url) - else: - open_article(args, parameters_for_opening_edit, url) - else: - del parameters_for_opening_edit["preload"] - del parameters_for_opening_edit["preloadparams[]"] - copy(quiz_wikitext) - print("\nThe wikitext of the quiz has been copied to the clipboard!") - url = f"{wiki_domain}/{quote(quiz_title)}?{urlencode(parameters_for_opening_edit)}" - if not args.new: - print( - f""" -The existing article will now be opened for editing. After that, please... -• scroll to the bottom of the wikitext in the editor -• add a new line -• paste the content of the clipboard in that line -• click on the 'Előnézet megtekintése' button ({wiki_modifier_keys[operating_system]}-{wiki_editor_keys["Show preview"]}) -• correct the spelling and formatting (if necessary), especially the formulas -• click on the 'Lap mentése' button ({wiki_modifier_keys[operating_system]}-{wiki_editor_keys["Publish page"]})""" - ) - input("\nPlease press Enter then follow these instructions...") - open_new_tab(url) - print( - "\nThe edit page of the quiz article has been opened in your browser!", end=" " - ) - if args.new: - print("Please follow the instructions there.") - - -def open_article_paste_text(args: Namespace, quiz_wikitext: str, url: str) -> None: - copy(quiz_wikitext) - print( - "\nThe wikitext of the quiz has been copied to the clipboard! " - "This will be overwritten but you may recall it later if you use an app like Pastebot." - ) - wait_for_pastebot_to_recognize_copy() - if args.verbose: - copy(url) - print("The URL has been copied to the clipboard!") - open_new_tab(url) - print( - "\nThe edit page of the new quiz article has been opened in your browser with the wikitext pre-filled! " - "Please upload illustrations manually, if there are any." - ) - return - - -def open_article( - args: Namespace, parameters_for_opening_edit: dict[str, str], url: str -) -> None: - logging.getLogger(__name__).warning( - "I can't create the article automatically " - "because the URL would be too long for some browsers (or the server)." - ) - if args.verbose: - copy(url) - print( - "\nThis URL has been copied to the clipboard! " - "It will be overwritten but you may recall it later if you use an app like Pastebot." - ) - wait_for_pastebot_to_recognize_copy() - parameters_for_opening_edit["summary"] = parameters_for_opening_edit[ - "summary" - ].replace("bővítése", "létrehozása") - - -def wait_for_pastebot_to_recognize_copy() -> None: - print("Waiting 2 seconds for Pastebot to recognize it...") - sleep(2) - print("...done!") - - if __name__ == "__main__": main() diff --git a/src/moodle_to_vikwikiquiz/quiz/helpers.py b/src/moodle_to_vikwikiquiz/quiz/helpers.py new file mode 100644 index 0000000..de539ec --- /dev/null +++ b/src/moodle_to_vikwikiquiz/quiz/helpers.py @@ -0,0 +1,20 @@ +from re import sub + +from .illustrations.illustration import Illustration # type: ignore +from .questions.helpers import format_latex_as_wikitext # type: ignore +from .quiz_element import QuizElement # type: ignore +from .illustrations.state_of_illustrations import StateOfIllustrations # type: ignore +from .questions.question import Question # type: ignore +from .questions.question_types import QuestionType # type: ignore + + +def prettify(text: str) -> str: + text = strip_whitespaces(text) + text = format_latex_as_wikitext(text) + return text + + +def strip_whitespaces(text: str) -> str: + text = text.strip("., \n") + text = sub(r" \n|\r\n|\s{2}", " ", text) + return text diff --git a/src/moodle_to_vikwikiquiz/quiz/questions/answer.py b/src/moodle_to_vikwikiquiz/quiz/questions/answers/answer.py similarity index 76% rename from src/moodle_to_vikwikiquiz/quiz/questions/answer.py rename to src/moodle_to_vikwikiquiz/quiz/questions/answers/answer.py index 48b4e27..464c34f 100644 --- a/src/moodle_to_vikwikiquiz/quiz/questions/answer.py +++ b/src/moodle_to_vikwikiquiz/quiz/questions/answers/answer.py @@ -1,6 +1,5 @@ -from ..illustrations.illustration import Illustration # type: ignore -from ..quiz_element import QuizElement # type: ignore -from ..illustrations.state_of_illustrations import StateOfIllustrations # type: ignore +from ...illustrations.illustration import Illustration # type: ignore +from ...quiz_element import QuizElement # type: ignore class Answer(QuizElement): diff --git a/src/moodle_to_vikwikiquiz/quiz/questions/answers/helpers.py b/src/moodle_to_vikwikiquiz/quiz/questions/answers/helpers.py new file mode 100644 index 0000000..9196474 --- /dev/null +++ b/src/moodle_to_vikwikiquiz/quiz/questions/answers/helpers.py @@ -0,0 +1,160 @@ +from bs4 import Tag + +from ...helpers import prettify # type: ignore +from .answer import Answer # type: ignore +from ..question_types import QuestionType # type: ignore + + +def answer_is_correct( + answer: Tag, + answer_text: str, + grade: float, + maximum_points: float, + correct_answers: set[str | None], +) -> bool: + if correct_answers and answer_text in correct_answers: + return True + elif "correct" in answer["class"]: + return True + elif grade == maximum_points: + answer_input_element = answer.find("input") + assert isinstance(answer_input_element, Tag) + if answer_input_element.has_attr("checked"): + return True + return False + + +def get_correct_answers( + answers: set[Answer], + grade: float, + maximum_points: float, + question_text: str, + question_type: QuestionType, + filename: str, +) -> None: + number_of_current_correct_answers = 0 + list_of_answers = list(answers) + correct_answers: list[Answer] = [] + for answer in answers: + if answer.correct: + number_of_current_correct_answers += 1 + correct_answers.append(answer) + + if number_of_current_correct_answers == len(answers) - 1: + for answer in answers: + if not answer.correct: + answer.correct = True + return + print(f"File:\t\t{filename}") + print(f"Question:\t'{question_text}'") + match number_of_current_correct_answers: + case 0: + print("\nI couldn't determine any correct answers for sure.", end=" ") + case 1: + print( + f"\nI see that answer #{list_of_answers.index(correct_answers[0]) + 1} is correct, " + f"but there might be additional correct answers because you only got {grade:g} points out of {maximum_points:g}.", + end=" ", + ) + case _: + correct_answer_indexes: list[int] = [] + for correct_answer in correct_answers: + correct_answer_indexes.append(list_of_answers.index(correct_answer) + 1) + print( + f"\nI see that answers {correct_answer_indexes} are correct, " + f"but this list may be incomplete because you only got {grade:g} points out of {maximum_points:g}.", + end=" ", + ) + print(f"The possible answers are:", end="\n\n") + for j, answer in enumerate(list_of_answers): + print(f"#{j + 1}\t{answer}") + print() + get_missing_correct_answers(correct_answers, list_of_answers, question_type) + + +def get_missing_correct_answers( + correct_answers: list[Answer], + list_of_answers: list[Answer], + question_type: QuestionType, +) -> None: + while True: + get_input_for_missing_correct_answers( + list_of_answers, correct_answers, question_type + ) + for answer in list_of_answers: + if answer.correct: + return + print("Error: no correct answers were provided!", end="\n\n") + + +def get_input_for_missing_correct_answers( + answers: list[Answer], correct_answers: list[Answer], question_type: QuestionType +) -> None: + while len(correct_answers) < len(answers): + additional_correct_answer = input( + f"Please enter a missing correct answer (if there are any remaining) then press Enter: " + ) + if additional_correct_answer == "": + break + elif not additional_correct_answer.isdigit(): + print("Error: an integer was expected!", end="\n\n") + continue + elif int(additional_correct_answer) - 1 not in range(len(answers)): + print( + "Error: the number is out of the range of possible answers!", end="\n\n" + ) + continue + elif int(additional_correct_answer) in correct_answers: + print( + "Error: this answer is already in the list of correct answers!", + end="\n\n", + ) + continue + answers[int(additional_correct_answer) - 1].correct = True + if question_type == QuestionType.SingleChoice: + break + + +def get_correct_answers_if_provided(question: Tag) -> set[str | None]: + tag = question.find("div", class_="rightanswer") + correct_answers: set[str | None] = set() + + if tag: + assert isinstance(tag, Tag) + hint_text = prettify(tag.text) + single_correct_answer_description_translations = [ + "A helyes válasz: ", + "The correct answer is: ", + ] + for ( + correct_answer_description + ) in single_correct_answer_description_translations: + if correct_answer_description in hint_text: + correct_answer = hint_text.removeprefix(correct_answer_description) + correct_answers.add(correct_answer) + return correct_answers + + multiple_correct_answer_description_translations = [ + "A helyes válaszok: ", + "The correct answers are: ", + ] + for ( + correct_answer_description + ) in multiple_correct_answer_description_translations: + if correct_answer_description in hint_text: + correct_answer_tags = tag.find_all("p") + for correct_answer_tag in correct_answer_tags: + correct_answer = correct_answer_tag.text + prettified_answer = prettify(correct_answer) + correct_answers.add(prettified_answer) + return correct_answers + + if tag.find("img"): + pass + return correct_answers + + raise NotImplementedError( + f"Correct answers could not be extracted from '{hint_text}'!" + ) + else: + return correct_answers diff --git a/src/moodle_to_vikwikiquiz/quiz/quiz_helpers.py b/src/moodle_to_vikwikiquiz/quiz/questions/helpers.py similarity index 54% rename from src/moodle_to_vikwikiquiz/quiz/quiz_helpers.py rename to src/moodle_to_vikwikiquiz/quiz/questions/helpers.py index e575287..68a9e87 100644 --- a/src/moodle_to_vikwikiquiz/quiz/quiz_helpers.py +++ b/src/moodle_to_vikwikiquiz/quiz/questions/helpers.py @@ -1,4 +1,4 @@ -from os import path, system +from os import path from pathlib import Path from re import findall, sub from urllib.parse import unquote, urlparse @@ -10,220 +10,68 @@ from plum import dispatch from pylatexenc.latexencode import unicode_to_latex # type: ignore -from .illustrations.illustration import Illustration # type: ignore -from .questions.answer import Answer # type: ignore -from .quiz_element import QuizElement # type: ignore -from .illustrations.state_of_illustrations import StateOfIllustrations # type: ignore -from .questions.question import Question # type: ignore -from .questions.question_types import QuestionType # type: ignore +from .question_types import QuestionType # type: ignore +from ..illustrations.illustration import Illustration # type: ignore +from ..illustrations.state_of_illustrations import StateOfIllustrations # type: ignore +from .answers.answer import Answer # type: ignore +from .question import Question # type: ignore +from ..quiz_element import QuizElement # type: ignore -def get_question_type(question: Tag) -> QuestionType: - if question.find("input", type="radio"): - return QuestionType.SingleChoice - elif question.find("input", type="checkbox"): - return QuestionType.MultipleChoice - else: - raise NotImplementedError("Question type not implemented.") - - -def get_grading_of_question(question: Tag) -> tuple[bool, float | None, float]: - correctly_answered: bool - - found_tag = question.find("div", class_="grade") - assert isinstance(found_tag, Tag) - - grading_text = found_tag.text - numbers_in_capture_groups: list[tuple[str, str]] = findall( - r"(\d+)([.,]\d+)?", grading_text - ) - numbers = [ - whole + fraction.replace(",", ".") - for whole, fraction in numbers_in_capture_groups - ] - grade: float | None = None - match len(numbers): - case 1: - maximum_points = float(numbers[0]) - case 2: - grade = float(numbers[0]) - maximum_points = float(numbers[1]) - case _: - raise NotImplementedError( - f"{len(numbers)} grade numbers found in '{grading_text}'!" - ) - if grade == maximum_points: - correctly_answered = True - else: - correctly_answered = False - return correctly_answered, grade, maximum_points - - -def get_correct_answers( - answers: set[Answer], - grade: float, - maximum_points: float, - question_text: str, - question_type: QuestionType, - filename: str, -) -> None: - number_of_current_correct_answers = 0 - list_of_answers = list(answers) - correct_answers: list[Answer] = [] - for answer in answers: - if answer.correct: - number_of_current_correct_answers += 1 - correct_answers.append(answer) - - if number_of_current_correct_answers == len(answers) - 1: - for answer in answers: - if not answer.correct: - answer.correct = True - return - print(f"File:\t\t{filename}") - print(f"Question:\t'{question_text}'") - match number_of_current_correct_answers: - case 0: - print("\nI couldn't determine any correct answers for sure.", end=" ") - case 1: - print( - f"\nI see that answer #{list_of_answers.index(correct_answers[0]) + 1} is correct, " - f"but there might be additional correct answers because you only got {grade:g} points out of {maximum_points:g}.", - end=" ", - ) - case _: - correct_answer_indexes: list[int] = [] - for correct_answer in correct_answers: - correct_answer_indexes.append(list_of_answers.index(correct_answer) + 1) - print( - f"\nI see that answers {correct_answer_indexes} are correct, " - f"but this list may be incomplete because you only got {grade:g} points out of {maximum_points:g}.", - end=" ", - ) - print(f"The possible answers are:", end="\n\n") - for j, answer in enumerate(list_of_answers): - print(f"#{j + 1}\t{answer}") - print() - get_missing_correct_answers(correct_answers, list_of_answers, question_type) +def question_already_exists( + existing_question: Question, new_question: Question +) -> bool: + return existing_question == new_question -def get_missing_correct_answers( - correct_answers: list[Answer], - list_of_answers: list[Answer], - question_type: QuestionType, +def add_answers_to_existing_question( + answers: set[Answer], existing_question: Question ) -> None: - while True: - get_input_for_missing_correct_answers( - list_of_answers, correct_answers, question_type - ) - for answer in list_of_answers: - if answer.correct: - return - print("Error: no correct answers were provided!", end="\n\n") + existing_question.answers.update(answers) -def get_input_for_missing_correct_answers( - answers: list[Answer], correct_answers: list[Answer], question_type: QuestionType -) -> None: - while len(correct_answers) < len(answers): - additional_correct_answer = input( - f"Please enter a missing correct answer (if there are any remaining) then press Enter: " - ) - if additional_correct_answer == "": - break - elif not additional_correct_answer.isdigit(): - print("Error: an integer was expected!", end="\n\n") - continue - elif int(additional_correct_answer) - 1 not in range(len(answers)): - print( - "Error: the number is out of the range of possible answers!", end="\n\n" - ) - continue - elif int(additional_correct_answer) in correct_answers: - print( - "Error: this answer is already in the list of correct answers!", - end="\n\n", - ) - continue - answers[int(additional_correct_answer) - 1].correct = True - if question_type == QuestionType.SingleChoice: - break +def get_if_has_illustration( + question: Tag, directory: Path, file: Path +) -> StateOfIllustrations: + if question.find("img", class_="img-responsive") or question.find( + "img", role="presentation" + ): + return get_if_illustrations_available(directory, file) + else: + return StateOfIllustrations.Nil -def prettify(text: str) -> str: - text = strip_whitespaces(text) - text = format_latex_as_wikitext(text) - return text +def get_if_illustrations_available(directory: Path, file: Path) -> StateOfIllustrations: + asset_folder = directory / f"{file.stem}_files" + if path.exists(asset_folder): + return StateOfIllustrations.YesAndAvailable + else: + return StateOfIllustrations.YesButUnavailable -def strip_whitespaces(text: str) -> str: - text = text.strip("., \n") - text = sub(r" \n|\r\n|\s{2}", " ", text) - return text +@dispatch +def format_latex_as_wikitext(latex: Tag) -> str: + wikitext = latex.text + mathjax = latex.find(class_="MathJax").find("span").text + wikitext = wikitext.replace(mathjax, "", 1) + wikitext = wikitext.replace(mathjax, f"{unicode_to_latex(mathjax)}") + return wikitext -def get_correct_answers_if_provided(question: Tag) -> set[str | None]: - tag = question.find("div", class_="rightanswer") - correct_answers: set[str | None] = set() - - if tag: - assert isinstance(tag, Tag) - hint_text = prettify(tag.text) - single_correct_answer_description_translations = [ - "A helyes válasz: ", - "The correct answer is: ", - ] - for ( - correct_answer_description - ) in single_correct_answer_description_translations: - if correct_answer_description in hint_text: - correct_answer = hint_text.removeprefix(correct_answer_description) - correct_answers.add(correct_answer) - return correct_answers - - multiple_correct_answer_description_translations = [ - "A helyes válaszok: ", - "The correct answers are: ", - ] - for ( - correct_answer_description - ) in multiple_correct_answer_description_translations: - if correct_answer_description in hint_text: - correct_answer_tags = tag.find_all("p") - for correct_answer_tag in correct_answer_tags: - correct_answer = correct_answer_tag.text - prettified_answer = prettify(correct_answer) - correct_answers.add(prettified_answer) - return correct_answers - - if tag.find("img"): - pass - return correct_answers - - raise NotImplementedError( - f"Correct answers could not be extracted from '{hint_text}'!" - ) +@dispatch # type: ignore +def format_latex_as_wikitext(latex: str) -> str: + if findall(latex_start_anchored := r"^\s?\\?\\\(\s?(\s?\\(?=\\))?", latex): + wikitext = sub(latex_start_anchored, "", latex) else: - return correct_answers - + latex_start = latex_start_anchored.replace(r"^\s?", "") + wikitext = sub(latex_start, "", latex) -def answer_is_correct( - answer: Tag, - answer_text: str, - grade: float, - maximum_points: float, - correct_answers: set[str | None], -) -> bool: - if correct_answers and answer_text in correct_answers: - return True - elif "correct" in answer["class"]: - return True - elif grade == maximum_points: - answer_input_element = answer.find("input") - assert isinstance(answer_input_element, Tag) - if answer_input_element.has_attr("checked"): - return True - return False + if findall(latex_end_anchored := r"\s*\\?\\\)\s?$", wikitext): + wikitext = sub(latex_end_anchored, "", wikitext) + else: + latex_end = latex_end_anchored.replace(r"\s?$", "") + wikitext = sub(latex_end, "", wikitext) + return wikitext def get_question_data( @@ -255,6 +103,47 @@ def get_question_text(found_tag: Tag) -> str: return text +def get_question_type(question: Tag) -> QuestionType: + if question.find("input", type="radio"): + return QuestionType.SingleChoice + elif question.find("input", type="checkbox"): + return QuestionType.MultipleChoice + else: + raise NotImplementedError("Question type not implemented.") + + +def get_grading_of_question(question: Tag) -> tuple[bool, float | None, float]: + correctly_answered: bool + + found_tag = question.find("div", class_="grade") + assert isinstance(found_tag, Tag) + + grading_text = found_tag.text + numbers_in_capture_groups: list[tuple[str, str]] = findall( + r"(\d+)([.,]\d+)?", grading_text + ) + numbers = [ + whole + fraction.replace(",", ".") + for whole, fraction in numbers_in_capture_groups + ] + grade: float | None = None + match len(numbers): + case 1: + maximum_points = float(numbers[0]) + case 2: + grade = float(numbers[0]) + maximum_points = float(numbers[1]) + case _: + raise NotImplementedError( + f"{len(numbers)} grade numbers found in '{grading_text}'!" + ) + if grade == maximum_points: + correctly_answered = True + else: + correctly_answered = False + return correctly_answered, grade, maximum_points + + def get_element_illustration( tag: Tag, element_text: str, @@ -345,63 +234,3 @@ def create_upload_filename( upload_filename += f" – válaszlehetőség {random_hash}" upload_filename += f" ({quiz_name}){extension}" return upload_filename - - -@dispatch -def format_latex_as_wikitext(latex: Tag) -> str: - wikitext = latex.text - mathjax = latex.find(class_="MathJax").find("span").text - wikitext = wikitext.replace(mathjax, "", 1) - wikitext = wikitext.replace(mathjax, f"{unicode_to_latex(mathjax)}") - return wikitext - - -@dispatch # type: ignore -def format_latex_as_wikitext(latex: str) -> str: - if findall(latex_start_anchored := r"^\s?\\?\\\(\s?(\s?\\(?=\\))?", latex): - wikitext = sub(latex_start_anchored, "", latex) - else: - latex_start = latex_start_anchored.replace(r"^\s?", "") - wikitext = sub(latex_start, "", latex) - - if findall(latex_end_anchored := r"\s*\\?\\\)\s?$", wikitext): - wikitext = sub(latex_end_anchored, "", wikitext) - else: - latex_end = latex_end_anchored.replace(r"\s?$", "") - wikitext = sub(latex_end, "", wikitext) - return wikitext - - -def question_already_exists( - existing_question: Question, new_question: Question -) -> bool: - return existing_question == new_question - - -def add_answers_to_existing_question( - answers: set[Answer], existing_question: Question -) -> None: - existing_question.answers.update(answers) - - -def get_if_has_illustration( - question: Tag, directory: Path, file: Path -) -> StateOfIllustrations: - if question.find("img", class_="img-responsive") or question.find( - "img", role="presentation" - ): - return get_if_illustrations_available(directory, file) - else: - return StateOfIllustrations.Nil - - -def get_if_illustrations_available(directory: Path, file: Path) -> StateOfIllustrations: - asset_folder = directory / f"{file.stem}_files" - if path.exists(asset_folder): - return StateOfIllustrations.YesAndAvailable - else: - return StateOfIllustrations.YesButUnavailable - - -def clear_terminal() -> None: - system("clear||cls") diff --git a/src/moodle_to_vikwikiquiz/quiz/questions/question.py b/src/moodle_to_vikwikiquiz/quiz/questions/question.py index 800d99b..7d2f9b9 100644 --- a/src/moodle_to_vikwikiquiz/quiz/questions/question.py +++ b/src/moodle_to_vikwikiquiz/quiz/questions/question.py @@ -2,7 +2,7 @@ from typing_extensions import override -from .answer import Answer # type: ignore +from .answers.answer import Answer # type: ignore from ..grading_types import GradingType # type: ignore from ..illustrations.illustration import Illustration # type: ignore from ..quiz_element import QuizElement # type: ignore diff --git a/src/moodle_to_vikwikiquiz/quiz/quiz.py b/src/moodle_to_vikwikiquiz/quiz/quiz.py index 69f75de..e08c0ce 100644 --- a/src/moodle_to_vikwikiquiz/quiz/quiz.py +++ b/src/moodle_to_vikwikiquiz/quiz/quiz.py @@ -13,14 +13,26 @@ # noinspection PyUnresolvedReferences from bs4 import BeautifulSoup, Tag -from .questions.answer import Answer # type: ignore from .grading_types import GradingType # type: ignore from .illustrations.illustration import Illustration # type: ignore from .illustrations.state_of_illustrations import StateOfIllustrations # type: ignore +from .questions.answers.answer import Answer # type: ignore +from .questions.answers.helpers import answer_is_correct, get_correct_answers, get_correct_answers_if_provided # type: ignore +from .questions.helpers import ( # type: ignore + add_answers_to_existing_question, + get_element_illustration, get_grading_of_question, + get_if_has_illustration, + get_if_illustrations_available, + get_question_data, + get_question_text, + get_question_type, + question_already_exists, +) from .questions.question_types import QuestionType # type: ignore -from .quiz_helpers import * # type: ignore +from .helpers import * # type: ignore from .questions.question import Question # type: ignore from .quiz_element import QuizElement # type: ignore +from ..helpers import clear_terminal # type: ignore def move_illustration_to_upload_folder( diff --git a/src/moodle_to_vikwikiquiz/wiki.py b/src/moodle_to_vikwikiquiz/wiki.py new file mode 100644 index 0000000..f524c11 --- /dev/null +++ b/src/moodle_to_vikwikiquiz/wiki.py @@ -0,0 +1,200 @@ +from argparse import Namespace +import logging +from platform import system +from urllib.parse import quote, urlencode +from webbrowser import open_new_tab + +from pyperclip import copy # type: ignore + +from .helpers import clear_terminal, remove_uploaded_files, wait_for_pastebot_to_recognize_copy # type: ignore +from .quiz.illustrations.state_of_illustrations import StateOfIllustrations # type: ignore +from .quiz.quiz import Quiz # type: ignore + + +def create_article( + args: Namespace, + parameters_for_opening_edit: dict[str, str], + quiz_title: str, + quiz_wikitext: str, + wiki_domain: str, + wiki_modifier_keys: dict[str, str], + wiki_editor_keys: dict[str, str], + operating_system: str, +) -> None: + if args.new: + parameters_for_opening_edit_with_paste = parameters_for_opening_edit.copy() + parameters_for_opening_edit_with_paste.update( + { + "preload": "Sablon:Előbetöltés", + "preloadparams[]": quiz_wikitext, + } + ) + parameters_for_opening_edit_with_paste["summary"] = ( + parameters_for_opening_edit_with_paste["summary"].replace( + "bővítése", "létrehozása" + ) + ) + url = f"{wiki_domain}/{quiz_title}?{urlencode(parameters_for_opening_edit_with_paste)}" + if len(url) < 2048: + return open_article_paste_text(args, quiz_wikitext, url) + else: + open_article(args, parameters_for_opening_edit, url) + else: + del parameters_for_opening_edit["preload"] + del parameters_for_opening_edit["preloadparams[]"] + copy(quiz_wikitext) + print("\nThe wikitext of the quiz has been copied to the clipboard!") + url = f"{wiki_domain}/{quote(quiz_title)}?{urlencode(parameters_for_opening_edit)}" + if not args.new: + print( + f""" +The existing article will now be opened for editing. After that, please... +• scroll to the bottom of the wikitext in the editor +• add a new line +• paste the content of the clipboard in that line +• click on the 'Előnézet megtekintése' button ({wiki_modifier_keys[operating_system]}-{wiki_editor_keys["Show preview"]}) +• correct the spelling and formatting (if necessary), especially the formulas +• click on the 'Lap mentése' button ({wiki_modifier_keys[operating_system]}-{wiki_editor_keys["Publish page"]})""" + ) + input("\nPlease press Enter then follow these instructions...") + open_new_tab(url) + print( + "\nThe edit page of the quiz article has been opened in your browser!", end=" " + ) + if args.new: + print("Please follow the instructions there.") + + +def open_article_paste_text(args: Namespace, quiz_wikitext: str, url: str) -> None: + copy(quiz_wikitext) + print( + "\nThe wikitext of the quiz has been copied to the clipboard! " + "This will be overwritten but you may recall it later if you use an app like Pastebot." + ) + wait_for_pastebot_to_recognize_copy() + if args.verbose: + copy(url) + print("The URL has been copied to the clipboard!") + open_new_tab(url) + print( + "\nThe edit page of the new quiz article has been opened in your browser with the wikitext pre-filled! " + "Please upload illustrations manually, if there are any." + ) + return + + +def open_article( + args: Namespace, parameters_for_opening_edit: dict[str, str], url: str +) -> None: + logging.getLogger(__name__).warning( + "I can't create the article automatically " + "because the URL would be too long for some browsers (or the server)." + ) + if args.verbose: + copy(url) + print( + "\nThis URL has been copied to the clipboard! " + "It will be overwritten but you may recall it later if you use an app like Pastebot." + ) + wait_for_pastebot_to_recognize_copy() + parameters_for_opening_edit["summary"] = parameters_for_opening_edit[ + "summary" + ].replace("bővítése", "létrehozása") + + +def get_article_instructions( + quiz: Quiz, wiki_domain: str +) -> tuple[str, dict[str, str], dict[str, str], dict[str, str]]: + wikitext_instructions = """ +""" + parameters_for_opening_edit = { + "action": "edit", + "summary": "Kvíz bővítése " + "a https://github.com/gy-mate/moodle-to-vikwikiquiz segítségével importált Moodle-kvíz(ek)ből", + "preload": "Sablon:Előbetöltés", + "preloadparams[]": wikitext_instructions, + } + clear_terminal() + return ( + operating_system, + parameters_for_opening_edit, + wiki_editor_keys, + wiki_modifier_keys, + ) + + +def log_in_to_wiki(wiki_domain: str) -> None: + input( + """Let's log in to the wiki! Please... +• if you see the login page, log in +• when you see the main page of the wiki, return here. + +Please press Enter to open the login page...""" + ) + open_new_tab(f"{wiki_domain}/index.php?title=Speciális:Belépés") + input("Please press Enter if you've logged in...") + clear_terminal()