refactoring for division of concerns

This commit is contained in:
Hartmut Seichter 2024-05-01 14:58:41 +02:00
parent 28378e3819
commit 595a0352c5
7 changed files with 223 additions and 198 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,6 +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
* [ ] overlay of compulsory with other modes ...
* [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

@ -18,210 +18,75 @@ 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"
@staticmethod
def run():
def is_translatable(self,field):
if 'translatable' in self.__schema[field]:
return self.__schema[field]['translatable']
else:
return True
# arguments
parser = ArgumentParser(description='versatile curricula generator')
def needs_spec(self,field):
if 'spec' in self.__schema[field]:
return self.__schema[field]
else:
return False
parser.add_argument('-m','--meta',action="extend", nargs="+", type=str,help="course description(s) as YAML file(s)")
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('-p','--pagebreak',action="store_true",help="add a pagebreak after each module")
parser.add_argument('-t','--title',action="store_true",help="take first value in list as title")
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')
def process_label(self,field,lang='de'):
# processes the label of a field item
return self.__schema[field]['label'][lang]
# get arguments
args = parser.parse_args()
if args.table_gen:
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')
tg = TableGenerator()
return [self.process_label(field,lang),meta[field]['value']]
tg.generate_table(args.table_gen)
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]
return
if self.needs_spec(field):
t = string.Template(self.get_template(field=field,lang=lang))
# book mode with predefined setting from a book file
if args.book and args.schema:
generator = MetaGenerator()
spec = meta[field]['spec'][lang]
with open(args.schema) as sf:
generator.set_schema(yaml.load(sf,Loader=yaml.Loader))
return [self.process_label(field,lang),t.substitute({'value': enum_val,'spec': spec})]
with open(args.book) as bf:
generator.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)
# verbose command line mode
elif args.schema and args.meta and len(args.fields) > 0:
generator = MetaGenerator()
actual_fields = []
if os.path.isfile(args.fields[0]):
with open(args.fields[0]) as ff:
actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields']
else:
actual_fields = args.fields
with open(args.schema) as f:
generator.set_schema(yaml.load(f,Loader=yaml.Loader))
for m in args.meta:
with open(m) as fm:
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)
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 = []
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))
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
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:
self.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=lang,pagebreak=pagebreak,createTitle=create_title,header_level=header_level)
except:
print(f'Error in {mod_path}',file=sys.stderr)
def main():
# arguments
parser = ArgumentParser(description='versatile curricula generator')
parser.add_argument('-m','--meta',action="extend", nargs="+", type=str,help="course description(s) as YAML file(s)")
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('-p','--pagebreak',action="store_true",help="add a pagebreak after each module")
parser.add_argument('-t','--title',action="store_true",help="take first value in list as title")
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()
if args.table_gen:
tg = TableGenerator()
tg.generate_table(args.table_gen)
return
# book mode with predefined setting from a book file
if args.book and args.schema:
cb = CourseBuilder()
with open(args.schema) as sf:
cb.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)
# verbose command line mode
elif args.schema and args.meta and len(args.fields) > 0:
cb = CourseBuilder()
actual_fields = []
if os.path.isfile(args.fields[0]):
with open(args.fields[0]) as ff:
actual_fields = yaml.load(ff,Loader=yaml.Loader)['fields']
else:
actual_fields = args.fields
with open(args.schema) as f:
cb.set_schema(yaml.load(f,Loader=yaml.Loader))
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,template=args.template)
else:
parser.print_help()
parser.print_help()
if __name__ == '__main__':
main()
CourseBuilder.run()

View file

@ -2,9 +2,18 @@
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:
@staticmethod
def generate_tablerow() -> str:
pass
@staticmethod
def generate(ti,pagebreak = False,title = False,header_level = 1) -> str:
@ -28,8 +37,12 @@ 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,145 @@
import string
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 = []
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))
# 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
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:
self.process(yaml.load(fm,Loader=yaml.Loader),fields=actual_fields,lang=lang,pagebreak=pagebreak,createTitle=create_title,header_level=header_level)
except:
print(f'Error in {mod_path}',file=sys.stderr)

View file

@ -14,9 +14,11 @@ all: table.en.pdf table.de.pdf
clean:
rm -f table.en.pdf table.de.pdf
debug-list:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml mod.interactsys.yaml -l de -f name credits --template ""
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:
python ${coursebuilder} -s schema.yaml -m mod.cg.yaml -l de -f fields.yaml