/* * Copyright (c) 2023 Emma Tebibyte * 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 . * * 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, } #[derive(PartialEq, Eq, Debug)] enum OptValueType { Help, Bool, Value, MultiValue, } struct Opt { arg: Argument, long: String, short: Option, 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>(_args_: T) -> Result> {{", 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> 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>(_args_: T) -> Result> {{", 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(), } }