diff --git a/cli/src/args.rs b/cli/src/args.rs index fea4f4344..690f4995e 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,5 +1,5 @@ #[cfg(not(feature = "clap"))] -use eyre::Report; +use eyre::{Report, WrapErr}; #[cfg(feature = "clap")] use clap::{ @@ -7,9 +7,12 @@ use clap::{ FromArgMatches, Parser, Subcommand, ValueEnum, }; -#[cfg(feature = "clap")] -use std::collections::VecDeque; -use std::{ffi::OsString, num::ParseIntError, path::PathBuf}; +use std::{collections::VecDeque, ffi::OsString, num::ParseIntError, path::PathBuf}; +#[cfg(not(feature = "clap"))] +use std::{ + io::{self, Write}, + process, +}; #[derive(Debug)] #[cfg_attr(feature = "clap", derive(Parser))] @@ -22,9 +25,125 @@ pub struct ZipCli { pub command: ZipCommand, } +#[cfg(not(feature = "clap"))] +#[derive(Debug)] +enum SubcommandName { + Compress, + Info, + Extract, +} + #[cfg(not(feature = "clap"))] impl ZipCli { - pub fn parse_argv(_argv: impl IntoIterator) -> Result { + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION"); + /* This should really be CARGO_BIN_NAME according to clap and cargo's own docs, but that env + * var wasn't found for some reason, so we use the crate name since that's the same for zip-cli + * and zip-clite at least. https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates */ + const BIN_NAME: &'static str = env!("CARGO_CRATE_NAME"); + const ARGV_PARSE_FAILED_EXIT_CODE: i32 = 2; + const NON_FAILURE_EXIT_CODE: i32 = 0; + + fn generate_version_text() -> String { + format!("{} {}\n", Self::BIN_NAME, Self::VERSION) + } + + fn generate_full_help_text() -> String { + format!( + r" +{} + +Usage: {} [OPTIONS] + +Commands: + compress + info + extract + +Options: + -v, --verbose + -h, --help + -V, --version + +Build this binary with '--features clap' for more thorough help text. +", + Self::DESCRIPTION, + Self::BIN_NAME + ) + } + + fn generate_brief_help_text(context: &str) -> String { + format!( + r" +error: {context} + +Usage: {} [OPTIONS] + +For more information, try '--help'. +", + Self::BIN_NAME + ) + } + + fn parse_up_to_subcommand_name(argv: &mut VecDeque) -> (bool, SubcommandName) { + let mut verbose: bool = false; + let mut subcommand_name: Option = None; + while subcommand_name.is_none() { + match argv.pop_front() { + None => { + let help_text = Self::generate_full_help_text(); + io::stderr() + .write_all(help_text.as_bytes()) + .wrap_err("Failed to write zero-arg help text to stderr") + .unwrap(); + process::exit(Self::ARGV_PARSE_FAILED_EXIT_CODE); + } + Some(arg) => match arg.as_encoded_bytes() { + b"-v" | b"--verbose" => verbose = true, + b"-V" | b"--version" => { + let version_text = Self::generate_version_text(); + io::stdout() + .write_all(version_text.as_bytes()) + .wrap_err("Failed to write version to stdout") + .unwrap(); + process::exit(Self::NON_FAILURE_EXIT_CODE) + } + b"-h" | b"--help" => { + let help_text = Self::generate_full_help_text(); + io::stdout() + .write_all(help_text.as_bytes()) + .wrap_err("Failed to write -h/--help help text to stdout") + .unwrap(); + process::exit(Self::NON_FAILURE_EXIT_CODE); + } + b"compress" => subcommand_name = Some(SubcommandName::Compress), + b"info" => subcommand_name = Some(SubcommandName::Info), + b"extract" => subcommand_name = Some(SubcommandName::Extract), + arg_bytes => { + let context = if arg_bytes.starts_with(b"-") { + format!("unrecognized flag {arg:?}") + } else { + format!("unrecognized subcommand name {arg:?}") + }; + let help_text = Self::generate_brief_help_text(&context); + io::stderr() + .write_all(help_text.as_bytes()) + .wrap_err("Failed to write unrecognized arg text to stderr") + .unwrap(); + process::exit(Self::ARGV_PARSE_FAILED_EXIT_CODE) + } + }, + } + } + (verbose, subcommand_name.unwrap()) + } + + pub fn parse_argv(argv: impl IntoIterator) -> Result { + let mut argv: VecDeque = argv.into_iter().collect(); + let _exe_name = argv.pop_front().unwrap(); + let (verbose, subcommand_name) = Self::parse_up_to_subcommand_name(&mut argv); + dbg!(verbose); + dbg!(subcommand_name); todo!() } }