Passed
Branch main (462934)
by Máté
01:15
created

src.moodle_to_vikwikiquiz.quiz_helpers.get_grading_of_question()   A

Complexity

Conditions 2

Size

Total Lines 15
Code Lines 12

Duplication

Lines 15
Ratio 100 %

Importance

Changes 0
Metric Value
eloc 12
dl 15
loc 15
rs 9.8
c 0
b 0
f 0
cc 2
nop 1
1
import os
2
import re
3
4
from bs4 import Tag
5
6
# future: report false positive to JetBrains developers
7
# noinspection PyPackages
8
from .question import Question  # type: ignore
9
10
# noinspection PyPackages
11
from .question_types import QuestionType  # type: ignore
12
13
14
def get_question_type(question: Tag) -> QuestionType:
15
    if question.find("input", type="radio"):
16
        return QuestionType.SingleChoice
17
    elif question.find("input", type="checkbox"):
18
        return QuestionType.MultipleChoice
19
    else:
20
        raise NotImplementedError("Question type not implemented.")
21
22
23
def get_grading_of_question(question: Tag) -> tuple[bool, float | None, float]:
24
    correctly_answered: bool
25
26
    found_tag = question.find("div", class_="grade")
27
    assert isinstance(found_tag, Tag)
28
29
    grading_text = found_tag.text
30
    numbers = re.findall(r"\d+\.\d+", grading_text)
31
    grade: float | None = None
32
    match len(numbers):
33
        case 1:
34
            maximum_points = float(numbers[0])
35
        case 2:
36
            grade = float(numbers[0])
37
            maximum_points = float(numbers[1])
38
        case _:
39
            raise NotImplementedError
40
    if grade == maximum_points:
41
        correctly_answered = True
42
    else:
43
        correctly_answered = False
44
    return correctly_answered, grade, maximum_points
45
46
47
def complete_correct_answers(
48
    answer_texts: list[str],
49
    correct_answers: list[int],
50
    grade: float,
51
    maximum_points: float,
52
    question_text: str,
53
    question_type: QuestionType,
54
    file_name: str,
55
) -> None:
56
    if len(correct_answers) == len(answer_texts) - 1:
57
        correct_answers.append(
58
            get_id_of_only_remaining_answer(answer_texts, correct_answers)
59
        )
60
        return
61
    print(f"File: {file_name}")
62
    print(f"Question: '{question_text}'")
63
    match len(correct_answers):
64
        case 0:
65
            print("\nI couldn't determine any correct answers for sure.", end=" ")
66
        case 1:
67
            print(
68
                f"\nI see that answer #{correct_answers[0]} is correct, "
69
                f"but there might be additional correct answers because you only got {grade:g} points out of {maximum_points:g}.",
70
                end=" ",
71
            )
72
        case _:
73
            print(
74
                f"\nI see that answers {correct_answers} are correct, "
75
                f"but this list may be incomplete because you only got {grade:g} points out of {maximum_points:g}.",
76
                end=" ",
77
            )
78
    print(f"The possible answers are:", end="\n\n")
79
    assert isinstance(answer_texts, list)
80
    # report false positive to mypy developers
81
    for j, answer in enumerate(answer_texts):  # type: ignore
82
        print(f"#{j + 1}\t{answer}")
83
    print()
84
    while True:
85
        get_missing_correct_answers(answer_texts, correct_answers, question_type)
86
        if correct_answers:
87
            break
88
        print("Error: no correct answers were provided!", end="\n\n")
89
90
91
def get_id_of_only_remaining_answer(
92
    answer_texts: list[str], correct_answers: list[int]
93
) -> int:
94
    for i, answer in enumerate(answer_texts, 1):
95
        if i not in correct_answers:
96
            return i
97
    raise NotImplementedError
98
99
100
def get_missing_correct_answers(
101
    answer_texts: list[str], correct_answers: list[int], question_type: QuestionType
102
) -> None:
103
    while len(correct_answers) < len(answer_texts):
104
        additional_correct_answer = input(
105
            f"Please enter a missing correct answer (if there is any remaining) then press Enter: "
106
        )
107
        if additional_correct_answer == "":
108
            break
109
        elif not additional_correct_answer.isdigit():
110
            print("Error: an integer was expected!", end="\n\n")
111
            continue
112
        elif int(additional_correct_answer) - 1 not in range(len(answer_texts)):
113
            print(
114
                "Error: the number is out of the range of possible answers!", end="\n\n"
115
            )
116
            continue
117
        elif int(additional_correct_answer) in correct_answers:
118
            print(
119
                "Error: this answer is already in the list of correct answers!",
120
                end="\n\n",
121
            )
122
            continue
123
        correct_answers.append(int(additional_correct_answer))
124
        if question_type == QuestionType.SingleChoice:
125
            break
126
127
128
def get_answers(
129
    question: Tag, grade: float, maximum_points: float
130
) -> tuple[list[str], list[int]]:
131
    answers = question.find("div", class_="answer")
132
    assert isinstance(answers, Tag)
133
    answer_texts: list[str] = []
134
    correct_answers: list[int] = []
135
    i = 1
136
    for answer in answers:
137
        if not isinstance(answer, Tag):
138
            continue
139
        found_tag = answer.find("div", class_="ml-1")
140
        assert isinstance(found_tag, Tag)
141
        answer_text = found_tag.text.rstrip(".\n")
142
        answer_text = format_latex_as_wikitext(answer_text)
143
        answer_texts.append(answer_text)
144
        if answer_is_correct(answer, grade, maximum_points):
145
            correct_answers.append(i)
146
        i += 1
147
    return answer_texts, correct_answers
148
149
150
def answer_is_correct(answer: Tag, grade: float, maximum_points: float) -> bool:
151
    if "correct" in answer["class"]:
152
        return True
153
    elif grade == maximum_points:
154
        answer_input_element = answer.find("input")
155
        assert isinstance(answer_input_element, Tag)
156
        if answer_input_element.has_attr("checked"):
157
            return True
158
    return False
159
160
161
def get_question_text(question: Tag) -> str:
162
    found_tag = question.find("div", class_="qtext")
163
    assert isinstance(found_tag, Tag)
164
    text = re.sub(r"\n", " ", found_tag.text)
165
    return text.rstrip()
166
167
168
def format_latex_as_wikitext(text: str) -> str:
169
    text = re.sub(r"^(\\)?\\\(( )?(( )?\\(?=\\))?", "<math>", text)
170
    text = re.sub(r"( \\)?\\\)( )?$", "</math>", text)
171
    return text
172
173
174
def question_already_exists(existing_question: Question, question_text: str) -> bool:
175
    return existing_question.text == question_text
176
177
178
def add_answers_to_existing_question(
179
    answer_texts: list[str], correct_answers: list[int], existing_question: Question
180
) -> None:
181
    # report false positive to mypy developers
182
    for k, answer in enumerate(answer_texts):  # type: ignore
183
        if answer not in existing_question.answers:
184
            assert isinstance(answer, str)
185
            existing_question.answers.append(answer)
186
            if k + 1 in correct_answers:
187
                existing_question.correct_answers.add(len(existing_question.answers))
188
189
190
def get_if_has_illustration(question: Tag) -> bool:
191
    if question.find("img", class_="img-responsive"):
192
        return True
193
    elif question.find("img", role="presentation"):
194
        return True
195
    else:
196
        return False
197
198
199
def clear_terminal():
200
    os.system("clear||cls")
201