use std::path::Path;
pub(crate) fn stem(path: impl AsRef<Path>, fallback: &str) -> String {
path.as_ref()
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| fallback.to_string())
}
const COMPACT_HEAD: usize = 4;
const COMPACT_TAIL: usize = 4;
const COMPACT_ELLIPSIS: &str = "...";
#[cfg_attr(not(feature = "gui"), allow(dead_code))]
pub(crate) fn compact_long_strings(text: &str) -> String {
compact_long_strings_mapped(text).0
}
pub(crate) fn compact_long_strings_mapped(text: &str) -> (String, Vec<Vec<usize>>) {
let threshold = COMPACT_HEAD + COMPACT_TAIL + COMPACT_ELLIPSIS.chars().count();
let ellipsis_len = COMPACT_ELLIPSIS.chars().count();
let chars: Vec<char> = text.chars().collect();
let mut i = 0usize;
let mut out = String::with_capacity(text.len());
let mut maps: Vec<Vec<usize>> = Vec::new();
let mut cur: Vec<usize> = Vec::new();
let mut full_col: usize = 0;
while i < chars.len() {
let c = chars[i];
i += 1;
if c == '\n' {
cur.push(full_col);
maps.push(std::mem::take(&mut cur));
out.push('\n');
full_col = 0;
continue;
}
if c != '"' {
cur.push(full_col);
out.push(c);
full_col += 1;
continue;
}
cur.push(full_col);
out.push('"');
full_col += 1;
let content_start = full_col;
let mut content: Vec<char> = Vec::new();
let mut closed = false;
while i < chars.len() {
let nc = chars[i];
i += 1;
if nc == '\\' {
content.push('\\');
if i < chars.len() {
content.push(chars[i]);
i += 1;
}
continue;
}
if nc == '"' {
closed = true;
break;
}
content.push(nc);
}
if content.len() > threshold && !(closed && is_object_key(&chars, i)) {
for k in 0..COMPACT_HEAD {
cur.push(content_start + k);
}
out.extend(&content[..COMPACT_HEAD]);
for _ in 0..ellipsis_len {
cur.push(content_start + COMPACT_HEAD);
}
out.push_str(COMPACT_ELLIPSIS);
let tail_start = content.len() - COMPACT_TAIL;
for k in 0..COMPACT_TAIL {
cur.push(content_start + tail_start + k);
}
out.extend(&content[tail_start..]);
} else {
for k in 0..content.len() {
cur.push(content_start + k);
}
out.extend(&content);
}
full_col = content_start + content.len();
if closed {
cur.push(full_col);
out.push('"');
full_col += 1;
}
}
cur.push(full_col);
maps.push(cur);
(out, maps)
}
fn is_object_key(chars: &[char], after: usize) -> bool {
chars[after..].iter().find(|c| **c != ' ' && **c != '\t') == Some(&':')
}
fn desktop_open_command(path: &Path) -> (&'static str, Vec<String>) {
let arg = path.to_string_lossy().into_owned();
if cfg!(target_os = "macos") {
("open", vec![arg])
} else if cfg!(target_os = "windows") {
("cmd", vec!["/C".into(), "start".into(), String::new(), arg])
} else {
("xdg-open", vec![arg])
}
}
pub(crate) fn open_in_desktop(path: impl AsRef<Path>) -> Result<(), String> {
let path = path.as_ref();
let (program, args) = desktop_open_command(path);
std::process::Command::new(program)
.args(args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map(|_| ())
.map_err(|e| format!("{program}: {e}"))
}
#[cfg(test)]
mod tests {
use super::{compact_long_strings, desktop_open_command};
#[test]
fn the_desktop_opener_passes_the_path_as_one_argument() {
let (program, args) =
desktop_open_command(std::path::Path::new("/tmp/a report/out file.html"));
assert!(!program.is_empty(), "every platform has an opener to name");
assert!(
args.iter().any(|a| a == "/tmp/a report/out file.html"),
"the path is one whole argument, not split or quoted: {args:?}"
);
}
#[test]
fn long_values_are_shortened_but_keys_and_short_values_are_not() {
let src = "{\n \"Key1\": \"anehusenhugroegureolegkregulregurcgeolrgulrecgulrogeulrcgeolrucg\",\n \"Key2\": \"short\"\n}";
let out = compact_long_strings(src);
assert!(out.contains("\"aneh...rucg\""));
assert!(out.contains("\"Key2\": \"short\""));
assert!(out.contains("\"Key1\""));
}
#[test]
fn head_and_tail_are_four_chars_each() {
let src = "\"0123456789abcdef\"";
assert_eq!(compact_long_strings(src), "\"0123...cdef\"");
}
#[test]
fn a_value_at_the_threshold_is_left_intact() {
let src = "\"12345678901\"";
assert_eq!(compact_long_strings(src), src);
}
#[test]
fn escaped_quotes_do_not_end_the_literal_early() {
let src = "\"aaaa\\\"bbbbbbbbbbbbbbbb\"";
let out = compact_long_strings(src);
assert!(out.starts_with('"') && out.ends_with('"'));
assert!(out.contains("..."));
assert_eq!(out.matches('"').count(), 2);
}
#[test]
fn non_string_structure_is_preserved() {
let src = "[1, 2, 3, true, null]";
assert_eq!(compact_long_strings(src), src);
}
#[test]
fn multibyte_content_does_not_panic_and_counts_by_char() {
let src = "\"ééééééééééééééééé\"";
let out = compact_long_strings(src);
assert!(out.contains("..."));
}
#[test]
fn the_map_expands_a_selected_compacted_literal_to_its_full_text() {
let full = "{\n \"k\": \"0123456789abcdef\"\n}";
let (compact, maps) = super::compact_long_strings_mapped(full);
assert_eq!(compact, compact_long_strings(full));
let comp_lines: Vec<&str> = compact.split('\n').collect();
assert_eq!(maps.len(), comp_lines.len());
let line = 1;
let comp_line = comp_lines[line];
let full_line: Vec<char> = full.split('\n').nth(line).unwrap().chars().collect();
let open = comp_line.find("\"0123").unwrap(); let close = comp_line.chars().count(); let (full_open, full_close) = (maps[line][open], maps[line][close]);
let extracted: String = full_line[full_open..full_close].iter().collect();
assert_eq!(extracted, "\"0123456789abcdef\"");
}
#[test]
fn per_line_maps_are_monotonic_and_sentinel_terminated() {
let full = "{\n \"k\": \"0123456789abcdef\",\n \"n\": 12\n}";
let (compact, maps) = super::compact_long_strings_mapped(full);
let comp_lines: Vec<&str> = compact.split('\n').collect();
let full_lines: Vec<&str> = full.split('\n').collect();
assert_eq!(maps.len(), comp_lines.len());
for (i, line_map) in maps.iter().enumerate() {
assert_eq!(line_map.len(), comp_lines[i].chars().count() + 1);
assert!(line_map.windows(2).all(|w| w[0] <= w[1]));
assert_eq!(*line_map.last().unwrap(), full_lines[i].chars().count());
}
}
}
#[cfg(test)]
mod key_tests {
use super::{compact_long_strings, compact_long_strings_mapped};
#[test]
fn a_long_key_is_never_shortened_however_long_its_value_is() {
let src =
"{\n \"authenticationChallengeIdentifier\": \"anehusenhugroegureolegkregulregu\"\n}";
let out = compact_long_strings(src);
assert!(
out.contains("\"authenticationChallengeIdentifier\""),
"the key survives intact: {out}"
);
assert!(
out.contains("\"aneh...regu\""),
"but its value doesn't: {out}"
);
}
#[test]
fn spacing_before_the_colon_does_not_hide_a_key() {
let long = "abcdefghijklmnopqrstuvwxyz";
for gap in ["", " ", " ", "\t"] {
let src = format!("{{\"{long}\"{gap}: 1}}");
assert!(
compact_long_strings(&src).contains(long),
"a key followed by {gap:?} then `:` is still a key"
);
}
let src = format!("[\n \"{long}\"\n : 1\n]");
assert!(compact_long_strings(&src).contains("\"abcd...wxyz\""));
}
#[test]
fn values_still_compact_including_ones_containing_a_colon() {
let src = "[\"https://example.com/a/very/long/path\"]";
assert_eq!(compact_long_strings(src), "[\"http...path\"]");
}
#[test]
fn an_uncompacted_key_maps_one_to_one_onto_the_full_text() {
let src = "{\"authenticationChallengeIdentifier\": \"anehusenhugroegureolegkregulregu\"}";
let (out, maps) = compact_long_strings_mapped(src);
assert_eq!(maps.len(), 1, "one line in, one line out");
let line = &maps[0];
assert_eq!(
line.len(),
out.chars().count() + 1,
"one entry per compacted column, plus the past-the-end sentinel"
);
let key_end = out.find(':').unwrap();
assert!(line[..=key_end].iter().enumerate().all(|(i, m)| *m == i));
assert!(
line.windows(2).all(|w| w[0] <= w[1]),
"and the map is still monotonic across the compacted value"
);
}
}