diff --git a/Cargo.lock b/Cargo.lock index b16dae0d0..95e0511ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2925,6 +2925,7 @@ dependencies = [ "codespan-reporting", "comemo", "dirs", + "ecow", "env_proxy", "flate2", "inferno", diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index 4b1676513..d24183073 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -26,6 +26,7 @@ chrono = { version = "0.4.24", default-features = false, features = ["clock", "s clap = { version = "4.2.4", features = ["derive", "env"] } codespan-reporting = "0.11" comemo = "0.3" +ecow = "0.1.1" dirs = "5" flate2 = "1" inferno = "0.11.15" diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index c4800171c..8cec89536 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -93,7 +93,7 @@ pub fn compile_once( } print_diagnostics(world, &[], &warnings, command.common.diagnostic_format) - .map_err(|_| "failed to print diagnostics")?; + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; if let Some(open) = command.open.take() { open_file(open.as_deref(), &command.output())?; @@ -115,7 +115,7 @@ pub fn compile_once( &warnings, command.common.diagnostic_format, ) - .map_err(|_| "failed to print diagnostics")?; + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; } } @@ -135,7 +135,8 @@ fn export(document: &Document, command: &CompileCommand) -> StrResult<()> { fn export_pdf(document: &Document, command: &CompileCommand) -> StrResult<()> { let output = command.output(); let buffer = typst::export::pdf(document); - fs::write(output, buffer).map_err(|_| "failed to write PDF file")?; + fs::write(output, buffer) + .map_err(|err| eco_format!("failed to write PDF file ({err})"))?; Ok(()) } @@ -176,11 +177,14 @@ fn export_image( ImageExportFormat::Png => { let pixmap = typst::export::render(frame, command.ppi / 72.0, Color::WHITE); - pixmap.save_png(path).map_err(|_| "failed to write PNG file")?; + pixmap + .save_png(path) + .map_err(|err| eco_format!("failed to write PNG file ({err})"))?; } ImageExportFormat::Svg => { let svg = typst::export::svg(frame); - fs::write(path, svg).map_err(|_| "failed to write SVG file")?; + fs::write(path, svg) + .map_err(|err| eco_format!("failed to write SVG file ({err})"))?; } } } diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index a6e60e5b8..c5cfd5145 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -35,7 +35,7 @@ fn main() -> ExitCode { let _guard = match crate::tracing::setup_tracing(&ARGS) { Ok(guard) => guard, Err(err) => { - eprintln!("failed to initialize tracing {}", err); + eprintln!("failed to initialize tracing ({err})"); None } }; diff --git a/crates/typst-cli/src/package.rs b/crates/typst-cli/src/package.rs index bec865167..51a8e4bcb 100644 --- a/crates/typst-cli/src/package.rs +++ b/crates/typst-cli/src/package.rs @@ -3,6 +3,7 @@ use std::io::{self, Write}; use std::path::{Path, PathBuf}; use codespan_reporting::term::{self, termcolor}; +use ecow::eco_format; use termcolor::WriteColor; use typst::diag::{PackageError, PackageResult}; use typst::syntax::PackageSpec; @@ -50,18 +51,19 @@ fn download_package(spec: &PackageSpec, package_dir: &Path) -> PackageResult<()> ); print_downloading(spec).unwrap(); + let data = match download_with_progress(&url) { Ok(data) => data, Err(ureq::Error::Status(404, _)) => { return Err(PackageError::NotFound(spec.clone())) } - Err(_) => return Err(PackageError::NetworkFailed), + Err(err) => return Err(PackageError::NetworkFailed(Some(eco_format!("{err}")))), }; let decompressed = flate2::read::GzDecoder::new(data.as_slice()); - tar::Archive::new(decompressed).unpack(package_dir).map_err(|_| { + tar::Archive::new(decompressed).unpack(package_dir).map_err(|err| { fs::remove_dir_all(package_dir).ok(); - PackageError::MalformedArchive + PackageError::MalformedArchive(Some(eco_format!("{err}"))) }) } diff --git a/crates/typst-cli/src/query.rs b/crates/typst-cli/src/query.rs index 68cf33196..55f04fe80 100644 --- a/crates/typst-cli/src/query.rs +++ b/crates/typst-cli/src/query.rs @@ -31,7 +31,7 @@ pub fn query(command: &QueryCommand) -> StrResult<()> { let serialized = format(data, command)?; println!("{serialized}"); print_diagnostics(&world, &[], &warnings, command.common.diagnostic_format) - .map_err(|_| "failed to print diagnostics")?; + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; } // Print diagnostics. @@ -43,7 +43,7 @@ pub fn query(command: &QueryCommand) -> StrResult<()> { &warnings, command.common.diagnostic_format, ) - .map_err(|_| "failed to print diagnostics")?; + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; } } diff --git a/crates/typst-cli/src/tracing.rs b/crates/typst-cli/src/tracing.rs index 80c2ff651..c01efd6d0 100644 --- a/crates/typst-cli/src/tracing.rs +++ b/crates/typst-cli/src/tracing.rs @@ -128,10 +128,10 @@ impl TracingGuard { impl Drop for TracingGuard { fn drop(&mut self) { if !std::thread::panicking() { - if let Err(e) = self.finish() { + if let Err(err) = self.finish() { // Since we are finished, we cannot rely on tracing to log the // error. - eprintln!("failed to flush tracing flamegraph: {e}"); + eprintln!("failed to flush tracing flamegraph ({err})"); } } } diff --git a/crates/typst-cli/src/update.rs b/crates/typst-cli/src/update.rs index 562d7d2d2..95418d038 100644 --- a/crates/typst-cli/src/update.rs +++ b/crates/typst-cli/src/update.rs @@ -51,15 +51,15 @@ pub fn update(command: &UpdateCommand) -> StrResult<()> { return self_replace::self_replace(&backup_path) .and_then(|_| fs::remove_file(&backup_path)) - .map_err(|err| eco_format!("failed to revert to backup: {err}")); + .map_err(|err| eco_format!("failed to revert to backup ({err})")); } let current_exe = env::current_exe().map_err(|err| { - eco_format!("failed to locate path of the running executable: {err}") + eco_format!("failed to locate path of the running executable ({err})") })?; fs::copy(current_exe, &backup_path) - .map_err(|err| eco_format!("failed to create backup: {err}"))?; + .map_err(|err| eco_format!("failed to create backup ({err})"))?; let release = Release::from_tag(command.version.as_ref())?; if !update_needed(&release)? && !command.force { @@ -69,14 +69,14 @@ pub fn update(command: &UpdateCommand) -> StrResult<()> { let binary_data = release.download_binary(needed_asset()?)?; let mut temp_exe = NamedTempFile::new() - .map_err(|err| eco_format!("failed to create temporary file: {err}"))?; + .map_err(|err| eco_format!("failed to create temporary file ({err})"))?; temp_exe .write_all(&binary_data) - .map_err(|err| eco_format!("failed to write binary data: {err}"))?; + .map_err(|err| eco_format!("failed to write binary data ({err})"))?; self_replace::self_replace(&temp_exe).map_err(|err| { fs::remove_file(&temp_exe).ok(); - eco_format!("failed to self-replace running executable: {err}") + eco_format!("failed to self-replace running executable ({err})") }) } @@ -118,7 +118,7 @@ impl Release { Err(ureq::Error::Status(404, _)) => { bail!("release not found (searched at {url})") } - Err(_) => bail!("failed to download release (network failed)"), + Err(err) => bail!("failed to download release ({err})"), } } @@ -138,7 +138,7 @@ impl Release { Err(ureq::Error::Status(404, _)) => { bail!("asset not found (searched for {})", asset.name); } - Err(_) => bail!("failed to load asset (network failed)"), + Err(err) => bail!("failed to download asset ({err})"), }; if asset_name.contains("windows") { @@ -152,7 +152,7 @@ impl Release { /// Extract the Typst binary from a ZIP archive. fn extract_binary_from_zip(data: &[u8], asset_name: &str) -> StrResult> { let mut archive = ZipArchive::new(Cursor::new(data)) - .map_err(|err| eco_format!("failed to extract ZIP archive: {err}"))?; + .map_err(|err| eco_format!("failed to extract ZIP archive ({err})"))?; let mut file = archive .by_name(&format!("{asset_name}/typst.exe")) @@ -160,7 +160,7 @@ fn extract_binary_from_zip(data: &[u8], asset_name: &str) -> StrResult> let mut buffer = vec![]; file.read_to_end(&mut buffer).map_err(|err| { - eco_format!("failed to read binary data from ZIP archive: {err}") + eco_format!("failed to read binary data from ZIP archive ({err})") })?; Ok(buffer) @@ -172,14 +172,14 @@ fn extract_binary_from_tar_xz(data: &[u8]) -> StrResult> { let mut file = archive .entries() - .map_err(|err| eco_format!("failed to extract tar.xz archive: {err}"))? + .map_err(|err| eco_format!("failed to extract tar.xz archive ({err})"))? .filter_map(Result::ok) .find(|e| e.path().unwrap_or_default().ends_with("typst")) .ok_or("tar.xz archive did not contain Typst binary")?; let mut buffer = vec![]; file.read_to_end(&mut buffer).map_err(|err| { - eco_format!("failed to read binary data from tar.xz archive: {err}") + eco_format!("failed to read binary data from tar.xz archive ({err})") })?; Ok(buffer) @@ -235,7 +235,7 @@ fn backup_path() -> StrResult { let backup_dir = root_backup_dir.join("typst"); fs::create_dir_all(&backup_dir) - .map_err(|err| eco_format!("failed to create backup directory: {err}"))?; + .map_err(|err| eco_format!("failed to create backup directory ({err})"))?; Ok(backup_dir.join("typst_backup.part")) } diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index b320c6518..f53cb5fd8 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -25,7 +25,7 @@ pub fn watch(mut command: CompileCommand) -> StrResult<()> { // Setup file watching. let (tx, rx) = std::sync::mpsc::channel(); let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()) - .map_err(|_| "failed to setup file watching")?; + .map_err(|err| eco_format!("failed to setup file watching ({err})"))?; // Watch all the files that are used by the input file and its dependencies. watch_dependencies(&mut world, &mut watcher, HashSet::new())?; @@ -41,7 +41,8 @@ pub fn watch(mut command: CompileCommand) -> StrResult<()> { .into_iter() .chain(std::iter::from_fn(|| rx.recv_timeout(timeout).ok())) { - let event = event.map_err(|_| "failed to watch directory")?; + let event = + event.map_err(|err| eco_format!("failed to watch directory ({err})"))?; // Workaround for notify-rs' implicit unwatch on remove/rename // (triggered by some editors when saving files) with the inotify @@ -94,7 +95,7 @@ fn watch_dependencies( tracing::info!("Watching {}", path.display()); watcher .watch(path, RecursiveMode::NonRecursive) - .map_err(|_| eco_format!("failed to watch {path:?}"))?; + .map_err(|err| eco_format!("failed to watch {path:?} ({err})"))?; } } diff --git a/crates/typst-library/src/compute/data.rs b/crates/typst-library/src/compute/data.rs index 92c462e62..222b14d35 100644 --- a/crates/typst-library/src/compute/data.rs +++ b/crates/typst-library/src/compute/data.rs @@ -1,5 +1,6 @@ use typst::diag::{format_xml_like_error, FileError}; use typst::eval::Bytes; +use typst::syntax::is_newline; use crate::prelude::*; @@ -197,15 +198,16 @@ cast! { } /// Format the user-facing CSV error message. -fn format_csv_error(error: csv::Error, line: usize) -> EcoString { - match error.kind() { +fn format_csv_error(err: csv::Error, line: usize) -> EcoString { + match err.kind() { csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), csv::ErrorKind::UnequalLengths { expected_len, len, .. } => { eco_format!( - "failed to parse csv file: found {len} instead of {expected_len} fields in line {line}" + "failed to parse CSV (found {len} instead of \ + {expected_len} fields in line {line})" ) } - _ => "failed to parse csv file".into(), + _ => eco_format!("failed to parse CSV ({err})"), } } @@ -278,7 +280,7 @@ pub fn json_decode( ) -> SourceResult { let Spanned { v: data, span } = data; serde_json::from_slice(data.as_slice()) - .map_err(format_json_error) + .map_err(|err| eco_format!("failed to parse JSON ({err})")) .at(span) } @@ -302,16 +304,10 @@ pub fn json_encode( serde_json::to_string(&value) } .map(|v| v.into()) - .map_err(|e| eco_format!("failed to encode value as json: {e}")) + .map_err(|err| eco_format!("failed to encode value as JSON ({err})")) .at(span) } -/// Format the user-facing JSON error message. -fn format_json_error(error: serde_json::Error) -> EcoString { - assert!(error.is_syntax() || error.is_eof()); - eco_format!("failed to parse json file: syntax error in line {}", error.line()) -} - /// Reads structured data from a TOML file. /// /// The file must contain a valid TOML table. TOML tables will be converted into @@ -366,7 +362,9 @@ pub fn toml_decode( let raw = std::str::from_utf8(data.as_slice()) .map_err(|_| "file is not valid utf-8") .at(span)?; - toml::from_str(raw).map_err(format_toml_error).at(span) + toml::from_str(raw) + .map_err(|err| format_toml_error(err, raw)) + .at(span) } /// Encodes structured data into a TOML string. @@ -385,21 +383,21 @@ pub fn toml_encode( let Spanned { v: value, span } = value; if pretty { toml::to_string_pretty(&value) } else { toml::to_string(&value) } .map(|v| v.into()) - .map_err(|e| eco_format!("failed to encode value as toml: {e}")) + .map_err(|err| eco_format!("failed to encode value as TOML ({err})")) .at(span) } /// Format the user-facing TOML error message. -fn format_toml_error(error: toml::de::Error) -> EcoString { - if let Some(range) = error.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 file: {}, index {}-{}", + "failed to parse TOML ({} at line {line} column {column})", error.message(), - range.start, - range.end ) } else { - eco_format!("failed to parse toml file: {}", error.message()) + eco_format!("failed to parse TOML ({})", error.message()) } } @@ -464,7 +462,7 @@ pub fn yaml_decode( ) -> SourceResult { let Spanned { v: data, span } = data; serde_yaml::from_slice(data.as_slice()) - .map_err(format_yaml_error) + .map_err(|err| eco_format!("failed to parse YAML ({err})")) .at(span) } @@ -480,15 +478,10 @@ pub fn yaml_encode( let Spanned { v: value, span } = value; serde_yaml::to_string(&value) .map(|v| v.into()) - .map_err(|e| eco_format!("failed to encode value as yaml: {e}")) + .map_err(|err| eco_format!("failed to encode value as YAML ({err})")) .at(span) } -/// Format the user-facing YAML error message. -fn format_yaml_error(error: serde_yaml::Error) -> EcoString { - eco_format!("failed to parse yaml file: {}", error.to_string().trim()) -} - /// Reads structured data from a CBOR file. /// /// The file must contain a valid cbor serialization. Mappings will be @@ -529,7 +522,7 @@ pub fn cbor_decode( ) -> SourceResult { let Spanned { v: data, span } = data; ciborium::from_reader(data.as_slice()) - .map_err(|e| eco_format!("failed to parse cbor: {e}")) + .map_err(|err| eco_format!("failed to parse CBOR ({err})")) .at(span) } @@ -546,7 +539,7 @@ pub fn cbor_encode( let mut res = Vec::new(); ciborium::into_writer(&value, &mut res) .map(|_| res.into()) - .map_err(|e| eco_format!("failed to encode value as cbor: {e}")) + .map_err(|err| eco_format!("failed to encode value as CBOR ({err})")) .at(span) } @@ -661,5 +654,5 @@ fn convert_xml(node: roxmltree::Node) -> Value { /// Format the user-facing XML error message. fn format_xml_error(error: roxmltree::Error) -> EcoString { - format_xml_like_error("xml file", error) + format_xml_like_error("XML", error) } diff --git a/crates/typst-library/src/meta/bibliography.rs b/crates/typst-library/src/meta/bibliography.rs index 164b1c11f..d871db232 100644 --- a/crates/typst-library/src/meta/bibliography.rs +++ b/crates/typst-library/src/meta/bibliography.rs @@ -650,8 +650,8 @@ fn parse_bib(path_str: &str, src: &str) -> StrResult> { } /// Format a Hayagriva loading error. -fn format_hayagriva_error(error: YamlBibliographyError) -> EcoString { - eco_format!("{error}") +fn format_hayagriva_error(err: YamlBibliographyError) -> EcoString { + eco_format!("{err}") } /// Format a BibLaTeX loading error. diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index a9d404140..a5699afd9 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -485,10 +485,9 @@ fn load_syntaxes(paths: &SyntaxPaths, bytes: &[Bytes]) -> StrResult StrResult> { synt::ThemeSet::load_from_reader(&mut cursor) .map(Arc::new) - .map_err(|e| eco_format!("failed to parse theme file `{path}`: {e}")) + .map_err(|err| eco_format!("failed to parse theme file `{path}` ({err})")) } /// Function to parse the theme argument. diff --git a/crates/typst/src/diag.rs b/crates/typst/src/diag.rs index 7c298ec03..a6379d65f 100644 --- a/crates/typst/src/diag.rs +++ b/crates/typst/src/diag.rs @@ -320,21 +320,23 @@ pub enum FileError { /// The package the file is part of could not be loaded. Package(PackageError), /// Another error. - Other, + /// + /// The optional string can give more details, if available. + Other(Option), } impl FileError { /// Create a file error from an I/O error. - pub fn from_io(error: io::Error, path: &Path) -> Self { - match error.kind() { + pub fn from_io(err: io::Error, path: &Path) -> Self { + match err.kind() { io::ErrorKind::NotFound => Self::NotFound(path.into()), io::ErrorKind::PermissionDenied => Self::AccessDenied, io::ErrorKind::InvalidData - if error.to_string().contains("stream did not contain valid UTF-8") => + if err.to_string().contains("stream did not contain valid UTF-8") => { Self::InvalidUtf8 } - _ => Self::Other, + _ => Self::Other(Some(eco_format!("{err}"))), } } } @@ -352,7 +354,8 @@ impl Display for FileError { Self::NotSource => f.pad("not a typst source file"), Self::InvalidUtf8 => f.pad("file is not valid utf-8"), Self::Package(error) => error.fmt(f), - Self::Other => f.pad("failed to load file"), + Self::Other(Some(err)) => write!(f, "failed to load file ({err})"), + Self::Other(None) => f.pad("failed to load file"), } } } @@ -370,14 +373,14 @@ impl From for FileError { } impl From for FileError { - fn from(error: PackageError) -> Self { - Self::Package(error) + fn from(err: PackageError) -> Self { + Self::Package(err) } } impl From for EcoString { - fn from(error: FileError) -> Self { - eco_format!("{error}") + fn from(err: FileError) -> Self { + eco_format!("{err}") } } @@ -385,16 +388,18 @@ impl From for EcoString { pub type PackageResult = Result; /// An error that occured while trying to load a package. +/// +/// Some variants have an optional string can give more details, if available. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum PackageError { /// The specified package does not exist. NotFound(PackageSpec), /// Failed to retrieve the package through the network. - NetworkFailed, + NetworkFailed(Option), /// The package archive was malformed. - MalformedArchive, + MalformedArchive(Option), /// Another error. - Other, + Other(Option), } impl std::error::Error for PackageError {} @@ -405,16 +410,25 @@ impl Display for PackageError { Self::NotFound(spec) => { write!(f, "package not found (searched for {spec})",) } - Self::NetworkFailed => f.pad("failed to load package (network failed)"), - Self::MalformedArchive => f.pad("failed to load package (archive malformed)"), - Self::Other => f.pad("failed to load package"), + Self::NetworkFailed(Some(err)) => { + write!(f, "failed to download package ({err})") + } + Self::NetworkFailed(None) => f.pad("failed to download package"), + Self::MalformedArchive(Some(err)) => { + write!(f, "failed to decompress package ({err})") + } + Self::MalformedArchive(None) => { + f.pad("failed to decompress package (archive malformed)") + } + Self::Other(Some(err)) => write!(f, "failed to load package ({err})"), + Self::Other(None) => f.pad("failed to load package"), } } } impl From for EcoString { - fn from(error: PackageError) -> Self { - eco_format!("{error}") + fn from(err: PackageError) -> Self { + eco_format!("{err}") } } @@ -423,26 +437,26 @@ pub fn format_xml_like_error(format: &str, error: roxmltree::Error) -> EcoString match error { roxmltree::Error::UnexpectedCloseTag { expected, actual, pos } => { eco_format!( - "failed to parse {format}: found closing tag '{actual}' \ - instead of '{expected}' in line {}", + "failed to parse {format} (found closing tag '{actual}' \ + instead of '{expected}' in line {})", pos.row ) } roxmltree::Error::UnknownEntityReference(entity, pos) => { eco_format!( - "failed to parse {format}: unknown entity '{entity}' in line {}", + "failed to parse {format} (unknown entity '{entity}' in line {})", pos.row ) } roxmltree::Error::DuplicatedAttribute(attr, pos) => { eco_format!( - "failed to parse {format}: duplicate attribute '{attr}' in line {}", + "failed to parse {format}: (duplicate attribute '{attr}' in line {})", pos.row ) } roxmltree::Error::NoRootNode => { - eco_format!("failed to parse {format}: missing root node") + eco_format!("failed to parse {format} (missing root node)") } - _ => eco_format!("failed to parse {format}"), + err => eco_format!("failed to parse {format} ({err})"), } } diff --git a/crates/typst/src/eval/datetime.rs b/crates/typst/src/eval/datetime.rs index 96127e9d4..ff2d76347 100644 --- a/crates/typst/src/eval/datetime.rs +++ b/crates/typst/src/eval/datetime.rs @@ -228,7 +228,10 @@ impl Debug for Datetime { fn format_time_format_error(error: Format) -> EcoString { match error { Format::InvalidComponent(name) => eco_format!("invalid component '{}'", name), - _ => "failed to format datetime in the requested format".into(), + Format::InsufficientTypeInformation { .. } => { + "failed to format datetime (insufficient information)".into() + } + err => eco_format!("failed to format datetime in the requested format ({err})"), } } @@ -263,6 +266,6 @@ fn format_time_invalid_format_description_error( InvalidFormatDescription::NotSupported { context, what, index, .. } => { eco_format!("{} is not supported in {} at index {}", what, context, index) } - _ => "failed to parse datetime format".into(), + err => eco_format!("failed to parse datetime format ({err})"), } } diff --git a/crates/typst/src/eval/plugin.rs b/crates/typst/src/eval/plugin.rs index 82bb5b6ae..7dbc8b3ef 100644 --- a/crates/typst/src/eval/plugin.rs +++ b/crates/typst/src/eval/plugin.rs @@ -42,7 +42,7 @@ impl Plugin { pub fn new(bytes: Bytes) -> StrResult { let engine = Engine::default(); let module = Module::new(&engine, bytes.as_slice()) - .map_err(|err| format!("failed to load WebAssembly module: {err}"))?; + .map_err(|err| format!("failed to load WebAssembly module ({err})"))?; let mut linker = Linker::new(&engine); linker diff --git a/crates/typst/src/image.rs b/crates/typst/src/image.rs index 83d8f3a78..3c2cae880 100644 --- a/crates/typst/src/image.rs +++ b/crates/typst/src/image.rs @@ -8,7 +8,7 @@ use std::rc::Rc; use std::sync::Arc; use comemo::{Prehashed, Track, Tracked}; -use ecow::{EcoString, EcoVec}; +use ecow::{eco_format, EcoString, EcoVec}; use image::codecs::gif::GifDecoder; use image::codecs::jpeg::JpegDecoder; use image::codecs::png::PngDecoder; @@ -463,7 +463,7 @@ impl SvgFontLoader for PreparedLoader { fn format_image_error(error: image::ImageError) -> EcoString { match error { image::ImageError::Limits(_) => "file is too large".into(), - _ => "failed to decode image".into(), + err => eco_format!("failed to decode image ({err})"), } } @@ -474,8 +474,8 @@ fn format_usvg_error(error: usvg::Error) -> EcoString { usvg::Error::MalformedGZip => "file is not compressed correctly".into(), usvg::Error::ElementsLimitReached => "file is too large".into(), usvg::Error::InvalidSize => { - "failed to parse svg: width, height, or viewbox is invalid".into() + "failed to parse SVG (width, height, or viewbox is invalid)".into() } - usvg::Error::ParsingFailed(error) => format_xml_like_error("svg", error), + usvg::Error::ParsingFailed(error) => format_xml_like_error("SVG", error), } } diff --git a/tests/typ/compute/construct.typ b/tests/typ/compute/construct.typ index ea9d28161..80e0876a5 100644 --- a/tests/typ/compute/construct.typ +++ b/tests/typ/compute/construct.typ @@ -221,5 +221,5 @@ #datetime.today().display(" []") --- -// Error: 26-36 failed to format datetime in the requested format +// Error: 26-36 failed to format datetime (insufficient information) #datetime.today().display("[hour]") diff --git a/tests/typ/compute/data.typ b/tests/typ/compute/data.typ index c613c479a..a8c79da15 100644 --- a/tests/typ/compute/data.typ +++ b/tests/typ/compute/data.typ @@ -27,7 +27,7 @@ #csv("nope.csv") --- -// Error: 6-22 failed to parse csv file: found 3 instead of 2 fields in line 3 +// Error: 6-22 failed to parse CSV (found 3 instead of 2 fields in line 3) #csv("/files/bad.csv") --- @@ -38,7 +38,7 @@ #test(data.at(2).weight, 150) --- -// Error: 7-24 failed to parse json file: syntax error in line 3 +// Error: 7-24 failed to parse JSON (expected value at line 3 column 14) #json("/files/bad.json") --- @@ -80,7 +80,7 @@ )) --- -// Error: 7-24 failed to parse toml file: expected `.`, `=`, index 15-16 +// Error: 7-24 failed to parse TOML (expected `.`, `=` at line 1 column 16) #toml("/files/bad.toml") --- @@ -98,7 +98,7 @@ #test(data.at("1"), "ok") --- -// Error: 7-24 failed to parse yaml file: while parsing a flow sequence, expected ',' or ']' at line 2 column 1 +// Error: 7-24 failed to parse YAML (while parsing a flow sequence, expected ',' or ']' at line 2 column 1) #yaml("/files/bad.yaml") --- @@ -127,5 +127,5 @@ ),)) --- -// Error: 6-22 failed to parse xml file: found closing tag 'data' instead of 'hello' in line 3 +// Error: 6-22 failed to parse XML (found closing tag 'data' instead of 'hello' in line 3) #xml("/files/bad.xml") diff --git a/tests/typ/visualize/image.typ b/tests/typ/visualize/image.typ index 60ce4f683..0e256eb86 100644 --- a/tests/typ/visualize/image.typ +++ b/tests/typ/visualize/image.typ @@ -58,7 +58,7 @@ A #box(image("/files/tiger.jpg", height: 1cm, width: 80%)) B #image("./image.typ") --- -// Error: 2-25 failed to parse svg: found closing tag 'g' instead of 'style' in line 4 +// Error: 2-25 failed to parse SVG (found closing tag 'g' instead of 'style' in line 4) #image("/files/bad.svg") --- @@ -66,7 +66,7 @@ A #box(image("/files/tiger.jpg", height: 1cm, width: 80%)) B #image.decode(``.text, format: "svg") --- -// Error: 2-168 failed to parse svg: missing root node +// Error: 2-168 failed to parse SVG (missing root node) #image.decode(``.text, format: "svg") --- @@ -78,5 +78,5 @@ A #box(image("/files/tiger.jpg", height: 1cm, width: 80%)) B #image.decode(read("/files/tiger.jpg", encoding: none), format: "jpg", width: 80%) --- -// Error: 2-83 failed to decode image +// Error: 2-83 failed to decode image (Format error decoding Png: Invalid PNG signature.) #image.decode(read("/files/tiger.jpg", encoding: none), format: "png", width: 80%)