This repository has been archived on 2023-07-03. You can view files and clone it, but cannot push or open issues or pull requests.
spd/src/env/derive/mod.rs

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(),
}
}