use crate::error::{Error, ErrorKind, Result};
use crate::opt::{Opt, OptArgData, OptBaseData, OptFileData, OptKind};
use darling::FromField;
use std::rc::Rc;
use syn::{spanned::Spanned, Field, Ident, Path, Type, TypePath};
#[derive(FromField)]
#[darling(attributes(conf))]
pub struct Attrs {
pub ident: Option<Ident>,
pub ty: Type,
#[darling(skip)]
pub is_option: bool,
#[darling(skip)]
pub takes_value: bool,
#[darling(default)]
pub default: Option<String>,
#[darling(default)]
pub no_long: bool,
#[darling(default)]
pub long: Option<String>,
#[darling(default)]
pub no_short: bool,
#[darling(default)]
pub short: Option<String>,
#[darling(default)]
pub help: Option<String>,
#[darling(default)]
pub negated_arg: bool,
#[darling(default)]
pub no_file: bool,
#[darling(default)]
pub file: Option<String>,
#[darling(default)]
pub section: Option<String>,
}
impl Attrs {
pub fn init(field: Field) -> Result<Attrs> {
let mut attrs = Attrs::from_field(&field)?;
attrs.apply_rules();
attrs.check_conflicts()?;
Ok(attrs)
}
fn apply_rules(&mut self) {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = &self.ty
{
if segments.len() == 1 && segments.first().unwrap().ident == "Option" {
let args = &segments.first().unwrap().arguments;
use syn::{
AngleBracketedGenericArguments as Brackets, GenericArgument::Type as InnerType,
PathArguments::AngleBracketed as PathAngles,
};
if let PathAngles(Brackets { args, .. }) = args {
if let InnerType(ty) = args.first().unwrap() {
self.ty = ty.clone();
self.is_option = true;
}
}
}
}
self.takes_value = true;
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = &self.ty
{
if segments.len() == 1 && segments.first().unwrap().ident == "bool" {
self.takes_value = false;
}
}
}
fn check_conflicts(&self) -> Result<()> {
macro_rules! check_conflicts {
($orig:expr, $others:expr) => {
let (orig, orig_name) = $orig;
if orig {
for (confl, confl_name) in $others.iter() {
if *confl {
return Err(Error {
span: self.ident.span(),
kind: ErrorKind::ConflictAttrs(
orig_name.to_string(),
confl_name.to_string(),
),
});
}
}
}
};
}
check_conflicts!(
(
self.no_short && self.no_long && self.no_file,
"no_short, no_long and no_file"
),
[
(self.long.is_some(), "long"),
(self.short.is_some(), "short"),
(self.help.is_some(), "help"),
(self.negated_arg, "negated_arg"),
(self.file.is_some(), "file"),
(self.section.is_some(), "section"),
]
);
check_conflicts!(
(self.no_short && self.no_long, "no_short and no_long"),
[
(self.negated_arg, "negated_arg"),
(self.help.is_some(), "help"),
]
);
check_conflicts!(
(self.no_short, "no_short"),
[(self.short.is_some(), "short"),]
);
check_conflicts!((self.no_long, "no_long"), [(self.long.is_some(), "long"),]);
check_conflicts!(
(self.negated_arg, "negated_arg"),
[
(self.takes_value, "field's type"),
(self.default.is_some(), "default"),
]
);
check_conflicts!(
(self.no_file, "no_file"),
[
(self.file.is_some(), "file"),
(self.section.is_some(), "section"),
]
);
Ok(())
}
pub fn get_file_data(&self) -> OptFileData {
OptFileData {
name: self
.file
.clone()
.unwrap_or_else(|| self.ident.as_ref().unwrap().to_string()),
section: self
.section
.clone()
.unwrap_or_else(|| "Defaults".to_string()),
}
}
pub fn get_arg_data(&self) -> Result<OptArgData> {
let ident = self.ident.clone().unwrap().to_string();
let long = if self.no_long {
None
} else {
let long = self.long.clone().unwrap_or_else(|| ident.clone());
Some(long.replace("_", "-"))
};
let short = if self.no_short {
None
} else {
match &self.short {
Some(s) => {
let mut chars = s.chars();
let first = chars.next();
let second = chars.next();
match (first, second) {
(Some(ch), None) => Some(ch.to_string()),
_ => {
return Err(Error {
span: self.ident.span(),
kind: ErrorKind::Parse(
"short argument can't be longer than \
one character"
.to_string(),
),
})
}
}
}
None => {
Some(ident.chars().next().unwrap().to_string())
}
}
};
Ok(OptArgData {
long,
short,
help: self.help.clone(),
negated: self.negated_arg,
})
}
pub fn parse_opt(self) -> Result<(Opt, Option<Opt>)> {
let base = Rc::new(OptBaseData {
is_option: self.is_option,
default: self.default.clone(),
id: self.ident.clone().unwrap(),
ty: self.ty.clone(),
});
let arg_kind = if self.takes_value {
OptKind::Arg
} else {
OptKind::Flag
};
let ret = if self.no_long && self.no_short && self.no_file {
(
Opt {
base,
kind: OptKind::Empty,
},
None,
)
} else if self.no_file {
(
Opt {
base,
kind: arg_kind(self.get_arg_data()?),
},
None,
)
} else if self.no_long && self.no_short {
(
Opt {
base,
kind: OptKind::File(self.get_file_data()),
},
None,
)
} else {
(
Opt {
base: Rc::clone(&base),
kind: arg_kind(self.get_arg_data()?),
},
Some(Opt {
base,
kind: OptKind::File(self.get_file_data()),
}),
)
};
Ok(ret)
}
}