aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2022-08-29 22:53:37 +0200
committermetamuffin <metamuffin@disroot.org>2022-08-29 22:53:37 +0200
commit3eb1adfeb8dd477479404a1269c8682e3b4edf12 (patch)
tree1a4788149dc4b958dee129c28b95553cc3927f16
parenta4dd9c4944340505962f07853d53ab02f3a02336 (diff)
downloadmetamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar
metamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar.bz2
metamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar.zst
split files
-rw-r--r--code/Cargo.lock26
-rw-r--r--code/Cargo.toml3
-rw-r--r--code/src/atom.rs73
-rw-r--r--code/src/html.rs44
-rw-r--r--code/src/main.rs179
-rw-r--r--code/src/markdown.rs77
-rw-r--r--content/articles/2022-08-29-blog-start.md2
7 files changed, 274 insertions, 130 deletions
diff --git a/code/Cargo.lock b/code/Cargo.lock
index b9776eb..a2766ae 100644
--- a/code/Cargo.lock
+++ b/code/Cargo.lock
@@ -46,6 +46,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
+ "iso8601",
"laby",
"markdown",
]
@@ -121,6 +122,15 @@ dependencies = [
]
[[package]]
+name = "iso8601"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f21abb3d09069861499d93d41a970243a90e215df0cf763ac9a31b9b27178d0"
+dependencies = [
+ "nom",
+]
+
+[[package]]
name = "itoap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -189,6 +199,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "once_cell"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/code/Cargo.toml b/code/Cargo.toml
index 62fbb09..8fd54fb 100644
--- a/code/Cargo.toml
+++ b/code/Cargo.toml
@@ -7,4 +7,5 @@ edition = "2021"
laby = "0.2.4"
clap = { version = "3.2.17", features = ["derive"] }
anyhow = "1.0.62"
-markdown = "0.3.0" \ No newline at end of file
+markdown = "0.3.0"
+iso8601 = "0.5.0"
diff --git a/code/src/atom.rs b/code/src/atom.rs
new file mode 100644
index 0000000..4c6a771
--- /dev/null
+++ b/code/src/atom.rs
@@ -0,0 +1,73 @@
+use crate::{get_articles, Args, ArticleMeta, BLOG_BASE};
+use std::process::{Command, Stdio};
+
+pub fn generate_atom(args: &Args) -> String {
+ let entries = get_articles(&args.root.as_ref().unwrap())
+ .iter()
+ .map(
+ |ArticleMeta {
+ title,
+ date,
+ filename,
+ ..
+ }| {
+ let datetime = iso8601::DateTime {
+ date: date.clone(),
+ time: iso8601::Time {
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisecond: 0,
+ tz_offset_hours: 0,
+ tz_offset_minutes: 0,
+ },
+ };
+ format!(
+ r#"
+ <entry>
+ <title>{title}</title>
+ <link href="https://{BLOG_BASE}/{filename}.html" />
+ <id>tag:metamuffin.org,{date},{title}</id>
+ <published>{datetime}</published>
+ <summary>N/A</summary>
+ <author>
+ <name>metamuffin</name>
+ <email>metamuffin@disroot.org</email>
+ </author>
+ </entry>"#
+ )
+ },
+ )
+ .collect::<Vec<_>>();
+ format!(
+ r#"<?xml version="1.0" encoding="utf-8"?>
+ <feed xmlns="http://www.w3.org/2005/Atom">
+ <title>metamuffin's blog</title>
+ <subtitle>where they post pointless stuff</subtitle>
+ <link href="https://{BLOG_BASE}/feed.atom" rel="self" />
+ <link href="https://{BLOG_BASE}/" />
+ <id>urn:uuid:3cf2b704-3d94-4f1f-b194-42798ab5b47c</id>
+ <updated>{}</updated>
+ <author>
+ <name>metamuffin</name>
+ <email>metamuffin@disroot.org</email>
+ </author>
+ {}
+ </feed>
+ "#,
+ now_rfc3339(),
+ entries.join("\n")
+ )
+}
+
+fn now_rfc3339() -> String {
+ String::from_utf8(
+ Command::new("/usr/bin/date")
+ .arg("--iso-8601=minutes")
+ .stdout(Stdio::piped())
+ .output()
+ .unwrap()
+ .stdout,
+ )
+ .unwrap()
+}
diff --git a/code/src/html.rs b/code/src/html.rs
new file mode 100644
index 0000000..bdc9a78
--- /dev/null
+++ b/code/src/html.rs
@@ -0,0 +1,44 @@
+use crate::{article_metadata, get_articles, markdown::blocks_to_html};
+use laby::{html, iter, li, ul, Render};
+use std::fs::read_to_string;
+
+pub fn scaffold(title: String, body: impl Render) -> impl Render {
+ html!(
+ head!(
+ link!(rel = "stylesheet", href = "./style.css"),
+ title!(format!("{} - metamuffin's blog", title))
+ ),
+ body!(
+ nav!(h2!("metamuffin's blog"), a!(href = "./index.html", "index")),
+ article!(body),
+ footer!(p!("written by metamuffin, licensed under CC-BY-ND-4.0"))
+ )
+ )
+}
+
+pub fn article(path: String) -> impl Render {
+ scaffold(
+ article_metadata(path.clone().into()).title,
+ laby::raw!(blocks_to_html(markdown::tokenize(
+ &read_to_string(path).unwrap()
+ ))),
+ )
+}
+
+pub fn index(root: &str) -> impl Render {
+ scaffold(
+ "index".to_string(),
+ ul!(iter!({
+ get_articles(&root).into_iter().map(|meta| {
+ li!(
+ meta.date.to_string(),
+ ": ",
+ a!(
+ href = format!("./{}", meta.path.to_str().unwrap().replace(".md", ".html")),
+ meta.title
+ )
+ )
+ })
+ })),
+ )
+}
diff --git a/code/src/main.rs b/code/src/main.rs
index d90139e..ed0a62a 100644
--- a/code/src/main.rs
+++ b/code/src/main.rs
@@ -1,15 +1,23 @@
+pub mod atom;
+pub mod html;
+pub mod markdown;
+
+use atom::generate_atom;
+use clap::{Parser, Subcommand};
+use html::{article, index};
+use laby::{internal::Buffer, Render};
use std::{
- fs::{read_to_string, File},
+ fs::File,
io::{BufRead, BufReader, Write},
path::PathBuf,
};
-use clap::{Parser, Subcommand};
-use laby::{html, internal::Buffer, iter, li, raw, ul, Render};
-use markdown::{Block, ListItem, Span};
+pub const BLOG_BASE: &'static str = "s.metamuffin.org/temp/blog-preview";
#[derive(Parser)]
-struct Args {
+pub struct Args {
+ #[clap(short, long)]
+ root: Option<String>,
#[clap(short, long)]
output: Option<String>,
#[clap(subcommand)]
@@ -17,29 +25,33 @@ struct Args {
}
#[derive(Subcommand)]
-enum ArgAction {
+pub enum ArgAction {
RenderArticle { input: String },
- RenderIndex { root: String },
+ RenderIndex,
+ GenerateAtom,
}
fn main() {
let args = Args::parse();
- match args.action {
+ match &args.action {
ArgAction::RenderArticle { input } => {
let mut out = Buffer::new();
- article(input).render(&mut out);
- write_output(&args.output, out.into_string());
+ article(input.to_owned()).render(&mut out);
+ write_output(&args, out.into_string());
}
- ArgAction::RenderIndex { root } => {
+ ArgAction::RenderIndex => {
let mut out = Buffer::new();
- index(root).render(&mut out);
- write_output(&args.output, out.into_string());
+ index(&args.root.as_ref().unwrap()).render(&mut out);
+ write_output(&args, out.into_string());
+ }
+ ArgAction::GenerateAtom => {
+ write_output(&args, generate_atom(&args));
}
}
}
-fn write_output(t: &Option<String>, o: String) {
- if let Some(f) = t {
+fn write_output(t: &Args, o: String) {
+ if let Some(f) = &t.output {
let mut f = File::create(f).unwrap();
f.write_fmt(format_args!("{o}")).unwrap()
} else {
@@ -47,125 +59,34 @@ fn write_output(t: &Option<String>, o: String) {
}
}
-fn scaffold(title: String, body: impl Render) -> impl Render {
- html!(
- head!(
- link!(rel = "stylesheet", href = "./style.css"),
- title!(format!("{} - metamuffin's blog", title))
- ),
- body!(
- nav!(h2!("metamuffin's blog"), a!(href = "./index.html", "index")),
- article!(body),
- footer!(p!("written by metamuffin, licensed under CC-BY-ND-4.0"))
- )
- )
+pub fn get_articles(root: &str) -> Vec<ArticleMeta> {
+ let mut a = std::fs::read_dir(root)
+ .unwrap()
+ .map(|e| e.unwrap())
+ .map(|e| article_metadata(e.path()))
+ .collect::<Vec<_>>();
+ a.sort_by_cached_key(|e| -match e.date {
+ iso8601::Date::YMD { year, month, day } => day as i32 + month as i32 * 40 + year * 37,
+ _ => unreachable!(),
+ });
+ a
}
-fn index(root: String) -> impl Render {
- scaffold(
- "index".to_string(),
- ul!(iter!(std::fs::read_dir(root)
- .unwrap()
- .map(|e| e.unwrap())
- .map(|e| (
- e.file_name().to_str().unwrap().to_string(),
- article_title(e.path())
- ))
- .map(|(path, title)| li!(
- path.as_str()[0..10].to_string(),
- ": ",
- a!(href = format!("./{}", path.replace(".md", ".html")), title)
- )))),
- )
+pub struct ArticleMeta {
+ title: String,
+ filename: String,
+ date: iso8601::Date,
+ path: PathBuf,
}
-
-fn article_title(path: PathBuf) -> String {
- let f = File::open(path).unwrap();
+fn article_metadata(path: PathBuf) -> ArticleMeta {
+ let f = File::open(&path).unwrap();
let mut f = BufReader::new(f);
let mut buf = String::new();
f.read_line(&mut buf).unwrap(); // assume the 1st line has the title
- String::from(&buf[2..])
-}
-
-fn article(path: String) -> impl Render {
- scaffold(
- article_title(path.clone().into()),
- raw!(blocks_to_html(markdown::tokenize(
- &read_to_string(path).unwrap()
- ))),
- )
-}
-
-fn span_to_html(ss: Vec<Span>) -> String {
- let mut out = String::new();
- for s in ss {
- out += match s {
- Span::Break => format!("<br/>"),
- Span::Text(t) => escape(&t),
- Span::Code(c) => format!("<code>{}</code>", escape(&c)),
- Span::Link(text, url, _) => {
- format!("<a href=\"{}\">{}</a>", escape(&url), escape(&text))
- }
- Span::Image(_, _, _) => todo!(),
- Span::Emphasis(c) => format!("<i>{}</i>", span_to_html(c)),
- Span::Strong(c) => format!("<b>{}</b>", span_to_html(c)),
- }
- .as_str()
- }
- out
-}
-fn blocks_to_html(blocks: Vec<Block>) -> String {
- let mut out = String::new();
- for e in blocks {
- out += match e {
- Block::Header(text, level) => {
- format!("<h{level}>{}</h{level}>", span_to_html(text))
- }
- Block::Paragraph(p) => format!("<p>{}</p>", span_to_html(p)),
- Block::Blockquote(q) => format!("<quote>{}</quote>", blocks_to_html(q)),
- Block::CodeBlock(_syntax, content) => {
- format!("<code><pre>{}</pre></code>", escape(&content)) // TODO syntax highlighting
- }
- Block::OrderedList(els, _) => format!(
- "<ol>{}</ol>",
- els.into_iter()
- .map(|e| format!(
- "<li>{}</li>",
- match e {
- ListItem::Simple(s) => span_to_html(s),
- ListItem::Paragraph(b) => blocks_to_html(b),
- }
- ))
- .collect::<Vec<_>>()
- .join("")
- ),
- Block::UnorderedList(els) => {
- format!(
- "<ul>{}</ul>",
- els.into_iter()
- .map(|e| format!(
- "<li>{}</li>",
- match e {
- ListItem::Simple(s) => span_to_html(s),
- ListItem::Paragraph(b) => blocks_to_html(b),
- }
- ))
- .collect::<Vec<_>>()
- .join("")
- )
- }
- Block::Raw(r) => r,
- Block::Hr => format!("<hr/>"),
- }
- .as_str();
+ ArticleMeta {
+ title: String::from(&buf[2..]),
+ filename: path.file_name().unwrap().to_str().unwrap().to_string(),
+ date: iso8601::date(&path.file_name().unwrap().to_str().unwrap()[0..10]).unwrap(),
+ path,
}
- out
-}
-
-fn escape(text: &str) -> String {
- text.replace("&", "&amp;")
- .replace("<", "&lt;")
- .replace(">", "&gt;")
- .replace("'", "&#8217;")
- .replace("\"", "&quot;")
}
diff --git a/code/src/markdown.rs b/code/src/markdown.rs
new file mode 100644
index 0000000..c194a07
--- /dev/null
+++ b/code/src/markdown.rs
@@ -0,0 +1,77 @@
+use markdown::{Block, ListItem, Span};
+
+
+pub fn span_to_html(ss: Vec<Span>) -> String {
+ let mut out = String::new();
+ for s in ss {
+ out += match s {
+ Span::Break => format!("<br/>"),
+ Span::Text(t) => escape(&t),
+ Span::Code(c) => format!("<code>{}</code>", escape(&c)),
+ Span::Link(text, url, _) => {
+ format!("<a href=\"{}\">{}</a>", escape(&url), escape(&text))
+ }
+ Span::Image(_, _, _) => todo!(),
+ Span::Emphasis(c) => format!("<i>{}</i>", span_to_html(c)),
+ Span::Strong(c) => format!("<b>{}</b>", span_to_html(c)),
+ }
+ .as_str()
+ }
+ out
+}
+pub fn blocks_to_html(blocks: Vec<Block>) -> String {
+ let mut out = String::new();
+ for e in blocks {
+ out += match e {
+ Block::Header(text, level) => {
+ format!("<h{level}>{}</h{level}>", span_to_html(text))
+ }
+ Block::Paragraph(p) => format!("<p>{}</p>", span_to_html(p)),
+ Block::Blockquote(q) => format!("<quote>{}</quote>", blocks_to_html(q)),
+ Block::CodeBlock(_syntax, content) => {
+ format!("<code><pre>{}</pre></code>", escape(&content)) // TODO syntax highlighting
+ }
+ Block::OrderedList(els, _) => format!(
+ "<ol>{}</ol>",
+ els.into_iter()
+ .map(|e| format!(
+ "<li>{}</li>",
+ match e {
+ ListItem::Simple(s) => span_to_html(s),
+ ListItem::Paragraph(b) => blocks_to_html(b),
+ }
+ ))
+ .collect::<Vec<_>>()
+ .join("")
+ ),
+ Block::UnorderedList(els) => {
+ format!(
+ "<ul>{}</ul>",
+ els.into_iter()
+ .map(|e| format!(
+ "<li>{}</li>",
+ match e {
+ ListItem::Simple(s) => span_to_html(s),
+ ListItem::Paragraph(b) => blocks_to_html(b),
+ }
+ ))
+ .collect::<Vec<_>>()
+ .join("")
+ )
+ }
+ Block::Raw(r) => r,
+ Block::Hr => format!("<hr/>"),
+ }
+ .as_str();
+ }
+ out
+}
+
+pub fn escape(text: &str) -> String {
+ text.replace("&", "&amp;")
+ .replace("<", "&lt;")
+ .replace(">", "&gt;")
+ .replace("'", "&#8217;")
+ .replace("\"", "&quot;")
+}
+
diff --git a/content/articles/2022-08-29-blog-start.md b/content/articles/2022-08-29-blog-start.md
new file mode 100644
index 0000000..7361f21
--- /dev/null
+++ b/content/articles/2022-08-29-blog-start.md
@@ -0,0 +1,2 @@
+# Starting my blog
+