diff options
author | metamuffin <metamuffin@disroot.org> | 2022-08-29 22:53:37 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2022-08-29 22:53:37 +0200 |
commit | 3eb1adfeb8dd477479404a1269c8682e3b4edf12 (patch) | |
tree | 1a4788149dc4b958dee129c28b95553cc3927f16 | |
parent | a4dd9c4944340505962f07853d53ab02f3a02336 (diff) | |
download | metamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar metamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar.bz2 metamuffin-blog-3eb1adfeb8dd477479404a1269c8682e3b4edf12.tar.zst |
split files
-rw-r--r-- | code/Cargo.lock | 26 | ||||
-rw-r--r-- | code/Cargo.toml | 3 | ||||
-rw-r--r-- | code/src/atom.rs | 73 | ||||
-rw-r--r-- | code/src/html.rs | 44 | ||||
-rw-r--r-- | code/src/main.rs | 179 | ||||
-rw-r--r-- | code/src/markdown.rs | 77 | ||||
-rw-r--r-- | content/articles/2022-08-29-blog-start.md | 2 |
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("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace("'", "’") - .replace("\"", """) } 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("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("'", "’") + .replace("\"", """) +} + 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 + |