Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of config parser #286

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions bpaf_derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ pub(crate) enum Name {
Long { name: Option<LitStr>, span: Span },
/// Enum variable, name must be specified
Env { name: Box<Expr> },
/// Value can also be consumed from a config key
Key { name: Option<LitStr>, span: Span },
}

impl StrictName {
Expand All @@ -165,6 +167,18 @@ impl StrictName {
None => return Err(Error::new(span, "Can't derive an explicit name for unnamed struct, try adding a name here like long(\"arg\")", ))
},
Name::Env { name, .. } => Self::Env { name },
Name::Key {
name: Some(name),..
} => StrictName::Key { name },
Name::Key {
name: None, span
} => match ident {
Some(name) => {
let derived_name = &to_kebab_case(&name.to_string());
Self::Key { name: LitStr::new(derived_name, span) }
}
None => return Err(Error::new(span, "Can't derive an explicit name for unnamed struct, try adding a name here like key(\"name\")", ))
},
})
}
}
Expand All @@ -174,6 +188,7 @@ pub(crate) enum StrictName {
Short { name: LitChar },
Long { name: LitStr },
Env { name: Box<Expr> },
Key { name: LitStr },
}

impl ToTokens for StrictName {
Expand All @@ -182,6 +197,7 @@ impl ToTokens for StrictName {
StrictName::Short { name } => quote!(short(#name)),
StrictName::Long { name } => quote!(long(#name)),
StrictName::Env { name } => quote!(env(#name)),
StrictName::Key { name } => quote!(key(#name)),
}
.to_tokens(tokens);
}
Expand Down Expand Up @@ -238,6 +254,7 @@ impl ToTokens for PostDecor {
PostDecor::Hide { .. } => quote!(hide()),
PostDecor::CustomUsage { usage, .. } => quote!(custom_usage(#usage)),
PostDecor::HideUsage { .. } => quote!(hide_usage()),
PostDecor::Enter { name, .. } => quote!(enter(#name)),
}
.to_tokens(tokens);
}
Expand Down Expand Up @@ -323,6 +340,10 @@ pub(crate) enum PostDecor {
HideUsage {
span: Span,
},
Enter {
name: Box<Expr>,
span: Span,
},
}
impl PostDecor {
fn span(&self) -> Span {
Expand All @@ -339,6 +360,7 @@ impl PostDecor {
| Self::Guard { span, .. }
| Self::Hide { span }
| Self::CustomUsage { span, .. }
| Self::Enter { span, .. }
| Self::HideUsage { span } => *span,
}
}
Expand Down Expand Up @@ -392,6 +414,13 @@ impl Name {
None
};
Name::Long { name, span }
} else if kw == "key" {
let name = if input.peek(token::Paren) {
Some(parse_lit_str(input)?)
} else {
None
};
Name::Key { name, span }
} else if kw == "env" {
let name = parse_expr(input)?;
Name::Env { name }
Expand Down Expand Up @@ -521,6 +550,9 @@ impl PostDecor {
} else if kw == "custom_usage" {
let usage = parse_arg(input)?;
Self::CustomUsage { usage, span }
} else if kw == "enter" {
let name = parse_expr(input)?;
Self::Enter { span, name }
} else {
return Ok(None);
}))
Expand Down
18 changes: 15 additions & 3 deletions bpaf_derive/src/field_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,25 @@ fn implicit_parser_custom_help() {
}

#[test]
fn short_long() {
fn short_long_key() {
let input: NamedField = parse_quote! {
#[bpaf(short, long)]
#[bpaf(short, long, key("num"))]
number: usize
};
let output = quote! {
::bpaf::short('n').long("number").argument::<usize>("ARG")
::bpaf::short('n').long("number").key("num").argument::<usize>("ARG")
};
assert_eq!(input.to_token_stream().to_string(), output.to_string());
}

#[test]
fn just_key() {
let input: NamedField = parse_quote! {
#[bpaf(key)]
number: usize
};
let output = quote! {
::bpaf::key("number").argument::<usize>("ARG")
};
assert_eq!(input.to_token_stream().to_string(), output.to_string());
}
Expand Down
21 changes: 21 additions & 0 deletions bpaf_derive/src/top_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ fn top_struct_construct() {
assert_eq!(top.to_token_stream().to_string(), expected.to_string());
}

#[test]
fn top_struct_enter() {
let top: Top = parse_quote! {
#[bpaf(enter("opt"))]
struct Opt { #[bpaf(enter("bar"))] verbose: bool }
};

let expected = quote! {
fn opt() -> impl ::bpaf::Parser<Opt> {
#[allow (unused_imports)]
use ::bpaf::Parser;
{
let verbose = ::bpaf::long("verbose").switch().enter("bar");
::bpaf::construct!(Opt { verbose, })
}.enter("opt")
}
};

assert_eq!(top.to_token_stream().to_string(), expected.to_string());
}

#[test]
fn top_enum_construct() {
let top: Top = parse_quote! {
Expand Down
27 changes: 25 additions & 2 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ffi::OsString;
use std::{ffi::OsString, rc::Rc};

pub(crate) use crate::arg::*;
use crate::{
Expand Down Expand Up @@ -37,6 +37,7 @@ pub struct Args<'a> {
name: Option<String>,
#[cfg(feature = "autocomplete")]
c_rev: Option<usize>,
config: Option<Rc<dyn crate::config::Config + 'static>>,
}

impl Args<'_> {
Expand Down Expand Up @@ -77,6 +78,12 @@ impl Args<'_> {
self.name = Some(name.to_owned());
self
}

#[must_use]
pub fn with_config(mut self, config: impl crate::config::Config + 'static) -> Self {
self.config = Some(Rc::new(config));
self
}
}

impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
Expand All @@ -86,6 +93,7 @@ impl<const N: usize> From<&'static [&'static str; N]> for Args<'_> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
config: None,
}
}
}
Expand All @@ -97,6 +105,7 @@ impl<'a> From<&'a [&'a std::ffi::OsStr]> for Args<'a> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
config: None,
}
}
}
Expand All @@ -108,6 +117,7 @@ impl<'a> From<&'a [&'a str]> for Args<'a> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
config: None,
}
}
}
Expand All @@ -119,6 +129,7 @@ impl<'a> From<&'a [String]> for Args<'a> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
config: None,
}
}
}
Expand All @@ -130,6 +141,7 @@ impl<'a> From<&'a [OsString]> for Args<'a> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name: None,
config: None,
}
}
}
Expand All @@ -150,6 +162,7 @@ impl Args<'_> {
#[cfg(feature = "autocomplete")]
c_rev: None,
name,
config: None,
}
}
}
Expand Down Expand Up @@ -245,7 +258,7 @@ pub use inner::State;
mod inner {
use std::{ops::Range, rc::Rc};

use crate::{error::Message, Args};
use crate::{config::ConfigReader, error::Message, Args};

use super::{split_os_argument, Arg, ArgType, ItemState};
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -278,6 +291,8 @@ mod inner {
/// scope starts on the right of the first consumed item and might end before the end
/// of the list, similarly for "commands"
scope: Range<usize>,

pub(crate) config: Option<ConfigReader>,
}

impl State {
Expand Down Expand Up @@ -400,6 +415,9 @@ mod inner {
if let Some(name) = args.name {
path.push(name);
}

let config = args.config.map(ConfigReader::new);

State {
item_state,
remaining,
Expand All @@ -409,6 +427,7 @@ mod inner {
path,
#[cfg(feature = "autocomplete")]
comp,
config,
}
}
}
Expand Down Expand Up @@ -472,6 +491,10 @@ mod inner {
self.remaining
}

pub(crate) fn full_len(&self) -> usize {
self.remaining + self.config.as_ref().map_or(0, |c| c.unconsumed())
}

/// Get an argument from a scope that was not consumed yet
pub(crate) fn get(&self, ix: usize) -> Option<&Arg> {
if self.scope.contains(&ix) && self.item_state.get(ix)?.present() {
Expand Down
Loading