use std::{collections::HashMap, ops::ControlFlow}; use anyhow::{Context, anyhow}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; use tracing::{error, info_span, instrument}; use crate::{ config::Config, dirs::Dirs, doc::Doc, html::navmap::NavigationMap, import_map::ImportMap, parse::parse_tree_with_diagnostics, state::{Source, Treehouse, report_diagnostics}, tree::SemaRoots, vfs::{self, Cd, Content, VPath, VPathBuf}, }; pub struct Sources { pub config: Config, pub treehouse: Treehouse, pub navigation_map: NavigationMap, pub import_map: ImportMap, } impl Sources { pub fn load(dirs: &Dirs) -> anyhow::Result { let config = { let _span = info_span!("load_config").entered(); let mut config: Config = toml_edit::de::from_str( &vfs::query::(&dirs.root, VPath::new_const("treehouse.toml")) .map(Content::string) .ok_or_else(|| anyhow!("config file does not exist"))??, ) .context("failed to deserialize config")?; config.site = std::env::var("TREEHOUSE_SITE").unwrap_or(config.site); config.autopopulate_emoji(&*dirs.emoji)?; config.autopopulate_pics(&*dirs.pic)?; config.load_syntaxes(dirs.syntax.clone())?; config }; let treehouse = load_trees(&config, dirs)?; let navigation_map = NavigationMap::build( &treehouse, treehouse.files_by_tree_path[VPath::new("index")], ); let import_map = ImportMap::generate( &config.site, &Cd::new(dirs.static_.clone(), VPathBuf::new("js")), &config.build.javascript.import_roots, ); Ok(Sources { config, treehouse, navigation_map, import_map, }) } } #[instrument(skip(config, dirs))] fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result { let mut treehouse = Treehouse::new(); let mut diagnostics = vec![]; let mut parsed_trees = HashMap::new(); let mut paths = vec![]; let mut doc_paths = vec![]; vfs::walk_dir_rec(&*dirs.content, VPath::ROOT, &mut |path| { match path.extension() { Some("tree") => paths.push(path.to_owned()), Some("dj") => doc_paths.push(path.to_owned()), _ => (), } ControlFlow::Continue(()) }); // Trees // NOTE: Sources are filled in later; they can be left out until a call to report_diagnostics. let file_ids: Vec<_> = paths .iter() .map(|path| treehouse.add_file(path.clone(), Source::Other(String::new()))) .collect(); let parse_results: Vec<_> = { let _span = info_span!("load_trees::parse").entered(); paths .into_par_iter() .zip(&file_ids) .flat_map(|(path, &file_id)| { vfs::query::(&dirs.content, &path) .and_then(|c| c.string().ok()) .map(|input| { let parse_result = parse_tree_with_diagnostics(file_id, &input); (path, file_id, input, parse_result) }) }) .collect() }; for (path, file_id, input, _) in &parse_results { let tree_path = path.with_extension(""); treehouse .files_by_tree_path .insert(tree_path.clone(), *file_id); treehouse.set_source( *file_id, Source::Tree { input: input.clone(), tree_path, }, ); } { let _span = info_span!("load_trees::sema").entered(); for (path, file_id, _, result) in parse_results { match result { Ok(roots) => { let roots = SemaRoots::from_roots( &mut treehouse, &mut diagnostics, config, file_id, roots, ); treehouse.roots.insert(file_id, roots); parsed_trees.insert(path, file_id); } Err(mut parse_diagnostics) => diagnostics.append(&mut parse_diagnostics), } } } // Docs let mut doc_file_ids = vec![]; for path in &doc_paths { if let Some(input) = vfs::query::(&dirs.content, path).and_then(|c| c.string().ok()) { let file_id = treehouse.add_file(path.clone(), Source::Other(input)); treehouse.files_by_doc_path.insert(path.clone(), file_id); doc_file_ids.push(file_id); } else { error!( "doc {path} does not exist in content directory even though it was enumerated via walk_dir_rec" ); } } for file_id in doc_file_ids { let (doc, mut doc_diagnostics) = Doc::parse(&mut treehouse, config, file_id); treehouse.docs.insert(file_id, doc); diagnostics.append(&mut doc_diagnostics); } // Tags for file_id in treehouse.files_by_tree_path.values() { let roots = &treehouse.roots[file_id]; for tag_name in &roots.attributes.tags { let tag = treehouse.tags.entry(tag_name.clone()).or_default(); tag.files.push(*file_id); } } for file_id in treehouse.files_by_doc_path.values() { let doc = &treehouse.docs[file_id]; for tag_name in &doc.attributes.tags { let tag = treehouse.tags.entry(tag_name.clone()).or_default(); tag.files.push(*file_id); } } // Diagnostics report_diagnostics(&treehouse, &diagnostics)?; Ok(treehouse) }