initial commit

This commit is contained in:
Price Hiller 2023-08-27 20:17:16 -05:00
commit 0841f6158d
Signed by: Price
SSH Key Fingerprint: SHA256:Y4S9ZzYphRn1W1kbJerJFO6GGsfu9O70VaBSxJO7dF8
25 changed files with 3563 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
out

1404
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "blog"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
comrak = "0.18.0"
syntect = "5.1.0"
anyhow = "1.0.75"
clap = { version = "4.4.0", features = ["derive"] }
chrono = { version = "0.4.26", default-features = false, features = ["clock", "serde"] }
serde = { version = "1.0.188", features = ["derive"]}
serde_yaml = "0.9.25"
serde_json = "1.0.105"
lazy_static = "1.4.0"
tera = "1.19.0"

636
assets/Kanagawa.tmTheme Normal file
View File

@ -0,0 +1,636 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!-- Generated by: TmTheme-Editor -->
<!-- ============================================ -->
<!-- app: http://tmtheme-editor.herokuapp.com -->
<!-- code: https://github.com/aziz/tmTheme-Editor -->
<plist version="1.0">
<dict>
<key>name</key>
<string>kanagawa</string>
<key>settings</key>
<array>
<dict>
<key>settings</key>
<dict>
<key>background</key>
<string>#1F1F28</string>
<key>foreground</key>
<string>#dcd7ba</string>
<key>lineHighlight</key>
<string>#363646</string>
<key>selection</key>
<string>#223249</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Comment</string>
<key>scope</key>
<string>comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string>italic</string>
<key>foreground</key>
<string>#727169</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98bb6c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Number</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#d27e99</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built-in constant</string>
<key>scope</key>
<string>constant.language</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7fb4ca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>User-defined constant</string>
<key>scope</key>
<string>constant.character, constant.other</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ffa066</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable</string>
<key>scope</key>
<string>variable</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcd7ba</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Keyword</string>
<key>scope</key>
<string>keyword</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FF5D62</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage</string>
<key>scope</key>
<string>storage</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage type</string>
<key>scope</key>
<string>storage.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#957FB8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Function argument</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FFA066</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag name</string>
<key>scope</key>
<string>entity.name.tag</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Tag attribute</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string></string>
<key>foreground</key>
<string>#7fb4ca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Punctuation Definition Entity</string>
<key>scope</key>
<string>punctuation.definition.entity</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#9CABCA</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Entity Other Attribute-Name</string>
<key>scope</key>
<string>entity.other.attribute-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class Name</string>
<key>scope</key>
<string>entity.name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Punctuation Comment</string>
<key>scope</key>
<string>punctuation.definition.comment</string>
<key>settings</key>
<dict>
<key>fontStyle</key>
<string> italic</string>
<key>foreground</key>
<string>#727169</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Brackets&#x2f;Braces</string>
<key>scope</key>
<string>brace</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#9cabca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Constant</string>
<key>scope</key>
<string>constant.language</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#ffa066</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Built In Types</string>
<key>scope</key>
<string>type.built-ins</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7fb4ca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Import Types</string>
<key>scope</key>
<string>type.import</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#E6C384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Class Inheritance</string>
<key>scope</key>
<string>class.inheritance</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Section Embedded</string>
<key>scope</key>
<string>section.embedded</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#9cabca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Punctuation</string>
<key>scope</key>
<string>punctuation</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#9cabca</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Types</string>
<key>scope</key>
<string>type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support Constant</string>
<key>scope</key>
<string>support.constant</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support Class</string>
<key>scope</key>
<string>support.class</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Property Name</string>
<key>scope</key>
<string>property-name</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage Modifier Lifetime Rust</string>
<key>scope</key>
<string>storage.modifier.lifetime.rust</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Storage Modifier</string>
<key>scope</key>
<string>storage.modifier</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Generic</string>
<key>scope</key>
<string>generic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Macro</string>
<key>scope</key>
<string>macro</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FF5D62</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support Macro</string>
<key>scope</key>
<string>support.macro</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FF5D62</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Path</string>
<key>scope</key>
<string>meta.path</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Variable Language</string>
<key>scope</key>
<string>variable.language</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FF5D62</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>keyword.control.flow</string>
<key>scope</key>
<string>keyword.control.flow</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#957FB8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>keyword.operator.logical</string>
<key>scope</key>
<string>keyword.operator.logical</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>keyword.operator.arithmetic</string>
<key>scope</key>
<string>keyword.operator.arithmetic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>punctuation.definition.string</string>
<key>scope</key>
<string>punctuation.definition.string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98bb6c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>support.constant.property-value</string>
<key>scope</key>
<string>support.constant.property-value</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98bb6c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>keyword.other.unit</string>
<key>scope</key>
<string>keyword.other.unit</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#98bb6c</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>keyword.operator</string>
<key>scope</key>
<string>keyword.operator</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>variable.annotation</string>
<key>scope</key>
<string>variable.annotation</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#FF5D62</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Support</string>
<key>scope</key>
<string>support</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>variable.other.member</string>
<key>scope</key>
<string>variable.other.member</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>Generic</string>
<key>scope</key>
<string>meta.generic</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>meta.function.return-type</string>
<key>scope</key>
<string>meta.function.return-type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>meta.property</string>
<key>scope</key>
<string>meta.property</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#e6c384</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>support.function</string>
<key>scope</key>
<string>support.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7e9cd8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>variable.type</string>
<key>scope</key>
<string>variable.type</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7aa89f</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>meta.function-call</string>
<key>scope</key>
<string>meta.function-call</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7e9cd8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>entity.name.function</string>
<key>scope</key>
<string>entity.name.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7e9cd8</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>meta.function-call.arguments</string>
<key>scope</key>
<string>meta.function-call.arguments</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#dcd7ba</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>variable.parameter</string>
<key>scope</key>
<string>variable.parameter</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#b8b4d0</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>variable.function</string>
<key>scope</key>
<string>variable.function</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#7e9cd8</string>
</dict>
</dict>
</array>
<key>uuid</key>
<string>D8D5E82E-3D5B-46B5-B38E-8C841C21347D</string>
<key>colorSpaceName</key>
<string>sRGB</string>
<key>semanticClass</key>
<string>theme.dark.kanagawa</string>
</dict>
</plist>

