use crate::trace;
use std::borrow::Cow;
use std::env;
use std::io;
use std::path::{Path, PathBuf};
pub use pagetop_minimal::{concatdoc, formatdoc, indoc, join, join_pair, kv};
pub use pagetop_minimal::paste;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NormalizeAsciiError {
IsEmpty,
EmptyAfterTrimming,
NonAscii,
}
pub fn normalize_ascii<'a>(input: &'a str) -> Result<Cow<'a, str>, NormalizeAsciiError> {
let bytes = input.as_bytes();
if bytes.is_empty() {
return Err(NormalizeAsciiError::IsEmpty);
}
let mut start = 0usize;
let mut end = 0usize;
let mut needs_alloc = false;
let mut needs_alloc_ws = false;
let mut has_content = false;
let mut prev_sep = false;
for (pos, &b) in bytes.iter().enumerate() {
if !b.is_ascii() {
return Err(NormalizeAsciiError::NonAscii);
}
if b.is_ascii_whitespace() {
if has_content {
if b != b' ' || prev_sep {
needs_alloc_ws = true;
}
prev_sep = true;
}
} else {
if needs_alloc_ws {
needs_alloc = true;
needs_alloc_ws = false;
}
if b.is_ascii_uppercase() {
needs_alloc = true;
}
prev_sep = false;
if !has_content {
start = pos;
has_content = true;
}
end = pos + 1;
}
}
if !has_content {
return Err(NormalizeAsciiError::EmptyAfterTrimming);
}
let slice = &input[start..end];
if !needs_alloc {
return Ok(Cow::Borrowed(slice));
}
let mut output = String::with_capacity(slice.len());
let mut prev_sep = true;
for &b in slice.as_bytes() {
if b.is_ascii_whitespace() {
if !prev_sep {
output.push(' ');
prev_sep = true;
}
} else {
output.push(b.to_ascii_lowercase() as char);
prev_sep = false;
}
}
Ok(Cow::Owned(output))
}
#[inline]
pub fn normalize_ascii_or_empty<'a>(input: &'a str, target: &'static str) -> Option<Cow<'a, str>> {
match normalize_ascii(input) {
Ok(s) => Some(s),
Err(NormalizeAsciiError::NonAscii) => {
trace::debug!(
target = %target,
input = %input.escape_default(),
"Ignoring due to non-ASCII chars"
);
None
}
Err(NormalizeAsciiError::IsEmpty | NormalizeAsciiError::EmptyAfterTrimming) => {
Some(Cow::Borrowed(""))
}
}
}
pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
let path = path.as_ref();
let candidate = if path.is_absolute() {
path.to_path_buf()
} else {
env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.or_else(|| env::current_dir().ok())
.unwrap_or_else(|| PathBuf::from("."))
.join(path)
};
let absolute_dir = candidate.canonicalize()?;
if absolute_dir.is_dir() {
Ok(absolute_dir)
} else {
Err({
let msg = format!("path \"{}\" is not a directory", absolute_dir.display());
trace::warn!(msg);
io::Error::new(io::ErrorKind::InvalidInput, msg)
})
}
}