Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3461e52

Browse files
committedAug 28, 2023
Initial implementation of config parser
1 parent 5b72c2b commit 3461e52

File tree

6 files changed

+364
-9
lines changed

6 files changed

+364
-9
lines changed
 

‎src/args.rs

+25-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::ffi::OsString;
1+
use std::{ffi::OsString, rc::Rc};
22

33
pub(crate) use crate::arg::*;
44
use crate::{
@@ -37,6 +37,7 @@ pub struct Args<'a> {
3737
name: Option<String>,
3838
#[cfg(feature = "autocomplete")]
3939
c_rev: Option<usize>,
40+
config: Option<Rc<dyn crate::config::Config + 'static>>,
4041
}
4142

4243
impl Args<'_> {
@@ -77,6 +78,12 @@ impl Args<'_> {
7778
self.name = Some(name.to_owned());
7879
self
7980
}
81+
82+
#[must_use]
83+
pub fn with_config(mut self, config: impl crate::config::Config + 'static) -> Self {
84+
self.config = Some(Rc::new(config));
85+
self
86+
}
8087
}
8188

8289
impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
@@ -86,6 +93,7 @@ impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
8693
#[cfg(feature = "autocomplete")]
8794
c_rev: None,
8895
name: None,
96+
config: None,
8997
}
9098
}
9199
}
@@ -97,6 +105,7 @@ impl<'a> From<&'a [&'a std::ffi::OsStr]> for Args<'a> {
97105
#[cfg(feature = "autocomplete")]
98106
c_rev: None,
99107
name: None,
108+
config: None,
100109
}
101110
}
102111
}
@@ -108,6 +117,7 @@ impl<'a> From<&'a [&'a str]> for Args<'a> {
108117
#[cfg(feature = "autocomplete")]
109118
c_rev: None,
110119
name: None,
120+
config: None,
111121
}
112122
}
113123
}
@@ -119,6 +129,7 @@ impl<'a> From<&'a [String]> for Args<'a> {
119129
#[cfg(feature = "autocomplete")]
120130
c_rev: None,
121131
name: None,
132+
config: None,
122133
}
123134
}
124135
}
@@ -130,6 +141,7 @@ impl<'a> From<&'a [OsString]> for Args<'a> {
130141
#[cfg(feature = "autocomplete")]
131142
c_rev: None,
132143
name: None,
144+
config: None,
133145
}
134146
}
135147
}
@@ -150,6 +162,7 @@ impl Args<'_> {
150162
#[cfg(feature = "autocomplete")]
151163
c_rev: None,
152164
name,
165+
config: None,
153166
}
154167
}
155168
}
@@ -245,7 +258,7 @@ pub use inner::State;
245258
mod inner {
246259
use std::{ops::Range, rc::Rc};
247260

248-
use crate::{error::Message, Args};
261+
use crate::{config::ConfigReader, error::Message, Args};
249262

250263
use super::{split_os_argument, Arg, ArgType, ItemState};
251264
#[derive(Clone, Debug)]
@@ -278,6 +291,8 @@ mod inner {
278291
/// scope starts on the right of the first consumed item and might end before the end
279292
/// of the list, similarly for "commands"
280293
scope: Range<usize>,
294+
295+
pub(crate) config: Option<ConfigReader>,
281296
}
282297

283298
impl State {
@@ -400,6 +415,9 @@ mod inner {
400415
if let Some(name) = args.name {
401416
path.push(name);
402417
}
418+
419+
let config = args.config.map(ConfigReader::new);
420+
403421
State {
404422
item_state,
405423
remaining,
@@ -409,6 +427,7 @@ mod inner {
409427
path,
410428
#[cfg(feature = "autocomplete")]
411429
comp,
430+
config,
412431
}
413432
}
414433
}
@@ -472,6 +491,10 @@ mod inner {
472491
self.remaining
473492
}
474493

494+
pub(crate) fn full_len(&self) -> usize {
495+
self.remaining + self.config.as_ref().map_or(0, |c| c.unconsumed())
496+
}
497+
475498
/// Get an argument from a scope that was not consumed yet
476499
pub(crate) fn get(&self, ix: usize) -> Option<&Arg> {
477500
if self.scope.contains(&ix) && self.item_state.get(ix)?.present() {

‎src/config.rs

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
//! The idea of the `Cursors` struct is to be able to keep a state for reading from multiple fields
2+
//! that is relatively cheap to clone
3+
4+
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
5+
6+
pub trait Config {
7+
fn get(&self, path: &[(&'static str, usize)], name: &'static str, num: usize)
8+
-> Option<String>;
9+
}
10+
11+
impl Config for BTreeMap<String, String> {
12+
fn get(
13+
&self,
14+
path: &[(&'static str, usize)],
15+
name: &'static str,
16+
num: usize,
17+
) -> Option<String> {
18+
if path.is_empty() && num == 0 {
19+
self.get(name).cloned()
20+
} else {
21+
None
22+
}
23+
}
24+
}
25+
26+
#[derive(Clone)]
27+
pub(crate) struct ConfigReader {
28+
config: Rc<dyn Config>,
29+
cursors: Cursors,
30+
unconsumed: usize,
31+
path: Vec<(&'static str, usize)>,
32+
}
33+
34+
impl ConfigReader {
35+
pub(crate) fn new(config: Rc<dyn Config + 'static>) -> Self {
36+
Self {
37+
config,
38+
cursors: Cursors::default(),
39+
unconsumed: 1000000000,
40+
path: Vec::new(),
41+
}
42+
}
43+
44+
pub(crate) fn enter(&mut self, name: &'static str) {
45+
let pos = self.cursors.enter(&self.path, name);
46+
self.path.push((name, pos));
47+
}
48+
49+
pub(crate) fn exit(&mut self) {
50+
self.path.pop();
51+
}
52+
pub(crate) fn get(&mut self, name: &'static str) -> Option<String> {
53+
let pos = self.cursors.get(&self.path, name);
54+
55+
let v = self.config.get(&self.path, name, pos)?;
56+
self.unconsumed -= 1;
57+
Some(v)
58+
}
59+
pub(crate) fn unconsumed(&self) -> usize {
60+
self.unconsumed
61+
}
62+
}
63+
64+
impl std::fmt::Debug for ConfigReader {
65+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66+
f.debug_struct("ConfigReader").finish()
67+
}
68+
}
69+
70+
#[derive(Debug, Clone, Default)]
71+
pub(crate) struct Cursors {
72+
tree: Rc<RefCell<Trie>>,
73+
items: Vec<usize>,
74+
}
75+
76+
impl Cursors {
77+
pub(crate) fn enter(&mut self, path: &[(&'static str, usize)], name: &'static str) -> usize {
78+
let mut t = (*self.tree).borrow_mut();
79+
let (pos, children) = t.enter(path, name, self.items.len());
80+
for child in children {
81+
if let Some(v) = self.items.get_mut(child) {
82+
*v = 0;
83+
}
84+
}
85+
86+
loop {
87+
match self.items.get_mut(pos) {
88+
Some(v) => {
89+
let res = *v;
90+
*v += 1;
91+
return res;
92+
}
93+
None => self.items.push(0),
94+
}
95+
}
96+
}
97+
98+
pub(crate) fn get(&mut self, path: &[(&'static str, usize)], name: &'static str) -> usize {
99+
let pos = (*self.tree)
100+
.borrow_mut()
101+
.enter(path, name, self.items.len())
102+
.0;
103+
104+
loop {
105+
match self.items.get_mut(pos) {
106+
Some(v) => {
107+
let res = *v;
108+
*v += 1;
109+
return res;
110+
}
111+
None => self.items.push(0),
112+
}
113+
}
114+
}
115+
}
116+
117+
#[derive(Debug, Default)]
118+
struct Trie {
119+
leaf: Option<usize>,
120+
children: BTreeMap<&'static str, Trie>,
121+
}
122+
123+
impl Trie {
124+
fn enter(
125+
&mut self,
126+
path: &[(&'static str, usize)],
127+
name: &'static str,
128+
fallback: usize,
129+
) -> (usize, impl Iterator<Item = usize> + '_) {
130+
let mut cur = self;
131+
132+
for (p, _) in path {
133+
cur = cur.children.entry(p).or_default();
134+
}
135+
cur = cur.children.entry(name).or_default();
136+
if cur.leaf.is_none() {
137+
cur.leaf = Some(fallback);
138+
}
139+
(
140+
cur.leaf.unwrap(),
141+
cur.children.values().filter_map(|t| t.leaf),
142+
)
143+
}
144+
}
145+
146+
#[test]
147+
fn basic_cursor_logic() {
148+
let mut cursors = Cursors::default();
149+
assert_eq!(0, cursors.get(&[], "hello"));
150+
assert_eq!(0, cursors.get(&[], "bob"));
151+
assert_eq!(1, cursors.get(&[], "hello"));
152+
assert_eq!(2, cursors.get(&[], "hello"));
153+
cursors.enter(&[], "nest");
154+
assert_eq!(0, cursors.get(&[("nest", 0)], "hello"));
155+
assert_eq!(1, cursors.get(&[("nest", 0)], "hello"));
156+
assert_eq!(2, cursors.get(&[("nest", 0)], "hello"));
157+
cursors.enter(&[], "nest");
158+
assert_eq!(0, cursors.get(&[("nest", 1)], "hello"));
159+
assert_eq!(1, cursors.get(&[("nest", 1)], "hello"));
160+
assert_eq!(2, cursors.get(&[("nest", 1)], "hello"));
161+
assert_eq!(3, cursors.get(&[], "hello"));
162+
}

‎src/lib.rs

+17-5
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ mod complete_gen;
173173
mod complete_run;
174174
#[cfg(feature = "autocomplete")]
175175
mod complete_shell;
176+
pub mod config;
176177
pub mod doc;
177178
mod error;
178179
mod from_os_str;
@@ -200,8 +201,8 @@ pub mod parsers {
200201
};
201202
#[doc(inline)]
202203
pub use crate::structs::{
203-
ParseCollect, ParseCon, ParseCount, ParseFallback, ParseFallbackWith, ParseLast, ParseMany,
204-
ParseOptional, ParseSome,
204+
ParseCollect, ParseCon, ParseCount, ParseEnter, ParseFallback, ParseFallbackWith,
205+
ParseLast, ParseMany, ParseOptional, ParseSome,
205206
};
206207
}
207208

@@ -222,9 +223,10 @@ use crate::{
222223
params::build_positional,
223224
parsers::{NamedArg, ParseAny, ParseCommand, ParsePositional},
224225
structs::{
225-
ParseCollect, ParseCount, ParseFail, ParseFallback, ParseFallbackWith, ParseGroupHelp,
226-
ParseGuard, ParseHide, ParseLast, ParseMany, ParseMap, ParseOptional, ParseOrElse,
227-
ParsePure, ParsePureWith, ParseSome, ParseUsage, ParseWith, ParseWithGroupHelp,
226+
ParseCollect, ParseCount, ParseEnter, ParseFail, ParseFallback, ParseFallbackWith,
227+
ParseGroupHelp, ParseGuard, ParseHide, ParseLast, ParseMany, ParseMap, ParseOptional,
228+
ParseOrElse, ParsePure, ParsePureWith, ParseSome, ParseUsage, ParseWith,
229+
ParseWithGroupHelp,
228230
},
229231
};
230232

@@ -869,6 +871,13 @@ pub trait Parser<T> {
869871
}
870872
// }}}
871873

874+
fn enter(self, name: &'static str) -> ParseEnter<Self>
875+
where
876+
Self: Sized + Parser<T>,
877+
{
878+
ParseEnter { inner: self, name }
879+
}
880+
872881
// combine
873882
// {{{ fallback
874883
/// Use this value as default if the value isn't present on a command line
@@ -1330,6 +1339,7 @@ pub fn short(short: char) -> NamedArg {
13301339
short: vec![short],
13311340
env: Vec::new(),
13321341
long: Vec::new(),
1342+
key: Vec::new(),
13331343
help: None,
13341344
}
13351345
}
@@ -1347,6 +1357,7 @@ pub fn long(long: &'static str) -> NamedArg {
13471357
short: Vec::new(),
13481358
long: vec![long],
13491359
env: Vec::new(),
1360+
key: Vec::new(),
13501361
help: None,
13511362
}
13521363
}
@@ -1376,6 +1387,7 @@ pub fn env(variable: &'static str) -> NamedArg {
13761387
short: Vec::new(),
13771388
long: Vec::new(),
13781389
help: None,
1390+
key: Vec::new(),
13791391
env: vec![variable],
13801392
}
13811393
}

‎src/params.rs

+16
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ pub struct NamedArg {
108108
pub(crate) short: Vec<char>,
109109
pub(crate) long: Vec<&'static str>,
110110
pub(crate) env: Vec<&'static str>,
111+
pub(crate) key: Vec<&'static str>,
111112
pub(crate) help: Option<Doc>,
112113
}
113114

@@ -161,6 +162,11 @@ impl NamedArg {
161162
self
162163
}
163164

165+
pub fn key(mut self, name: &'static str) -> Self {
166+
self.key.push(name);
167+
self
168+
}
169+
164170
/// Add a help message to a `flag`/`switch`/`argument`
165171
///
166172
/// `bpaf` converts doc comments and string into help by following those rules:
@@ -666,6 +672,14 @@ impl<T> ParseArgument<T> {
666672
return Ok(val);
667673
}
668674

675+
if let Some(config) = &mut args.config {
676+
for name in &self.named.key {
677+
if let Some(val) = config.get(name) {
678+
return Ok(val.into());
679+
}
680+
}
681+
}
682+
669683
if let Some(item) = self.item() {
670684
let missing = MissingItem {
671685
item,
@@ -675,6 +689,8 @@ impl<T> ParseArgument<T> {
675689
Err(Error(Message::Missing(vec![missing])))
676690
} else if let Some(name) = self.named.env.first() {
677691
Err(Error(Message::NoEnv(name)))
692+
} else if let Some(name) = self.named.key.first() {
693+
todo!("complain about name {:?}", name)
678694
} else {
679695
unreachable!()
680696
}

‎src/structs.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -723,8 +723,8 @@ where
723723
match parser.eval(args) {
724724
// we keep including values for as long as we consume values from the argument
725725
// list or at least one value
726-
Ok(val) => Ok(if args.len() < *len {
727-
*len = args.len();
726+
Ok(val) => Ok(if args.full_len() < *len {
727+
*len = args.full_len();
728728
Some(val)
729729
} else {
730730
None
@@ -1142,3 +1142,28 @@ impl<T> Parser<T> for Box<dyn Parser<T>> {
11421142
self.as_ref().meta()
11431143
}
11441144
}
1145+
1146+
pub struct ParseEnter<T> {
1147+
pub(crate) inner: T,
1148+
pub(crate) name: &'static str,
1149+
}
1150+
1151+
impl<T, P> Parser<T> for ParseEnter<P>
1152+
where
1153+
P: Parser<T>,
1154+
{
1155+
fn eval(&self, args: &mut State) -> Result<T, Error> {
1156+
if let Some(config) = &mut args.config {
1157+
config.enter(self.name);
1158+
}
1159+
let r = self.inner.eval(args);
1160+
if let Some(config) = &mut args.config {
1161+
config.exit();
1162+
}
1163+
r
1164+
}
1165+
1166+
fn meta(&self) -> Meta {
1167+
self.inner.meta()
1168+
}
1169+
}

‎tests/config.rs

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use bpaf::config::*;
2+
use bpaf::*;
3+
4+
struct DummyConfig(usize);
5+
6+
impl Config for DummyConfig {
7+
fn get(
8+
&self,
9+
path: &[(&'static str, usize)],
10+
name: &'static str,
11+
num: usize,
12+
) -> Option<String> {
13+
use std::fmt::Write;
14+
let mut res = String::new();
15+
16+
for (name, ix) in path {
17+
if *ix >= self.0 {
18+
return None;
19+
}
20+
write!(&mut res, "{}[{}].", name, ix).ok()?
21+
}
22+
if num >= self.0 {
23+
None
24+
} else {
25+
write!(&mut res, "{}[{}]", name, num).ok()?;
26+
Some(res)
27+
}
28+
}
29+
}
30+
31+
#[test]
32+
fn basic_config() {
33+
let cfg = [("name".into(), "Bob".into()), ("age".into(), "21".into())]
34+
.into_iter()
35+
.collect::<std::collections::BTreeMap<String, String>>();
36+
37+
let name = long("name").key("name").argument::<String>("NAME");
38+
39+
let age = long("age").key("age").argument::<usize>("AGE");
40+
let parser = construct!(name, age).to_options();
41+
42+
let args = Args::from(&[]).with_config(cfg.clone());
43+
let r = parser.run_inner(args).unwrap();
44+
assert_eq!(r.0, "Bob");
45+
assert_eq!(r.1, 21);
46+
}
47+
48+
#[test]
49+
fn many_enter() {
50+
let parser = long("name")
51+
.key("name")
52+
.argument::<String>("NAME")
53+
.many()
54+
.enter("group")
55+
.to_options();
56+
57+
let args = Args::from(&[]).with_config(DummyConfig(4));
58+
let r = parser.run_inner(args).unwrap();
59+
60+
assert_eq!(
61+
r,
62+
[
63+
"group[0].name[0]",
64+
"group[0].name[1]",
65+
"group[0].name[2]",
66+
"group[0].name[3]"
67+
]
68+
);
69+
}
70+
71+
#[test]
72+
fn enter_many() {
73+
let parser = long("name")
74+
.key("name")
75+
.argument::<String>("NAME")
76+
.enter("group")
77+
.many()
78+
.to_options();
79+
80+
let args = Args::from(&[]).with_config(DummyConfig(4));
81+
let r = parser.run_inner(args).unwrap();
82+
83+
assert_eq!(
84+
r,
85+
[
86+
"group[0].name[0]",
87+
"group[1].name[0]",
88+
"group[2].name[0]",
89+
"group[3].name[0]"
90+
]
91+
);
92+
}
93+
94+
#[test]
95+
fn many_enter_many() {
96+
let parser = long("name")
97+
.key("name")
98+
.argument::<String>("NAME")
99+
.many()
100+
.enter("group")
101+
.many()
102+
.map(|x| x.into_iter().flatten().collect::<Vec<_>>())
103+
.to_options();
104+
105+
let args = Args::from(&[]).with_config(DummyConfig(2));
106+
let r = parser.run_inner(args).unwrap();
107+
108+
assert_eq!(
109+
r,
110+
[
111+
"group[0].name[0]",
112+
"group[0].name[1]",
113+
"group[1].name[0]",
114+
"group[1].name[1]",
115+
]
116+
);
117+
}

0 commit comments

Comments
 (0)
Please sign in to comment.