UNPKG

6.34 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.fileSystemLoader = exports.contentToLines = exports.loadFromFile = exports.includeAndRenderExamples = exports.typescriptSourceToMarkdown = void 0;
4/**
5 * A tiny module to include annotated (working!) code snippets into the documentation
6 *
7 * Not using 'literate-programming' or 'erasumus' projects because they work
8 * the other way around: take code from MarkDown, save it as a file, then
9 * execute that.
10 *
11 * We do the opposite: start from source code annotated with MarkDown and
12 * extract it into (larger) MarkDown files.
13 *
14 * Including into README
15 * ---------------------
16 *
17 * To include the examples directly into the README, make a link to the
18 * annotated TypeScript file on a line by itself, and make sure the
19 * extension of the file ends in `.lit.ts`.
20 *
21 * For example:
22 *
23 * [example](test/integ.bucket.lit.ts)
24 *
25 * Annotating source
26 * -----------------
27 *
28 * We use the triple-slash comment for our directives, since it's valid TypeScript
29 * and are treated as regular comments if not the very first thing in the file.
30 *
31 * By default, the whole file is included, unless the source contains the statement
32 * "/// !show". For example:
33 *
34 * a
35 * /// !show
36 * b
37 * /// !hide
38 * c
39 *
40 * In this example, only 'b' would be included in the output. A single file may
41 * switching including and excluding on and off multiple times in the same file.
42 *
43 * Other lines starting with triple slashes will be rendered as Markdown in between
44 * the source lines. For example:
45 *
46 * const x = 1;
47 * /// Now we're going to print x:
48 * console.log(x);
49 *
50 * Will be rendered as:
51 *
52 * ```ts
53 * const x = 1;
54 * ```
55 *
56 * Now we're going to print x:
57 *
58 * ```ts
59 * console.log(x);
60 * ```
61 */
62const fs = require("node:fs");
63const path = require("node:path");
64/**
65 * Convert an annotated TypeScript source file to MarkDown
66 */
67function typescriptSourceToMarkdown(lines, codeBlockAnnotations) {
68 const relevantLines = findRelevantLines(lines);
69 const markdownLines = markdownify(relevantLines, codeBlockAnnotations);
70 return markdownLines;
71}
72exports.typescriptSourceToMarkdown = typescriptSourceToMarkdown;
73/**
74 * Given MarkDown source, find source files to include and render
75 *
76 * We recognize links on a line by themselves if the link text starts
77 * with the string "example" (case insensitive). For example:
78 *
79 * [example](test/integ.bucket.ts)
80 */
81function includeAndRenderExamples(lines, loader, projectRoot) {
82 const ret = [];
83 const regex = /^\[([^\]]*)\]\(([^)]+\.lit\.ts)\)/i;
84 for (const line of lines) {
85 const m = regex.exec(line);
86 if (m) {
87 // Found an include
88 const filename = m[2];
89 // eslint-disable-next-line no-await-in-loop
90 const { lines: source, fullPath } = loader(filename);
91 // 'lit' source attribute will make snippet compiler know to extract the same source
92 // Needs to be relative to the project root.
93 const imported = typescriptSourceToMarkdown(source, [`lit=${toUnixPath(path.relative(projectRoot, fullPath))}`]);
94 ret.push(...imported);
95 }
96 else {
97 ret.push(line);
98 }
99 }
100 return ret;
101}
102exports.includeAndRenderExamples = includeAndRenderExamples;
103/**
104 * Load a file into a string array
105 */
106function loadFromFile(fileName) {
107 const content = fs.readFileSync(fileName, { encoding: 'utf-8' });
108 return contentToLines(content);
109}
110exports.loadFromFile = loadFromFile;
111/**
112 * Turn file content string into an array of lines ready for processing using the other functions
113 */
114function contentToLines(content) {
115 return content.split('\n').map((x) => x.trimRight());
116}
117exports.contentToLines = contentToLines;
118/**
119 * Return a file system loader given a base directory
120 */
121function fileSystemLoader(directory) {
122 return (fileName) => {
123 const fullPath = path.resolve(directory, fileName);
124 return { fullPath, lines: loadFromFile(fullPath) };
125 };
126}
127exports.fileSystemLoader = fileSystemLoader;
128const RELEVANT_TAG = '/// !show';
129const DETAIL_TAG = '/// !hide';
130const INLINE_MD_REGEX = /^\s*\/\/\/ (.*)$/;
131/**
132 * Find the relevant lines of the input source
133 *
134 * Respects switching tags, returns everything if no switching found.
135 *
136 * Strips common indentation from the blocks it finds.
137 */
138function findRelevantLines(lines) {
139 let inRelevant = false;
140 let didFindRelevant = false;
141 const ret = [];
142 for (const line of lines) {
143 if (line.trim() === RELEVANT_TAG) {
144 inRelevant = true;
145 didFindRelevant = true;
146 }
147 else if (line.trim() === DETAIL_TAG) {
148 inRelevant = false;
149 }
150 else {
151 if (inRelevant) {
152 ret.push(line);
153 }
154 }
155 }
156 // Return full lines list if no switching found
157 return stripCommonIndent(didFindRelevant ? ret : lines);
158}
159/**
160 * Remove common leading whitespace from the given lines
161 */
162function stripCommonIndent(lines) {
163 const leadingWhitespace = /^(\s*)/;
164 const indents = lines.map((l) => leadingWhitespace.exec(l)[1].length);
165 const commonIndent = Math.min(...indents);
166 return lines.map((l) => l.slice(commonIndent));
167}
168/**
169 * Turn source lines into Markdown, starting in TypeScript mode
170 */
171function markdownify(lines, codeBlockAnnotations) {
172 const typescriptLines = [];
173 const ret = [];
174 for (const line of lines) {
175 const m = INLINE_MD_REGEX.exec(line);
176 if (m) {
177 // Literal MarkDown line
178 flushTS();
179 ret.push(m[1]);
180 }
181 else {
182 typescriptLines.push(line);
183 }
184 }
185 flushTS();
186 return ret;
187 /**
188 * Flush typescript lines with a triple-backtick-ts block around it.
189 */
190 function flushTS() {
191 if (typescriptLines.length !== 0) {
192 // eslint-disable-next-line prefer-template
193 ret.push(`\`\`\`ts${codeBlockAnnotations.length > 0 ? ` ${codeBlockAnnotations.join(' ')}` : ''}`, ...typescriptLines, '```');
194 typescriptLines.splice(0); // Clear
195 }
196 }
197}
198function toUnixPath(x) {
199 return x.replace(/\\/g, '/');
200}
201//# sourceMappingURL=literate.js.map
\No newline at end of file