npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

hoprog

v1.0.18

Published

Homework Project Generator

Downloads

99

Readme

npm pipeline status coverage

logo 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:

  1. Welche Datei wird in welchem Projekt erzeugt. Dies sind sogenannte File-Filter.
  2. 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:

  1. Hoprog global installieren: npm install --global hoprog. Nun kann Hoprog einfach über hoprog aufgerufen werden. Falls Hoprog aktualisiert wurde, kann das Update mit npm update -g hoprog installiert werden.

  2. Hoprog direkt ausführen über npx hoprog

  3. Hoprog klonen und im Verzeichnis des Klons (also hoprog)

    npm run build

    und

    npm link

    aufrufen. Nun kann Hoprog einfach über hoprog aufgerufen werden. Diese Variante ist sinnvoll, wenn an Hoprog selbst Änderungen durchgeführt werden sollen.

    Falls der Ordner des Klons gelöscht wurde oder grading nicht mehr global erreichbar sein soll, kann dies mit npm unlink -g hoprog gelöscht werden (unlink ist ein Alias für rm bzw uninstall).

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ür blatt[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): *_/ -> */
  • Markdown, HTML, XML
    • Ext: md, html, xml
    • Multi Line Comment: <!--, -->,
    • Replace Mulit Line Commend End (if in comment): --_> -> -->
  • Shellscript, Dotenv, Gitignore, Yaml
    • Ext: sh, env, gitignore, yaml, yml
    • Single Line Comment: #
  • LaTeX
    • Ext: tex, bst, cls
    • Single Line Comment: %
  • CSS
    • Ext: css
    • Multi Line Comment: /*, */,
    • Replace Mulit Line Commend End (if in comment): *_/ -> */

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 Kommentaren
  • move: 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:
    // @gen *.solution:start
    ...
    // @gen *.solution:end
    Dies ist für Dateien, die in der Projektvorlage enthalten sind.
  • 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.