Skip to content

Commit 125b5f1

Browse files
committed
static site generator
1 parent 2a3842b commit 125b5f1

18 files changed

+224
-124
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
bin
22
obj
33
aoc-crypt.key
4+
docs/node_modules

docs/build.js

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const marked = require('marked');
4+
5+
6+
function* findReadmes(dir) {
7+
const entries = fs.readdirSync(dir, { withFileTypes: true });
8+
9+
for (const entry of entries) {
10+
const fullPath = path.join(dir, entry.name);
11+
12+
if (entry.isDirectory()) {
13+
const match = fullPath.match(/(\d{4})\/Day(\d{1,2})/i);
14+
if (match) {
15+
const year = parseInt(match[1], 10);
16+
const day = parseInt(match[2], 10);
17+
18+
// Check the directory for a readme.md file
19+
const readmePath = path.join(fullPath, 'readme.md');
20+
const solutionPath = path.join(fullPath, 'Solution.cs');
21+
const illustrationPath = path.join(fullPath, 'illustration.jpeg');
22+
if (fs.existsSync(readmePath) && fs.existsSync(solutionPath)) {
23+
24+
const rawContent = fs.readFileSync(readmePath, 'utf8');
25+
26+
let name = '';
27+
const match = rawContent.match(/^## --- Day \d+: (.+?) ---/);
28+
if (match) {
29+
name = match[1];
30+
}
31+
32+
let lines = rawContent.split('\n');
33+
while (lines.length > 0) {
34+
let line = lines.shift();
35+
if (line.match(/^## --- Day \d+: (.+?) ---/)) {
36+
break;
37+
}
38+
}
39+
40+
yield {
41+
year,
42+
day,
43+
path: readmePath,
44+
name: name,
45+
notes: marked.parse(lines.join('\n')),
46+
code: fs.readFileSync(solutionPath, 'utf8'),
47+
illustration: fs.existsSync(illustrationPath) ? illustrationPath : 'docs/elf.jpeg',
48+
};
49+
}
50+
}
51+
52+
// Recursively search within this directory
53+
yield* findReadmes(fullPath);
54+
}
55+
}
56+
}
57+
58+
59+
function copyDirectory(src, dest) {
60+
if (!fs.existsSync(src)) {
61+
throw new Error(`Source directory does not exist: ${src}`);
62+
}
63+
64+
if (!fs.existsSync(dest)) {
65+
fs.mkdirSync(dest, { recursive: true });
66+
}
67+
68+
const items = fs.readdirSync(src);
69+
70+
items.forEach(item => {
71+
const srcPath = path.join(src, item);
72+
const destPath = path.join(dest, item);
73+
74+
if (fs.statSync(srcPath).isDirectory()) {
75+
copyDirectory(srcPath, destPath);
76+
} else {
77+
fs.copyFileSync(srcPath, destPath);
78+
}
79+
});
80+
};
81+
82+
83+
function loadTemplate(templatePath) {
84+
return fs.readFileSync(templatePath, 'utf8');
85+
}
86+
87+
88+
function fillTemplate(template, replacements) {
89+
return template.replace(/{{\s*([\w-]+)\s*}}/g, (_, key) => replacements[key] || '');
90+
}
91+
92+
function generateYearPicker(year, day, yearToDays) {
93+
let res = '';
94+
for (let y of Object.keys(yearToDays).sort()){
95+
let lastDay = yearToDays[y][yearToDays[y].length-1];
96+
let target = `/${y}/${lastDay}/`
97+
let selected = y == year ? 'selected="selected"' : '';
98+
res += `<option ${selected} value="${target}">${y}</option>`
99+
}
100+
return `<select>${res}</select>`;
101+
}
102+
103+
function generateDayPicker(year, day, yearToDays) {
104+
let res = '';
105+
for (i = 1; i <= yearToDays[year].length; i++) {
106+
const link = `<a href="/${year}/${i}">${i.toString().padStart(2, '0')}</a>`;
107+
res += i == day ? `<span class="current">${link}</span>` : `<span>${link}</span>`;
108+
}
109+
return res;
110+
}
111+
const template = loadTemplate('docs/template.html');
112+
113+
114+
const yearToDays = {};
115+
for (const { year, day } of findReadmes('.')) {
116+
if (!yearToDays[year]) {
117+
yearToDays[year] = [];
118+
}
119+
yearToDays[year].push(day);
120+
}
121+
122+
for(const year of Object.keys(yearToDays)){
123+
yearToDays[year] = yearToDays[year].sort((a,b) => a-b);
124+
}
125+
126+
127+
128+
copyDirectory('docs/static', 'build');
129+
130+
// Iterate over readme.md files and print filled templates
131+
for (const { year, day, name, notes, code, illustration } of findReadmes('.')) {
132+
const filledHtml = fillTemplate(template, {
133+
url: `https://aoc.csokavar.hu/${year}/${day}/`,
134+
'problem-id': `${year}/${day}`,
135+
'problem-name': `${name}`,
136+
'year-picker': generateYearPicker(year,day, yearToDays),
137+
'day-picker': generateDayPicker(year, day, yearToDays),
138+
notes,
139+
code,
140+
});
141+
const dst = `build/${year}/${day}`;
142+
fs.mkdirSync(dst, { recursive: true });
143+
fs.writeFileSync(path.join(dst, 'index.html'), filledHtml);
144+
fs.copyFileSync(illustration, path.join(dst, 'illustration.jpeg'));
145+
}

docs/package-lock.json

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/package.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "docs",
3+
"version": "1.0.0",
4+
"main": "build.js",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1"
7+
},
8+
"author": "",
9+
"license": "ISC",
10+
"description": "",
11+
"dependencies": {
12+
"marked": "^15.0.3"
13+
}
14+
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

docs/static/index.html

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!DOCTYPE html>
2+
3+
<script>
4+
const url = new URL(window.location.href);
5+
if (url.search.match(/^\?(\d{4})\/\d{1,2}/)) {
6+
window.location = url.search.substring(1);
7+
}
8+
</script>
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)