Tobias Schmitz a18ca3481d
Report errors in external files (#6308)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
2025-06-10 12:46:27 +00:00

92 lines
3.0 KiB
Rust

use ecow::eco_format;
use typst_syntax::Spanned;
use crate::diag::{At, LineCol, LoadError, LoadedWithin, ReportPos, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Str, Value};
use crate::loading::{DataSource, Load, Readable};
/// Reads structured data from a YAML file.
///
/// The file must contain a valid YAML object or array. YAML mappings will be
/// converted into Typst dictionaries, and YAML sequences will be converted into
/// Typst arrays. Strings and booleans will be converted into the Typst
/// equivalents, null-values (`null`, `~` or empty ``) will be converted into
/// `{none}`, and numbers will be converted to floats or integers depending on
/// whether they are whole numbers. Custom YAML tags are ignored, though the
/// loaded value will still be present.
///
/// Be aware that integers larger than 2<sup>63</sup>-1 will be converted to
/// floating point numbers, which may give an approximative value.
///
/// The YAML files in the example contain objects with authors as keys,
/// each with a sequence of their own submapping with the keys
/// "title" and "published"
///
/// # Example
/// ```example
/// #let bookshelf(contents) = {
/// for (author, works) in contents {
/// author
/// for work in works [
/// - #work.title (#work.published)
/// ]
/// }
/// }
///
/// #bookshelf(
/// yaml("scifi-authors.yaml")
/// )
/// ```
#[func(scope, title = "YAML")]
pub fn yaml(
engine: &mut Engine,
/// A [path]($syntax/#paths) to a YAML file or raw YAML bytes.
source: Spanned<DataSource>,
) -> SourceResult<Value> {
let loaded = source.load(engine.world)?;
serde_yaml::from_slice(loaded.data.as_slice())
.map_err(format_yaml_error)
.within(&loaded)
}
#[scope]
impl yaml {
/// Reads structured data from a YAML string/bytes.
#[func(title = "Decode YAML")]
#[deprecated = "`yaml.decode` is deprecated, directly pass bytes to `yaml` instead"]
pub fn decode(
engine: &mut Engine,
/// YAML data.
data: Spanned<Readable>,
) -> SourceResult<Value> {
yaml(engine, data.map(Readable::into_source))
}
/// Encode structured data into a YAML string.
#[func(title = "Encode YAML")]
pub fn encode(
/// Value to be encoded.
value: Spanned<Value>,
) -> SourceResult<Str> {
let Spanned { v: value, span } = value;
serde_yaml::to_string(&value)
.map(|v| v.into())
.map_err(|err| eco_format!("failed to encode value as YAML ({err})"))
.at(span)
}
}
/// Format the user-facing YAML error message.
pub fn format_yaml_error(error: serde_yaml::Error) -> LoadError {
let pos = error
.location()
.map(|loc| {
let line_col = LineCol::one_based(loc.line(), loc.column());
let range = loc.index()..loc.index();
ReportPos::full(range, line_col)
})
.unwrap_or_default();
LoadError::new(pos, "failed to parse YAML", error)
}