Skip to content

Commit e618590

Browse files
committed
Make a script to separate snippets
1 parent 4eae121 commit e618590

File tree

3 files changed

+149
-0
lines changed

3 files changed

+149
-0
lines changed

scripts/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "scripts",
3+
"version": "1.0.0",
4+
"description": "Internal repo scripts",
5+
"scripts": {
6+
},
7+
"author": "samstern@google.com",
8+
"license": "Apache-2.0"
9+
}

scripts/separate-snippets.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import * as cp from "child_process";
2+
import * as fs from "fs";
3+
import * as path from "path";
4+
5+
const RE_START_SNIPPET = /\[START\s+([A-Za-z_]+)\s*\]/;
6+
const RE_END_SNIPPET = /\[END\s+([A-Za-z_]+)\s*\]/;
7+
// TODO: Handle multiline imports?
8+
const RE_REQUIRE = /const {(.+?)} = require\((.+?)\)/;
9+
10+
function isBlank(line: string) {
11+
return line.trim().length === 0;
12+
}
13+
14+
/**
15+
* Turns a series of source lines into a standalone snippet file by:
16+
* - Converting require statements into top-level imports.
17+
* - Adjusting indentation to left-align all content
18+
* - Removing any blank lines at the starts
19+
* @param lines the lines containing the snippet (including START/END comments)
20+
*/
21+
function processSnippet(lines: string[]): string {
22+
const importLines: string[] = [];
23+
const otherLines: string[] = [];
24+
25+
for (const line of lines) {
26+
const requireMatch = line.match(RE_REQUIRE);
27+
if (requireMatch) {
28+
const asImport = `import {${requireMatch[1]}} from ${requireMatch[2]}`;
29+
importLines.push(asImport);
30+
} else {
31+
otherLines.push(line);
32+
}
33+
}
34+
35+
// Adjust indentation of the otherLines so that they're left aligned
36+
const nonBlankLines = otherLines.filter((l) => !isBlank(l));
37+
const indentSizes = nonBlankLines.map((l) => l.length - l.trimLeft().length);
38+
const minIndent = Math.min(...indentSizes);
39+
40+
const adjustedOtherLines: string[] = [];
41+
for (const line of otherLines) {
42+
if (isBlank(line)) {
43+
adjustedOtherLines.push("");
44+
} else {
45+
adjustedOtherLines.push(line.substr(minIndent));
46+
}
47+
}
48+
49+
// Special case: if the first line after the comments is blank we want to remove it
50+
const firstNonComment = adjustedOtherLines.findIndex(
51+
(l) => !l.startsWith("//")
52+
);
53+
if (isBlank(otherLines[firstNonComment])) {
54+
adjustedOtherLines.splice(firstNonComment, 1);
55+
}
56+
57+
// TODO: Should we add a preamble?
58+
const content = [...importLines, ...adjustedOtherLines].join("\n");
59+
return content;
60+
}
61+
62+
/**
63+
* Lists all the files in this repository that should be checked for snippets
64+
*/
65+
function listSnippetFiles(): string[] {
66+
const output = cp
67+
.execSync(
68+
'find . -type f -name "*.js" -not -path "*node_modules*" -not -path "./snippets*"'
69+
)
70+
.toString();
71+
return output.split("\n").filter((x) => !isBlank(x));
72+
}
73+
74+
/**
75+
* Collect all the snippets from a file into a map of snippet name to lines.
76+
* @param filePath the file path to read.
77+
*/
78+
function collectSnippets(filePath: string): { [name: string]: string[] } {
79+
const fileContents = fs.readFileSync(filePath).toString();
80+
const lines = fileContents.split("\n");
81+
82+
let currSnippetName = "";
83+
let inSnippet = false;
84+
const snippetLines: { [name: string]: string[] } = {};
85+
for (const line of lines) {
86+
const startMatch = line.match(RE_START_SNIPPET);
87+
const endMatch = line.match(RE_END_SNIPPET);
88+
89+
if (startMatch) {
90+
inSnippet = true;
91+
currSnippetName = startMatch[1];
92+
snippetLines[currSnippetName] = [];
93+
}
94+
95+
if (inSnippet) {
96+
snippetLines[currSnippetName].push(line);
97+
}
98+
99+
if (endMatch) {
100+
if (endMatch[1] !== currSnippetName) {
101+
throw new Error(
102+
`Snippet ${currSnippetName} in ${filePath} has unmatched START/END tags`
103+
);
104+
}
105+
inSnippet = false;
106+
}
107+
}
108+
109+
return snippetLines;
110+
}
111+
112+
async function main() {
113+
const fileNames = listSnippetFiles();
114+
115+
for (const filePath of fileNames) {
116+
const fileSlug = filePath
117+
.replace(".js", "")
118+
.replace("./", "")
119+
.replace(/\./g, "-");
120+
const snippetDir = path.join("./snippets", fileSlug);
121+
122+
console.log(`Processing: ${filePath} --> ${snippetDir}`);
123+
124+
if (!fs.existsSync(snippetDir)) {
125+
fs.mkdirSync(snippetDir, { recursive: true });
126+
}
127+
128+
const snippetLines = collectSnippets(filePath);
129+
for (const snippetName in snippetLines) {
130+
const filePath = path.join(snippetDir, `${snippetName}.js`);
131+
const content = processSnippet(snippetLines[snippetName]);
132+
fs.writeFileSync(filePath, content);
133+
}
134+
}
135+
}
136+
137+
main();

snippets/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Ignore this folder
2+
3+
This folder contains generated files based on the source in other folders!

0 commit comments

Comments
 (0)