This commit is contained in:
Hartmut Seichter 2025-03-18 19:11:56 +01:00
parent 2e1490d98f
commit 1d00963035
4 changed files with 411 additions and 361 deletions

View file

@ -10,10 +10,6 @@ import yaml
from coursebuilder.schema import Schema
class LanguageSelector:
context_lang: str
class Course:
i18n_name: str = "lang.{}.yaml"
mod_name: str = "mod.yaml"
@ -28,19 +24,19 @@ class Course:
for p in path.parent.glob(Course.i18n_name.format("*"))
}
def validate(self, *, schema: Schema, lang: str) -> None:
print(self.__data)
pass
def __getitem__(self, name: str, /) -> Any:
return (
self.__data[name]
if name in self.__data.keys()
else self.__i18n[LanguageSelector.context_lang][name]
)
"""this overload magic function takes a key and a language code like my_key.en or my_key.de"""
s = name.split(".")
def __getattr__(self, name: str, /) -> Any:
return self.__data[name]
if len(s) != 2:
raise ValueError(
"query with item selector requires form ['key.lang'] alternative is ['key.*']"
)
return (
self.__i18n[s[-1]][s[0]]
if s[-1] in self.__i18n.keys() and s[0] in self.__i18n[s[-1]].keys()
else self.__data[s[0]]
)
def __str__(self):
return f"data:{self.__data}\ni18n:{self.__i18n}"
@ -83,22 +79,52 @@ def main():
type=str,
help="schema to validate against",
)
parser.add_argument(
"-f",
"--fields",
help="Fields to be used, the table will be build accordingly",
action="extend",
nargs="+",
type=str,
)
parser.add_argument(
"-c",
"--create",
help="Fields to be used, the table will be build accordingly",
type=str,
)
# get arguments
args = parser.parse_args()
# just input
# with input
if args.input and args.schema:
with open(args.schema) as f_schema:
schema = Schema(schema=yaml.load(f_schema, Loader=yaml.Loader))
sc = StudyCourse(path=(Path(".") / args.input).absolute())
schema = Schema(schema=yaml.load(f_schema, Loader=yaml.Loader)) # Schema
sc = StudyCourse(path=(Path(".") / args.input).absolute()) # Database
LanguageSelector.context_lang = "de"
actual_fields = args.fields if args.fields else schema.keys()
for k in schema.keys():
print(k)
for field in actual_fields:
for lang in sc.languages:
for shortcode, course in sc.courses.items():
print(course[k])
print(
field,
"@",
shortcode,
".",
lang,
"\n",
course[f"{field}.{lang}"],
)
elif args.schema and args.create:
schema = Schema(
schema=yaml.load(open(args.schema), Loader=yaml.Loader)
) # Schema
print(schema.fields())
print(schema.types())
print(schema.facets())
# run as main

View file

