refactoring: remove dependency on SimpleFiles, make tree parsing multithreaded
This commit is contained in:
parent
505163383f
commit
0713b59063
|
@ -108,7 +108,8 @@ pub fn fix_file(
|
||||||
diagnostics: &mut Vec<Diagnostic<FileId>>,
|
diagnostics: &mut Vec<Diagnostic<FileId>>,
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
) -> Result<String, parse::ErrorsEmitted> {
|
) -> Result<String, parse::ErrorsEmitted> {
|
||||||
parse_tree_with_diagnostics(treehouse, file_id)
|
let source = treehouse.source(file_id).input();
|
||||||
|
parse_tree_with_diagnostics(file_id, source)
|
||||||
.map(|roots| {
|
.map(|roots| {
|
||||||
let mut source = treehouse.source(file_id).input().to_owned();
|
let mut source = treehouse.source(file_id).input().to_owned();
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
|
@ -146,7 +147,7 @@ pub fn fix_file_cli(fix_args: FixArgs, root: &dyn Dir) -> anyhow::Result<Edit> {
|
||||||
|
|
||||||
let mut treehouse = Treehouse::new();
|
let mut treehouse = Treehouse::new();
|
||||||
let mut diagnostics = vec![];
|
let mut diagnostics = vec![];
|
||||||
let file_id = treehouse.add_file(fix_args.file.as_str().to_owned(), Source::Other(file));
|
let file_id = treehouse.add_file(fix_args.file.clone(), Source::Other(file));
|
||||||
let edit_path = root.edit_path(&fix_args.file).ok_or_else(|| {
|
let edit_path = root.edit_path(&fix_args.file).ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"{} is not an editable file (perhaps it is not in a persistent path?)",
|
"{} is not an editable file (perhaps it is not in a persistent path?)",
|
||||||
|
@ -178,7 +179,7 @@ pub fn fix_file_cli(fix_args: FixArgs, root: &dyn Dir) -> anyhow::Result<Edit> {
|
||||||
Edit::NoOp
|
Edit::NoOp
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report_diagnostics(&treehouse.files, &diagnostics)?;
|
report_diagnostics(&treehouse, &diagnostics)?;
|
||||||
Edit::NoOp
|
Edit::NoOp
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -196,7 +197,7 @@ pub fn fix_all_cli(fix_all_args: FixAllArgs, dir: &dyn Dir) -> anyhow::Result<Ed
|
||||||
|
|
||||||
let mut treehouse = Treehouse::new();
|
let mut treehouse = Treehouse::new();
|
||||||
let mut diagnostics = vec![];
|
let mut diagnostics = vec![];
|
||||||
let file_id = treehouse.add_file(path.as_str().to_string(), Source::Other(content));
|
let file_id = treehouse.add_file(path.to_owned(), Source::Other(content));
|
||||||
let edit_path = dir.edit_path(path).context("path is not editable")?;
|
let edit_path = dir.edit_path(path).context("path is not editable")?;
|
||||||
|
|
||||||
if let Ok(fixed) = fix_file(&mut treehouse, &mut diagnostics, file_id) {
|
if let Ok(fixed) = fix_file(&mut treehouse, &mut diagnostics, file_id) {
|
||||||
|
@ -204,7 +205,7 @@ pub fn fix_all_cli(fix_all_args: FixAllArgs, dir: &dyn Dir) -> anyhow::Result<Ed
|
||||||
return Ok(Edit::Write(edit_path, fixed));
|
return Ok(Edit::Write(edit_path, fixed));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report_diagnostics(&treehouse.files, &diagnostics)?;
|
report_diagnostics(&treehouse, &diagnostics)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,13 +144,12 @@ async fn branch(RawQuery(named_id): RawQuery, State(state): State<Arc<Server>>)
|
||||||
});
|
});
|
||||||
if let Some(branch_id) = branch_id {
|
if let Some(branch_id) = branch_id {
|
||||||
let branch = state.sources.treehouse.tree.branch(branch_id);
|
let branch = state.sources.treehouse.tree.branch(branch_id);
|
||||||
if let Source::Tree {
|
if let Source::Tree { input, tree_path } =
|
||||||
input, target_path, ..
|
state.sources.treehouse.source(branch.file_id)
|
||||||
} = state.sources.treehouse.source(branch.file_id)
|
|
||||||
{
|
{
|
||||||
if let Some(content) = state
|
if let Some(content) = state
|
||||||
.target
|
.target
|
||||||
.content(target_path)
|
.content(tree_path)
|
||||||
.await
|
.await
|
||||||
.and_then(|s| String::from_utf8(s).ok())
|
.and_then(|s| String::from_utf8(s).ok())
|
||||||
{
|
{
|
||||||
|
@ -172,7 +171,7 @@ async fn branch(RawQuery(named_id): RawQuery, State(state): State<Arc<Server>>)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
format!("500 Internal Server Error: branch metadata points to entry {target_path} which does not have readable content")
|
format!("500 Internal Server Error: branch metadata points to entry {tree_path} which does not have readable content")
|
||||||
)
|
)
|
||||||
.into_response();
|
.into_response();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,16 +47,16 @@ pub fn wc_cli(content_dir: &dyn Dir, mut wc_args: WcArgs) -> anyhow::Result<()>
|
||||||
.content(path)
|
.content(path)
|
||||||
.and_then(|b| String::from_utf8(b).ok())
|
.and_then(|b| String::from_utf8(b).ok())
|
||||||
{
|
{
|
||||||
let file_id = treehouse.add_file(path.to_string(), Source::Other(content));
|
let file_id = treehouse.add_file(path.clone(), Source::Other(content.clone()));
|
||||||
match parse_tree_with_diagnostics(&mut treehouse, file_id) {
|
match parse_tree_with_diagnostics(file_id, &content) {
|
||||||
Ok(parsed) => {
|
Ok(parsed) => {
|
||||||
let source = treehouse.source(file_id);
|
let source = treehouse.source(file_id);
|
||||||
let word_count = wc_roots(source.input(), &parsed);
|
let word_count = wc_roots(source.input(), &parsed);
|
||||||
println!("{word_count:>8} {}", treehouse.filename(file_id));
|
println!("{word_count:>8} {}", treehouse.path(file_id));
|
||||||
total += word_count;
|
total += word_count;
|
||||||
}
|
}
|
||||||
Err(diagnostics) => {
|
Err(diagnostics) => {
|
||||||
report_diagnostics(&treehouse.files, &diagnostics)?;
|
report_diagnostics(&treehouse, &diagnostics)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ mod include_static_helper;
|
||||||
use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc};
|
use std::{collections::HashMap, fmt, ops::ControlFlow, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{anyhow, ensure, Context};
|
use anyhow::{anyhow, ensure, Context};
|
||||||
use codespan_reporting::diagnostic::Diagnostic;
|
|
||||||
use dir_helper::DirHelper;
|
use dir_helper::DirHelper;
|
||||||
use handlebars::{handlebars_helper, Handlebars};
|
use handlebars::{handlebars_helper, Handlebars};
|
||||||
use include_static_helper::IncludeStaticHelper;
|
use include_static_helper::IncludeStaticHelper;
|
||||||
|
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::{error, info_span, instrument};
|
use tracing::{error, info_span, instrument};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
html::{breadcrumbs::breadcrumbs_to_html, navmap::NavigationMap, tree::branches_to_html},
|
html::{breadcrumbs::breadcrumbs_to_html, navmap::NavigationMap, tree::branches_to_html},
|
||||||
import_map::ImportMap,
|
import_map::ImportMap,
|
||||||
parse::parse_tree_with_diagnostics,
|
parse::parse_tree_with_diagnostics,
|
||||||
state::{report_diagnostics, Source},
|
state::{report_diagnostics, FileId, Source},
|
||||||
tree::SemaRoots,
|
tree::SemaRoots,
|
||||||
vfs::{
|
vfs::{
|
||||||
self, Cd, ContentCache, Dir, DirEntry, DynDir, EditPath, ImageSize, MemDir, Overlay,
|
self, Cd, ContentCache, Dir, DirEntry, DynDir, EditPath, ImageSize, MemDir, Overlay,
|
||||||
|
@ -26,13 +26,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::state::{FileId, Treehouse};
|
use crate::state::Treehouse;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ParsedTree {
|
|
||||||
root_key: String,
|
|
||||||
file_id: FileId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Page {
|
struct Page {
|
||||||
|
@ -96,84 +90,81 @@ fn load_templates(handlebars: &mut Handlebars, dir: &dyn Dir) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(treehouse, config, source, target_path, tree_path))]
|
|
||||||
fn parse_tree(
|
|
||||||
treehouse: &mut Treehouse,
|
|
||||||
config: &Config,
|
|
||||||
source: String,
|
|
||||||
source_path: VPathBuf,
|
|
||||||
target_path: VPathBuf,
|
|
||||||
tree_path: String,
|
|
||||||
) -> anyhow::Result<(Option<ParsedTree>, Vec<Diagnostic<FileId>>)> {
|
|
||||||
let file_id = treehouse.add_file(
|
|
||||||
source_path.as_str().to_owned(),
|
|
||||||
Source::Tree {
|
|
||||||
input: source,
|
|
||||||
target_path: target_path.clone(),
|
|
||||||
tree_path: tree_path.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match parse_tree_with_diagnostics(treehouse, file_id) {
|
|
||||||
Ok(roots) => {
|
|
||||||
let mut diagnostics = vec![];
|
|
||||||
let roots = SemaRoots::from_roots(treehouse, &mut diagnostics, config, file_id, roots);
|
|
||||||
|
|
||||||
let root_key = tree_path.clone();
|
|
||||||
treehouse.roots.insert(root_key.clone(), roots);
|
|
||||||
|
|
||||||
Ok((Some(ParsedTree { root_key, file_id }), diagnostics))
|
|
||||||
}
|
|
||||||
Err(diagnostics) => Ok((None, diagnostics)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(config, dirs))]
|
#[instrument(skip(config, dirs))]
|
||||||
fn parse_trees(
|
fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result<Treehouse> {
|
||||||
config: &Config,
|
|
||||||
dirs: &Dirs,
|
|
||||||
) -> anyhow::Result<(Treehouse, HashMap<VPathBuf, ParsedTree>)> {
|
|
||||||
let mut treehouse = Treehouse::new();
|
let mut treehouse = Treehouse::new();
|
||||||
let mut diagnostics = vec![];
|
let mut diagnostics = vec![];
|
||||||
let mut parsed_trees = HashMap::new();
|
let mut parsed_trees = HashMap::new();
|
||||||
|
|
||||||
|
let mut paths = vec![];
|
||||||
|
|
||||||
vfs::walk_dir_rec(&*dirs.content, VPath::ROOT, &mut |path| {
|
vfs::walk_dir_rec(&*dirs.content, VPath::ROOT, &mut |path| {
|
||||||
if path.extension() == Some("tree") {
|
if path.extension() == Some("tree") {
|
||||||
if let Some(source) = dirs
|
paths.push(path.to_owned());
|
||||||
.content
|
|
||||||
.content(path)
|
|
||||||
.and_then(|b| String::from_utf8(b).ok())
|
|
||||||
{
|
|
||||||
let tree_path = path.with_extension("");
|
|
||||||
let target_path = path.with_extension("html");
|
|
||||||
|
|
||||||
match parse_tree(
|
|
||||||
&mut treehouse,
|
|
||||||
config,
|
|
||||||
source,
|
|
||||||
path.to_owned(),
|
|
||||||
target_path,
|
|
||||||
tree_path.as_str().to_owned(),
|
|
||||||
) {
|
|
||||||
Ok((parsed_tree, mut parse_diagnostics)) => {
|
|
||||||
diagnostics.append(&mut parse_diagnostics);
|
|
||||||
if let Some(parsed_tree) = parsed_tree {
|
|
||||||
parsed_trees.insert(tree_path, parsed_tree);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!("failed to parse tree {path}: {err:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ControlFlow::Continue(())
|
ControlFlow::Continue(())
|
||||||
});
|
});
|
||||||
|
|
||||||
report_diagnostics(&treehouse.files, &diagnostics)?;
|
// 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();
|
||||||
|
|
||||||
Ok((treehouse, parsed_trees))
|
let parse_results: Vec<_> = {
|
||||||
|
let _span = info_span!("load_trees::parse").entered();
|
||||||
|
paths
|
||||||
|
.into_par_iter()
|
||||||
|
.zip(&file_ids)
|
||||||
|
.flat_map(|(path, &file_id)| {
|
||||||
|
dirs.content
|
||||||
|
.content(&path)
|
||||||
|
.and_then(|b| String::from_utf8(b).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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report_diagnostics(&treehouse, &diagnostics)?;
|
||||||
|
|
||||||
|
Ok(treehouse)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(sources, handlebars))]
|
#[instrument(skip(sources, handlebars))]
|
||||||
|
@ -205,23 +196,19 @@ fn generate_simple_template_or_error(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(sources, dirs, handlebars, parsed_tree), fields(root_key = parsed_tree.root_key))]
|
#[instrument(skip(sources, dirs, handlebars))]
|
||||||
fn generate_tree(
|
fn generate_tree(
|
||||||
sources: &Sources,
|
sources: &Sources,
|
||||||
dirs: &Dirs,
|
dirs: &Dirs,
|
||||||
handlebars: &Handlebars,
|
handlebars: &Handlebars,
|
||||||
parsed_tree: &ParsedTree,
|
file_id: FileId,
|
||||||
) -> anyhow::Result<String> {
|
) -> anyhow::Result<String> {
|
||||||
let breadcrumbs = breadcrumbs_to_html(
|
let breadcrumbs = breadcrumbs_to_html(&sources.config, &sources.navigation_map, file_id);
|
||||||
&sources.config,
|
|
||||||
&sources.navigation_map,
|
|
||||||
&parsed_tree.root_key,
|
|
||||||
);
|
|
||||||
|
|
||||||
let roots = sources
|
let roots = sources
|
||||||
.treehouse
|
.treehouse
|
||||||
.roots
|
.roots
|
||||||
.get(&parsed_tree.root_key)
|
.get(&file_id)
|
||||||
.expect("tree should have been added to the treehouse");
|
.expect("tree should have been added to the treehouse");
|
||||||
|
|
||||||
let tree = {
|
let tree = {
|
||||||
|
@ -232,7 +219,7 @@ fn generate_tree(
|
||||||
&sources.treehouse,
|
&sources.treehouse,
|
||||||
&sources.config,
|
&sources.config,
|
||||||
dirs,
|
dirs,
|
||||||
parsed_tree.file_id,
|
file_id,
|
||||||
&roots.branches,
|
&roots.branches,
|
||||||
);
|
);
|
||||||
tree
|
tree
|
||||||
|
@ -261,10 +248,7 @@ fn generate_tree(
|
||||||
scripts: roots.attributes.scripts.clone(),
|
scripts: roots.attributes.scripts.clone(),
|
||||||
styles: roots.attributes.styles.clone(),
|
styles: roots.attributes.styles.clone(),
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
tree_path: sources
|
tree_path: sources.treehouse.tree_path(file_id).map(|s| s.to_string()),
|
||||||
.treehouse
|
|
||||||
.tree_path(parsed_tree.file_id)
|
|
||||||
.map(|s| s.to_owned()),
|
|
||||||
tree,
|
tree,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -289,9 +273,9 @@ fn generate_tree_or_error(
|
||||||
sources: &Sources,
|
sources: &Sources,
|
||||||
dirs: &Dirs,
|
dirs: &Dirs,
|
||||||
handlebars: &Handlebars,
|
handlebars: &Handlebars,
|
||||||
parsed_tree: &ParsedTree,
|
file_id: FileId,
|
||||||
) -> String {
|
) -> String {
|
||||||
match generate_tree(sources, dirs, handlebars, parsed_tree) {
|
match generate_tree(sources, dirs, handlebars, file_id) {
|
||||||
Ok(html) => html,
|
Ok(html) => html,
|
||||||
Err(error) => format!("error: {error:?}"),
|
Err(error) => format!("error: {error:?}"),
|
||||||
}
|
}
|
||||||
|
@ -300,7 +284,6 @@ fn generate_tree_or_error(
|
||||||
pub struct Sources {
|
pub struct Sources {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
pub treehouse: Treehouse,
|
pub treehouse: Treehouse,
|
||||||
pub parsed_trees: HashMap<VPathBuf, ParsedTree>,
|
|
||||||
pub navigation_map: NavigationMap,
|
pub navigation_map: NavigationMap,
|
||||||
pub import_map: ImportMap,
|
pub import_map: ImportMap,
|
||||||
}
|
}
|
||||||
|
@ -324,8 +307,11 @@ impl Sources {
|
||||||
config
|
config
|
||||||
};
|
};
|
||||||
|
|
||||||
let (treehouse, parsed_trees) = parse_trees(&config, dirs)?;
|
let treehouse = load_trees(&config, dirs)?;
|
||||||
let navigation_map = NavigationMap::build(&treehouse, "index");
|
let navigation_map = NavigationMap::build(
|
||||||
|
&treehouse,
|
||||||
|
treehouse.files_by_tree_path[VPath::new("index")],
|
||||||
|
);
|
||||||
let import_map = ImportMap::generate(
|
let import_map = ImportMap::generate(
|
||||||
&config.site,
|
&config.site,
|
||||||
&Cd::new(dirs.static_.clone(), VPathBuf::new("js")),
|
&Cd::new(dirs.static_.clone(), VPathBuf::new("js")),
|
||||||
|
@ -335,7 +321,6 @@ impl Sources {
|
||||||
Ok(Sources {
|
Ok(Sources {
|
||||||
config,
|
config,
|
||||||
treehouse,
|
treehouse,
|
||||||
parsed_trees,
|
|
||||||
navigation_map,
|
navigation_map,
|
||||||
import_map,
|
import_map,
|
||||||
})
|
})
|
||||||
|
@ -429,11 +414,11 @@ impl Dir for TreehouseDir {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sources
|
self.sources
|
||||||
.parsed_trees
|
.treehouse
|
||||||
|
.files_by_tree_path
|
||||||
.get(path)
|
.get(path)
|
||||||
.map(|parsed_tree| {
|
.map(|&file_id| {
|
||||||
generate_tree_or_error(&self.sources, &self.dirs, &self.handlebars, parsed_tree)
|
generate_tree_or_error(&self.sources, &self.dirs, &self.handlebars, file_id).into()
|
||||||
.into()
|
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
if path.file_name().is_some_and(|s| !s.starts_with('_')) {
|
if path.file_name().is_some_and(|s| !s.starts_with('_')) {
|
||||||
|
@ -525,7 +510,7 @@ pub fn target(dirs: Arc<Dirs>, sources: Arc<Sources>) -> DynDir {
|
||||||
Cd::new(dirs.static_.clone(), VPathBuf::new("robots.txt")).to_dyn(),
|
Cd::new(dirs.static_.clone(), VPathBuf::new("robots.txt")).to_dyn(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let dir_index = DirIndex::new(sources.parsed_trees.keys().map(|x| &**x));
|
let dir_index = DirIndex::new(sources.treehouse.files_by_tree_path.keys().map(|x| &**x));
|
||||||
let tree_view = TreehouseDir::new(dirs, sources, dir_index);
|
let tree_view = TreehouseDir::new(dirs, sources, dir_index);
|
||||||
|
|
||||||
let tree_view = ContentCache::new(tree_view);
|
let tree_view = ContentCache::new(tree_view);
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, fmt::Write};
|
||||||
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::{config::Config, state::FileId, vfs::VPath};
|
||||||
|
|
||||||
use super::{navmap::NavigationMap, EscapeAttribute};
|
use super::{navmap::NavigationMap, EscapeAttribute};
|
||||||
|
|
||||||
|
@ -10,26 +10,31 @@ use super::{navmap::NavigationMap, EscapeAttribute};
|
||||||
pub fn breadcrumbs_to_html(
|
pub fn breadcrumbs_to_html(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
navigation_map: &NavigationMap,
|
navigation_map: &NavigationMap,
|
||||||
tree_path: &str,
|
file_id: FileId,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
|
|
||||||
if let Some(path) = navigation_map.paths.get(tree_path) {
|
if let Some(path) = navigation_map.paths.get(&file_id) {
|
||||||
for (i, element) in path.iter().enumerate() {
|
for (i, element) in path.iter().enumerate() {
|
||||||
// Skip the index because it's implied by the logo on the left.
|
// Skip the index because it's implied by the logo on the left.
|
||||||
if element != "index" {
|
if &**element != VPath::new_const("index") {
|
||||||
s.push_str("<li class=\"breadcrumb\">");
|
s.push_str("<li class=\"breadcrumb\">");
|
||||||
{
|
{
|
||||||
let short_element = path
|
let short_element = path
|
||||||
.get(i - 1)
|
.get(i - 1)
|
||||||
.map(|p| format!("{p}/"))
|
.map(|p| format!("{p}/"))
|
||||||
.and_then(|prefix| element.strip_prefix(prefix.as_str()).map(Cow::Borrowed))
|
.and_then(|prefix| {
|
||||||
|
element
|
||||||
|
.as_str()
|
||||||
|
.strip_prefix(prefix.as_str())
|
||||||
|
.map(Cow::Borrowed)
|
||||||
|
})
|
||||||
.unwrap_or_else(|| Cow::Owned(format!("/{element}")));
|
.unwrap_or_else(|| Cow::Owned(format!("/{element}")));
|
||||||
write!(
|
write!(
|
||||||
s,
|
s,
|
||||||
"<a href=\"{site}/{element}\">{short_element}</a>",
|
"<a href=\"{site}/{element}\">{short_element}</a>",
|
||||||
site = EscapeAttribute(&config.site),
|
site = EscapeAttribute(&config.site),
|
||||||
element = EscapeAttribute(element)
|
element = EscapeAttribute(element.as_str())
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,23 @@ use std::collections::HashMap;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
state::Treehouse,
|
state::{FileId, Treehouse},
|
||||||
tree::{attributes::Content, SemaBranchId},
|
tree::{attributes::Content, SemaBranchId},
|
||||||
|
vfs::VPathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
struct NavigationMapBuilder {
|
struct NavigationMapBuilder {
|
||||||
stack: Vec<String>,
|
stack: Vec<VPathBuf>,
|
||||||
navigation_map: NavigationMap,
|
navigation_map: NavigationMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigationMapBuilder {
|
impl NavigationMapBuilder {
|
||||||
fn enter_tree(&mut self, tree: String) {
|
fn enter_tree(&mut self, file_id: FileId, tree_path: VPathBuf) {
|
||||||
self.stack.push(tree.clone());
|
self.stack.push(tree_path.clone());
|
||||||
self.navigation_map.paths.insert(tree, self.stack.clone());
|
self.navigation_map
|
||||||
|
.paths
|
||||||
|
.insert(file_id, self.stack.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_tree(&mut self) {
|
fn exit_tree(&mut self) {
|
||||||
|
@ -31,12 +34,12 @@ impl NavigationMapBuilder {
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct NavigationMap {
|
pub struct NavigationMap {
|
||||||
/// Tells you which pages need to be opened to get to the key.
|
/// Tells you which pages need to be opened to get to the key.
|
||||||
pub paths: HashMap<String, Vec<String>>,
|
pub paths: HashMap<FileId, Vec<VPathBuf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NavigationMap {
|
impl NavigationMap {
|
||||||
#[instrument(name = "NavigationMap::build", skip(treehouse))]
|
#[instrument(name = "NavigationMap::build", skip(treehouse))]
|
||||||
pub fn build(treehouse: &Treehouse, root_tree_path: &str) -> Self {
|
pub fn build(treehouse: &Treehouse, root_file_id: FileId) -> Self {
|
||||||
let mut builder = NavigationMapBuilder::default();
|
let mut builder = NavigationMapBuilder::default();
|
||||||
|
|
||||||
fn rec_branch(
|
fn rec_branch(
|
||||||
|
@ -45,8 +48,8 @@ impl NavigationMap {
|
||||||
branch_id: SemaBranchId,
|
branch_id: SemaBranchId,
|
||||||
) {
|
) {
|
||||||
let branch = treehouse.tree.branch(branch_id);
|
let branch = treehouse.tree.branch(branch_id);
|
||||||
if let Content::Link(linked) = &branch.attributes.content {
|
if let Content::ResolvedLink(linked) = &branch.attributes.content {
|
||||||
rec_tree(treehouse, builder, linked);
|
rec_tree(treehouse, builder, *linked);
|
||||||
} else {
|
} else {
|
||||||
for &child_id in &branch.children {
|
for &child_id in &branch.children {
|
||||||
rec_branch(treehouse, builder, child_id);
|
rec_branch(treehouse, builder, child_id);
|
||||||
|
@ -54,12 +57,18 @@ impl NavigationMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rec_tree(treehouse: &Treehouse, builder: &mut NavigationMapBuilder, tree_path: &str) {
|
fn rec_tree(treehouse: &Treehouse, builder: &mut NavigationMapBuilder, file_id: FileId) {
|
||||||
if let Some(roots) = treehouse.roots.get(tree_path) {
|
if let Some(roots) = treehouse.roots.get(&file_id) {
|
||||||
// Pages can link to each other causing infinite recursion, so we need to handle that
|
// Pages can link to each other causing infinite recursion, so we need to handle that
|
||||||
// case by skipping pages that already have been analyzed.
|
// case by skipping pages that already have been analyzed.
|
||||||
if !builder.navigation_map.paths.contains_key(tree_path) {
|
if !builder.navigation_map.paths.contains_key(&file_id) {
|
||||||
builder.enter_tree(tree_path.to_owned());
|
builder.enter_tree(
|
||||||
|
file_id,
|
||||||
|
treehouse
|
||||||
|
.tree_path(file_id)
|
||||||
|
.expect("tree files may only link to other tree files")
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
for &branch_id in &roots.branches {
|
for &branch_id in &roots.branches {
|
||||||
rec_branch(treehouse, builder, branch_id);
|
rec_branch(treehouse, builder, branch_id);
|
||||||
}
|
}
|
||||||
|
@ -68,7 +77,7 @@ impl NavigationMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rec_tree(treehouse, &mut builder, root_tree_path);
|
rec_tree(treehouse, &mut builder, root_file_id);
|
||||||
|
|
||||||
builder.finish()
|
builder.finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ pub fn branch_to_html(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_children =
|
let has_children = !branch.children.is_empty()
|
||||||
!branch.children.is_empty() || matches!(branch.attributes.content, Content::Link(_));
|
|| matches!(branch.attributes.content, Content::ResolvedLink(_));
|
||||||
|
|
||||||
let class = if has_children { "branch" } else { "leaf" };
|
let class = if has_children { "branch" } else { "leaf" };
|
||||||
let mut class = String::from(class);
|
let mut class = String::from(class);
|
||||||
|
@ -44,7 +44,7 @@ pub fn branch_to_html(
|
||||||
class.push_str(" draft");
|
class.push_str(" draft");
|
||||||
}
|
}
|
||||||
|
|
||||||
let component = if let Content::Link(_) = branch.attributes.content {
|
let component = if let Content::ResolvedLink(_) = branch.attributes.content {
|
||||||
"b-linked"
|
"b-linked"
|
||||||
} else {
|
} else {
|
||||||
"b"
|
"b"
|
||||||
|
@ -55,8 +55,9 @@ pub fn branch_to_html(
|
||||||
Cow::Borrowed(component)
|
Cow::Borrowed(component)
|
||||||
};
|
};
|
||||||
|
|
||||||
let linked_branch = if let Content::Link(link) = &branch.attributes.content {
|
let linked_branch = if let Content::ResolvedLink(file_id) = &branch.attributes.content {
|
||||||
format!(" data-th-link=\"{}\"", EscapeHtml(link))
|
let path = treehouse.tree_path(*file_id).expect(".tree file expected");
|
||||||
|
format!(" data-th-link=\"{}\"", EscapeHtml(path.as_str()))
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
|
@ -126,7 +127,7 @@ pub fn branch_to_html(
|
||||||
page_id: treehouse
|
page_id: treehouse
|
||||||
.tree_path(file_id)
|
.tree_path(file_id)
|
||||||
.expect(".tree file expected")
|
.expect(".tree file expected")
|
||||||
.to_owned(),
|
.to_string(),
|
||||||
|
|
||||||
config,
|
config,
|
||||||
dirs,
|
dirs,
|
||||||
|
@ -137,13 +138,14 @@ pub fn branch_to_html(
|
||||||
.render(&events, s);
|
.render(&events, s);
|
||||||
|
|
||||||
let branch = treehouse.tree.branch(branch_id);
|
let branch = treehouse.tree.branch(branch_id);
|
||||||
if let Content::Link(link) = &branch.attributes.content {
|
if let Content::ResolvedLink(file_id) = &branch.attributes.content {
|
||||||
|
let path = treehouse.tree_path(*file_id).expect(".tree file expected");
|
||||||
write!(
|
write!(
|
||||||
s,
|
s,
|
||||||
"<noscript><a class=\"navigate icon-go\" href=\"{}/{}\">Go to linked tree: <code>{}</code></a></noscript>",
|
"<noscript><a class=\"navigate icon-go\" href=\"{}/{}\">Go to linked tree: <code>{}</code></a></noscript>",
|
||||||
EscapeAttribute(&config.site),
|
EscapeAttribute(&config.site),
|
||||||
EscapeAttribute(link),
|
EscapeAttribute(path.as_str()),
|
||||||
EscapeHtml(link),
|
EscapeHtml(path.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -151,12 +153,13 @@ pub fn branch_to_html(
|
||||||
|
|
||||||
s.push_str("<th-bb>");
|
s.push_str("<th-bb>");
|
||||||
{
|
{
|
||||||
if let Content::Link(link) = &branch.attributes.content {
|
if let Content::ResolvedLink(file_id) = &branch.attributes.content {
|
||||||
|
let path = treehouse.tree_path(*file_id).expect(".tree file expected");
|
||||||
write!(
|
write!(
|
||||||
s,
|
s,
|
||||||
"<a class=\"icon icon-go\" href=\"{}/{}\" title=\"linked tree\"></a>",
|
"<a class=\"icon icon-go\" href=\"{}/{}\" title=\"linked tree\"></a>",
|
||||||
EscapeAttribute(&config.site),
|
EscapeAttribute(&config.site),
|
||||||
EscapeAttribute(link),
|
EscapeAttribute(path.as_str()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -8,12 +8,11 @@ use crate::state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse};
|
||||||
|
|
||||||
pub struct ErrorsEmitted;
|
pub struct ErrorsEmitted;
|
||||||
|
|
||||||
#[instrument(skip(treehouse))]
|
#[instrument(skip(input))]
|
||||||
pub fn parse_tree_with_diagnostics(
|
pub fn parse_tree_with_diagnostics(
|
||||||
treehouse: &mut Treehouse,
|
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
|
input: &str,
|
||||||
) -> Result<Roots, Vec<Diagnostic<FileId>>> {
|
) -> Result<Roots, Vec<Diagnostic<FileId>>> {
|
||||||
let input = &treehouse.source(file_id).input();
|
|
||||||
Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
|
Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
|
||||||
vec![Diagnostic {
|
vec![Diagnostic {
|
||||||
severity: Severity::Error,
|
severity: Severity::Error,
|
||||||
|
|
|
@ -3,23 +3,19 @@ use std::{collections::HashMap, ops::Range};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use codespan_reporting::{
|
use codespan_reporting::{
|
||||||
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
||||||
files::SimpleFiles,
|
|
||||||
term::termcolor::{ColorChoice, StandardStream},
|
term::termcolor::{ColorChoice, StandardStream},
|
||||||
};
|
};
|
||||||
|
use tracing::instrument;
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
tree::{SemaBranchId, SemaRoots, SemaTree},
|
tree::{SemaBranchId, SemaRoots, SemaTree},
|
||||||
vfs::VPathBuf,
|
vfs::{VPath, VPathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
Tree {
|
Tree { input: String, tree_path: VPathBuf },
|
||||||
input: String,
|
|
||||||
tree_path: String,
|
|
||||||
target_path: VPathBuf,
|
|
||||||
},
|
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,26 +34,54 @@ impl AsRef<str> for Source {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Files = SimpleFiles<String, Source>;
|
#[derive(Debug, Clone)]
|
||||||
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
pub struct File {
|
||||||
|
pub path: VPathBuf,
|
||||||
|
pub source: Source,
|
||||||
|
pub line_starts: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
fn line_start(&self, line_index: usize) -> Result<usize, codespan_reporting::files::Error> {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
match line_index.cmp(&self.line_starts.len()) {
|
||||||
|
Ordering::Less => Ok(self
|
||||||
|
.line_starts
|
||||||
|
.get(line_index)
|
||||||
|
.cloned()
|
||||||
|
.expect("failed despite previous check")),
|
||||||
|
Ordering::Equal => Ok(self.source.as_ref().len()),
|
||||||
|
Ordering::Greater => Err(codespan_reporting::files::Error::LineTooLarge {
|
||||||
|
given: line_index,
|
||||||
|
max: self.line_starts.len() - 1,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FileId(usize);
|
||||||
|
|
||||||
/// Treehouse compilation context.
|
/// Treehouse compilation context.
|
||||||
pub struct Treehouse {
|
pub struct Treehouse {
|
||||||
pub files: Files,
|
pub files: Vec<File>,
|
||||||
|
pub files_by_tree_path: HashMap<VPathBuf, FileId>,
|
||||||
|
|
||||||
pub tree: SemaTree,
|
pub tree: SemaTree,
|
||||||
pub branches_by_named_id: HashMap<String, SemaBranchId>,
|
pub branches_by_named_id: HashMap<String, SemaBranchId>,
|
||||||
pub roots: HashMap<String, SemaRoots>,
|
pub roots: HashMap<FileId, SemaRoots>,
|
||||||
|
|
||||||
pub branch_redirects: HashMap<String, SemaBranchId>,
|
pub branch_redirects: HashMap<String, SemaBranchId>,
|
||||||
|
|
||||||
missingno_generator: ulid::Generator,
|
pub missingno_generator: ulid::Generator,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Treehouse {
|
impl Treehouse {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
files: Files::new(),
|
files: vec![],
|
||||||
|
files_by_tree_path: HashMap::new(),
|
||||||
|
|
||||||
tree: SemaTree::default(),
|
tree: SemaTree::default(),
|
||||||
branches_by_named_id: HashMap::new(),
|
branches_by_named_id: HashMap::new(),
|
||||||
|
@ -69,27 +93,34 @@ impl Treehouse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_file(&mut self, filename: String, source: Source) -> FileId {
|
pub fn add_file(&mut self, path: VPathBuf, source: Source) -> FileId {
|
||||||
self.files.add(filename, source)
|
let id = FileId(self.files.len());
|
||||||
|
self.files.push(File {
|
||||||
|
line_starts: codespan_reporting::files::line_starts(source.input()).collect(),
|
||||||
|
|
||||||
|
path,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the name of a file, assuming it was previously registered.
|
||||||
|
pub fn path(&self, file_id: FileId) -> &VPath {
|
||||||
|
&self.files[file_id.0].path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the source code of a file, assuming it was previously registered.
|
/// Get the source code of a file, assuming it was previously registered.
|
||||||
pub fn source(&self, file_id: FileId) -> &Source {
|
pub fn source(&self, file_id: FileId) -> &Source {
|
||||||
self.files
|
&self.files[file_id.0].source
|
||||||
.get(file_id)
|
|
||||||
.expect("file should have been registered previously")
|
|
||||||
.source()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name of a file, assuming it was previously registered.
|
pub fn set_source(&mut self, file_id: FileId, source: Source) {
|
||||||
pub fn filename(&self, file_id: FileId) -> &str {
|
self.files[file_id.0].line_starts =
|
||||||
self.files
|
codespan_reporting::files::line_starts(source.input()).collect();
|
||||||
.get(file_id)
|
self.files[file_id.0].source = source;
|
||||||
.expect("file should have been registered previously")
|
|
||||||
.name()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tree_path(&self, file_id: FileId) -> Option<&str> {
|
pub fn tree_path(&self, file_id: FileId) -> Option<&VPath> {
|
||||||
match self.source(file_id) {
|
match self.source(file_id) {
|
||||||
Source::Tree { tree_path, .. } => Some(tree_path),
|
Source::Tree { tree_path, .. } => Some(tree_path),
|
||||||
Source::Other(_) => None,
|
Source::Other(_) => None,
|
||||||
|
@ -109,6 +140,49 @@ impl Default for Treehouse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> codespan_reporting::files::Files<'a> for Treehouse {
|
||||||
|
type FileId = FileId;
|
||||||
|
|
||||||
|
type Name = &'a VPath;
|
||||||
|
|
||||||
|
type Source = &'a str;
|
||||||
|
|
||||||
|
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
|
||||||
|
Ok(self.path(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(
|
||||||
|
&'a self,
|
||||||
|
id: Self::FileId,
|
||||||
|
) -> Result<Self::Source, codespan_reporting::files::Error> {
|
||||||
|
Ok(self.source(id).input())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_index(
|
||||||
|
&'a self,
|
||||||
|
id: Self::FileId,
|
||||||
|
byte_index: usize,
|
||||||
|
) -> Result<usize, codespan_reporting::files::Error> {
|
||||||
|
let file = &self.files[id.0];
|
||||||
|
Ok(file
|
||||||
|
.line_starts
|
||||||
|
.binary_search(&byte_index)
|
||||||
|
.unwrap_or_else(|next_line| next_line - 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_range(
|
||||||
|
&'a self,
|
||||||
|
id: Self::FileId,
|
||||||
|
line_index: usize,
|
||||||
|
) -> Result<Range<usize>, codespan_reporting::files::Error> {
|
||||||
|
let file = &self.files[id.0];
|
||||||
|
let line_start = file.line_start(line_index)?;
|
||||||
|
let next_line_start = file.line_start(line_index + 1)?;
|
||||||
|
|
||||||
|
Ok(line_start..next_line_start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TomlError {
|
pub struct TomlError {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub span: Option<Range<usize>>,
|
pub span: Option<Range<usize>>,
|
||||||
|
@ -135,7 +209,11 @@ pub fn toml_error_to_diagnostic(error: TomlError) -> Diagnostic<FileId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report_diagnostics(files: &Files, diagnostics: &[Diagnostic<FileId>]) -> anyhow::Result<()> {
|
#[instrument(skip(files, diagnostics))]
|
||||||
|
pub fn report_diagnostics(
|
||||||
|
files: &Treehouse,
|
||||||
|
diagnostics: &[Diagnostic<FileId>],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||||
let config = codespan_reporting::term::Config::default();
|
let config = codespan_reporting::term::Config::default();
|
||||||
for diagnostic in diagnostics {
|
for diagnostic in diagnostics {
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl SemaRoots {
|
||||||
|
|
||||||
if successfully_parsed && attributes.title.is_empty() {
|
if successfully_parsed && attributes.title.is_empty() {
|
||||||
attributes.title = match treehouse.source(file_id) {
|
attributes.title = match treehouse.source(file_id) {
|
||||||
Source::Tree { tree_path, .. } => tree_path.clone(),
|
Source::Tree { tree_path, .. } => tree_path.to_string(),
|
||||||
_ => panic!("parse_attributes called for a non-.tree file"),
|
_ => panic!("parse_attributes called for a non-.tree file"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +310,7 @@ impl SemaBranch {
|
||||||
"note: a generated id `{}` will be used, but this id is unstable and will not persist across generations",
|
"note: a generated id `{}` will be used, but this id is unstable and will not persist across generations",
|
||||||
attributes.id
|
attributes.id
|
||||||
),
|
),
|
||||||
format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.filename(file_id)),
|
format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.path(file_id)),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -334,6 +334,26 @@ impl SemaBranch {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve content.links.
|
||||||
|
if let Content::Link(tree_path) = &attributes.content {
|
||||||
|
if let Some(file_id) = treehouse.files_by_tree_path.get(tree_path) {
|
||||||
|
attributes.content = Content::ResolvedLink(*file_id);
|
||||||
|
} else {
|
||||||
|
diagnostics.push(Diagnostic {
|
||||||
|
severity: Severity::Error,
|
||||||
|
code: Some("attr".into()),
|
||||||
|
message: format!("linked tree `{tree_path}` does not exist"),
|
||||||
|
labels: vec![Label {
|
||||||
|
style: LabelStyle::Primary,
|
||||||
|
file_id,
|
||||||
|
range: attribute_warning_span.clone(),
|
||||||
|
message: "".into(),
|
||||||
|
}],
|
||||||
|
notes: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
attributes
|
attributes
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{state::FileId, vfs::VPathBuf};
|
||||||
|
|
||||||
/// Top-level `%%` root attributes.
|
/// Top-level `%%` root attributes.
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct RootAttributes {
|
pub struct RootAttributes {
|
||||||
|
@ -118,7 +120,12 @@ pub enum Content {
|
||||||
///
|
///
|
||||||
/// Note that `Link` branches must not contain any children. If a `Link` branch does contain
|
/// Note that `Link` branches must not contain any children. If a `Link` branch does contain
|
||||||
/// children, an `attribute`-type error is raised.
|
/// children, an `attribute`-type error is raised.
|
||||||
Link(String),
|
Link(VPathBuf),
|
||||||
|
|
||||||
|
/// Valid link to another tree.
|
||||||
|
/// This replaces `Content::Link` during semantic analysis.
|
||||||
|
#[serde(skip)]
|
||||||
|
ResolvedLink(FileId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
|
Loading…
Reference in a new issue