use std::{ fs::{read_to_string, File}, io::{BufRead, BufReader, Write}, path::PathBuf, }; use clap::{Parser, Subcommand}; use laby::{html, internal::Buffer, iter, li, raw, ul, Render}; use markdown::{Block, Span}; #[derive(Parser)] struct Args { #[clap(short, long)] output: Option, #[clap(subcommand)] action: ArgAction, } #[derive(Subcommand)] enum ArgAction { RenderArticle { input: String }, RenderIndex { root: String }, } fn main() { let args = Args::parse(); match args.action { ArgAction::RenderArticle { input } => { let md_source = read_to_string(input).unwrap(); let mut out = Buffer::new(); article(md_source).render(&mut out); write_output(&args.output, out.into_string()); } ArgAction::RenderIndex { root } => { let mut out = Buffer::new(); index(root).render(&mut out); write_output(&args.output, out.into_string()); } } } fn write_output(t: &Option, o: String) { if let Some(f) = t { let mut f = File::create(f).unwrap(); f.write_fmt(format_args!("{o}")).unwrap() } else { println!("{o}") } } fn scaffold(title: String, body: impl Render) -> impl Render { html!( head!(title!(title)), body!( nav!(h2!("metamuffin's blog"), a!(href = "./index.html", "index")), article!(body) ) ) } 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) )))), ) } fn article_title(path: PathBuf) -> String { 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(md_source: String) -> impl Render { scaffold( "blub".to_string(), raw!(blocks_to_html(markdown::tokenize(&md_source))), ) } fn span_to_html(ss: Vec) -> String { let mut out = String::new(); for s in ss { out += match s { Span::Break => format!("
"), Span::Text(t) => escape(&t), Span::Code(c) => format!("
{}
", escape(&c)), Span::Link(text, url, _) => { format!("{}", escape(&url), escape(&text)) } Span::Image(_, _, _) => todo!(), Span::Emphasis(c) => format!("{}", span_to_html(c)), Span::Strong(c) => format!("{}", span_to_html(c)), } .as_str() } out } fn blocks_to_html(blocks: Vec) -> String { let mut out = String::new(); for e in blocks { out += match e { markdown::Block::Header(text, level) => { format!("{}", span_to_html(text)) } markdown::Block::Paragraph(p) => span_to_html(p), markdown::Block::Blockquote(q) => format!("{}", blocks_to_html(q)), markdown::Block::CodeBlock(_syntax, content) => { format!("
{}
", escape(&content)) // TODO syntax highlighting } markdown::Block::OrderedList(_, _) => todo!(), markdown::Block::UnorderedList(_) => todo!(), markdown::Block::Raw(r) => r, markdown::Block::Hr => format!("
"), } .as_str(); } out } fn escape(text: &str) -> String { text.replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("'", "’") .replace("\"", """) }