1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.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 | */
|
62 | const fs = require("node:fs");
|
63 | const path = require("node:path");
|
64 | /**
|
65 | * Convert an annotated TypeScript source file to MarkDown
|
66 | */
|
67 | function typescriptSourceToMarkdown(lines, codeBlockAnnotations) {
|
68 | const relevantLines = findRelevantLines(lines);
|
69 | const markdownLines = markdownify(relevantLines, codeBlockAnnotations);
|
70 | return markdownLines;
|
71 | }
|
72 | exports.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 | */
|
81 | function 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 | }
|
102 | exports.includeAndRenderExamples = includeAndRenderExamples;
|
103 | /**
|
104 | * Load a file into a string array
|
105 | */
|
106 | function loadFromFile(fileName) {
|
107 | const content = fs.readFileSync(fileName, { encoding: 'utf-8' });
|
108 | return contentToLines(content);
|
109 | }
|
110 | exports.loadFromFile = loadFromFile;
|
111 | /**
|
112 | * Turn file content string into an array of lines ready for processing using the other functions
|
113 | */
|
114 | function contentToLines(content) {
|
115 | return content.split('\n').map((x) => x.trimRight());
|
116 | }
|
117 | exports.contentToLines = contentToLines;
|
118 | /**
|
119 | * Return a file system loader given a base directory
|
120 | */
|
121 | function fileSystemLoader(directory) {
|
122 | return (fileName) => {
|
123 | const fullPath = path.resolve(directory, fileName);
|
124 | return { fullPath, lines: loadFromFile(fullPath) };
|
125 | };
|
126 | }
|
127 | exports.fileSystemLoader = fileSystemLoader;
|
128 | const RELEVANT_TAG = '/// !show';
|
129 | const DETAIL_TAG = '/// !hide';
|
130 | const 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 | */
|
138 | function 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 | */
|
162 | function 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 | */
|
171 | function 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 | }
|
198 | function toUnixPath(x) {
|
199 | return x.replace(/\\/g, '/');
|
200 | }
|
201 | //# sourceMappingURL=literate.js.map |
\ | No newline at end of file |