use std::io::{self, Read};
use zeroize::Zeroizing;
use crate::error::{CliError, Result};
pub fn read_input(arg: Option<&str>) -> Result<String> {
let raw: String = match arg {
Some(s) if s != "-" => s.to_string(),
_ => (*read_stdin()?).clone(),
};
Ok(strip_whitespace(&raw))
}
pub fn read_phrase_input(arg: Option<&str>) -> Result<Zeroizing<String>> {
let raw: Zeroizing<String> = match arg {
Some(s) if s != "-" => Zeroizing::new(s.to_string()),
_ => read_stdin()?,
};
Ok(Zeroizing::new(normalize_phrase(&raw)))
}
fn normalize_phrase(s: &str) -> String {
s.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn read_stdin() -> Result<Zeroizing<String>> {
let mut buf: Zeroizing<String> = Zeroizing::new(String::new());
io::stdin()
.read_to_string(&mut buf)
.map_err(|e| CliError::BadInput(format!("failed to read stdin: {}", e)))?;
let _entropy_pin = crate::mlock::pin_pages_for(buf.as_bytes());
Ok(buf)
}
pub fn strip_whitespace(s: &str) -> String {
let had_whitespace = s.chars().any(|c| c.is_whitespace());
let stripped: String = s.chars().filter(|c| !c.is_whitespace()).collect();
if had_whitespace {
let len = stripped.len();
if len > 0 && len % 2 == 0 {
let half = len / 2;
if stripped.is_char_boundary(half) && stripped[..half] == stripped[half..] {
return stripped[..half].to_string();
}
}
}
stripped
}
pub fn is_stdin_arg(arg: Option<&str>) -> bool {
matches!(arg, None | Some("-"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strip_whitespace_handles_all_three_workflows() {
let pipe = "ms10entrsqqqq\n\nms10e ntrsq qqqq qqqq";
assert_eq!(strip_whitespace(pipe), "ms10entrsqqqqms10entrsqqqqqqqqq");
let typed = "ms10e ntrsq qqqqq\nqqqqq cj9sx";
assert_eq!(strip_whitespace(typed), "ms10entrsqqqqqqqqqqqcj9sx");
let pasted = "\t ms10entrsqqqq \n";
assert_eq!(strip_whitespace(pasted), "ms10entrsqqqq");
}
#[test]
fn strip_whitespace_dedupes_doubled_content() {
let canonical = "ms10entrsqqqqqqqqqqqqqqqqqqqqqqqqqqqqcj9sxraq34v7f";
let chunked = "ms10e ntrsq qqqqq qqqqq qqqqq qqqqq qqqqq qqcj9 sxraq 34v7f";
let encode_stdout = format!("{}\n\n{}", canonical, chunked);
assert_eq!(strip_whitespace(&encode_stdout), canonical);
assert_eq!(strip_whitespace(canonical), canonical);
let back_typed = "ms10e ntrsq qqqqq qqqqq qqqqq qqqqq\nqqqqq qqcj9 sxraq 34v7f";
assert_eq!(strip_whitespace(back_typed), canonical);
}
#[test]
fn is_stdin_arg_recognizes_none_and_dash() {
assert!(is_stdin_arg(None));
assert!(is_stdin_arg(Some("-")));
assert!(!is_stdin_arg(Some("ms10...")));
}
#[test]
fn read_input_with_explicit_arg_returns_stripped() {
let out = read_input(Some(" ms10 ")).unwrap();
assert_eq!(out, "ms10");
}
#[test]
fn normalize_phrase_preserves_word_spaces() {
let phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
assert_eq!(normalize_phrase(phrase), phrase);
}
#[test]
fn normalize_phrase_collapses_runs_and_trims() {
let phrase = " abandon abandon about ";
assert_eq!(normalize_phrase(phrase), "abandon abandon about");
}
#[test]
fn read_phrase_input_with_explicit_arg_preserves_spaces() {
let out = read_phrase_input(Some("abandon abandon about")).unwrap();
assert_eq!(out.as_str(), "abandon abandon about");
}
}