generate ids

This commit is contained in:
liquidex 2023-08-20 12:15:48 +02:00
parent f99738d031
commit 1a92f85c83
13 changed files with 297 additions and 109 deletions

14
Cargo.lock generated
View file

@ -944,6 +944,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -1102,6 +1111,9 @@ name = "toml_datetime"
version = "0.6.3" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
@ -1110,6 +1122,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde",
"serde_spanned",
"toml_datetime", "toml_datetime",
"winnow", "winnow",
] ]

View file

@ -164,3 +164,6 @@
% id = "01H87RD70VNVQ75DCWW5FQG9AR" % id = "01H87RD70VNVQ75DCWW5FQG9AR"
- breaking - breaking
% content.link = "secret"
- this block includes another block's content

View file

@ -18,7 +18,7 @@ handlebars = "4.3.7"
pulldown-cmark = { version = "0.9.3", default-features = false } pulldown-cmark = { version = "0.9.3", default-features = false }
serde = { version = "1.0.183", features = ["derive"] } serde = { version = "1.0.183", features = ["derive"] }
tokio = { version = "1.32.0", features = ["full"] } 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-http = { version = "0.4.3", features = ["fs"] }
tower-livereload = "0.8.0" tower-livereload = "0.8.0"
walkdir = "2.3.3" walkdir = "2.3.3"

View file

@ -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(())
}
}

View file

