summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCara Salter <cara@devcara.com>2021-12-19 12:48:36 -0500
committerCara Salter <cara@devcara.com>2021-12-19 12:48:36 -0500
commitc4d7f8f50d53057005d6bb28ac487f69ea45bd5e (patch)
tree34968e205ebbb97264a2df4e8951af5a01db22cb
parentef01d3badb9765271a5bf55a4f0702b68f099e2e (diff)
downloadsite-c4d7f8f50d53057005d6bb28ac487f69ea45bd5e.tar.gz
site-c4d7f8f50d53057005d6bb28ac487f69ea45bd5e.zip
Add blog post parsing and templates
templates WIP, not building properly
-rw-r--r--Cargo.lock100
-rw-r--r--Cargo.toml18
-rw-r--r--src/blog/mod.rs12
-rw-r--r--src/blog/post.rs170
-rw-r--r--src/build.rs28
-rw-r--r--src/internal/markdown.rs21
-rw-r--r--src/internal/mod.rs16
-rw-r--r--src/main.rs19
-rw-r--r--templates/footer.rs.html12
-rw-r--r--templates/header.rs.html16
-rw-r--r--templates/index.rs.html9
11 files changed, 414 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 78f66fb..f71d922 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index d4b8657..ae5ffa0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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>