diff options
-rw-r--r-- | Cargo.lock | 100 | ||||
-rw-r--r-- | Cargo.toml | 18 | ||||
-rw-r--r-- | src/blog/mod.rs | 12 | ||||
-rw-r--r-- | src/blog/post.rs | 170 | ||||
-rw-r--r-- | src/build.rs | 28 | ||||
-rw-r--r-- | src/internal/markdown.rs | 21 | ||||
-rw-r--r-- | src/internal/mod.rs | 16 | ||||
-rw-r--r-- | src/main.rs | 19 | ||||
-rw-r--r-- | templates/footer.rs.html | 12 | ||||
-rw-r--r-- | templates/header.rs.html | 16 | ||||
-rw-r--r-- | templates/index.rs.html | 9 |
11 files changed, 414 insertions, 7 deletions
@@ -183,6 +183,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.43", + "winapi", +] + +[[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -442,6 +455,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" [[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] name = "h2" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -798,6 +817,25 @@ dependencies = [ ] [[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -981,7 +1019,7 @@ dependencies = [ "indexmap", "line-wrap", "serde", - "time", + "time 0.3.5", "xml-rs", ] @@ -1116,6 +1154,7 @@ dependencies = [ "bytecount", "itertools", "md5", + "mime", "nom", ] @@ -1163,6 +1202,9 @@ name = "serde" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -1199,6 +1241,18 @@ dependencies = [ ] [[package]] +name = "serde_yaml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] name = "sha-1" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1251,11 +1305,18 @@ dependencies = [ name = "site" version = "0.1.0" dependencies = [ + "chrono", "color-eyre", "comrak", + "glob", "kankyo", "ructe", + "serde", + "serde_yaml", + "thiserror", "tokio", + "tracing", + "tracing-subscriber 0.3.3", "warp", ] @@ -1386,6 +1447,16 @@ dependencies = [ [[package]] name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" @@ -1523,7 +1594,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", ] [[package]] @@ -1538,6 +1620,20 @@ dependencies = [ ] [[package]] +name = "tracing-subscriber" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3" +dependencies = [ + "ansi_term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] name = "try-lock" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2,16 +2,32 @@ name = "site" version = "0.1.0" edition = "2021" +build = "src/build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] warp = "0.3" comrak = "0.12" -ructe = "0.13" color-eyre = "0.5" kankyo = "0.3" +tracing = "0.1" +chrono = "0.4" +thiserror = "1" +serde_yaml = "0.8" +glob = "0.3" + +[build-dependencies] +ructe = { version = "0.13", features = ["warp03"] } + +[dependencies.tracing-subscriber] +version = "0.3" +features = ["fmt"] [dependencies.tokio] version = "1" features = ["full"] + +[dependencies.serde] +version = "1" +features = ["derive"] diff --git a/src/blog/mod.rs b/src/blog/mod.rs index 199d6db..3640bcd 100644 --- a/src/blog/mod.rs +++ b/src/blog/mod.rs @@ -1,10 +1,18 @@ +pub mod post; pub mod handlers { +use std::sync::Arc; + use warp::{Reply, Rejection}; use warp::http::Response; +use crate::templates::{self, Html, RenderRucte}; + +use crate::internal::SiteState; - pub async fn list() -> Result<impl Reply, Rejection> { - Ok("test") + pub async fn list(state: Arc<SiteState>) -> Result<impl Reply, Rejection> { + let state = state.clone(); + Response::builder() + .html(|o| templates::index_html(o)) } pub async fn post(name: String) -> Result<impl Reply, Rejection> { diff --git a/src/blog/post.rs b/src/blog/post.rs new file mode 100644 index 0000000..abe57aa --- /dev/null +++ b/src/blog/post.rs @@ -0,0 +1,170 @@ +use std::{cmp::Ordering, path::PathBuf}; +use glob::glob; +use color_eyre::eyre::{Result, Context, eyre}; +use tokio::fs; + +use chrono::prelude::*; + + +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct Post { + pub front_matter: frontmatter::Data, + pub body_html: String, + pub date: DateTime<FixedOffset>, +} + +impl Ord for Post { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(&other).unwrap() + } +} + +impl PartialOrd for Post { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.date.cmp(&other.date)) + } +} + +async fn read_post(dir: &str, fname: PathBuf) -> Result<Post> { + let body = fs::read_to_string(fname.clone()) + .await + .wrap_err_with(|| format!("couldn't read {:?}", fname))?; + + let (front_matter, content_offset) = frontmatter::Data::parse(body.clone().as_str()) + .wrap_err_with(|| format!("can't parse frontmatter of {:?}", fname))?; + + let body = &body[content_offset..]; + let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d") + .map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?; + let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap()); + let body_html = crate::internal::markdown::render(&body) + .wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?; + let date: DateTime<FixedOffset> = DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0,0,0)), Utc) + .with_timezone(&Utc) + .into(); + + Ok(Post { + front_matter, + body_html, + date + } ) +} + +pub async fn load(dir: &str) -> Result<Vec<Post>> { + let futs = glob(&format!("{}/*.md", dir))? + .filter_map(Result::ok) + .map(|fname| read_post(dir, fname)); + + Ok(Vec::new()) +} + +mod frontmatter { + use serde::{Serialize, Deserialize}; + use color_eyre::eyre::Result; + #[derive(Eq, PartialEq, Deserialize, Default, Debug, Serialize, Clone)] + pub struct Data { + pub title: String, + pub date: String, + pub series: Option<Vec<String>>, + pub tags: Option<Vec<String>>, + } + + enum ParseState { + Searching, + ReadingFM { buf: String, line_start: bool }, + SkipNewLine { end: bool }, + ReadingMark { count: usize, end: bool }, + } + + #[derive(Debug, thiserror::Error)] + enum Error { + #[error("EOF while parsing")] + EOF, + #[error("Error parsing YAML: {0:?}")] + Yaml(#[from] serde_yaml::Error), + } + + impl Data { + pub fn parse(input: &str) -> Result<(Data, usize)> { + let mut state = ParseState::Searching; + + let mut payload = None; + let offset; + + let mut chars = input.char_indices(); + 'parse: loop { + let (i, ch) = match chars.next() { + Some(x) => x, + None => return Err(Error::EOF)?, + }; + match &mut state { + ParseState::Searching => match ch { + '-' => { + state = ParseState::ReadingMark { + count: 1, + end: false, + }; + }, + '\n' | '\t' | ' ' => { + + }, + _ => { + panic!("Start of frontmatter not found!"); + } + }, + ParseState::ReadingMark {count, end } => match ch { + '-' => { + *count += 1; + if *count == 3 { + state = ParseState::SkipNewLine{ end: *end }; + } + } + _ => { + panic!("Malformed frontmatter"); + } + }, + ParseState::SkipNewLine { end } => match ch { + '\n' => { + if *end { + offset = i + 1; + break 'parse; + } else { + state = ParseState::ReadingFM { + buf: String::new(), + line_start: true, + }; + } + }, + _ => { + panic!("Expected newline, got {:?}", ch); + } + }, + ParseState::ReadingFM { buf, line_start } => match ch { + '-' if *line_start => { + let mut state_tmp = ParseState::ReadingMark { + count: 1, + end: true, + }; + std::mem::swap(&mut state, &mut state_tmp); + if let ParseState::ReadingFM {buf, ..} = state_tmp { + payload = Some(buf); + } else { + unreachable!(); + } + }, + ch => { + buf.push(ch); + *line_start = ch == '\n'; + } + } + } + } + + let payload = payload.unwrap(); + + let fm = serde_yaml::from_str(&payload)?; + + Ok((fm, offset)) + } + } +} diff --git a/src/build.rs b/src/build.rs new file mode 100644 index 0000000..3fcea82 --- /dev/null +++ b/src/build.rs @@ -0,0 +1,28 @@ +use std::process::Command; +use ructe::{Ructe, RucteError}; + +fn main() -> Result<(), Box<dyn std::error::Error>> { + let mut ructe = Ructe::from_env()?; + let mut statics = ructe.statics()?; + statics.add_files("statics")?; + ructe.compile_templates("templates")?; + + let output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .output() + .unwrap(); + + let out = std::env::var("out").unwrap_or("/fake".into()); + println!("cargo:rustc-env=out={}", out); + + let git_hash = String::from_utf8(output.stdout).unwrap(); + println!( + "cargo:rustc-env=GIT_SHA={}", + if git_hash.as_str() == "" { + out.into() + } else { + git_hash + } + ); + Ok(()) +} diff --git a/src/internal/markdown.rs b/src/internal/markdown.rs new file mode 100644 index 0000000..9798876 --- /dev/null +++ b/src/internal/markdown.rs @@ -0,0 +1,21 @@ +use color_eyre::{Result, eyre::Context}; +use comrak::{ComrakOptions, Arena, parse_document, format_html}; + + +pub fn render(inp: &str) -> Result<String> { + let mut options = ComrakOptions::default(); + options.extension.autolink = true; + options.extension.table = true; + options.extension.superscript = true; + options.extension.footnotes = true; + + options.render.unsafe_ = true; + + let arena = Arena::new(); + let root = parse_document(&arena, inp, &options); + + let mut html = vec![]; + format_html(root, &options, &mut html).unwrap(); + + String::from_utf8(html).wrap_err("this is somehow not UTF-8") +} diff --git a/src/internal/mod.rs b/src/internal/mod.rs new file mode 100644 index 0000000..f924c50 --- /dev/null +++ b/src/internal/mod.rs @@ -0,0 +1,16 @@ +use color_eyre::eyre::Result; +use crate::blog::post::Post; + +pub mod markdown; + +pub struct SiteState { + pub blog: Vec<Post>, +} + +pub async fn init() -> Result<SiteState> { + let blog = crate::blog::post::load("blog").await?; + + Ok(SiteState { + blog, + }) +} diff --git a/src/main.rs b/src/main.rs index 8e92b51..04ab18e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,31 @@ +#[macro_use] +extern crate tracing; + use color_eyre::eyre::Result; -use std::net::IpAddr; +use std::{net::IpAddr, sync::Arc}; use warp::Filter; use std::str::FromStr; pub mod blog; +mod internal; + +use internal::SiteState; #[tokio::main] async fn main() -> Result<()> { color_eyre::install()?; + tracing_subscriber::fmt::init(); + info!("Starting launch of {}", env!("GIT_SHA")); + // Load .env kankyo::init(); + let state = Arc::new(internal::init().await?); + let blog_base = warp::path!("blog" / ..); - let blog_list = blog_base.and_then(blog::handlers::list); + let blog_list = blog_base.and(give_site_state(state.clone())).and_then(blog::handlers::list); let blog_post = blog_base.and( warp::path!(String) .and(warp::get()).and_then(blog::handlers::post)); @@ -36,3 +47,7 @@ async fn main() -> Result<()> { Ok(()) } + +fn give_site_state(sitestate: Arc<SiteState>) -> impl Filter<Extract = (Arc<SiteState>,), Error=std::convert::Infallible> + Clone { + warp::any().map(move || sitestate.clone()) +} diff --git a/templates/footer.rs.html b/templates/footer.rs.html new file mode 100644 index 0000000..5029544 --- /dev/null +++ b/templates/footer.rs.html @@ -0,0 +1,12 @@ +@() +</div> +<hr /> +<footer> + <blockquote>Content copyright Cara Salter 2021, based on <a + href="https://github.com/Xe/site">Xe/site</a> + under the Zlib license</blockquote> + <p>This server runs version @env!("GIT_SHA"), located on my <a + href='https://git.carathe.dev/~muirrum/site/commit/@env!("GIT_SHA")'>sourcehut</a> +</footer> +</body> +</html> diff --git a/templates/header.rs.html b/templates/header.rs.html new file mode 100644 index 0000000..afcf445 --- /dev/null +++ b/templates/header.rs.html @@ -0,0 +1,16 @@ +@use chrono::{DateLike, Utc}; + +@(title: Option<&str>, styles: Option<&str>) + +<!DOCTYPE html> +<html lang="en"> + <head> + @if title.is_some() { + <title>@title.unwrap()</title> + } else { + <title>cara salter</title> + } + </head> + <body> + <div class="container"> + diff --git a/templates/index.rs.html b/templates/index.rs.html new file mode 100644 index 0000000..6e35935 --- /dev/null +++ b/templates/index.rs.html @@ -0,0 +1,9 @@ +@use super::{header_html} + +@() + +@:header_html(None, None) + +<link rel="canonical" href="https://devcara.com"> +Test +</div> |