testutils 0.0.12

Offers a range of utility functions, macros, and tools, such as `simple_benchmark()` and `dbg_ref!()`, `os_cmd::Runner`, designed for testing purposes.
Documentation
use alloc::borrow::Cow;

use tap::Pipe;

use crate::os_cmd::{MiniStr, RunnableCommand, presets::CowStrVec};
pub(crate) type TinyCmds<'a> = CowStrVec<'a, 9>;

impl<'a> RunnableCommand<'a> for CommandRepr<'a> {}

/// Command Representation
///
/// - Raw: raw &str (e.g., "cargo +nightly fmt")
/// - Slice: `Box<[&str]>` (e.g., `vec!["cargo", "+nightly",
///   "fmt"].into_boxed_slice()`)
/// - OwnedSlice: `Box<[MiniStr]>` (e.g., `["cargo", "+nightly",
///   "fmt"].pipe(collect_boxed_ministr_slice)`)
#[derive(Debug, Clone)]
pub enum CommandRepr<'a> {
  Raw(&'a str),
  Slice(Box<[&'a str]>),
  OwnedSlice(Box<[MiniStr]>),
}

impl Default for CommandRepr<'_> {
  /// Default: Raw("cargo")
  fn default() -> Self {
    Self::Raw("cargo")
  }
}

impl From<Box<[MiniStr]>> for CommandRepr<'_> {
  fn from(value: Box<[MiniStr]>) -> Self {
    Self::OwnedSlice(value)
  }
}

impl From<Vec<MiniStr>> for CommandRepr<'_> {
  fn from(value: Vec<MiniStr>) -> Self {
    Self::OwnedSlice(value.into())
  }
}

impl<'a> From<Box<[&'a str]>> for CommandRepr<'a> {
  fn from(value: Box<[&'a str]>) -> Self {
    Self::Slice(value)
  }
}

impl<'a> From<Vec<&'a str>> for CommandRepr<'a> {
  fn from(value: Vec<&'a str>) -> Self {
    Self::Slice(value.into_boxed_slice())
  }
}

impl<'a, const N: usize> From<[&'a str; N]> for CommandRepr<'a> {
  fn from(value: [&'a str; N]) -> Self {
    Self::Slice(value.into())
  }
}

impl<'a> From<&'a str> for CommandRepr<'a> {
  fn from(value: &'a str) -> Self {
    Self::Raw(value)
  }
}

impl<'a> CommandRepr<'a> {
  /// - Raw(&str) => [collect_raw](collect_raw) => command vec
  /// - Slice(Box<[&str]>) => `TinyVec<[Cow<&str>]>`
  /// - OwnedSlice(Box<[compact_str::CompactString]>) =>
  ///   `TinyVec<[Cow<String>]>`
  pub fn into_tinyvec(self, remove_comments: bool) -> TinyCmds<'a> {
    match self {
      Self::Raw(raw) => collect_raw(raw, remove_comments),
      Self::Slice(items) => items
        .into_iter()
        .map(Cow::from)
        .collect(),
      Self::OwnedSlice(items) => items
        .into_iter()
        .map(Cow::from)
        .collect(),
    }
  }
}

/// Parses raw command string into executable components
///
/// Why TinyVec:
/// - Stack-allocated for small commands (<=9 elements)
/// - Fallback to heap for large commands automatically
///
/// > size: in x64 Linux, `TinyVec<[Cow<'_, str>; 9]>`: 224
///
///
/// ## Example
///
/// ```
/// # #[cfg(feature = "re_exports_tap")]
/// # {
/// use testutils::{os_cmd::collect_raw, tap::Pipe};
/// let into_vec = |s| collect_raw(s, true);
/// let vec = r#"
///     // output "world" string
///     printf
///     "%s"  world
///     "#
/// .pipe(into_vec);
///
/// assert_eq!(vec.as_ref(), &["printf", "%s", "world"]);
/// # }
/// ```
pub fn collect_raw(raw: &str, remove_comments: bool) -> TinyCmds<'_> {
  raw
    .trim_ascii() // Trim ASCII whitespace efficiently (rust 1.80+)
    .pipe(|s| match remove_comments {
      true => remove_comments_and_collect(s),
      _ => s.into(), // Convert to Cow without cloning
    })
    .pipe_deref(shlex::Shlex::new) // Safe command line splitting
    .map(Cow::from)
    .collect()
}

/// Preprocesses command string by removing comment lines
///
/// Why Cow:
///
/// - This involves an external condition check. (Required by
///   [collect_raw](collect_raw))
///
///   - When `remove_comments` is true, this function is executed.
///   - When it is false, it directly returns the original string (`&str`).
///   - To ensure both conditions return the same type, this function wraps the
///     returned string in `Cow`.
pub fn remove_comments_and_collect(s: &str) -> Cow<'_, str> {
  if !s.contains("//") {
    return s.into();
  }

  s.lines()
    // Since shell commands can contain `\"\n{space} \"`,
    // don't use `.map(|x| x.trim())` here.
    .filter(|x| {
      !x.trim_ascii_start()
        .starts_with("//")
    })
    .collect::<String>()
    .pipe(Cow::from)
}

#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(all(feature = "print_ext", feature = "re_exports_tap"))]
  #[test]
  fn doc_collect_raw() {
    use crate::{os_cmd::collect_raw, tap::Pipe};
    let into_vec = |s| collect_raw(s, true);
    let vec = r#"
    // This is a comment
    printf
    "%s"  world
    "#
    .pipe(into_vec);

    assert_eq!(vec.as_ref(), &["printf", "%s", "world"]);
  }

  #[cfg(feature = "print_ext")]
  #[ignore]
  #[test]
  fn test_collect_raw() {
    use tap::Tap;

    use crate::print_ext::normal::edbg;

    let into_vec = |s| collect_raw(s, true);

    r#"
    // This is a comment
    printf "%s" "
    Hello
      // wonderful
      world
    "
    "#
    .pipe(into_vec)
    .tap(edbg);
  }

  #[cfg(feature = "print_ext")]
  #[ignore]
  #[test]
  fn test_run_trait() {
    let cmd = r#"printf "%s\n" Hello"#;
    let repr: CommandRepr = cmd.into();
    crate::dbg!(repr);

    let _ = repr.run();
  }
}