145 lines
4 KiB
Bash
Executable file
145 lines
4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# mm-pdf generate — render Markdown → HTML → PDF using nasr-m2o container
|
|
set -euo pipefail
|
|
|
|
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
STYLE="${MM_PDF_STYLE:-dark}"
|
|
OUT=""
|
|
OUT_HTML=""
|
|
CONTAINER=""
|
|
INPUT=""
|
|
|
|
usage() {
|
|
echo "Usage: mm-pdf generate <input.md> [--out output.pdf] [--style dark|purple|light] [--container NAME]"
|
|
exit 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--out) OUT="$2"; shift 2 ;;
|
|
--style) STYLE="$2"; shift 2 ;;
|
|
--container) CONTAINER="$2"; shift 2 ;;
|
|
--help|-h) usage ;;
|
|
*) INPUT="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$INPUT" ]] && usage
|
|
[[ ! -f "$INPUT" ]] && { echo "Error: $INPUT not found"; exit 1; }
|
|
|
|
INPUT_ABS="$(realpath "$INPUT")"
|
|
BASENAME="${INPUT_ABS%.md}"
|
|
OUT="${OUT:-${BASENAME}.pdf}"
|
|
OUT_ABS="$(realpath -m "$OUT")"
|
|
OUT_HTML_ABS="${OUT_ABS%.pdf}.html"
|
|
|
|
# Auto-discover container
|
|
if [[ -z "$CONTAINER" ]]; then
|
|
CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "nasr-m2o|m2o" | head -1)
|
|
[[ -z "$CONTAINER" ]] && { echo "Error: no m2o container running. Start nasr-m2o or pass --container"; exit 1; }
|
|
fi
|
|
echo "Using container: $CONTAINER"
|
|
|
|
# Copy files into container
|
|
TMP="/tmp/mm-pdf-$$"
|
|
docker exec "$CONTAINER" mkdir -p "$TMP"
|
|
docker cp "$INPUT_ABS" "$CONTAINER:$TMP/input.md"
|
|
|
|
if [[ -f "$SKILL_DIR/templates/${STYLE}.css" ]]; then
|
|
docker cp "$SKILL_DIR/templates/${STYLE}.css" "$CONTAINER:$TMP/style.css"
|
|
else
|
|
docker cp "$SKILL_DIR/templates/dark.css" "$CONTAINER:$TMP/style.css"
|
|
fi
|
|
|
|
# Step 1: Markdown → styled HTML (via Node/marked)
|
|
echo "Step 1: Converting Markdown → HTML..."
|
|
docker exec "$CONTAINER" bash -c "
|
|
cd $TMP
|
|
# Install marked locally in tmp dir so require() can find it
|
|
[ -d node_modules/marked ] || npm install marked --prefix . -q 2>/dev/null
|
|
|
|
node - <<'JSEOF'
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { marked } = require('marked');
|
|
|
|
const mdRaw = fs.readFileSync('input.md', 'utf8');
|
|
const css = fs.readFileSync('style.css', 'utf8');
|
|
|
|
// Strip YAML frontmatter
|
|
let body = mdRaw;
|
|
const fm = {};
|
|
const fmMatch = mdRaw.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
if (fmMatch) {
|
|
fmMatch[1].split('\n').forEach(line => {
|
|
const i = line.indexOf(':');
|
|
if (i > 0) fm[line.slice(0,i).trim().toLowerCase()] = line.slice(i+1).trim();
|
|
});
|
|
body = mdRaw.slice(fmMatch[0].length);
|
|
}
|
|
|
|
// Extract first H1 as title
|
|
let title = fm.title || '';
|
|
if (!title) {
|
|
const h1 = body.match(/^#\s+(.+)$/m);
|
|
if (h1) { title = h1[1].trim(); body = body.replace(h1[0], ''); }
|
|
}
|
|
title = title || 'Machine.Machine';
|
|
const subtitle = fm.subtitle || fm.description || '';
|
|
const dateLine = [fm.client, fm.date].filter(Boolean).join(' \u00a0·\u00a0 ');
|
|
|
|
marked.setOptions({ gfm: true, breaks: false });
|
|
const contentHtml = marked.parse(body);
|
|
|
|
const coverHtml = \`<div class=\"cover\">
|
|
<div class=\"logo\">MACHINE.MACHINE</div>
|
|
<div>
|
|
<h1 class=\"title\">\${title}</h1>
|
|
\${subtitle ? '<div class=\"subtitle\">' + subtitle + '</div>' : ''}
|
|
</div>
|
|
\${dateLine ? '<div class=\"meta\">' + dateLine + '</div>' : ''}
|
|
</div>\`;
|
|
|
|
const html = \`<!DOCTYPE html>
|
|
<html lang=\"en\">
|
|
<head>
|
|
<meta charset=\"utf-8\">
|
|
<title>\${title}</title>
|
|
<style>\${css}</style>
|
|
</head>
|
|
<body>
|
|
\${coverHtml}
|
|
<div class=\"body-content\">
|
|
\${contentHtml}
|
|
</div>
|
|
</body>
|
|
</html>\`;
|
|
|
|
fs.writeFileSync('output.html', html);
|
|
console.log('HTML written:', html.length, 'bytes');
|
|
JSEOF
|
|
"
|
|
|
|
echo "Step 2: Rendering HTML → PDF..."
|
|
docker exec "$CONTAINER" bash -c "
|
|
cd $TMP
|
|
google-chrome \
|
|
--headless \
|
|
--no-sandbox \
|
|
--disable-gpu \
|
|
--disable-web-security \
|
|
--print-to-pdf=output.pdf \
|
|
--print-to-pdf-no-header \
|
|
--run-all-compositor-stages-before-draw \
|
|
--virtual-time-budget=5000 \
|
|
'file://$TMP/output.html'
|
|
"
|
|
|
|
# Copy both outputs back
|
|
docker cp "$CONTAINER:$TMP/output.html" "$OUT_HTML_ABS"
|
|
docker cp "$CONTAINER:$TMP/output.pdf" "$OUT_ABS"
|
|
docker exec "$CONTAINER" rm -rf "$TMP"
|
|
|
|
echo ""
|
|
echo "HTML: $OUT_HTML_ABS ($(du -h "$OUT_HTML_ABS" | cut -f1))"
|
|
echo "PDF: $OUT_ABS ($(du -h "$OUT_ABS" | cut -f1))"
|