summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/model
diff options
context:
space:
mode:
authorTobias Schmitz <tobiasschmitz2001@gmail.com>2025-06-10 14:46:27 +0200
committerGitHub <noreply@github.com>2025-06-10 12:46:27 +0000
commita18ca3481da17a4de1cc7f9890f0c61efb480655 (patch)
tree84fb3fb78574856e20626f96754957bde5920dfb /crates/typst-library/src/model
parent82da96ed957a68017e092e2606226b45c34324f1 (diff)
Report errors in external files (#6308)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
Diffstat (limited to 'crates/typst-library/src/model')
-rw-r--r--crates/typst-library/src/model/bibliography.rs119
1 files changed, 66 insertions, 53 deletions
diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs
index 51e3b03b..11435657 100644
--- a/crates/typst-library/src/model/bibliography.rs
+++ b/crates/typst-library/src/model/bibliography.rs
@@ -19,7 +19,10 @@ use smallvec::{smallvec, SmallVec};
use typst_syntax::{Span, Spanned};
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
-use crate::diag::{bail, error, At, FileError, HintedStrResult, SourceResult, StrResult};
+use crate::diag::{
+ bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos,
+ SourceResult, StrResult,
+};
use crate::engine::{Engine, Sink};
use crate::foundations::{
elem, Bytes, CastInfo, Content, Derived, FromValue, IntoValue, Label, NativeElement,
@@ -31,7 +34,7 @@ use crate::layout::{
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
Sides, Sizing, TrackSizings,
};
-use crate::loading::{DataSource, Load};
+use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
use crate::model::{
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
Url,
@@ -294,24 +297,21 @@ impl Bibliography {
world: Tracked<dyn World + '_>,
sources: Spanned<OneOrMultiple<DataSource>>,
) -> SourceResult<Derived<OneOrMultiple<DataSource>, Self>> {
- let data = sources.load(world)?;
- let bibliography = Self::decode(&sources.v, &data).at(sources.span)?;
+ let loaded = sources.load(world)?;
+ let bibliography = Self::decode(&loaded)?;
Ok(Derived::new(sources.v, bibliography))
}
/// Decode a bibliography from loaded data sources.
#[comemo::memoize]
#[typst_macros::time(name = "load bibliography")]
- fn decode(
- sources: &OneOrMultiple<DataSource>,
- data: &[Bytes],
- ) -> StrResult<Bibliography> {
+ fn decode(data: &[Loaded]) -> SourceResult<Bibliography> {
let mut map = IndexMap::new();
let mut duplicates = Vec::<EcoString>::new();
// We might have multiple bib/yaml files
- for (source, data) in sources.0.iter().zip(data) {
- let library = decode_library(source, data)?;
+ for d in data.iter() {
+ let library = decode_library(d)?;
for entry in library {
match map.entry(Label::new(PicoStr::intern(entry.key()))) {
indexmap::map::Entry::Vacant(vacant) => {
@@ -325,7 +325,11 @@ impl Bibliography {
}
if !duplicates.is_empty() {
- bail!("duplicate bibliography keys: {}", duplicates.join(", "));
+ // TODO: Store spans of entries for duplicate key error messages.
+ // Requires hayagriva entries to store their location, which should
+ // be fine, since they are 1kb anyway.
+ let span = data.first().unwrap().source.span;
+ bail!(span, "duplicate bibliography keys: {}", duplicates.join(", "));
}
Ok(Bibliography(Arc::new(ManuallyHash::new(map, typst_utils::hash128(data)))))
@@ -351,36 +355,47 @@ impl Debug for Bibliography {
}
/// Decode on library from one data source.
-fn decode_library(source: &DataSource, data: &Bytes) -> StrResult<Library> {
- let src = data.as_str().map_err(FileError::from)?;
+fn decode_library(loaded: &Loaded) -> SourceResult<Library> {
+ let data = loaded.data.as_str().within(loaded)?;
- if let DataSource::Path(path) = source {
+ if let LoadSource::Path(file_id) = loaded.source.v {
// If we got a path, use the extension to determine whether it is
// YAML or BibLaTeX.
- let ext = Path::new(path.as_str())
+ let ext = file_id
+ .vpath()
+ .as_rooted_path()
.extension()
.and_then(OsStr::to_str)
.unwrap_or_default();
match ext.to_lowercase().as_str() {
- "yml" | "yaml" => hayagriva::io::from_yaml_str(src)
- .map_err(|err| eco_format!("failed to parse YAML ({err})")),
- "bib" => hayagriva::io::from_biblatex_str(src)
- .map_err(|errors| format_biblatex_error(src, Some(path), errors)),
- _ => bail!("unknown bibliography format (must be .yml/.yaml or .bib)"),
+ "yml" | "yaml" => hayagriva::io::from_yaml_str(data)
+ .map_err(format_yaml_error)
+ .within(loaded),
+ "bib" => hayagriva::io::from_biblatex_str(data)
+ .map_err(format_biblatex_error)
+ .within(loaded),
+ _ => bail!(
+ loaded.source.span,
+ "unknown bibliography format (must be .yml/.yaml or .bib)"
+ ),
}
} else {
// If we just got bytes, we need to guess. If it can be decoded as
// hayagriva YAML, we'll use that.
- let haya_err = match hayagriva::io::from_yaml_str(src) {
+ let haya_err = match hayagriva::io::from_yaml_str(data) {
Ok(library) => return Ok(library),
Err(err) => err,
};
// If it can be decoded as BibLaTeX, we use that isntead.
- let bib_errs = match hayagriva::io::from_biblatex_str(src) {
- Ok(library) => return Ok(library),
- Err(err) => err,
+ let bib_errs = match hayagriva::io::from_biblatex_str(data) {
+ // If the file is almost valid yaml, but contains no `@` character
+ // it will be successfully parsed as an empty BibLaTeX library,
+ // since BibLaTeX does support arbitrary text outside of entries.
+ Ok(library) if !library.is_empty() => return Ok(library),
+ Ok(_) => None,
+ Err(err) => Some(err),
};
// If neither decoded correctly, check whether `:` or `{` appears
@@ -388,7 +403,7 @@ fn decode_library(source: &DataSource, data: &Bytes) -> StrResult<Library> {
// and emit the more appropriate error.
let mut yaml = 0;
let mut biblatex = 0;
- for c in src.chars() {
+ for c in data.chars() {
match c {
':' => yaml += 1,
'{' => biblatex += 1,
@@ -396,37 +411,33 @@ fn decode_library(source: &DataSource, data: &Bytes) -> StrResult<Library> {
}
}
- if yaml > biblatex {
- bail!("failed to parse YAML ({haya_err})")
- } else {
- Err(format_biblatex_error(src, None, bib_errs))
+ match bib_errs {
+ Some(bib_errs) if biblatex >= yaml => {
+ Err(format_biblatex_error(bib_errs)).within(loaded)
+ }
+ _ => Err(format_yaml_error(haya_err)).within(loaded),
}
}
}
/// Format a BibLaTeX loading error.
-fn format_biblatex_error(
- src: &str,
- path: Option<&str>,
- errors: Vec<BibLaTeXError>,
-) -> EcoString {
- let Some(error) = errors.first() else {
- return match path {
- Some(path) => eco_format!("failed to parse BibLaTeX file ({path})"),
- None => eco_format!("failed to parse BibLaTeX"),
- };
+fn format_biblatex_error(errors: Vec<BibLaTeXError>) -> LoadError {
+ // TODO: return multiple errors?
+ let Some(error) = errors.into_iter().next() else {
+ // TODO: can this even happen, should we just unwrap?
+ return LoadError::new(
+ ReportPos::None,
+ "failed to parse BibLaTeX",
+ "something went wrong",
+ );
};
- let (span, msg) = match error {
- BibLaTeXError::Parse(error) => (&error.span, error.kind.to_string()),
- BibLaTeXError::Type(error) => (&error.span, error.kind.to_string()),
+ let (range, msg) = match error {
+ BibLaTeXError::Parse(error) => (error.span, error.kind.to_string()),
+ BibLaTeXError::Type(error) => (error.span, error.kind.to_string()),
};
- let line = src.get(..span.start).unwrap_or_default().lines().count();
- match path {
- Some(path) => eco_format!("failed to parse BibLaTeX file ({path}:{line}: {msg})"),
- None => eco_format!("failed to parse BibLaTeX ({line}: {msg})"),
- }
+ LoadError::new(range, "failed to parse BibLaTeX", msg)
}
/// A loaded CSL style.
@@ -442,8 +453,8 @@ impl CslStyle {
let style = match &source {
CslSource::Named(style) => Self::from_archived(*style),
CslSource::Normal(source) => {
- let data = Spanned::new(source, span).load(world)?;
- Self::from_data(data).at(span)?
+ let loaded = Spanned::new(source, span).load(world)?;
+ Self::from_data(&loaded.data).within(&loaded)?
}
};
Ok(Derived::new(source, style))
@@ -464,16 +475,18 @@ impl CslStyle {
/// Load a CSL style from file contents.
#[comemo::memoize]
- pub fn from_data(data: Bytes) -> StrResult<CslStyle> {
- let text = data.as_str().map_err(FileError::from)?;
+ pub fn from_data(bytes: &Bytes) -> LoadResult<CslStyle> {
+ let text = bytes.as_str()?;
citationberg::IndependentStyle::from_xml(text)
.map(|style| {
Self(Arc::new(ManuallyHash::new(
style,
- typst_utils::hash128(&(TypeId::of::<Bytes>(), data)),
+ typst_utils::hash128(&(TypeId::of::<Bytes>(), bytes)),
)))
})
- .map_err(|err| eco_format!("failed to load CSL style ({err})"))
+ .map_err(|err| {
+ LoadError::new(ReportPos::None, "failed to load CSL style", err)
+ })
}
/// Get the underlying independent style.