"""
TIM example plugin: a palindrome checker.
"""
import os
import re
from dataclasses import dataclass, asdict
from typing import Union
from flask import render_template_string
from marshmallow import validates, ValidationError, missing
from tim_common.markupmodels import GenericMarkupModel
from tim_common.pluginserver_flask import (
GenericHtmlModel,
GenericAnswerModel,
register_plugin_app,
launch_if_main,
PluginAnswerResp,
PluginAnswerWeb,
PluginReqs,
EditorTab,
)
from tim_common.utils import Missing
[docs]@dataclass
class PaliStateModel:
"""Model for the information that is stored in TIM database for each answer."""
userword: str
[docs]@dataclass
class PaliMarkupModel(GenericMarkupModel):
points_array: list[list[float]] | Missing = missing
inputstem: str | Missing = missing
needed_len: int | Missing = missing
initword: str | Missing = missing
cols: int | Missing = missing
inputplaceholder: str | Missing = missing
[docs] @validates("points_array")
def validate_points_array(self, value: list[list[float]] | Missing) -> None:
if isinstance(value, list) and (
len(value) != 2 or not all(len(v) == 2 for v in value)
):
raise ValidationError("Must be of size 2 x 2.")
[docs]@dataclass
class PaliHtmlModel(GenericHtmlModel[PaliInputModel, PaliMarkupModel, PaliStateModel]):
[docs] def get_component_html_name(self) -> str:
return "pali-runner"
[docs] def get_static_html(self) -> str:
return render_static_pali(self)
[docs]@dataclass
class PaliAnswerModel(
GenericAnswerModel[PaliInputModel, PaliMarkupModel, PaliStateModel]
):
pass
[docs]def render_static_pali(m: PaliHtmlModel) -> str:
return render_template_string(
"""
<div class="csRunDiv no-popup-menu">
{% if header %}<h4>{{ header }}</h4>{% endif %}
{% if stem %}<p class="stem">{{ stem }}</p>{% endif %}
<div><label>{{ inputstem or '' }}
<input type="text" class="form-control" placeholder="{{inputplaceholder or ''}}" value="{{userword or ''}}" size="{{cols or ''}}"></label>
</div>
<button class="timButton">
{{ buttonText or button or "Save" }}
</button>
{% if footer %}<p class="plgfooter">{{ footer }}</p>{% endif %}
</div>
""".strip(),
**asdict(m.markup),
userword=m.state.userword if m.state else "",
)
[docs]def answer(args: PaliAnswerModel) -> PluginAnswerResp:
web: PluginAnswerWeb = {}
result: PluginAnswerResp = {"web": web}
needed_len = args.markup.needed_len
userword = args.input.userword
pali_ok = args.input.paliOK or False
len_ok = True
if not isinstance(needed_len, Missing):
len_ok = check_letters(userword, needed_len)
if not len_ok:
web["error"] = "Wrong length"
if not needed_len and not pali_ok:
len_ok = False
points_array = (
args.markup.points_array
if isinstance(args.markup.points_array, list)
else [[0, 0.25], [0.5, 1]]
)
p_index = 1 if pali_ok else 0
l_index = 1 if len_ok else 0
points = points_array[p_index][l_index]
# plugin can ask not to save the word
nosave = args.input.nosave
if not nosave:
save = {"userword": userword}
result["save"] = save
result["tim_info"] = {"points": points}
web["result"] = "saved"
return result
[docs]def check_letters(word: str, needed_len: int) -> bool:
"""Checks if word has needed amount of chars.
:param word: word to check
:param needed_len: how many letters needed
:return: true if len match
"""
s = word.upper()
return len(re.sub("[^[A-ZÅÄÖ]", "", s)) == needed_len
[docs]def reqs() -> PluginReqs:
templates = [
"""
``` {#ekapali plugin="pali"}
header: Kirjoita palindromi
stem: Kirjoita palindromi, jossa on 5 kirjainta.
-points_array: [[0, 0.1], [0.6, 1]]
inputstem: "Palindromisi 5 kirjainta inputstem:"
needed_len: 5
answerLimit: 3
initword: muikku
cols: 20
```""",
"""
``` {#tokapali plugin="pali"}
header: Kirjoita palindromi
stem: Kirjoita palindromi, jossa on 7 kirjainta.
-points_array: [[0, 0.1], [0.6, 1]]
inputstem: "Palindromisi:"
needed_len: 7
answerLimit: 4
initword: muikku
cols: 20
```""",
]
editor_tabs: list[EditorTab] = [
{
"text": "Plugins",
"items": [
{
"text": "Pali",
"items": [
{
"data": templates[0].strip(),
"text": "5 letters",
"expl": "Add a 5-letter palindrome task",
},
{
"data": templates[1].strip(),
"text": "7 letters",
"expl": "Add a 7-letter palindrome task",
},
],
},
],
},
]
result: PluginReqs = {
"js": ["js/build/pali.js"],
"multihtml": True,
}
# Show pali templates only in development mode.
if os.environ["COMPOSE_PROFILES"] == "dev":
result["editor_tabs"] = editor_tabs
return result
app = register_plugin_app(
__name__,
html_model=PaliHtmlModel,
answer_model=PaliAnswerModel,
answer_handler=answer,
reqs_handler=reqs,
)
launch_if_main(__name__, app)