use std::ffi::OsStr;
use std::path::Path;
use super::MAX_WORKSPACE_CHARS;
pub(super) fn workspace_basename(workspace: &str) -> String {
let basename = Path::new(workspace)
.file_name()
.and_then(OsStr::to_str)
.unwrap_or("workspace");
sanitize_nonempty(basename, MAX_WORKSPACE_CHARS, "workspace")
}
pub(super) fn sanitize_optional(value: Option<&str>, max_chars: usize) -> Option<String> {
value
.map(|value| sanitize_display_text(value, max_chars))
.filter(|value| !value.is_empty())
}
pub(super) fn sanitize_nonempty(value: &str, max_chars: usize, fallback: &str) -> String {
let value = sanitize_display_text(value, max_chars);
if value.is_empty() {
fallback.to_string()
} else {
value
}
}
pub(crate) fn sanitize_display_text(value: &str, max_chars: usize) -> String {
#[derive(Clone, Copy)]
enum State {
Text,
Escape,
EscapeIntermediate,
Csi,
ControlString,
ControlStringEscape,
}
let mut state = State::Text;
let mut output = String::new();
let mut output_chars = 0usize;
let mut pending_space = false;
for character in value.chars() {
match state {
State::Escape => {
state = match character {
'[' => State::Csi,
']' | 'P' | 'X' | '^' | '_' => State::ControlString,
'\u{20}'..='\u{2f}' => State::EscapeIntermediate,
_ => State::Text,
};
continue;
}
State::EscapeIntermediate => {
if ('\u{30}'..='\u{7e}').contains(&character) {
state = State::Text;
}
continue;
}
State::Csi => {
if ('\u{40}'..='\u{7e}').contains(&character) {
state = State::Text;
}
continue;
}
State::ControlString => {
state = match character {
'\u{7}' | '\u{9c}' => State::Text,
'\u{1b}' => State::ControlStringEscape,
_ => State::ControlString,
};
continue;
}
State::ControlStringEscape => {
state = if character == '\\' {
State::Text
} else if character == '\u{1b}' {
State::ControlStringEscape
} else {
State::ControlString
};
continue;
}
State::Text => {}
}
state = match character {
'\u{1b}' => State::Escape,
'\u{9b}' => State::Csi,
'\u{90}' | '\u{98}' | '\u{9d}' | '\u{9e}' | '\u{9f}' => State::ControlString,
_ => State::Text,
};
if !matches!(state, State::Text) {
continue;
}
let bidi_control = matches!(
character,
'\u{061c}'
| '\u{200e}'
| '\u{200f}'
| '\u{202a}'..='\u{202e}'
| '\u{2066}'..='\u{206f}'
);
if character.is_control() || bidi_control || character.is_whitespace() {
pending_space |= !output.is_empty();
continue;
}
if output_chars >= max_chars {
break;
}
if pending_space && output_chars + 1 < max_chars {
output.push(' ');
output_chars += 1;
}
pending_space = false;
if output_chars < max_chars {
output.push(character);
output_chars += 1;
}
}
output
}