diff --git a/content/about-treehouse.tree b/content/about-treehouse.tree index b7a38a0..6e7f4c7 100644 --- a/content/about-treehouse.tree +++ b/content/about-treehouse.tree @@ -105,7 +105,7 @@ - but without overwhelming your computer or bandwidth % id = "01H89RFHCQ1XA3BB3BTKXH36CX" - - you can disable the JavaScript, and everything will still work + - you can disable the JavaScript, and everything will mostly work % id = "01H89RFHCQS2WW7PBP1YV0BEJZ" - but you may not find the experience favorable @@ -123,7 +123,9 @@ + can you even call it that? % id = "01H89RFHCQ48R7BCZV8JWPVFCY" - - have I invented something new here? + + have I invented something new here? + + - the "Choose Your Own Poem" lol % id = "01H89RFHCQAXJ0ST31TP1A104V" + ### the treehouse is a mostly statically generated website diff --git a/crates/treehouse/src/cli/regenerate.rs b/crates/treehouse/src/cli/regenerate.rs index 8fb1f8b..5fb36e2 100644 --- a/crates/treehouse/src/cli/regenerate.rs +++ b/crates/treehouse/src/cli/regenerate.rs @@ -20,6 +20,7 @@ use walkdir::WalkDir; use crate::{ cli::parse::parse_tree_with_diagnostics, + config::Config, html::{navmap::build_navigation_map, tree::branches_to_html}, tree::SemaRoots, }; @@ -95,7 +96,7 @@ impl Generator { Ok(()) } - fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result { + fn generate_all_files(&self, config: &Config, paths: &Paths<'_>) -> anyhow::Result { let mut treehouse = Treehouse::new(); let mut handlebars = Handlebars::new(); @@ -103,7 +104,7 @@ impl Generator { &mut handlebars, &mut treehouse, "tree", - &dirs.template_dir.join("tree.hbs"), + &paths.template_dir.join("tree.hbs"), )?; struct ParsedTree { @@ -116,11 +117,11 @@ impl Generator { for path in &self.tree_files { let utf8_filename = path.to_string_lossy(); - let tree_path = path.strip_prefix(dirs.content_dir).unwrap_or(path); + let tree_path = path.strip_prefix(paths.content_dir).unwrap_or(path); let target_path = if tree_path == OsStr::new("index.tree") { - dirs.target_dir.join("index.html") + paths.target_dir.join("index.html") } else { - dirs.target_dir.join(tree_path).with_extension("html") + paths.target_dir.join(tree_path).with_extension("html") }; debug!("generating: {path:?} -> {target_path:?}"); @@ -162,12 +163,19 @@ impl Generator { branches_to_html( &mut tree, &mut treehouse, + config, parsed_tree.file_id, &roots.branches, ); treehouse.roots.insert(parsed_tree.tree_path, roots); - let template_data = TemplateData { tree }; + #[derive(Serialize)] + pub struct TemplateData<'a> { + pub config: &'a Config, + pub tree: String, + } + + let template_data = TemplateData { config, tree }; let templated_html = match handlebars.render("tree", &template_data) { Ok(html) => html, Err(error) => { @@ -196,37 +204,38 @@ impl Generator { } #[derive(Debug, Clone, Copy)] -pub struct Dirs<'a> { +pub struct Paths<'a> { pub target_dir: &'a Path, pub static_dir: &'a Path, pub template_dir: &'a Path, pub content_dir: &'a Path, + + pub config_file: &'a Path, } -#[derive(Serialize)] -pub struct TemplateData { - pub tree: String, -} - -pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> { +pub fn regenerate(paths: &Paths<'_>) -> anyhow::Result<()> { let start = Instant::now(); + info!("loading config"); + let mut config = Config::load(paths.config_file)?; + config.site = std::env::var("TREEHOUSE_SITE").unwrap_or(config.site); + info!("cleaning target directory"); - let _ = std::fs::remove_dir_all(dirs.target_dir); - std::fs::create_dir_all(dirs.target_dir)?; + let _ = std::fs::remove_dir_all(paths.target_dir); + std::fs::create_dir_all(paths.target_dir)?; info!("copying static directory to target directory"); - copy_dir(dirs.static_dir, dirs.target_dir.join("static"))?; + copy_dir(paths.static_dir, paths.target_dir.join("static"))?; info!("generating standalone pages"); let mut generator = Generator::default(); - generator.add_directory_rec(dirs.content_dir)?; - let treehouse = generator.generate_all_files(dirs)?; + generator.add_directory_rec(paths.content_dir)?; + let treehouse = generator.generate_all_files(&config, paths)?; info!("generating navigation map"); let navigation_map = build_navigation_map(&treehouse, "index"); std::fs::write( - dirs.target_dir.join("navmap.js"), + paths.target_dir.join("navmap.js"), navigation_map.to_javascript(), )?; @@ -238,10 +247,10 @@ pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> { Ok(()) } -pub fn regenerate_or_report_error(dirs: &Dirs<'_>) { +pub fn regenerate_or_report_error(paths: &Paths<'_>) { info!("regenerating site content"); - match regenerate(dirs) { + match regenerate(paths) { Ok(_) => (), Err(error) => eprintln!("error: {error:?}"), } diff --git a/crates/treehouse/src/config.rs b/crates/treehouse/src/config.rs new file mode 100644 index 0000000..7a728cc --- /dev/null +++ b/crates/treehouse/src/config.rs @@ -0,0 +1,25 @@ +use std::{collections::HashMap, path::Path}; + +use anyhow::Context; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Config { + /// Website root; used when generating links. + /// Can also be specified using the environment variable `$TREEHOUSE_SITE`. (this is the + /// preferred way of setting this in production, so as not to clobber treehouse.toml.) + pub site: String, + + /// User-defined keys. + pub user: HashMap, + + /// Links exported to Markdown for use with reference syntax `[text][key]`. + pub links: HashMap, +} + +impl Config { + pub fn load(path: &Path) -> anyhow::Result { + let string = std::fs::read_to_string(path).context("cannot read config file")?; + toml_edit::de::from_str(&string).context("error in config file") + } +} diff --git a/crates/treehouse/src/html/tree.rs b/crates/treehouse/src/html/tree.rs index 5a2c101..77a7c0b 100644 --- a/crates/treehouse/src/html/tree.rs +++ b/crates/treehouse/src/html/tree.rs @@ -4,6 +4,7 @@ use pulldown_cmark::{BrokenLink, LinkType}; use treehouse_format::pull::BranchKind; use crate::{ + config::Config, html::EscapeAttribute, state::{FileId, Treehouse}, tree::{attributes::Content, SemaBranchId}, @@ -14,6 +15,7 @@ use super::{markdown, EscapeHtml}; pub fn branch_to_html( s: &mut String, treehouse: &mut Treehouse, + config: &Config, file_id: FileId, branch_id: SemaBranchId, ) { @@ -110,7 +112,8 @@ pub fn branch_to_html( if let Content::Link(link) = &branch.attributes.content { write!( s, - "", + "", + EscapeAttribute(&config.site), EscapeAttribute(link), EscapeHtml(link), ) @@ -122,7 +125,8 @@ pub fn branch_to_html( if let Content::Link(link) = &branch.attributes.content { write!( s, - "", + "", + EscapeAttribute(&config.site), EscapeAttribute(link), ) .unwrap(); @@ -144,7 +148,7 @@ pub fn branch_to_html( let num_children = branch.children.len(); for i in 0..num_children { let child_id = treehouse.tree.branch(branch_id).children[i]; - branch_to_html(s, treehouse, file_id, child_id); + branch_to_html(s, treehouse, config, file_id, child_id); } s.push_str(""); } @@ -159,12 +163,13 @@ pub fn branch_to_html( pub fn branches_to_html( s: &mut String, treehouse: &mut Treehouse, + config: &Config, file_id: FileId, branches: &[SemaBranchId], ) { s.push_str("
    "); for &child in branches { - branch_to_html(s, treehouse, file_id, child); + branch_to_html(s, treehouse, config, file_id, child); } s.push_str("