127
assets/style/article.css Normal file
View File

@ -0,0 +1,127 @@
/* Article Specific Styling Below */
article {
display: flex;
max-width: min(700px, 100vw);
padding-left: 3px;
padding-left: 3px;
align-self: center;
flex-direction: column;
}
article > p > img {
margin-left: 50%;
transform: translateX(-50%);
max-height: 75vh;
max-width: min(90vw, 800px);
}
.article-name {
color: var(--springGreen);
font-weight: bold;
}
.article-summary {
font-size: 1.2rem;
}
.article-tags > ul {
display: flex;
justify-items: flex-start;
gap: 8px;
padding: 0;
margin: 0;
text-align: center;
}
.article-tags a {
color: unset;
background-color: color-mix(in srgb, var() 25%, transparent);
}
.article-tags > ul > li {
width: auto;
display: inline;
float: none;
}
.article-tags > ul > li:not(:last-child)::after {
content: " ⦁";
}
.article-dates {
color: var(--fujiGray);
display: flex;
gap: 10px;
justify-content: space-evenly;
font-size: 0.9rem;
}
.article-published::before {
content: "Published: ";
}
.article-last-updated::before {
content: "Last Updated: ";
}
.articles-container {
width: min(600px, 90vw);
}
.articles-container > ul {
margin: 0;
padding: 0;
}
.articles-container > ul > li {
list-style-type: none;
margin: 10px
}
.article-frontmatter {
background-color: var(--winterGreen);
font-family: sans-serif;
border-radius: 10px;
padding: 10px;
}
.article-frontmatter:hover {
background-color: color-mix(in srgb, var(--autumnGreen) 35%, transparent);
transition-duration: 0.25s;
}
.article-frontmatter > a {
text-decoration: none;
color: var(--fujiWhite);
}
.article-frontmatter > a:hover {
font-weight: unset;
text-decoration: none;
background-color: unset;
color: var(--fujiWhite);
}
.article-frontmatter > a > .article-name {
font-size: 1.5rem;
}
.article-frontmatter > a > .article-dates {
justify-content: flex-start;
}
.article-frontmatter-tags > ul {
gap: 8px;
padding-left: 0;
}
.article-frontmatter-tags a {
color: unset;
background-color: color-mix(in srgb, var() 25%, transparent);
}
.article-frontmatter-tags > ul > li {
display: inline;
}
.article-frontmatter-tags > ul > li:not(:last-child)::after {
content: " ⦁";
}

236
assets/style/style.css Normal file
View File

