Skip to content

Commit

Permalink
feat(snap): Jsonlines support
Browse files Browse the repository at this point in the history
Looks like ndjson was merged into jsonlines, so went with that.

One difference at one point was allowing blank lines from what I've
heard.  I'm not seeing it mentioned in jsonlines but went with
supporting it.

Part of assert-rs#92
  • Loading branch information
epage committed Apr 19, 2024
1 parent 5c46d3c commit ae6bce4
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 5 deletions.
6 changes: 6 additions & 0 deletions crates/snapbox/src/data/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub enum DataFormat {
Text,
#[cfg(feature = "json")]
Json,
#[cfg(feature = "json")]
JsonLines,
#[cfg(feature = "term-svg")]
TermSvg,
}
Expand All @@ -18,6 +20,8 @@ impl DataFormat {
Self::Text => "txt",
#[cfg(feature = "json")]
Self::Json => "json",
#[cfg(feature = "json")]
Self::JsonLines => "jsonl",
#[cfg(feature = "term-svg")]
Self::TermSvg => "term.svg",
}
Expand All @@ -37,6 +41,8 @@ impl From<&std::path::Path> for DataFormat {
match ext {
#[cfg(feature = "json")]
"json" => DataFormat::Json,
#[cfg(feature = "json")]
"jsonl" => DataFormat::JsonLines,
#[cfg(feature = "term-svg")]
"term.svg" => Self::TermSvg,
_ => DataFormat::Text,
Expand Down
73 changes: 68 additions & 5 deletions crates/snapbox/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ pub(crate) enum DataInner {
Text(String),
#[cfg(feature = "json")]
Json(serde_json::Value),
// Always a `Value::Array` but using `Value` for easier bookkeeping
#[cfg(feature = "json")]
JsonLines(serde_json::Value),
#[cfg(feature = "term-svg")]
TermSvg(String),
}
Expand All @@ -128,6 +131,11 @@ impl Data {
DataInner::Json(raw.into()).into()
}

#[cfg(feature = "json")]
pub fn jsonlines(raw: impl Into<Vec<serde_json::Value>>) -> Self {
DataInner::JsonLines(serde_json::Value::Array(raw.into())).into()
}

fn error(raw: impl Into<crate::Error>, intended: DataFormat) -> Self {
DataError {
error: raw.into(),
Expand Down Expand Up @@ -173,7 +181,7 @@ impl Data {
let inferred_format = DataFormat::from(path);
match inferred_format {
#[cfg(feature = "json")]
DataFormat::Json => data.coerce_to(inferred_format),
DataFormat::Json | DataFormat::JsonLines => data.coerce_to(inferred_format),
#[cfg(feature = "term-svg")]
DataFormat::TermSvg => {
let data = data.coerce_to(DataFormat::Text);
Expand Down Expand Up @@ -229,7 +237,9 @@ impl Data {
DataInner::Binary(_) => None,
DataInner::Text(data) => Some(data.to_owned()),
#[cfg(feature = "json")]
DataInner::Json(value) => Some(serde_json::to_string_pretty(value).unwrap()),
DataInner::Json(_) => Some(self.to_string()),
#[cfg(feature = "json")]
DataInner::JsonLines(_) => Some(self.to_string()),
#[cfg(feature = "term-svg")]
DataInner::TermSvg(data) => Some(data.to_owned()),
}
Expand All @@ -241,9 +251,9 @@ impl Data {
DataInner::Binary(data) => Ok(data.clone()),
DataInner::Text(data) => Ok(data.clone().into_bytes()),
#[cfg(feature = "json")]
DataInner::Json(value) => {
serde_json::to_vec_pretty(value).map_err(|err| format!("{err}").into())
}
DataInner::Json(_) => Ok(self.to_string().into_bytes()),
#[cfg(feature = "json")]
DataInner::JsonLines(_) => Ok(self.to_string().into_bytes()),
#[cfg(feature = "term-svg")]
DataInner::TermSvg(data) => Ok(data.clone().into_bytes()),
}
Expand All @@ -268,6 +278,8 @@ impl Data {
(DataInner::Text(inner), DataFormat::Text) => DataInner::Text(inner),
#[cfg(feature = "json")]
(DataInner::Json(inner), DataFormat::Json) => DataInner::Json(inner),
#[cfg(feature = "json")]
(DataInner::JsonLines(inner), DataFormat::JsonLines) => DataInner::JsonLines(inner),
#[cfg(feature = "term-svg")]
(DataInner::TermSvg(inner), DataFormat::TermSvg) => DataInner::TermSvg(inner),
(DataInner::Binary(inner), _) => {
Expand All @@ -280,6 +292,11 @@ impl Data {
.map_err(|err| err.to_string())?;
DataInner::Json(inner)
}
#[cfg(feature = "json")]
(DataInner::Text(inner), DataFormat::JsonLines) => {
let inner = parse_jsonlines(&inner).map_err(|err| err.to_string())?;
DataInner::JsonLines(serde_json::Value::Array(inner))
}
#[cfg(feature = "term-svg")]
(DataInner::Text(inner), DataFormat::TermSvg) => DataInner::TermSvg(inner),
(inner, DataFormat::Binary) => {
Expand Down Expand Up @@ -311,6 +328,10 @@ impl Data {
(DataInner::Text(inner), DataFormat::Text) => Self::text(inner),
#[cfg(feature = "json")]
(DataInner::Json(inner), DataFormat::Json) => Self::json(inner),
#[cfg(feature = "json")]
(DataInner::JsonLines(inner), DataFormat::JsonLines) => {
DataInner::JsonLines(inner).into()
}
#[cfg(feature = "term-svg")]
(DataInner::TermSvg(inner), DataFormat::TermSvg) => inner.into(),
(DataInner::Binary(inner), _) => {
Expand Down Expand Up @@ -342,6 +363,14 @@ impl Data {
Err(_) => Self::text(inner),
}
}
#[cfg(feature = "json")]
(DataInner::Text(inner), DataFormat::JsonLines) => {
if let Ok(jsonlines) = parse_jsonlines(&inner) {
Self::jsonlines(jsonlines)
} else {
Self::text(inner)
}
}
#[cfg(feature = "term-svg")]
(DataInner::Text(inner), DataFormat::TermSvg) => {
DataInner::TermSvg(anstyle_svg::Term::new().render_svg(&inner)).into()
Expand All @@ -366,6 +395,10 @@ impl Data {
(inner, DataFormat::Json) => inner.into(),
// reachable if more than one structured data format is enabled
#[allow(unreachable_patterns)]
#[cfg(feature = "json")]
(inner, DataFormat::JsonLines) => inner.into(),
// reachable if more than one structured data format is enabled
#[allow(unreachable_patterns)]
#[cfg(feature = "term-svg")]
(inner, DataFormat::TermSvg) => inner.into(),
};
Expand All @@ -381,6 +414,8 @@ impl Data {
DataInner::Text(_) => DataFormat::Text,
#[cfg(feature = "json")]
DataInner::Json(_) => DataFormat::Json,
#[cfg(feature = "json")]
DataInner::JsonLines(_) => DataFormat::JsonLines,
#[cfg(feature = "term-svg")]
DataInner::TermSvg(_) => DataFormat::TermSvg,
}
Expand All @@ -393,6 +428,8 @@ impl Data {
DataInner::Text(_) => DataFormat::Text,
#[cfg(feature = "json")]
DataInner::Json(_) => DataFormat::Json,
#[cfg(feature = "json")]
DataInner::JsonLines(_) => DataFormat::JsonLines,
#[cfg(feature = "term-svg")]
DataInner::TermSvg(_) => DataFormat::TermSvg,
}
Expand All @@ -405,6 +442,8 @@ impl Data {
DataInner::Text(_) => None,
#[cfg(feature = "json")]
DataInner::Json(_) => None,
#[cfg(feature = "json")]
DataInner::JsonLines(_) => None,
#[cfg(feature = "term-svg")]
DataInner::TermSvg(data) => text_elem(data),
}
Expand Down Expand Up @@ -437,6 +476,14 @@ 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 = "json")]
DataInner::JsonLines(data) => {
let array = data.as_array().expect("jsonlines is always an array");
for value in array {
writeln!(f, "{}", serde_json::to_string(value).unwrap())?;
}
Ok(())
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(data) => data.fmt(f),
}
Expand All @@ -451,6 +498,8 @@ impl PartialEq for Data {
(DataInner::Text(left), DataInner::Text(right)) => left == right,
#[cfg(feature = "json")]
(DataInner::Json(left), DataInner::Json(right)) => left == right,
#[cfg(feature = "json")]
(DataInner::JsonLines(left), DataInner::JsonLines(right)) => left == right,
#[cfg(feature = "term-svg")]
(DataInner::TermSvg(left), DataInner::TermSvg(right)) => {
// HACK: avoid including `width` and `height` in the comparison
Expand All @@ -475,6 +524,20 @@ impl std::fmt::Display for DataError {
}
}

#[cfg(feature = "json")]
fn parse_jsonlines(text: &str) -> Result<Vec<serde_json::Value>, serde_json::Error> {
let mut lines = Vec::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let json = serde_json::from_str::<serde_json::Value>(line)?;
lines.push(json);
}
Ok(lines)
}

#[cfg(feature = "term-svg")]
fn text_elem(svg: &str) -> Option<&str> {
let open_elem_start_idx = svg.find("<text")?;
Expand Down
20 changes: 20 additions & 0 deletions crates/snapbox/src/data/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ impl Normalize for NormalizeNewlines {
normalize_value(&mut value, crate::utils::normalize_lines);
Data::json(value)
}
#[cfg(feature = "json")]
DataInner::JsonLines(value) => {
let mut value = value;
normalize_value(&mut value, crate::utils::normalize_lines);
DataInner::JsonLines(value).into()
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
let lines = crate::utils::normalize_lines(&text);
Expand Down Expand Up @@ -48,6 +54,12 @@ impl Normalize for NormalizePaths {
normalize_value(&mut value, crate::utils::normalize_paths);
Data::json(value)
}
#[cfg(feature = "json")]
DataInner::JsonLines(value) => {
let mut value = value;
normalize_value(&mut value, crate::utils::normalize_paths);
DataInner::JsonLines(value).into()
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
let lines = crate::utils::normalize_paths(&text);
Expand Down Expand Up @@ -94,6 +106,14 @@ impl Normalize for NormalizeMatches<'_> {
}
Data::json(value)
}
#[cfg(feature = "json")]
DataInner::JsonLines(value) => {
let mut value = value;
if let DataInner::Json(exp) = &self.pattern.inner {
normalize_value_matches(&mut value, exp, self.substitutions);
}
DataInner::JsonLines(value).into()
}
#[cfg(feature = "term-svg")]
DataInner::TermSvg(text) => {
if let Some(pattern) = self.pattern.render() {
Expand Down

0 comments on commit ae6bce4

Please sign in to comment.