"); } diff --git a/crates/treehouse/src/main.rs b/crates/treehouse/src/main.rs index c74243e..67b5cca 100644 --- a/crates/treehouse/src/main.rs +++ b/crates/treehouse/src/main.rs @@ -3,12 +3,13 @@ use std::path::Path; use clap::Parser; use cli::{ fix::fix_file_cli, - regenerate::{self, regenerate_or_report_error, Dirs}, + regenerate::{self, regenerate_or_report_error, Paths}, Command, ProgramArgs, }; use log::{error, info}; mod cli; +mod config; mod html; mod paths; mod state; @@ -19,8 +20,9 @@ async fn fallible_main() -> anyhow::Result<()> { match args.command { Command::Regenerate(regenerate_args) => { - let dirs = Dirs { + let dirs = Paths { target_dir: Path::new("target/site"), + config_file: Path::new("treehouse.toml"), // NOTE: These are intentionally left unconfigurable from within treehouse.toml // because this is is one of those things that should be consistent between sites. diff --git a/static/js/tree.js b/static/js/tree.js index 117e151..f3bcb87 100644 --- a/static/js/tree.js +++ b/static/js/tree.js @@ -1,3 +1,5 @@ +// This is definitely not a three.js ripoff. + import { navigationMap } from "/navmap.js"; const branchStateKey = "treehouse.openBranches"; @@ -68,7 +70,7 @@ class LinkedBranch extends Branch { async loadTreePromise(_initiator) { try { - let response = await fetch(`/${this.linkedTree}.html`); + let response = await fetch(`${TREEHOUSE_SITE}/${this.linkedTree}.html`); if (response.status == 404) { throw `Hmm, seems like the tree "${this.linkedTree}" does not exist.`; } @@ -107,7 +109,7 @@ function expandDetailsRecursively(element) { } function navigateToPage(page) { - window.location.pathname = `/${page}.html` + window.location.href = `${TREEHOUSE_SITE}/${page}.html` } async function navigateToBranch(fragment) { diff --git a/template/tree.hbs b/template/tree.hbs index 108dbec..4bcc4bb 100644 --- a/template/tree.hbs +++ b/template/tree.hbs @@ -9,18 +9,19 @@ - - - + + + - - - + + + +