@ -0,0 +1,236 @@
/* Yo, you're looking at my horrid CSS. Judge, let me know what I'm doing wrong because I sure as shit am NOT a web dev.
* Though maybe someday? For now, this is my cobbled together mess based on Mozilla's web developer documentation and a
* fuckton of fiddling. */
/* TODO: Split this up and improve some of our organization of classes. Ideally the delivered css should be no more than
* 4k in size so the article content can take up 10k. Stay within the TCP first roundtrip size to speed up loading.
* Granted the style SHOULD get cached so it's not that big of a deal, but something to resolve.
*
* TODO: Regardless of the size of this beast, I need to deduplicate some of the functionality of these classes. Lots of
* things are unecessarily repated that could be consolidated. <- DO THIS PART FIRST, IGNORE SIZE */
:root {
--fujiWhite: #dcd7ba;
--oldWhite: #c8c093;
--sumiInk0: #16161d;
--sumiInk1: #1f1f28;
--sumiInk2: #2a2a37;
--sumiInk3: #363646;
--sumiInk4: #54546d;
--waveBlue1: #223249;
--waveBlue2: #2d4f67;
--winterGreen: #2b3328;
--winterYellow: #49443c;
--winterRed: #43242b;
--winterBlue: #252535;
--autumnGreen: #76946a;
--autumnRed: #c34043;
--autumnYellow: #dca561;
--samuraiRed: #e82424;
--roninYellow: #ff9e3b;
--waveAqua1: #6a9589;
--dragonBlue: #658594;
--fujiGray: #727169;
--springViolet1: #938aa9;
--oniViolet: #957fb8;
--crystalBlue: #7e9cd8;
--springViolet2: #9cabca;
--springBlue: #7fb4ca;
--lightBlue: #a3d4d5;
--waveAqua2: #7aa89f;
--springGreen: #98bb6c;
--boatYellow1: #938056;
--boatYellow2: #c0a36e;
--carpYellow: #e6c384;
--sakuraPink: #d27e99;
--waveRed: #e46876;
--peachRed: #ff5d62;
--surimiOrange: #ffa066;
--navbar-height: 40px;
--navbar-bg-color: var(--sumiInk4);
--navbar-border-color: var(--sumiInk3);
}
::selection {
background: color-mix(in srgb, var(--lightBlue) 25%, transparent);
}
/* This ensures the page uses full width at all times. We have to do this because some elements (our images in
* particular) are wider than their parents and we couldn't use flex for that causing some alignment issues when the
* webpage was too narrow (Mobile devices). */
html,
body {
max-width: 100%;
overflow-x: hidden;
}
html {
background-color: var(--sumiInk0);
color: var(--fujiWhite);
}
body {
padding-top: var(--navbar-height);
line-height: 1.6;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
nav {
display: flex;
flex-direction: row;
align-self: center;
background-color: color-mix(in srgb, var(--navbar-bg-color) 20%, transparent);
backdrop-filter: blur(12px);
/* HACK: Fucken safari doesn't support `backdrop-filter`, webkit has its own. D o g s h i t. */
-webkit-backdrop-filter: blur(12px);
font-size: 1.1rem;
font-family: sans-serif;
height: var(--navbar-height);
position: fixed;
top: 0;
width: 300px;
border-bottom-width: 2px;
border-bottom-style: solid;
border-bottom-color: var(--sumiInk3);
border-right-style: solid;
border-right-color: var(--sumiInk3);
border-bottom-right-radius: 10px;
border-left-style: solid;
border-left-color: var(--sumiInk3);
border-bottom-left-radius: 10px;
/* Set the navbar to have highest priority so it shows above all other elements */
}
nav a:link,
nav a:visited {
color: var(--fujiWhite);
background-color: unset;
text-decoration: none;
}
nav a:hover,
nav a:visited:hover {
color: unset;
background-color: unset;
}
.nav-item {
width: 100%;
}
.nav-item:not(:last-child) {
border-right: 2px solid var(--sumiInk3);
}
.nav-item:hover {
color: var(--fujiWhite);
transition-duration: 0.3s;
background-color: color-mix(in srgb, var(--navbar-bg-color) 40%, transparent);
}
.nav-item a {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
img {
padding: 10px;
overflow: scroll;
max-height: 75vh;
max-width: min(95vw, 125%);
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 0;
font-family: sans-serif;
}
a:link {
color: var(--crystalBlue);
}
a:hover,
a:visited:hover {
color: var(--fujiWhite);
background-color: color-mix(in srgb, var(--crystalBlue) 50%, transparent);
text-decoration-color: var(--crystalBlue);
}
a:visited {
color: var(--oniViolet);
}
blockquote {
background-color: var(--sumiInk1);
padding: 10px;
border-left-width: 5px;
border-left-style: solid;
border-left-color: var(--sumiInk4);
}
blockquote > p {
margin: 0;
}
pre {
border-radius: 4px;
overflow: auto;
outline-style: solid;
outline-color: var(--sumiInk2);
outline-width: 2.5px;
width: min(90vw, 800px);
font-size: 0.85rem;
transition-duration: 0.25s;
align-self: center;
padding: 5px;
}
pre:hover {
outline-color: var(--sumiInk3);
/* NOTE: We have to set important because Comrak's default syntax adapter will ALWAYS set a color for the background
* here, no way to avoid it unless we want to rewrite chunks of the syntax adapter. Currently I'm too lazy to
* investigate that.*/
background-color: var(--sumiInk2) !important;
}
code {
border-radius: 6px;
padding: 2px;
font-family: monospace;
}
p > code {
background-color: var(--sumiInk3);
}
.page-info {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
font-family: sans-serif;
}
.page-title {
font-size: 2rem;
font-style: bold;
font-family: sans-serif;
}
.page-detail {
font-size: 0.95rem;
max-width: min(600px, 95vw);
}
hr {
margin: 30px;
}

19
assets/style/tags.css Normal file
View File

@ -0,0 +1,19 @@
/* Tags.html */
.tags {
max-width: min(750px, 90vw);
}
.tags > ul {
margin: 0;
padding: 0;
}
.tags > ul > li {
display: inline;
font-family: sans-serif;
font-size: 1.5rem;
}
.tags > ul > li:not(:last-child)::after {
content: " ⦁";
}

View File

@ -0,0 +1,19 @@
{% macro gen(frontmatter, link) %}
<div class="article-frontmatter">
<a href="/articles/{{ link }}">
<div class="article-name">{{ frontmatter.name }}</div>
<div class="article-detail">{{ frontmatter.summary }}</div>
<div class="article-dates">
<div class="article-published">{{ frontmatter.published }}</div>
<div class="article-last-updated">{{ frontmatter.updated }}</div>
</div>
<div class="article-frontmatter-tags">
<ul>
{% for tag in frontmatter.tags -%}
<li><a href="/tags/{{ tag }}.html">{{ tag }}</a></li>
{% endfor -%}
</ul>
</div>
</a>
</div>
{% endmacro gen %}

View File

@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<title>{{ article_title }}</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/style/style.css" rel="stylesheet" />
<link href="/style/article.css" rel="stylesheet" />
</head>
{% include "nav.html" %}
<body>
<div class="page-info">
<div class="page-title article-name">{{ article_title }}</div>
<div class="page-detail article-summary">{{ article_summary }}</div>
<div class="article-dates">
<div class="article-published">{{ article_published }}</div>
<div class="article-last-updated">{{ article_last_updated }}</div>
</div>
<div class="article-tags">
<ul>
{% for tag in article_tags -%}
<li><a href="/tags/{{ tag }}.html">{{ tag }}</a></li>
{% endfor -%}
</ul>
</div>
</div>
<article>
<hr />
{{ article_content }}
</article>
</body>
</html>

View File

@ -0,0 +1,29 @@
{% import "article-frontmatter.html" as front_matter_macro %}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Articles</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/style/style.css" rel="stylesheet">
<link href="/style/article.css" rel="stylesheet" />
</head>
{% include "nav.html" %}
<body>
<div class="page-info">
<div class="page-title">Articles</div>
<div class="page-detail">Grimoire of brain damage</div>
</div>
<div class="articles-container">
<ul>
{% for article in articles -%}
<a href="/articles/{{ article.link }}">
<li>
{{ front_matter_macro::gen(frontmatter=article.frontmatter, link=article.link) }}
</li>
</a>
{% endfor %}
</ul>
</div>
</body>
</html>

View File

View File

@ -0,0 +1,5 @@
<nav>
<div class="nav-item"><a href="/home.html">Home</a></div>
<div class="nav-item"><a href="/articles.html">Articles</a></div>
<div class="nav-item"><a href="/tags.html">Tags</a></div>
</nav>

View File

@ -0,0 +1,31 @@
{% import "article-frontmatter.html" as front_matter_macro %}
<!doctype html>
<html lang="en">
<head>
<title>Tags</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/style/style.css" rel="stylesheet" />
<link href="/style/article.css" rel="stylesheet" />
</head>
{% include "nav.html" %}
<body>
<div class="page-info">
<div class="page-title">{{ tag }}</div>
<div class="page-detail">Articles tagged <code>{{ tag }}</code></div>
</div>
<div class="articles-container">
<ul>
{% for article in article_links -%}
<a href="/articles/{{ article.link }}">
<li>
{{ front_matter_macro::gen(frontmatter=article.frontmatter,
link=article.link) }}
</li>
</a>
{% endfor %}
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Tags</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="/style/style.css" rel="stylesheet" />
<link href="/style/tags.css" rel="stylesheet" />
</head>
{% include "nav.html" %}
<body>
<div class="tags">
<ul>
{% for tag in tags -%}
<li><a href="/tags/{{ tag }}.html">{{ tag }}</a></li>
{% endfor -%}
</ul>
</div>
</body>
</html>

BIN
posts/assets/out.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

398
posts/example.md Normal file
View File

@ -0,0 +1,398 @@
---
name: Example Article
summary: This is an example summary for quick detail to be parsed into frontmatter shit
tags:
- example
- nope
category:
- example
- nope
published: 2023-07-24
updated: 2023-07-24
---
# Heading
Content
## Sub Heading
More content
```lua
local my_table = {
string = "Nah",
boolean = true,
arr = {
"nah",
"yes",
true,
false,
nil
},
}
```
```rust
use std::collections::BTreeMap;
use comrak::plugins::syntect::SyntectAdapterBuilder;
use comrak::{markdown_to_html_with_plugins, ComrakOptions, ComrakPlugins};
use syntect::highlighting::ThemeSet;
#[derive(debug)]
fn main() {
let theme = ThemeSet::load_from_reader(&mut std::io::Cursor::new(include_bytes!("../TwoDark.tmTheme"))).unwrap();
let mut t = BTreeMap::new();
t.insert(String::from("TwoDark"), theme);
let mut theme_set= ThemeSet::new();
theme_set.themes = t;
let adapter = SyntectAdapterBuilder::new().theme("TwoDark").theme_set(theme_set).build();
let options = ComrakOptions::default();
let mut plugins = ComrakPlugins::default();
// Hello World
plugins.render.codefence_syntax_highlighter = Some(&adapter);
let formatted = markdown_to_html_with_plugins(include_str!("../example.md"), &options, &plugins);
println!("{}", formatted);
}
fn tester<'a>(in: i32) {
println!("Hi {in}!");
}
impl MyExample<T> {
fn fuck<T>() -> T {
T
}
}
struct MyMan {
man: i32
}
enum fuck {
RED: 1,
BLUE: 2,
GREEN: "Yep"
}
```
```js
var parseXML = function (data) {
var xml, tmp;
if (!data || typeof data !== "string") {
return null;
}
try {
if (window.DOMParser) {
// Standard
tmp = new DOMParser();
xml = tmp.parseFromString(data, "text/xml");
} else {
// IE
xml = new ActiveXObject("Microsoft.XMLDOM");
xml.async = false;
xml.loadXML(data);
}
} catch (e) {
xml = undefined;
}
if (
!xml ||
!xml.documentElement ||
xml.getElementsByTagName("parsererror").length
) {
jQuery.error("Invalid XML: " + data);
}
return xml;
};
Sound.play = function() {}
Sound.prototype = { something; }
Sound.prototype.play = function() {}
Sound.prototype.play = myfunc
var parser = document.createElement('a');
parser.href = "http://example.com:3000/pathname/?search=test#hash";
parser.hostname; // => "example.com"
```
```python
import re
class Example:
...
class Eee(Example):
...
print("Yest")
def my_fn():
...
my_fn(test, no)
class SublimeTasksBase(sublime_plugin.TextCommand):
def run(self, edit):
self.open_tasks_bullet = self.view.settings().get('open_tasks_bullet')
self.done_tasks_bullet = self.view.settings().get('done_tasks_bullet')
self.date_format = self.view.settings().get('date_format')
if self.view.settings().get('done_tag'):
self.done_tag = "@done"
else:
self.done_tag = ""
self.runCommand(edit)
class NewCommand(SublimeTasksBase):
thing = NewCommand()
def runCommand(self, edit):
for region in self.view.sel():
line = self.view.line(region)
line_contents = self.view.substr(line).rstrip()
has_bullet = re.match('^(\s*)[' + re.escape(self.open_tasks_bullet) + re.escape(self.done_tasks_bullet) + ']', self.view.substr(line))
current_scope = self.view.scope_name(self.view.sel()[0].b)
if has_bullet:
grps = has_bullet.groups()
line_contents = self.view.substr(line) + '\n' + grps[0] + self.open_tasks_bullet + ' '
self.view.replace(edit, line, line_contents)
```
```php
<?php
// base class with member properties and methods
class Vegetable {
var $edible;
var $color;
function Vegetable($edible, $color="green")
{
$this->edible = $edible;
$this->color = $color;
}
function is_edible()
{
return $this->edible;
}
function what_color()
{
return $this->color;
}
} // end of class Vegetable
// extends the base class
class Spinach extends Vegetable {
var $cooked = false;
function Spinach()
{
$this->Vegetable(true, "green");
}
function cook_it()
{
$this->cooked = true;
}
function is_cooked()
{
return $this->cooked;
}
} // end of class Spinach
?>
```
```ocaml
open Lexer_flow
module Ast = Spider_monkey_ast
open Ast
module Error = Parse_error
module SSet = Set.Make(String)
module SMap = Map.Make(String)
type lex_mode =
| NORMAL_LEX
| TYPE_LEX
| JSX_TAG
| JSX_CHILD
let mode_to_string = function
| NORMAL_LEX -> "NORMAL"
| TYPE_LEX -> "TYPE"
| JSX_TAG -> "JSX TAG"
| JSX_CHILD -> "JSX CHILD"
let lex lex_env = function
| NORMAL_LEX -> token lex_env
| TYPE_LEX -> type_token lex_env
| JSX_TAG -> lex_jsx_tag lex_env
| JSX_CHILD -> lex_jsx_child lex_env
type env = {
errors : (Loc.t * Error.t) list ref;
comments : Comment.t list ref;
labels : SSet.t;
lb : Lexing.lexbuf;
lookahead : lex_result ref;
last : (lex_env * lex_result) option ref;
priority : int;
strict : bool;
in_export : bool;
in_loop : bool;
in_switch : bool;
in_function : bool;
no_in : bool;
no_call : bool;
no_let : bool;
allow_yield : bool;
(* Use this to indicate that the "()" as in "() => 123" is not allowed in
* this expression *)
error_callback : (env -> Error.t -> unit) option;
lex_mode_stack : lex_mode list ref;
lex_env : lex_env ref;
}
```
```css
body {
font-family: arial;
}
h1,
p,
table {
background-color: #ccc;
border: 1px solid;
color: #39f;
text-align: center;
width: 100%;
}
.addon-store .pagehead h1 {
display: inline-block;
}
.addon-store .addon-summary:after {
clear: both;
}
#addon-store .pagehead .electrocat-small {
bottom: -7px;
position: absolute;
right: 0;
}
.addon-store .addons-nav a.selected {
border-bottom-color: #d26911;
color: #333;
font-weight: bold;
padding: 0 0 14px;
}
.addon-store .addon-icon {
background: #fff;
border: 1px solid #ddd;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
float: left;
height: 80px;
margin-right: 14px;
width: 80px;
}
.addon-store .developer-callout {
background-color: #f1f1f1;
background-image: -moz-linear-gradient(#fafafa, #f1f1f1);
background-image: -webkit-linear-gradient(#fafafa, #f1f1f1);
background-image: linear-gradient(#fafafa, #f1f1f1);
background-repeat: repeat-x;
border: 1px solid #ddd;
border-bottom: 1px solid #ccc;
border-radius: 3px;
box-shadow:
inset 0 1px 0 #fff,
0 1px 5px #f1f1f1;
margin-top: 40px;
text-shadow: 0 1px 0 #fff;
}
```
```
No language here!
```
```c
/* stringmerge.c -- Given two sorted files of strings, it creates
* a sorted file consisting of all their elements.
* The names of the files are passed as command
* line parameters.
*/
#include <stdio.h>
#define MAXBUFFER 128
int getline(FILE * fd, char buff[], int nmax){
/* It reads a line from fd and stores up to nmax of
* its characters to buff.
*/
char c;
int n=0;
while ((c=getc(fd))!='\n'){
if(c==EOF)return EOF;
if(n<nmax)
buff[n++]=c;
}
buff[n]='\0';
return n;
}
```
```bash
for i in *; do
printf "%s\n" "${i}"
done
```
```go
data, err := json.Marshal(esEntry)
if err != nil {
log.Fatalf("failed to marshal entry: %v", err)
}
resp, err := es.Index("site-search-kb", bytes.NewBuffer(data), es.Index.WithDocumentID(entry.ID))
if err != nil {
log.Fatal(err)
}
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated:
// ok
default:
log.Fatalf("failed to index entry %q: %s", entry.Title, resp.String())
}
```
[See here](./another.md)
Hey this should be an image below:
![Image of html shit](./assets/out.png "Yep")
Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.
And this is inline code: `Hi world!`
> Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis. Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet. Nisi anim cupidatat excepteur officia. Reprehenderit nostrud nostrud ipsum Lorem est aliquip amet voluptate voluptate dolor minim nulla est proident. Nostrud officia pariatur ut officia. Sit irure elit esse ea nulla sunt ex occaecat reprehenderit commodo officia dolor Lorem duis laboris cupidatat officia voluptate. Culpa proident adipisicing id nulla nisi laboris ex in Lorem sunt duis officia eiusmod. Aliqua reprehenderit commodo ex non excepteur duis sunt velit enim. Voluptate laboris sint cupidatat ullamco ut ea consectetur et est culpa et culpa duis.

0
src/config.rs Normal file
View File

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
use tera::Tera;
pub mod markdown;
pub mod page_gen;
pub trait TemplateRenderer {
fn render_template(&self, tera: &Tera) -> anyhow::Result<String>;
}

142
src/main.rs Normal file
View File

@ -0,0 +1,142 @@
use anyhow::{Context, Ok};
use clap::Parser;
use blog::{
markdown::article::{Article, FrontMatter},
page_gen::{
articles::Articles,
tags::{TagArticles, Tags},
},
TemplateRenderer,
};
use std::{collections::HashMap, path::PathBuf};
use tera::Tera;
#[derive(Parser, Debug)]
#[command(author = "Price Hiller <price@orion-technologies.io>", version = "0.1", about = "Parses markdown documents to html with code syntax highlighting", long_about = None)]
struct Args {
/// Path to a directory containing markdown files to parse
#[arg()]
documents: PathBuf,
/// Whether or not the parsed file should be written back as <FILE_NAME>.html
#[arg(short, long, value_name = "write-html", default_value_t = false)]
write_html: bool,
/// Path to a custom theme to use instead of default
#[arg(short, long)]
theme: Option<PathBuf>,
}
fn main() -> anyhow::Result<()> {
let comrak_settings = blog::markdown::MDComrakSettings::default().unwrap();
let posts_dir = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/posts/"));
let posts_walkable = std::fs::read_dir(&posts_dir)
.context("Unable to read posts directory!")?;
let out_path = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/out"));
std::fs::create_dir_all(&out_path)
.context(format!("Unable to create out directory at '{out_path:?}'"))?;
let mut tera = Tera::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/templates/**/*.html"
))
.context("Tera Template Import Error")?;
tera.autoescape_on(vec![]);
let mut detected_tags_article: HashMap<String, Vec<(FrontMatter, String)>> = HashMap::new();
let mut article_links: Vec<(FrontMatter, String)> = Vec::new();
println!("Rendering Articles");
for dir_entry in posts_walkable {
let path = dir_entry?.path();
if path.is_file() {
println!(
"Parsing Article: {}",
path.file_name().unwrap().to_str().unwrap()
);
let parsed_article = Article::parse(&path, &comrak_settings).unwrap();
println!(
"Rendering Article Template: {} - {}",
path.file_name().unwrap().to_str().unwrap(),
&parsed_article.frontmatter.name
);
let rendered_article = parsed_article
.render_template(&tera)
.context("Article Template Rendering Error")?;
let new_file_name = String::from(path.file_stem().unwrap().to_str().unwrap()) + ".html";
let write_path = &out_path.join(PathBuf::from(format!("articles/{}", &new_file_name)));
write_file(&write_path, rendered_article.as_bytes())?;
println!("Finished Rendering Article Template");
for article_tag in &parsed_article.frontmatter.tags {
let tag = detected_tags_article
.entry(article_tag.clone())
.or_default();
tag.push((parsed_article.frontmatter.clone(), new_file_name.clone()));
}
article_links.push((parsed_article.frontmatter.clone(), new_file_name.clone()));
}
}
println!("Finished rendering Article templates");
println!("Rendering Tags Page");
let tags_page = Tags::new(&detected_tags_article.keys().collect::<Vec<&String>>())
.render_template(&tera)
.context("Main Tags Page Rendering Error")?;
let tags_write_path = &out_path.join("tags.html");
write_file(&tags_write_path, tags_page.as_bytes())?;
println!("Finished Rendering Tags Page");
println!("Rendering Articles Page");
let articles_page = Articles::new(&article_links)
.render_template(&tera)
.context("Main Articles Page Rendering Error")?;
let articles_write_path = &out_path.join("articles.html");
write_file(&articles_write_path, articles_page.as_bytes())?;
println!("Finished Rendering Articles Page");
println!("Rendering Individual Tag Pages");
for (tag, article_link) in detected_tags_article.iter() {
println!("Rendering Tag Page: {}", &tag);
let tag_article = TagArticles::new(tag, article_link).render_template(&tera)?;
write_file(
&out_path.join(format!("tags/{tag}.html")),
tag_article.as_bytes(),
)?;
}
println!("Finished rendering Individual Tag Pages");
let base_asset_dir = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/"));
copy_recursive(&base_asset_dir.join("style/"), &out_path.join("style"))?;
copy_recursive(&posts_dir.join("assets/"), &out_path.join("articles/assets"))?;
Ok(())
}
fn copy_recursive(src: &PathBuf, dest: &PathBuf) -> anyhow::Result<()> {
std::fs::create_dir_all(&dest)?;
for item in std::fs::read_dir(src)? {
let entry = item?;
let file_type = entry.file_type()?;
let dest = &dest.join(entry.file_name());
if file_type.is_dir() {
copy_recursive(&entry.path(), &dest)?;
} else if file_type.is_symlink() {
let _ = std::fs::remove_file(&dest);
std::fs::copy(std::fs::read_link(entry.path())?, &dest)?;
} else {
let _ = std::fs::remove_file(&dest);
std::fs::copy(&entry.path(), &dest)?;
}
}
Ok(())
}
fn write_file(file_path: &PathBuf, contents: &[u8]) -> anyhow::Result<()> {
let mut dir = file_path.clone();
if dir.pop() {
std::fs::create_dir_all(dir)?;
}
std::fs::write(file_path, contents).context(format!("Failed to write to {file_path:?}"))
}

