first MVP
This commit is contained in:
parent
c1d22c46e7
commit
e2e96c9f43
4 changed files with 151 additions and 116 deletions
19
README.md
19
README.md
|
@ -6,6 +6,25 @@ Coursebuilder is a helper and validator for curricula. It helps to amalgate and
|
||||||
|
|
||||||
(c) Copyright 2020-2023 Hartmut Seichter
|
(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
|
# Licence
|
||||||
|
|
||||||
Coursebuilder is licensed under the terms of the MIT License. For details consult https://opensource.org/license/mit/ or the attached license file
|
Coursebuilder is licensed under the terms of the MIT License. For details consult https://opensource.org/license/mit/ or the attached license file
|
||||||
|
|
||||||
|
|
|
@ -3,95 +3,19 @@
|
||||||
"""
|
"""
|
||||||
CourseBuilder
|
CourseBuilder
|
||||||
|
|
||||||
Coursebuilder is a preprocessor tool to generate curricula with pandoc from
|
Coursebuilder is a preprocessor tool for [pandoc](https://pandoc.org)
|
||||||
structured representations. Data scheme and values are kept in YAML files
|
to generate multi-lingual curricula documentation tables from
|
||||||
in order to version content with Git.
|
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
|
from argparse import ArgumentParser
|
||||||
import itertools
|
import itertools
|
||||||
import yaml
|
import yaml
|
||||||
import os
|
|
||||||
import textwrap
|
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:
|
class CourseBuilder:
|
||||||
|
@ -99,11 +23,27 @@ class CourseBuilder:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.__schema = None
|
self.__schema = None
|
||||||
|
|
||||||
|
|
||||||
def set_schema(self,schema = None):
|
def set_schema(self,schema = None):
|
||||||
self.__schema = schema
|
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)
|
h_len = int(line_length * column_ratio)
|
||||||
d_len = line_length-h_len
|
d_len = line_length-h_len
|
||||||
|
@ -136,14 +76,27 @@ class CourseBuilder:
|
||||||
headline = True
|
headline = True
|
||||||
|
|
||||||
# to control pagebreaks for pandoc
|
# to control pagebreaks for pandoc
|
||||||
# if pagebreak:
|
if pagebreak:
|
||||||
# print('\n\\newpage')
|
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']
|
def process_enum(self,meta,field,lang='de'):
|
||||||
# fields = ['form-of-exam']
|
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 = []
|
table_items = []
|
||||||
|
|
||||||
|
@ -151,29 +104,11 @@ class CourseBuilder:
|
||||||
match self.__schema[field]['type']:
|
match self.__schema[field]['type']:
|
||||||
case 'str': table_items.append(self.process_str(meta,field,lang))
|
case 'str': table_items.append(self.process_str(meta,field,lang))
|
||||||
case 'enum': table_items.append(self.process_enum(meta,field,lang))
|
case 'enum': table_items.append(self.process_enum(meta,field,lang))
|
||||||
|
case 'int': table_items.append(self.process_int(meta,field,lang))
|
||||||
# print(table_items)
|
|
||||||
|
|
||||||
self.generate_markdown(table_items)
|
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():
|
def main():
|
||||||
|
|
||||||
# get command line parameters
|
# 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('-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('-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")
|
parser.add_argument('-s','--schema',help="using provided schema")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,9 @@ name: {
|
||||||
en: "Computer Graphics"
|
en: "Computer Graphics"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
credits:
|
||||||
|
value: 5
|
||||||
|
|
||||||
# common
|
# common
|
||||||
common:
|
common:
|
||||||
id: CG # fix for any variante
|
id: CG # fix for any variante
|
||||||
|
@ -72,6 +75,30 @@ content:
|
||||||
* Overview visualizations
|
* Overview visualizations
|
||||||
* Graphical User Interfaces
|
* 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: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
|
||||||
|
|
||||||
|
|
||||||
# German Variant
|
# German Variant
|
||||||
de:
|
de:
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#
|
#
|
||||||
name:
|
name:
|
||||||
type: str
|
type: str
|
||||||
|
translatable: false
|
||||||
label: {
|
label: {
|
||||||
de: "Modulname",
|
de: "Modulname",
|
||||||
en: "name of course"
|
en: "name of course"
|
||||||
|
@ -16,6 +17,7 @@ name:
|
||||||
#
|
#
|
||||||
id:
|
id:
|
||||||
type: str
|
type: str
|
||||||
|
translatable: false
|
||||||
label: {
|
label: {
|
||||||
de: "Kürzel",
|
de: "Kürzel",
|
||||||
en: "code"
|
en: "code"
|
||||||
|
@ -26,6 +28,7 @@ id:
|
||||||
#
|
#
|
||||||
instructor:
|
instructor:
|
||||||
type: str
|
type: str
|
||||||
|
translatable: false
|
||||||
label: {
|
label: {
|
||||||
de: "Modulverantwortliche:r",
|
de: "Modulverantwortliche:r",
|
||||||
en: "module instructor"
|
en: "module instructor"
|
||||||
|
@ -51,8 +54,42 @@ content:
|
||||||
en: "content"
|
en: "content"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Lehrform
|
||||||
|
#
|
||||||
form:
|
form:
|
||||||
|
label: {
|
||||||
|
de: "Lehrform",
|
||||||
|
en: "form of instruction"
|
||||||
|
}
|
||||||
type: enum
|
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
|
# Voraussetzungen für die Teilnahme
|
||||||
#
|
#
|
||||||
|
@ -93,7 +130,15 @@ used-in:
|
||||||
en: "used in study programs"
|
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
|
# credits/ECTS
|
||||||
#
|
#
|
||||||
|
@ -103,7 +148,11 @@ credits:
|
||||||
de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote"
|
de: "Kreditpunkte und Gewichtung der Note in der Gesamtnote"
|
||||||
}
|
}
|
||||||
type: int
|
type: int
|
||||||
template: "${value}CP (${value} / 120) "
|
template:
|
||||||
|
de: "${value}CP Gewichtung: ${value}CP von 120CP "
|
||||||
|
en: "${value}CP weight: ${value} / 120 "
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Leistungsnachweis
|
# Leistungsnachweis
|
||||||
#
|
#
|
||||||
|
@ -127,8 +176,9 @@ form-of-exam:
|
||||||
en: "alternative examination"
|
en: "alternative examination"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# term
|
# Semester
|
||||||
#
|
#
|
||||||
term:
|
term:
|
||||||
label: {
|
label: {
|
||||||
|
@ -150,6 +200,7 @@ term:
|
||||||
en: "winter and summer term"
|
en: "winter and summer term"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# Häufigkeit des Angebots
|
# Häufigkeit des Angebots
|
||||||
#
|
#
|
||||||
|
@ -169,7 +220,9 @@ frequency:
|
||||||
en: "once per study year"
|
en: "once per study year"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
duration: enum
|
duration: enum
|
||||||
|
|
||||||
kind:
|
kind:
|
||||||
type: enum
|
type: enum
|
||||||
label: {
|
label: {
|
||||||
|
@ -187,7 +240,12 @@ kind:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remarks: str
|
remarks:
|
||||||
|
type: str
|
||||||
|
label: {
|
||||||
|
de: "Bemerkungen",
|
||||||
|
en: "Remarks"
|
||||||
|
}
|
||||||
|
|
||||||
# test:
|
# test:
|
||||||
# label: { de: "Name", en: "A Test" }
|
# label: { de: "Name", en: "A Test" }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue