diff --git a/Cargo.lock b/Cargo.lock index 20b1bf44..6f8edac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" +[[package]] +name = "anstyle-lossy" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a0444767dbd4aea9355cb47a370eb184dbfe918875e127eff52cb9d1638181" +dependencies = [ + "anstyle", +] + [[package]] name = "anstyle-parse" version = "0.2.1" @@ -64,6 +73,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anstyle-svg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84fef329014c87809e539be8caa9b817f2d072d7ecb39d7e3c6d96da17848a89" +dependencies = [ + "anstream", + "anstyle", + "anstyle-lossy", + "html-escape", + "unicode-width", +] + [[package]] name = "anstyle-wincon" version = "3.0.2" @@ -388,6 +410,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "humantime" version = "2.1.0" @@ -826,6 +857,7 @@ version = "0.5.1" dependencies = [ "anstream", "anstyle", + "anstyle-svg", "backtrace", "content_inspector", "document-features", @@ -957,6 +989,18 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/crates/snapbox/Cargo.toml b/crates/snapbox/Cargo.toml index 8441c890..f221af9e 100644 --- a/crates/snapbox/Cargo.toml +++ b/crates/snapbox/Cargo.toml @@ -45,6 +45,8 @@ examples = ["dep:escargot"] ## Snapshotting of json json = ["structured-data", "dep:serde_json"] +## Snapshotting of term styling +term-svg = ["structured-data", "dep:anstyle-svg"] ## Snapshotting of structured data structured-data = ["dep:serde_json"] @@ -91,6 +93,7 @@ anstream = { version = "0.6.7", optional = true } document-features = { version = "0.2.6", optional = true } serde_json = { version = "1.0.85", optional = true} +anstyle-svg = { version = "0.1.0", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = ["Win32_Foundation"], optional = true } diff --git a/crates/snapbox/src/data/format.rs b/crates/snapbox/src/data/format.rs index 3bfea284..f22eec32 100644 --- a/crates/snapbox/src/data/format.rs +++ b/crates/snapbox/src/data/format.rs @@ -6,6 +6,8 @@ pub enum DataFormat { Text, #[cfg(feature = "json")] Json, + #[cfg(feature = "term-svg")] + TermSvg, } impl DataFormat { @@ -16,6 +18,8 @@ impl DataFormat { Self::Text => "txt", #[cfg(feature = "json")] Self::Json => "json", + #[cfg(feature = "term-svg")] + Self::TermSvg => "term.svg", } } } diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 3631bfc2..796899d8 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -113,6 +113,8 @@ enum DataInner { Text(String), #[cfg(feature = "json")] Json(serde_json::Value), + #[cfg(feature = "term-svg")] + TermSvg(String), } impl Data { @@ -181,18 +183,38 @@ impl Data { .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?; Self::json(serde_json::from_str::(&data).unwrap()) } + #[cfg(feature = "term-svg")] + DataFormat::TermSvg => { + let data = std::fs::read_to_string(path) + .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?; + Self::from(DataInner::TermSvg(data)) + } }, None => { let data = std::fs::read(path) .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?; let data = Self::binary(data); - match path - .extension() + + let file_name = path + .file_name() .and_then(|e| e.to_str()) - .unwrap_or_default() - { + .unwrap_or_default(); + let (file_stem, mut ext) = file_name.split_once('.').unwrap_or((file_name, "")); + if file_stem.is_empty() { + (_, ext) = file_stem.split_once('.').unwrap_or((file_name, "")); + } + match ext { #[cfg(feature = "json")] "json" => data.coerce_to(DataFormat::Json), + #[cfg(feature = "term-svg")] + "term.svg" => { + let data = data.coerce_to(DataFormat::Text); + let inner = match data.inner { + DataInner::Text(text) => DataInner::TermSvg(text), + other => other, + }; + inner.into() + } _ => data.coerce_to(DataFormat::Text), } } @@ -244,6 +266,8 @@ impl Data { DataInner::Text(data) => Some(data.to_owned()), #[cfg(feature = "json")] DataInner::Json(value) => Some(serde_json::to_string_pretty(value).unwrap()), + #[cfg(feature = "term-svg")] + DataInner::TermSvg(data) => Some(data.to_owned()), } } @@ -256,6 +280,8 @@ impl Data { DataInner::Json(value) => { serde_json::to_vec_pretty(value).map_err(|err| format!("{err}").into()) } + #[cfg(feature = "term-svg")] + DataInner::TermSvg(data) => Ok(data.clone().into_bytes()), } } @@ -267,6 +293,8 @@ impl Data { (DataInner::Text(inner), DataFormat::Text) => Self::text(inner), #[cfg(feature = "json")] (DataInner::Json(inner), DataFormat::Json) => Self::json(inner), + #[cfg(feature = "term-svg")] + (DataInner::TermSvg(inner), DataFormat::TermSvg) => inner.into(), (DataInner::Binary(inner), _) => { if is_binary(&inner) { Self::binary(inner) @@ -296,6 +324,10 @@ impl Data { Err(_) => Self::text(inner), } } + #[cfg(feature = "term-svg")] + (DataInner::Text(inner), DataFormat::TermSvg) => { + DataInner::TermSvg(anstyle_svg::Term::new().render_svg(&inner)).into() + } (inner, DataFormat::Binary) => { let remake: Self = inner.into(); Self::binary(remake.to_bytes().expect("error case handled")) @@ -310,6 +342,14 @@ impl Data { remake } } + // reachable if more than one structured data format is enabled + #[allow(unreachable_patterns)] + #[cfg(feature = "json")] + (inner, DataFormat::Json) => inner.into(), + // reachable if more than one structured data format is enabled + #[allow(unreachable_patterns)] + #[cfg(feature = "term-svg")] + (inner, DataFormat::TermSvg) => inner.into(), }; data.source = self.source; data @@ -323,6 +363,8 @@ impl Data { DataInner::Text(_) => DataFormat::Text, #[cfg(feature = "json")] DataInner::Json(_) => DataFormat::Json, + #[cfg(feature = "term-svg")] + DataInner::TermSvg(_) => DataFormat::TermSvg, } } } @@ -344,6 +386,8 @@ impl std::fmt::Display for Data { DataInner::Text(data) => data.fmt(f), #[cfg(feature = "json")] DataInner::Json(data) => serde_json::to_string_pretty(data).unwrap().fmt(f), + #[cfg(feature = "term-svg")] + DataInner::TermSvg(data) => data.fmt(f), } } } diff --git a/crates/snapbox/src/data/normalize.rs b/crates/snapbox/src/data/normalize.rs index 316bcba7..80815f67 100644 --- a/crates/snapbox/src/data/normalize.rs +++ b/crates/snapbox/src/data/normalize.rs @@ -21,6 +21,11 @@ impl Normalize for NormalizeNewlines { normalize_value(&mut value, crate::utils::normalize_lines); Data::json(value) } + #[cfg(feature = "term-svg")] + DataInner::TermSvg(text) => { + let lines = crate::utils::normalize_lines(&text); + DataInner::TermSvg(lines).into() + } }; new.source = data.source; new @@ -43,6 +48,11 @@ impl Normalize for NormalizePaths { normalize_value(&mut value, crate::utils::normalize_paths); Data::json(value) } + #[cfg(feature = "term-svg")] + DataInner::TermSvg(text) => { + let lines = crate::utils::normalize_paths(&text); + DataInner::TermSvg(lines).into() + } }; new.source = data.source; new @@ -82,6 +92,13 @@ impl Normalize for NormalizeMatches<'_> { } Data::json(value) } + #[cfg(feature = "term-svg")] + DataInner::TermSvg(text) => { + let lines = self + .substitutions + .normalize(&text, &self.pattern.render().unwrap()); + DataInner::TermSvg(lines).into() + } }; new.source = data.source; new