Skip to content

Commit

Permalink
Merge pull request #84 from jhilmer/allow-multiple-flags
Browse files Browse the repository at this point in the history
add support for repeated flags
  • Loading branch information
ksk001100 authored Dec 28, 2024
2 parents b3282e4 + d7396bf commit 962bafa
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 12 deletions.
55 changes: 55 additions & 0 deletions examples/multiple_flags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use seahorse::{App, Context, Flag, FlagType};
use std::env;

fn main() {
let args: Vec<String> = env::args().collect();
let name = "multiple_flags";

let app = App::new(name)
.author(env!("CARGO_PKG_AUTHORS"))
.description(env!("CARGO_PKG_DESCRIPTION"))
.usage("multiple_flags [args]")
.version(env!("CARGO_PKG_VERSION"))
.action(action)
.flag(
Flag::new("verbose", FlagType::Bool)
.description("Increase verbosity level by repeat --verbose(-v) multiple times")
.alias("v")
.multiple(),
)
.flag(
Flag::new("header", FlagType::String)
.description("Set header of the request, argument can be repeated")
.alias("H")
.multiple(),
)
.flag(
Flag::new("offset", FlagType::Uint)
.description("Counter offset, argument can be repeated")
.alias("o")
.multiple(),
);

app.run(args);
}

fn action(c: &Context) {
// Count the number of times the flag was passed
let verbosity_level = c.bool_flag_vec("verbose").iter().flatten().count();

println!("Verbosity level: {}", verbosity_level);

// Print only the first 'header' flag passed
println!("Headers: {:?}", c.string_flag("header"));

// To access all 'header' flags passed, if the flag is not marked as multiple the
// vector will only contain one element, the rest will be ignored.
for header in c.string_flag_vec("header") {
println!("Header: {:?}", header);
}

// Access all 'offset' flags passed
for offset in c.uint_flag_vec("offset") {
println!("offset: {:?}", offset);
}
}
192 changes: 180 additions & 12 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,31 @@ impl Context {
let flags_val = match flags {
Some(flags) => {
for flag in flags {
if let Some(index) = flag.option_index(&parsed_args) {
parsed_args.remove(index);
let mut found_flag = false;
loop {
if let Some(index) = flag.option_index(&parsed_args) {
found_flag = true;
parsed_args.remove(index);

let val = if flag.flag_type != FlagType::Bool {
if parsed_args.len() <= index {
None
let val = if flag.flag_type != FlagType::Bool {
if parsed_args.len() <= index {
None
} else {
Some(parsed_args.remove(index))
}
} else {
Some(parsed_args.remove(index))
None
};
v.push((flag.name.to_string(), flag.value(val)));
if !flag.multiple {
break;
}
} else {
None
};
v.push((flag.name.to_string(), flag.value(val)))
} else {
v.push((flag.name.to_string(), Err(FlagError::NotFound)))
if !found_flag || !flag.multiple {
v.push((flag.name.to_string(), Err(FlagError::NotFound)));
}
break;
}
}
}
Some(v)
Expand Down Expand Up @@ -66,6 +76,20 @@ impl Context {
}
}

// Get flag values for repeated flags
fn result_flag_value_vec(&self, name: &str) -> Vec<Result<FlagValue, FlagError>> {
self.flags
.as_ref()
.unwrap()
.iter()
.filter(|flag| flag.0 == name)
.map(|f| match &f.1 {
Ok(val) => Ok(val.to_owned()),
Err(e) => Err(e.to_owned()),
})
.collect::<Vec<_>>()
}

/// Get bool flag
///
/// Example
Expand All @@ -89,6 +113,34 @@ impl Context {
}
}

/// Get bool flags for repeated flags
///
/// Example
///
/// ```
/// use seahorse::Context;
///
/// fn action(c: &Context) {
/// for b in c.bool_flag_vec("bool") {
/// match b {
/// Ok(b) => println!("{}", b),
/// Err(e) => println!("{}", e)
/// }
/// }
/// }
/// ```
pub fn bool_flag_vec(&self, name: &str) -> Vec<Result<bool, FlagError>> {
let r = self.result_flag_value_vec(name);

r.iter()
.map(|r| match r {
Ok(FlagValue::Bool(val)) => Ok(val.clone()),
Err(FlagError::NotFound) => Err(FlagError::NotFound),
_ => Err(FlagError::TypeError),
})
.collect::<Vec<_>>()
}

/// Get string flag
///
/// Example
Expand All @@ -111,6 +163,34 @@ impl Context {
}
}

/// Get string flags for repeated flags
///
/// Example
///
/// ```
/// use seahorse::Context;
///
/// fn action(c: &Context) {
/// for s in c.string_flag_vec("string") {
/// match s {
/// Ok(s) => println!("{}", s),
/// Err(e) => println!("{}", e)
/// }
/// }
/// }
/// ```
pub fn string_flag_vec(&self, name: &str) -> Vec<Result<String, FlagError>> {
let r = self.result_flag_value_vec(name);

r.iter()
.map(|r| match r {
Ok(FlagValue::String(val)) => Ok(val.clone()),
Err(FlagError::NotFound) => Err(FlagError::NotFound),
_ => Err(FlagError::TypeError),
})
.collect::<Vec<_>>()
}

/// Get int flag
///
/// Example
Expand All @@ -133,6 +213,34 @@ impl Context {
}
}

/// Get int flags for repeated flags
///
/// Example
///
/// ```
/// use seahorse::Context;
///
/// fn action(c: &Context) {
/// for i in c.int_flag_vec("int") {
/// match i {
/// Ok(i) => println!("{}", i),
/// Err(e) => println!("{}", e)
/// }
/// }
/// }
/// ```
pub fn int_flag_vec(&self, name: &str) -> Vec<Result<isize, FlagError>> {
let r = self.result_flag_value_vec(name);

r.iter()
.map(|r| match r {
Ok(FlagValue::Int(val)) => Ok(val.clone()),
Err(FlagError::NotFound) => Err(FlagError::NotFound),
_ => Err(FlagError::TypeError),
})
.collect::<Vec<_>>()
}

/// Get Uint flag
///
/// Example
Expand All @@ -155,6 +263,34 @@ impl Context {
}
}

