first MVP to separate transformation from representation

This commit is contained in:
Hartmut Seichter 2024-05-16 23:15:27 +02:00
parent 52c3ab5c37
commit bee767eb98
10 changed files with 146 additions and 46 deletions

View file

@ -18,48 +18,58 @@ import pandas as pd
from tablegenerator import TableGenerator
from markdowngenerator import MarkdownGenerator
from templategenerator import TemplateGenerator
from converter import Converter
from schema import Schema
from query import Query
class CourseBuilder:
@staticmethod
def generate(args):
if args.schema and args.meta and len(args.fields) > 0:
if args.schema and args.meta:
# get actual fields
actual_fields = []
actual_fields = None
# use a file instead of list
if os.path.isfile(args.fields[0]):
if args.fields and os.path.isfile(args.fields[0]):
with open(args.fields[0]) as ff:
actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields']
else:
# seem we have a list or None
actual_fields = args.fields
# get schema
actual_schema = None
schema = None
with open(args.schema) as f:
actual_schema = yaml.load(f,Loader=yaml.Loader)
schema = Schema(yaml.load(f,Loader=yaml.Loader))
# if no fields are given, take all!
if actual_fields == None:
actual_fields = list(schema.keys())
# in case we are running query mode
query = Query(args.query) if args.query else None
result = []
# iterate through meta files
for m in args.meta:
with open(m) as fm:
generator = Converter()
generator.set_schema(actual_schema)
meta = yaml.load(fm,Loader=yaml.Loader)
table_items = generator.process(meta=meta,fields=actual_fields,lang=args.lang)
table_items = schema.process(meta=meta,fields=actual_fields,lang=args.lang) if query == None else schema.process_raw(meta=meta,fields=actual_fields,lang=args.lang)
if args.legacy:
MarkdownGenerator.generate_table_legacy(table_items=table_items,add_pagebreak=args.pagebreak,title_template=args.title,first_colwidth=args.maxcol)
MarkdownGenerator.generate_table_legacy(table_items=table_items,add_pagebreak=args.pagebreak,title_template=args.title,first_colwidth=args.leftcol)
elif query:
query.run(table_items)
else:
MarkdownGenerator.generate_table(table_items=table_items,add_pagebreak=args.pagebreak,title_template=args.title,first_colwidth=args.maxcol)
MarkdownGenerator.generate_table(table_items=table_items,add_pagebreak=args.pagebreak,title_template=args.title,first_colwidth=args.leftcol)
# MarkdownGenerator.generate(table_items,pagebreak=args.pagebreak,title=args.title,header_level=args.level)
@staticmethod
def run():
@ -71,6 +81,8 @@ class CourseBuilder:
parser.add_argument('-l','--lang',help="Language to parse from meta file (use de or en)",default='de')
parser.add_argument('-f','--fields',help="Fields to be used, the table will be build accordingly",action="extend", nargs="+", type=str)
parser.add_argument('-s','--schema',help="using provided schema")
parser.add_argument('-q','--query',help="compound query to select items")
parser.add_argument('-p','--pagebreak',action="store_true",help="add a pagebreak after each module")
parser.add_argument('--title',type=str,default=None,help="template for title - use curly brackets (i.e. {}) to mark where the title string is inserted")
parser.add_argument('-b','--book',type=str,help="process a whole curriculum book with sections")
@ -81,7 +93,7 @@ class CourseBuilder:
parser.add_argument('--legacy',action="store_true",help="use legacy generator mode for compatibility")
parser.add_argument('--maxcol',type=int,default=28,help='maximum size of left column')
parser.add_argument('--leftcol',type=int,default=28,help='maximum size of left column')
# get arguments
args = parser.parse_args()
@ -96,11 +108,6 @@ class CourseBuilder:
# book mode with predefined setting from a book file
if args.book and args.schema:
generator = Converter()
with open(args.schema) as sf:
generator.set_schema(yaml.load(sf,Loader=yaml.Loader))
with open(args.book) as bf:
actual_fields = []
@ -109,14 +116,19 @@ class CourseBuilder:
book_path = os.path.abspath(args.book)
for bi in book['book']:
if 'fields' in bi:
actual_fields = bi['fields']
if 'sections' in bi:
for section in bi['sections']:
if 'text' in section:
print(section['text'][args.lang])
if 'modules' in section:
# override fields
args.fields = actual_fields
# expand filenames to be relative to the book

View file

@ -2,8 +2,6 @@
import itertools
class MarkdownGenerator:
@staticmethod
@ -34,7 +32,6 @@ class MarkdownGenerator:
print('\\pagebreak')
@staticmethod
def generate_table_legacy(table_items,add_pagebreak = False,title_template = None,first_colwidth = 28):

13
coursebuilder/query.py Normal file
View file

@ -0,0 +1,13 @@
class Query:
def __init__(self,query) -> None:
self.__query = query
def run(self,table_items):
# print(table_items)
for row in table_items:
print(row)
# print(eval(self.__query,{row:row}))
pass

View file

@ -1,13 +1,13 @@
import string
class Converter:
class Schema:
def __init__(self) -> None:
self.__schema = None
def set_schema(self,schema = None):
def __init__(self,schema) -> None:
self.__schema = schema
def keys(self):
return self.__schema.keys()
def get_template(self,field,lang='de'):
if 'template' in self.__schema[field]:
return self.__schema[field]['template'][lang]
@ -62,7 +62,9 @@ class Converter:
t = string.Template(self.get_template(field,lang))
return [self.process_label(field,lang),t.substitute({'value' : v})]
def process_multinum(self,meta,field,lang='de'):
"""multinums have various values"""
v = meta[field]['value']
t = string.Template(self.get_template(field,lang))
if hasattr(v, "__len__"):
@ -104,8 +106,53 @@ class Converter:
case 'int' | 'num' : table_items.append(self.process_num(meta,field,lang))
case 'multinum' : table_items.append(self.process_multinum(meta,field,lang))
case 'multikey': table_items.append(self.process_multikey(meta,field,lang))
case _: raise ValueError
except Exception as exp:
print(field,' not resolvable in ',self.__schema,exp)
# maybe return tableitems as np.Dataframe?
return table_items
def get_str(self,meta,field,lang='de'):
if self.is_translatable(field):
return meta[field][lang]
else:
if not 'value' in meta[field]:
raise AssertionError(field,'incomplete')
return meta[field]['value']
def get_enum(self,meta,field,lang):
vv = meta[field]['value']
return self.__schema[field]['values'][vv][lang]
# if self.needs_spec(field):
# t = string.Template(self.get_template(field=field,lang=lang))
# spec = meta[field]['spec'][lang]
# return [self.process_label(field,lang),t.substitute({'value': enum_val,'spec': spec})]
# else:
# return [self.process_label(field,lang),enum_val]
def get_value(self,meta,field,lang):
match self.__schema[field]['type']:
case 'str': return self.get_str(meta,field,lang)
case 'enum': return self.get_enum(meta,field,lang)
def process_raw(self,meta,fields,lang):
items = [{'field' : field,
'lang' : lang,
'type' : self.__schema[field]['type'],
'label' : self.process_label(field,lang),
'value' : self.get_value(meta,field,lang)
}
for field in fields]
# maybe return tableitems as np.Dataframe?
return items

View file

@ -6,7 +6,7 @@ target_de_book := ${build_dir}/curricullum.de.pdf
targets := ${target_de} ${target_en} ${target_de_book}
target_flags := --template pandoc-template/eisvogel.latex
target_flags := --template pandoc-template/eisvogel.latex -V table-use-row-colors:true
coursebuilder := ../coursebuilder
@ -22,6 +22,8 @@ ${target_de}:
mkdir -p ${build_dir}
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml | pandoc ${target_flags} -o ${target_de}
${target_de_book}:
python ${coursebuilder} -s schema.yaml -b book.yaml -p --title "### {}" -l de --leftcol 36 | pandoc ${target_flags} -V lang:de -o ${target_de_book}
clean:
rm -f ${targets}
@ -30,9 +32,7 @@ debug:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml mod.test.yaml -p --title "## {}" -l de -f name credits goal content
# | pandoc ${target_flags} -V lang:de -o ${target_de}
debug-book:
python ${coursebuilder} -s schema.yaml -b book.yaml -p --title "### {}" -l de --legacy | pandoc ${target_flags} -V lang:de -o ${target_de_book}
debug-query:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml -q "kind == compulsory"
.PHONY: clean

View file

@ -6,6 +6,7 @@
book:
- fields:
- name
- instructor
- id
- goal
- content
@ -36,3 +37,18 @@ book:
- mod.test.yaml
#
# tables
#
query:
list-credits-and-workload:
- fields:
- name
- credits
- workload
# just for ideas
regulations:
- globals:
- course_name: Applied Computer Science (M.Sc.)

View file

@ -1,5 +1,6 @@
fields:
- name
- instructor
- id
- goal
- content

View file

@ -2,6 +2,9 @@ name:
de: Computergrafik
en: Computer Graphics
instructor:
de: Prof. Hartmut Seichter, PhD
en: Prof. Hartmut Seichter, PhD
id:
value: CG

View file

@ -2,6 +2,9 @@ name:
de: Test Vorlesung
en: Lecture of Test
instructor:
de: Cicero
en: Cicero
id:
value: Test

View file

@ -15,9 +15,8 @@ name:
#
instructor:
type: str
translatable: false
label:
de: "Modulverantwortlicher/Modulverantwortliche"
de: "Modulverantwortlicher / Modulverantwortliche"
en: "module instructor"
@ -196,11 +195,11 @@ credits:
# Leistungsnachweis
#
form-of-exam:
type: enum
label: {
de: "Leistungsnachweis",
en: "form of examination"
}
type: enum
values: {
'written' : {
de: "Schriftliche Prüfung",
@ -225,28 +224,28 @@ form-of-exam:
# Semester
#
term:
type: multinum
label: {
de: "Semester",
en: "term"
}
type: multinum
template:
de: " ${value}. Semester"
en: " ${value}. semester"
de: " ${value}\\. Semester"
en: " ${value}\\. semester"
#
# Häufigkeit des Angebots
#
frequency:
type: enum
label: {
de: "Häufigkeit des Angebots",
en: "frequency of Offer"
}
type: "enum"
values: {
'once_per_term' : {
de: "jedes Semester",
en: "every term"
en: "every semester"
},
'once_per_year' : {
de: "einmal im Studienjahr",
@ -254,6 +253,9 @@ frequency:
}
}
#
# Dauer des Angebots
#
duration:
type: int
label:
@ -263,6 +265,9 @@ duration:
de: "$value Semester"
en: "$value term(s)"
#
# Art der Veranstaltung
#
kind:
type: enum
label: {
@ -280,6 +285,9 @@ kind:
}
}
#
# Freiform Bemerkungen
#
remarks:
type: str
label: {