grepme 0.1.2

Grep with support for advanced regex features and automatic directory recursion.
Documentation
//! Command-line argument parsing (`Config`) and the CLI help text.
use std::{env, path::PathBuf};

/// Parsed command-line configuration for a `grepme` invocation.
pub struct Config {
    pub query: String,
    pub targets: Vec<PathBuf>,
    pub ignore_case: bool,
    pub output_file: Option<String>,
}

impl Config {
    /// Parses raw command-line arguments (as returned by [`std::env::args`])
    /// into a [`Config`].
    ///
    /// The first element of `args` is expected to be the executable path and
    /// is skipped. Any arguments starting with `-` are treated as flags and
    /// must appear before the query. The first non-flag argument is treated
    /// as the query, and everything after it as search targets (files or
    /// directories).
    ///
    /// # Examples
    /// ```
    /// use grepme::config::Config;
    ///
    /// let args: Vec<String> = vec![
    ///     "grepme".to_string(),
    ///     "-i".to_string(),
    ///     "duck".to_string(),
    ///     "src/main.rs".to_string(),
    /// ];
    ///
    /// let config = Config::build(&args).unwrap();
    ///
    /// assert_eq!(config.query, "duck");
    /// assert!(config.ignore_case);
    /// assert_eq!(config.targets, vec![std::path::PathBuf::from("src/main.rs")]);
    /// ```
    ///
    /// # Errors
    ///
    /// Returns an `Err` if too few arguments are supplied, an unrecognised
    /// flag is passed, or `--help`/`-h` is passed alongside other arguments.
    ///
    /// ```
    /// use grepme::config::Config;
    ///
    /// let args: Vec<String> = vec!["grepme".to_string()];
    /// assert!(Config::build(&args).is_err());
    /// ```
    pub fn build(args: &[String]) -> Result<Self, String> {
        let mut current_index: usize = 1; // 0 is the executable path called, so we start from 1
        if args.len() < current_index + 2 {
            return Err("not enough arguments".to_string());
        }
        let mut ignore_case: bool = match env::var("IGNORE_CASE") {
            Ok(val) => val.parse().unwrap_or(false),
            Err(_) => false,
        }; // can be overridden by flags
        let mut output_file: Option<String> = None;

        // get the command line args (if any)
        while args[current_index].starts_with('-') {
            let flag = args[current_index].clone();

            if flag.eq("-i") || flag.eq("--ignore-case") {
                ignore_case = true;
            } else if flag.eq("-o") || flag.eq("--output-file") {
                current_index += 1; // the next arg is the output file path
                output_file = Some(args[current_index].clone());
            } else if flag.eq("-v") || flag.eq("--version") {
                return Err(format!(
                    "the {flag} flag can only be called by itself."
                ));
            } else if flag.eq("-h") || flag.eq("--help") {
                return Err(format!(
                    "the {flag} flag can only be called by itself."
                ));
            } else {
                return Err(
                    "unrecogonised flag passed\nRun `grepme --help` to see available flags."
                        .to_string(),
                );
            }
            current_index += 1;
        }
        if args.len() < current_index + 2 {
            return Err(
                "query and file path are required arguments".to_string()
            );
        }
        let query: String = args[current_index].clone();
        current_index += 1;
        let targets: Vec<PathBuf> =
            args[current_index..].iter().map(PathBuf::from).collect();

        Ok(Self {
            query,
            targets,
            ignore_case,
            output_file,
        })
    }
}

/// The text printed for `grepme --help` / `grepme -h` (or when no arguments
/// are supplied).
#[cfg(unix)]
pub const HELPTEXT: &str = concat!(
    "\n",
    env!("CARGO_PKG_NAME"),
    " v",
    env!("CARGO_PKG_VERSION"),
    " by ",
    env!("CARGO_PKG_AUTHORS"),
    " - ",
    env!("CARGO_PKG_LICENSE"),
    "\n\n",
    env!("CARGO_PKG_DESCRIPTION"),
    "\n\n",
    "Usage:\n  grepme [arguments] query [paths/glob]\n\n",
    "Arguments:\n",
    "  --ignore-case | -i           Performs case-insensitive search\n",
    "  --output-file | -o <file>    Writes stdout to file\n",
    "      --version | -v           Shows the version number\n",
    "         --help | -h           Shows this help text\n\n",
    "Query:\n  can be any plain unquoted string or a quoted regex pattern.\n\n",
    "Paths:\n  space separated list of files, directories, and/or glob patters.\n",
);

#[cfg(not(unix))]
pub const HELPTEXT: &str = concat!(
    "\n",
    env!("CARGO_PKG_NAME"),
    " v",
    env!("CARGO_PKG_VERSION"),
    " by ",
    env!("CARGO_PKG_AUTHORS"),
    " - ",
    env!("CARGO_PKG_LICENSE"),
    "\n\n",
    env!("CARGO_PKG_DESCRIPTION"),
    "\n\n",
    "Usage:\n  grepme [arguments] query [paths]\n\n",
    "Arguments:\n",
    "  --ignore-case | -i           Performs case-insensitive search\n",
    "  --output-file | -o <file>    Writes stdout to file\n",
    "      --version | -v           Shows the version number\n",
    "         --help | -h           Shows this help text\n\n",
    "Query:\n  can be any plain unquoted string or a quoted regex pattern.\n\n",
    "Paths:\n  space separated list of files and directories.\n",
);