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 tablegenerator import TableGenerator
from markdowngenerator import MarkdownGenerator from markdowngenerator import MarkdownGenerator
from templategenerator import TemplateGenerator from templategenerator import TemplateGenerator
from converter import Converter from schema import Schema
from query import Query
class CourseBuilder: class CourseBuilder:
@staticmethod @staticmethod
def generate(args): def generate(args):
if args.schema and args.meta and len(args.fields) > 0: if args.schema and args.meta:
# get actual fields # get actual fields
actual_fields = [] actual_fields = None
# use a file instead of list # 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: with open(args.fields[0]) as ff:
actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields'] actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields']
else: else:
# seem we have a list or None
actual_fields = args.fields actual_fields = args.fields
# get schema # get schema
actual_schema = None schema = None
with open(args.schema) as f: 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 # iterate through meta files
for m in args.meta: for m in args.meta:
with open(m) as fm: with open(m) as fm:
generator = Converter()
generator.set_schema(actual_schema)
meta = yaml.load(fm,Loader=yaml.Loader) 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: 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: 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 @staticmethod
def run(): 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('-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('-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('-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('-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('--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") 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('--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 # get arguments
args = parser.parse_args() args = parser.parse_args()
@ -96,11 +108,6 @@ class CourseBuilder:
# book mode with predefined setting from a book file # book mode with predefined setting from a book file
if args.book and args.schema: 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: with open(args.book) as bf:
actual_fields = [] actual_fields = []
@ -109,14 +116,19 @@ class CourseBuilder:
book_path = os.path.abspath(args.book) book_path = os.path.abspath(args.book)
for bi in book['book']: for bi in book['book']:
if 'fields' in bi: if 'fields' in bi:
actual_fields = bi['fields'] actual_fields = bi['fields']
if 'sections' in bi: if 'sections' in bi:
for section in bi['sections']: for section in bi['sections']:
if 'text' in section: if 'text' in section:
print(section['text'][args.lang]) print(section['text'][args.lang])
if 'modules' in section:
if 'modules' in section:
# override fields
args.fields = actual_fields args.fields = actual_fields
# expand filenames to be relative to the book # expand filenames to be relative to the book

View file

@ -2,8 +2,6 @@
import itertools import itertools
class MarkdownGenerator: class MarkdownGenerator:
@staticmethod @staticmethod
@ -32,7 +30,6 @@ class MarkdownGenerator:
if add_pagebreak: if add_pagebreak:
print('\\pagebreak') print('\\pagebreak')
@staticmethod @staticmethod

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 import string
class Converter: class Schema:
def __init__(self) -> None: def __init__(self,schema) -> None:
self.__schema = None
def set_schema(self,schema = None):
self.__schema = schema self.__schema = schema
def keys(self):
return self.__schema.keys()
def get_template(self,field,lang='de'): def get_template(self,field,lang='de'):
if 'template' in self.__schema[field]: if 'template' in self.__schema[field]:
return self.__schema[field]['template'][lang] return self.__schema[field]['template'][lang]
@ -62,7 +62,9 @@ class Converter:
t = string.Template(self.get_template(field,lang)) t = string.Template(self.get_template(field,lang))
return [self.process_label(field,lang),t.substitute({'value' : v})] return [self.process_label(field,lang),t.substitute({'value' : v})]
def process_multinum(self,meta,field,lang='de'): def process_multinum(self,meta,field,lang='de'):
"""multinums have various values"""
v = meta[field]['value'] v = meta[field]['value']
t = string.Template(self.get_template(field,lang)) t = string.Template(self.get_template(field,lang))
if hasattr(v, "__len__"): if hasattr(v, "__len__"):
@ -104,8 +106,53 @@ class Converter:
case 'int' | 'num' : table_items.append(self.process_num(meta,field,lang)) case 'int' | 'num' : table_items.append(self.process_num(meta,field,lang))
case 'multinum' : table_items.append(self.process_multinum(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 'multikey': table_items.append(self.process_multikey(meta,field,lang))
case _: raise ValueError
except Exception as exp: except Exception as exp:
print(field,' not resolvable in ',self.__schema,exp) print(field,' not resolvable in ',self.__schema,exp)
# maybe return tableitems as np.Dataframe? # maybe return tableitems as np.Dataframe?
return table_items 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} 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 coursebuilder := ../coursebuilder
@ -22,6 +22,8 @@ ${target_de}:
mkdir -p ${build_dir} mkdir -p ${build_dir}
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml | pandoc ${target_flags} -o ${target_de} 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: clean:
rm -f ${targets} 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 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} # | pandoc ${target_flags} -V lang:de -o ${target_de}
debug-query:
debug-book: python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml -q "kind == compulsory"
python ${coursebuilder} -s schema.yaml -b book.yaml -p --title "### {}" -l de --legacy | pandoc ${target_flags} -V lang:de -o ${target_de_book}
.PHONY: clean .PHONY: clean

View file

@ -4,8 +4,9 @@
# #
book: book:
- fields: - fields:
- name - name
- instructor
- id - id
- goal - goal
- content - content
@ -36,3 +37,18 @@ book:
- mod.test.yaml - 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: fields:
- name - name
- instructor
- id - id
- goal - goal
- content - content

View file

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

View file

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

View file

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