hoprog
v1.0.17
Published
Homework Project Generator
Downloads
25
Readme
Hoprog -- Homework Project Generator
Creates projects and solution projects for class room exercises.
[[TOC]]
Motivation
Im Rahmen der (Programmier-) Lehre werden Studenten verschiedene Übungsaufgaben mit Projektvorlagen zu Verfügung gestellt. Zusätzlich will man als Dozent Musterlösungen für die Aufgaben erstellen, sei es für Tutoren, den eigenen Gebrauch oder zur Korrektur. In manchen Fällen bauen Übungsaufgaben aufeinander auf. Im Fall des Autors dieses Pakets wird bspw. das Backend einer Webanwendung in 7 Übungsblättern erstellt.
Nun ist es extrem aufwendig, bspw. bei wöchentlichen Aufgaben, 10 oder mehr Übungsprojekte mit Musterlösungen zu erstellen und zu warten (also über 20 Projekte!). Viele Dateien finden sich identisch oder nur mit leichten Abweichungen in fast allen Projekten. Hier hilft Hoprog. Mit Hoprog kann ein generisches Projekt als Ausgangsbasis erstellt werden und die Vorlagen sowie die Musterlösungen davon abgeleitet, sprich generiert, werden.
Dabei werden folgende Anforderungen gestellt:
- Das generische Projekt selbst soll übersetzbar und testbar sein.
- Die Musterlösungen müssen übersetzbar und testbar sein.
- Bei Änderungen am generischen Projekt sollen die abgeleiteten Projekte einfach neu erstellt werden können.
Übersicht
Man erstellt ein generische Projekt, bspw. "modul.generic". Dort setzt man als Dozent alle Aufgaben um. D.h. man erstellt eine Musterlösung über alle Teilaufgaben.
In den Dateien der Musterlösung werden Kommentare mit Anweisungen für den Generator eingefügt. Da dies nicht in allen Dateien möglich ist, können alternativ Konfigurationsdateien (.hoprog.json
) verwendet werden. Es gibt zwei Arten von Anweisungen:
- Welche Datei wird in welchem Projekt erzeugt. Dies sind sogenannte File-Filter.
- Innerhalb einer Datei: Welche Bereiche werden nur in ausgewählten Projekten erzeugt. Die sind sogenannte Area-Filter.
Die einzelnen Filter sind weiter unten beschrieben.
Installation
Es gibt drei Möglichkeiten:
- Hoprog global installieren:
npm install --global hoprog
. Nun kann Hoprog einfach überhoprog
aufgerufen werden. Falls Hoprog aktualisiert wurde, kann das Update mitnpm update -g hoprog
installiert werden. - Hoprog direkt ausführen über
npx hoprog
- Hoprog klonen und im Klon
npm run build
aufrufen. Im PATH eine Dateihoprog
angelegen mit folgendem Inhalt:
Nun kann Hoprog einfach über#!/usr/bin/env node require("/path/to/hoprog/dist/cli.js")
hoprog
aufgerufen werden. Diese Variante ist sinnvoll, wenn an Hoprog selbst Änderungen durchgeführt werden sollen.
Im Folgenden wird davon ausgegangen, dass Hoprog mittels hoprog
aufgerufen werden kann.
Verwendung
Nachdem alle Dateien im generischen Projekt ausgezeichnet wurden (bzw. Konfigurationsdatein angelegt wurden) und die Einstellungen in den Settings definiert wurden (siehe unten), können die Projekte mit
hoprog generate
erzeugt werden.
Mit
hoprog generate --help
wird eine Hilfe ausgegeben, die die möglichen Optionen anzeigt.
Settings
Die Settings müssen in einer Datei hoprog.json
liegen. Die Settings enthalten die Einstellungen, die man alternativ zu Optionen auf der Kommandozeile mitgeben kann. Diese sind von File-Filter in Konfigurationsdateien zun unterscheiden (sieh unten).
Wichtigste Einstellung ist die Definition der zu generierenden Projekte über ``.
Hier ein typisches Beispiel:
// Configuration for Homework Project Generator
{
"projectNames": ["we1.blatt[00-10]","we1.blatt[00-10].solution"]
}
Damit werden Projekte "we1.blatt00", "we1.blatt01" bis "we1.blatt10" sowie die Musterlösungen "we1.blatt00.solution" bis "we1.blatt10.solution" erzeugt.
Die Projektnamen dürfen keine Leerzeichen, Kommas, eckige Klammern oder Doppelpunkte enthalten. Auch Kommentarzeichen müssen vermieden werden.
Die Settings und ihre Default-Werte sind unter src/settings.ts
definiert. Die Default-Werte gehen davon aus, dass die generierten Projekte im Parent-Verzeichnis des generischen Projekts abgelegt werden. Die Werte sind für JavaScript-Projekte optimiert.
Generator-Anweisungen
Die grundsätzliche Idee ist, dass in der Vorlage alle Dateien vorhanden sind und mittels der Konfiguration bestimmt werden kann, welche Dateien und in diesen welche Bereiche in welche generierten Projekte kopiert werden.
Als generische Vorlage enthält dieses Projekt spezielle Bezeichner und Kommentare. Am einfachsten ist die Konfiguration über Kommentare in den Dateien. Da nicht alle Dateitypen Kommentare unterstützen, können alternativ in einer Konfigurationsdatei die gleichen Angaben erfolgen, wobei die Bereiche hier etwas anders gehandhabt werden.
File-Filter
Für File-Filter (egal, ob als Kommentar oder in der Konfigurationsdatei) gilt:
- Der Filter besteht aus dem Projektnamen, etwa
we2.blatt01
- In eckigen Klammern können Zahlenbereiche angegeben werden, um mehrere Projekte zu spezifizieren:
we2.blatt[01-07]
. Falls das zweite Argument fehlt (aber ein "-" vorhanden ist), werden alle folgenden Projekte ausgewählt. - Es kann nur ein Zahlenbereich definiert werden.
*
wird intern als Regular Expression.*
interpretiert. Bei File-Filtern kann man das einsetzen, ist aber i.A. sinnlos (da die meisten Dateien in den Template-Projekte nur in genau einem Template enthalten sind). Für Area-Filter ist dies aber sehr praktisch.
Generell können mehrere Filter definiert werden. Wenn man Kommentare verwendet, werden einfach mehrere Kommentare untereinander angegeben. In der Konfigurationsdatei können Strings oder Arrays verwendet werden.
Achtung:
- File-Filter müssen ganz oben in der Datei stehen!
- Vorangehende Nullen werden beim Vergleich berücksichtigt, d.h.
blatt1
passt nicht fürblatt[01-09]
.
Unterstützte Dateitypen
Kommentare für File- und Area-Filter können in folgenden Dateitypen verwendet werden:
- JavaScript, TypeScript, Java, JSON (with comments), Antlr Grammers
- Ext:
js
,mjs
,jsx
,ts
,mts
,tsx
,java
,json
,jsonc
,g4
- Single Line Comment:
//
, - Multi Line Comment:
/*
,*/
, - Replace Mulit Line Commend End (if in comment):
*_/
->*/
- Ext:
- Markdown, HTML, XML
- Ext:
md
,html
,xml
- Multi Line Comment:
<!--
,-->
, - Replace Mulit Line Commend End (if in comment):
--_>
->-->
- Ext:
- Shellscript, Dotenv, Gitignore, Yaml
- Ext:
sh
,env
,gitignore
,yaml
,yml
- Single Line Comment:
#
- Ext:
- LaTeX
- Ext:
tex
,bst
,cls
- Single Line Comment:
%
- Ext:
- CSS
- Ext:
css
- Multi Line Comment:
/*
,*/
, - Replace Mulit Line Commend End (if in comment):
*_/
->*/
- Ext:
File-Filter in Kommentaren
Mit einem Kommentar in der ersten Zeile einer Datei wird festgelegt, ob die Datei generell kopiert wird.
Diese Kommentare werden ergänzend zur Konfiguration verwendet.
Dabei werden Kommentare erkannt, die mit @gen
beginnen. Diese Kommentare werden in jedem Fall beim Kopieren gelöscht.
File-Filter müssen immer oben in der Datei stehen!
Beispiele
- Datei wird ausschließlich in Projektvorlage für Blatt 01 für Studenten kopiert:
// @gen we2.blatt01
- Datei wird in Projektvorlage für Blatt 01 bis Blatt 07 für Studenten kopiert:
// @gen we2.blatt[01-07]
- Datei wird in Musterlösung für Blatt 01 und alle weiteren kopiert:
// @gen we2.blatt[01-].solution
- Datei wird in in Projektvorlage für Blatt 01 sowie in Musterlösung für Blatt 01 und alle weiteren kopiert:
// @gen we2.blatt[01-].solution // @gen we2.blatt01
- Dies könnte man alternativ in einem Kommentar mit Kommas beschreiben:
// @gen we2.blatt[01-].solution,we2.blatt01
File-Filter in Konfigurationsdateien
Man kann in Konfigurationsdateien Generator-Anweisungen mit File-Filtern hinterlegen.
In Verzeichnissen im Generator-Projekt können dazu Dateien .hoprog.json
angelegt. Davon kann es mehrere geben. Der Inhalt bezieht sich jeweils auf das aktuelle Verzeichnis.
In jedem Fall ist es ein Array mit Objekten. Die Objekte enthalten folgende Angaben:
file
: Datei(en), für die der Filter gilt. Hierbei kann mit*
gearbeitet werden, falls alle Dateien eines Ordners selektiert werden sollen. Hierbei kann das*
nur hinten stehen!. Die Dateiangaben sind immer relativ zur Konfigurationsdatei.project
: Ein oder mehrere File-Filter mit den gleichen Regeln wie bei Kommentarenmove
: Optional kann angegeben werden, wohin die Datei verschoben bzw. umbenannt werden soll. Falls eine Datei selektiert wurde, kann eine Umbenennung (inkl. Verschiebung) erfolgen, ansonsten kann die Datei nur von einem Ordner in einen anderen verschoben werden.replace
: Optional können Textersetzungen angegeben werden. Hierbei wird ein Objektliteral definiert, die Keys sind die Suchstrings, die Value die Ersetzung. In der Ersetzung können folgende Variablen verwendet werden:${task}
: Nummer des Tasks wie im Projekt angegeben. Dies kann nur verwendet werden, wenn der File-Filter einen Task-Bereich (in eckigen Klammern) definiert.${project}
: Name des Projekts
Beispiele
[
{
"file": "package.json",
"project": "we2.blatt[03-05]",
"replace": {
"TASK": "${task}",
"PROJECT": "${project}"
}
},
{
"file": "envs/.env01",
"project": "we2.blatt01",
"move": "env"
},
{
"file": "logo.png",
"project": [
"we2.blatt[01-]",
"we2.blatt[01-].solution"
]
},
{
"file": "specific/blatt01/*",
"project": "we2.blatt01",
"move": "./"
}
]
Never
Wenn eine Datei niemals generiert werden soll, kann als Project-Pattern im File-Filter never
gesetzt werden. In diesem Fall wird keine Warnung erzeugt, wenn diese Datei niemals generiert wurde und alle Area-Filter werden ebenfalls ignoriert. Dies kann verwendet werden, um gezielt Datein auszukommentieren (etwa, wenn dies in einem neuen Semester nicht mehr verwendet aber auch noch nicht gelöscht werden sollen).
@nogen
Um einfach bestimmte Dateien nicht mehr zu generieren, gibt es drei Möglichkeiten:
- Man löscht sie im generischen Projekt.
- Man entfernt die Projekt-Filter (erhält dann aber u.U. Warnungen bei nicht verwendeten Area-Filtern)
- Man ersetzt im Projekt-Filter
@gen
mit@nogen
. In diesem Fall wird die Datei niemals erzeugt und es werden keine Warnungen ausgegeben.
Area-Filter
Des weiteren können in einer Datei (die kopiert werden soll) Bereiche ein- und ausgeblendet werden. Diese könne nur in Kommentaren angegeben werden.
Falls verschiedene Versionen von Dateien generiert werden sollen, in denen keine Area-Filter möglich sind, muss man auf File-Filter (in Konfigurationsdatein) mit move
zurückgreifen.
Area-Filter in Kommentaren
Auch Filter für Bereiche beginnen mit @gen
gefolgt von einem Filter für das Projekt.
Ein Area-Filter beginnt mit @gen ...:start
und endet mit @gen :end
. Optional kann beim Ende der File-Filter stehen.
Area-Filter können Single-Line-Kommentare sein, in diesem Fall werden einfach nur die Kommentare der Area-Filter gelöscht (bzw. der Bereich dazwischen). D.h. aber auch, dass der Code ausführbar sein muss, wenn im Generator-Projekt kein Fehler auftreten soll.
Alternativ können Multiline-Kommentare verwendet werden, so dass der Code nicht unbedingt übersetzbar sein muss.
Diese müssen mit /* @gen ...:start
beginnen und mit @gen ...:end */
oder einfach nur @gen :end */
enden.
Um in dem auskommentieren Bereich mehrzeilige Kommentare zu ermöglichen, muss das Kommentarende dort mit einem Pseudo-Kommentarende abgeschlossen werden, damit die Syntax generell korrekt bleibt.
Zur besseren Sichtbarkeit können nach start
oder end
beliebig viele '*
folgen.
Die Projekt-Filter können mehrere Projekte enthalten, diese werden mit "," getrennt.
Unter File-Filter ist die Liste der unterstützten Datei-Typen zu sehen.
Achtung: Die Dateiinhalt werden nicht wirklich geparst. D.h. es wird nur nach den entsprechenden Zeichen gesucht und bspw. nicht deren Kontext (in einem String oder so) betrachtet.
I.d.R. sollte das kein Problem sein, da ja nach gen-Kommentaren gesucht wird, d.h. nach dem Kommentarzeichen muss @gen
stehen, damit Probleme auftreten können. Dies ist höchst unwahrscheinlich.
Formatierung
Einrückungen und Zeilenumbrüche werden geeignet entfernt, d.h. die @gen-Kommentare sollten keine Spuren im generierten Code hinterlassen. Generell werden Zeilenkommentare inkl. dem Zeilenumbruch entfernt. Mehrzeilige Area-Kommentare werden i.A. ohne umstehende Leerzeichen oder Zeilenumbrüche entfernt. Ausnahme sind Area-Kommentare, die mehrzeilig sind, um Code auszukommentieren -- bei diesen werden die umgebenden Leerzeichen und Umbrüche entfernt.
Beispiele
- Ein Bereich, der nur in Musterlösungen sichtbar sein soll, aber nicht in der Projektvorlage für Studenten:
Dies ist für Dateien, die in der Projektvorlage enthalten sind.// @gen *.solution:start ... // @gen *.solution:end
- Ein Bereich, der nur in der Projektvorlage sichtbar sein soll, aber nicht in der Musterlösung:
/* @gen we2.blatt01:start ***** ... @gen :end ******************** */
- Damit die generische Lösung lauffähig ist, kann Code, der nur ein bestimmten Projekten sichtbar sein soll, wie folgt auskommentiert werden:
function foo() { /* @gen we1.blatt01:start throw new Error("Not implemented yet"); @gen :end */ // @gen we1.*.solution:start doSomething(); // @gen :end }
Hier ein typisches Beispiel:
// @gen we2.blatt03
// @gen we2.blatt[03-07].solution
import { foo } from 'bar';
function bar() {
/* @gen we2.blatt03:start
throw new Error("Not implemented yet");
@gen we2.blatt03:end */
// @gen *.solution:start
return foo();
// @gen *.solution:end
}
Im Projekt we2.blatt03 wird dann generiert:
import { foo } from 'bar';
function bar() {
throw new Error("Not implemented yet");
}
Und in den Musterlösungen
import { foo } from 'bar';
function bar() {
return foo();
}
Die Projekt-Filter (im Area-Filter) können mehrere Projekte enthalten, diese werden mit "," getrennt. Beispiel:
// @gen we2.blatt07,*.solution:start
...
// @gen we2.blatt07,*.solution:end
Hier wird ein Bereich definiert, der in allen Solutions sowie in Blatt 07 sichtbar ist.
Never
Wenn ein Bereich niemals generiert werden soll, kann als Project-Pattern never
gesetzt werden. In diesem Fall wird keine Warnung erzeugt, wenn dieser Bereich niemals generiert wurde. Dies kann verwendet werden, um bspw. ein Komma zwischen Alternativen zu setzen, die sich im generierten Code gegenseitig ausschließen. Außerdem können so Anweisungen eingefügt werden, damit das Generator-Projekt erfolgreich compiliert und getestet werden kann.
Nesting
Area-Filter können geschachtelt oder kombiniert werden. Die Angabe des Filters am Ende dient ausschließlich der besseren Lesbarkeit.
Da die File-Filter vor den Area-Filtern ausgeführt werden, bietet es sich manchmal an, die Area-Filter gröber zu definieren. Die erleichtert die Lesbarkeit und macht es einfacher, falls File-Filter angepasst werden müssen.
Hier ein typische Beispiel:
// @gen we2.blatt03
// @gen we2.blatt[03-07].solution
import { foo } from 'bar';
function bar() {
/* @gen we2.blatt03:start
throw new Error("Not implemented yet");
@gen we2.blatt03:end */
// @gen *.solution:start
// @gen we2.blatt[04-07].solution:start
prepareSomething();
// @gen we2.blatt[04-07].solution:end
return foo();
// @gen *.solution:end
}
Sorted und Commaseparated
Areafilter können zu Beginn mit weiteren Markiereungen definiert werden: :sorted
und :commaseparated
(wobei dies :sorted
erfordert).
Damit werden die Zeilen innerhalb des Bereichs alphabetisch sortiert. Wenn :commaseparated
angegebn wird, werden nach jeder Zeile (bis auf die letzte) Kommas eingefügt.
Dadurch kann man sich beim generichen Projekt mehr auf den Inhalt konzentrieren. Hier ein typisches Beispiel:
"devDependencies": {
// @gen *:start:sorted:commaseparated
// @gen notPureHTML,*.solution:start
"typescript": "^5.5.4",
// @gen notPureHTML,*.solution:end
// @gen we1.blatt[01-06],*.solution:start
"@types/node": "^22.3.0",
// @gen we1.blatt[01-06],*.solution:end
// @gen we1.blatt[01-06],we1.blatt[09],*.solution:start
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"jest-junit": "^16.0.0",
"ts-jest": "^29.2.4",
"ts-node": "^10.9.2",
// @gen we1.blatt[01-06],we1.blatt[09],*.solution:end
...
"rimraf": "^6.0.1",
"ziplocalgit": "^1.0.10"
// @gen *:end
},
Weitere Einstellungen
Aliases
In der Konfigurationsdatei können unter aliases
Kürzel definiert werden, um Projektfilter in den Filtern kürzer oder prägnanter schreiben zu können.
Beispiel: Wenn bspw. nur die Projekte 07 und 08 reine HTML-Projekte sind, könnte man definieren:
aliases: { html: "projects[07-08]"}
In den Filtern ist dann @gen projects[07-08]:start
und @gen htmnl:start
identisch.
License
This program and the accompanying materials are made available under the terms of the Eclipse Public License v. 2.0 which is available at https://www.eclipse.org/legal/epl-2.0.