mirror of
https://github.com/typst/typst
synced 2025-08-24 03:34:14 +08:00
refactor: apply some suggestions
- rename Loaded::bytes to data - change `let data = source.load()` to `let loaded` - only expose a single `within` method for the LoadedWithin trait - invalid utf-8 data is a rare edge case - only store one EcoString inside LoadError - mutate it when using `LoadedWithin::within` method
This commit is contained in:
parent
1c08683248
commit
2d3e883d2b
@ -190,7 +190,7 @@ impl SystemWorld {
|
||||
source.lines()
|
||||
} else if let Some(bytes) = slot.file.get() {
|
||||
let bytes = bytes.as_ref().expect("file is not valid");
|
||||
Lines::from_bytes(bytes.as_slice()).expect("file is not valid utf-8")
|
||||
Lines::from_bytes(bytes).expect("file is not valid utf-8")
|
||||
} else {
|
||||
panic!("file id does not point to any source file");
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::ffi::OsStr;
|
||||
|
||||
use typst_library::diag::{warning, At, LoadedAt, SourceResult, StrResult};
|
||||
use typst_library::diag::{warning, At, LoadedWithin, SourceResult, StrResult};
|
||||
use typst_library::engine::Engine;
|
||||
use typst_library::foundations::{Bytes, Derived, Packed, Smart, StyleChain};
|
||||
use typst_library::introspection::Locator;
|
||||
@ -27,17 +27,17 @@ pub fn layout_image(
|
||||
|
||||
// Take the format that was explicitly defined, or parse the extension,
|
||||
// or try to detect the format.
|
||||
let Derived { source, derived: data } = &elem.source;
|
||||
let Derived { source, derived: loaded } = &elem.source;
|
||||
let format = match elem.format(styles) {
|
||||
Smart::Custom(v) => v,
|
||||
Smart::Auto => determine_format(source, &data.bytes).at(span)?,
|
||||
Smart::Auto => determine_format(source, &loaded.data).at(span)?,
|
||||
};
|
||||
|
||||
// Warn the user if the image contains a foreign object. Not perfect
|
||||
// because the svg could also be encoded, but that's an edge case.
|
||||
if format == ImageFormat::Vector(VectorFormat::Svg) {
|
||||
let has_foreign_object =
|
||||
memchr::memmem::find(&data.bytes, b"<foreignObject").is_some();
|
||||
memchr::memmem::find(&loaded.data, b"<foreignObject").is_some();
|
||||
|
||||
if has_foreign_object {
|
||||
engine.sink.warn(warning!(
|
||||
@ -53,7 +53,7 @@ pub fn layout_image(
|
||||
let kind = match format {
|
||||
ImageFormat::Raster(format) => ImageKind::Raster(
|
||||
RasterImage::new(
|
||||
data.bytes.clone(),
|
||||
loaded.data.clone(),
|
||||
format,
|
||||
elem.icc(styles).as_ref().map(|icc| icc.derived.clone()),
|
||||
)
|
||||
@ -61,11 +61,11 @@ pub fn layout_image(
|
||||
),
|
||||
ImageFormat::Vector(VectorFormat::Svg) => ImageKind::Svg(
|
||||
SvgImage::with_fonts(
|
||||
data.bytes.clone(),
|
||||
loaded.data.clone(),
|
||||
engine.world,
|
||||
&families(styles).map(|f| f.as_str()).collect::<Vec<_>>(),
|
||||
)
|
||||
.in_text(data)?,
|
||||
.within(loaded)?,
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Diagnostics.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Display, Formatter, Write as _};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::Utf8Error;
|
||||
@ -581,9 +581,9 @@ pub type LoadResult<T> = Result<T, LoadError>;
|
||||
/// [`FileId`]: typst_syntax::FileId
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LoadError {
|
||||
pub pos: ReportPos,
|
||||
pub message: EcoString,
|
||||
pub error: EcoString,
|
||||
pos: ReportPos,
|
||||
/// Must contain a message formatted like this: `"failed to do thing (cause)"`.
|
||||
message: EcoString,
|
||||
}
|
||||
|
||||
impl LoadError {
|
||||
@ -594,109 +594,98 @@ impl LoadError {
|
||||
) -> Self {
|
||||
Self {
|
||||
pos: pos.into(),
|
||||
message: eco_format!("{message}"),
|
||||
error: eco_format!("{error}"),
|
||||
message: eco_format!("{message} ({error})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`LoadResult`] to a [`SourceResult`] by adding the [`Loaded`] context.
|
||||
pub trait LoadedAt<T> {
|
||||
/// Add the span information.
|
||||
fn in_text(self, data: &Loaded) -> SourceResult<T>;
|
||||
|
||||
/// Add the span information.
|
||||
fn in_invalid_text(self, data: &Loaded) -> SourceResult<T>;
|
||||
pub trait LoadedWithin<T> {
|
||||
/// Report an error, possibly in an external file.
|
||||
fn within(self, loaded: &Loaded) -> SourceResult<T>;
|
||||
}
|
||||
|
||||
impl<T> LoadedAt<T> for Result<T, LoadError> {
|
||||
/// Report an error, possibly in an external file.
|
||||
fn in_text(self, data: &Loaded) -> SourceResult<T> {
|
||||
self.map_err(|err| data.err_in_text(err.pos, err.message, err.error))
|
||||
}
|
||||
|
||||
/// Report an error in invalid text.
|
||||
fn in_invalid_text(self, data: &Loaded) -> SourceResult<T> {
|
||||
self.map_err(|err| data.err_in_invalid_text(err.pos, err.message, err.error))
|
||||
impl<T> LoadedWithin<T> for Result<T, LoadError> {
|
||||
fn within(self, loaded: &Loaded) -> SourceResult<T> {
|
||||
self.map_err(|err| load_err_in_text(loaded, err.pos, err.message))
|
||||
}
|
||||
}
|
||||
|
||||
impl Loaded {
|
||||
/// Report an error, possibly in an external file.
|
||||
pub fn err_in_text(
|
||||
&self,
|
||||
pos: impl Into<ReportPos>,
|
||||
msg: impl std::fmt::Display,
|
||||
error: impl std::fmt::Display,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
let pos = pos.into();
|
||||
// This also does utf-8 validation. Only report an error in an external
|
||||
// file if it is human readable (valid utf-8), otherwise fall back to
|
||||
// `err_in_invalid_text`.
|
||||
let lines = Lines::from_bytes(&self.bytes);
|
||||
match (self.source.v, lines) {
|
||||
(LoadSource::Path(file_id), Ok(lines)) => {
|
||||
if let Some(range) = pos.range(&lines) {
|
||||
let span = Span::from_range(file_id, range);
|
||||
return eco_vec!(error!(span, "{msg} ({error})"));
|
||||
}
|
||||
/// Report an error, possibly in an external file. This will delegate to
|
||||
/// [`load_err_in_invalid_text`] if the data isn't valid utf-8.
|
||||
fn load_err_in_text(
|
||||
loaded: &Loaded,
|
||||
pos: impl Into<ReportPos>,
|
||||
mut message: EcoString,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
let pos = pos.into();
|
||||
// This also does utf-8 validation. Only report an error in an external
|
||||
// file if it is human readable (valid utf-8), otherwise fall back to
|
||||
// `load_err_in_invalid_text`.
|
||||
let lines = Lines::from_bytes(&loaded.data);
|
||||
match (loaded.source.v, lines) {
|
||||
(LoadSource::Path(file_id), Ok(lines)) => {
|
||||
if let Some(range) = pos.range(&lines) {
|
||||
let span = Span::from_range(file_id, range);
|
||||
return eco_vec![SourceDiagnostic::error(span, message)];
|
||||
}
|
||||
|
||||
// Either `ReportPos::None` was provided, or resolving the range
|
||||
// from the line/column failed. If present report the possibly
|
||||
// wrong line/column in the error message anyway.
|
||||
let span = Span::from_range(file_id, 0..self.bytes.len());
|
||||
let error = if let Some(pair) = pos.line_col(&lines) {
|
||||
let (line, col) = pair.numbers();
|
||||
error!(span, "{msg} ({error} at {line}:{col})")
|
||||
} else {
|
||||
error!(span, "{msg} ({error})")
|
||||
};
|
||||
eco_vec![error]
|
||||
// Either `ReportPos::None` was provided, or resolving the range
|
||||
// from the line/column failed. If present report the possibly
|
||||
// wrong line/column in the error message anyway.
|
||||
let span = Span::from_range(file_id, 0..loaded.data.len());
|
||||
if let Some(pair) = pos.line_col(&lines) {
|
||||
message.pop();
|
||||
let (line, col) = pair.numbers();
|
||||
write!(&mut message, " at {line}:{col})").ok();
|
||||
}
|
||||
(LoadSource::Bytes, Ok(lines)) => {
|
||||
let error = if let Some(pair) = pos.line_col(&lines) {
|
||||
let (line, col) = pair.numbers();
|
||||
error!(self.source.span, "{msg} ({error} at {line}:{col})")
|
||||
} else {
|
||||
error!(self.source.span, "{msg} ({error})")
|
||||
};
|
||||
eco_vec![error]
|
||||
}
|
||||
_ => self.err_in_invalid_text(pos, msg, error),
|
||||
eco_vec![SourceDiagnostic::error(span, message)]
|
||||
}
|
||||
(LoadSource::Bytes, Ok(lines)) => {
|
||||
if let Some(pair) = pos.line_col(&lines) {
|
||||
message.pop();
|
||||
let (line, col) = pair.numbers();
|
||||
write!(&mut message, " at {line}:{col})").ok();
|
||||
}
|
||||
eco_vec![SourceDiagnostic::error(loaded.source.span, message)]
|
||||
}
|
||||
_ => load_err_in_invalid_text(loaded, pos, message),
|
||||
}
|
||||
}
|
||||
|
||||
/// Report an error (possibly from an external file) that isn't valid utf-8.
|
||||
pub fn err_in_invalid_text(
|
||||
&self,
|
||||
pos: impl Into<ReportPos>,
|
||||
msg: impl std::fmt::Display,
|
||||
error: impl std::fmt::Display,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
let line_col = pos.into().try_line_col(&self.bytes).map(|p| p.numbers());
|
||||
let error = match (self.source.v, line_col) {
|
||||
(LoadSource::Path(file), _) => {
|
||||
let path = if let Some(package) = file.package() {
|
||||
format!("{package}{}", file.vpath().as_rooted_path().display())
|
||||
} else {
|
||||
format!("{}", file.vpath().as_rootless_path().display())
|
||||
};
|
||||
|
||||
if let Some((line, col)) = line_col {
|
||||
error!(self.source.span, "{msg} ({error} in {path}:{line}:{col})")
|
||||
} else {
|
||||
error!(self.source.span, "{msg} ({error} in {path})")
|
||||
}
|
||||
/// Report an error (possibly from an external file) that isn't valid utf-8.
|
||||
fn load_err_in_invalid_text(
|
||||
loaded: &Loaded,
|
||||
pos: impl Into<ReportPos>,
|
||||
mut message: EcoString,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
let line_col = pos.into().try_line_col(&loaded.data).map(|p| p.numbers());
|
||||
match (loaded.source.v, line_col) {
|
||||
(LoadSource::Path(file), _) => {
|
||||
message.pop();
|
||||
if let Some(package) = file.package() {
|
||||
write!(
|
||||
&mut message,
|
||||
" in {package}{}",
|
||||
file.vpath().as_rooted_path().display()
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
write!(&mut message, " in {}", file.vpath().as_rootless_path().display())
|
||||
.ok();
|
||||
};
|
||||
if let Some((line, col)) = line_col {
|
||||
write!(&mut message, ":{line}:{col}").ok();
|
||||
}
|
||||
(LoadSource::Bytes, Some((line, col))) => {
|
||||
error!(self.source.span, "{msg} ({error} at {line}:{col})")
|
||||
}
|
||||
(LoadSource::Bytes, None) => {
|
||||
error!(self.source.span, "{msg} ({error})")
|
||||
}
|
||||
};
|
||||
eco_vec![error]
|
||||
message.push(')');
|
||||
}
|
||||
(LoadSource::Bytes, Some((line, col))) => {
|
||||
message.pop();
|
||||
write!(&mut message, " at {line}:{col})").ok();
|
||||
}
|
||||
(LoadSource::Bytes, None) => (),
|
||||
}
|
||||
eco_vec![SourceDiagnostic::error(loaded.source.span, message)]
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
||||
@ -819,22 +808,21 @@ impl LineCol {
|
||||
/// Format a user-facing error message for an XML-like file format.
|
||||
pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> LoadError {
|
||||
let pos = LineCol::one_based(error.pos().row as usize, error.pos().col as usize);
|
||||
let message = eco_format!("failed to parse {format}");
|
||||
let error = match error {
|
||||
let message = match error {
|
||||
roxmltree::Error::UnexpectedCloseTag(expected, actual, _) => {
|
||||
eco_format!("found closing tag '{actual}' instead of '{expected}'")
|
||||
eco_format!("failed to parse {format} (found closing tag '{actual}' instead of '{expected}')")
|
||||
}
|
||||
roxmltree::Error::UnknownEntityReference(entity, _) => {
|
||||
eco_format!("unknown entity '{entity}'")
|
||||
eco_format!("failed to parse {format} (unknown entity '{entity}')")
|
||||
}
|
||||
roxmltree::Error::DuplicatedAttribute(attr, _) => {
|
||||
eco_format!("duplicate attribute '{attr}'")
|
||||
eco_format!("failed to parse {format} (duplicate attribute '{attr}')")
|
||||
}
|
||||
roxmltree::Error::NoRootNode => {
|
||||
eco_format!("missing root node")
|
||||
eco_format!("failed to parse {format} (missing root node)")
|
||||
}
|
||||
err => eco_format!("{err}"),
|
||||
err => eco_format!("failed to parse {format} ({err})"),
|
||||
};
|
||||
|
||||
LoadError { pos: pos.into(), message, error }
|
||||
LoadError { pos: pos.into(), message }
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
use typst_utils::LazyHash;
|
||||
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::diag::{bail, LoadError, LoadResult, StrResult};
|
||||
use crate::foundations::{cast, func, scope, ty, Array, Reflect, Repr, Str, Value};
|
||||
|
||||
/// A sequence of bytes.
|
||||
@ -112,6 +112,21 @@ impl Bytes {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_str(&self) -> LoadResult<&str> {
|
||||
match self.inner().as_any().downcast_ref::<Str>() {
|
||||
Some(string) => Ok(string.as_str()),
|
||||
None => self.as_str().map_err(|err| {
|
||||
let start = err.valid_up_to();
|
||||
let end = start + err.error_len().unwrap_or(0);
|
||||
LoadError::new(
|
||||
start..end,
|
||||
"failed to convert to string",
|
||||
"file is not valid utf-8",
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve an index or throw an out of bounds error.
|
||||
fn locate(&self, index: i64) -> StrResult<usize> {
|
||||
self.locate_opt(index).ok_or_else(|| out_of_bounds(index, self.len()))
|
||||
|
@ -151,8 +151,8 @@ pub fn plugin(
|
||||
/// A [path]($syntax/#paths) to a WebAssembly file or raw WebAssembly bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Module> {
|
||||
let data = source.load(engine.world)?;
|
||||
Plugin::module(data.bytes).at(source.span)
|
||||
let loaded = source.load(engine.world)?;
|
||||
Plugin::module(loaded.data).at(source.span)
|
||||
}
|
||||
|
||||
#[scope]
|
||||
|
@ -23,8 +23,8 @@ pub fn cbor(
|
||||
/// A [path]($syntax/#paths) to a CBOR file or raw CBOR bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Value> {
|
||||
let data = source.load(engine.world)?;
|
||||
ciborium::from_reader(data.bytes.as_slice())
|
||||
let loaded = source.load(engine.world)?;
|
||||
ciborium::from_reader(loaded.data.as_slice())
|
||||
.map_err(|err| eco_format!("failed to parse CBOR ({err})"))
|
||||
.at(source.span)
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
use az::SaturatingAs;
|
||||
use ecow::EcoVec;
|
||||
use typst_syntax::Spanned;
|
||||
|
||||
use crate::diag::{bail, LineCol, ReportPos, SourceDiagnostic, SourceResult};
|
||||
use crate::diag::{bail, LineCol, LoadError, LoadedWithin, ReportPos, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{cast, func, scope, Array, Dict, IntoValue, Type, Value};
|
||||
use crate::loading::{DataSource, Load, Loaded, Readable};
|
||||
use crate::loading::{DataSource, Load, Readable};
|
||||
|
||||
/// Reads structured data from a CSV file.
|
||||
///
|
||||
@ -45,7 +44,7 @@ pub fn csv(
|
||||
#[default(RowType::Array)]
|
||||
row_type: RowType,
|
||||
) -> SourceResult<Array> {
|
||||
let data = source.load(engine.world)?;
|
||||
let loaded = source.load(engine.world)?;
|
||||
|
||||
let mut builder = ::csv::ReaderBuilder::new();
|
||||
let has_headers = row_type == RowType::Dict;
|
||||
@ -54,7 +53,7 @@ pub fn csv(
|
||||
|
||||
// Counting lines from 1 by default.
|
||||
let mut line_offset: usize = 1;
|
||||
let mut reader = builder.from_reader(data.bytes.as_slice());
|
||||
let mut reader = builder.from_reader(loaded.data.as_slice());
|
||||
let mut headers: Option<::csv::StringRecord> = None;
|
||||
|
||||
if has_headers {
|
||||
@ -64,7 +63,8 @@ pub fn csv(
|
||||
reader
|
||||
.headers()
|
||||
.cloned()
|
||||
.map_err(|err| format_csv_error(&data, err, 1))?,
|
||||
.map_err(|err| format_csv_error(err, 1))
|
||||
.within(&loaded)?,
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ pub fn csv(
|
||||
// incorrect with `has_headers` set to `false`. See issue:
|
||||
// https://github.com/BurntSushi/rust-csv/issues/184
|
||||
let line = line + line_offset;
|
||||
let row = result.map_err(|err| format_csv_error(&data, err, line))?;
|
||||
let row = result.map_err(|err| format_csv_error(err, line)).within(&loaded)?;
|
||||
let item = if let Some(headers) = &headers {
|
||||
let mut dict = Dict::new();
|
||||
for (field, value) in headers.iter().zip(&row) {
|
||||
@ -164,11 +164,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// Format the user-facing CSV error message.
|
||||
fn format_csv_error(
|
||||
data: &Loaded,
|
||||
err: ::csv::Error,
|
||||
line: usize,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
fn format_csv_error(err: ::csv::Error, line: usize) -> LoadError {
|
||||
let msg = "failed to parse CSV";
|
||||
let pos = (err.kind().position())
|
||||
.map(|pos| {
|
||||
@ -178,13 +174,13 @@ fn format_csv_error(
|
||||
.unwrap_or(LineCol::one_based(line, 1).into());
|
||||
match err.kind() {
|
||||
::csv::ErrorKind::Utf8 { .. } => {
|
||||
data.err_in_text(pos, msg, "file is not valid utf-8")
|
||||
LoadError::new(pos, msg, "file is not valid utf-8")
|
||||
}
|
||||
::csv::ErrorKind::UnequalLengths { expected_len, len, .. } => {
|
||||
let err =
|
||||
format!("found {len} instead of {expected_len} fields in line {line}");
|
||||
data.err_in_text(pos, msg, err)
|
||||
LoadError::new(pos, msg, err)
|
||||
}
|
||||
_ => data.err_in_text(pos, "failed to parse CSV", err),
|
||||
_ => LoadError::new(pos, "failed to parse CSV", err),
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ecow::eco_format;
|
||||
use typst_syntax::Spanned;
|
||||
|
||||
use crate::diag::{At, LineCol, SourceResult};
|
||||
use crate::diag::{At, LineCol, LoadError, LoadedWithin, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{func, scope, Str, Value};
|
||||
use crate::loading::{DataSource, Load, Readable};
|
||||
@ -54,11 +54,13 @@ pub fn json(
|
||||
/// A [path]($syntax/#paths) to a JSON file or raw JSON bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Value> {
|
||||
let data = source.load(engine.world)?;
|
||||
serde_json::from_slice(data.bytes.as_slice()).map_err(|err| {
|
||||
let pos = LineCol::one_based(err.line(), err.column());
|
||||
data.err_in_text(pos, "failed to parse JSON", err)
|
||||
})
|
||||
let loaded = source.load(engine.world)?;
|
||||
serde_json::from_slice(loaded.data.as_slice())
|
||||
.map_err(|err| {
|
||||
let pos = LineCol::one_based(err.line(), err.column());
|
||||
LoadError::new(pos, "failed to parse JSON", err)
|
||||
})
|
||||
.within(&loaded)
|
||||
}
|
||||
|
||||
#[scope]
|
||||
|
@ -27,7 +27,7 @@ pub use self::toml_::*;
|
||||
pub use self::xml_::*;
|
||||
pub use self::yaml_::*;
|
||||
|
||||
use crate::diag::{At, LoadError, LoadResult, LoadedAt, SourceResult};
|
||||
use crate::diag::{At, LoadedWithin, SourceResult};
|
||||
use crate::foundations::OneOrMultiple;
|
||||
use crate::foundations::{cast, Bytes, Scope, Str};
|
||||
use crate::World;
|
||||
@ -124,16 +124,16 @@ impl Load for Spanned<&OneOrMultiple<DataSource>> {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Loaded {
|
||||
pub source: Spanned<LoadSource>,
|
||||
pub bytes: Bytes,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
impl Loaded {
|
||||
pub fn new(source: Spanned<LoadSource>, bytes: Bytes) -> Self {
|
||||
Self { source, bytes }
|
||||
Self { source, data: bytes }
|
||||
}
|
||||
|
||||
pub fn load_str(&self) -> SourceResult<&str> {
|
||||
self.bytes.load_str().in_invalid_text(self)
|
||||
self.data.load_str().within(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,24 +144,6 @@ pub enum LoadSource {
|
||||
Bytes,
|
||||
}
|
||||
|
||||
pub trait LoadStr {
|
||||
fn load_str(&self) -> LoadResult<&str>;
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> LoadStr for T {
|
||||
fn load_str(&self) -> LoadResult<&str> {
|
||||
std::str::from_utf8(self.as_ref()).map_err(|err| {
|
||||
let start = err.valid_up_to();
|
||||
let end = start + err.error_len().unwrap_or(0);
|
||||
LoadError::new(
|
||||
start..end,
|
||||
"failed to convert to string",
|
||||
"file is not valid utf-8",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be read from a file.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum Readable {
|
||||
|
@ -35,10 +35,10 @@ pub fn read(
|
||||
#[default(Some(Encoding::Utf8))]
|
||||
encoding: Option<Encoding>,
|
||||
) -> SourceResult<Readable> {
|
||||
let data = path.map(DataSource::Path).load(engine.world)?;
|
||||
let loaded = path.map(DataSource::Path).load(engine.world)?;
|
||||
Ok(match encoding {
|
||||
None => Readable::Bytes(data.bytes),
|
||||
Some(Encoding::Utf8) => Readable::Str(data.load_str()?.into()),
|
||||
None => Readable::Bytes(loaded.data),
|
||||
Some(Encoding::Utf8) => Readable::Str(loaded.load_str()?.into()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use ecow::eco_format;
|
||||
use typst_syntax::Spanned;
|
||||
|
||||
use crate::diag::{At, LoadError, LoadedAt, ReportPos, SourceResult};
|
||||
use crate::diag::{At, LoadError, LoadedWithin, ReportPos, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{func, scope, Str, Value};
|
||||
use crate::loading::{DataSource, Load, Readable};
|
||||
@ -32,9 +32,9 @@ pub fn toml(
|
||||
/// A [path]($syntax/#paths) to a TOML file or raw TOML bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Value> {
|
||||
let data = source.load(engine.world)?;
|
||||
let raw = data.load_str()?;
|
||||
::toml::from_str(raw).map_err(format_toml_error).in_text(&data)
|
||||
let loaded = source.load(engine.world)?;
|
||||
let raw = loaded.load_str()?;
|
||||
::toml::from_str(raw).map_err(format_toml_error).within(&loaded)
|
||||
}
|
||||
|
||||
#[scope]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use roxmltree::ParsingOptions;
|
||||
use typst_syntax::Spanned;
|
||||
|
||||
use crate::diag::{format_xml_like_error, LoadError, LoadedAt, SourceResult};
|
||||
use crate::diag::{format_xml_like_error, LoadError, LoadedWithin, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{dict, func, scope, Array, Dict, IntoValue, Str, Value};
|
||||
use crate::loading::{DataSource, Load, Readable};
|
||||
@ -60,14 +60,14 @@ pub fn xml(
|
||||
/// A [path]($syntax/#paths) to an XML file or raw XML bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Value> {
|
||||
let data = source.load(engine.world)?;
|
||||
let text = data.load_str()?;
|
||||
let loaded = source.load(engine.world)?;
|
||||
let text = loaded.load_str()?;
|
||||
let document = roxmltree::Document::parse_with_options(
|
||||
text,
|
||||
ParsingOptions { allow_dtd: true, ..Default::default() },
|
||||
)
|
||||
.map_err(format_xml_error)
|
||||
.in_text(&data)?;
|
||||
.within(&loaded)?;
|
||||
Ok(convert_xml(document.root()))
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use ecow::{eco_format, EcoVec};
|
||||
use ecow::eco_format;
|
||||
use typst_syntax::Spanned;
|
||||
|
||||
use crate::diag::{At, LineCol, ReportPos, SourceDiagnostic, SourceResult};
|
||||
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, Loaded, Readable};
|
||||
use crate::loading::{DataSource, Load, Readable};
|
||||
|
||||
/// Reads structured data from a YAML file.
|
||||
///
|
||||
@ -44,9 +44,10 @@ pub fn yaml(
|
||||
/// A [path]($syntax/#paths) to a YAML file or raw YAML bytes.
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Value> {
|
||||
let data = source.load(engine.world)?;
|
||||
serde_yaml::from_slice(data.bytes.as_slice())
|
||||
.map_err(|err| format_yaml_error(&data, err))
|
||||
let loaded = source.load(engine.world)?;
|
||||
serde_yaml::from_slice(loaded.data.as_slice())
|
||||
.map_err(format_yaml_error)
|
||||
.within(&loaded)
|
||||
}
|
||||
|
||||
#[scope]
|
||||
@ -76,10 +77,7 @@ impl yaml {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_yaml_error(
|
||||
data: &Loaded,
|
||||
error: serde_yaml::Error,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
pub fn format_yaml_error(error: serde_yaml::Error) -> LoadError {
|
||||
let pos = error
|
||||
.location()
|
||||
.map(|loc| {
|
||||
@ -88,5 +86,5 @@ pub fn format_yaml_error(
|
||||
ReportPos::full(range, line_col)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
data.err_in_text(pos, "failed to parse YAML", error)
|
||||
LoadError::new(pos, "failed to parse YAML", error)
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ use typst_syntax::{Span, Spanned};
|
||||
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
|
||||
|
||||
use crate::diag::{
|
||||
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedAt, ReportPos,
|
||||
SourceDiagnostic, SourceResult, StrResult,
|
||||
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos,
|
||||
SourceResult, StrResult,
|
||||
};
|
||||
use crate::engine::{Engine, Sink};
|
||||
use crate::foundations::{
|
||||
@ -34,7 +34,7 @@ use crate::layout::{
|
||||
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
|
||||
Sides, Sizing, TrackSizings,
|
||||
};
|
||||
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, LoadStr, Loaded};
|
||||
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
|
||||
use crate::model::{
|
||||
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
||||
Url,
|
||||
@ -297,8 +297,8 @@ impl Bibliography {
|
||||
world: Tracked<dyn World + '_>,
|
||||
sources: Spanned<OneOrMultiple<DataSource>>,
|
||||
) -> SourceResult<Derived<OneOrMultiple<DataSource>, Self>> {
|
||||
let data = sources.load(world)?;
|
||||
let bibliography = Self::decode(&data)?;
|
||||
let loaded = sources.load(world)?;
|
||||
let bibliography = Self::decode(&loaded)?;
|
||||
Ok(Derived::new(sources.v, bibliography))
|
||||
}
|
||||
|
||||
@ -355,10 +355,10 @@ impl Debug for Bibliography {
|
||||
}
|
||||
|
||||
/// Decode on library from one data source.
|
||||
fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
||||
let str = data.load_str()?;
|
||||
fn decode_library(loaded: &Loaded) -> SourceResult<Library> {
|
||||
let data = loaded.load_str()?;
|
||||
|
||||
if let LoadSource::Path(file_id) = data.source.v {
|
||||
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 = file_id
|
||||
@ -369,25 +369,27 @@ fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
||||
.unwrap_or_default();
|
||||
|
||||
match ext.to_lowercase().as_str() {
|
||||
"yml" | "yaml" => hayagriva::io::from_yaml_str(str)
|
||||
.map_err(|err| format_yaml_error(data, err)),
|
||||
"bib" => hayagriva::io::from_biblatex_str(str)
|
||||
.map_err(|errors| format_biblatex_error(data, errors)),
|
||||
"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!(
|
||||
data.source.span,
|
||||
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(str) {
|
||||
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(str) {
|
||||
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.
|
||||
@ -401,7 +403,7 @@ fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
||||
// and emit the more appropriate error.
|
||||
let mut yaml = 0;
|
||||
let mut biblatex = 0;
|
||||
for c in str.chars() {
|
||||
for c in data.chars() {
|
||||
match c {
|
||||
':' => yaml += 1,
|
||||
'{' => biblatex += 1,
|
||||
@ -411,22 +413,19 @@ fn decode_library(data: &Loaded) -> SourceResult<Library> {
|
||||
|
||||
match bib_errs {
|
||||
Some(bib_errs) if biblatex >= yaml => {
|
||||
Err(format_biblatex_error(data, bib_errs))
|
||||
Err(format_biblatex_error(bib_errs)).within(&loaded)
|
||||
}
|
||||
_ => Err(format_yaml_error(data, haya_err)),
|
||||
_ => Err(format_yaml_error(haya_err)).within(&loaded),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a BibLaTeX loading error.
|
||||
fn format_biblatex_error(
|
||||
data: &Loaded,
|
||||
errors: Vec<BibLaTeXError>,
|
||||
) -> EcoVec<SourceDiagnostic> {
|
||||
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 data.err_in_text(ReportPos::None, "failed to parse BibLaTeX", "???");
|
||||
return LoadError::new(ReportPos::None, "failed to parse BibLaTeX", "???");
|
||||
};
|
||||
|
||||
let (range, msg) = match error {
|
||||
@ -434,7 +433,7 @@ fn format_biblatex_error(
|
||||
BibLaTeXError::Type(error) => (error.span, error.kind.to_string()),
|
||||
};
|
||||
|
||||
data.err_in_text(range, "failed to parse BibLaTeX", msg)
|
||||
LoadError::new(range, "failed to parse BibLaTeX", msg)
|
||||
}
|
||||
|
||||
/// A loaded CSL style.
|
||||
@ -450,8 +449,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.bytes).in_text(&data)?
|
||||
let loaded = Spanned::new(source, span).load(world)?;
|
||||
Self::from_data(&loaded.data).within(&loaded)?
|
||||
}
|
||||
};
|
||||
Ok(Derived::new(source, style))
|
||||
|
@ -11,7 +11,7 @@ use typst_utils::ManuallyHash;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::Lang;
|
||||
use crate::diag::{LineCol, LoadError, LoadResult, LoadedAt, ReportPos, SourceResult};
|
||||
use crate::diag::{LineCol, LoadError, LoadResult, LoadedWithin, ReportPos, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed,
|
||||
@ -19,7 +19,7 @@ use crate::foundations::{
|
||||
};
|
||||
use crate::html::{tag, HtmlElem};
|
||||
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
|
||||
use crate::loading::{DataSource, Load, LoadStr};
|
||||
use crate::loading::{DataSource, Load};
|
||||
use crate::model::{Figurable, ParElem};
|
||||
use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize};
|
||||
use crate::visualize::Color;
|
||||
@ -539,10 +539,10 @@ impl RawSyntax {
|
||||
world: Tracked<dyn World + '_>,
|
||||
sources: Spanned<OneOrMultiple<DataSource>>,
|
||||
) -> SourceResult<Derived<OneOrMultiple<DataSource>, Vec<RawSyntax>>> {
|
||||
let data = sources.load(world)?;
|
||||
let list = data
|
||||
let loaded = sources.load(world)?;
|
||||
let list = loaded
|
||||
.iter()
|
||||
.map(|data| Self::decode(&data.bytes).in_text(data))
|
||||
.map(|data| Self::decode(&data.data).within(data))
|
||||
.collect::<SourceResult<_>>()?;
|
||||
Ok(Derived::new(sources.v, list))
|
||||
}
|
||||
@ -599,8 +599,8 @@ impl RawTheme {
|
||||
world: Tracked<dyn World + '_>,
|
||||
source: Spanned<DataSource>,
|
||||
) -> SourceResult<Derived<DataSource, Self>> {
|
||||
let data = source.load(world)?;
|
||||
let theme = Self::decode(&data.bytes).in_text(&data)?;
|
||||
let loaded = source.load(world)?;
|
||||
let theme = Self::decode(&loaded.data).within(&loaded)?;
|
||||
Ok(Derived::new(source.v, theme))
|
||||
}
|
||||
|
||||
|
@ -65,8 +65,8 @@ pub struct ImageElem {
|
||||
#[required]
|
||||
#[parse(
|
||||
let source = args.expect::<Spanned<DataSource>>("source")?;
|
||||
let data = source.load(engine.world)?;
|
||||
Derived::new(source.v, data)
|
||||
let loaded = source.load(engine.world)?;
|
||||
Derived::new(source.v, loaded)
|
||||
)]
|
||||
pub source: Derived<DataSource, Loaded>,
|
||||
|
||||
@ -154,8 +154,8 @@ pub struct ImageElem {
|
||||
/// to `{auto}`, Typst will try to extract an ICC profile from the image.
|
||||
#[parse(match args.named::<Spanned<Smart<DataSource>>>("icc")? {
|
||||
Some(Spanned { v: Smart::Custom(source), span }) => Some(Smart::Custom({
|
||||
let data = Spanned::new(&source, span).load(engine.world)?;
|
||||
Derived::new(source, data.bytes)
|
||||
let loaded = Spanned::new(&source, span).load(engine.world)?;
|
||||
Derived::new(source, loaded.data)
|
||||
})),
|
||||
Some(Spanned { v: Smart::Auto, .. }) => Some(Smart::Auto),
|
||||
None => None,
|
||||
@ -194,8 +194,8 @@ impl ImageElem {
|
||||
scaling: Option<Smart<ImageScaling>>,
|
||||
) -> StrResult<Content> {
|
||||
let bytes = data.v.into_bytes();
|
||||
let data = Loaded::new(Spanned::new(LoadSource::Bytes, data.span), bytes.clone());
|
||||
let source = Derived::new(DataSource::Bytes(bytes), data);
|
||||
let loaded = Loaded::new(Spanned::new(LoadSource::Bytes, data.span), bytes.clone());
|
||||
let source = Derived::new(DataSource::Bytes(bytes), loaded);
|
||||
let mut elem = ImageElem::new(source);
|
||||
if let Some(format) = format {
|
||||
elem.push_format(format);
|
||||
|
Loading…
x
Reference in New Issue
Block a user