first MVP to separate transformation from representation
This commit is contained in:
parent
52c3ab5c37
commit
bee767eb98
10 changed files with 146 additions and 46 deletions
|
@ -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
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MarkdownGenerator:
|
class MarkdownGenerator:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -34,7 +32,6 @@ class MarkdownGenerator:
|
||||||
print('\\pagebreak')
|
print('\\pagebreak')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_table_legacy(table_items,add_pagebreak = False,title_template = None,first_colwidth = 28):
|
def generate_table_legacy(table_items,add_pagebreak = False,title_template = None,first_colwidth = 28):
|
||||||
|
|
||||||
|
|
13
coursebuilder/query.py
Normal file
13
coursebuilder/query.py
Normal 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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -6,6 +6,7 @@
|
||||||
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.)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
fields:
|
fields:
|
||||||
- name
|
- name
|
||||||
|
- instructor
|
||||||
- id
|
- id
|
||||||
- goal
|
- goal
|
||||||
- content
|
- content
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue