first MVP

This commit is contained in:
Hartmut Seichter 2023-11-08 15:32:41 +01:00
parent c1d22c46e7
commit e2e96c9f43
4 changed files with 151 additions and 116 deletions

View file

@ -6,6 +6,25 @@ Coursebuilder is a helper and validator for curricula. It helps to amalgate and
(c) Copyright 2020-2023 Hartmut Seichter
# Usage
```sh
$> python coursebuilder
coursebuilder [-h] [-m META [META ...]] [-l LANG] [-f FIELDS [FIELDS ...]] [-s SCHEMA]
options:
-h, --help show this help message and exit
-m META [META ...], --meta META [META ...]
course description(s) as YAML file(s)
-l LANG, --lang LANG Language to parse from meta file (use de or en)
-f FIELDS [FIELDS ...], --fields FIELDS [FIELDS ...]
Fields to be used
-s SCHEMA, --schema SCHEMA
using provided schema
```
# 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,95 +3,19 @@
"""
CourseBuilder
Coursebuilder is a preprocessor tool to generate curricula with pandoc from
structured representations. Data scheme and values are kept in YAML files
in order to version content with Git.
Coursebuilder is a preprocessor tool for [pandoc](https://pandoc.org)
to generate multi-lingual curricula documentation tables from
structured representations as a flatfile database. Data scheme and
actual values are kept in YAML files in order to version them with git.
"""
from argparse import ArgumentParser
import itertools
import yaml
import os
import textwrap
import string
config_file = 'modulhandbuch.yaml'
line_length = 128
column_ratio= 0.28
def build_curriculum(input_path,lang='de',pagebreak=False,title=False):
# open the config file
file_path = os.path.realpath(__file__)
config_path = os.path.join(os.path.dirname(file_path),config_file)
transforms = None # for translation
# load transforms
with open(config_path,'r') as cf:
transforms = yaml.load(cf,Loader=Loader)
# open meta.yaml in the directory
with open(os.path.join(input_path),'r') as fp:
# get configuration data
desc = yaml.load(fp,Loader=Loader)
# collect transformations
ti = []
# fix for now
for k,v in desc[lang].items():
v = v if v else ""
ti.append((transforms[lang][k],str(v)))
# to limit
h_len = int(line_length * column_ratio)
d_len = line_length-h_len
if title:
print('#',desc[lang]['name'],'\n')
print(''.join(['+',"".ljust(h_len,'-'),'+',"".ljust(d_len,'-'),'+']))
headline = False
for k,v in ti:
h = textwrap.wrap(k, h_len, break_long_words=False)
wrapper = textwrap.TextWrapper(d_len)
t = [wrapper.wrap(i) for i in v.split('\n') if i != '']
t = list(itertools.chain.from_iterable(t))
# get rows
rows = list(itertools.zip_longest(h,t,fillvalue=""))
# expand rows
for r in rows:
print(''.join(['|',r[0].ljust(h_len,' '),'|',r[1].ljust(d_len,' '),'|']))
if headline:
print(''.join(['+',"".ljust(h_len,'-'),'+',"".ljust(d_len,'-'),'+']))
else:
print(''.join(['+',"".ljust(h_len,'='),'+',"".ljust(d_len,'='),'+']))
headline = True
# to control pagebreaks for pandoc
if pagebreak:
print('\n\\newpage')
# create a meta file
def create_meta():
# open the config file
file_path = os.path.realpath(__file__)
config_path = os.path.join(os.path.dirname(file_path),config_file)
transforms = None # for translation
# load transforms
with open(config_path,'r') as cf:
while line := cf.readline():
print(line.rstrip())
class CourseBuilder:
@ -99,11 +23,27 @@ 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 hasattr(self.__schema[field],'template'):
return self.__schema[field]['template'][lang]
else:
return "$value"
def generate_markdown(self,ti):
def is_translatable(self,field):
if hasattr(self.__schema[field],'translatable'):
return self.__schema[field]['translatable']
else:
return True
def generate_markdown(self,ti,pagebreak = False) -> str:
line_length = 128
column_ratio= 0.28
h_len = int(line_length * column_ratio)
d_len = line_length-h_len
@ -136,14 +76,27 @@ class CourseBuilder:
headline = True
# to control pagebreaks for pandoc
# if pagebreak:
# print('\n\\newpage')
if pagebreak:
print('\n\\newpage')
def process_label(self,field,lang='de'):
# processes the label of a field item
return self.__schema[field]['label'][lang]
def process(self,meta,fields,lang):
def process_str(self,meta,field,lang='de'):
#
return [self.process_label(field,lang),meta[field][lang]]
# fields = ['name','goal','form-of-exam']
# fields = ['form-of-exam']
def process_enum(self,meta,field,lang='de'):
v = meta[field]['value']
return [self.process_label(field,lang),self.__schema[field]['values'][v][lang]]
def process_int(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(self,meta,fields = [],lang = 'de'):
table_items = []
@ -151,29 +104,11 @@ class CourseBuilder:
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))
# print(table_items)
case 'int': table_items.append(self.process_int(meta,field,lang))
self.generate_markdown(table_items)
def process_label(self,field,lang='de'):
pass
def process_str(self,meta,field,lang='de'):
return [self.__schema[field]['label'][lang],meta[field][lang]]
# print(self.__schema[field]['label'][lang],':',meta[field][lang])
def process_enum(self,meta,field,lang='de'):
v = meta[field]['value']
return [self.__schema[field]['label'][lang],self.__schema[field]['values'][v][lang]]
# print(self.__schema[field]['label'][lang],':',self.__schema[field]['values'][v][lang])
def process_int(self,lang='de'):
pass
def main():
# get command line parameters
@ -186,10 +121,6 @@ def main():
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",action="extend", nargs="+", type=str)
# parser.add_argument('-c','--create',action='store_true',help="Create a meta file from description")
# parser.add_argument('-n','--newpage',action='store_true',help="Create a pagebreak after each table")
# parser.add_argument('-t','--title',action='store_true',help="Create a title ahead of each table")
parser.add_argument('-s','--schema',help="using provided schema")

