Skip to content

Commit

Permalink
Add option for JSON escaping for placeholder values
Browse files Browse the repository at this point in the history
  • Loading branch information
akx committed Mar 21, 2023
1 parent 2012f30 commit 43b8129
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tokio = { version = "1", optional = true, features = ["io-util"] }
unicode-segmentation = { version = "1", optional = true }
unicode-width = { version = "0.1", optional = true }
vt100 = { version = "0.15.1", optional = true }
json = { version = "0.12.4" }

[dev-dependencies]
clap = { version = "3", features = ["color", "derive"] }
Expand Down
96 changes: 87 additions & 9 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use instant::Instant;
#[cfg(feature = "unicode-segmentation")]
use unicode_segmentation::UnicodeSegmentation;

use json;

use crate::format::{
BinaryBytes, DecimalBytes, FormattedDuration, HumanBytes, HumanCount, HumanDuration,
HumanFloatCount,
Expand All @@ -25,6 +27,7 @@ pub struct ProgressStyle {
char_width: usize,
tab_width: usize,
pub(crate) format_map: HashMap<&'static str, Box<dyn ProgressTracker>>,
json_strings: bool,
}

#[cfg(feature = "unicode-segmentation")]
Expand Down Expand Up @@ -82,6 +85,12 @@ impl ProgressStyle {
Ok(Self::new(Template::from_str(template)?))
}

/// Set a template that will render a JSON object with the given keys,
/// and enable JSON escaping.
pub fn with_json_keys(keys: &[&str]) -> Result<Self, TemplateError> {
Ok(Self::new(Template::from_json_keys(keys)?).json_strings(true))
}

pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
self.tab_width = new_tab_width;
self.template.set_tab_width(new_tab_width);
Expand All @@ -100,9 +109,16 @@ impl ProgressStyle {
template,
format_map: HashMap::default(),
tab_width: DEFAULT_TAB_WIDTH,
json_strings: false,
}
}

/// Sets whether strings should be output as JSON strings.
pub fn json_strings(mut self, flag: bool) -> Self {
self.json_strings = flag;
self
}

/// Sets the tick character sequence for spinners
///
/// Note that the last character is used as the [final tick string][Self::get_final_tick_str()].
Expand Down Expand Up @@ -340,17 +356,41 @@ impl ProgressStyle {
align: *align,
truncate: *truncate,
};
match style {
Some(s) => cur
.write_fmt(format_args!("{}", s.apply_to(padded)))
.unwrap(),
None => cur.write_fmt(format_args!("{padded}")).unwrap(),
if self.json_strings {
match style {
Some(s) => cur.push_str(&json::stringify(format!(
"{}",
s.apply_to(padded)
))),
None => cur.push_str(&json::stringify(format!("{padded}"))),
}
} else {
match style {
Some(s) => cur
.write_fmt(format_args!("{}", s.apply_to(padded)))
.unwrap(),
None => cur.write_fmt(format_args!("{padded}")).unwrap(),
}
}
}
None => {
if self.json_strings {
match style {
Some(s) => cur.push_str(&json::stringify(format!(
"{}",
s.apply_to(&buf)
))),
None => cur.push_str(&json::stringify(buf.clone())),
}
} else {
match style {
Some(s) => {
cur.write_fmt(format_args!("{}", s.apply_to(&buf))).unwrap()
}
None => cur.push_str(&buf),
}
}
}
None => match style {
Some(s) => cur.write_fmt(format_args!("{}", s.apply_to(&buf))).unwrap(),
None => cur.push_str(&buf),
},
}
}
TemplatePart::Literal(s) => cur.push_str(s.expanded()),
Expand Down Expand Up @@ -592,6 +632,24 @@ impl Template {
Self::from_str_with_tab_width(s, DEFAULT_TAB_WIDTH)
}

fn from_json_keys(keys: &[&str]) -> Result<Self, TemplateError> {
let json_template = keys
.iter()
.enumerate()
.map(|(i, k)| {
// Prefix first segment with JSON object opening brace,
// otherwise a key-value separator comma.
let prefix = if i == 0 { "{{" } else { ", " };
// Suffix last segment with JSON object closing brace,
// otherwise nothing (separator is in the prefix of the previous segment).
let suffix = if i == keys.len() - 1 { "}}" } else { "" };
format!("{prefix}\"{k}\": {{{k}}}{suffix}")
})
.collect::<Vec<String>>()
.join("");
Self::from_str_with_tab_width(&json_template, DEFAULT_TAB_WIDTH)
}

fn set_tab_width(&mut self, new_tab_width: usize) {
for part in &mut self.parts {
if let TemplatePart::Literal(s) = part {
Expand Down Expand Up @@ -984,4 +1042,24 @@ mod tests {
assert_eq!(&buf[2], "bar");
assert_eq!(&buf[3], "baz");
}

#[test]
fn json_strings() {
const WIDTH: u16 = 80;
let pos = Arc::new(AtomicPosition::new());
let mut state = ProgressState::new(Some(10), pos);
let mut buf = Vec::new();
let style = ProgressStyle::with_json_keys(&["pos", "len", "prefix", "msg"]).unwrap();
let prefix = "a\ne\ni";
state.prefix = TabExpandedString::new(prefix.into(), 2);
let msg = "'ö\"";
state.message = TabExpandedString::new(msg.into(), 2);
style.format_state(&state, &mut buf, WIDTH);
assert_eq!(buf.len(), 1);
let obj = json::parse(&buf[0]).unwrap();
assert_eq!(obj["pos"], "0");
assert_eq!(obj["len"], "10");
assert_eq!(obj["prefix"], prefix);
assert_eq!(obj["msg"], msg);
}
}

0 comments on commit 43b8129

Please sign in to comment.