aboutsummaryrefslogtreecommitdiff
path: root/structopt/structopt-derive/src/parse.rs
diff options
context:
space:
mode:
Diffstat (limited to 'structopt/structopt-derive/src/parse.rs')
-rw-r--r--structopt/structopt-derive/src/parse.rs304
1 files changed, 304 insertions, 0 deletions
diff --git a/structopt/structopt-derive/src/parse.rs b/structopt/structopt-derive/src/parse.rs
new file mode 100644
index 0000000..a704742
--- /dev/null
+++ b/structopt/structopt-derive/src/parse.rs
@@ -0,0 +1,304 @@
+use std::iter::FromIterator;
+
+use proc_macro_error::{abort, ResultExt};
+use quote::ToTokens;
+use syn::{
+ self, parenthesized,
+ parse::{Parse, ParseBuffer, ParseStream},
+ parse2,
+ punctuated::Punctuated,
+ spanned::Spanned,
+ Attribute, Expr, ExprLit, Ident, Lit, LitBool, LitStr, Token,
+};
+
+pub struct StructOptAttributes {
+ pub paren_token: syn::token::Paren,
+ pub attrs: Punctuated<StructOptAttr, Token![,]>,
+}
+
+impl Parse for StructOptAttributes {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let content;
+ let paren_token = parenthesized!(content in input);
+ let attrs = content.parse_terminated(StructOptAttr::parse)?;
+
+ Ok(StructOptAttributes { paren_token, attrs })
+ }
+}
+
+pub enum StructOptAttr {
+ // single-identifier attributes
+ Short(Ident),
+ Long(Ident),
+ Env(Ident),
+ Flatten(Ident),
+ Subcommand(Ident),
+ NoVersion(Ident),
+ VerbatimDocComment(Ident),
+
+ // ident [= "string literal"]
+ About(Ident, Option<LitStr>),
+ Author(Ident, Option<LitStr>),
+
+ // ident = "string literal"
+ Version(Ident, LitStr),
+ RenameAllEnv(Ident, LitStr),
+ RenameAll(Ident, LitStr),
+ NameLitStr(Ident, LitStr),
+
+ // parse(parser_kind [= parser_func])
+ Parse(Ident, ParserSpec),
+
+ // ident [= arbitrary_expr]
+ Skip(Ident, Option<Expr>),
+
+ // ident = arbitrary_expr
+ NameExpr(Ident, Expr),
+
+ // ident(arbitrary_expr,*)
+ MethodCall(Ident, Vec<Expr>),
+}
+
+impl Parse for StructOptAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ use self::StructOptAttr::*;
+
+ let name: Ident = input.parse()?;
+ let name_str = name.to_string();
+
+ if input.peek(Token![=]) {
+ // `name = value` attributes.
+ let assign_token = input.parse::<Token![=]>()?; // skip '='
+
+ if input.peek(LitStr) {
+ let lit: LitStr = input.parse()?;
+ let lit_str = lit.value();
+
+ let check_empty_lit = |s| {
+ if lit_str.is_empty() {
+ abort!(
+ lit.span(),
+ "`#[structopt({} = \"\")]` is deprecated in structopt 0.3, \
+ now it's default behavior",
+ s
+ );
+ }
+ };
+
+ match &*name_str.to_string() {
+ "rename_all" => Ok(RenameAll(name, lit)),
+ "rename_all_env" => Ok(RenameAllEnv(name, lit)),
+
+ "version" => {
+ check_empty_lit("version");
+ Ok(Version(name, lit))
+ }
+
+ "author" => {
+ check_empty_lit("author");
+ Ok(Author(name, Some(lit)))
+ }
+
+ "about" => {
+ check_empty_lit("about");
+ Ok(About(name, Some(lit)))
+ }
+
+ "skip" => {
+ let expr = ExprLit {
+ attrs: vec![],
+ lit: Lit::Str(lit),
+ };
+ let expr = Expr::Lit(expr);
+ Ok(Skip(name, Some(expr)))
+ }
+
+ _ => Ok(NameLitStr(name, lit)),
+ }
+ } else {
+ match input.parse::<Expr>() {
+ Ok(expr) => {
+ if name_str == "skip" {
+ Ok(Skip(name, Some(expr)))
+ } else {
+ Ok(NameExpr(name, expr))
+ }
+ }
+
+ Err(_) => abort! {
+ assign_token.span(),
+ "expected `string literal` or `expression` after `=`"
+ },
+ }
+ }
+ } else if input.peek(syn::token::Paren) {
+ // `name(...)` attributes.
+ let nested;
+ parenthesized!(nested in input);
+
+ match name_str.as_ref() {
+ "parse" => {
+ let parser_specs: Punctuated<ParserSpec, Token![,]> =
+ nested.parse_terminated(ParserSpec::parse)?;
+
+ if parser_specs.len() == 1 {
+ Ok(Parse(name, parser_specs[0].clone()))
+ } else {
+ abort!(name.span(), "parse must have exactly one argument")
+ }
+ }
+
+ "raw" => match nested.parse::<LitBool>() {
+ Ok(bool_token) => {
+ let expr = ExprLit {
+ attrs: vec![],
+ lit: Lit::Bool(bool_token),
+ };
+ let expr = Expr::Lit(expr);
+ Ok(MethodCall(name, vec![expr]))
+ }
+
+ Err(_) => {
+ abort!(name.span(),
+ "`#[structopt(raw(...))` attributes are removed in structopt 0.3, \
+ they are replaced with raw methods";
+ help = "if you meant to call `clap::Arg::raw()` method \
+ you should use bool literal, like `raw(true)` or `raw(false)`";
+ note = raw_method_suggestion(nested);
+ );
+ }
+ },
+
+ _ => {
+ let method_args: Punctuated<_, Token![,]> =
+ nested.parse_terminated(Expr::parse)?;
+ Ok(MethodCall(name, Vec::from_iter(method_args)))
+ }
+ }
+ } else {
+ // Attributes represented with a sole identifier.
+ match name_str.as_ref() {
+ "long" => Ok(Long(name)),
+ "short" => Ok(Short(name)),
+ "env" => Ok(Env(name)),
+ "flatten" => Ok(Flatten(name)),
+ "subcommand" => Ok(Subcommand(name)),
+ "no_version" => Ok(NoVersion(name)),
+ "verbatim_doc_comment" => Ok(VerbatimDocComment(name)),
+
+ "about" => (Ok(About(name, None))),
+ "author" => (Ok(Author(name, None))),
+
+ "skip" => Ok(Skip(name, None)),
+
+ "version" => abort!(
+ name.span(),
+ "#[structopt(version)] is invalid attribute, \
+ structopt 0.3 inherits version from Cargo.toml by default, \
+ no attribute needed"
+ ),
+
+ _ => abort!(name.span(), "unexpected attribute: {}", name_str),
+ }
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct ParserSpec {
+ pub kind: Ident,
+ pub eq_token: Option<Token![=]>,
+ pub parse_func: Option<Expr>,
+}
+
+impl Parse for ParserSpec {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let kind = input
+ .parse()
+ .map_err(|_| input.error("parser specification must start with identifier"))?;
+ let eq_token = input.parse()?;
+ let parse_func = match eq_token {
+ None => None,
+ Some(_) => Some(input.parse()?),
+ };
+ Ok(ParserSpec {
+ kind,
+ eq_token,
+ parse_func,
+ })
+ }
+}
+
+struct CommaSeparated<T>(Punctuated<T, Token![,]>);
+
+impl<T: Parse> Parse for CommaSeparated<T> {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let res = Punctuated::parse_separated_nonempty(input)?;
+ Ok(CommaSeparated(res))
+ }
+}
+
+fn raw_method_suggestion(ts: ParseBuffer) -> String {
+ let do_parse = move || -> Result<(Ident, CommaSeparated<Expr>), syn::Error> {
+ let name = ts.parse()?;
+ let _eq: Token![=] = ts.parse()?;
+ let val: LitStr = ts.parse()?;
+ Ok((name, syn::parse_str(&val.value())?))
+ };
+
+ fn to_string<T: ToTokens>(val: &T) -> String {
+ val.to_token_stream()
+ .to_string()
+ .replace(" ", "")
+ .replace(",", ", ")
+ }
+
+ if let Ok((name, val)) = do_parse() {
+ let exprs = val.0;
+ let suggestion = if exprs.len() == 1 {
+ let val = to_string(&exprs[0]);
+ format!(" = {}", val)
+ } else {
+ let val = exprs
+ .into_iter()
+ .map(|expr| to_string(&expr))
+ .collect::<Vec<_>>()
+ .join(", ");
+
+ format!("({})", val)
+ };
+
+ format!(
+ "if you need to call `clap::Arg/App::{}` method you \
+ can do it like this: #[structopt({}{})]",
+ name, name, suggestion
+ )
+ } else {
+ "if you need to call some method from `clap::Arg/App` \
+ you should use raw method, see \
+ https://docs.rs/structopt/0.3/structopt/#raw-methods"
+ .into()
+ }
+}
+
+pub fn parse_structopt_attributes(all_attrs: &[Attribute]) -> Vec<StructOptAttr> {
+ all_attrs
+ .iter()
+ .filter(|attr| attr.path.is_ident("structopt"))
+ .flat_map(|attr| {
+ let attrs: StructOptAttributes = parse2(attr.tokens.clone())
+ .map_err(|e| match &*e.to_string() {
+ // this error message is misleading and points to Span::call_site()
+ // so we patch it with something meaningful
+ "unexpected end of input, expected parentheses" => {
+ let span = attr.path.span();
+ let patch_msg = "expected parentheses after `structopt`";
+ syn::Error::new(span, patch_msg)
+ }
+ _ => e,
+ })
+ .unwrap_or_abort();
+ attrs.attrs
+ })
+ .collect()
+}