@ -3,8 +3,9 @@ use std::ops::Range;
use anyhow::Context; use anyhow::Context;
use treehouse_format::ast::Branch; use treehouse_format::ast::Branch;
use crate::state::{FileId, Treehouse};
use super::{ use super::{
diagnostics::{Diagnosis, FileId},
parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics}, parse::{self, parse_toml_with_diagnostics, parse_tree_with_diagnostics},
FixArgs, FixArgs,
}; };
@ -19,7 +20,7 @@ struct State {
fixes: Vec<Fix>, 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 mut rng = rand::thread_rng();
let ulid = ulid::Generator::new() let ulid = ulid::Generator::new()
.generate_with_source(&mut rng) .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 // the top-level table. Then we also need to pretty-print everything to match the right
// indentation level. // indentation level.
if let Ok(mut toml) = 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") { if !toml.contains_key("id") {
toml["id"] = toml_edit::value(ulid.to_string()); 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. // Then we fix child branches.
for child in &branch.children { 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( pub fn fix_file(
diagnosis: &mut Diagnosis, treehouse: &mut Treehouse,
file_id: FileId, file_id: FileId,
) -> Result<String, parse::ErrorsEmitted> { ) -> Result<String, parse::ErrorsEmitted> {
parse_tree_with_diagnostics(diagnosis, file_id).map(|roots| { parse_tree_with_diagnostics(treehouse, file_id).map(|roots| {
let mut source = diagnosis.get_source(file_id).to_owned(); let mut source = treehouse.get_source(file_id).to_owned();
let mut state = State::default(); let mut state = State::default();
for branch in &roots.branches { 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 // 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 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 file = std::fs::read_to_string(&fix_args.file).context("cannot read file to fix")?;
let mut diagnosis = Diagnosis::new(); let mut treehouse = Treehouse::new();
let file_id = diagnosis.files.add(utf8_filename, file); 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 { if fix_args.apply {
// Try to write the backup first. If writing that fails, bail out without overwriting // Try to write the backup first. If writing that fails, bail out without overwriting
// the source file. // the source file.
if let Some(backup_path) = fix_args.backup { 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")?; .context("cannot write backup; original file will not be overwritten")?;
} }
std::fs::write(&fix_args.file, fixed).context("cannot overwrite original file")?; 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}"); println!("{fixed}");
} }
} else { } else {
diagnosis.report()?; treehouse.report_diagnostics()?;
} }
Ok(()) Ok(())

View file

@ -1,4 +1,3 @@
pub mod diagnostics;
pub mod fix; pub mod fix;
mod parse; mod parse;
pub mod regenerate; pub mod regenerate;

View file

@ -3,17 +3,17 @@ use std::{ops::Range, str::FromStr};
use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity}; use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
use treehouse_format::ast::Roots; use treehouse_format::ast::Roots;
use super::diagnostics::{Diagnosis, FileId}; use crate::state::{toml_error_to_diagnostic, FileId, TomlError, Treehouse};
pub struct ErrorsEmitted; pub struct ErrorsEmitted;
pub fn parse_tree_with_diagnostics( pub fn parse_tree_with_diagnostics(
diagnosis: &mut Diagnosis, treehouse: &mut Treehouse,
file_id: FileId, file_id: FileId,
) -> Result<Roots, ErrorsEmitted> { ) -> 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| { Roots::parse(&mut treehouse_format::pull::Parser { input, position: 0 }).map_err(|error| {
diagnosis.diagnostics.push(Diagnostic { treehouse.diagnostics.push(Diagnostic {
severity: Severity::Error, severity: Severity::Error,
code: Some("tree".into()), code: Some("tree".into()),
message: error.kind.to_string(), message: error.kind.to_string(),
@ -30,28 +30,20 @@ pub fn parse_tree_with_diagnostics(
} }
pub fn parse_toml_with_diagnostics( pub fn parse_toml_with_diagnostics(
diagnosis: &mut Diagnosis, treehouse: &mut Treehouse,
file_id: FileId, file_id: FileId,
range: Range<usize>, range: Range<usize>,
) -> Result<toml_edit::Document, ErrorsEmitted> { ) -> 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| { toml_edit::Document::from_str(input).map_err(|error| {
diagnosis.diagnostics.push(Diagnostic { treehouse
severity: Severity::Error, .diagnostics
code: Some("toml".into()), .push(toml_error_to_diagnostic(TomlError {
message: error.message().to_owned(), message: error.message().to_owned(),
labels: error span: error.span(),
.span()
.map(|span| Label {
style: LabelStyle::Primary,
file_id, file_id,
range: range.start + span.start..range.start + span.end, input_range: range.clone(),
message: String::new(), }));
})
.into_iter()
.collect(),
notes: vec![],
});
ErrorsEmitted ErrorsEmitted
}) })
} }

View file

@ -19,7 +19,7 @@ use walkdir::WalkDir;
use crate::{cli::parse::parse_tree_with_diagnostics, html::tree::branches_to_html}; 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)] #[derive(Default)]
struct Generator { struct Generator {
@ -39,23 +39,23 @@ impl Generator {
fn register_template( fn register_template(
handlebars: &mut Handlebars<'_>, handlebars: &mut Handlebars<'_>,
diagnosis: &mut Diagnosis, treehouse: &mut Treehouse,
name: &str, name: &str,
path: &Path, path: &Path,
) -> anyhow::Result<FileId> { ) -> anyhow::Result<FileId> {
let source = std::fs::read_to_string(path) let source = std::fs::read_to_string(path)
.with_context(|| format!("cannot read template file {path:?}"))?; .with_context(|| format!("cannot read template file {path:?}"))?;
let file_id = diagnosis let file_id = treehouse
.files .files
.add(path.to_string_lossy().into_owned(), source); .add(path.to_string_lossy().into_owned(), source);
let file = diagnosis let file = treehouse
.files .files
.get(file_id) .get(file_id)
.expect("file was just added to the list"); .expect("file was just added to the list");
let source = file.source(); let source = file.source();
if let Err(error) = handlebars.register_template_string(name, source) { if let Err(error) = handlebars.register_template_string(name, source) {
Self::wrangle_handlebars_error_into_diagnostic( Self::wrangle_handlebars_error_into_diagnostic(
diagnosis, treehouse,
file_id, file_id,
error.line_no, error.line_no,
error.column_no, error.column_no,
@ -66,18 +66,18 @@ impl Generator {
} }
fn wrangle_handlebars_error_into_diagnostic( fn wrangle_handlebars_error_into_diagnostic(
diagnosis: &mut Diagnosis, treehouse: &mut Treehouse,
file_id: FileId, file_id: FileId,
line: Option<usize>, line: Option<usize>,
column: Option<usize>, column: Option<usize>,
message: String, message: String,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if let (Some(line), Some(column)) = (line, column) { if let (Some(line), Some(column)) = (line, column) {
let line_range = diagnosis let line_range = treehouse
.files .files
.line_range(file_id, line) .line_range(file_id, line)
.expect("file was added to the list"); .expect("file was added to the list");
diagnosis.diagnostics.push(Diagnostic { treehouse.diagnostics.push(Diagnostic {
severity: Severity::Error, severity: Severity::Error,
code: Some("template".into()), code: Some("template".into()),
message, message,
@ -90,7 +90,7 @@ impl Generator {
notes: vec![], notes: vec![],
}) })
} else { } else {
let file = diagnosis let file = treehouse
.files .files
.get(file_id) .get(file_id)
.expect("file should already be in the list"); .expect("file should already be in the list");
@ -99,13 +99,13 @@ impl Generator {
Ok(()) Ok(())
} }
fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result<Diagnosis> { fn generate_all_files(&self, dirs: &Dirs<'_>) -> anyhow::Result<Treehouse> {
let mut diagnosis = Diagnosis::new(); let mut treehouse = Treehouse::new();
let mut handlebars = Handlebars::new(); let mut handlebars = Handlebars::new();
let tree_template = Self::register_template( let tree_template = Self::register_template(
&mut handlebars, &mut handlebars,
&mut diagnosis, &mut treehouse,
"tree", "tree",
&dirs.template_dir.join("tree.hbs"), &dirs.template_dir.join("tree.hbs"),
)?; )?;
@ -123,7 +123,7 @@ impl Generator {
let source = match std::fs::read_to_string(path) { let source = match std::fs::read_to_string(path) {
Ok(source) => source, Ok(source) => source,
Err(error) => { Err(error) => {
diagnosis.diagnostics.push(Diagnostic { treehouse.diagnostics.push(Diagnostic {
severity: Severity::Error, severity: Severity::Error,
code: None, code: None,
message: format!("{utf8_filename}: cannot read file: {error}"), message: format!("{utf8_filename}: cannot read file: {error}"),
@ -133,19 +133,18 @@ impl Generator {
continue; 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 mut tree = String::new();
let source = diagnosis.get_source(file_id); branches_to_html(&mut tree, &mut treehouse, file_id, &roots.branches);
branches_to_html(&mut tree, &roots.branches, source);
let template_data = TemplateData { tree }; let template_data = TemplateData { tree };
let templated_html = match handlebars.render("tree", &template_data) { let templated_html = match handlebars.render("tree", &template_data) {
Ok(html) => html, Ok(html) => html,
Err(error) => { Err(error) => {
Self::wrangle_handlebars_error_into_diagnostic( Self::wrangle_handlebars_error_into_diagnostic(
&mut diagnosis, &mut treehouse,
tree_template, tree_template,
error.line_no, error.line_no,
error.column_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"); info!("generating standalone pages");
let mut generator = Generator::default(); let mut generator = Generator::default();
generator.add_directory_rec(dirs.content_dir)?; 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(()) Ok(())
} }

View 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),
}

View file

@ -1,2 +1,20 @@
use std::fmt::{self, Display, Write};
pub mod attributes;
mod markdown; mod markdown;
pub mod tree; 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("&quot;")?;
} else {
f.write_char(c)?;
}
}
Ok(())
}
}

View file

@ -1,13 +1,81 @@
use std::fmt::Write;
use codespan_reporting::diagnostic::{Diagnostic, Label, LabelStyle, Severity};
use treehouse_format::{ast::Branch, pull::BranchKind}; 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) { use super::{attributes::Attributes, markdown};
s.push_str(if !branch.children.is_empty() {
"<li class=\"branch\">" 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 { } 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() { if !branch.children.is_empty() {
s.push_str(match branch.kind { 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() { if !branch.children.is_empty() {
s.push_str("</summary>"); 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("</details>");
} }
} }
s.push_str("</li>"); 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>"); s.push_str("<ul>");
for child in branches { for child in branches {
branch_to_html(s, child, source); branch_to_html(s, treehouse, file_id, child);
} }
s.push_str("</ul>"); s.push_str("</ul>");
} }

View file

@ -10,6 +10,7 @@ use log::{error, info};
mod cli; mod cli;
mod html; mod html;
mod state;
async fn fallible_main() -> anyhow::Result<()> { async fn fallible_main() -> anyhow::Result<()> {
let args = ProgramArgs::parse(); let args = ProgramArgs::parse();

View 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![],
}
}