From 17baac0640a6b58537fa89346190b3228b07cf70 Mon Sep 17 00:00:00 2001 From: jetstream0 <49297268+jetstream0@users.noreply.github.com> Date: Sun, 30 Jul 2023 23:23:20 -0700 Subject: [PATCH] build system (saki) done --- README.md | 31 ++++++++--- index.ts | 18 +++++++ package.json | 2 +- saki.ts | 114 ++++++++++++++++++++++++++++++++++++++-- static/favicon.ico | Bin 0 -> 1628 bytes static/styles/index.css | 0 static/styles/post.css | 0 tests.ts | 4 +- 8 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 static/favicon.ico create mode 100644 static/styles/index.css create mode 100644 static/styles/post.css diff --git a/README.md b/README.md index ca7beca..ba982d9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,26 @@ -## Hedgeblog -My personal blog. +# Hedgeblog +My [personal blog](https://www.prussiafan.club), because what the world needs is yet another semi-abandoned blog. -# Makoto -Makoto is the markdown-to-html parser. +## Technical Goals +- Completely rewrite the blog, and get it working +- Be able to be served statically (so it can be deployed on Github Pages or Cloudflare Pages for no dinero) +- No dependencies - or basically, I want to write every line of code +- No Javascript served to client - the web pages should be pure HTML and CSS -# Ryuji -Ryuji is a simple templating system. +## Non-Technical Goals +- Make two things I can call "Ryuji" and "Saki" to go along with "Makoto" (those are the three main characters of one of the best manga series ever) +- Move over some of the old blog posts (only the stuff I like), after rewriting them +- Start writing stuff on the blog again, at least semi-regularly -# Saki -Saki is the build system that puts it all together and creates the blog's static html. +## Makoto +Makoto is the markdown-to-html parser, made with no dependencies. It was made around two months before Ryuji and Saki, and is meant to be more of a standalone thing. This is the sole npm dependency of the project (because I published makoto to npm and wanted to make sure it worked). + +It also has a very cool warnings feature, that isn't used in this project, but can be seen in action if you use the [Makoto Web Editor](https://makoto.prussia.dev). + +## Ryuji +Ryuji is a simple templating system. It's Jinja/Nunjucks inspired but has less features. On the upside, Ryuji is less than 200 lines of code and supports if statements, for loops, components and inserting variables. + +I didn't write any docs for it, but you can see the syntax if you look in the `templates` directory or look in `tests.ts`. + +## Saki +Saki is the build system that puts it all together and outputs the blog's static html. diff --git a/index.ts b/index.ts index e69de29..29a920d 100644 --- a/index.ts +++ b/index.ts @@ -0,0 +1,18 @@ +import { Renderer } from './ryuji.js'; +import { BlogBuilder, PostMetadata } from './saki.js'; +import _posts_metadata from './posts/_metadata.json'; + +let renderer: Renderer = new Renderer("templates", "components"); +let builder: BlogBuilder = new BlogBuilder(renderer); + +let posts_metadata: PostMetadata[] = Object.values(_posts_metadata); + +builder.serve_static_folder("static"); + +//home page +builder.serve_template("/", "index.html", { + posts: posts_metadata, +}); + +//blog posts +builder.serve_markdowns("/posts", "/posts", "post.html", posts_metadata, true); diff --git a/package.json b/package.json index bc16ecf..73cd6ee 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "blog", "main": "index.js", "scripts": { - "build": "tsc -p . && node saki.js", + "build": "tsc -p . && node index.js", "test": "tsc -p . && node tests.js" }, "repository": { diff --git a/saki.ts b/saki.ts index 5b696a6..30d7bce 100644 --- a/saki.ts +++ b/saki.ts @@ -1,7 +1,73 @@ -import { readFileSync, writeFileSync } from 'fs'; -import { Renderer } from './ryuji'; +import * as path from 'path'; +import { copyFileSync, existsSync, readdirSync, rmSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import type { Renderer } from './ryuji.js'; import { parse_md_to_html } from 'makoto'; +export class Builder { + build_dir: string; + + constructor(build_dir: string="/build") { + this.build_dir = path.join(__dirname, build_dir); + if (existsSync(this.build_dir)) { + //wipe the build directory + rmSync(this.build_dir, { + recursive: true, + }); + } + mkdirSync(this.build_dir); + } + + static copy_folder(folder_path: string, dest_path: string) { + let children: string[] = readdirSync(folder_path); + for (let i=0; i < children.length; i++) { + let child: string = children[i]; + let child_path: string = path.join(folder_path, child); + let copy_path: string = path.join(dest_path, child); + if (child.includes(".")) { + //file + copyFileSync(child_path, copy_path); + } else { + //directory, make directory and recursively copy + mkdirSync(copy_path); + Builder.copy_folder(child_path, path.join(dest_path, child)); + } + } + } + + serve_static_folder(static_dir: string, serve_from: string="/") { + let static_path: string = path.join(__dirname, static_dir); + let dest_path: string = path.join(this.build_dir, serve_from); + Builder.copy_folder(static_path, dest_path); + } + + serve_content(content: string, serve_path: string) { + let dest_path: string = path.join(this.build_dir, serve_path); + if (!serve_path.includes(".")) { + //serve as index.html in serve_path directory + if (dest_path !== this.build_dir && dest_path !== path.join(this.build_dir, "/")) { + //will not make a new directory if `serve_path` is "/", since the build directory already exists + mkdirSync(dest_path); + } + writeFileSync(path.join(dest_path, "index.html"), content); + } else { + //serve as file regularly + writeFileSync(dest_path, content); + } + } + + serve_file(file_path: string, serve_path: string) { + let file_content: string = readFileSync(path.join(__dirname, file_path), "utf-8"); + this.serve_content(file_content, serve_path); + } + + _serve_template(renderer: Renderer, serve_path: string, template_name: string, vars: any) { + let content: string = renderer.render_template(template_name, vars); + this.serve_content(content, serve_path); + } +} + +//this code is more or less specific to my blog + export interface PostMetadata { title: string, slug: string, @@ -16,4 +82,46 @@ export interface Post extends PostMetadata { html: string, } -// +export class BlogBuilder extends Builder { + renderer: Renderer; + + constructor(renderer: Renderer, build_dir: string="/build") { + super(build_dir); + this.renderer = renderer; + } + + serve_template(serve_path: string, template_name: string, vars: any) { + super._serve_template(this.renderer, serve_path, template_name, vars); + } + + serve_markdown(serve_path: string, template_name: string, markdown_post: Post, additional_vars: any={}) { + additional_vars.post = markdown_post; + this.serve_template(serve_path, template_name, { + post: markdown_post, + author_expected: markdown_post.author.toLowerCase().startsWith("jetstream0") || markdown_post.author.toLowerCase().startsWith("prussia"), + }); + } + + serve_markdowns(serve_path: string, posts_path: string, template_name: string, posts_metadata: PostMetadata[], own_dir: boolean=true) { + let posts_dir_path: string = path.join(this.build_dir, posts_path); + if (!existsSync(posts_dir_path)) { + mkdirSync(posts_dir_path); + } + for (let i=0; i < posts_metadata.length; i++) { + let post_metadata: PostMetadata = posts_metadata[i]; + let post_md_path: string = path.join(__dirname, posts_path, `${post_metadata.slug}.md`); + let post_md: string = readFileSync(post_md_path, "utf-8"); + let post_html: string = parse_md_to_html(post_md); + let post: Post = { + ...post_metadata, + md: post_md, + html: post_html, + }; + if (own_dir) { + this.serve_markdown(path.join(serve_path, post.slug), template_name, post); + } else { + this.serve_markdown(path.join(serve_path, `${post.slug}.html`), template_name, post); + } + } + } +} diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a404e79f28129b6132bc9d05213225572df0130e GIT binary patch literal 1628 zcmV-i2BZ0jP)lg8{`_+SvdA1?fpd zK~z}7?U!p%RAm^)|L<9L7Zw&00YR|DTbc?uN!~MK>?Tc#7rdmFrRZo%I-+GU-f|4g z(n)YE!zMwa>7sZ`(WEh_qJSpF9LowXh$xHf3heGV=ii4#cF(dfHGZfMzMtOnoHM_D zp67j@=lw5uhYNQB|7(C^vs$gTX7%3y8Vd63Z|{G7OadT6MrWR|{X>9yv_H_RNF_(DQ_}}N!28k>Ms`!5M6B~Dcc*ZmWK8LYedj$ zTT@mL;_}XA=nNa3w_n_f)Tb>6bQ400%O5rg zp_|tv%bK%U9tz_|*U?1c>1LmREQGy&Lwypl+X_O(^A))(=S?4w`zt|bSX#kdRzhaV{jhXigF?i=q z_Yd66?E%x;+Mr;I>shXRXXZmu5@PE-0cWJZBP99VaEO6>ys~s-u|bR-mg13D)mHH! zc6$Qs&m!>6CX(rRp|MS|s{5l5b6DF{S$Ij4gT|$jSWe3E;+U^`|8tBmuGKhpM8vC} zfYXmk(kimX99-wKW13zw;@NBN=aaD*?FlH0K=g+W@;fBRq`Gm!QMdmk(SJsgsrcCCw>#QfIXmnv+jG%aO_HqZD(&;C z#4K@7uAS-_9O`I#9rO#YCP`)^zEKNs%opNSDsei`UR6%4)AqhR7OPzS*;jm^1*j`^ zm`k~*;hg*POEdMDs4bsjU9^=X`4N&-4d8mPUuFZHG5#0a<<||tfdOYV07pMjLXtd% zJ`N3_HYnIiI>Q56x?L`stQSdVW*|eGg>jB=a|t_Woje!EulFx@zofp*;=}#oi-r zD%R062btX;d*4D9nE;XP2H5WxKa*!i!F<3Aa760SOu3JtvgMNL1t2=R&{DB%{L?>p z#E^{7NjA<#r(ZMx>#Mp@8-!s(K-@_ zEu=aMEwSwf0384U0HKyvz*1MYG%OT-6c0eTgj|(m3JP4-?LfcW@23fALScSqWGJnG zwOB?viw44&rx|I<=yH(c5^QJ*Vaj$J=~$u{gJ$7@o232lwr^CD+c1acAJJjJ*V;O$ zn+Wq+G7IS>l|#?XEOlM-dwL4^9UyCy1oUfEax2o9hAvrFuVcJA6yhkEkBKC+Bp|TG z!-mZ(_bcQEben22R=7fV1IydoOquAuQzlErKqr-_5 z0A^taN!Er0-k_48D^t%~HF9Y%PVy)ol(`TaS%ArH0IA4!l2rOiKT;iq<6e`*7HO=a zB|6c`6y);=lG*?^2wmbX2RR9O