View file

@ -3,6 +3,9 @@ name: {
en: "Computer Graphics"
}
credits:
value: 5
# common
common:
id: CG # fix for any variante
@ -72,6 +75,30 @@ content:
* Overview visualizations
* Graphical User Interfaces
media-of-instruction:
de: |
* H5P Lernmodule
* Lernforum
* Übungen
* Folien
* Auszug aus der Literaturliste:
* Bar-Zeev, Avi. Scenegraphs: Past, Present and Future, 2003 http://www.realityprime.com/scenegraph.php
* Burley, Brent. “Physically-Based Shading at Disney.” In ACM SIGGRAPH, 2012:1-7, 2012
* Goldstein, E. Bruce. Sensation and Perception. 3rd ed. Belmont, Calif.: Wadsworth Pub. Co., 1989
* Hughes, John F. Computer Graphics: Principles and Practice. Third edition. Upper Saddle River, New Jersey: Addison-Wesley, 2014
* Shirley, Peter, and R. Keith Morley. Realistic Ray Tracing. 2. ed. Natick, Mass: A K Peters, 2003
en: |
* H5P learning modules
* learning forum
* exersises
* slides and quizzes
* Literature:
* Bar-Zeev, Avi. Scenegraphs: Past, Present and Future, 2003 http://www.realityprime.com/scenegraph.php.
* Burley, Brent. “Physically-Based Shading at Disney.” In ACM SIGGRAPH, 2012:17, 2012
* Goldstein, E. Bruce. Sensation and Perception. 3rd ed. Belmont, Calif.: Wadsworth Pub. Co., 1989
* Hughes, John F. Computer Graphics: Principles and Practice. Third edition. Upper Saddle River, New Jersey: Addison-Wesley, 2014
* Shirley, Peter, and R. Keith Morley. Realistic Ray Tracing. 2. ed. Natick, Mass: A K Peters, 2003
# German Variant
de:

View file

@ -6,6 +6,7 @@
#
name:
type: str
translatable: false
label: {
de: "Modulname",
en: "name of course"
@ -16,6 +17,7 @@ name:
#
id:
type: str
translatable: false
label: {
de: "Kürzel",
en: "code"
@ -26,6 +28,7 @@ id:
#
instructor:
type: str
translatable: false
label: {
de: "Modulverantwortliche:r",
en: "module instructor"
@ -51,8 +54,42 @@ content:
en: "content"
}
#
# Lehrform
#
form:
label: {
de: "Lehrform",
en: "form of instruction"
}
type: enum
values: {
'lecture' : {
de: "Vorlesung",
en: "lecture"
},
'lecture_seminar' : {
de: "Seminaristische Vorlesung",
en: "lecture and seminar"
},
'seminar' : {
de: "Seminar",
en: "seminar"
},
'exersise' : {
de: "Übung",
en: "lab exersise"
},
'pc_lab' : {
de: "Rechnergestütztes Praktikum",
en: "PC exersise"
},
'project' : {
de: "Project",
en: "project"
}
}
#
# Voraussetzungen für die Teilnahme
#
@ -93,7 +130,15 @@ used-in:
en: "used in study programs"
}
workload: #tricky! { 'presence': 10, 'exersise': 10, 'exam-prep' : 0 }
#
# Arbeitsaufwand
#
workload:
type: str
label: {
de: "Arbeitsaufwand / Gesamtworkload",
en: "workload"
}
#
# credits/ECTS
#
@ -103,7 +148,11 @@ credits:
de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote"
}
type: int
template: "${value}CP (${value} / 120) "
template:
de: "${value}CP Gewichtung: ${value}CP von 120CP "
en: "${value}CP weight: ${value} / 120 "
#
# Leistungsnachweis
#
@ -127,8 +176,9 @@ form-of-exam:
en: "alternative examination"
}
}
#
# term
# Semester
#
term:
label: {
@ -150,6 +200,7 @@ term:
en: "winter and summer term"
}
}
#
# Häufigkeit des Angebots
#
@ -169,7 +220,9 @@ frequency:
en: "once per study year"
}
}
duration: enum
kind:
type: enum
label: {
@ -187,7 +240,12 @@ kind:
}
}
remarks: str
remarks:
type: str
label: {
de: "Bemerkungen",
en: "Remarks"
}
# test:
# label: { de: "Name", en: "A Test" }