/// Get uint flags for repeated flags
///
/// Example
///
/// ```
/// use seahorse::Context;
///
/// fn action(c: &Context) {
/// for i in c.uint_flag_vec("uint") {
/// match i {
/// Ok(i) => println!("{}", i),
/// Err(e) => println!("{}", e)
/// }
/// }
/// }
/// ```
pub fn uint_flag_vec(&self, name: &str) -> Vec<Result<usize, FlagError>> {
let r = self.result_flag_value_vec(name);

r.iter()
.map(|r| match r {
Ok(FlagValue::Uint(val)) => Ok(val.clone()),
Err(FlagError::NotFound) => Err(FlagError::NotFound),
_ => Err(FlagError::TypeError),
})
.collect::<Vec<_>>()
}

/// Get float flag
///
/// Example
Expand All @@ -177,6 +313,36 @@ impl Context {
}
}

/// Get float flags for repeated flags
///
/// Example
///
/// ```
/// use seahorse::Context;
///
/// fn action(c: &Context) {
/// for f in c.float_flag_vec("float") {
/// match f {
/// Ok(f) => println!("{}", f),
/// Err(e) => println!("{}", e)
/// }
/// }
/// }
/// ```
pub fn float_flag_vec(&self, name: &str) -> Vec<Result<f64, FlagError>> {
let r = self.result_flag_value_vec(name);

// I would like to map the Result<FlagValue, FlagError> to Result<f64, FlagError>

r.iter()
.map(|r| match *r {
Ok(FlagValue::Float(val)) => Ok(val),
Err(FlagError::NotFound) => Err(FlagError::NotFound),
_ => Err(FlagError::TypeError),
})
.collect::<Vec<_>>()
}

/// Display help
///
/// Example
Expand Down Expand Up @@ -213,6 +379,8 @@ mod tests {
"1234567654321".to_string(),
"--float".to_string(),
"1.23".to_string(),
"--float".to_string(),
"1.44".to_string(),
"--invalid_float".to_string(),
"invalid".to_string(),
];
Expand All @@ -221,7 +389,7 @@ mod tests {
Flag::new("string", FlagType::String),
Flag::new("int", FlagType::Int),
Flag::new("uint", FlagType::Uint),
Flag::new("float", FlagType::Float),
Flag::new("float", FlagType::Float).multiple(),
Flag::new("invalid_float", FlagType::Float),
Flag::new("not_specified", FlagType::String),
];
Expand Down
37 changes: 37 additions & 0 deletions src/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub struct Flag {
pub flag_type: FlagType,
/// Flag alias
pub alias: Option<Vec<String>>,
/// Multiple occurrence
pub multiple: bool,
}

/// `FlagType` enum
Expand Down Expand Up @@ -72,6 +74,7 @@ impl Flag {
description: None,
flag_type,
alias: None,
multiple: false,
}
}

Expand All @@ -90,6 +93,21 @@ impl Flag {
self
}

/// Set multiple flag
///
/// Example
///
/// ```
/// use seahorse::{Flag, FlagType};
///
/// let bool_flag = Flag::new("bool", FlagType::Bool)
/// .multiple();
/// ```
pub fn multiple(mut self) -> Self {
self.multiple = true;
self
}

/// Set alias of the flag
///
/// Example
Expand Down Expand Up @@ -284,4 +302,23 @@ mod tests {
_ => assert!(false),
}
}

#[test]
fn multiple_string_flag_test() {
let string_flag = Flag::new("string", FlagType::String);
let v = vec![
"cli".to_string(),
"command".to_string(),
"args".to_string(),
"--string".to_string(),
"test".to_string(),
"--string".to_string(),
"test2".to_string(),
];

match string_flag.value(Some(v[4].to_owned())) {
Ok(FlagValue::String(val)) => assert_eq!("test".to_string(), val),
_ => assert!(false),
}
}
}

0 comments on commit 962bafa

Please sign in to comment.