Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/scripts/sync-docs.mjs
blob: c36cbdb0c30d8fa4398945e9e2e9892cee147ace (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1

// Copies the reference of Tazel from a checkout of Progmasoft/tazel into the English docs:
//
//   node scripts/sync-docs.mjs ../tazel
//
// The title of each document becomes its front matter, links between the documents become
// links between pages of the site, and links to other files of the repository go to GitHub.

import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import { dirname, join, resolve } from "node:path";

const checkout = process.argv[2];
if (!checkout) {
  console.error("usage: node scripts/sync-docs.mjs <checkout of Progmasoft/tazel>");
  process.exit(2);
}

const repository = "https://github.com/Progmasoft/tazel/blob/main";
const documents = [
  { source: "CLI.md", target: "2.reference/1.commands.md", path: "/docs/reference/commands" },
  {
    source: "PROJECT_FILES.md",
    target: "2.reference/2.project-file.md",
    path: "/docs/reference/project-file",
  },
  { source: "LOCKFILE.md", target: "2.reference/3.lockfile.md", path: "/docs/reference/lockfile" },
  { source: "PLUGINS.md", target: "3.extending/1.plugins.md", path: "/docs/extending/plugins" },
  {
    source: "ARCHITECTURE.md",
    target: "4.internals/1.architecture.md",
    path: "/docs/internals/architecture",
  },
];
const pages = new Map(documents.map((document) => [document.source, document.path]));

function link(target) {
  const [file, anchor] = target.split("#", 2);
  const suffix = anchor === undefined ? "" : `#${anchor}`;
  if (file === "") return target;
  const page = pages.get(file);
  if (page) return `${page}${suffix}`;
  if (file.startsWith("../")) return `${repository}/${file.slice(3)}${suffix}`;
  return `${repository}/Documents/${file}${suffix}`;
}

function convert(text, source) {
  const lines = text.replace(/\r\n/g, "\n").split("\n");
  const spdx = lines.filter((line) => line.startsWith("<!-- SPDX-"));
  const body = lines.filter((line) => !line.startsWith("<!-- SPDX-"));
  const titleIndex = body.findIndex((line) => line.startsWith("# "));
  if (titleIndex < 0) throw new Error(`${source} has no title`);
  const title = body[titleIndex].slice(2).replace(/`/g, "");
  body.splice(0, titleIndex + 1);
  while (body[0] === "") body.shift();
  const firstParagraph = [];
  for (const line of body) {
    if (line === "") break;
    firstParagraph.push(line);
  }
  const paragraph = firstParagraph
    .join(" ")
    .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
    .replace(/[`*]/g, "")
    .replace(/"/g, "'");
  // The first sentence describes the page to search engines.
  const description = paragraph.match(/^.*?\.(?=\s|$)/)?.[0] ?? paragraph;
  const converted = body
    .join("\n")
    .replace(/\]\(((?!https?:|mailto:|#)[^)\s]+)\)/g, (_, target) => `](${link(target)})`);
  return [
    "---",
    `title: ${JSON.stringify(title)}`,
    `description: ${JSON.stringify(description)}`,
    `source: ${JSON.stringify(`${repository}/Documents/${source}`)}`,
    "---",
    "",
    ...spdx,
    "",
    converted.trimEnd(),
    "",
  ].join("\n");
}

const root = resolve(import.meta.dirname, "../content/en-US/docs");
for (const document of documents) {
  const text = readFileSync(join(checkout, "Documents", document.source), "utf8");
  const target = join(root, document.target);
  mkdirSync(dirname(target), { recursive: true });
  writeFileSync(target, convert(text, document.source));
  console.log(`${document.source} -> content/en-US/docs/${document.target}`);
}