100
src/markdown/article.rs Normal file
View File

@ -0,0 +1,100 @@
use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
use anyhow::Context;
use chrono::NaiveDate;
use comrak::{nodes::NodeValue, parse_document, Arena};
use serde::{Deserialize, Serialize};
use crate::TemplateRenderer;
use super::{iter_nodes, MDComrakSettings};
#[derive(Debug, Serialize)]
pub struct Article {
pub html_content: String,
pub frontmatter: FrontMatter
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FrontMatter {
pub name: String,
pub summary: String,
pub published: NaiveDate,
pub updated: NaiveDate,
pub tags: Vec<String>,
}
impl Article {
pub fn parse(
article_md_path: &PathBuf,
comrak_settings: &MDComrakSettings,
) -> anyhow::Result<Article> {
let article_content = Self::load_doc(article_md_path).with_context(|| {
format!("Could not read article markdown document at {article_md_path:?}")
})?;
let arena = Arena::new();
let root = parse_document(&arena, &article_content, &comrak_settings.options);
let mut front_matter_raw: String = String::from("");
iter_nodes(root, &mut |node| match node.data.borrow().value {
// NOTE: This is kinda hacky, we just assume the frontmatter delimiter is ALWAYS "---";
// furthermore, we assume there is even a frontmatter input period and this will have a
// nuclear incident if there doesn't happen to be a frontmatter delimiter. We should
// better handle this, but as of the time of this writing I don't fucken care enough.
// Will come back to later or this will be a temporary permanent fix.
NodeValue::FrontMatter(ref front_data) => {
front_matter_raw = front_data
.lines()
.filter(|line| match *line {
"---" => false,
_ => true,
})
.map(|s| s.to_string() + "\n")
.collect::<String>();
}
_ => (),
});
let frontmatter: FrontMatter =
serde_yaml::from_str(&front_matter_raw).with_context(|| {
format!("Failed to parse frontmatter for document: {article_md_path:?}")
})?;
let mut html_out = vec![];
comrak::format_html_with_plugins(
root,
&comrak_settings.options,
&mut html_out,
&comrak_settings.plugins,
)?;
let html = String::from_utf8(html_out)?;
Ok(Article {
html_content: html,
frontmatter,
})
}
fn load_doc<P: AsRef<Path>>(file_path: P) -> anyhow::Result<String> {
let mut f = File::options().read(true).write(false).open(file_path)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
}
impl TemplateRenderer for Article {
fn render_template(&self, tera: &tera::Tera) -> anyhow::Result<String> {
let mut tera_context = tera::Context::new();
tera_context.insert("article_title", &self.frontmatter.name);
tera_context.insert("article_summary", &self.frontmatter.summary);
tera_context.insert("article_published", &self.frontmatter.published);
tera_context.insert("article_last_updated", &self.frontmatter.updated);
tera_context.insert("article_tags", &self.frontmatter.tags);
tera_context.insert("article_content", &self.html_content);
tera.render("article.html", &tera_context).context(format!("Failed to render Article: '{}'", &self.frontmatter.name))
}
}

60
src/markdown/mod.rs Normal file
View File

@ -0,0 +1,60 @@
use std::io::Cursor;
use comrak::{nodes::AstNode, ComrakOptions, ComrakPlugins, plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder}};
use lazy_static::lazy_static;
use syntect::highlighting::ThemeSet;
pub mod article;
lazy_static! {
pub static ref SYNTECT_ADAPTER: SyntectAdapter =
MDComrakSettings::load_theme("kanagawa", &mut Cursor::new(include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/Kanagawa.tmTheme"
))))
.expect("Unable to load custom syntax theme!");
}
pub fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &mut F)
where
F: FnMut(&'a AstNode<'a>),
{
f(node);
for c in node.children() {
iter_nodes(c, f);
}
}
#[derive(Debug)]
pub struct MDComrakSettings<'a> {
pub options: ComrakOptions,
pub plugins: ComrakPlugins<'a>,
}
impl MDComrakSettings<'_> {
pub fn default<'a>() -> anyhow::Result<MDComrakSettings<'a>> {
let mut options = ComrakOptions::default();
options.render.unsafe_ = true;
options.extension.front_matter_delimiter = Some("---".to_owned());
let mut plugins = ComrakPlugins::default();
plugins.render.codefence_syntax_highlighter = Some(&*SYNTECT_ADAPTER);
Ok(MDComrakSettings { options, plugins })
}
pub fn load_theme<R>(theme_name: &str, theme_cursor: &mut R) -> anyhow::Result<SyntectAdapter>
where
R: std::io::BufRead + std::io::Seek,
{
let theme = ThemeSet::load_from_reader(theme_cursor)?;
let mut theme_set = ThemeSet::new();
theme_set.themes.insert(String::from(theme_name), theme);
let adapter = SyntectAdapterBuilder::new()
.theme_set(theme_set)
.theme(theme_name)
.build();
Ok(adapter)
}
}

