Skip to content

Commit e41e1bc

Browse files
Adding scripts to migrate, check and consolidate snippets.
1 parent 21c1695 commit e41e1bc

File tree

6 files changed

+188
-0
lines changed

6 files changed

+188
-0
lines changed
File renamed without changes.
File renamed without changes.

utils/checkSnippetFormatting.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { exit } from 'process';
2+
import { parseAllSnippets } from './snippetParser.js';
3+
4+
const [ errored ] = parseAllSnippets();
5+
6+
if(errored) exit(1);

utils/consolidateSnippets.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { exit } from 'process';
2+
import { parseAllSnippets, reverseSlugify } from './snippetParser.js';
3+
import { join } from 'path';
4+
import { copyFileSync, writeFileSync } from 'fs';
5+
6+
const dataPath = 'public/data/';
7+
const indexPath = join(dataPath, '_index.json');
8+
const iconPath = 'public/icons/';
9+
const snippetsPath = 'snippets/';
10+
11+
const [ errored, snippets ] = parseAllSnippets();
12+
13+
if(errored) exit(1);
14+
15+
const index = [];
16+
for(const [language, categories] of Object.entries(snippets)) {
17+
const languageIconPath = join(snippetsPath, language, 'icon.svg');
18+
19+
copyFileSync(languageIconPath, join(iconPath, `${language}.svg`));
20+
21+
index.push({ lang: reverseSlugify(language), icon: `/icons/${language}.svg` });
22+
23+
const languageFilePath = join(dataPath, `${language}.json`);
24+
25+
writeFileSync(languageFilePath, JSON.stringify(categories, null, 4));
26+
}
27+
28+
writeFileSync(indexPath, JSON.stringify(index, null, 4));

utils/migrateSnippets.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { copyFileSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs';
2+
import { join } from 'path';
3+
4+
function slugify(string, separator = "-") {
5+
return string
6+
.toString() // Cast to string (optional)
7+
.toLowerCase() // Convert the string to lowercase letters
8+
.trim() // Remove whitespace from both sides of a string (optional)
9+
.replace(/\s+/g, separator) // Replace spaces with {separator}
10+
.replace(/[^\w\-]+/g, "") // Remove all non-word chars
11+
.replace(/\_/g, separator) // Replace _ with {separator}
12+
.replace(/\-\-+/g, separator) // Replace multiple - with single {separator}
13+
.replace(/\-$/g, ""); // Remove trailing -
14+
}
15+
16+
function formatSnippet(snippet) {
17+
return `---
18+
Title: ${snippet.title}
19+
Description: ${snippet.description}
20+
Author: ${snippet.author}
21+
Tags: ${snippet.tags}
22+
---
23+
24+
\`\`\`
25+
${snippet.code.join('\n')}
26+
\`\`\`
27+
`;
28+
}
29+
30+
const dataDir = "public/data";
31+
const iconsDir = "public/icons";
32+
const snippetDir = "snippets";
33+
34+
for (const file of readdirSync(dataDir)) {
35+
if(!file.endsWith('.json') || file === '_index.json') continue;
36+
37+
const languageName = file.slice(0, -5);
38+
const content = JSON.parse(readFileSync(join(dataDir, file)));
39+
const languagePath = join(snippetDir, languageName);
40+
const iconPath = join(iconsDir, `${languageName}.svg`);
41+
42+
mkdirSync(languagePath, { recursive: true });
43+
copyFileSync(iconPath, join(languagePath, 'icon.svg'));
44+
45+
for (const category of content) {
46+
if(category === 'icon.svg') continue;
47+
const categoryPath = join(languagePath, slugify(category.categoryName));
48+
49+
mkdirSync(categoryPath, { recursive: true });
50+
51+
for(const snippet of category.snippets) {
52+
const snippetPath = join(categoryPath, `${slugify(snippet.title)}.md`);
53+
54+
writeFileSync(snippetPath, formatSnippet(snippet));
55+
}
56+
}
57+
}

utils/snippetParser.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { existsSync, readdirSync, readFileSync } from 'fs';
2+
import { join } from 'path';
3+
4+
export function reverseSlugify(string, separator = "-") {
5+
return string
6+
.split(separator)
7+
.map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
8+
.join(' ')
9+
.trim();
10+
}
11+
12+
let errored = false;
13+
function raise(issue, snippet = '') {
14+
console.error(`${issue}${snippet ? ` in '${snippet}'` : ''}`);
15+
errored = true;
16+
return null;
17+
}
18+
19+
const propertyRegex = /^\s+([a-zA-Z]+): (.+)/;
20+
const headerEndCodeStartRegex = /^\s+---\s+```.*\n/;
21+
const codeRegex = /^(.+)```/s
22+
function parseSnippet(snippetPath, text) {
23+
let cursor = 0;
24+
25+
const fromCursor = () => text.substring(cursor);
26+
27+
if(!fromCursor().trim().startsWith('---')) return raise('Missing header start delimiter \'---\'', snippetPath);
28+
cursor += 3;
29+
30+
const properties = {};
31+
let match;
32+
while((match = propertyRegex.exec(fromCursor())) !== null) {
33+
cursor += match[0].length;
34+
properties[match[1].toLowerCase()] = match[2];
35+
}
36+
37+
if(!('title' in properties)) return raise(`Missing 'title' property`, snippetPath);
38+
if(!('description' in properties)) return raise(`Missing 'description' property`, snippetPath);
39+
if(!('author' in properties)) return raise(`Missing 'author' property`, snippetPath);
40+
if(!('tags' in properties)) return raise(`Missing 'tags' property`, snippetPath);
41+
42+
match = headerEndCodeStartRegex.exec(fromCursor());
43+
if(match === null) return raise('Missing header end \'---\' or code start \'```\'', snippetPath);
44+
cursor += match[0].length;
45+
46+
match = codeRegex.exec(fromCursor());
47+
if(match === null) return raise('Missing code block end \'```\'', snippetPath);
48+
const code = match[1];
49+
50+
return {
51+
title: properties.title,
52+
description: properties.description,
53+
author: properties.author,
54+
tags: properties.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag),
55+
code: code,
56+
}
57+
}
58+
59+
const snippetPath = "snippets/";
60+
export function parseAllSnippets() {
61+
const snippets = {};
62+
63+
for(const language of readdirSync(snippetPath)) {
64+
const languagePath = join(snippetPath, language);
65+
66+
const languageIconPath = join(languagePath, 'icon.svg');
67+
68+
if(!existsSync(languageIconPath)) {
69+
raise(`icon for '${language}' is missing`);
70+
continue;
71+
}
72+
73+
const categories = [];
74+
for(const category of readdirSync(languagePath)) {
75+
if(category === 'icon.svg') continue;
76+
const categoryPath = join(languagePath, category);
77+
78+
const categorySnippets = [];
79+
for(const snippet of readdirSync(categoryPath)) {
80+
const snippetPath = join(categoryPath, snippet);
81+
const snippetContent = readFileSync(snippetPath).toString();
82+
83+
const snippetData = parseSnippet(snippetPath, snippetContent);
84+
if(!snippetData) continue;
85+
categorySnippets.push(snippetData);
86+
}
87+
categories.push({
88+
categoryName: reverseSlugify(category),
89+
snippets: categorySnippets,
90+
});
91+
}
92+
93+
snippets[language] = categories;
94+
}
95+
96+
return [ errored, snippets ];
97+
}

0 commit comments

Comments
 (0)