diff options
| author | Christine Dodrill <me@christine.website> | 2020-09-19 11:33:46 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-19 11:33:46 -0400 |
| commit | a2fba89738caac83ce24d40b762d6205f2266361 (patch) | |
| tree | 3a61afb3b1a9d42b0a61b67be527322b6740f179 /src/app | |
| parent | 1e61d2ad33f7ac7751063bdd373ab3d1305015e2 (diff) | |
| download | xesite-a2fba89738caac83ce24d40b762d6205f2266361.tar.xz xesite-a2fba89738caac83ce24d40b762d6205f2266361.zip | |
TL;DR Rust (#210)
* start mara code
* better alt text
* more mara tests
* cleanups
* blog: start tl;dr rust post
* more words
* feature complete
* little oopses
* oops lol
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/markdown.rs | 79 | ||||
| -rw-r--r-- | src/app/mod.rs | 141 |
2 files changed, 220 insertions, 0 deletions
diff --git a/src/app/markdown.rs b/src/app/markdown.rs new file mode 100644 index 0000000..fe33a21 --- /dev/null +++ b/src/app/markdown.rs @@ -0,0 +1,79 @@ +use color_eyre::eyre::{Result, WrapErr}; +use comrak::nodes::{Ast, AstNode, NodeValue}; +use comrak::{format_html, parse_document, markdown_to_html, Arena, ComrakOptions}; +use std::cell::RefCell; +use crate::templates::Html; +use url::Url; + +pub fn render(inp: &str) -> Result<String> { + let mut options = ComrakOptions::default(); + + options.extension.autolink = true; + options.extension.table = true; + options.extension.description_lists = true; + options.extension.superscript = true; + options.extension.strikethrough = true; + options.extension.footnotes = true; + + options.render.unsafe_ = true; + + let arena = Arena::new(); + let root = parse_document(&arena, inp, &options); + + iter_nodes(root, &|node| { + let mut data = node.data.borrow_mut(); + match &mut data.value { + &mut NodeValue::Link(ref mut link) => { + let base = Url::parse("https://christine.website/")?; + let u = base.join(std::str::from_utf8(&link.url.clone())?)?; + if u.scheme() != "conversation" { + return Ok(()); + } + let parent = node.parent().unwrap(); + node.detach(); + let mut message = vec![]; + for child in node.children() { + format_html(child, &options, &mut message)?; + } + let message = std::str::from_utf8(&message)?; + let message = markdown_to_html(message, &options); + let mood = without_first(u.path()); + let name = u.host_str().unwrap_or("Mara"); + + let mut html = vec![]; + crate::templates::mara(&mut html, mood, name, Html(message))?; + + let new_node = + arena.alloc(AstNode::new(RefCell::new(Ast::new(NodeValue::HtmlInline(html))))); + parent.append(new_node); + + Ok(()) + } + _ => Ok(()), + } + })?; + + let mut html = vec![]; + format_html(root, &options, &mut html).unwrap(); + + String::from_utf8(html).wrap_err("post is somehow invalid UTF-8") +} + +fn iter_nodes<'a, F>(node: &'a AstNode<'a>, f: &F) -> Result<()> +where + F: Fn(&'a AstNode<'a>) -> Result<()>, +{ + f(node)?; + for c in node.children() { + iter_nodes(c, f)?; + } + Ok(()) +} + +fn without_first(string: &str) -> &str { + string + .char_indices() + .nth(1) + .and_then(|(i, _)| string.get(i..)) + .unwrap_or("") +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..44f05e7 --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,141 @@ +use crate::{post::Post, signalboost::Person}; +use color_eyre::eyre::Result; +use serde::Deserialize; +use std::{fs, path::PathBuf}; + +pub mod markdown; + +#[derive(Clone, Deserialize)] +pub struct Config { + #[serde(rename = "clackSet")] + clack_set: Vec<String>, + signalboost: Vec<Person>, + port: u16, + #[serde(rename = "resumeFname")] + resume_fname: PathBuf, +} + +async fn patrons() -> Result<Option<patreon::Users>> { + use patreon::*; + let creds: Credentials = envy::prefixed("PATREON_").from_env().unwrap(); + let cli = Client::new(creds); + + match cli.campaign().await { + Ok(camp) => { + let id = camp.data[0].id.clone(); + + match cli.pledges(id).await { + Ok(users) => Ok(Some(users)), + Err(why) => { + log::error!("error getting pledges: {:?}", why); + Ok(None) + } + } + } + Err(why) => { + log::error!("error getting patreon campaign: {:?}", why); + Ok(None) + } + } +} + +pub const ICON: &'static str = "https://christine.website/static/img/avatar.png"; + +pub struct State { + pub cfg: Config, + pub signalboost: Vec<Person>, + pub resume: String, + pub blog: Vec<Post>, + pub gallery: Vec<Post>, + pub talks: Vec<Post>, + pub everything: Vec<Post>, + pub jf: jsonfeed::Feed, + pub sitemap: Vec<u8>, + pub patrons: Option<patreon::Users>, +} + +pub async fn init(cfg: PathBuf) -> Result<State> { + let cfg: Config = serde_dhall::from_file(cfg).parse()?; + let sb = cfg.signalboost.clone(); + let resume = fs::read_to_string(cfg.resume_fname.clone())?; + let resume: String = markdown::render(&resume)?; + let blog = crate::post::load("blog")?; + let gallery = crate::post::load("gallery")?; + let talks = crate::post::load("talks")?; + let mut everything: Vec<Post> = vec![]; + + { + let blog = blog.clone(); + let gallery = gallery.clone(); + let talks = talks.clone(); + everything.extend(blog.iter().cloned()); + everything.extend(gallery.iter().cloned()); + everything.extend(talks.iter().cloned()); + }; + + everything.sort(); + everything.reverse(); + + let mut jfb = jsonfeed::Feed::builder() + .title("Christine Dodrill's Blog") + .description("My blog posts and rants about various technology things.") + .author( + jsonfeed::Author::new() + .name("Christine Dodrill") + .url("https://christine.website") + .avatar(ICON), + ) + .feed_url("https://christine.website/blog.json") + .user_comment("This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1") + .home_page_url("https://christine.website") + .icon(ICON) + .favicon(ICON); + + for post in &everything { + let post = post.clone(); + jfb = jfb.item(post.clone().into()); + } + + let mut sm: Vec<u8> = vec![]; + let smw = sitemap::writer::SiteMapWriter::new(&mut sm); + let mut urlwriter = smw.start_urlset()?; + for url in &[ + "https://christine.website/resume", + "https://christine.website/contact", + "https://christine.website/", + "https://christine.website/blog", + "https://christine.website/signalboost", + ] { + urlwriter.url(*url)?; + } + + for post in &everything { + urlwriter.url(format!("https://christine.website/{}", post.link))?; + } + + urlwriter.end()?; + + Ok(State { + cfg: cfg, + signalboost: sb, + resume: resume, + blog: blog, + gallery: gallery, + talks: talks, + everything: everything, + jf: jfb.build(), + sitemap: sm, + patrons: patrons().await?, + }) +} + +#[cfg(test)] +mod tests { + use color_eyre::eyre::Result; + #[tokio::test] + async fn init() -> Result<()> { + let _ = pretty_env_logger::try_init(); + super::init("./config.dhall".into()).await?; + Ok(()) + } +} |
