Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
Hartmut Seichter
0a5db9f8ad just cleanup 2024-10-29 17:46:46 +01:00
Hartmut Seichter
010aa5e72f better error handling 2024-05-06 16:37:34 +02:00
Hartmut Seichter
445a976354 use generator properly in book mode 2024-05-06 16:37:21 +02:00
Hartmut Seichter
5a4b2c8f3a repair book mode with refactored rendering 2024-05-03 07:42:51 +02:00
35df5e2aa1 command line mode 2024-05-01 17:14:23 +02:00
595a0352c5 refactoring for division of concerns 2024-05-01 14:58:41 +02:00
Hartmut Seichter
28378e3819 intermediate version 2024-05-01 08:10:27 +02:00
Hartmut Seichter
6f51fbf76c adding a proper exception handler to find actual problems in external files 2024-04-25 19:35:13 +02:00
14 changed files with 323 additions and 209 deletions

View file

@ -1,4 +1,4 @@
Copyright 2020-2023 Hartmut Seichter
Copyright 2020-2024 Hartmut Seichter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -35,7 +35,6 @@ options:
© Copyright 2020-2024 Hartmut Seichter
# Licence
Coursebuilder is licensed under the terms of the MIT License. For details consult https://opensource.org/license/mit/ or the attached license file

View file

@ -3,5 +3,7 @@
* [ ] proper referencing of tables
* [ ] custom python code in tables
* [x] fix overlong table cells (pandoc longtable only deals with overlong tables but not cells)
* [ ] add a book mode for mixing input and headers (# Blah -m mod.cg.yaml)
* [ ] table generator
* [x] add a book mode for mixing input and headers (# Blah -m mod.cg.yaml)
* [~] table generator
* [ ] overlay of compulsory with other modes ...
* [ ] add template based generator

View file

@ -13,145 +13,18 @@ actual values are kept in YAML files in order to version them with git.
from argparse import ArgumentParser
import yaml
import string
import os
import os,sys
from tablegenerator import TableGenerator
from markdowngenerator import MarkdownGenerator
from templategenerator import TemplateGenerator
from metagenerator import MetaGenerator
class CourseBuilder:
def __init__(self) -> None:
self.__schema = None
def set_schema(self,schema = None):
self.__schema = schema
def get_template(self,field,lang='de'):
if 'template' in self.__schema[field]:
return self.__schema[field]['template'][lang]
else:
return "$value"
def is_translatable(self,field):
if 'translatable' in self.__schema[field]:
return self.__schema[field]['translatable']
else:
return True
def needs_spec(self,field):
if 'spec' in self.__schema[field]:
return self.__schema[field]
else:
return False
def process_label(self,field,lang='de'):
# processes the label of a field item
return self.__schema[field]['label'][lang]
def process_str(self,meta,field,lang='de'):
if self.is_translatable(field):
return [self.process_label(field,lang),meta[field][lang]]
else:
if not 'value' in meta[field]:
raise AssertionError(field,'incomplete')
return [self.process_label(field,lang),meta[field]['value']]
def process_enum(self,meta,field,lang='de'):
"""
enum have a specification 'specs' option
that can be forced by the scheme
"""
vv = meta[field]['value']
enum_val = 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 process_num(self,meta,field,lang='de'):
v = meta[field]['value']
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'):
v = meta[field]['value']
t = string.Template(self.get_template(field,lang))
if hasattr(v, "__len__"):
vv = [t.substitute({'value' : ev}) for ev in v]
return [self.process_label(field,lang),', '.join(vv)]
else:
return self.process_num(meta=meta,field=field,lang=lang)
def process_multikey(self,meta,field,lang='de'):
"""
multikey need to assign a numeric value to a key
"""
vs = meta[field]['value']
t = string.Template(self.get_template(field,lang))
k = self.process_label(field,lang)
parts = []
for e in vs:
kk = self.__schema[field]['keys'][e][lang]
parts.append(t.substitute({'key': kk, 'value' : vs[e]}))
return [k,', '.join(parts)]
def process(self,meta,fields = [],lang = 'de',pagebreak = False,createTitle=False,header_level=1):
table_items = []
for field in fields:
match self.__schema[field]['type']:
case 'str': table_items.append(self.process_str(meta,field,lang))
case 'enum': table_items.append(self.process_enum(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 'multikey': table_items.append(self.process_multikey(meta,field,lang))
mdg = MarkdownGenerator()
mdg.generate_markdown(table_items,pagebreak,createTitle,header_level=header_level)
def process_book_section(self,section,lang='de'):
pass
def process_book(self,book,bookpath,create_title,pagebreak,lang='de',header_level=2):
actual_fields = []
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'][lang])
if 'modules' in section:
for m in section['modules']:
mod_path = os.path.join(os.path.dirname(bookpath),m)
with open(mod_path) as fm:
self.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=lang,pagebreak=pagebreak,createTitle=create_title,header_level=header_level)
def main():
@staticmethod
def run():
# arguments
parser = ArgumentParser(description='versatile curricula generator')
@ -165,7 +38,7 @@ def main():
parser.add_argument('-b','--book',type=str,help="process a whole curriculum book with sections")
parser.add_argument('--level',type=int,default=1,help="level of header tags")
parser.add_argument('--table-gen',type=str,default=None,help='runs table generator')
parser.add_argument('--template',type=str,default=None,help='defines a template to be used with fields')
# get arguments
args = parser.parse_args()
@ -179,24 +52,47 @@ def main():
return
# book mode with predefined setting from a book file
if args.book and args.schema:
cb = CourseBuilder()
generator = MetaGenerator()
with open(args.schema) as sf:
cb.set_schema(yaml.load(sf,Loader=yaml.Loader))
generator.set_schema(yaml.load(sf,Loader=yaml.Loader))
with open(args.book) as bf:
cb.process_book(yaml.load(bf,Loader=yaml.Loader),os.path.abspath(args.book),lang=args.lang,pagebreak=args.pagebreak,create_title=args.title,header_level=args.level)
actual_fields = []
book = yaml.load(bf,Loader=yaml.Loader)
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:
for m in section['modules']:
mod_path = os.path.join(os.path.dirname(book_path),m)
with open(mod_path) as fm:
try:
table_items = generator.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=args.lang,pagebreak=args.pagebreak,createTitle=args.title,header_level=args.level)
MarkdownGenerator.generate(table_items,pagebreak=args.pagebreak,title=args.title,header_level=args.level)
except Exception as exc:
print(f'{type(exc).__name__} in {mod_path}: {exc}',file=sys.stderr)
# verbose command line mode
elif args.schema and args.meta and len(args.fields) > 0:
cb = CourseBuilder()
# get actual fields
actual_fields = []
if os.path.isfile(args.fields[0]):
@ -206,14 +102,29 @@ def main():
actual_fields = args.fields
# get schema
actual_schema = None
with open(args.schema) as f:
cb.set_schema(yaml.load(f,Loader=yaml.Loader))
actual_schema = yaml.load(f,Loader=yaml.Loader)
# iterate through meta files
for m in args.meta:
with open(m) as fm:
cb.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=args.lang,pagebreak=args.pagebreak,createTitle=args.title,header_level=args.level)
generator = MetaGenerator()
generator.set_schema(actual_schema)
table_items = generator.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=args.lang,pagebreak=args.pagebreak,createTitle=args.title,header_level=args.level,template=args.template)
if args.template:
TemplateGenerator.generate(table_items)
else:
MarkdownGenerator.generate(table_items,pagebreak=args.pagebreak,title=args.title,header_level=args.level)
# print(table_items)
else:
parser.print_help()
if __name__ == '__main__':
main()
CourseBuilder.run()

