Merge branch 'config-refactor' into 'master'
Config File Handling See merge request arkham/arkham!4
This commit is contained in:
commit
ff8b1e9298
|
@ -3,4 +3,6 @@ image: "rust:latest"
|
|||
test:cargo:
|
||||
script:
|
||||
- rustc --version && cargo --version # Print version info for debugging
|
||||
- cargo test --workspace --verbose
|
||||
- cargo test --workspace
|
||||
- cargo test --workspace --features config_toml
|
||||
- cargo test --workspace --features config_json
|
||||
|
|
|
@ -8,6 +8,9 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"console",
|
||||
"criterion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -469,6 +472,9 @@ name = "serde"
|
|||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
|
@ -542,6 +548,15 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
|
|
|
@ -9,6 +9,14 @@ edition = "2018"
|
|||
[dependencies]
|
||||
# indicatif = "0.15.0"
|
||||
console = "0.14.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true}
|
||||
toml = { version="0.5.8", optional = true}
|
||||
|
||||
[features]
|
||||
config = ["serde"]
|
||||
config_toml = ["toml", "config"]
|
||||
config_json = ["serde_json", "config"]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
@ -16,3 +24,4 @@ criterion = "0.3"
|
|||
[[bench]]
|
||||
name = "arg_parsing"
|
||||
harness = false
|
||||
|
||||
|
|
|
@ -10,15 +10,12 @@ fn parse_args(c: &mut Criterion) {
|
|||
"c.json".into(),
|
||||
"thing".into(),
|
||||
];
|
||||
let app = App::new()
|
||||
let mut app = App::new()
|
||||
.opt(Opt::scalar("user").short("u").long("user"))
|
||||
.command(
|
||||
Command::new("thing")
|
||||
.opt(Opt::scalar("config").short("c").long("config"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_value("config"), Some("c.json".into()));
|
||||
}),
|
||||
.handler(|_, _ctx, _| {}),
|
||||
);
|
||||
|
||||
b.iter(|| {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
use arkham::{App, Command, Context, Opt};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.name("Config Test Example")
|
||||
.config_filename("examples/config.toml")
|
||||
.command(
|
||||
Command::new("hello")
|
||||
.opt(Opt::scalar("name").short("n").long("name"))
|
||||
.short_desc("Prints a hello message with a passed name")
|
||||
.handler(hello),
|
||||
)
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn hello(_: &App, ctx: &Context, _args: &[String]) {
|
||||
println!(
|
||||
"Hello, {}",
|
||||
ctx.get_string("name")
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
name = "Alice Alisson"
|
|
@ -21,7 +21,7 @@ fn main() {
|
|||
|
||||
fn fibonacci_handler(_app: &App, ctx: &Context, _args: &[String]) {
|
||||
let v = fibonacci(
|
||||
ctx.get_value("count")
|
||||
ctx.get_string("count")
|
||||
.unwrap_or("1".to_string())
|
||||
.parse()
|
||||
.unwrap_or(1),
|
||||
|
|
75
src/app.rs
75
src/app.rs
|
@ -1,6 +1,8 @@
|
|||
use crate::print_command_help;
|
||||
|
||||
use super::command::{help, Command, Handler};
|
||||
use super::context::Context;
|
||||
use super::opt::{ActiveOpt, Opt, OptError, OptKind};
|
||||
use super::opt::{Opt, OptError, OptKind};
|
||||
|
||||
use std::env;
|
||||
|
||||
|
@ -10,6 +12,7 @@ pub struct App {
|
|||
name: Option<&'static str>,
|
||||
version: Option<&'static str>,
|
||||
pub root: Command,
|
||||
config_filename: Option<&'static str>,
|
||||
env_prefix: Option<&'static str>,
|
||||
}
|
||||
|
||||
|
@ -20,6 +23,7 @@ impl Default for App {
|
|||
version: None,
|
||||
root: Command::new("root"),
|
||||
env_prefix: None,
|
||||
config_filename: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -143,16 +147,20 @@ impl App {
|
|||
/// ```rust
|
||||
/// use arkham::{App, Command, Context, Opt};
|
||||
/// App::new()
|
||||
/// .opt(Opt::flag("name").short("n").long("name"))
|
||||
/// .opt(Opt::scalar("name").short("n").long("name"))
|
||||
/// .handler(my_handler)
|
||||
/// .run_with(vec!["-n".to_string(), "alice".to_string()]);
|
||||
///
|
||||
/// fn my_handler(app: &App, ctx: &Context, args: &[String]) {
|
||||
/// println!("Hello, {}", ctx.get_value("name").unwrap());
|
||||
/// println!("Hello, {}", ctx.get_string("name").unwrap());
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run_with(&self, args: Vec<String>) -> Result<()> {
|
||||
run_command(self, &self.root, &args, &mut vec![])
|
||||
pub fn run_with(&mut self, args: Vec<String>) -> Result<()> {
|
||||
let mut ctx = Context::new(self.root.clone());
|
||||
if let Some(filename) = self.config_filename {
|
||||
ctx.load_config_file(filename);
|
||||
}
|
||||
run_command(self, &self.root, &args, &mut ctx)
|
||||
}
|
||||
|
||||
/// Execute the app and any specified handlers based on the arguments passsed to the
|
||||
|
@ -168,7 +176,7 @@ impl App {
|
|||
/// .run();
|
||||
///
|
||||
/// fn my_handler(app: &App, ctx: &Context, args: &[String]) {
|
||||
/// println!("Hello, {}", ctx.get_value("name").unwrap_or_else(|| "unnamed".into()));
|
||||
/// println!("Hello, {}", ctx.get_string("name").unwrap_or_else(|| "unnamed".into()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
|
@ -178,7 +186,7 @@ impl App {
|
|||
|
||||
/// This is the core logic for parsing arguments and executing handlers. It is ran but the App::run
|
||||
/// and App::run_with functions.
|
||||
fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveOpt>) -> Result<()> {
|
||||
fn run_command(app: &App, cmd: &Command, args: &[String], ctx: &mut Context) -> Result<()> {
|
||||
// Get an iterator for the incomming arguments
|
||||
let mut args = args.iter();
|
||||
// We will keep track of any arguments that arent consumed by the current command.
|
||||
|
@ -192,11 +200,11 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
if let Some(opt) = cmd.opts.iter().find(|o| &o.long == &arg[2..]) {
|
||||
match opt.kind {
|
||||
OptKind::Flag => {
|
||||
opts.push(ActiveOpt::new(opt.clone(), vec!["".into()]));
|
||||
ctx.set_flag(opt);
|
||||
}
|
||||
OptKind::String => {
|
||||
if let Some(value) = args.next() {
|
||||
opts.push(ActiveOpt::new(opt.clone(), vec![value.clone()]));
|
||||
ctx.set_opt(opt, value.clone());
|
||||
} else {
|
||||
return Err(OptError::InvalidOpt(opt.name.clone()));
|
||||
}
|
||||
|
@ -212,11 +220,11 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
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()]));
|
||||
ctx.set_flag(opt);
|
||||
}
|
||||
OptKind::String => {
|
||||
if let Some(value) = args.next() {
|
||||
opts.push(ActiveOpt::new(opt.clone(), vec![value.clone()]));
|
||||
ctx.set_opt(opt, value.clone());
|
||||
} else {
|
||||
return Err(OptError::InvalidOpt(opt.name.clone()));
|
||||
}
|
||||
|
@ -237,7 +245,7 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
.find(|cmd| ignored.iter().any(|a| *a == cmd.name))
|
||||
{
|
||||
ignored.retain(|a| *a != cmd.name);
|
||||
return run_command(app, cmd, &ignored, opts);
|
||||
return run_command(app, cmd, &ignored, ctx);
|
||||
}
|
||||
|
||||
// Automatic command help display
|
||||
|
@ -253,16 +261,34 @@ fn run_command(app: &App, cmd: &Command, args: &[String], opts: &mut Vec<ActiveO
|
|||
|
||||
// Execute the command handler
|
||||
if let Some(handler) = cmd.handler {
|
||||
let mut ctx = Context::new(cmd.clone(), opts.clone());
|
||||
ctx.cmd = cmd.clone();
|
||||
if let Some(prefix) = app.env_prefix {
|
||||
ctx.env_prefix = Some(prefix.to_string());
|
||||
}
|
||||
handler(app, &ctx, &ignored);
|
||||
} else {
|
||||
crate::vox::print(app.application_header());
|
||||
print_command_help(cmd, &vec![])
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "config"))]
|
||||
impl App {
|
||||
pub fn config_filename(&mut self, _filename: &'static str) -> Self {
|
||||
panic!("Not compiled with configuration capabilities. Add a config parser as a feature");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config")]
|
||||
impl App {
|
||||
pub fn config_filename(mut self, filename: &'static str) -> Self {
|
||||
self.config_filename = Some(filename);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -270,14 +296,13 @@ mod tests {
|
|||
#[test]
|
||||
fn test_long_string() {
|
||||
let args: Vec<String> = vec!["--user".into(), "joe".into()];
|
||||
let app = App::new()
|
||||
App::new()
|
||||
.opt(Opt::scalar("user").short("u").long("user"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
});
|
||||
|
||||
let res = app.run_with(args);
|
||||
assert!(res.is_ok());
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
})
|
||||
.run_with(args)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -289,18 +314,18 @@ mod tests {
|
|||
"c.json".into(),
|
||||
"thing".into(),
|
||||
];
|
||||
let app = App::new()
|
||||
App::new()
|
||||
.opt(Opt::scalar("user").short("u").long("user"))
|
||||
.command(
|
||||
Command::new("thing")
|
||||
.opt(Opt::scalar("config").short("c").long("config"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_value("config"), Some("c.json".into()));
|
||||
assert_eq!(ctx.get_string("user"), Some("joe".into()));
|
||||
assert_eq!(ctx.get_string("config"), Some("c.json".into()));
|
||||
}),
|
||||
);
|
||||
let res = app.run_with(args);
|
||||
assert!(res.is_ok());
|
||||
)
|
||||
.run_with(args)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn function_handler(_app: &App, _ctx: &Context, _args: &[String]) {
|
||||
|
|
357
src/context.rs
357
src/context.rs
|
@ -1,32 +1,168 @@
|
|||
use crate::{
|
||||
opt::{ActiveOpt, OptKind},
|
||||
Command,
|
||||
};
|
||||
use std::env;
|
||||
use crate::{Command, Opt};
|
||||
use std::{collections::BTreeMap, env};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Map(BTreeMap<String, ContextValue>);
|
||||
|
||||
impl Map {
|
||||
pub fn new() -> Self {
|
||||
Map(BTreeMap::new())
|
||||
}
|
||||
pub fn set(&mut self, name: String, v: ContextValue) {
|
||||
self.0.insert(name, v);
|
||||
}
|
||||
pub fn get(&self, name: &str) -> Option<&ContextValue> {
|
||||
self.0.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Array(Vec<ContextValue>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ContextValue {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Map(Map),
|
||||
Array(Array),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ValueKind {
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Bool,
|
||||
Map,
|
||||
Array,
|
||||
}
|
||||
|
||||
impl ContextValue {
|
||||
pub fn string(&self) -> Option<String> {
|
||||
if let Self::String(v) = self {
|
||||
Some(v.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bool(&self) -> Option<bool> {
|
||||
if let Self::Bool(v) = self {
|
||||
Some(*v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn integer(&self) -> Option<i64> {
|
||||
if let Self::Integer(v) = self {
|
||||
Some(*v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn float(&self) -> Option<f64> {
|
||||
if let Self::Float(v) = self {
|
||||
Some(*v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map(&self) -> Map {
|
||||
if let Self::Map(v) = self {
|
||||
v.clone()
|
||||
} else {
|
||||
Map::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn array(&self) -> Array {
|
||||
if let Self::Array(v) = self {
|
||||
v.clone()
|
||||
} else {
|
||||
Array(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> ValueKind {
|
||||
match self {
|
||||
Self::String(_) => ValueKind::String,
|
||||
Self::Integer(_) => ValueKind::Integer,
|
||||
Self::Float(_) => ValueKind::Float,
|
||||
Self::Bool(_) => ValueKind::Bool,
|
||||
Self::Map(_) => ValueKind::Map,
|
||||
Self::Array(_) => ValueKind::Array,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_string(value: String, kind: ValueKind) -> Option<Self> {
|
||||
match kind {
|
||||
ValueKind::String => Some(Self::String(value)),
|
||||
ValueKind::Integer => value.parse::<i64>().ok().map(|v| Self::Integer(v)),
|
||||
ValueKind::Float => value.parse::<f64>().ok().map(|v| Self::Float(v)),
|
||||
ValueKind::Bool => value.parse::<bool>().ok().map(|v| Self::Bool(v)),
|
||||
ValueKind::Array => None,
|
||||
ValueKind::Map => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ContextValue {
|
||||
fn from(fr: String) -> Self {
|
||||
ContextValue::String(fr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for ContextValue {
|
||||
fn from(fr: i64) -> Self {
|
||||
ContextValue::Integer(fr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for ContextValue {
|
||||
fn from(fr: f64) -> Self {
|
||||
ContextValue::Float(fr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ContextValue {
|
||||
fn from(fr: bool) -> Self {
|
||||
ContextValue::Bool(fr)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
opts: Vec<ActiveOpt>,
|
||||
cmd: Command,
|
||||
pub(crate) cmd: Command,
|
||||
pub(crate) config_data: Map,
|
||||
pub(crate) env_prefix: Option<String>,
|
||||
pub(crate) env_enabled: bool,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn new(cmd: Command, opts: Vec<ActiveOpt>) -> Self {
|
||||
pub(crate) fn new(cmd: Command) -> Self {
|
||||
Self {
|
||||
opts,
|
||||
cmd,
|
||||
env_prefix: None,
|
||||
env_enabled: true,
|
||||
config_data: Map::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_opt<T: Into<ContextValue>>(&mut self, opt: &Opt, value: T) {
|
||||
self.config_data.set(opt.name.clone(), value.into());
|
||||
}
|
||||
|
||||
pub(crate) fn set_flag(&mut self, opt: &Opt) {
|
||||
self.config_data.set(opt.name.clone(), true.into());
|
||||
}
|
||||
|
||||
/// Checks for the existance of a flag
|
||||
pub fn flag(&self, name: &str) -> bool {
|
||||
self.opts
|
||||
.iter()
|
||||
.any(|o| o.definition.name == name && matches!(o.definition.kind, OptKind::Flag))
|
||||
|| self.get_env_value(name).is_some()
|
||||
self.config_data.get(name).is_some() || self.get_env_value(name).is_some()
|
||||
}
|
||||
|
||||
fn get_env_value(&self, name: &str) -> Option<String> {
|
||||
|
@ -42,24 +178,157 @@ impl Context {
|
|||
}
|
||||
|
||||
/// Returns the value of an option if one exists
|
||||
pub fn get_value(&self, name: &str) -> Option<String> {
|
||||
self.opts
|
||||
.iter()
|
||||
.find_map(|o| {
|
||||
if o.definition.name == name {
|
||||
Some(o.raw_value.first().cloned())
|
||||
pub fn get_value(&self, name: &str) -> Option<ContextValue> {
|
||||
self.config_data
|
||||
.get(name)
|
||||
.cloned()
|
||||
.or(self.get_env_value(name).map(|v| ContextValue::String(v)))
|
||||
}
|
||||
|
||||
/// Returns a string for the field if it is a string
|
||||
pub fn get_string(&self, name: &str) -> Option<String> {
|
||||
match self.get_value(name) {
|
||||
Some(ContextValue::String(v)) => Some(v),
|
||||
Some(ContextValue::Integer(v)) => Some(v.to_string()),
|
||||
Some(ContextValue::Float(v)) => Some(v.to_string()),
|
||||
Some(ContextValue::Bool(v)) => Some(v.to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a i64 for the field if it is an integer
|
||||
pub fn get_int(&self, name: &str) -> Option<i64> {
|
||||
match self.get_value(name) {
|
||||
Some(ContextValue::String(v)) => v.parse().ok(),
|
||||
Some(ContextValue::Integer(v)) => Some(v),
|
||||
Some(ContextValue::Float(v)) => Some(v as i64),
|
||||
Some(ContextValue::Bool(true)) => Some(1),
|
||||
Some(ContextValue::Bool(false)) => Some(0),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a f64 for the field if it is a float
|
||||
pub fn get_float(&self, name: &str) -> Option<f64> {
|
||||
match self.get_value(name) {
|
||||
Some(ContextValue::String(v)) => v.parse().ok(),
|
||||
Some(ContextValue::Integer(v)) => Some(v as f64),
|
||||
Some(ContextValue::Float(v)) => Some(v),
|
||||
Some(ContextValue::Bool(true)) => Some(1_f64),
|
||||
Some(ContextValue::Bool(false)) => Some(0_f64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a f64 for the field if it is a float
|
||||
pub fn get_bool(&self, name: &str) -> Option<bool> {
|
||||
match self.get_value(name) {
|
||||
Some(ContextValue::String(v)) => v.parse().ok(),
|
||||
Some(ContextValue::Integer(0)) => Some(false),
|
||||
Some(ContextValue::Integer(_)) => Some(true),
|
||||
Some(ContextValue::Float(v)) => {
|
||||
if v == 0_f64 {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
Some(true)
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.or(self.get_env_value(name))
|
||||
}
|
||||
Some(ContextValue::Bool(v)) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Can be used to display the automatic help message for the current command.
|
||||
pub fn display_help(&self) {
|
||||
crate::command::print_command_help(&self.cmd, &vec![])
|
||||
}
|
||||
|
||||
pub(crate) fn load_config_file(&mut self, filename: &str) {
|
||||
if std::fs::metadata(filename)
|
||||
.ok()
|
||||
.map(|f| f.is_file())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let contents = std::fs::read_to_string(filename).unwrap();
|
||||
self.load_config_str(&contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "config"))]
|
||||
mod noconfig {
|
||||
use super::Context;
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn load_config_str(&mut self, _filename: &str) {}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config_toml")]
|
||||
mod toml {
|
||||
use super::{Array, Context, ContextValue, Map};
|
||||
|
||||
impl From<toml::Value> for ContextValue {
|
||||
fn from(fr: toml::Value) -> Self {
|
||||
match fr {
|
||||
toml::Value::String(v) => ContextValue::String(v),
|
||||
toml::Value::Integer(v) => ContextValue::Integer(v),
|
||||
toml::Value::Float(v) => ContextValue::Float(v),
|
||||
toml::Value::Boolean(v) => ContextValue::Bool(v),
|
||||
toml::Value::Array(v) => ContextValue::Array(Array(
|
||||
v.iter().map(|v| ContextValue::from(v.clone())).collect(),
|
||||
)),
|
||||
toml::Value::Table(v) => ContextValue::Map(Map(v
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), ContextValue::from(v.clone())))
|
||||
.collect())),
|
||||
toml::Value::Datetime(v) => ContextValue::String(v.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn load_config_str(&mut self, data: &str) {
|
||||
self.config_data = ContextValue::from(toml::from_str::<toml::Value>(data).unwrap())
|
||||
.map()
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "config_json")]
|
||||
mod json {
|
||||
use super::{Array, Context, ContextValue, Map};
|
||||
use serde_json::Value;
|
||||
|
||||
impl From<Value> for ContextValue {
|
||||
fn from(fr: Value) -> Self {
|
||||
match fr {
|
||||
Value::String(v) => ContextValue::String(v),
|
||||
Value::Number(v) if v.is_i64() => ContextValue::Integer(v.as_i64().unwrap_or(0)),
|
||||
Value::Number(v) if v.is_f64() => ContextValue::Float(v.as_f64().unwrap_or(0_f64)),
|
||||
Value::Number(v) => ContextValue::Integer(v.as_i64().unwrap_or(0)),
|
||||
Value::Array(v) => ContextValue::Array(Array(
|
||||
v.iter().map(|v| ContextValue::from(v.clone())).collect(),
|
||||
)),
|
||||
Value::Bool(v) => ContextValue::Bool(v),
|
||||
Value::Null => ContextValue::String("null".to_string()),
|
||||
Value::Object(v) => ContextValue::Map(Map(v
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), ContextValue::from(v.clone())))
|
||||
.collect())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub(crate) fn load_config_str(&mut self, data: &str) {
|
||||
self.config_data =
|
||||
ContextValue::from(serde_json::from_str::<Value>(data).unwrap_or_default())
|
||||
.map()
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -80,7 +349,7 @@ mod tests {
|
|||
App::new()
|
||||
.opt(Opt::scalar("thing").short("-t").long("--t"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("thing"), Some("one".into()));
|
||||
assert_eq!(ctx.get_string("thing"), Some("one".into()));
|
||||
})
|
||||
.run_with(vec![])
|
||||
.unwrap();
|
||||
|
@ -89,9 +358,47 @@ mod tests {
|
|||
App::new()
|
||||
.opt(Opt::scalar("thing").short("t").long("thing"))
|
||||
.handler(|_, ctx, _| {
|
||||
assert_eq!(ctx.get_value("thing"), Some("1".into()));
|
||||
assert_eq!(ctx.get_string("thing"), Some("1".into()));
|
||||
})
|
||||
.run_with(vec!["-t".into(), "1".into()])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "config_toml")]
|
||||
#[test]
|
||||
fn config_load_toml() {
|
||||
use super::{Command, Context};
|
||||
let cmd = Command::new("xxx");
|
||||
let mut ctx = Context::new(cmd);
|
||||
ctx.load_config_str(
|
||||
r#"
|
||||
config_thing = "good"
|
||||
num = 15
|
||||
|
||||
[nested]
|
||||
something = 14
|
||||
"#,
|
||||
);
|
||||
assert_eq!(ctx.get_string("config_thing"), Some("good".into()));
|
||||
}
|
||||
|
||||
#[cfg(feature = "config_json")]
|
||||
#[test]
|
||||
fn config_load_toml() {
|
||||
use super::{Command, Context};
|
||||
let cmd = Command::new("xxx");
|
||||
let mut ctx = Context::new(cmd);
|
||||
ctx.load_config_str(
|
||||
r#"
|
||||
{
|
||||
"config_thing": "good",
|
||||
"num": 15,
|
||||
"nested": {
|
||||
"something": 14
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
assert_eq!(ctx.get_string("config_thing"), Some("good".into()));
|
||||
}
|
||||
}
|
||||
|
|
15
src/opt.rs
15
src/opt.rs
|
@ -106,18 +106,3 @@ pub(crate) enum OptKind {
|
|||
Flag,
|
||||
String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ActiveOpt {
|
||||
pub definition: Opt,
|
||||
pub raw_value: Vec<String>,
|
||||
}
|
||||
|
||||
impl ActiveOpt {
|
||||
pub fn new(definition: Opt, raw_value: Vec<String>) -> Self {
|
||||
ActiveOpt {
|
||||
definition,
|
||||
raw_value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue