use std::{
fs,
io::{IsTerminal, stdin},
};
use anyhow::{Context, bail};
use clap::Parser;
use pimalaya_cli::clap::parsers::path_parser;
#[derive(Debug, Parser)]
pub struct MessageArg {
#[arg(name = "message-raw", value_name = "MESSAGE", raw = true)]
pub raw: Vec<String>,
}
impl MessageArg {
pub fn parse(&self) -> anyhow::Result<String> {
let message = self.resolve()?;
if message.trim().is_empty() {
bail!("Message is empty");
}
Ok(message)
}
fn resolve(&self) -> anyhow::Result<String> {
if !self.raw.is_empty() {
let mime = self.raw.join(" ").replace("\\r", "").replace("\\n", "\r\n");
if let Some(path) = path_parser(&mime).ok().filter(|path| path.is_file()) {
let contents = fs::read_to_string(&path)
.with_context(|| format!("Failed to read message file `{}`", path.display()))?;
return Ok(normalize_crlf(&contents));
}
return Ok(normalize_crlf(&mime));
}
if !stdin().is_terminal() {
let lines: Vec<_> = stdin().lines().map_while(Result::ok).collect();
return Ok(lines.join("\r\n"));
}
bail!("No message provided: pass it as an argument, a file path, or via stdin");
}
}
fn normalize_crlf(input: &str) -> String {
let mut out = String::with_capacity(input.len());
let mut prev = '\0';
for ch in input.chars() {
if ch == '\n' && prev != '\r' {
out.push('\r');
}
out.push(ch);
prev = ch;
}
out
}
#[cfg(test)]
mod tests {
use super::normalize_crlf;
#[test]
fn bare_lf_gains_cr() {
assert_eq!(normalize_crlf("a\nb\n"), "a\r\nb\r\n");
}
#[test]
fn existing_crlf_is_untouched() {
assert_eq!(normalize_crlf("a\r\nb\r\n"), "a\r\nb\r\n");
}
#[test]
fn mixed_endings_converge_to_crlf() {
assert_eq!(normalize_crlf("a\r\nb\nc"), "a\r\nb\r\nc");
}
#[test]
fn lone_cr_is_preserved() {
assert_eq!(normalize_crlf("a\rb"), "a\rb");
}
}