29
src/page_gen/articles.rs Normal file
View File

@ -0,0 +1,29 @@
use super::tags::ArticleLink;
use crate::{markdown::article::FrontMatter, TemplateRenderer};
use anyhow::Context;
pub struct Articles<'a> {
articles: Vec<ArticleLink<'a>>,
}
impl Articles<'_> {
pub fn new<'a>(articles: &'a Vec<(FrontMatter, String)>) -> Articles<'a> {
let mut qual_article_links = Vec::new();
for (frontmatter, link) in articles {
qual_article_links.push(ArticleLink { frontmatter, link });
}
Articles {
articles: qual_article_links,
}
}
}
impl TemplateRenderer for Articles<'_> {
fn render_template(&self, tera: &tera::Tera) -> anyhow::Result<String> {
let mut tera_context = tera::Context::new();
tera_context.insert("articles", &self.articles);
tera.render("articles.html", &tera_context)
.context(format!("Failed to render primary articles page!"))
}
}

11
src/page_gen/mod.rs Normal file
View File

@ -0,0 +1,11 @@
use crate::markdown::article::FrontMatter;
use serde::Serialize;
pub mod articles;
pub mod tags;
#[derive(Serialize)]
pub struct ArticleLink<'a> {
frontmatter: &'a FrontMatter,
link: &'a String,
}

