Merge branch 'env-config' into 'master'
Environment based options/flags See merge request arkham/arkham!3
This commit is contained in:
commit
7f757c76f9
|
@ -10,6 +10,7 @@ building blocks for building attractive and smooth CLIs
|
|||
* Nested subcommands with their own flags
|
||||
* Opts are hierarchal and can be utilized from parent commands
|
||||
* Automatic usage details for subcommands and bare execution
|
||||
* Flags and options can be set via environment variables
|
||||
|
||||
## Styling
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ fn parse_args(c: &mut Criterion) {
|
|||
Command::new("thing")
|
||||
.opt(Opt::scalar("config").short("c").long("config"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_string("config"), Some("c.json".into()));
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_value("config"), Some("c.json".into()));
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ fn main() {
|
|||
|
||||
fn fibonacci_handler(_app: &App, ctx: &Context, _args: &[String]) {
|
||||
let v = fibonacci(
|
||||
ctx.get_string("count")
|
||||
ctx.get_value("count")
|
||||
.unwrap_or("1".to_string())
|
||||
.parse()
|
||||
.unwrap_or(1),
|
||||
|
|
57
src/app.rs
57
src/app.rs
|
@ -10,6 +10,7 @@ pub struct App {
|
|||
name: Option<&'static str>,
|
||||
version: Option<&'static str>,
|
||||
pub root: Command,
|
||||
env_prefix: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
|
@ -18,6 +19,7 @@ impl Default for App {
|
|||
name: None,
|
||||
version: None,
|
||||
root: Command::new("root"),
|
||||
env_prefix: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +70,21 @@ impl App {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the environment variable prefix for option resolution. If set to something like
|
||||
/// APP_NAME a option with the name THING, will look for an environment variable named
|
||||
/// APP_NAME_THING.
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust
|
||||
/// use arkham::App;
|
||||
/// App::new().env_prefix("APP_NAME");
|
||||
///
|
||||
/// ```
|
||||
pub fn env_prefix(mut self, prefix: &'static str) -> Self {
|
||||
self.env_prefix = Some(prefix);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a root level command to the application. This command can then be executed with:
|
||||
///
|
||||
/// myapp command_name
|
||||
|
@ -131,7 +148,7 @@ impl App {
|
|||
/// .run_with(vec!["-n".to_string(), "alice".to_string()]);
|
||||
///
|
||||
/// fn my_handler(app: &App, ctx: &Context, args: &[String]) {
|
||||
/// println!("Hello, {}", ctx.get_string("name").unwrap());
|
||||
/// println!("Hello, {}", ctx.get_value("name").unwrap());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run_with(&self, args: Vec<String>) -> Result<()> {
|
||||
|
@ -151,7 +168,7 @@ impl App {
|
|||
/// .run();
|
||||
///
|
||||
/// fn my_handler(app: &App, ctx: &Context, args: &[String]) {
|
||||
/// println!("Hello, {}", ctx.get_string("name").unwrap_or_else(|| "unnamed".into()));
|
||||
/// println!("Hello, {}", ctx.get_value("name").unwrap_or_else(|| "unnamed".into()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
|
@ -193,7 +210,18 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
// Check for short args
|
||||
if arg.starts_with("-") {
|
||||
if let Some(opt) = cmd.opts.iter().find(|o| &o.short == &arg[1..]) {
|
||||
match opt.kind {
|
||||
OptKind::Flag => {
|
||||
opts.push(ActiveOpt::new(opt.clone(), vec!["".into()]));
|
||||
}
|
||||
OptKind::String => {
|
||||
if let Some(value) = args.next() {
|
||||
opts.push(ActiveOpt::new(opt.clone(), vec![value.clone()]));
|
||||
} else {
|
||||
return Err(OptError::InvalidOpt(opt.name.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ignored.push(arg.clone());
|
||||
}
|
||||
|
@ -225,7 +253,11 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
|
||||
// Execute the command handler
|
||||
if let Some(handler) = cmd.handler {
|
||||
handler(app, &Context::new(cmd.clone(), opts.clone()), &ignored);
|
||||
let mut ctx = Context::new(cmd.clone(), opts.clone());
|
||||
if let Some(prefix) = app.env_prefix {
|
||||
ctx.env_prefix = Some(prefix.to_string());
|
||||
}
|
||||
handler(app, &ctx, &ignored);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -241,7 +273,7 @@ mod tests {
|
|||
let app = App::new()
|
||||
.opt(Opt::scalar("user").short("u").long("user"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
});
|
||||
|
||||
let res = app.run_with(args);
|
||||
|
@ -263,8 +295,8 @@ mod tests {
|
|||
Command::new("thing")
|
||||
.opt(Opt::scalar("config").short("c").long("config"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_string("config"), Some("c.json".into()));
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_value("config"), Some("c.json".into()));
|
||||
}),
|
||||
);
|
||||
let res = app.run_with(args);
|
||||
|
@ -318,4 +350,17 @@ mod tests {
|
|||
.run_with(vec!["-u".into()]);
|
||||
assert!(r.is_err(), "Should error for invalid short flag");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_env_prefix() {
|
||||
std::env::set_var("ARKHAM_thing", "1");
|
||||
App::new()
|
||||
.env_prefix("ARKHAM")
|
||||
.opt(Opt::flag("thing").short("-t").long("--t"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.flag("thing"), true);
|
||||
})
|
||||
.run_with(vec![])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,23 @@ use crate::{
|
|||
opt::{ActiveOpt, OptKind},
|
||||
Command,
|
||||
};
|
||||
use std::env;
|
||||
|
||||
pub struct Context {
|
||||
opts: Vec<ActiveOpt>,
|
||||
cmd: Command,
|
||||
pub(crate) env_prefix: Option<String>,
|
||||
pub(crate) env_enabled: bool,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn new(cmd: Command, opts: Vec<ActiveOpt>) -> Self {
|
||||
Self { opts, cmd }
|
||||
Self {
|
||||
opts,
|
||||
cmd,
|
||||
env_prefix: None,
|
||||
env_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for the existance of a flag
|
||||
|
@ -18,10 +26,23 @@ impl Context {
|
|||
self.opts
|
||||
.iter()
|
||||
.any(|o| o.definition.name == name && matches!(o.definition.kind, OptKind::Flag))
|
||||
|| self.get_env_value(name).is_some()
|
||||
}
|
||||
|
||||
fn get_env_value(&self, name: &str) -> Option<String> {
|
||||
if self.env_enabled == false {
|
||||
return None;
|
||||
}
|
||||
let name = self
|
||||
.env_prefix
|
||||
.as_ref()
|
||||
.map(|pre| format!("{}_{}", pre, name))
|
||||
.unwrap_or(name.to_string());
|
||||
env::var(name).ok()
|
||||
}
|
||||
|
||||
/// Returns the value of an option if one exists
|
||||
pub fn get_string(&self, name: &str) -> Option<String> {
|
||||
pub fn get_value(&self, name: &str) -> Option<String> {
|
||||
self.opts
|
||||
.iter()
|
||||
.find_map(|o| {
|
||||
|
@ -32,6 +53,7 @@ impl Context {
|
|||
}
|
||||
})
|
||||
.flatten()
|
||||
.or(self.get_env_value(name))
|
||||
}
|
||||
|
||||
/// Can be used to display the automatic help message for the current command.
|
||||
|
@ -39,3 +61,37 @@ impl Context {
|
|||
crate::command::print_command_help(&self.cmd, &vec![])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{App, Opt};
|
||||
#[test]
|
||||
fn test_env() {
|
||||
std::env::set_var("thing", "1");
|
||||
App::new()
|
||||
.opt(Opt::flag("thing").short("-t").long("--t"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.flag("thing"), true);
|
||||
})
|
||||
.run_with(vec![])
|
||||
.unwrap();
|
||||
|
||||
std::env::set_var("thing", "one");
|
||||
App::new()
|
||||
.opt(Opt::scalar("thing").short("-t").long("--t"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("thing"), Some("one".into()));
|
||||
})
|
||||
.run_with(vec![])
|
||||
.unwrap();
|
||||
|
||||
std::env::set_var("thing", "one");
|
||||
App::new()
|
||||
.opt(Opt::scalar("thing").short("t").long("thing"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("thing"), Some("1".into()));
|
||||
})
|
||||
.run_with(vec!["-t".into(), "1".into()])
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue