use ecow::{eco_format, EcoString};
use typst_syntax::{is_newline, Spanned};
use crate::diag::{At, FileError, SourceResult};
use crate::engine::Engine;
use crate::foundations::{func, scope, Str, Value};
use crate::loading::{DataSource, Load, Readable};
#[func(scope, title = "TOML")]
pub fn toml(
engine: &mut Engine,
source: Spanned<DataSource>,
) -> SourceResult<Value> {
let data = source.load(engine.world)?;
let raw = data.as_str().map_err(FileError::from).at(source.span)?;
::toml::from_str(raw)
.map_err(|err| format_toml_error(err, raw))
.at(source.span)
}
#[scope]
impl toml {
#[func(title = "Decode TOML")]
#[deprecated = "`toml.decode` is deprecated, directly pass bytes to `toml` instead"]
pub fn decode(
engine: &mut Engine,
data: Spanned<Readable>,
) -> SourceResult<Value> {
toml(engine, data.map(Readable::into_source))
}
#[func(title = "Encode TOML")]
pub fn encode(
value: Spanned<Value>,
#[named]
#[default(true)]
pretty: bool,
) -> SourceResult<Str> {
let Spanned { v: value, span } = value;
if pretty { ::toml::to_string_pretty(&value) } else { ::toml::to_string(&value) }
.map(|v| v.into())
.map_err(|err| eco_format!("failed to encode value as TOML ({err})"))
.at(span)
}
}
fn format_toml_error(error: ::toml::de::Error, raw: &str) -> EcoString {
if let Some(head) = error.span().and_then(|range| raw.get(..range.start)) {
let line = head.lines().count();
let column = 1 + head.chars().rev().take_while(|&c| !is_newline(c)).count();
eco_format!(
"failed to parse TOML ({} at line {line} column {column})",
error.message(),
)
} else {
eco_format!("failed to parse TOML ({})", error.message())
}
}