54
src/page_gen/tags.rs Normal file
View File

@ -0,0 +1,54 @@
use anyhow::Context;
use serde::Serialize;
use crate::{TemplateRenderer, markdown::article::FrontMatter};
pub struct Tags<'a> {
tags: &'a Vec<&'a String>,
}
impl Tags<'_> {
pub fn new<'a>(tags: &'a Vec<&'a String>) -> Tags<'a> {
Tags { tags }
}
}
impl TemplateRenderer for Tags<'_> {
fn render_template(&self, tera: &tera::Tera) -> anyhow::Result<String> {
let mut tera_context = tera::Context::new();
tera_context.insert("tags", &self.tags);
tera.render("tags.html", &tera_context).context(format!(
"Failed to render tags page, had tags: {:#?}",
&self.tags
))
}
}
#[derive(Serialize)]
pub struct ArticleLink<'a> {
pub frontmatter: &'a FrontMatter,
pub link: &'a String
}
pub struct TagArticles<'a> {
tag: &'a String,
article_links: Vec<ArticleLink<'a>>
}
impl TagArticles<'_> {
pub fn new<'a>(tag: &'a String, article_links: &'a Vec<(FrontMatter, String)>) -> TagArticles<'a> {
let mut qual_article_links = Vec::new();
for (frontmatter, link) in article_links {
qual_article_links.push(ArticleLink { frontmatter, link });
}
TagArticles { tag, article_links: qual_article_links }
}
}
impl TemplateRenderer for TagArticles<'_> {
fn render_template(&self, tera: &tera::Tera) -> anyhow::Result<String> {
let mut terra_context = tera::Context::new();
terra_context.insert("tag", &self.tag);
terra_context.insert("article_links", &self.article_links);
tera.render("tag-articles.html", &terra_context).context(format!("Failed to render tag articles page for tag: {}", &self.tag))
}
}

