mirror of
https://github.com/Start9Labs/rpc-toolkit.git
synced 2026-03-26 02:11:56 +00:00
538 lines
25 KiB
Rust
538 lines
25 KiB
Rust
use super::*;
|
|
use syn::spanned::Spanned;
|
|
|
|
pub fn parse_command_attr(args: AttributeArgs) -> Result<Options> {
|
|
let mut opt = Options::Leaf(Default::default());
|
|
for arg in args {
|
|
match arg {
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("subcommands") => {
|
|
let inner = opt.to_parent()?;
|
|
if !inner.subcommands.is_empty() {
|
|
return Err(Error::new(list.span(), "duplicate argument `subcommands`"));
|
|
}
|
|
for subcmd in list.nested {
|
|
match subcmd {
|
|
NestedMeta::Meta(Meta::Path(subcmd)) => inner.subcommands.push(subcmd),
|
|
NestedMeta::Lit(Lit::Str(s)) => {
|
|
inner.subcommands.push(syn::parse_str(&s.value())?)
|
|
}
|
|
NestedMeta::Meta(Meta::List(mut self_impl))
|
|
if self_impl.path.is_ident("self") =>
|
|
{
|
|
if self_impl.nested.len() == 1 {
|
|
match self_impl.nested.pop().unwrap().into_value() {
|
|
NestedMeta::Meta(Meta::Path(self_impl)) => {
|
|
if inner.self_impl.is_some() {
|
|
return Err(Error::new(
|
|
self_impl.span(),
|
|
"duplicate argument `self`",
|
|
));
|
|
}
|
|
inner.self_impl = Some(SelfImplInfo {
|
|
path: self_impl,
|
|
is_async: false,
|
|
blocking: false,
|
|
})
|
|
}
|
|
NestedMeta::Meta(Meta::List(l)) if l.nested.len() == 1 => {
|
|
if inner.self_impl.is_some() {
|
|
return Err(Error::new(
|
|
self_impl.span(),
|
|
"duplicate argument `self`",
|
|
));
|
|
}
|
|
let blocking = match l.nested.first().unwrap() {
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("blocking") => true,
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("async") => false,
|
|
arg => return Err(Error::new(arg.span(), "unknown argument")),
|
|
};
|
|
inner.self_impl = Some(SelfImplInfo {
|
|
path: l.path,
|
|
is_async: !blocking,
|
|
blocking,
|
|
})
|
|
}
|
|
a => {
|
|
return Err(Error::new(
|
|
a.span(),
|
|
"`self` implementation must be a path, or a list with 1 argument",
|
|
))
|
|
}
|
|
}
|
|
} else {
|
|
return Err(Error::new(
|
|
self_impl.nested.span(),
|
|
"`self` can only have one implementation",
|
|
));
|
|
}
|
|
}
|
|
arg => {
|
|
return Err(Error::new(arg.span(), "unknown argument to `subcommands`"))
|
|
}
|
|
}
|
|
}
|
|
if inner.subcommands.is_empty() {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`subcommands` requires at least 1 argument",
|
|
));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("subcommands") => {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`subcommands` requires at least 1 argument",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("subcommands") => {
|
|
return Err(Error::new(
|
|
nv.path.span(),
|
|
"`subcommands` cannot be assigned to",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("display") => {
|
|
if list.nested.len() == 1 {
|
|
match &list.nested[0] {
|
|
NestedMeta::Meta(Meta::Path(display_impl)) => {
|
|
if opt.common().display.is_some() {
|
|
return Err(Error::new(
|
|
display_impl.span(),
|
|
"duplicate argument `display`",
|
|
));
|
|
}
|
|
opt.common().display = Some(display_impl.clone())
|
|
}
|
|
a => {
|
|
return Err(Error::new(
|
|
a.span(),
|
|
"`display` implementation must be a path",
|
|
))
|
|
}
|
|
}
|
|
} else {
|
|
return Err(Error::new(
|
|
list.nested.span(),
|
|
"`display` can only have one implementation",
|
|
));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("display") => {
|
|
return Err(Error::new(p.span(), "`display` requires an argument"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("display") => {
|
|
return Err(Error::new(
|
|
nv.path.span(),
|
|
"`display` cannot be assigned to",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("local") => {
|
|
match &opt.common().exec_ctx {
|
|
ExecutionContext::Standard => {
|
|
opt.common().exec_ctx = ExecutionContext::LocalOnly(p)
|
|
}
|
|
ExecutionContext::LocalOnly(_) => {
|
|
return Err(Error::new(p.span(), "duplicate argument: `local`"))
|
|
}
|
|
ExecutionContext::RemoteOnly(_) => {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`local` and `remote` are mutually exclusive",
|
|
))
|
|
}
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("local") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`local` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("local") => {
|
|
return Err(Error::new(nv.path.span(), "`local` cannot be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("remote") => {
|
|
match &opt.common().exec_ctx {
|
|
ExecutionContext::Standard => {
|
|
opt.common().exec_ctx = ExecutionContext::RemoteOnly(p)
|
|
}
|
|
ExecutionContext::LocalOnly(_) => {
|
|
return Err(Error::new(p.span(), "duplicate argument: `remote`"))
|
|
}
|
|
ExecutionContext::RemoteOnly(_) => {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`local` and `remote` are mutually exclusive",
|
|
))
|
|
}
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("remote") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`remote` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("remote") => {
|
|
return Err(Error::new(nv.path.span(), "`remote` cannot be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("blocking") => {
|
|
if opt.common().blocking.is_some() {
|
|
return Err(Error::new(p.span(), "duplicate argument `blocking`"));
|
|
}
|
|
opt.common().blocking = Some(p);
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("blocking") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`blocking` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("blocking") => {
|
|
return Err(Error::new(
|
|
nv.path.span(),
|
|
"`blocking` cannot be assigned to",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("about") => {
|
|
if let Lit::Str(about) = nv.lit {
|
|
if opt.common().about.is_some() {
|
|
return Err(Error::new(about.span(), "duplicate argument `about`"));
|
|
}
|
|
opt.common().about = Some(about);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "about message must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("about") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`about` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("about") => {
|
|
return Err(Error::new(p.span(), "`about` must be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rename") => {
|
|
if let Lit::Str(rename) = nv.lit {
|
|
if opt.common().rename.is_some() {
|
|
return Err(Error::new(rename.span(), "duplicate argument `rename`"));
|
|
}
|
|
opt.common().rename = Some(
|
|
syn::parse_str(&rename.value())
|
|
.map_err(|e| Error::new(rename.span(), format!("{}", e)))?,
|
|
);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "`rename` must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("rename") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`rename` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rename") => {
|
|
return Err(Error::new(p.span(), "`rename` must be assigned to"));
|
|
}
|
|
_ => {
|
|
return Err(Error::new(arg.span(), "unknown argument"));
|
|
}
|
|
}
|
|
}
|
|
if let Options::Parent(opt) = &opt {
|
|
if opt.self_impl.is_none() {
|
|
if let Some(display) = &opt.common.display {
|
|
return Err(Error::new(
|
|
display.span(),
|
|
"cannot define `display` for a command without an implementation",
|
|
));
|
|
}
|
|
match &opt.common.exec_ctx {
|
|
ExecutionContext::LocalOnly(local) => {
|
|
return Err(Error::new(
|
|
local.span(),
|
|
"cannot define `local` for a command without an implementation",
|
|
))
|
|
}
|
|
ExecutionContext::RemoteOnly(remote) => {
|
|
return Err(Error::new(
|
|
remote.span(),
|
|
"cannot define `remote` for a command without an implementation",
|
|
))
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn parse_arg_attr(attr: Attribute, arg: PatType, has_stdin: &mut bool) -> Result<ArgOptions> {
|
|
let arg_span = arg.span();
|
|
let mut opt = ArgOptions {
|
|
ty: *arg.ty,
|
|
optional: false,
|
|
check_is_present: false,
|
|
help: None,
|
|
name: match *arg.pat {
|
|
Pat::Ident(i) => Some(i.ident),
|
|
_ => None,
|
|
},
|
|
short: None,
|
|
long: None,
|
|
parse: None,
|
|
stdin: false,
|
|
};
|
|
match attr.parse_meta()? {
|
|
Meta::List(list) => {
|
|
for arg in list.nested {
|
|
match arg {
|
|
NestedMeta::Meta(Meta::List(mut list)) if list.path.is_ident("parse") => {
|
|
if list.nested.len() == 1 {
|
|
match list.nested.pop().unwrap().into_value() {
|
|
NestedMeta::Meta(Meta::Path(parse_impl)) => {
|
|
if opt.parse.is_some() {
|
|
return Err(Error::new(
|
|
list.span(),
|
|
"duplicate argument `parse`",
|
|
));
|
|
}
|
|
opt.parse = Some(parse_impl)
|
|
}
|
|
a => {
|
|
return Err(Error::new(
|
|
a.span(),
|
|
"`parse` implementation must be a path",
|
|
))
|
|
}
|
|
}
|
|
} else {
|
|
return Err(Error::new(
|
|
list.nested.span(),
|
|
"`parse` can only have one implementation",
|
|
));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("parse") => {
|
|
return Err(Error::new(p.span(), "`parse` requires an argument"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("parse") => {
|
|
return Err(Error::new(nv.path.span(), "`parse` cannot be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("stdin") => {
|
|
if *has_stdin {
|
|
return Err(Error::new(p.span(), "duplicate argument `stdin`"));
|
|
}
|
|
if opt.short.is_some() {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`stdin` and `short` are mutually exclusive",
|
|
));
|
|
}
|
|
if opt.long.is_some() {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`stdin` and `long` are mutually exclusive",
|
|
));
|
|
}
|
|
if opt.help.is_some() {
|
|
return Err(Error::new(
|
|
p.span(),
|
|
"`stdin` and `help` are mutually exclusive",
|
|
));
|
|
}
|
|
opt.stdin = true;
|
|
*has_stdin = true;
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("stdin") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`stdin` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("stdin") => {
|
|
return Err(Error::new(nv.path.span(), "`stdin` cannot be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("help") => {
|
|
if let Lit::Str(help) = nv.lit {
|
|
if opt.help.is_some() {
|
|
return Err(Error::new(help.span(), "duplicate argument `help`"));
|
|
}
|
|
if opt.stdin {
|
|
return Err(Error::new(
|
|
help.span(),
|
|
"`stdin` and `help` are mutually exclusive",
|
|
));
|
|
}
|
|
opt.help = Some(help);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "help message must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("help") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`help` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("help") => {
|
|
return Err(Error::new(p.span(), "`help` must be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("rename") => {
|
|
if let Lit::Str(rename) = nv.lit {
|
|
if opt.name.is_some() {
|
|
return Err(Error::new(
|
|
rename.span(),
|
|
"duplicate argument `rename`",
|
|
));
|
|
}
|
|
opt.name = Some(
|
|
syn::parse_str(&rename.value())
|
|
.map_err(|e| Error::new(rename.span(), format!("{}", e)))?,
|
|
);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "`rename` must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("rename") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`rename` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("rename") => {
|
|
return Err(Error::new(p.span(), "`rename` must be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("short") => {
|
|
if let Lit::Str(short) = nv.lit {
|
|
if short.value().len() != 1 {
|
|
return Err(Error::new(
|
|
short.span(),
|
|
"`short` value must be 1 character",
|
|
));
|
|
}
|
|
if opt.short.is_some() {
|
|
return Err(Error::new(short.span(), "duplicate argument `short`"));
|
|
}
|
|
if opt.stdin {
|
|
return Err(Error::new(
|
|
short.span(),
|
|
"`stdin` and `short` are mutually exclusive",
|
|
));
|
|
}
|
|
opt.short = Some(short);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "`short` must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("short") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`short` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("short") => {
|
|
return Err(Error::new(p.span(), "`short` must be assigned to"));
|
|
}
|
|
NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("long") => {
|
|
if let Lit::Str(long) = nv.lit {
|
|
if opt.long.is_some() {
|
|
return Err(Error::new(long.span(), "duplicate argument `long`"));
|
|
}
|
|
if opt.stdin {
|
|
return Err(Error::new(
|
|
long.span(),
|
|
"`stdin` and `long` are mutually exclusive",
|
|
));
|
|
}
|
|
opt.long = Some(long);
|
|
} else {
|
|
return Err(Error::new(nv.lit.span(), "`long` must be a string"));
|
|
}
|
|
}
|
|
NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("long") => {
|
|
return Err(Error::new(
|
|
list.path.span(),
|
|
"`long` does not take any arguments",
|
|
));
|
|
}
|
|
NestedMeta::Meta(Meta::Path(p)) if p.is_ident("long") => {
|
|
return Err(Error::new(p.span(), "`long` must be assigned to"));
|
|
}
|
|
_ => {
|
|
return Err(Error::new(arg.span(), "unknown argument"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Meta::Path(_) => (),
|
|
Meta::NameValue(nv) => return Err(Error::new(nv.span(), "`arg` cannot be assigned to")),
|
|
}
|
|
if opt.name.is_none() {
|
|
return Err(Error::new(
|
|
arg_span,
|
|
"cannot infer name for pattern argument",
|
|
));
|
|
}
|
|
Ok(opt)
|
|
}
|
|
|
|
pub fn parse_param_attrs(item: &mut ItemFn) -> Result<Vec<ParamType>> {
|
|
let mut params = Vec::new();
|
|
let mut has_stdin = false;
|
|
for param in item.sig.inputs.iter_mut() {
|
|
if let FnArg::Typed(param) = param {
|
|
let mut ty = ParamType::None;
|
|
let mut i = 0;
|
|
while i != param.attrs.len() {
|
|
if param.attrs[i].path.is_ident("arg") {
|
|
let attr = param.attrs.remove(i);
|
|
if matches!(ty, ParamType::None) {
|
|
ty = ParamType::Arg(parse_arg_attr(attr, param.clone(), &mut has_stdin)?);
|
|
} else if matches!(ty, ParamType::Arg(_)) {
|
|
return Err(Error::new(
|
|
attr.span(),
|
|
"`arg` attribute may only be specified once",
|
|
));
|
|
} else if matches!(ty, ParamType::Context(_)) {
|
|
return Err(Error::new(
|
|
attr.span(),
|
|
"`arg` and `context` are mutually exclusive",
|
|
));
|
|
}
|
|
} else if param.attrs[i].path.is_ident("context") {
|
|
let attr = param.attrs.remove(i);
|
|
if matches!(ty, ParamType::None) {
|
|
ty = ParamType::Context(*param.ty.clone());
|
|
} else if matches!(ty, ParamType::Context(_)) {
|
|
return Err(Error::new(
|
|
attr.span(),
|
|
"`context` attribute may only be specified once",
|
|
));
|
|
} else if matches!(ty, ParamType::Arg(_)) {
|
|
return Err(Error::new(
|
|
attr.span(),
|
|
"`arg` and `context` are mutually exclusive",
|
|
));
|
|
}
|
|
} else {
|
|
i += 1;
|
|
}
|
|
}
|
|
if matches!(ty, ParamType::None) {
|
|
return Err(Error::new(
|
|
param.span(),
|
|
"must specify either `arg` or `context` attributes",
|
|
));
|
|
}
|
|
params.push(ty)
|
|
} else {
|
|
return Err(Error::new(
|
|
param.span(),
|
|
"commands may not take `self` as an argument",
|
|
));
|
|
}
|
|
}
|
|
Ok(params)
|
|
}
|