800 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			800 lines
		
	
	
		
			33 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| /* 
 | |
|  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
 | |
|  * SPDX-License-Identifier: LGPL-3.0-or-later
 | |
|  *
 | |
|  * This file is part of SPD.
 | |
|  *
 | |
|  * SPD is free software: you can redistribute it and/or modify it under the
 | |
|  * terms of the GNU Lesser General Public License as published by the Free
 | |
|  * Software Foundation, either version 3 of the License, or (at your option) any
 | |
|  * later version.
 | |
|  *
 | |
|  * SPD is distributed in the hope that it will be useful, but WITHOUT ANY
 | |
|  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | |
|  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 | |
|  * details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public License
 | |
|  * along with SPD. If not, see <https://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * This file incorporates work covered by the following copyright and permission
 | |
|  * notice:
 | |
|  *
 | |
|  *     Copyright 2017 Douman
 | |
|  *
 | |
|  *     Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  *     you may not use this file except in compliance with the License.
 | |
|  *     You may obtain a copy of the License at
 | |
|  *
 | |
|  *         http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  *     Unless required by applicable law or agreed to in writing, software
 | |
|  *     distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  *     See the License for the specific language governing permissions and
 | |
|  *     limitations under the License.
 | |
|  */
 | |
| 
 | |
| 
 | |
| //! Command line argument parser derive
 | |
| 
 | |
| extern crate alloc;
 | |
| extern crate proc_macro;
 | |
| 
 | |
| mod utils;
 | |
| use utils::*;
 | |
| 
 | |
| use crate::println;
 | |
| 
 | |
| use proc_macro::TokenStream;
 | |
| use quote::quote;
 | |
| 
 | |
| use alloc::{
 | |
|     string::String,
 | |
|     borrow::ToOwned,
 | |
|     vec,
 | |
|     vec::*,
 | |
| };
 | |
| use core::fmt::Write;
 | |
| 
 | |
| struct Argument {
 | |
|     field_name: String,
 | |
|     name: String,
 | |
|     desc: String,
 | |
|     required: bool,
 | |
|     is_optional: bool,
 | |
|     default: Option<String>,
 | |
| }
 | |
| 
 | |
| #[derive(PartialEq, Eq, Debug)]
 | |
| enum OptValueType {
 | |
|     Help,
 | |
|     Bool,
 | |
|     Value,
 | |
|     MultiValue,
 | |
| }
 | |
| 
 | |
| struct Opt {
 | |
|     arg: Argument,
 | |
|     long: String,
 | |
|     short: Option<String>,
 | |
|     typ: OptValueType,
 | |
| }
 | |
| 
 | |
| struct Command {
 | |
|     variant_name: String,
 | |
|     command_name: String,
 | |
|     desc: String,
 | |
| }
 | |
| 
 | |
| const FROM_FN: &str = "core::str::FromStr::from_str";
 | |
| const TAB: &str = "    ";
 | |
| const PARSER_TRAIT: &str = "arg::Args";
 | |
| const DEFAULT_INIT: &str = "Default::default()";
 | |
| const INVALID_ARG_TYPE_STRING: &str = "Attribute accepts only str";
 | |
| const INVALID_REQUIRED_BOOL: &str = "Attribute required cannot be applied to bool switch";
 | |
| const UNKNOWN_ARG_ATTR: &str = "Unknown attribute is used";
 | |
| const ARG_INVALID_CHARS: &[char] = &[' ', '\t'];
 | |
| const ARG_NAME_SPACE_ERROR: &str = "Name contains space character";
 | |
| 
 | |