View file

@ -2,13 +2,21 @@
import textwrap,itertools
# we need raw value maybe add a third item - tuple the input?
# alternative use a dictionary
# { name: XYZ }
# or make it the class
class MarkdownGenerator:
def __init__(self) -> None:
@staticmethod
def generate_tablerow() -> str:
pass
def generate_markdown(self,ti,pagebreak = False,title = False,header_level = 1) -> str:
@staticmethod
def generate(ti,pagebreak = False,title = False,header_level = 1) -> str:
line_length = 128
column_ratio= 0.28
@ -16,6 +24,9 @@ class MarkdownGenerator:
h_len = int(line_length * column_ratio)
d_len = line_length-h_len
#
# generate title (currently the first one)
#
if title:
print('#' * header_level,ti[0][1],'\n')
@ -27,8 +38,11 @@ class MarkdownGenerator:
# this implements a Markdown Grid-Table
#
# test if this affected by a third item!
for k,v in ti:
#
if v == None:
v = ''

View file

@ -0,0 +1,155 @@
import os,string,sys
import yaml
class MetaGenerator:
def __init__(self) -> None:
self.__schema = None
def set_schema(self,schema = None):
self.__schema = schema
def get_template(self,field,lang='de'):
if 'template' in self.__schema[field]:
return self.__schema[field]['template'][lang]
else:
return "$value"
def is_translatable(self,field):
if 'translatable' in self.__schema[field]:
return self.__schema[field]['translatable']
else:
return True
def needs_spec(self,field):
if 'spec' in self.__schema[field]:
return self.__schema[field]
else:
return False
def process_label(self,field,lang='de'):
# processes the label of a field item
return self.__schema[field]['label'][lang]
def process_str(self,meta,field,lang='de'):
if self.is_translatable(field):
return [self.process_label(field,lang),meta[field][lang]]
else:
if not 'value' in meta[field]:
raise AssertionError(field,'incomplete')
return [self.process_label(field,lang),meta[field]['value']]
def process_enum(self,meta,field,lang='de'):
"""
enum have a specification 'specs' option
that can be forced by the scheme
"""
vv = meta[field]['value']
enum_val = 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 process_num(self,meta,field,lang='de'):
v = meta[field]['value']
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'):
v = meta[field]['value']
t = string.Template(self.get_template(field,lang))
if hasattr(v, "__len__"):
vv = [t.substitute({'value' : ev}) for ev in v]
return [self.process_label(field,lang),', '.join(vv)]
else:
return self.process_num(meta=meta,field=field,lang=lang)
def process_multikey(self,meta,field,lang='de'):
"""
multikey need to assign a numeric value to a key
"""
vs = meta[field]['value']
t = string.Template(self.get_template(field,lang))
k = self.process_label(field,lang)
parts = []
for e in vs:
kk = self.__schema[field]['keys'][e][lang]
parts.append(t.substitute({'key': kk, 'value' : vs[e]}))
return [k,', '.join(parts)]
def process(self,meta,fields = [],lang = 'de',pagebreak = False,createTitle=False,header_level=1,template=None):
table_items = []
# iterate over requested fields
for field in fields:
try:
# correlate with schema and append
match self.__schema[field]['type']:
case 'str': table_items.append(self.process_str(meta,field,lang))
case 'enum': table_items.append(self.process_enum(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 'multikey': table_items.append(self.process_multikey(meta,field,lang))
except Exception as exp:
print(field,' not resolvable in ',self.__schema,exp)
# maybe return tableitems as np.Dataframe?
return table_items
# if template != None:
# # use template generator
# TemplateGenerator.generate(table_items,pagebreak,createTitle,header_level=header_level)
# pass
# else:
# # conventional MD mode
# MarkdownGenerator.generate(table_items,pagebreak,createTitle,header_level=header_level)
# def process_book_section(self,section,lang='de'):
# pass
# book mode
# def process_book(self,book,bookpath,create_title,pagebreak,lang='de',header_level=2):
# actual_fields = []
# 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'][lang])
# if 'modules' in section:
# for m in section['modules']:
# mod_path = os.path.join(os.path.dirname(bookpath),m)
# with open(mod_path) as fm:
# try:
# table_items = self.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=lang,pagebreak=pagebreak,createTitle=create_title,header_level=header_level)
# print(table_items)
# except Exception as exc:
# print(f'{type(exc).__name__} in {mod_path}: {exc}',file=sys.stderr)

View file

@ -35,7 +35,7 @@ class TableGenerator:
self.run_template(rows=rows)
def run_template(self,rows = [],lang = 'de'):
def run_template(self,rows = [],lang = 'de') -> None:
t = string.Template(self.get_latex_template())

View file

@ -0,0 +1,11 @@
#!/usr/bin/env python
import textwrap,itertools
class TemplateGenerator:
@staticmethod
def generate(ti,pagebreak = False,title = False,header_level = 1) -> str:
print(ti)
pass

View file

@ -1,17 +1,30 @@
coursebuilder := ../../coursebuilder
table.en.pdf:
@echo "creating English version ..."
python ../../coursebuilder -s schema.yaml -m mod.cg.yaml -l en -f fields.yaml | pandoc --template pandoc-template/eisvogel.latex -o table.en.pdf
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l en -f fields.yaml | pandoc --template pandoc-template/eisvogel.latex -o table.en.pdf
table.de.pdf:
@echo "creating German version ..."
python ../../coursebuilder -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml | pandoc --template pandoc-template/eisvogel.latex -o table.de.pdf
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml | pandoc --template pandoc-template/eisvogel.latex -o table.de.pdf
all: table.en.pdf table.de.pdf
clean:
rm -f table.en.pdf table.de.pdf
debug-template:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml -l de -f name credits --template "$$name | $$credits"
debug-markdown:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml -l de -f name credits
debug-book:
python ${coursebuilder} -s schema.yaml -b book.yaml -l de
debug:
python ../../coursebuilder -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml
table:
python ${coursebuilder} -s schema.yaml -b book.yaml -l de -f fields.yaml

View file

@ -11,7 +11,7 @@ book:
- content
- form-of-instruction
- prerequisites
- media-of-instruction
- teaching-material
- author-of-indenture
- used-in
- workload
@ -28,4 +28,10 @@ book:
en: "## compulsory courses {.unnumbered}"
- modules:
- mod.cg.yaml
- text:
de: "## Wahlbereich {.unnumbered}"
en: "## elective courses {.unnumbered}"
- modules:
- mod.interactsys.yaml

View file

@ -5,7 +5,7 @@ fields:
- content
- form-of-instruction
- prerequisites
- media-of-instruction
- teaching-material
- author-of-indenture
- used-in
- workload

View file

@ -74,7 +74,7 @@ content:
* Overview visualizations
* Graphical User Interfaces
media-of-instruction:
teaching-material:
de: |
* H5P Lernmodule
* Lernforum

View file

@ -61,7 +61,7 @@ content:
* evaluation methods of interactive systems
* statistical methods for UX design
media-of-instruction:
teaching-material:
de: |
H5P Lernmodule, Lernforum und Übungen am PC
@ -101,6 +101,9 @@ workload:
form-of-exam:
value: alternative
spec:
de:
en:
frequency:
value: once_per_year

View file

@ -142,7 +142,7 @@ prerequisites:
#
# Wie können die Studierenden sich auf die Teilnahme an diesem Modul vorbereiten?
#
media-of-instruction:
teaching-material:
type: str
label: {
de: "Literatur und multimediale Lehr- und Lernprogramme",