@ -1,142 +1,159 @@
class Schema:
__yes_vals: list[str] = ["true", "yes", "on"]
__unique: str = "unique"
__spec: str = "spec"
__facets: str = "facets"
__fields: str = "fields"
__type: str = "type"
__label: str = "label"
def __init__(self, *, schema: dict) -> None:
self.__schema = schema
def __getitem__(self, field):
return self.__schema[field]
def facets(self) -> list[str]:
return list(self.__schema[Schema.__facets])
def keys(self):
return self.__schema.keys()
def fields(self) -> list[str]:
return list(self.__schema[Schema.__fields].keys())
def is_translatable(self, field):
if "translatable" in self.__schema[field]:
return self.__schema[field]["translatable"]
else:
return True
def types(self) -> dict:
return {
field: a[Schema.__type]
for field, a in self.__schema[Schema.__fields].items()
}
def needs_spec(self, field):
if "spec" in self.__schema[field]:
return self.__schema[field]
else:
return False
def get_value(self, meta: dict, field: str, lang: str):
"""
treats receiving the value like a variant,
returns values with their language specific representations
"""
match self.__schema[field]["type"]:
case "str":
def is_unique(self, field: str) -> bool:
return (
meta[field][lang]
if self.is_translatable(field)
else meta[field]["value"]
Schema.__yes_vals in self.__schema[field][Schema.__unique].lower()
if Schema.__unique in self.__schema[field].keys()
else True
)
case "enum" | "int" | "num" | "multikey":
return meta[field]["value"]
case "multinum":
def needs_spec(self, field: str) -> bool:
return (
meta[field]["value"]
if hasattr(meta[field]["value"], "__iter__")
else [
meta[field]["value"],
]
) # force list!
def to_list_of_dict(self, meta, fields, lang):
"""
generates a list of dict which can easily be converted
to a pandas dataframe
"""
# list comprehension for rows
return [
{
"field": field, # field name
"lang": lang, # language shortcode
"type": self.__schema[field]["type"], # datatype
"label": self.__schema[field]["label"][lang], # label
"value": self.get_value(meta, field, lang), # actual value
"template": self.__schema[field]["template"][lang]
if "template" in self.__schema[field]
else None,
# getting crazy with nested dict comprehension
"enum_values": {
k: v[lang] for (k, v) in self.__schema[field]["values"].items()
}
if "enum" in self.__schema[field]["type"]
else None,
"key_values": {
k: v[lang] for (k, v) in self.__schema[field]["keys"].items()
}
if "multikey" in self.__schema[field]["type"]
else None,
"spec": meta[field]["spec"][lang] if "spec" in meta[field] else None,
}
for field in fields
]
def to_short_dict(self, meta, fields, lang):
"""
generates a short version of dict which can easily be converted
to a pandas dataframe
"""
# dict comprehension for whole meta part
return {field: self.get_value(meta, field, lang) for field in fields}
def to_list_of_tuple(self, meta, fields, lang):
"""
generates a list of tuples with a label and value (text)
this is usually consumed by a Markdown generator
todo: needs deuglyfication of free standing loop, templates are possible for all
"""
list = []
for r in self.to_list_of_dict(meta, fields, lang):
match r["type"]:
case "str":
list.append((r["label"], r["value"]))
case "int" | "num":
list.append(
(
r["label"],
r["template"].format(value=r["value"], spec=r["spec"])
if r["template"]
else r["value"],
)
)
case "enum":
list.append(
(
r["label"],
r["template"].format(
value=r["enum_values"][r["value"]], spec=r["spec"]
)
if r["template"]
else r["enum_values"][r["value"]],
)
)
case "multikey":
list.append(
(
r["label"],
", ".join(
[
r["template"].format(
key=r["key_values"][k], value=v
)
for k, v in r["value"].items()
]
),
)
)
case "multinum":
list.append(
(
r["label"],
", ".join(
r["template"].format(value=v) for v in r["value"]
),
)
Schema.__yes_vals in self.__schema[field][Schema.__spec].lower()
if Schema.__spec in self.__schema[field].keys()
else False
)
return list
# def get_value(self, meta: dict, field: str, lang: str):
# """
# treats receiving the value like a variant,
# returns values with their language specific representations
# """
# match self.__schema[field]["type"]:
# case "str":
# return (
# meta[field][lang]
# if self.is_translatable(field)
# else meta[field]["value"]
# )
# case "enum" | "int" | "num" | "multikey":
# return meta[field]["value"]
# case "multinum":
# return (
# meta[field]["value"]
# if hasattr(meta[field]["value"], "__iter__")
# else [
# meta[field]["value"],
# ]
# ) # force list!
# def to_list_of_dict(self, meta, fields, lang):
# """
# generates a list of dict which can easily be converted
# to a pandas dataframe
# """
# # list comprehension for rows
# return [
# {
# "field": field, # field name
# "lang": lang, # language shortcode
# "type": self.__schema[field]["type"], # datatype
# "label": self.__schema[field]["label"][lang], # label
# "value": self.get_value(meta, field, lang), # actual value
# "template": self.__schema[field]["template"][lang]
# if "template" in self.__schema[field]
# else None,
# # getting crazy with nested dict comprehension
# "enum_values": {
# k: v[lang] for (k, v) in self.__schema[field]["values"].items()
# }
# if "enum" in self.__schema[field]["type"]
# else None,
# "key_values": {
# k: v[lang] for (k, v) in self.__schema[field]["keys"].items()
# }
# if "multikey" in self.__schema[field]["type"]
# else None,
# "spec": meta[field]["spec"][lang] if "spec" in meta[field] else None,
# }
# for field in fields
# ]
# def to_short_dict(self, meta, fields, lang):
# """
# generates a short version of dict which can easily be converted
# to a pandas dataframe
# """
# # dict comprehension for whole meta part
# return {field: self.get_value(meta, field, lang) for field in fields}
# def to_list_of_tuple(self, meta, fields, lang):
# """
# generates a list of tuples with a label and value (text)
# this is usually consumed by a Markdown generator
# todo: needs deuglyfication of free standing loop, templates are possible for all
# """
# list = []
# for r in self.to_list_of_dict(meta, fields, lang):
# match r["type"]:
# case "str":
# list.append((r["label"], r["value"]))
# case "int" | "num":
# list.append(
# (
# r["label"],
# r["template"].format(value=r["value"], spec=r["spec"])
# if r["template"]
# else r["value"],
# )
# )
# case "enum":
# list.append(
# (
# r["label"],
# r["template"].format(
# value=r["enum_values"][r["value"]], spec=r["spec"]
# )
# if r["template"]
# else r["enum_values"][r["value"]],
# )
# )
# case "multikey":
# list.append(
# (
# r["label"],
# ", ".join(
# [
# r["template"].format(
# key=r["key_values"][k], value=v
# )
# for k, v in r["value"].items()
# ]
# ),
# )
# )
# case "multinum":
# list.append(
# (
# r["label"],
# ", ".join(
# r["template"].format(value=v) for v in r["value"]
# ),
# )
# )
# return list

View file

@ -13,8 +13,7 @@ credits: 5
form-of-exam:
type: alternative
duration:
value: 1
duration: 1
author-of-indenture:

View file

@ -1,6 +1,18 @@
# fields in curricular description
# leaning on methods in OpenAPI 3.0
facets: ["de", "en"]
templates:
name:
de: Modulname {value}
en: name of course {value}
fields:
#
# Kürzel / ID
#
id:
type: str
translatable: false
label: { de: "Kürzel", en: "code" }
#
# Modulname
#
@ -19,14 +31,6 @@ instructor:
de: "Modulverantwortlicher / Modulverantwortliche"
en: "module instructor"
#
# Kürzel / ID
#
id:
type: str
translatable: false
label: { de: "Kürzel", en: "code" }
#
# Qualifikationsziele
#
@ -77,10 +81,14 @@ form-of-instruction:
{
"lecture": { de: "Vorlesung", en: "lecture" },
"lecture_seminar":
{ de: "Seminaristische Vorlesung", en: "lecture and seminar" },
{
de: "Seminaristische Vorlesung",
en: "lecture and seminar",
},
"seminar": { de: "Seminar", en: "seminar" },
"exersise": { de: "Übung", en: "lab exersise" },
"pc_lab": { de: "Rechnergestütztes Praktikum", en: "PC exersise" },
"pc_lab":
{ de: "Rechnergestütztes Praktikum", en: "PC exersise" },
"project": { de: "Project", en: "project" },
}
template:
@ -210,18 +218,18 @@ form-of-exam:
#
# Art der Veranstaltung
#
kind:
type: enum
label:
{
de: "Art der Veranstaltung (Pflicht, Wahl, etc.)",
en: "kind of module (compulsory, elective)",
}
values:
{
"compulsory": { de: "Pflicht", en: "compulsory" },
"elective": { de: "Wahl/Wahlpflicht", en: "elective" },
}
# kind:
# type: enum
# label:
# {
# de: "Art der Veranstaltung (Pflicht, Wahl, etc.)",
# en: "kind of module (compulsory, elective)",
# }
# values:
# {
# "compulsory": { de: "Pflicht", en: "compulsory" },
# "elective": { de: "Wahl/Wahlpflicht", en: "elective" },
# }
#
# Freiform Bemerkungen