Skip to content

Commit 64afae1

Browse files
authored
Auto-generate docs
Feature/generate html tables
2 parents 56cbf18 + ea30713 commit 64afae1

File tree

10 files changed

+3257
-239
lines changed

10 files changed

+3257
-239
lines changed

README.md

Lines changed: 3032 additions & 190 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@
3838
},
3939
"scripts": {
4040
"publish": "vsce package && vsce publish",
41-
"generate": "deno run --allow-write --allow-read src/app.ts",
42-
"generate:table": "deno run --allow-write --allow-read src/app.ts --table --snippets=false",
43-
"generate:all": "deno run --allow-write --allow-read src/app.ts --table --snippets"
41+
"generate": "deno run -A src/app.ts --snippets --docs",
42+
"generate:snippets": "deno run -A src/app.ts --snippets"
4443
},
4544
"contributes": {
4645
"snippets": [

src/app.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,43 @@
11
import { parse } from "./deps.ts";
2-
import { VscSnippetDefinition } from "./models/app.ts";
3-
import { variants } from "./snippets/app.ts";
4-
import { logTables } from "./utils/markdown.ts";
2+
import { generateDocs, populateDocsBlock } from "./docs-gen/snippets.ts";
3+
import { languages } from "./snippets/app.ts";
54
import {
65
convertToVscSnippet,
7-
generateSnippetsFile,
6+
generateSnippets,
87
groupSnippets,
98
} from "./utils/snippets.ts";
109

1110
const flags = parse(Deno.args, {
12-
boolean: ["table", "snippets"],
13-
default: { snippets: true },
11+
boolean: ["snippets", "docs"],
12+
default: { snippets: false, docs: false },
1413
});
1514

16-
if (!flags.table && !flags.snippets) {
17-
console.log("Please specify at least one flag: --table or --snippets");
15+
if (!flags.snippets && !flags.docs) {
16+
console.log("Please specify at least one flag: --snippets or --docs");
1817
} else {
19-
variants.forEach((variant) => {
20-
const categorizedVscSnippets: VscSnippetDefinition[] = variant
21-
.snippetsWithMeta.map(
22-
(item) => ({
23-
...item,
24-
snippets: convertToVscSnippet(item.snippets),
25-
}),
26-
);
18+
if (flags.snippets) {
19+
console.log("\nGenerating snippets...");
20+
languages.forEach((language) => {
21+
const categorizedVscSnippets = language
22+
.snippetDefinitions.map(
23+
(item) => {
24+
const snippets = convertToVscSnippet(item.snippets);
25+
return { ...item, snippets };
26+
},
27+
);
2728

28-
if (flags.table) {
29-
logTables(variant.label, categorizedVscSnippets);
30-
}
31-
if (flags.snippets) {
3229
const variantVscSnippet = groupSnippets(
3330
categorizedVscSnippets.map((item) => item.snippets),
3431
);
35-
generateSnippetsFile(variant.extension, variantVscSnippet);
36-
}
37-
});
32+
generateSnippets(language.fileExtension, variantVscSnippet);
33+
});
34+
}
35+
36+
// TODO: probably better to make it generate from vsc json
37+
// pass in meta, and snippets converted to vsc format
38+
if (flags.docs) {
39+
console.log("\nGenerating docs...");
40+
const docs = generateDocs(languages);
41+
populateDocsBlock(docs);
42+
}
3843
}

src/deps.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
export { parse } from "https://deno.land/std@0.168.0/flags/mod.ts";
21
export { ensureDirSync } from "https://deno.land/std@0.141.0/fs/ensure_dir.ts";
2+
export { parse } from "https://deno.land/std@0.168.0/flags/mod.ts";
33
export { markdownTable } from "https://esm.sh/markdown-table@3";
4+
5+
import replace, * as _replace from "npm:replace-in-file";
6+
7+
// Fix types
8+
export const replaceInFile = replace as unknown as (
9+
config: _replace.ReplaceInFileConfig,
10+
) => Promise<_replace.ReplaceResult[]>;

src/docs-gen/snippets.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { replaceInFile } from "../deps.ts";
2+
import { XSnippetDefinition, XSnippetVariant } from "../models/app.ts";
3+
import {
4+
$col,
5+
$colCode,
6+
$colCodeBlock,
7+
$row,
8+
$table,
9+
htmlComment,
10+
joinByDoubleNewLine,
11+
joinByNewLine,
12+
} from "./table-html.ts";
13+
14+
type SnippetRow = {
15+
prefix: string;
16+
name: string;
17+
body: string | string[];
18+
};
19+
20+
const snippetRow = ({ prefix, name, body }: SnippetRow) => {
21+
const parsedBody = Array.isArray(body) ? body.join("\n") : body;
22+
const cols = joinByNewLine([
23+
$colCode(prefix),
24+
$col(name),
25+
$colCodeBlock(parsedBody),
26+
]);
27+
28+
return $row(cols);
29+
};
30+
31+
const generateSnippetTable = (items: SnippetRow[]) => {
32+
const headings = ["Prefix", "Name", "Body"];
33+
const rows = items.map(snippetRow);
34+
35+
return $table(headings, rows);
36+
};
37+
38+
const generateSnippetSection = (
39+
{ meta, snippets }: XSnippetDefinition,
40+
) => {
41+
const title = `### ${meta.title}`;
42+
const description = meta.description ?? "";
43+
const table = generateSnippetTable(
44+
Object.entries(snippets).map(([prefix, value]) => ({
45+
...value,
46+
prefix,
47+
})),
48+
);
49+
50+
return joinByNewLine([title, description, table, ""]);
51+
};
52+
53+
const generateVariantSection = (variant: XSnippetVariant) => {
54+
const title = `## ${variant.label}`;
55+
const description = variant.description ?? "";
56+
const sections = variant.snippetDefinitions.map(generateSnippetSection);
57+
58+
return joinByNewLine([title, description, "", ...sections]);
59+
};
60+
61+
export const generateDocs = (variants: XSnippetVariant[]) => {
62+
return joinByDoubleNewLine(variants.map(generateVariantSection));
63+
};
64+
65+
const docsGenId = "docs-gen";
66+
const docsGen = {
67+
start: htmlComment(`START:${docsGenId}`),
68+
end: htmlComment(`END:${docsGenId}`),
69+
};
70+
71+
const docsBlock = (s: string) => {
72+
return joinByNewLine([docsGen.start, s, docsGen.end]);
73+
};
74+
75+
export const populateDocsBlock = async (input: string) => {
76+
const regex = new RegExp(
77+
`${docsGen.start}[\\s\\S]*?${docsGen.end}`,
78+
"g",
79+
);
80+
81+
const file = "./README.md";
82+
const options = {
83+
files: file,
84+
from: regex,
85+
to: docsBlock(input),
86+
};
87+
88+
try {
89+
const results = await replaceInFile(options);
90+
const readmeResult = results.find((r) => r.file === file);
91+
92+
if (readmeResult?.hasChanged) {
93+
console.log("✅ README updated");
94+
} else {
95+
console.log("👍 README already up to date");
96+
}
97+
} catch (error) {
98+
console.error("Error while updating README:", error);
99+
}
100+
};

src/docs-gen/table-html.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export const joinInline = (s: string[]) => s.join("");
2+
export const joinByNewLine = (s: string[]) => s.join("\n");
3+
export const joinByDoubleNewLine = (s: string[]) => s.join("\n\n");
4+
export const indent = (s: string, size = 2) => `${" ".repeat(size)}${s}`;
5+
export const escapeBackticks = (s: string) => s.replace(/`/g, "\`");
6+
7+
export const htmlComment = (s: string) => `<!-- ${s} -->`;
8+
export const code = (s: string) => {
9+
return escapeBackticks("`" + s + "`");
10+
};
11+
12+
export const codeBlock = (s: string, lang = "javascript") => {
13+
return joinByNewLine([
14+
`${indent(escapeBackticks("```" + lang))}`,
15+
s,
16+
`${indent(escapeBackticks("```"))}`,
17+
]);
18+
};
19+
20+
export const $row = (s: string) => {
21+
return joinByNewLine(["", "<tr>", s, "</tr>"]);
22+
};
23+
24+
export const $colDoubleNewLine = (
25+
s: string,
26+
cb?: (input: string) => string,
27+
) => {
28+
return joinByDoubleNewLine(["<td>", cb?.(s) ?? s, "</td>"]);
29+
};
30+
31+
export const $col = (s: string) => {
32+
return `<td>${s}</td>`;
33+
};
34+
export const $colCode = (s: string) => {
35+
return $colDoubleNewLine(s, code);
36+
};
37+
export const $colCodeBlock = (s: string) => {
38+
return $colDoubleNewLine(s, codeBlock);
39+
};
40+
41+
export const $headerRow = (headers: string[]) => {
42+
const cols = joinByNewLine(headers.map($col));
43+
return $row(cols);
44+
};
45+
46+
export const $table = (headings: string[], rows: string[]) => {
47+
return joinByNewLine([
48+
"<table>",
49+
$headerRow(headings),
50+
joinByNewLine(rows),
51+
"</table>",
52+
]);
53+
};

src/utils/markdown.ts renamed to src/docs-gen/table-md.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { VscSnippetDefinition, VscSnippetDict } from "../models/app.ts";
21
import { markdownTable } from "../deps.ts";
3-
import { replaceSymbol } from "./general.ts";
2+
import { VscSnippetDefinition, VscSnippetDict } from "../models/app.ts";
3+
import { replaceSymbol } from "../utils/general.ts";
44

55
export const code = (str: string) => `\`${str}\``;
66

@@ -13,7 +13,8 @@ export const serializeForMarkdown = (str: string) => {
1313
.replace(/\t/g, "&nbsp;&nbsp;")
1414
.replace(/\|/g, "\\|");
1515
}
16-
// TODO: dont remove | when it is in ``
16+
// TODO: don't remove | when it is in code block
17+
// but it differs for every .md implementation
1718
return str.replace(/\|/g, "\\|");
1819
};
1920

src/models/app.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ export type GenericSnippetDictWithMeta<T> = {
1919
};
2020
export type XSnippetDefinition = GenericSnippetDictWithMeta<XSnippetDict>;
2121
export type VscSnippetDefinition = GenericSnippetDictWithMeta<VscSnippetDict>;
22+
23+
export type XSnippetVariant = {
24+
label: string;
25+
description?: string;
26+
language: string;
27+
fileExtension: string;
28+
snippetDefinitions: XSnippetDefinition[];
29+
};

src/snippets/app.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
import { XSnippetDefinition } from "../models/app.ts";
1+
import { XSnippetVariant } from "../models/app.ts";
22
import { javascript } from "./js/app.ts";
33
import { typescript } from "./ts/app.ts";
44

5-
type SnippetVariant = {
6-
label: string;
7-
language: string;
8-
extension: string;
9-
snippetsWithMeta: XSnippetDefinition[];
10-
};
11-
export const variants: SnippetVariant[] = [
5+
export const languages: XSnippetVariant[] = [
126
{
137
label: "Snippets",
148
language: "javascript",
15-
extension: "js",
16-
snippetsWithMeta: javascript,
9+
fileExtension: "js",
10+
snippetDefinitions: javascript,
1711
},
1812
{
1913
label: "TypeScript specific",
14+
description: "Only applied to .ts and .tsx files",
2015
language: "typescript",
21-
extension: "ts",
22-
snippetsWithMeta: typescript,
16+
fileExtension: "ts",
17+
snippetDefinitions: typescript,
2318
},
2419
];

src/utils/snippets.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@ export const groupSnippets = (dicts: VscSnippetDict[]) => {
1717
}));
1818
};
1919

20-
export const generateSnippetsFile = (name: string, data: VscSnippetDict) => {
20+
export const generateSnippets = (name: string, data: VscSnippetDict) => {
2121
const path = "./dist";
22-
ensureDirSync(path);
23-
const file = `${path}/${name}.code-snippets`;
22+
const fileName = `${name}.code-snippets`;
23+
try {
24+
ensureDirSync(path);
25+
const file = `${path}/${fileName}`;
2426

25-
Deno.writeTextFileSync(
26-
file,
27-
JSON.stringify(data, null, 2),
28-
);
27+
Deno.writeTextFileSync(
28+
file,
29+
JSON.stringify(data, null, 2),
30+
);
31+
32+
console.log(`✅ ${fileName}`);
33+
} catch (error) {
34+
console.log(`❌ ${fileName}`);
35+
console.error(error);
36+
}
2937
};

0 commit comments

Comments
 (0)