Parse command line arguments as configuration options.
I made an "ArgumentTree" to store all the arguments efficiently as they're being parsed. This would've been significantly easier if `figment::value::Value` gave us a mutable reference to the data. If it did, I would've been able to just use `Dict` but as the last commit showed, that wouldn't work; I need something mutable.
This commit is contained in:
parent
d2a22d6ee1
commit
2a225ac2db
|
@ -0,0 +1,93 @@
|
||||||
|
use std::{collections::HashMap, slice::Iter, iter::Peekable};
|
||||||
|
|
||||||
|
use figment::value::{Value, Dict};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ArgumentTreeNode {
|
||||||
|
Leaf(Value),
|
||||||
|
Branch(HashMap<String, ArgumentTreeNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgumentTreeNode {
|
||||||
|
/// Insert a value into the tree.
|
||||||
|
pub fn insert(&mut self, keys: &mut Peekable<&mut Iter<String>>, value: Value) {
|
||||||
|
let key = keys.next().unwrap();
|
||||||
|
|
||||||
|
match self {
|
||||||
|
ArgumentTreeNode::Leaf(_) => panic!("Cannot insert into a leaf node"),
|
||||||
|
ArgumentTreeNode::Branch(children) => {
|
||||||
|
match children.get_mut(key) {
|
||||||
|
// If the key is already in the tree, insert into it,
|
||||||
|
// going farther until the key doesn't exist anymore which
|
||||||
|
// is where we insert the value.
|
||||||
|
Some(node) => {
|
||||||
|
node.insert(keys, value);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Check if we should insert a leaf or a branch by
|
||||||
|
// `peek`ing to see if there is a next key.
|
||||||
|
if keys.peek().is_none() {
|
||||||
|
let node = ArgumentTreeNode::Leaf(value);
|
||||||
|
children.insert(key.to_owned(), node);
|
||||||
|
} else {
|
||||||
|
let mut node = ArgumentTreeNode::Branch(HashMap::new());
|
||||||
|
node.insert(keys, value);
|
||||||
|
|
||||||
|
children.insert(key.to_owned(), node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the tree into a `Dict`.
|
||||||
|
fn to_dict(&self) -> Dict {
|
||||||
|
match self {
|
||||||
|
ArgumentTreeNode::Leaf(_) => panic!("Cannot convert a leaf node to a dict!"),
|
||||||
|
ArgumentTreeNode::Branch(children) => {
|
||||||
|
let mut dict = Dict::new();
|
||||||
|
|
||||||
|
// Iterate over the children and recursively convert them to `Dict`s.
|
||||||
|
// When it run into a leaf node, insert its value.
|
||||||
|
for (key, node) in children.iter() {
|
||||||
|
match node {
|
||||||
|
ArgumentTreeNode::Leaf(value) => {
|
||||||
|
dict.insert(key.to_owned(), value.clone());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
dict.insert(key.to_owned(), Value::from(node.to_dict()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ArgumentTree {
|
||||||
|
pub root: ArgumentTreeNode
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgumentTree {
|
||||||
|
/// Create a new tree.
|
||||||
|
pub fn new() -> ArgumentTree {
|
||||||
|
ArgumentTree {
|
||||||
|
root: ArgumentTreeNode::Branch(HashMap::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert into the tree.
|
||||||
|
pub fn insert(&mut self, keys: &mut Iter<String>, value: Value) {
|
||||||
|
let mut keys = keys.peekable();
|
||||||
|
|
||||||
|
self.root.insert(&mut keys, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the tree into a `Dict`.
|
||||||
|
pub fn to_dict(&mut self) -> Dict {
|
||||||
|
self.root.to_dict()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use std::slice::Iter;
|
|
||||||
|
|
||||||
use figment::{Provider, Metadata, Profile, Error};
|
use figment::{Provider, Metadata, Profile, Error};
|
||||||
use figment::value::{Map, Dict, Value, Tag};
|
use figment::value::{Map, Dict, Value, Tag};
|
||||||
use serde::Deserialize;
|
|
||||||
|
use crate::config::ArgumentTree;
|
||||||
|
|
||||||
/// A provider that fetches its data from a given URL.
|
/// A provider that fetches its data from a given URL.
|
||||||
pub struct CliProvider {
|
pub struct CliProvider {
|
||||||
|
@ -22,21 +20,15 @@ impl CliProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provider for CliProvider {
|
impl Provider for CliProvider {
|
||||||
/// Returns metadata with kind `Network`, custom source `self.url`,
|
/// Returns metadata with kind `Cli Flags`, custom source is the
|
||||||
/// and interpolator that returns a URL of `url/a/b/c` for key `a.b.c`.
|
/// command line arguments separated by spaces.
|
||||||
fn metadata(&self) -> Metadata {
|
fn metadata(&self) -> Metadata {
|
||||||
let args = &self.args;
|
let args = &self.args;
|
||||||
Metadata::named("CLI Flags")
|
Metadata::named("Cli Flags")
|
||||||
.source(args.join(" "))
|
.source(args.join(" "))
|
||||||
//.source(args.map(|args| args.collect::<Vec<_>>().join(" ")).unwrap_or(String::default()))
|
|
||||||
/* .interpolater(move |profile, keys| match profile.is_custom() {
|
|
||||||
true => format!("{}/{}/{}", url, profile, keys.join("/")),
|
|
||||||
false => format!("{}/{}", url, keys.join("/")),
|
|
||||||
}) */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the data from `self.url`. Note that `Dict`, `Map`, and
|
/// Parses the command line arguments into a `Map` and `Value`s.
|
||||||
/// `Profile` are `Deserialize`, so we can deserialized to them.
|
|
||||||
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
|
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
|
||||||
// Parse a `Value` from a `String`
|
// Parse a `Value` from a `String`
|
||||||
fn parse_from_string(string: &String) -> Value {
|
fn parse_from_string(string: &String) -> Value {
|
||||||
|
@ -50,98 +42,36 @@ impl Provider for CliProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_keys(keys: &mut Iter<&str>, dict: &Dict, vals: &Vec<String>) -> Value {
|
|
||||||
let key = keys.next();
|
|
||||||
|
|
||||||
match key {
|
|
||||||
None => {
|
|
||||||
if vals.len() == 1 {
|
|
||||||
parse_from_string(&vals[0])
|
|
||||||
} else {
|
|
||||||
let mut values = Vec::new();
|
|
||||||
for val in vals.iter() {
|
|
||||||
values.push(parse_from_string(val));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Array(Tag::Default, values)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(key) => {
|
|
||||||
let key = key.to_string();
|
|
||||||
println!("Key is {}", key);
|
|
||||||
|
|
||||||
println!("Dict is {:?}", dict);
|
|
||||||
|
|
||||||
match dict.get(&key) {
|
|
||||||
Some(val) => {
|
|
||||||
println!("Val is {:?}", val);
|
|
||||||
|
|
||||||
match val.as_dict() {
|
|
||||||
Some(dict) => parse_keys(keys, &dict, vals),
|
|
||||||
None => panic!("Expected a `Dict`, got some other value"),
|
|
||||||
}
|
|
||||||
//parse_keys(keys, &dict, vals)
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
let mut current_dict = Dict::new();
|
|
||||||
let val = parse_keys(keys, ¤t_dict, vals);
|
|
||||||
|
|
||||||
current_dict.insert(key.to_string(), val);
|
|
||||||
|
|
||||||
Value::from(current_dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* let mut current_dict = Dict::new();
|
|
||||||
let val = parse_keys(keys, ¤t_dict, vals);
|
|
||||||
|
|
||||||
current_dict.insert(key.to_string(), val);
|
|
||||||
|
|
||||||
Value::from(current_dict) */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_cli(args: &Vec<std::string::String>)-> Result<Dict, Error> {
|
fn parse_cli(args: &Vec<std::string::String>)-> Result<Dict, Error> {
|
||||||
let (args, argv) = argmap::parse(args.iter());
|
// TODO: Parse _args as booleans
|
||||||
|
let (_args, argv) = argmap::parse(args.iter());
|
||||||
let mut dict = Dict::new();
|
|
||||||
|
|
||||||
|
let mut tree = ArgumentTree::new();
|
||||||
for (key, vals) in argv {
|
for (key, vals) in argv {
|
||||||
let len = vals.len();
|
let len = vals.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_vec = key.split(".").collect::<Vec<_>>();
|
// Parse the string argument values into a `Value`
|
||||||
if key_vec.len() > 1 {
|
let val = match len {
|
||||||
|
1 => parse_from_string(&vals[0]),
|
||||||
|
_ => {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for val in vals {
|
||||||
|
vec.push(parse_from_string(&val));
|
||||||
|
}
|
||||||
|
Value::from(vec)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Separate the key into its parts and then insert it into the tree
|
||||||
|
let key_vec = key.split(".").map(|s| s.to_string()).collect::<Vec<_>>();
|
||||||
let mut key_iter = key_vec.iter();
|
let mut key_iter = key_vec.iter();
|
||||||
|
tree.insert(&mut key_iter, val);
|
||||||
//let key = key_iter.next();
|
|
||||||
let key = key_vec.first();
|
|
||||||
let val = parse_keys(&mut key_iter, &dict, &vals);
|
|
||||||
|
|
||||||
println!("Final val is {:?}", val);
|
|
||||||
|
|
||||||
dict.insert(key.unwrap().to_string(), val);
|
|
||||||
} else {
|
|
||||||
if len == 1 {
|
|
||||||
dict.insert(key, parse_from_string(&vals[0]));
|
|
||||||
} else {
|
|
||||||
let mut values = Vec::new();
|
|
||||||
for val in &vals {
|
|
||||||
values.push(parse_from_string(val));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dict.insert(key, Value::Array(Tag::Default, values));
|
Ok(tree.to_dict())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Dict: {:?}", dict);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ok(dict)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.profile {
|
match &self.profile {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
|
|
||||||
|
pub mod argument_tree;
|
||||||
|
pub use argument_tree::*;
|
||||||
|
|
||||||
pub mod cli_provider;
|
pub mod cli_provider;
|
||||||
pub use cli_provider::CliProvider;
|
pub use cli_provider::*;
|
Loading…
Reference in New Issue