From b18c218b4e5269526d9949f6dc73ca8ab86ea35d Mon Sep 17 00:00:00 2001 From: Djeex Date: Mon, 31 Aug 2026 19:24:39 +0200 Subject: [PATCH] Add MIT license and remove the one-off content migration scripts --- scripts/convert-content.py | 284 ------------------------------------- scripts/mapping-en.py | 55 ------- 2 files changed, 339 deletions(-) delete mode 100644 scripts/convert-content.py delete mode 100644 scripts/mapping-en.py diff --git a/scripts/convert-content.py b/scripts/convert-content.py deleted file mode 100644 index 6b9320c..0000000 --- a/scripts/convert-content.py +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env python3 -""" -One-shot converter: old docudjeex (Docus v3 / @nuxt-themes/docus MDC) markdown --> new docudjeex-v2-docusv4 (Docus v4 / Nuxt UI MDC) markdown. - -Docus v4 kept the same MDC block-component *syntax* (::name{props} ... ::) -but swapped the component *vocabulary* to Nuxt UI components: - ::alert{type=info} -> ::note - ::alert{type=success} ("Tip:") -> ::tip - ::alert{type=warning} -> ::warning - ::alert{type=danger} -> ::caution - :::list{type=} (nested in alert) -> unwrapped, passthrough - ::card{icon=} #title #description -> ::card{icon="i-x" title="T"} description :: - ::card-grid ... #title #default -> "### Title" + ::card-group ... :: - ::terminal (yaml content: list) -> ```console fenced block - ::code-group -> unchanged (same syntax in both versions) - :ellipsis{...} -> dropped (Docus v3-only decoration) - [text]{style="..."} -> unchanged (generic MDC inline syntax) - icon `prefix:name` -> `i-prefix-name` (Nuxt Icon: full Iconify catalog) - -Usage: convert-content.py -""" -import re -import sys -from pathlib import Path - -FENCE_OPEN = re.compile(r'^(\s*)(:{2,})([\w-]+)(\{(.*)\})?\s*$') -FENCE_CLOSE = re.compile(r'^\s*(:{2,})\s*$') - - -def convert_icon(raw): - raw = raw.strip() - if ':' not in raw: - return raw - prefix, name = raw.split(':', 1) - return f"i-{prefix}-{name}" - - -def parse_props(props_str): - props = {} - if not props_str: - return props - for m in re.finditer(r'([\w-]+)=("([^"]*)"|(\S+))', props_str): - key = m.group(1) - val = m.group(3) if m.group(3) is not None else m.group(4) - props[key] = val - return props - - -class Node: - def __init__(self, kind, name=None, props=None): - self.kind = kind - self.name = name - self.props = props or {} - self.children = [] - - -def parse_blocks(lines): - root = Node('component', name='__root__') - stack = [root] - text_buf = [] - - def flush_text(): - if text_buf: - n = Node('text') - n.children = text_buf[:] - stack[-1].children.append(n) - text_buf.clear() - - for line in lines: - close_m = FENCE_CLOSE.match(line) - open_m = FENCE_OPEN.match(line) - if close_m and len(stack) > 1: - flush_text() - stack.pop() - continue - if open_m: - flush_text() - name = open_m.group(3) - props = parse_props(open_m.group(5)) - node = Node('component', name=name, props=props) - stack[-1].children.append(node) - stack.append(node) - continue - text_buf.append(line) - flush_text() - return root - - -def indent(text, n=2): - pad = ' ' * n - return '\n'.join(pad + line if line.strip() else line for line in text.split('\n')) - - -def render_children(node): - return '\n'.join(render_node(c) for c in node.children) - - -def split_slots(node): - slots = {'__default__': []} - current = '__default__' - for c in node.children: - if c.kind == 'text': - for line in c.children: - m = re.match(r'^\s*#(\w+)\s*$', line) - if m: - current = m.group(1) - slots.setdefault(current, []) - else: - slots[current].append(line) - else: - slots.setdefault(current, []) - slots[current].append(c) - return slots - - -def render_slot(slot_items): - parts, buf = [], [] - for item in slot_items: - if isinstance(item, str): - buf.append(item) - else: - if buf: - parts.append('\n'.join(buf)) - buf = [] - parts.append(render_node(item)) - if buf: - parts.append('\n'.join(buf)) - return '\n'.join(parts).strip('\n') - - -ALERT_TYPE_MAP = { - 'info': 'note', - 'success': 'tip', # Docus's "success" was always used for "✨ Tip:" callouts - 'warning': 'warning', - 'danger': 'caution', -} - - -def render_alert(node): - atype = node.props.get('type', 'info') - new_name = ALERT_TYPE_MAP.get(atype, 'note') - body = render_children(node).strip('\n') - return f"::{new_name}\n{body}\n::" - - -def render_list(node): - return render_children(node).strip('\n') - - -def render_card(node): - slots = split_slots(node) - icon = convert_icon(node.props.get('icon', '')) - title = render_slot(slots.get('title', [])).strip() - title = re.sub(r'^__(.*)__$', r'\1', title) - desc = render_slot(slots.get('description', [])).strip() - props = [] - if icon: - props.append(f'icon="{icon}"') - if title: - title_escaped = title.replace('"', '\\"') - props.append(f'title="{title_escaped}"') - props_str = '{' + ' '.join(props) + '}' if props else '' - return f"::card{props_str}\n{desc}\n::" - - -def render_card_grid(node): - slots = split_slots(node) - title = render_slot(slots.get('title', [])).strip() - default_items = slots.get('default', []) - cards = [render_node(c) for c in default_items if not isinstance(c, str)] - out = [] - if title: - out.append(f"### {title}") - out.append("") - out.append("::card-group") - out.append('\n\n'.join(cards)) - out.append("::") - return '\n'.join(out) - - -def render_terminal(node): - text = render_children(node) - m = re.search(r'content:\s*\n((?:\s*-.*\n?)+)', text) - lines_out = [] - if m: - for item in re.finditer(r'^\s*-\s*(.*)$', m.group(1), re.M): - lines_out.append(item.group(1)) - return "```console\n" + '\n'.join(lines_out) + "\n```" - - -def render_code_group(node): - # Same syntax in both versions: reconstruct verbatim. - body = render_children(node).strip('\n') - return f"::code-group\n{body}\n::" - - -RENDERERS = { - 'alert': render_alert, - 'list': render_list, - 'card': render_card, - 'card-grid': render_card_grid, - 'terminal': render_terminal, - 'code-group': render_code_group, -} - - -def render_node(node): - if node.kind == 'text': - return '\n'.join(node.children) - if node.name == '__root__': - return render_children(node) - fn = RENDERERS.get(node.name) - if fn is None: - inner = render_children(node) - return f"\n{inner}\n" - return fn(node) - - -def strip_ellipsis(text): - return re.sub(r'^\s*:ellipsis\{[^}]*\}\s*\n?', '', text, flags=re.M) - - -LIST_START_RE = re.compile(r'^\s*([-*]\s|\d+\.\s)') - - -def fix_list_starts(text): - """Nuxt Content's remark-based parser is generally lenient, but keep this - guard (proved necessary for the Zensical/Python-Markdown port) in case a - list-directly-after-text pattern needs it here too.""" - lines = text.split('\n') - out = [] - for line in lines: - if LIST_START_RE.match(line) and out and out[-1].strip() != '' and not LIST_START_RE.match(out[-1]): - out.append('') - out.append(line) - return '\n'.join(out) - - -def convert_frontmatter(fm_text): - keep = {} - for m in re.finditer(r'^(title|description):\s*(.*)$', fm_text, re.M): - keep[m.group(1)] = m.group(2) - icon_m = re.search(r'^icon:\s*(\S+)', fm_text, re.M) - icon_line = f"navigation:\n icon: {convert_icon(icon_m.group(1))}\n" if icon_m else "" - lines = ['---'] - if 'title' in keep: - lines.append(f"title: {keep['title']}") - if 'description' in keep: - lines.append(f"description: {keep['description']}") - if icon_line: - lines.append(icon_line.rstrip('\n')) - lines.append('---') - return '\n'.join(lines) - - -def convert_file(src_path: Path, dest_path: Path): - raw = src_path.read_text(encoding='utf-8') - fm_match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)$', raw, re.S) - if fm_match: - fm_text, body = fm_match.group(1), fm_match.group(2) - new_fm = convert_frontmatter(fm_text) - else: - new_fm, body = '', raw - - body = strip_ellipsis(body) - # Inline icon props like {icon=logos:docker-icon} outside of the ::card - # renderer (none expected after conversion, but harmless if present). - root = parse_blocks(body.split('\n')) - converted = render_node(root) - converted = fix_list_starts(converted) - converted = re.sub(r'\n{3,}', '\n\n', converted) - - dest_path.parent.mkdir(parents=True, exist_ok=True) - out = (new_fm + '\n\n' + converted.strip() + '\n') if new_fm else (converted.strip() + '\n') - dest_path.write_text(out, encoding='utf-8') - - return 'UNCONVERTED' in converted - - -if __name__ == '__main__': - src, dest = Path(sys.argv[1]), Path(sys.argv[2]) - if convert_file(src, dest): - print(f"WARNING: unconverted constructs remain in {dest}") diff --git a/scripts/mapping-en.py b/scripts/mapping-en.py deleted file mode 100644 index 150f7bd..0000000 --- a/scripts/mapping-en.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Old content/ path -> new content/en/ path mapping. - -Unlike the Zensical port, Docus v4 keeps the same numeric-prefix -auto-navigation convention as the old site, so paths are reused almost -verbatim (just rooted under content/en/, and index.md must sit exactly at -the locale root per Docus v4's content.config.ts landing-page convention). -""" - -MAPPING = [ - ("0.index.md", "index.md"), - ("1.about/1.welcome.md", "1.about/1.welcome.md"), - ("2.general/1.networking/1.nat.md", "2.general/1.networking/1.nat.md"), - ("2.general/1.networking/2.dns.md", "2.general/1.networking/2.dns.md"), - ("2.general/1.networking/3.samba.md", "2.general/1.networking/3.samba.md"), - ("2.general/2.storage/1.raid.md", "2.general/2.storage/1.raid.md"), - ("2.general/2.storage/2.zfs.md", "2.general/2.storage/2.zfs.md"), - ("2.general/3.hardware/1.basics.md", "2.general/3.hardware/1.basics.md"), - ("2.general/3.hardware/2.network.md", "2.general/3.hardware/2.network.md"), - ("2.general/3.hardware/3.prolonas.md", "2.general/3.hardware/3.prolonas.md"), - ("3.serveex/1.introduction.md", "3.serveex/1.introduction.md"), - ("3.serveex/2.core/1.installation.md", "3.serveex/2.core/1.installation.md"), - ("3.serveex/2.core/2.docker.md", "3.serveex/2.core/2.docker.md"), - ("3.serveex/2.core/3.swag.md", "3.serveex/2.core/3.swag.md"), - ("3.serveex/3.security/1.wireguard.md", "3.serveex/3.security/1.wireguard.md"), - ("3.serveex/3.security/2.authentik.md", "3.serveex/3.security/2.authentik.md"), - ("3.serveex/3.security/3.cloudflare.md", "3.serveex/3.security/3.cloudflare.md"), - ("3.serveex/4.monitoring/1.uptime-kuma.md", "3.serveex/4.monitoring/1.uptime-kuma.md"), - ("3.serveex/4.monitoring/2.dozzle.md", "3.serveex/4.monitoring/2.dozzle.md"), - ("3.serveex/4.monitoring/3.speedtest-tracker.md", "3.serveex/4.monitoring/3.speedtest-tracker.md"), - ("3.serveex/4.monitoring/4.beszel.md", "3.serveex/4.monitoring/4.beszel.md"), - ("3.serveex/4.monitoring/5.upsnap.md", "3.serveex/4.monitoring/5.upsnap.md"), - ("3.serveex/5.media/1.plex.md", "3.serveex/5.media/1.plex.md"), - ("3.serveex/5.media/2.qbittorrent.md", "3.serveex/5.media/2.qbittorrent.md"), - ("3.serveex/5.media/3.servarr.md", "3.serveex/5.media/3.servarr.md"), - ("3.serveex/6.cloud/1.immich.md", "3.serveex/6.cloud/1.immich.md"), - ("3.serveex/6.cloud/2.nextcloud.md", "3.serveex/6.cloud/2.nextcloud.md"), - ("3.serveex/7.files/1.file-browser.md", "3.serveex/7.files/1.file-browser.md"), - ("3.serveex/7.files/2.pingvin.md", "3.serveex/7.files/2.pingvin.md"), - ("3.serveex/8.development/1.code-server.md", "3.serveex/8.development/1.code-server.md"), - ("3.serveex/8.development/2.gitea.md", "3.serveex/8.development/2.gitea.md"), - ("3.serveex/8.development/3.it-tools.md", "3.serveex/8.development/3.it-tools.md"), - ("3.serveex/9.apps/1.adguard.md", "3.serveex/9.apps/1.adguard.md"), - ("3.serveex/9.apps/2.vaultwarden.md", "3.serveex/9.apps/2.vaultwarden.md"), - ("4.stockeex/1.introduction.md", "4.stockeex/1.introduction.md"), - ("5.nonsense/1.python/1.nvidia-stock-bot.md", "5.nonsense/1.python/1.nvidia-stock-bot.md"), - ("5.nonsense/1.python/2. adguard-cidre.md", "5.nonsense/1.python/2.adguard-cidre.md"), - ("5.nonsense/1.python/3.lumeex.md", "5.nonsense/1.python/3.lumeex.md"), - ("5.nonsense/1.python/4.instameex.md", "5.nonsense/1.python/4.instameex.md"), - ("5.nonsense/2.bash/1.servarr-duplicates.md", "5.nonsense/2.bash/1.servarr-duplicates.md"), - ("5.nonsense/2.bash/2.luks- backup.md", "5.nonsense/2.bash/2.luks-backup.md"), - ("5.nonsense/2.bash/3.socat-proxy.md", "5.nonsense/2.bash/3.socat-proxy.md"), - ("5.nonsense/2.bash/4.hotdisk.md", "5.nonsense/2.bash/4.hotdisk.md"), - ("5.nonsense/2.bash/5.backrest-docker-stop.md", "5.nonsense/2.bash/5.backrest-docker-stop.md"), - ("Recycled/1.deprecated/1.wireguard-14.md", "6.recycled/1.deprecated/1.wireguard-14.md"), -]