Initial commit
This commit is contained in:
commit
bdfe1d1000
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
19
Cargo.lock
generated
Normal file
19
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clime"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"e",
|
||||||
|
"stilts",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "e"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stilts"
|
||||||
|
version = "0.1.0"
|
||||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "clime"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
stilts = { path = "../stilts/" }
|
||||||
|
e = { path = "../e" }
|
||||||
97
flake.lock
Normal file
97
flake.lock
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1715322226,
|
||||||
|
"narHash": "sha256-ezoe/FwfJpA7sskLoLP2iwfwkYnscEFCP6Vk5kPwh9k=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "297c756ba6249d483c1dafe42378560458842173",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1713828541,
|
||||||
|
"narHash": "sha256-KtvQeE12MSkCOhvVmnmcZCjnx7t31zWin2XVSDOwBDE=",
|
||||||
|
"path": "/nix/store/h5g4m28iz22w31c0asl73mcir6z7x5ra-source",
|
||||||
|
"rev": "b500489fd3cf653eafc075f9362423ad5cdd8676",
|
||||||
|
"type": "path"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"fenix": "fenix",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-analyzer-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1715255944,
|
||||||
|
"narHash": "sha256-vLLgYpdtKBaGYTamNLg1rbRo1bPXp4Jgded/gnprPVw=",
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"rev": "5bf2f85c8054d80424899fa581db1b192230efb5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
26
flake.nix
Normal file
26
flake.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
inputs.nixpkgs.url = "nixpkgs";
|
||||||
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
inputs.fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, fenix }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
fenix' = fenix.packages.${system};
|
||||||
|
nightly = fenix'.default;
|
||||||
|
stable = fenix'.stable;
|
||||||
|
in {
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = [ (fenix'.combine [
|
||||||
|
(stable.withComponents [
|
||||||
|
"cargo" "rustc" "rust-src" "rust-analyzer" "clippy"
|
||||||
|
])
|
||||||
|
nightly.rustfmt
|
||||||
|
]) pkgs.cargo-watch ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
edition = "2021"
|
||||||
|
hard_tabs = true
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
max_width = 80
|
||||||
|
empty_item_single_line = false
|
||||||
415
src/lib.rs
Normal file
415
src/lib.rs
Normal file
|
|
@ -0,0 +1,415 @@
|
||||||
|
use e::default;
|
||||||
|
use stilts::Push;
|
||||||
|
|
||||||
|
pub type HelpMessage = &'static str;
|
||||||
|
|
||||||
|
pub trait Value {
|
||||||
|
fn parse(s: &str) -> Result<Self, HelpMessage>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
macro_rules! impl_value_using_from_str {
|
||||||
|
($t:ty) => {
|
||||||
|
impl $crate::Value for $t {
|
||||||
|
fn parse(s: &str) -> Result<Self, HelpMessage> {
|
||||||
|
s.parse().map_err(|_| concat!("Invalid ", stringify!($t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_value_using_from_str!(String);
|
||||||
|
impl_value_using_from_str!(u8);
|
||||||
|
impl_value_using_from_str!(u16);
|
||||||
|
impl_value_using_from_str!(u32);
|
||||||
|
impl_value_using_from_str!(u64);
|
||||||
|
impl_value_using_from_str!(i8);
|
||||||
|
impl_value_using_from_str!(i16);
|
||||||
|
impl_value_using_from_str!(i32);
|
||||||
|
impl_value_using_from_str!(i64);
|
||||||
|
impl_value_using_from_str!(std::path::PathBuf);
|
||||||
|
|
||||||
|
pub trait Collect {
|
||||||
|
fn collect(&mut self, s: &str) -> Result<(), HelpMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Collect for T
|
||||||
|
where
|
||||||
|
T: Value,
|
||||||
|
{
|
||||||
|
fn collect(&mut self, s: &str) -> Result<(), HelpMessage> {
|
||||||
|
*self = T::parse(s)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Collect for Vec<T>
|
||||||
|
where
|
||||||
|
T: Value,
|
||||||
|
{
|
||||||
|
fn collect(&mut self, s: &str) -> Result<(), HelpMessage> {
|
||||||
|
self.push(T::parse(s)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Collect for Option<T>
|
||||||
|
where
|
||||||
|
T: Value,
|
||||||
|
{
|
||||||
|
fn collect(&mut self, s: &str) -> Result<(), HelpMessage> {
|
||||||
|
*self = Some(T::parse(s)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum ParseError {
|
||||||
|
InvalidParamFormat {
|
||||||
|
param: &'static str,
|
||||||
|
expected: &'static str,
|
||||||
|
},
|
||||||
|
MissingParamFor(&'static str),
|
||||||
|
UnknownParam(String),
|
||||||
|
SingleDashMustBeSingleCharacter(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum BindError {
|
||||||
|
DuplicateShort(char),
|
||||||
|
DuplicateLong(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Struct-of-array-style storage
|
||||||
|
struct Soa<'a, T: ?Sized> {
|
||||||
|
ts: Vec<&'a mut T>,
|
||||||
|
longs: Vec<&'static str>,
|
||||||
|
descriptions: Vec<Option<&'static str>>,
|
||||||
|
|
||||||
|
// Shorts are not not 1:1 in length with
|
||||||
|
// bindings, so we create a second linked vec that maps indices in
|
||||||
|
// shorts to indices in bindings
|
||||||
|
shorts: Vec<char>,
|
||||||
|
shorts_indices: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: ?Sized> Soa<'a, T> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
ts: Vec::new(),
|
||||||
|
longs: default(),
|
||||||
|
descriptions: default(),
|
||||||
|
shorts: default(),
|
||||||
|
shorts_indices: default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(
|
||||||
|
&mut self,
|
||||||
|
t: &'a mut T,
|
||||||
|
long: &'static str,
|
||||||
|
short: Option<char>,
|
||||||
|
description: Option<&'static str>,
|
||||||
|
) -> Result<(), BindError> {
|
||||||
|
if self.longs.iter().any(|l| *l == long) {
|
||||||
|
return Err(BindError::DuplicateLong(long));
|
||||||
|
}
|
||||||
|
if let Some(short) = short {
|
||||||
|
if self.shorts.iter().any(|c| *c == short) {
|
||||||
|
return Err(BindError::DuplicateShort(short));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = self.ts.len();
|
||||||
|
|
||||||
|
self.ts.push(t);
|
||||||
|
self.longs.push(long);
|
||||||
|
self.descriptions.push(description);
|
||||||
|
|
||||||
|
if let Some(c) = short {
|
||||||
|
self.shorts.push(c);
|
||||||
|
self.shorts_indices.push(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_for_long(
|
||||||
|
&mut self,
|
||||||
|
long: &str,
|
||||||
|
) -> Option<(&'static str, &mut &'a mut T)> {
|
||||||
|
self.longs
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, s)| *s == &long)
|
||||||
|
.map(|(i, s)| (*s, &mut self.ts[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_for_short(
|
||||||
|
&mut self,
|
||||||
|
short: char,
|
||||||
|
) -> Option<(&'static str, &mut &'a mut T)> {
|
||||||
|
self.shorts
|
||||||
|
.iter()
|
||||||
|
.zip(self.shorts_indices.iter().copied())
|
||||||
|
.find(|(s, _)| **s == short)
|
||||||
|
.map(|(_, i)| (self.longs[i], &mut self.ts[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params<'a> = Soa<'a, dyn Collect + 'a>;
|
||||||
|
type Flags<'a> = Soa<'a, bool>;
|
||||||
|
|
||||||
|
pub struct Cli<'a> {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
name: &'static str,
|
||||||
|
description: Option<&'static str>,
|
||||||
|
version: Option<&'static str>,
|
||||||
|
|
||||||
|
params: Params<'a>,
|
||||||
|
flags: Flags<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Cli<'a> {
|
||||||
|
pub fn new(name: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
description: default(),
|
||||||
|
version: default(),
|
||||||
|
|
||||||
|
params: Soa::new(),
|
||||||
|
flags: Soa::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(&mut self, d: &'static str) -> &mut Self {
|
||||||
|
self.description = Some(d);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&mut self, v: &'static str) -> &mut Self {
|
||||||
|
self.version = Some(v);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(&mut self, pb: ParamBuilder<'a>) -> Result<(), BindError> {
|
||||||
|
match pb.kind {
|
||||||
|
Kind::Param(p) => {
|
||||||
|
self.params.add(p, pb.long, pb.short, pb.description)
|
||||||
|
},
|
||||||
|
Kind::Flag(f) => {
|
||||||
|
self.flags.add(f, pb.long, pb.short, pb.description)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse<
|
||||||
|
'b,
|
||||||
|
A: AsRef<str>,
|
||||||
|
B: From<&'b str>,
|
||||||
|
PB: Push<B>,
|
||||||
|
PE: Push<ParseError>,
|
||||||
|
>(
|
||||||
|
&mut self,
|
||||||
|
args: &'b [A],
|
||||||
|
mut out: PB,
|
||||||
|
mut errors: PE,
|
||||||
|
) {
|
||||||
|
let mut it = args.iter();
|
||||||
|
fn next_param<'a, A: AsRef<str> + 'a>(
|
||||||
|
it: &mut impl Iterator<Item = &'a A>,
|
||||||
|
name: &'static str,
|
||||||
|
) -> Result<&'a str, ParseError> {
|
||||||
|
it.next()
|
||||||
|
.map(|s| s.as_ref())
|
||||||
|
.ok_or(ParseError::MissingParamFor(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(arg) = it.next() {
|
||||||
|
let mut go = || {
|
||||||
|
if let Some(arg) = arg.as_ref().strip_prefix("--") {
|
||||||
|
if let Some((n, b)) = self.params.find_for_long(arg) {
|
||||||
|
return b.collect(next_param(&mut it, n)?).map_err(
|
||||||
|
|e| ParseError::InvalidParamFormat {
|
||||||
|
param: n,
|
||||||
|
expected: e,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.flags.find_for_long(arg) {
|
||||||
|
Some((_, b)) => {
|
||||||
|
**b = true;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
None => Err(ParseError::UnknownParam(arg.to_string())),
|
||||||
|
}
|
||||||
|
} else if let Some(arg) = arg.as_ref().strip_prefix('-') {
|
||||||
|
if arg.len() > 1 {
|
||||||
|
return Err(
|
||||||
|
ParseError::SingleDashMustBeSingleCharacter(
|
||||||
|
arg.to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg = arg.chars().next().unwrap();
|
||||||
|
|
||||||
|
if let Some((n, b)) = self.params.find_for_short(arg) {
|
||||||
|
return b.collect(next_param(&mut it, n)?).map_err(
|
||||||
|
|e| ParseError::InvalidParamFormat {
|
||||||
|
param: n,
|
||||||
|
expected: e,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.flags.find_for_short(arg) {
|
||||||
|
Some((_, b)) => {
|
||||||
|
**b = true;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
None => Err(ParseError::UnknownParam(arg.to_string())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push(arg.as_ref().into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(err) = go().err() {
|
||||||
|
errors.push(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_env<PS: Push<String>, PE: Push<ParseError>>(
|
||||||
|
&mut self,
|
||||||
|
out: PS,
|
||||||
|
errors: PE,
|
||||||
|
) {
|
||||||
|
let env_args = std::env::args().collect::<Vec<_>>();
|
||||||
|
self.parse(env_args.as_slice(), out, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Kind<'a> {
|
||||||
|
Param(&'a mut dyn Collect),
|
||||||
|
Flag(&'a mut bool),
|
||||||
|
}
|
||||||
|
pub struct ParamBuilder<'a> {
|
||||||
|
kind: Kind<'a>,
|
||||||
|
long: &'static str,
|
||||||
|
description: Option<&'static str>,
|
||||||
|
short: Option<char>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn param<'a>(
|
||||||
|
long: &'static str,
|
||||||
|
v: &'a mut dyn Collect,
|
||||||
|
) -> ParamBuilder<'a> {
|
||||||
|
ParamBuilder {
|
||||||
|
kind: Kind::Param(v),
|
||||||
|
long,
|
||||||
|
description: default(),
|
||||||
|
short: default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flag<'a>(long: &'static str, v: &'a mut bool) -> ParamBuilder<'a> {
|
||||||
|
ParamBuilder {
|
||||||
|
kind: Kind::Flag(v),
|
||||||
|
long,
|
||||||
|
description: default(),
|
||||||
|
short: default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ParamBuilder<'a> {
|
||||||
|
pub fn short(mut self, s: char) -> Self {
|
||||||
|
self.short = Some(s);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, d: &'static str) -> Self {
|
||||||
|
self.description = Some(d);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind(self, cli: &mut Cli<'a>) {
|
||||||
|
cli.bind(self).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bind_checked(self, cli: &mut Cli<'a>) -> Result<(), BindError> {
|
||||||
|
cli.bind(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::{flag, param, Cli};
|
||||||
|
use e::default;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn basic() {
|
||||||
|
let mut cli = Cli::new("test");
|
||||||
|
cli.description("A simple test CLI");
|
||||||
|
cli.version("0.0.0");
|
||||||
|
|
||||||
|
let mut foo = String::new();
|
||||||
|
param("foo", &mut foo)
|
||||||
|
.short('f')
|
||||||
|
.description("foo")
|
||||||
|
.bind(&mut cli);
|
||||||
|
|
||||||
|
let mut bar: u64 = 0;
|
||||||
|
param("bar", &mut bar).description("bar").bind(&mut cli);
|
||||||
|
|
||||||
|
let mut baz: bool = false;
|
||||||
|
flag("baz", &mut baz)
|
||||||
|
.short('b')
|
||||||
|
.description("baz")
|
||||||
|
.bind(&mut cli);
|
||||||
|
|
||||||
|
let mut quux: bool = false;
|
||||||
|
assert!(flag("quux", &mut quux)
|
||||||
|
.short('b')
|
||||||
|
.bind_checked(&mut cli)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
let mut args: Vec<&str> = Vec::new();
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
cli.parse(
|
||||||
|
&["--foo", "foo", "--bar", "854", "-b", "arg"],
|
||||||
|
&mut args,
|
||||||
|
&mut errors,
|
||||||
|
);
|
||||||
|
assert_eq!(foo, "foo");
|
||||||
|
assert_eq!(bar, 854);
|
||||||
|
assert!(baz);
|
||||||
|
assert_eq!(args, &["arg"]);
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn complex_types() {
|
||||||
|
let mut cli = Cli::new("test");
|
||||||
|
|
||||||
|
let mut foos: Vec<String> = default();
|
||||||
|
param("foo", &mut foos).bind(&mut cli);
|
||||||
|
|
||||||
|
let mut bar: Option<String> = default();
|
||||||
|
param("bar", &mut bar).bind(&mut cli);
|
||||||
|
|
||||||
|
cli.parse::<_, String, _, _>(
|
||||||
|
&["--foo", "test", "--foo", "test2", "--bar", "bar"],
|
||||||
|
(),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(foos, &["test", "test2"]);
|
||||||
|
assert_eq!(bar, Some("bar".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue