Push code
This commit is contained in:
commit
8652d9d6cd
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
/Cargo.lock
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "figment-cliarg-provider"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
figment = "0.10.6"
|
||||||
|
wild = "2.0.4"
|
||||||
|
argmap = "1.1.2"
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Sean Ervin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,22 @@
|
||||||
|
# CLI Argument provider for Figment
|
||||||
|
|
||||||
|
This crate is a pretty simple implementation for getting config options from cli arguments.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
CLI Arguments:
|
||||||
|
```shell
|
||||||
|
./program --db.name=my-database --host=my-host --port=5432 --user=my-user --password=my-password
|
||||||
|
```
|
||||||
|
|
||||||
|
TOML config:
|
||||||
|
```toml
|
||||||
|
[db]
|
||||||
|
name = "my-database"
|
||||||
|
host = "localhost"
|
||||||
|
port = 5432
|
||||||
|
user = "my-user"
|
||||||
|
password = "my-password"
|
||||||
|
```
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
use figment::{Provider, Metadata, Profile, Error};
|
||||||
|
use figment::value::{Map, Dict, Value, Tag};
|
||||||
|
|
||||||
|
use crate::argument_tree::ArgumentTree;
|
||||||
|
|
||||||
|
/// A provider that fetches its data from a given URL.
|
||||||
|
pub struct FigmentCliArgsProvider {
|
||||||
|
/// The profile to emit data to if nesting is disabled.
|
||||||
|
profile: Option<Profile>,
|
||||||
|
args: Vec<std::string::String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FigmentCliArgsProvider {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
profile: None,
|
||||||
|
args: wild::args().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Provider for FigmentCliArgsProvider {
|
||||||
|
/// Returns metadata with kind `Cli Flags`, custom source is the
|
||||||
|
/// command line arguments separated by spaces.
|
||||||
|
fn metadata(&self) -> Metadata {
|
||||||
|
let args = &self.args;
|
||||||
|
Metadata::named("Cli Flags")
|
||||||
|
.source(args.join(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the command line arguments into a `Map` and `Value`s.
|
||||||
|
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
|
||||||
|
// Parse a `Value` from a `String`
|
||||||
|
fn parse_from_string(string: &String) -> Value {
|
||||||
|
// TODO: Other integer types
|
||||||
|
match string.parse::<i32>() {
|
||||||
|
Ok(i) => Value::Num(Tag::Default, figment::value::Num::I32(i)),
|
||||||
|
Err(_) => match string.parse::<bool>() {
|
||||||
|
Ok(b) => Value::Bool(Tag::Default, b),
|
||||||
|
Err(_) => Value::from(string.to_owned()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_cli(args: &Vec<std::string::String>)-> Result<Dict, Error> {
|
||||||
|
// 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 {
|
||||||
|
let len = vals.len();
|
||||||
|
if len == 0 {
|
||||||
|
// This is usually where booleans are given (use_cache, replace_torrents, etc.)
|
||||||
|
dict.insert(key, Value::Bool(Tag::Default, true));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the string argument values into a `Value`
|
||||||
|
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();
|
||||||
|
tree.insert(&mut key_iter, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
dict.append(&mut tree.to_dict());
|
||||||
|
|
||||||
|
Ok(dict)
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.profile {
|
||||||
|
// Don't nest: `fetch` into a `Dict`.
|
||||||
|
Some(profile) => Ok(profile.collect(parse_cli(&self.args)?)),
|
||||||
|
None => {
|
||||||
|
let mut map = Map::new();
|
||||||
|
map.insert(Profile::default(), parse_cli(&self.args)?);
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod cli_provider;
|
||||||
|
pub use cli_provider::FigmentCliArgsProvider;
|
||||||
|
|
||||||
|
mod argument_tree;
|
Loading…
Reference in New Issue