Skip to content

Commit

Permalink
Implementing new QQuestion: aiken
Browse files Browse the repository at this point in the history
Installed mypy and ended up refactoring random files
  • Loading branch information
LucasWolfgang committed Oct 7, 2023
1 parent 8cb8cf4 commit 39491d2
Show file tree
Hide file tree
Showing 38 changed files with 470 additions and 4,600 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "QAS Editor"
version = "0.0.6"
version = "0.0.7"
description = "Question and Answer Sheet editor"
authors = [
"Lucas Wolfgang <lucawolfcs@hotmail.com>"
Expand Down
2 changes: 1 addition & 1 deletion qas_editor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

__author__ = "Lucas Wolfgang"

__version__ = "0.0.6"
__version__ = "0.0.7"

__doc__= """Question and Answer Sheet Editor is a desktop-focused python API\
and a UI\nutility to automate/help with tasks related to the creation, deletion\
Expand Down
82 changes: 53 additions & 29 deletions qas_editor/answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,59 +21,84 @@
from typing import TYPE_CHECKING, Dict, List, Callable
from .enums import TolFormat, TextFormat, ShapeType, EmbeddedFormat, Direction,\
TolType
from .utils import Serializable, File, FText, TList, attribute_setup
from .parsers.text import FText
from .utils import Serializable, File, TList
_LOG = logging.getLogger(__name__)


class Item:
"""This is an abstract class Question used as a parent for specific
types of Questions.
"""
ANS_TYPE = None

def __init__(self, feedbacks: Dict[str, FText] = None,
hints: Dict[str, FText] = None):
def __init__(self, feedbacks: List[FText] = None,
hints: List[FText] = None):
"""[summary]
Args:
name (str): name of the question
question (FText): text of the question
default_grade (float): the default mark
general_feedback (str, optional): general feedback.
dbid (int, optional): id number.
feedback (str, optional): general feedback.
hints (Dict[str, FText], optional): hints.
"""
self._grading = None
self._proc = None
self._feedbacks = feedbacks
self._hints = hints
self._options = None

grading = attribute_setup(float, "_grading")
feedbacks = attribute_setup(dict, "_feedbacks")
hints = attribute_setup(dict, "_free_hints")
options = attribute_setup(TList, "_options")
@property
def feedbacks(self) -> List[FText]:
return self._feedbacks

@property
def hints(self) -> List[FText]:
return self._hints

@property
def processor(self) -> str:
"""Function that does the processing part (define grades, when hints
will be shown, etc). Stored in text format.
"""
return self._proc

@processor.setter
def processor(self, value):
if isinstance(value, str):
exec(value) # Ignore the result. What we want is no Exception here
self._value = value

def check(self):
return True


class ChoicesItem(Item):
"""This is the basic class used to hold possible answers
"""
This is the basic class used to hold possible answers
"""
MARKER_INT = 9635

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._options = TList(Choice)
self._options = TList[Choice]()

@property
def options(self) -> TList[Choice]:
"""_summary_
Returns:
TList[Choice]: _description_
"""
return self._options

class Choice(Item):
"""
This is the basic class used to hold possible answers
"""
def check(self):
return True

def __init__(self, fraction=0.0, text="", feedback: FText = None,
formatting: TextFormat = None):
self.fraction = fraction

options = attribute_setup(dict, "_options")
class Choice:
"""This is the basic class used to hold possible answers
Attributes:
"""

def __init__(self, text, parser: Callable = None):
self._text = FText()
self._text.parse(text, parser)
self.fixed = False
self.show = False


class Answer(Serializable):
Expand All @@ -87,9 +112,8 @@ def __init__(self, fraction=0.0, text="", feedback: FText = None,
self.formatting = TextFormat.AUTO if formatting is None else formatting
self.text = text
self._feedback = FText()
self.feedback = feedback
self._feedback.parse(feedback)

feedback = FText.prop("_feedback")


class ANumerical(Answer):
Expand Down Expand Up @@ -126,7 +150,7 @@ def __init__(self, grade: int, cformat: EmbeddedFormat,
opts: List[Answer] = None):
self.cformat = cformat
self.grade = grade
self.opts = TList(Answer, opts)
self.opts = TList[Answer](opts)

def to_cloze(self) -> str:
"""A
Expand Down
12 changes: 5 additions & 7 deletions qas_editor/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import csv
from typing import TYPE_CHECKING, Dict, List, Iterator

from .utils import Serializable, File, FText
from .question import _Question
from .utils import Serializable, File
from .question import QQuestion, _Question
from .enums import Status
from .parsers import aiken, csv_card, cloze, gift, json, kahoot, latex, \
from .parsers import aiken, csv_card, cloze, gift, kahoot, latex, \
markdown, moodle, olx, ims
if TYPE_CHECKING:
from .utils import Dataset
Expand Down Expand Up @@ -54,7 +54,6 @@ class Category(Serializable): # pylint: disable=R0904
read_cloze = classmethod(cloze.read_cloze)
read_csvcard = classmethod(csv_card.read_cards)
read_gift = classmethod(gift.read_gift)
read_json = classmethod(json.read_json)
read_kahoot = classmethod(kahoot.read_kahoot)
read_latex = classmethod(latex.read_latex)
read_markdown = classmethod(markdown.read_markdown)
Expand All @@ -67,7 +66,6 @@ class Category(Serializable): # pylint: disable=R0904
write_cloze = cloze.write_cloze
write_csvcard = csv_card.write_cards
write_gift = gift.write_gift
write_json = json.write_json
write_kahoot = kahoot.write_kahoot
write_latex = latex.write_latex
write_markdown = markdown.write_markdown
Expand Down Expand Up @@ -161,7 +159,7 @@ def add_question(self, question) -> bool:
Returns:
bool: _description_
"""
if question in self.__questions or not isinstance(question, _Question):
if question in self.__questions or not isinstance(question, QQuestion):
return False
if question.parent is not None:
question.parent.pop_question(question)
Expand Down Expand Up @@ -268,7 +266,7 @@ def get_tags(self, tags: dict):
for cat in self.__categories.values():
cat.get_tags(tags)

def get_question(self, index: int) -> _Question:
def get_question(self, index: int) -> QQuestion:
"""A helper to get an given index. Using <code>questions</code> would
not work becuase it returns an iterator, which requires to be cast to
list before accessing.
Expand Down
4 changes: 2 additions & 2 deletions qas_editor/gui/popup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Callable
from typing import TYPE_CHECKING
from PyQt5 import QtWidgets, Qt, QtGui, QtCore
from .utils import action_handler, HOTKEYS, key_name
from ..enums import Distribution, Status
Expand Down Expand Up @@ -148,7 +148,7 @@ def __init__(self, parent, top: Category, gtags: dict):
_content.addWidget(self._title, 0, 1)
self._by_tags = QtWidgets.QCheckBox("By tags", self)
_content.addWidget(self._by_tags, 1, 0)
self._tags = TList(str)
self._tags = TList[str]()
_tagbar = QtWidgets.GTagBar(self)
_tagbar.from_list(self._tags)
_tagbar.set_gtags(gtags)
Expand Down
3 changes: 2 additions & 1 deletion qas_editor/gui/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
from ..answer import Answer, ACalculated, DragGroup, EmbeddedItem, DropZone,\
SelectOption
from ..enums import EmbeddedFormat, TextFormat, TolType, TolFormat, EnhancedEnum
from ..utils import FText, Hint
from ..utils import Hint
from ..parsers.text import FText
if TYPE_CHECKING:
from PyQt5.QtGui import QKeyEvent

Expand Down
26 changes: 15 additions & 11 deletions qas_editor/parsers/aiken.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
import logging
import glob
from typing import TYPE_CHECKING, Type
from ..question import QMultichoice
from ..utils import FText
from ..enums import TextFormat
from ..answer import Answer
from qas_editor.question import QMultichoice, QQuestion
from qas_editor.processors import PROCESSORS
from qas_editor.answer import Choice, ChoicesItem
if TYPE_CHECKING:
from ..category import Category

Expand All @@ -32,23 +31,27 @@


def _from_question(buffer, line: str, name: str):
question = QQuestion(name)
simple_choice = ChoicesItem()
header = line
answers = []
match = None
for _line in buffer:
match = _PATTERN.match(_line)
if match:
answers.append(Answer(0.0, match[1], None, TextFormat.PLAIN))
simple_choice.options.append(Choice(match[1]))
break
header += _line
target = 0
for _line in buffer:
match = _PATTERN.match(_line)
if not match:
answers[ord(_line[8].upper())-65].fraction = 100.0
target = ord(_line[8].upper())-65
break
answers.append(Answer(0.0, match[1], None, TextFormat.PLAIN))
question = FText([header.strip()]).from_string
return QMultichoice(name=name, options=answers, question=question)
simple_choice.options.append(Choice(match[1]))
question.body.text.append(header.strip())
question.body.text.append(simple_choice)
simple_choice.processor = PROCESSORS["multichoice"].format(index=target)
return question


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -82,7 +85,7 @@ def write_aiken(category: Type[Category], file_path: str) -> None:
Args:
file_path (str): _description_
"""
def _to_aiken(cat: "Category", writer) -> str:
def _to_aiken(cat: Type[Category], writer) -> str:
for question in cat.questions:
if isinstance(question, QMultichoice):
writer(f"{question.question.get()}\n")
Expand All @@ -94,5 +97,6 @@ def _to_aiken(cat: "Category", writer) -> str:
writer(correct)
for name in cat:
_to_aiken(cat[name], writer)
return None
with open(file_path, "w", encoding="utf-8") as ofile:
_to_aiken(category, ofile.write)
3 changes: 2 additions & 1 deletion qas_editor/parsers/gift.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
QProblem, QShortAnswer, QMultichoice
from ..enums import TextFormat
from ..answer import Answer, ANumerical, Subquestion
from ..utils import FText, gen_hier
from ..utils import gen_hier
from .text import FText
if TYPE_CHECKING:
from ..category import Category
_LOG = logging.getLogger(__name__)
Expand Down
6 changes: 2 additions & 4 deletions qas_editor/parsers/ims/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import re
import logging
import random
import hashlib
from xml.etree import ElementTree as et
from ... import _LOG
from ...question import QQuestion
from ...answer import Item
from ...utils import Var, FText
from ..text import Var, FText
from .imscc import CC
if TYPE_CHECKING:
from ...category import Category
Expand Down Expand Up @@ -90,7 +88,7 @@ def _parse_calculated(self, xml: et.ElementTree):

def _parse_fill_in_multiple_blanks(self, xml: et.ElementTree, qst: QQuestion):
""" Return an array of possible answers """
qst.body.from_string(xml.find("presentation/material/mattext", self._ns).text,
qst.body.parse(xml.find("presentation/material/mattext", self._ns).text,
{"[": get_canvas_vars})
answers = []
correct_answers = []
Expand Down
Loading

0 comments on commit 39491d2

Please sign in to comment.