aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2020-09-19 11:33:46 -0400
committerGitHub <noreply@github.com>2020-09-19 11:33:46 -0400
commita2fba89738caac83ce24d40b762d6205f2266361 (patch)
tree3a61afb3b1a9d42b0a61b67be527322b6740f179 /src/app
parent1e61d2ad33f7ac7751063bdd373ab3d1305015e2 (diff)
downloadxesite-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.rs79
-rw-r--r--src/app/mod.rs141
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(())
+ }
+}