aboutsummaryrefslogtreecommitdiff
path: root/code/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'code/src/main.rs')
-rw-r--r--code/src/main.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/code/src/main.rs b/code/src/main.rs
new file mode 100644
index 0000000..09ce3d8
--- /dev/null
+++ b/code/src/main.rs
@@ -0,0 +1,140 @@
+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<String>,
+ #[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<String>, 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<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!("<pre><code>{}</code></pre>", 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 {
+ markdown::Block::Header(text, level) => {
+ format!("<h{level}>{}</h{level}>", span_to_html(text))
+ }
+ markdown::Block::Paragraph(p) => format!("<p>{}</p>", span_to_html(p)),
+ markdown::Block::Blockquote(q) => format!("<quote>{}</quote>", blocks_to_html(q)),
+ markdown::Block::CodeBlock(_syntax, content) => {
+ format!("<pre><code>{}</code></pre>", escape(&content)) // TODO syntax highlighting
+ }
+ markdown::Block::OrderedList(_, _) => todo!(),
+ markdown::Block::UnorderedList(_) => todo!(),
+ markdown::Block::Raw(r) => r,
+ markdown::Block::Hr => format!("<hr/>"),
+ }
+ .as_str();
+ }
+ out
+}
+
+fn escape(text: &str) -> String {
+ text.replace("&", "&amp;")
+ .replace("<", "&lt;")
+ .replace(">", "&gt;")
+ .replace("'", "&#8217;")
+ .replace("\"", "&quot;")
+}