diff --git a/.DS_Store b/.DS_Store index dc8b856..f912345 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/ascii/ascii-logo.md b/ascii/ascii-logo.md new file mode 100644 index 0000000..7598ce1 --- /dev/null +++ b/ascii/ascii-logo.md @@ -0,0 +1,28 @@ + + + &&&& &&&&& + +xX&&XX+++x + &&$x+xX++x+:; + &;;:$&;:;X&& + ::::::::::; + &&$$$$$$$$$$$;::::x::+:; + &&XXX;::::::::::::::::::xxx + &&$x+x;::::::::::::::::::X& + &&X+++x;::::::::::::::::::X& + &&Xxxxx;::::::::::::::::::X& + &&&&&&+;::++;::;++;::;++:x&& + &$::&&X:;&&&&;:X&&::X& + &&xX&&$:;&&&&Xx&&&:;X& + &&&&&&&&& &&&& &&&& + + +██╗ ██╗███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██╗ ██████╗ ██╗ ██╗███╗ ███╗ +██║ ██║██╔════╝██╔════╝ ██╔═══██╗██╔════╝ ██╔══██╗██║ ██║██║██║ ██╔══██╗ ██║ ██║████╗ ████║ +███████║█████╗ ██║ ███╗ ██║ ██║███████╗ ██████╔╝██║ ██║██║██║ ██║ ██║ ██║ ██║██╔████╔██║ +██╔══██║██╔══╝ ██║ ██║ ██║ ██║╚════██║ ██╔══██╗██║ ██║██║██║ ██║ ██║ ╚██╗ ██╔╝██║╚██╔╝██║ +██║ ██║██║ ╚██████╔╝███████╗╚██████╔╝███████║ ██████╔╝╚██████╔╝██║███████╗██████╔╝ ╚████╔╝ ██║ ╚═╝ ██║ +╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝ ╚═══╝ ╚═╝ ╚═╝ + +DEBIAN 13 VM auf w4d-gpu unterm Schreibtisch + + diff --git a/cms.py b/cms.py new file mode 100755 index 0000000..61612d3 --- /dev/null +++ b/cms.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +"""Mini-CMS: MD-Dateien in md/ per Browseroberflaeche bearbeiten +und als statische Seite (HTML + CSS) nach output/ exportieren. +Nur Python-Standardbibliothek, kein Internet noetig. + +Aufruf: + python3 cms.py -> Server + UI starten (http://localhost:8500) + python3 cms.py export -> nur exportieren, kein Server + python3 cms.py port 9000 -> Server auf anderem Port +""" + +import json +import os +import re +import shutil +import sys +import threading +import webbrowser +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from urllib.parse import unquote, urlparse + +ROOT = os.path.dirname(os.path.abspath(__file__)) +MD_DIR = os.path.join(ROOT, "md") +TPL_DIR = os.path.join(ROOT, "templates") +CSS_DIR = os.path.join(ROOT, "css") +OUT_DIR = os.path.join(ROOT, "output") +PORT = 8500 + +for d in (MD_DIR, TPL_DIR, CSS_DIR, OUT_DIR): + os.makedirs(d, exist_ok=True) + +SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9_-]*$", re.IGNORECASE) +START_PAGE = "startseite" +FOOTER_PAGE = "footer" # Baustein: wird in jede Seite eingefuegt, nicht eigenstaendig exportiert + + +# ---------------------------------------------------------------- markdown + +def md_to_html(text): + """Minimaler Markdown->HTML-Konverter: + Ueberschriften, **fett**, *kursiv*, `code`, [Links](url), + Listen, Code-Bloecke, > Zitate, --- Trennlinien, Absaetze. + """ + lines = text.replace("\r\n", "\n").split("\n") + out = [] + i = 0 + in_code = False + in_list = False + list_tag = None + + def inline(s): + s = re.sub(r"`([^`]+)`", r"\1", s) + s = re.sub(r"\*\*([^*]+)\*\*", r"\1", s) + s = re.sub(r"\*([^*]+)\*", r"\1", s) + s = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'\1', s) + return s + + def close_list(): + nonlocal in_list, list_tag + if in_list: + out.append("" % list_tag) + in_list = False + list_tag = None + + while i < len(lines): + line = lines[i] + + if line.strip().startswith("```"): + close_list() + if not in_code: + out.append("
")
+                in_code = True
+            else:
+                out.append("
") + in_code = False + i += 1 + continue + + if in_code: + out.append(_escape(line)) + i += 1 + continue + + stripped = line.strip() + + if not stripped: + close_list() + i += 1 + continue + + m = re.match(r"^(#{1,6})\s+(.*)$", stripped) + if m: + close_list() + level = len(m.group(1)) + out.append("%s" % (level, inline(_escape(m.group(2))), level)) + i += 1 + continue + + if re.match(r"^-{3,}$", stripped): + close_list() + out.append("
") + i += 1 + continue + + m = re.match(r"^([-*+])\s+(.*)$", stripped) + if m: + if not in_list or list_tag != "ul": + close_list() + out.append("