Skip to content

Commit cad03b6

Browse files
parse up to subcommand name
1 parent d588c1b commit cad03b6

File tree

1 file changed

+124
-5
lines changed

1 file changed

+124
-5
lines changed

cli/src/args.rs

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
#[cfg(not(feature = "clap"))]
2-
use eyre::Report;
2+
use eyre::{Report, WrapErr};
33

44
#[cfg(feature = "clap")]
55
use clap::{
66
builder::ValueParser, value_parser, Arg, ArgAction, ArgGroup, ArgMatches, Args, Command,
77
FromArgMatches, Parser, Subcommand, ValueEnum,
88
};
99

10-
#[cfg(feature = "clap")]
11-
use std::collections::VecDeque;
12-
use std::{ffi::OsString, num::ParseIntError, path::PathBuf};
10+
use std::{collections::VecDeque, ffi::OsString, num::ParseIntError, path::PathBuf};
11+
#[cfg(not(feature = "clap"))]
12+
use std::{
13+
io::{self, Write},
14+
process,
15+
};
1316

1417
#[derive(Debug)]
1518
#[cfg_attr(feature = "clap", derive(Parser))]
@@ -22,9 +25,125 @@ pub struct ZipCli {
2225
pub command: ZipCommand,
2326
}
2427

28+
#[cfg(not(feature = "clap"))]
29+
#[derive(Debug)]
30+
enum SubcommandName {
31+
Compress,
32+
Info,
33+
Extract,
34+
}
35+
2536
#[cfg(not(feature = "clap"))]
2637
impl ZipCli {
27-
pub fn parse_argv(_argv: impl IntoIterator<Item = OsString>) -> Result<Self, Report> {
38+
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
39+
const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");
40+
/* This should really be CARGO_BIN_NAME according to clap and cargo's own docs, but that env
41+
* var wasn't found for some reason, so we use the crate name since that's the same for zip-cli
42+
* and zip-clite at least. https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates */
43+
const BIN_NAME: &'static str = env!("CARGO_CRATE_NAME");
44+
const ARGV_PARSE_FAILED_EXIT_CODE: i32 = 2;
45+
const NON_FAILURE_EXIT_CODE: i32 = 0;
46+
47+
fn generate_version_text() -> String {
48+
format!("{} {}\n", Self::BIN_NAME, Self::VERSION)
49+
}
50+
51+
fn generate_full_help_text() -> String {
52+
format!(
53+
r"
54+
{}
55+
56+
Usage: {} [OPTIONS] <COMMAND>
57+
58+
Commands:
59+
compress
60+
info
61+
extract
62+
63+
Options:
64+
-v, --verbose
65+
-h, --help
66+
-V, --version
67+
68+
Build this binary with '--features clap' for more thorough help text.
69+
",
70+
Self::DESCRIPTION,
71+
Self::BIN_NAME
72+
)
73+
}
74+
75+
fn generate_brief_help_text(context: &str) -> String {
76+
format!(
77+
r"
78+
error: {context}
79+
80+
Usage: {} [OPTIONS] <COMMAND>
81+
82+
For more information, try '--help'.
83+
",
84+
Self::BIN_NAME
85+
)
86+
}
87+
88+
fn parse_up_to_subcommand_name(argv: &mut VecDeque<OsString>) -> (bool, SubcommandName) {
89+
let mut verbose: bool = false;
90+
let mut subcommand_name: Option<SubcommandName> = None;
91+
while subcommand_name.is_none() {
92+
match argv.pop_front() {
93+
None => {
94+
let help_text = Self::generate_full_help_text();
95+
io::stderr()
96+
.write_all(help_text.as_bytes())
97+
.wrap_err("Failed to write zero-arg help text to stderr")
98+
.unwrap();
99+
process::exit(Self::ARGV_PARSE_FAILED_EXIT_CODE);
100+
}
101+
Some(arg) => match arg.as_encoded_bytes() {
102+
b"-v" | b"--verbose" => verbose = true,
103+
b"-V" | b"--version" => {
104+
let version_text = Self::generate_version_text();
105+
io::stdout()
106+
.write_all(version_text.as_bytes())
107+
.wrap_err("Failed to write version to stdout")
108+
.unwrap();
109+
process::exit(Self::NON_FAILURE_EXIT_CODE)
110+
}
111+
b"-h" | b"--help" => {
112+
let help_text = Self::generate_full_help_text();
113+
io::stdout()
114+
.write_all(help_text.as_bytes())
115+
.wrap_err("Failed to write -h/--help help text to stdout")
116+
.unwrap();
117+
process::exit(Self::NON_FAILURE_EXIT_CODE);
118+
}
119+
b"compress" => subcommand_name = Some(SubcommandName::Compress),
120+
b"info" => subcommand_name = Some(SubcommandName::Info),
121+
b"extract" => subcommand_name = Some(SubcommandName::Extract),
122+
arg_bytes => {
123+
let context = if arg_bytes.starts_with(b"-") {
124+
format!("unrecognized flag {arg:?}")
125+
} else {
126+
format!("unrecognized subcommand name {arg:?}")
127+
};
128+
let help_text = Self::generate_brief_help_text(&context);
129+
io::stderr()
130+
.write_all(help_text.as_bytes())
131+
.wrap_err("Failed to write unrecognized arg text to stderr")
132+
.unwrap();
133+
process::exit(Self::ARGV_PARSE_FAILED_EXIT_CODE)
134+
}
135+
},
136+
}
137+
}
138+
(verbose, subcommand_name.unwrap())
139+
}
140+
141+
pub fn parse_argv(argv: impl IntoIterator<Item = OsString>) -> Result<Self, Report> {
142+
let mut argv: VecDeque<OsString> = argv.into_iter().collect();
143+
let _exe_name = argv.pop_front().unwrap();
144+
let (verbose, subcommand_name) = Self::parse_up_to_subcommand_name(&mut argv);
145+
dbg!(verbose);
146+
dbg!(subcommand_name);
28147
todo!()
29148
}
30149
}

0 commit comments

Comments
 (0)