175
src/tree_sitter_adapter.rs Normal file
View File

@ -0,0 +1,175 @@
/// As a future note to myself. This shit is REALLY cool, but it seems there's some bugs on
/// treesitter's side when it comes to applying highlighting to javascript and that Neovim's
/// treesitter implementation is MUCH better than what you get out of the box from the treesitter
/// project. One on front, this is cool as fuck, on another front, this was a total pain in the
/// ass. Maybe try this later and next time don't go down the rabbit hole of binding rust to Neovim
/// to get their parsers and queries for our own use. Did it work? Yep. Was it fast? Fuck no. Could
/// it have been fast? Probably, but I'm not wasting the time to write the ffi to Neovim only for
/// some fucker over there to change an interface or nuke one; I'm not gonna put up the maintenance
/// effort.
///
/// Full credit for a decent chunk of this to Andrew Biehl. His blog post
/// https://andrewtbiehl.com/blog/jekyll-tree-sitter is what sent me down this horrid path along
/// with some of his code in his kramdown syntax module. I did *significantly* speed this code up
/// though, as in several magnitudes for my purposes.
use std::{
collections::HashMap,
convert,
io::{self, Write},
path::PathBuf,
};
use anyhow::Context;
use comrak::adapters::SyntaxHighlighterAdapter;
use tree_sitter::Language;
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
use tree_sitter_loader::{Config, LanguageConfiguration, Loader};
pub struct TreesitterSyntaxAdapter {
parsers_directory: PathBuf,
}
impl TreesitterSyntaxAdapter {
pub fn new(parsers_directory: PathBuf) -> anyhow::Result<Self> {
Ok(TreesitterSyntaxAdapter { parsers_directory })
}
pub fn get_loader(parsers_directory: &PathBuf) -> anyhow::Result<Loader> {
Loader::new()
.and_then(|mut loader| {
let config = {
let parsers_directory = parsers_directory.clone();
let parser_directories = vec![parsers_directory];
Config { parser_directories }
};
loader.find_all_languages(&config)?;
Ok(loader)
})
.with_context(|| {
let parser_directory_str = parsers_directory.display();
format!(
"Failed to load Treesitter parsers from directory: '{parser_directory_str}'"
)
})
}
fn get_language_config<'a>(
loader: &'a Loader,
code_lang: &'a str,
) -> anyhow::Result<(Language, &'a LanguageConfiguration<'a>)> {
loader
.language_configuration_for_scope(code_lang)
.transpose()
.context("Language configuration not found")
.and_then(convert::identity)
.with_context(|| format!("Failed to retrieve Treesitter language configuration for language: '{code_lang}'"))
}
fn get_highlight_configuration<'a>(
language: Language,
config: &'a LanguageConfiguration<'a>,
scope: &'a str,
) -> anyhow::Result<&'a HighlightConfiguration> {
config
.highlight_config(language)
.transpose()
.with_context(|| {
format!("Failed to retrieve Treesitter highlights for language: '{scope}'")
})
.and_then(convert::identity)
}
fn get_highlights<'a>(
&'a self,
loader: &'a Loader,
highlighter: &'a mut Highlighter,
code: &'a str,
code_lang: &'a str,
) -> anyhow::Result<
impl Iterator<Item = Result<HighlightEvent, tree_sitter_highlight::Error>> + 'a,
> {
let (language, lang_config) = Self::get_language_config(&loader, code_lang)?;
let highlight_config = Self::get_highlight_configuration(language, lang_config, code_lang)?;
let highlights =
highlighter.highlight(&highlight_config, code.as_bytes(), None, |lang| loader.highlight_config_for_injection_string(lang))?;
// loader.configure_highlights(&highlight_config.names().to_vec());
Ok(highlights)
}
pub fn determine_ts_scope_name(code_lang: &str) -> String {
match code_lang {
_ => format!("source.{}", code_lang),
}
}
}
impl SyntaxHighlighterAdapter for TreesitterSyntaxAdapter {
fn write_highlighted(
&self,
output: &mut dyn Write,
lang: Option<&str>,
code: &str,
) -> io::Result<()> {
let scope = if let Some(lang) = lang {
if lang.is_empty() {
return Ok(());
}
TreesitterSyntaxAdapter::determine_ts_scope_name(lang)
} else {
return Ok(());
};
let loader = TreesitterSyntaxAdapter::get_loader(&self.parsers_directory).unwrap();
let mut highlighter = Highlighter::new();
let highlights = self
.get_highlights(&loader, &mut highlighter, code, scope.as_str())
.unwrap();
let highlight_names = loader.highlight_names();
println!("AT LANGUAGE -> {}", scope);
println!("HIGHLIGHTS: {:#?}", highlight_names);
for event in highlights {
match event.unwrap() {
HighlightEvent::Source { start, end } => {
write!(output, "{}", String::from(&code[start..end]))?
}
HighlightEvent::HighlightStart(s) => write!(
output,
"<span class=\"ts-{}\">",
highlight_names[s.0].replace(".", "-")
)?,
HighlightEvent::HighlightEnd => write!(output, "</span>")?,
}
}
Ok(())
}
fn write_pre_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> io::Result<()> {
if attributes.contains_key("lang") {
write!(output, "<pre lang=\"{}\">", attributes["lang"])
} else {
output.write_all(b"<pre>")
}
}
fn write_code_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> io::Result<()> {
if attributes.contains_key("class") {
write!(output, "<code class=\"{}\">", attributes["class"])
} else {
output.write_all(b"<code>")
}
}
}