| fn parse_segment(segment: &syn::PathSegment) -> OptValueType {
 | |
|     if segment.ident == "bool" {
 | |
|         OptValueType::Bool
 | |
|     } else if segment.ident == "Vec" {
 | |
|         OptValueType::MultiValue
 | |
|     } else {
 | |
|         OptValueType::Value
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn from_enum(ast: &syn::DeriveInput, payload: &syn::DataEnum) -> TokenStream {
 | |
|     let mut about_prog = String::new();
 | |
|     for attr in ast.attrs.iter() {
 | |
|         let attr = match attr.parse_meta() {
 | |
|             Ok(attr) => attr,
 | |
|             Err(error) => {
 | |
|                 return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         match attr {
 | |
|             syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
 | |
|                 if let syn::Lit::Str(ref text) = value.lit {
 | |
|                     about_prog.push_str(&text.value());
 | |
|                     about_prog.push_str("\n");
 | |
|                 }
 | |
|             } else {
 | |
|             },
 | |
|             _ => (),
 | |
|         }
 | |
|     }
 | |
|     about_prog.pop();
 | |
| 
 | |
|     let mut commands = Vec::new();
 | |
|     for variant in payload.variants.iter() {
 | |
|         let mut desc = String::new();
 | |
|         let variant_name = variant.ident.to_string();
 | |
|         if variant_name.is_empty() {
 | |
|             return syn::Error::new_spanned(&variant.ident, "Oi, mate, You cannot have enum variant without name").to_compile_error().into()
 | |
|         }
 | |
|         let command_name = to_hyphenated_lower_case(&variant_name);
 | |
|         if command_name.eq_ignore_ascii_case("help") {
 | |
|             return syn::Error::new_spanned(&variant.ident, "Oi, mate, You cannot use variant 'Help'").to_compile_error().into()
 | |
|         }
 | |
| 
 | |
|         for attr in variant.attrs.iter() {
 | |
|             let attr = match attr.parse_meta() {
 | |
|                 Ok(attr) => attr,
 | |
|                 Err(error) => {
 | |
|                     return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             match attr {
 | |
|                 syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
 | |
|                     if let syn::Lit::Str(ref text) = value.lit {
 | |
|                         desc.push_str(&text.value());
 | |
|                         desc.push_str(" ");
 | |
|                     }
 | |
|                 },
 | |
|                 _ => continue
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         let field = match &variant.fields {
 | |
|             syn::Fields::Unit => return syn::Error::new_spanned(&variant.fields, "Unit variant cannot be used").to_compile_error().into(),
 | |
|             syn::Fields::Named(_) => return syn::Error::new_spanned(&variant.fields, "I'm too lazy to support named variant").to_compile_error().into(),
 | |
|             syn::Fields::Unnamed(fields) => {
 | |
|                 if fields.unnamed.empty_or_trailing() {
 | |
|                     return syn::Error::new_spanned(&fields, "MUST specify single field").to_compile_error().into();
 | |
|                 } else if fields.unnamed.len() > 1 {
 | |
|                     return syn::Error::new_spanned(&fields, "MUST not specify more than 1 field").to_compile_error().into();
 | |
|                 } else {
 | |
|                     fields.unnamed.first().unwrap()
 | |
|                 }
 | |
|             },
 | |
|         };
 | |
| 
 | |
|         match &field.ty {
 | |
|             syn::Type::Path(ref ty) => {
 | |
|                 let ty = ty.path.segments.last().expect("To have at least one segment");
 | |
|                 if ty.ident == "Option" {
 | |
|                     return syn::Error::new_spanned(&ty, "Command cannot be optional").to_compile_error().into()
 | |
|                 } else {
 | |
|                     match parse_segment(ty) {
 | |
|                         OptValueType::Bool => return syn::Error::new_spanned(&ty, "Command value cannot be boolean").to_compile_error().into(),
 | |
|                         OptValueType::MultiValue => return syn::Error::new_spanned(&ty, "Command value Vec<_>").to_compile_error().into(),
 | |
|                         _ => (),
 | |
|                     }
 | |
|                 }
 | |
|             },
 | |
|             ty => {
 | |
|                 return syn::Error::new_spanned(&ty, "Expected simple ident or path").to_compile_error().into()
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         commands.push(Command {
 | |
|             command_name,
 | |
|             variant_name,
 | |
|             desc
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     if commands.is_empty() {
 | |
|         return syn::Error::new_spanned(&ast, "Enum must have at least one variant").to_compile_error().into()
 | |
|     }
 | |
| 
 | |
|     let (impl_gen, type_gen, where_clause) = ast.generics.split_for_impl();
 | |
| 
 | |
|     let help_msg = {
 | |
|         use std::io::Write;
 | |
|         use tabwriter::TabWriter;
 | |
| 
 | |
|         let mut tw = TabWriter::new(vec![]);
 | |
| 
 | |
|         let _ = write!(tw, "COMMANDS:\n");
 | |
|         for command in commands.iter() {
 | |
|             let _ = write!(tw, "\t{}\t{}\n", command.command_name, command.desc);
 | |
|         }
 | |
| 
 | |
|         let _ = tw.flush();
 | |
| 
 | |
|         String::from_utf8(tw.into_inner().unwrap()).unwrap()
 | |
|     };
 | |
| 
 | |
|     let mut result = String::new();
 | |
|     let _ = writeln!(result, "{} {} for {}{} {{", quote!(impl #impl_gen), PARSER_TRAIT, ast.ident, quote!(#type_gen #where_clause));
 | |
| 
 | |
|     let _ = writeln!(result, "{}const HELP: &'static str = \"{}\";", TAB, help_msg);
 | |
| 
 | |
|     //from_args START
 | |
|     let _ = writeln!(result, "{}fn from_args<'a, T: IntoIterator<Item = &'a str>>(_args_: T) -> Result<Self, arg::ParseKind<'a>> {{", TAB);
 | |
| 
 | |
|     let _ = writeln!(result, "{0}{0}let mut _args_ = _args_.into_iter();\n", TAB);
 | |
| 
 | |
|     //args START
 | |
|     let _ = writeln!(result, "{0}{0}while let Some(_arg_) = _args_.next() {{", TAB);
 | |
| 
 | |
|     //help
 | |
|     let _ = writeln!(result, "{0}{0}{0}if _arg_.eq_ignore_ascii_case(\"help\") {{", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}return Err(arg::ParseKind::Top(arg::ParseError::HelpRequested(Self::HELP)));", TAB);
 | |
|     let _ = write!(result, "{0}{0}{0}}}", TAB);
 | |
| 
 | |
|     for command in commands.iter() {
 | |
|         //arg START
 | |
|         let _ = writeln!(result, " else if _arg_.eq_ignore_ascii_case(\"{}\") {{", command.command_name);
 | |
| 
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}match {1}::from_args(_args_) {{", TAB, PARSER_TRAIT);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(res) => return Ok(Self::{1}(res)),", TAB, command.variant_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Top(error)) => return Err(arg::ParseKind::Sub(\"{1}\", error)),", TAB, command.command_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Sub(name, error)) => return Err(arg::ParseKind::Sub(name, error)),", TAB);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
 | |
| 
 | |
|         //arg END
 | |
|         let _ = write!(result, "{0}{0}{0}}}", TAB);
 | |
|     }
 | |
| 
 | |
|     //args END
 | |
|     let _ = writeln!(result, "\n{0}{0}}}", TAB);
 | |
| 
 | |
|     let _ = writeln!(result, "{0}{0}Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"command\")))", TAB);
 | |
| 
 | |
|     //from_args END
 | |
|     let _ = writeln!(result, "{}}}", TAB);
 | |
| 
 | |
|     let _ = writeln!(result, "}}");
 | |
| 
 | |
|     if let Ok(val) = std::env::var("ARG_RS_PRINT_PARSER") {
 | |
|         match val.trim() {
 | |
|             "0" | "false" => (),
 | |
|             _ => println!("{result}"),
 | |
|         }
 | |
|     }
 | |
|     result.parse().expect("To parse generated code")
 | |
| }
 | |
| 
 | |
| fn from_struct(ast: &syn::DeriveInput, payload: &syn::DataStruct) -> TokenStream {
 | |
|     let mut about_prog = String::new();
 | |
|     for attr in ast.attrs.iter() {
 | |
|         let attr = match attr.parse_meta() {
 | |
|             Ok(attr) => attr,
 | |
|             Err(error) => {
 | |
|                 return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         match attr {
 | |
|             syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
 | |
|                 if let syn::Lit::Str(ref text) = value.lit {
 | |
|                     about_prog.push_str(&text.value());
 | |
|                     about_prog.push_str("\n");
 | |
|                 }
 | |
|             } else {
 | |
|             },
 | |
|             _ => (),
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     about_prog.pop();
 | |
| 
 | |
|     let mut options = Vec::new();
 | |
|     let mut arguments = Vec::new();
 | |
| 
 | |
|     options.push(Opt {
 | |
|         arg: Argument {
 | |
|             field_name: "_".to_owned(),
 | |
|             name: "help".to_owned(),
 | |
|             desc: "Prints this help information".to_owned(),
 | |
|             required: false,
 | |
|             is_optional: false,
 | |
|             default: None,
 | |
|         },
 | |
|         short: Some("h".to_owned()),
 | |
|         long: "help".to_owned(),
 | |
|         typ: OptValueType::Help,
 | |
|     });
 | |
| 
 | |
|     let mut sub_command = None;
 | |
|     let mut multi_argument = None;
 | |
| 
 | |
|     for field in payload.fields.iter() {
 | |
|         let field_name = field.ident.as_ref().unwrap().to_string();
 | |
|         let name = field.ident.as_ref().unwrap().to_string().trim_matches(|ch| !char::is_alphanumeric(ch)).to_owned();
 | |
|         let mut desc = String::new();
 | |
|         let mut short = None;
 | |
|         let mut long = None;
 | |
|         let mut required = false;
 | |
|         let mut is_sub = false;
 | |
| 
 | |
|         let (is_optional, typ) = match field.ty {
 | |
|             syn::Type::Path(ref ty) => {
 | |
|                 let ty = ty.path.segments.last().expect("To have at least one segment");
 | |
| 
 | |
|                 if ty.ident == "Option" {
 | |
|                     let ty = match &ty.arguments {
 | |
|                         syn::PathArguments::AngleBracketed(ref args) => match args.args.len() {
 | |
|                             0 => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option is without type arguments. Fix it").to_compile_error().into(),
 | |
|                             1 => match args.args.first().unwrap() {
 | |
|                                 syn::GenericArgument::Type(syn::Type::Path(ty)) => parse_segment(ty.path.segments.last().expect("To have at least one segment")),
 | |
|                                 _ => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option should have type argument, but got some other shite. Fix it").to_compile_error().into(),
 | |
|                             },
 | |
|                             _ => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option has too many type arguments. Fix it").to_compile_error().into()
 | |
|                         },
 | |
|                         syn::PathArguments::None => return syn::Error::new_spanned(&ty.ident, "Oi, mate, Option is without type arguments. Fix it").to_compile_error().into(),
 | |
|                         syn::PathArguments::Parenthesized(_) => return syn::Error::new_spanned(&ty.ident, "Oi, mate, you got wrong brackets for your Option . Fix it").to_compile_error().into(),
 | |
|                     };
 | |
| 
 | |
|                     (true, ty)
 | |
|                 } else {
 | |
|                     (false, parse_segment(ty))
 | |
|                 }
 | |
|             },
 | |
|             _ => (false, OptValueType::Value),
 | |
|         };
 | |
| 
 | |
|         if is_optional && typ == OptValueType::MultiValue {
 | |
|             return syn::Error::new_spanned(field, "Option<Vec<_>> makes no sense. Just use plain Vec<_>").to_compile_error().into();
 | |
|         }
 | |
| 
 | |
|         let mut default = None;
 | |
| 
 | |
|         for attr in field.attrs.iter() {
 | |
|             let attr = match attr.parse_meta() {
 | |
|                 Ok(attr) => attr,
 | |
|                 Err(error) => {
 | |
|                     return syn::Error::new_spanned(attr, format!("cannot parse attribute: {error}")).to_compile_error().into()
 | |
|                 }
 | |
|             };
 | |
| 
 | |
|             match attr {
 | |
|                 syn::Meta::NameValue(value) => if value.path.is_ident("doc") {
 | |
|                     if let syn::Lit::Str(ref text) = value.lit {
 | |
|                         desc.push_str(&text.value());
 | |
|                         desc.push_str(" ");
 | |
|                     }
 | |
|                 },
 | |
|                 syn::Meta::List(value) => if value.path.is_ident("arg") {
 | |
|                     for value_attr in value.nested.iter() {
 | |
|                         match value_attr {
 | |
|                             syn::NestedMeta::Meta(value_attr) => {
 | |
|                                 match value_attr {
 | |
|                                     syn::Meta::Path(value_attr) => if value_attr.is_ident("short") {
 | |
|                                         short = Some(format!("{}", name.chars().next().unwrap()).to_lowercase());
 | |
|                                     } else if value_attr.is_ident("long") {
 | |
|                                         long = Some(name.to_lowercase());
 | |
|                                     } else if value_attr.is_ident("default_value") {
 | |
|                                         default = Some(DEFAULT_INIT.to_owned());
 | |
|                                     } else if value_attr.is_ident("required") {
 | |
|                                         if typ != OptValueType::Bool {
 | |
|                                             required = true
 | |
|                                         } else {
 | |
|                                             return syn::Error::new_spanned(value_attr, INVALID_REQUIRED_BOOL).to_compile_error().into();
 | |
|                                         }
 | |
|                                     } else if value_attr.is_ident("sub") {
 | |
|                                         if typ == OptValueType::Value {
 | |
|                                             is_sub = true;
 | |
|                                         } else {
 | |
|                                             return syn::Error::new_spanned(value_attr, "Sub-command must be simple value").to_compile_error().into();
 | |
|                                         }
 | |
|                                     }
 | |
|                                     syn::Meta::NameValue(value_attr) => if value_attr.path.is_ident("short") {
 | |
|                                         if let syn::Lit::Str(ref text) = value_attr.lit {
 | |
|                                             let value_attr_text = text.value();
 | |
| 
 | |
|                                             if value_attr_text.contains(ARG_INVALID_CHARS) {
 | |
|                                                 return syn::Error::new_spanned(value_attr.lit.clone(), ARG_NAME_SPACE_ERROR).to_compile_error().into();
 | |
|                                             }
 | |
| 
 | |
|                                             short = Some(value_attr_text);
 | |
|                                         } else {
 | |
|                                             return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
 | |
|                                         }
 | |
|                                     } else if value_attr.path.is_ident("long") {
 | |
|                                         if let syn::Lit::Str(ref text) = value_attr.lit {
 | |
|                                             let value_attr_text = text.value();
 | |
| 
 | |
|                                             if value_attr_text.contains(ARG_INVALID_CHARS) {
 | |
|                                                 return syn::Error::new_spanned(value_attr.lit.clone(), ARG_NAME_SPACE_ERROR).to_compile_error().into();
 | |
|                                             }
 | |
| 
 | |
|                                             long = Some(value_attr_text)
 | |
|                                         } else {
 | |
|                                             return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
 | |
|                                         }
 | |
|                                     } else if value_attr.path.is_ident("default_value") {
 | |
|                                         if let syn::Lit::Str(ref text) = value_attr.lit {
 | |
|                                             default = Some(text.value());
 | |
|                                         } else {
 | |
|                                             return syn::Error::new_spanned(value_attr.path.clone(), INVALID_ARG_TYPE_STRING).to_compile_error().into();
 | |
|                                         }
 | |
|                                     } else {
 | |
|                                         return syn::Error::new_spanned(value_attr.path.clone(), UNKNOWN_ARG_ATTR).to_compile_error().into();
 | |
|                                     }
 | |
|                                     _ => {
 | |
|                                     },
 | |
|                                 }
 | |
|                             },
 | |
|                             syn::NestedMeta::Lit(_) => (),
 | |
|                         }
 | |
|                     }
 | |
|                 },
 | |
|                 _ => (),
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         desc.pop();
 | |
| 
 | |
|         if required && default.is_some() {
 | |
|             return syn::Error::new_spanned(field.ident.clone(), "Marked as required, but default value is provided?").to_compile_error().into();
 | |
|         } else if is_optional && default.is_some() {
 | |
|             return syn::Error::new_spanned(field.ident.clone(), "Optional, but default value is provided?").to_compile_error().into();
 | |
|         } else if is_sub && is_optional {
 | |
|             return syn::Error::new_spanned(field.ident.clone(), "Sub-command cannot be optional").to_compile_error().into();
 | |
|         } else if is_sub && default.is_some() {
 | |
|             return syn::Error::new_spanned(field.ident.clone(), "Sub-command cannot have default value").to_compile_error().into();
 | |
|         } else if !required && !is_optional && default.is_none() {
 | |
|             default = Some(DEFAULT_INIT.to_owned());
 | |
|         }
 | |
| 
 | |
|         if short.is_none() && long.is_none() {
 | |
|             if typ == OptValueType::MultiValue {
 | |
|                 if multi_argument.is_some() {
 | |
|                     return syn::Error::new_spanned(field.ident.clone(), "Second argument collection. There can be only one").to_compile_error().into();
 | |
|                 } else if sub_command.is_some() {
 | |
|                     return syn::Error::new_spanned(field.ident.clone(), "Multi-argument collection and sub-command are mutually exclusive").to_compile_error().into();
 | |
|                 }
 | |
| 
 | |
|                 multi_argument = Some(Argument {
 | |
|                     field_name,
 | |
|                     name,
 | |
|                     desc,
 | |
|                     required,
 | |
|                     is_optional,
 | |
|                     default,
 | |
|                 });
 | |
| 
 | |
|             } else if is_sub {
 | |
|                 if sub_command.is_some() {
 | |
|                     return syn::Error::new_spanned(field.ident.clone(), "Second sub-command. There can be only one").to_compile_error().into();
 | |
|                 } else if multi_argument.is_some() {
 | |
|                     return syn::Error::new_spanned(field.ident.clone(), "Sub-command and multi-argument collection are mutually exclusive").to_compile_error().into();
 | |
|                 }
 | |
| 
 | |
|                 sub_command = Some(Argument {
 | |
|                     field_name,
 | |
|                     name,
 | |
|                     desc,
 | |
|                     required: true,
 | |
|                     is_optional: false,
 | |
|                     default: None,
 | |
|                 });
 | |
|             } else {
 | |
|                 arguments.push(Argument {
 | |
|                     field_name,
 | |
|                     name,
 | |
|                     desc,
 | |
|                     required,
 | |
|                     is_optional,
 | |
|                     default,
 | |
|                 })
 | |
|             }
 | |
| 
 | |
|         } else {
 | |
|             let long = match long {
 | |
|                 Some(long) => long,
 | |
|                 None => name.clone()
 | |
|             };
 | |
| 
 | |
|             options.push(Opt {
 | |
|                 arg: Argument {
 | |
|                     field_name,
 | |
|                     name,
 | |
|                     desc,
 | |
|                     required,
 | |
|                     is_optional,
 | |
|                     default,
 | |
|                 },
 | |
|                 short,
 | |
|                 long,
 | |
|                 typ
 | |
|             })
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     let (impl_gen, type_gen, where_clause) = ast.generics.split_for_impl();
 | |
| 
 | |
|     let help_msg = {
 | |
|         use std::io::Write;
 | |
|         use tabwriter::TabWriter;
 | |
| 
 | |
|         let mut tw = TabWriter::new(vec![]);
 | |
| 
 | |
|         let _ = write!(tw, "{}
 | |
| USAGE:", about_prog);
 | |
| 
 | |
|         if !options.is_empty() {
 | |
|             let _ = write!(tw, " [OPTIONS]");
 | |
|         }
 | |
| 
 | |
|         for argument in arguments.iter() {
 | |
|             let _ = if argument.required {
 | |
|                 write!(tw, " <{}>", argument.name)
 | |
|             } else {
 | |
|                 write!(tw, " [{}]", argument.name)
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         if let Some(argument) = multi_argument.as_ref() {
 | |
|             let _ = if argument.required {
 | |
|                 write!(tw, " <{}>...", argument.name)
 | |
|             } else {
 | |
|                 write!(tw, " [{}]...", argument.name)
 | |
|             };
 | |
|         } else if let Some(argument) = sub_command.as_ref() {
 | |
|             let _ = write!(tw, " <{}>", argument.name);
 | |
|         }
 | |
| 
 | |
|         if !options.is_empty() {
 | |
|             let _ = write!(tw, "\n\nOPTIONS:\n");
 | |
|         }
 | |
| 
 | |
|         for option in options.iter() {
 | |
|             let _ = write!(tw, "\t");
 | |
|             if let Some(short) = option.short.as_ref() {
 | |
|                 let _ = write!(tw, "-{},", short);
 | |
|             }
 | |
|             let _ = write!(tw, "\t");
 | |
| 
 | |
|             let _ = write!(tw, "--{}", option.long);
 | |
| 
 | |
|             let _ = match option.typ {
 | |
|                 OptValueType::MultiValue => write!(tw, " <{}>...", option.arg.name),
 | |
|                 OptValueType::Value => write!(tw, " <{}>", option.arg.name),
 | |
|                 _ => Ok(()),
 | |
|             };
 | |
| 
 | |
|             let _ = write!(tw, "\t{}\n", option.arg.desc);
 | |
|         }
 | |
| 
 | |
|         if !arguments.is_empty() || multi_argument.is_some() || sub_command.is_some() {
 | |
|             let _ = write!(tw, "\nARGS:\n");
 | |
|         }
 | |
| 
 | |
|         for argument in arguments.iter() {
 | |
|             let _ = if argument.required {
 | |
|                 writeln!(tw, "\t<{}>\t{}", argument.name, argument.desc)
 | |
|             } else {
 | |
|                 writeln!(tw, "\t[{}]\t{}", argument.name, argument.desc)
 | |
|             };
 | |
|         }
 | |
| 
 | |
|         if let Some(argument) = multi_argument.as_ref() {
 | |
|             let _ = writeln!(tw, "\t<{}>...\t{}", argument.name, argument.desc);
 | |
|         } else if let Some(command) = sub_command.as_ref() {
 | |
|             let _ = writeln!(tw, "\t<{}>\t{}", command.name, command.desc);
 | |
|         }
 | |
| 
 | |
|         let _ = tw.flush();
 | |
| 
 | |
|         String::from_utf8(tw.into_inner().unwrap()).unwrap()
 | |
|     };
 | |
| 
 | |
|     let mut result = String::new();
 | |
|     let _ = writeln!(result, "{} {} for {}{} {{", quote!(impl #impl_gen), PARSER_TRAIT, ast.ident, quote!(#type_gen #where_clause));
 | |
|     let _ = writeln!(result, "{}const HELP: &'static str = \"{}\";", TAB, help_msg);
 | |
| 
 | |
|     let _ = writeln!(result, "{}fn from_args<'a, T: IntoIterator<Item = &'a str>>(_args_: T) -> Result<Self, arg::ParseKind<'a>> {{", TAB);
 | |
| 
 | |
|     for option in options.iter() {
 | |
|         if option.arg.field_name == "_" {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         let _ = match option.typ {
 | |
|             OptValueType::MultiValue => writeln!(result, "{0}{0}let mut {1} = Vec::new();", TAB, option.arg.field_name),
 | |
|             OptValueType::Bool => writeln!(result, "{0}{0}let mut {1} = false;", TAB, option.arg.field_name),
 | |
|             _ => writeln!(result, "{0}{0}let mut {1} = None;", TAB, option.arg.field_name),
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     for argument in arguments.iter() {
 | |
|         let _ = writeln!(result, "{0}{0}let mut {1} = None;", TAB, argument.field_name);
 | |
|     }
 | |
| 
 | |
|     if let Some(argument) = multi_argument.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}let mut {1} = Vec::new();", TAB, argument.field_name);
 | |
|     } else if let Some(command) = sub_command.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}let mut {1} = None;", TAB, command.field_name);
 | |
|     }
 | |
| 
 | |
|     let _ = writeln!(result, "{0}{0}let mut _args_ = _args_.into_iter();\n", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}while let Some(_arg_) = _args_.next() {{", TAB);
 | |
| 
 | |
|     //options
 | |
|     let _ = writeln!(result, "{0}{0}{0}if let Some(_arg_) = _arg_.strip_prefix('-') {{", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}match _arg_ {{", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}{0}\"h\" | \"-help\" => return Err(arg::ParseKind::Top(arg::ParseError::HelpRequested(Self::HELP))),", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}{0}\"\" => (),", TAB);
 | |
| 
 | |
|     for option in options.iter() {
 | |
|         if option.arg.field_name == "_" {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         let _ = write!(result, "{0}{0}{0}{0}{0}", TAB);
 | |
| 
 | |
|         if let Some(short) = option.short.as_ref() {
 | |
|             let _ = write!(result, "\"{}\" | ", short);
 | |
|         }
 | |
| 
 | |
|         let _ = write!(result, "\"-{}\" => ", option.long);
 | |
| 
 | |
|         let _ = match option.typ {
 | |
|             OptValueType::Help => panic!("Option Help is invalid here. Bug report it"),
 | |
|             OptValueType::Bool => write!(result, "{{ {0} = !{0}; continue }},", option.arg.field_name),
 | |
|             OptValueType::Value => write!(result, "match _args_.next() {{
 | |
| {0}{0}{0}{0}{0}{0}Some(_next_arg_) => match {1}(_next_arg_) {{
 | |
| {0}{0}{0}{0}{0}{0}{0}Ok(value) => {{ {2} = Some(value); continue }},
 | |
| {0}{0}{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidFlagValue(\"{3}\", _next_arg_))),
 | |
| {0}{0}{0}{0}{0}{0}}},
 | |
| {0}{0}{0}{0}{0}{0}None => return Err(arg::ParseKind::Top(arg::ParseError::MissingValue(\"{3}\"))),
 | |
| {0}{0}{0}{0}{0}}}", TAB, FROM_FN, option.arg.field_name, option.arg.name),
 | |
|             OptValueType::MultiValue => write!(result, "match _args_.next() {{
 | |
| {0}{0}{0}{0}{0}{0}Some(_next_arg_) => match {1}(_next_arg_) {{
 | |
| {0}{0}{0}{0}{0}{0}{0}Ok(value) => {{ {2}.push(value); continue }},
 | |
| {0}{0}{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidFlagValue(\"{3}\", _next_arg_))),
 | |
| {0}{0}{0}{0}{0}{0}}},
 | |
| {0}{0}{0}{0}{0}{0}None => return Err(arg::ParseKind::Top(arg::ParseError::MissingValue(\"{3}\"))),
 | |
| {0}{0}{0}{0}{0}}}", TAB, FROM_FN, option.arg.field_name, option.arg.name),
 | |
|         };
 | |
|         let _ = writeln!(result, "");
 | |
|     }
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}{0}_ => return Err(arg::ParseKind::Top(arg::ParseError::UnknownFlag(_arg_))),", TAB);
 | |
| 
 | |
|     let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
 | |
|     let _ = writeln!(result, "{0}{0}{0}}}", TAB);
 | |
|     //rest args
 | |
|     for (idx, arg) in arguments.iter().enumerate() {
 | |
|         if idx == 0 {
 | |
|             let _ = writeln!(result, "{0}{0}{0}if {1}.is_none() {{", TAB, arg.field_name);
 | |
|         } else {
 | |
|             let _ = writeln!(result, "{0}{0}{0}}} else if {1}.is_none() {{", TAB, arg.field_name);
 | |
|         }
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}match {1}(_arg_) {{", TAB, FROM_FN);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {1} = Some(_res_),", TAB, arg.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidArgValue(\"{1}\", _arg_))),", TAB, arg.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
 | |
|     }
 | |
|     //too many args?
 | |
|     if !arguments.is_empty() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}}} else {{", TAB);
 | |
|     }
 | |
| 
 | |
|     if let Some(arg) = multi_argument.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}match {1}(_arg_) {{", TAB, FROM_FN);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {1}.push(_res_),", TAB, arg.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(_) => return Err(arg::ParseKind::Top(arg::ParseError::InvalidArgValue(\"{1}\", _arg_))),", TAB, arg.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
 | |
|     } else if let Some(command) = sub_command.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}match {1}::from_args(core::iter::once(_arg_).chain(_args_)) {{", TAB, PARSER_TRAIT);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Ok(_res_) => {{ {1} = Some(_res_); break; }},", TAB, command.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Top(_)) => return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{1}\"))),", TAB, command.field_name);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}{0}Err(arg::ParseKind::Sub(name, error)) => return Err(arg::ParseKind::Sub(name, error)),", TAB);
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0}}}", TAB);
 | |
|     } else {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{0} return Err(arg::ParseKind::Top(arg::ParseError::TooManyArgs));", TAB);
 | |
|     }
 | |
|     //exit args
 | |
|     if !arguments.is_empty() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}}}", TAB);
 | |
|         let _ = writeln!(result, "{0}{0}}}", TAB);
 | |
|     } else {
 | |
|         let _ = writeln!(result, "{0}{0}}}", TAB);
 | |
|     }
 | |
| 
 | |
|     //Set defaults
 | |
|     for option in options.iter() {
 | |
|         if option.arg.field_name == "_" {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         let _ = match option.typ {
 | |
|             OptValueType::MultiValue => Ok(()),
 | |
|             OptValueType::Bool => Ok(()),
 | |
|             _ => match option.arg.default {
 | |
|                 Some(ref default) => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ {2} }};", TAB, option.arg.field_name, default),
 | |
|                 None => match option.arg.is_optional {
 | |
|                     true => Ok(()),
 | |
|                     false => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, option.arg.field_name, option.arg.name),
 | |
|                 },
 | |
|             },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     for arg in arguments.iter() {
 | |
|         let _ = match arg.default {
 | |
|             Some(ref default) => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ {2} }};", TAB, arg.field_name, default),
 | |
|             None => match arg.is_optional {
 | |
|                 true => Ok(()),
 | |
|                 false => writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, arg.field_name, arg.name),
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     if let Some(command) = sub_command.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}let {1} = if let Some(value) = {1} {{ value }} else {{ return Err(arg::ParseKind::Top(arg::ParseError::RequiredArgMissing(\"{2}\"))) }};", TAB, command.field_name, command.name);
 | |
|     }
 | |
| 
 | |
|     //Fill result
 | |
|     let _ = writeln!(result, "{0}{0}Ok(Self {{", TAB);
 | |
| 
 | |
|     for option in options.iter() {
 | |
|         if option.arg.field_name == "_" {
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|         let _ = if option.arg.is_optional && option.typ == OptValueType::Bool {
 | |
|             writeln!(result, "{0}{0}{0}{1}: Some({1}),", TAB, option.arg.field_name)
 | |
|         } else {
 | |
|             writeln!(result, "{0}{0}{0}{1},", TAB, option.arg.field_name)
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     for arg in arguments.iter() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
 | |
|     }
 | |
| 
 | |
|     if let Some(arg) = multi_argument.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
 | |
|     } else if let Some(arg) = sub_command.as_ref() {
 | |
|         let _ = writeln!(result, "{0}{0}{0}{1},", TAB, arg.field_name);
 | |
|     }
 | |
| 
 | |
|     let _ = writeln!(result, "{0}{0}}})", TAB);
 | |
| 
 | |
|     //Exit fn
 | |
|     let _ = writeln!(result, "{}}}", TAB);
 | |
| 
 | |
|     let _ = writeln!(result, "}}");
 | |
| 
 | |
|     if let Ok(val) = std::env::var("ARG_RS_PRINT_PARSER") {
 | |
|         match val.trim() {
 | |
|             "0" | "false" => (),
 | |
|             _ => println!("{result}"),
 | |
|         }
 | |
|     }
 | |
|     result.parse().expect("To parse generated code")
 | |
| }
 | |
| 
 | |
| #[proc_macro_derive(Args, attributes(parser, arg))]
 | |
| pub fn parser_derive(input: TokenStream) -> TokenStream {
 | |
|     const INVALID_INPUT_TYPE: &str = "Unsupported parser input type. Expect: struct";
 | |
|     let ast: syn::DeriveInput = syn::parse(input).unwrap();
 | |
| 
 | |
|     match ast.data {
 | |
|         syn::Data::Struct(ref data) => from_struct(&ast, data),
 | |
|         syn::Data::Enum(ref data) => from_enum(&ast, data),
 | |
|         _ => syn::Error::new_spanned(ast.ident, INVALID_INPUT_TYPE).to_compile_error().into(),
 | |
|     }
 | |
| }
 |