generate ids
This commit is contained in:
parent
f99738d031
commit
1a92f85c83
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -944,6 +944,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -1102,6 +1111,9 @@ name = "toml_datetime"
|
|||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
|
@ -1110,6 +1122,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
|
|
@ -164,3 +164,6 @@
|
|||
|
||||
% id = "01H87RD70VNVQ75DCWW5FQG9AR"
|
||||
- breaking
|
||||
|
||||
% content.link = "secret"
|
||||
- this block includes another block's content
|
||||
|
|
|
@ -18,7 +18,7 @@ handlebars = "4.3.7"
|
|||
pulldown-cmark = { version = "0.9.3", default-features = false }
|
||||
serde = { version = "1.0.183", features = ["derive"] }
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
toml_edit = "0.19.14"
|
||||
toml_edit = { version = "0.19.14", features = ["serde"] }
|
||||
tower-http = { version = "0.4.3", features = ["fs"] }
|
||||
tower-livereload = "0.8.0"
|
||||
walkdir = "2.3.3"
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
use anyhow::Context;
|
||||
use codespan_reporting::{
|
||||
diagnostic::Diagnostic,
|
||||
files::SimpleFiles,
|
||||
term::termcolor::{ColorChoice, StandardStream},
|
||||
};
|
||||
|
||||
pub type Files = SimpleFiles<String, String>;
|
||||
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
||||
|
||||
pub struct Diagnosis {
|
||||
pub files: Files,
|
||||
pub diagnostics: Vec<Diagnostic<FileId>>,
|
||||
}
|
||||
|
||||
impl Diagnosis {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
files: Files::new(),
|
||||
diagnostics: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the source code of a file, assuming it was previously registered.
|
||||
pub fn get_source(&self, file_id: FileId) -> &str {
|
||||
self.files
|
||||
.get(file_id)
|
||||
.expect("file should have been registered previously")
|
||||
.source()
|
||||
}
|
||||
|
||||
pub fn report(&self) -> anyhow::Result<()> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
for diagnostic in &self.diagnostics {
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, &self.files, diagnostic)
|
||||
.context("could not emit diagnostic")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -3,8 +3,9 @@ use std::ops::Range;
|
|||
use anyhow::Context;
|
||||
use treehouse_format::ast::Branch;
|
||||
|
||||
use crate::state::{FileId, Treehouse};
|
||||
|
||||
use super::{
|
||||
diagnostics::{Diagnosis, FileId},
|
||||
parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics},
|
||||
FixArgs,
|
||||
};
|
||||
|
@ -19,7 +20,7 @@ struct State {
|
|||
fixes: Vec<Fix>,
|
||||
}
|
||||
|
||||
fn dfs_fix_branch(diagnosis: &mut Diagnosis, file_id: FileId, state: &mut State, branch: &Branch) {
|
||||
fn dfs_fix_branch(treehouse: &mut Treehouse, file_id: FileId, state: &mut State, branch: &Branch) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let ulid = ulid::Generator::new()
|
||||
.generate_with_source(&mut rng)
|
||||
|
@ -31,7 +32,7 @@ fn dfs_fix_branch(diagnosis: &mut Diagnosis, file_id: FileId, state: &mut State,
|
|||
// the top-level table. Then we also need to pretty-print everything to match the right
|
||||
// indentation level.
|
||||
if let Ok(mut toml) =
|
||||
parse_toml_with_diagnostics(diagnosis, file_id, attributes.data.clone())
|
||||
parse_toml_with_diagnostics(treehouse, file_id, attributes.data.clone())
|
||||
{
|
||||
if !toml.contains_key("id") {
|
||||
toml["id"] = toml_edit::value(ulid.to_string());
|
||||
|
@ -68,7 +69,7 @@ fn dfs_fix_branch(diagnosis: &mut Diagnosis, file_id: FileId, state: &mut State,
|
|||
|
||||
// Then we fix child branches.
|
||||
for child in &branch.children {
|
||||
dfs_fix_branch(diagnosis, file_id, state, child);
|
||||
dfs_fix_branch(treehouse, file_id, state, child);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,15 +101,15 @@ fn fix_indent_in_generated_toml(toml: &str, min_indent_level: usize) -> String {
|
|||
}
|
||||
|
||||
pub fn fix_file(
|
||||
diagnosis: &mut Diagnosis,
|
||||
treehouse: &mut Treehouse,
|
||||
file_id: FileId,
|
||||
) -> Result<String, parse::ErrorsEmitted> {
|
||||
parse_tree_with_diagnostics(diagnosis, file_id).map(|roots| {
|
||||
let mut source = diagnosis.get_source(file_id).to_owned();
|
||||
parse_tree_with_diagnostics(treehouse, file_id).map(|roots| {
|
||||
let mut source = treehouse.get_source(file_id).to_owned();
|
||||
let mut state = State::default();
|
||||
|
||||
for branch in &roots.branches {
|
||||
dfs_fix_branch(diagnosis, file_id, &mut state, branch);
|
||||
dfs_fix_branch(treehouse, file_id, &mut state, branch);
|
||||
}
|
||||
|
||||
// Doing a depth-first search of the branches yields fixes from the beginning of the file
|
||||
|
@ -127,15 +128,15 @@ pub fn fix_file_cli(fix_args: FixArgs) -> anyhow::Result<()> {
|
|||
let utf8_filename = fix_args.file.to_string_lossy().into_owned();
|
||||
let file = std::fs::read_to_string(&fix_args.file).context("cannot read file to fix")?;
|
||||
|
||||
let mut diagnosis = Diagnosis::new();
|
||||
let file_id = diagnosis.files.add(utf8_filename, file);
|
||||
let mut treehouse = Treehouse::new();
|
||||
let file_id = treehouse.files.add(utf8_filename, file);
|
||||
|
||||
if let Ok(fixed) = fix_file(&mut diagnosis, file_id) {
|
||||
if let Ok(fixed) = fix_file(&mut treehouse, file_id) {
|
||||
if fix_args.apply {
|
||||
// Try to write the backup first. If writing that fails, bail out without overwriting
|
||||
// the source file.
|
||||
if let Some(backup_path) = fix_args.backup {
|
||||
std::fs::write(backup_path, diagnosis.get_source(file_id))
|
||||
std::fs::write(backup_path, treehouse.get_source(file_id))
|
||||
.context("cannot write backup; original file will not be overwritten")?;
|
||||
}
|
||||
std::fs::write(&fix_args.file, fixed).context("cannot overwrite original file")?;
|
||||
|
@ -143,7 +144,7 @@ pub fn fix_file_cli(fix_args: FixArgs) -> anyhow::Result<()> {
|
|||
println!("{fixed}");
|
||||
}
|
||||
} else {
|
||||
diagnosis.report()?;
|
||||
treehouse.report_diagnostics()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod diagnostics;
|
||||
pub mod fix;
|
||||
mod parse;
|
||||
pub mod regenerate;
|
||||
|
|
|
@ -3,17 +3,17 @@ use std::{ops::Range, str::FromStr};
|
|||
use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
|
||||
use treehouse_format::ast::Roots;
|
||||
|
||||
use super::diagnostics::{Diagnosis, FileId};
|
||||
use crate::state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse};
|
||||
|
||||
pub struct ErrorsEmitted;
|
||||
|
||||
pub fn parse_tree_with_diagnostics(
|
||||
diagnosis: &mut Diagnosis,
|
||||
treehouse: &mut Treehouse,
|
||||
file_id: FileId,
|
||||
) -> Result<Roots, ErrorsEmitted> {
|
||||
let input = diagnosis.get_source(file_id);
|
||||
let input = treehouse.get_source(file_id);
|
||||
Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
|
||||
diagnosis.diagnostics.push(Diagnostic {
|
||||
treehouse.diagnostics.push(Diagnostic {
|
||||
severity: Severity::Error,
|
||||
code: Some("tree".into()),
|
||||
message: error.kind.to_string(),
|
||||
|
@ -30,28 +30,20 @@ pub fn parse_tree_with_diagnostics(
|
|||
}
|
||||
|
||||
pub fn parse_toml_with_diagnostics(
|
||||
diagnosis: &mut Diagnosis,
|
||||
treehouse: &mut Treehouse,
|
||||
file_id: FileId,
|
||||
range: Range<usize>,
|
||||
) -> Result<toml_edit::Document, ErrorsEmitted> {
|
||||
let input = &diagnosis.get_source(file_id)[range.clone()];
|
||||
let input = &treehouse.get_source(file_id)[range.clone()];
|
||||
toml_edit::Document::from_str(input).map_err(|error| {
|
||||
diagnosis.diagnostics.push(Diagnostic {
|
||||
severity: Severity::Error,
|
||||
code: Some("toml".into()),
|
||||
message: error.message().to_owned(),
|
||||
labels: error
|
||||
.span()
|
||||
.map(|span| Label {
|
||||
style: LabelStyle::Primary,
|
||||
file_id,
|
||||
range: range.start + span.start..range.start + span.end,
|
||||
message: String::new(),
|
||||
})
|
||||
.into_iter()
|
||||
.collect(),
|
||||
notes: vec![],
|
||||
});
|
||||
treehouse
|
||||
.diagnostics
|
||||
.push(toml_error_to_diagnostic(TomlError {
|
||||
message: error.message().to_owned(),
|
||||
span: error.span(),
|
||||
file_id,
|
||||
input_range: range.clone(),
|
||||
}));
|
||||
ErrorsEmitted
|
||||
})
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use walkdir::WalkDir;
|
|||
|
||||
use crate::{cli::parse::parse_tree_with_diagnostics, html::tree::branches_to_html};
|
||||
|
||||
use super::diagnostics::{Diagnosis, FileId};
|
||||
use crate::state::{FileId, Treehouse};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Generator {
|
||||
|
@ -39,23 +39,23 @@ impl Generator {
|
|||
|
||||
fn register_template(
|
||||
handlebars: &mut Handlebars<'_>,
|
||||
diagnosis: &mut Diagnosis,
|
||||
treehouse: &mut Treehouse,
|
||||
name: &str,
|
||||
path: &Path,
|
||||
) -> anyhow::Result<FileId> {
|
||||
let source = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("cannot read template file {path:?}"))?;
|
||||
let file_id = diagnosis
|
||||
let file_id = treehouse
|
||||
.files
|
||||
.add(path.to_string_lossy().into_owned(), source);
|
||||
let file = diagnosis
|
||||
let file = treehouse
|
||||
.files
|
||||
.get(file_id)
|
||||
.expect("file was just added to the list");
|
||||
let source = file.source();
|
||||
if let Err(error) = handlebars.register_template_string(name, source) {
|
||||
Self::wrangle_handlebars_error_into_diagnostic(
|
||||
diagnosis,
|
||||
treehouse,
|
||||
file_id,
|
||||
error.line_no,
|
||||
error.column_no,
|
||||
|
@ -66,18 +66,18 @@ impl Generator {
|
|||
}
|
||||
|
||||
fn wrangle_handlebars_error_into_diagnostic(
|
||||
diagnosis: &mut Diagnosis,
|
||||
treehouse: &mut Treehouse,
|
||||
file_id: FileId,
|
||||
line: Option<usize>,
|
||||
column: Option<usize>,
|
||||
message: String,
|
||||
) -> anyhow::Result<()> {
|
||||
if let (Some(line), Some(column)) = (line, column) {
|
||||
let line_range = diagnosis
|
||||
let line_range = treehouse
|
||||
.files
|
||||
.line_range(file_id, line)
|
||||
.expect("file was added to the list");
|
||||
diagnosis.diagnostics.push(Diagnostic {
|
||||
treehouse.diagnostics.push(Diagnostic {
|
||||
severity: Severity::Error,
|
||||
code: Some("template".into()),
|
||||
message,
|
||||
|
@ -90,7 +90,7 @@ impl Generator {
|
|||
notes: vec![],
|
||||
})
|
||||
} else {
|
||||
let file = diagnosis
|
||||
let file = treehouse
|
||||
.files
|
||||
.get(file_id)
|
||||
.expect("file should already be in the list");
|
||||
|
@ -99,13 +99,13 @@ impl Generator {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result<Diagnosis> {
|
||||
let mut diagnosis = Diagnosis::new();
|
||||
fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result<Treehouse> {
|
||||
let mut treehouse = Treehouse::new();
|
||||
|
||||
let mut handlebars = Handlebars::new();
|
||||
let tree_template = Self::register_template(
|
||||
&mut handlebars,
|
||||
&mut diagnosis,
|
||||
&mut treehouse,
|
||||
"tree",
|
||||
&dirs.template_dir.join("tree.hbs"),
|
||||
)?;
|
||||
|
@ -123,7 +123,7 @@ impl Generator {
|
|||
let source = match std::fs::read_to_string(path) {
|
||||
Ok(source) => source,
|
||||
Err(error) => {
|
||||
diagnosis.diagnostics.push(Diagnostic {
|
||||
treehouse.diagnostics.push(Diagnostic {
|
||||
severity: Severity::Error,
|
||||
code: None,
|
||||
message: format!("{utf8_filename}: cannot read file: {error}"),
|
||||
|
@ -133,19 +133,18 @@ impl Generator {
|
|||
continue;
|
||||
}
|
||||
};
|
||||
let file_id = diagnosis.files.add(utf8_filename.into_owned(), source);
|
||||
let file_id = treehouse.files.add(utf8_filename.into_owned(), source);
|
||||
|
||||
if let Ok(roots) = parse_tree_with_diagnostics(&mut diagnosis, file_id) {
|
||||
if let Ok(roots) = parse_tree_with_diagnostics(&mut treehouse, file_id) {
|
||||
let mut tree = String::new();
|
||||
let source = diagnosis.get_source(file_id);
|
||||
branches_to_html(&mut tree, &roots.branches, source);
|
||||
branches_to_html(&mut tree, &mut treehouse, file_id, &roots.branches);
|
||||
|
||||
let template_data = TemplateData { tree };
|
||||
let templated_html = match handlebars.render("tree", &template_data) {
|
||||
Ok(html) => html,
|
||||
Err(error) => {
|
||||
Self::wrangle_handlebars_error_into_diagnostic(
|
||||
&mut diagnosis,
|
||||
&mut treehouse,
|
||||
tree_template,
|
||||
error.line_no,
|
||||
error.column_no,
|
||||
|
@ -159,7 +158,7 @@ impl Generator {
|
|||
}
|
||||
}
|
||||
|
||||
Ok(diagnosis)
|
||||
Ok(treehouse)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,9 +186,9 @@ pub fn regenerate(dirs: &Dirs<'_>) -> anyhow::Result<()> {
|
|||
info!("generating standalone pages");
|
||||
let mut generator = Generator::default();
|
||||
generator.add_directory_rec(dirs.content_dir)?;
|
||||
let diagnosis = generator.generate_all_files(dirs)?;
|
||||
let treehouse = generator.generate_all_files(dirs)?;
|
||||
|
||||
diagnosis.report()?;
|
||||
treehouse.report_diagnostics()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
40
crates/treehouse/src/html/attributes.rs
Normal file
40
crates/treehouse/src/html/attributes.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
/// Branch attributes.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct Attributes {
|
||||
/// Unique identifier of the branch.
|
||||
///
|
||||
/// Note that this must be unique to the _entire_ site, not just a single tree.
|
||||
/// This is because trees may be embedded within each other using [`Content::Link`].
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
|
||||
/// Controls how the block should be presented.
|
||||
#[serde(default)]
|
||||
pub content: Content,
|
||||
}
|
||||
|
||||
/// Controls for block content presentation.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Content {
|
||||
/// Children are stored inline in the block. Nothing special happens.
|
||||
#[default]
|
||||
Inline,
|
||||
|
||||
/// Link to another tree.
|
||||
///
|
||||
/// When JavaScript is enabled, the tree's roots will be embedded inline into the branch and
|
||||
/// loaded lazily.
|
||||
///
|
||||
/// Without JavaScript, the tree will be linked with an `<a>` element.
|
||||
///
|
||||
/// The string provided as an argument is relative to the `content` root and should not contain
|
||||
/// any file extensions. For example, to link to `content/my-article.tree`,
|
||||
/// use `content.link = "my-article"`.
|
||||
///
|
||||
/// Note that `Link` branches must not contain any children. If a `Link` branch does contain
|
||||
/// children, an `attribute`-type error is raised.
|
||||
Link(String),
|
||||
}
|
|
@ -1,2 +1,20 @@
|
|||
use std::fmt::{self, Display, Write};
|
||||
|
||||
pub mod attributes;
|
||||
mod markdown;
|
||||
pub mod tree;
|
||||
|
||||
pub struct EscapeAttribute<'a>(&'a str);
|
||||
|
||||
impl<'a> Display for EscapeAttribute<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for c in self.0.chars() {
|
||||
if c == '"' {
|
||||
f.write_str(""")?;
|
||||
} else {
|
||||
f.write_char(c)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,81 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
|
||||
use treehouse_format::{ast::Branch, pull::BranchKind};
|
||||
|
||||
use super::markdown;
|
||||
use crate::{
|
||||
html::EscapeAttribute,
|
||||
state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse},
|
||||
};
|
||||
|
||||
pub fn branch_to_html(s: &mut String, branch: &Branch, source: &str) {
|
||||
s.push_str(if !branch.children.is_empty() {
|
||||
"<li class=\"branch\">"
|
||||
use super::{attributes::Attributes, markdown};
|
||||
|
||||
pub fn branch_to_html(s: &mut String, treehouse: &mut Treehouse, file_id: FileId, branch: &Branch) {
|
||||
let source = treehouse.get_source(file_id);
|
||||
|
||||
let mut successfully_parsed = true;
|
||||
let mut attributes = if let Some(attributes) = &branch.attributes {
|
||||
toml_edit::de::from_str(&source[attributes.data.clone()]).unwrap_or_else(|error| {
|
||||
treehouse
|
||||
.diagnostics
|
||||
.push(toml_error_to_diagnostic(TomlError {
|
||||
message: error.message().to_owned(),
|
||||
span: error.span(),
|
||||
file_id,
|
||||
input_range: attributes.data.clone(),
|
||||
}));
|
||||
successfully_parsed = false;
|
||||
Attributes::default()
|
||||
})
|
||||
} else {
|
||||
"<li class=\"leaf\">"
|
||||
});
|
||||
Attributes::default()
|
||||
};
|
||||
let successfully_parsed = successfully_parsed;
|
||||
|
||||
// Only check for attribute validity if the attributes were parsed successfully.
|
||||
if successfully_parsed {
|
||||
let attribute_warning_span = branch
|
||||
.attributes
|
||||
.as_ref()
|
||||
.map(|attributes| attributes.percent.clone())
|
||||
.unwrap_or(branch.kind_span.clone());
|
||||
if attributes.id.is_empty() {
|
||||
attributes.id = format!("treehouse-missingno-{}", treehouse.next_missingno());
|
||||
treehouse.diagnostics.push(Diagnostic {
|
||||
severity: Severity::Warning,
|
||||
code: Some("attr".into()),
|
||||
message: "branch does not have an `id` attribute".into(),
|
||||
labels: vec![Label {
|
||||
style: LabelStyle::Primary,
|
||||
file_id,
|
||||
range: attribute_warning_span,
|
||||
message: String::new(),
|
||||
}],
|
||||
notes: vec![
|
||||
format!(
|
||||
"note: a generated id `{}` will be used, but this id is unstable and will not persist across generations",
|
||||
attributes.id
|
||||
),
|
||||
format!("help: run `treehouse fix {}` to add missing ids to branches", treehouse.get_filename(file_id)),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reborrow because the closure requires unique access (it adds a new diagnostic.)
|
||||
let source = treehouse.get_source(file_id);
|
||||
|
||||
let class = if !branch.children.is_empty() {
|
||||
"branch"
|
||||
} else {
|
||||
"leaf"
|
||||
};
|
||||
write!(
|
||||
s,
|
||||
"<li class=\"{class}\" id=\"{}\">",
|
||||
EscapeAttribute(&attributes.id)
|
||||
)
|
||||
.unwrap();
|
||||
{
|
||||
if !branch.children.is_empty() {
|
||||
s.push_str(match branch.kind {
|
||||
|
@ -42,17 +110,22 @@ pub fn branch_to_html(s: &mut String, branch: &Branch, source: &str) {
|
|||
|
||||
if !branch.children.is_empty() {
|
||||
s.push_str("</summary>");
|
||||
branches_to_html(s, &branch.children, source);
|
||||
branches_to_html(s, treehouse, file_id, &branch.children);
|
||||
s.push_str("</details>");
|
||||
}
|
||||
}
|
||||
s.push_str("</li>");
|
||||
}
|
||||
|
||||
pub fn branches_to_html(s: &mut String, branches: &[Branch], source: &str) {
|
||||
pub fn branches_to_html(
|
||||
s: &mut String,
|
||||
treehouse: &mut Treehouse,
|
||||
file_id: FileId,
|
||||
branches: &[Branch],
|
||||
) {
|
||||
s.push_str("<ul>");
|
||||
for child in branches {
|
||||
branch_to_html(s, child, source);
|
||||
branch_to_html(s, treehouse, file_id, child);
|
||||
}
|
||||
s.push_str("</ul>");
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use log::{error, info};
|
|||
|
||||
mod cli;
|
||||
mod html;
|
||||
mod state;
|
||||
|
||||
async fn fallible_main() -> anyhow::Result<()> {
|
||||
let args = ProgramArgs::parse();
|
||||
|
|
90
crates/treehouse/src/state.rs
Normal file
90
crates/treehouse/src/state.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use anyhow::Context;
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label, LabelStyle, Severity},
|
||||
files::SimpleFiles,
|
||||
term::termcolor::{ColorChoice, StandardStream},
|
||||
};
|
||||
use ulid::Ulid;
|
||||
|
||||
pub type Files = SimpleFiles<String, String>;
|
||||
pub type FileId = <Files as codespan_reporting::files::Files<'static>>::FileId;
|
||||
|
||||
/// Treehouse compilation context.
|
||||
pub struct Treehouse {
|
||||
pub files: Files,
|
||||
pub diagnostics: Vec<Diagnostic<FileId>>,
|
||||
|
||||
missingno_generator: ulid::Generator,
|
||||
}
|
||||
|
||||
impl Treehouse {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
files: Files::new(),
|
||||
diagnostics: vec![],
|
||||
|
||||
missingno_generator: ulid::Generator::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the source code of a file, assuming it was previously registered.
|
||||
pub fn get_source(&self, file_id: FileId) -> &str {
|
||||
self.files
|
||||
.get(file_id)
|
||||
.expect("file should have been registered previously")
|
||||
.source()
|
||||
}
|
||||
|
||||
/// Get the name of a file, assuming it was previously registered.
|
||||
pub fn get_filename(&self, file_id: FileId) -> &str {
|
||||
self.files
|
||||
.get(file_id)
|
||||
.expect("file should have been registered previously")
|
||||
.name()
|
||||
}
|
||||
|
||||
pub fn report_diagnostics(&self) -> anyhow::Result<()> {
|
||||
let writer = StandardStream::stderr(ColorChoice::Auto);
|
||||
let config = codespan_reporting::term::Config::default();
|
||||
for diagnostic in &self.diagnostics {
|
||||
codespan_reporting::term::emit(&mut writer.lock(), &config, &self.files, diagnostic)
|
||||
.context("could not emit diagnostic")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn next_missingno(&mut self) -> Ulid {
|
||||
self.missingno_generator
|
||||
.generate()
|
||||
.expect("just how much disk space do you have?")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TomlError {
|
||||
pub message: String,
|
||||
pub span: Option<Range<usize>>,
|
||||
pub file_id: FileId,
|
||||
pub input_range: Range<usize>,
|
||||
}
|
||||
|
||||
pub fn toml_error_to_diagnostic(error: TomlError) -> Diagnostic<FileId> {
|
||||
Diagnostic {
|
||||
severity: Severity::Error,
|
||||
code: Some("toml".into()),
|
||||
message: error.message,
|
||||
labels: error
|
||||
.span
|
||||
.map(|span| Label {
|
||||
style: LabelStyle::Primary,
|
||||
file_id: error.file_id,
|
||||
range: error.input_range.start + span.start..error.input_range.start + span.end,
|
||||
message: String::new(),
|
||||
})
|
||||
.into_iter()
|
||||
.collect(),
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue