Vendor argh sources

Further customization will come in future commits
This commit is contained in:
topjohnwu
2025-10-17 23:48:31 -07:00
committed by John Wu
parent 5bcb55b7fc
commit b72ba6759e
23 changed files with 3039 additions and 71 deletions

41
native/src/Cargo.lock generated
View File

@@ -14,35 +14,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "argh"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240"
dependencies = [
"argh_derive",
"argh_shared",
"rust-fuzzy-search",
]
[[package]]
name = "argh_derive"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803"
dependencies = [
"argh_shared",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "argh_shared"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6"
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -53,13 +24,13 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
name = "base"
version = "0.0.0"
dependencies = [
"argh",
"bitflags",
"bytemuck",
"cfg-if",
"const_format",
"cxx",
"cxx-gen",
"derive",
"libc",
"nix",
"num-derive",
@@ -648,14 +619,12 @@ dependencies = [
name = "magisk"
version = "0.0.0"
dependencies = [
"argh",
"base",
"bit-set",
"bitflags",
"bytemuck",
"cxx",
"cxx-gen",
"derive",
"nix",
"num-derive",
"num-traits",
@@ -668,7 +637,6 @@ dependencies = [
name = "magiskboot"
version = "0.0.0"
dependencies = [
"argh",
"base",
"bytemuck",
"byteorder",
@@ -710,7 +678,6 @@ dependencies = [
name = "magiskpolicy"
version = "0.0.0"
dependencies = [
"argh",
"base",
"cxx",
"cxx-gen",
@@ -952,12 +919,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-fuzzy-search"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2"
[[package]]
name = "rustversion"
version = "1.0.22"

View File

@@ -1,6 +1,6 @@
[workspace]
exclude = ["external"]
members = ["base", "boot", "core", "core/derive", "init", "sepolicy"]
members = ["base", "base/derive", "boot", "core", "init", "sepolicy"]
resolver = "2"
[workspace.package]
@@ -9,7 +9,7 @@ edition = "2024"
[workspace.dependencies]
base = { path = "base" }
derive = { path = "core/derive" }
derive = { path = "base/derive" }
magiskpolicy = { path = "sepolicy" }
cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" }
@@ -27,7 +27,6 @@ bit-set = "0.8.0"
syn = "2.0.106"
quote = "1.0.40"
proc-macro2 = "1.0.101"
argh = { version = "0.1.13", default-features = false }
pb-rs = { version = "0.10.0", default-features = false }
quick-protobuf = "0.8.1"
flate2 = { version = "1.1.2", default-features = false }

View File

@@ -13,11 +13,11 @@ selinux = []
cxx-gen = { workspace = true }
[dependencies]
derive = { workspace = true }
cxx = { workspace = true }
libc = { workspace = true }
cfg-if = { workspace = true }
thiserror = { workspace = true }
argh = { workspace = true }
bytemuck = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }

1233
native/src/base/argh.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,185 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::cell::RefCell;
/// A type for collecting procedural macro errors.
#[derive(Default)]
pub struct Errors {
errors: RefCell<Vec<syn::Error>>,
}
/// Produce functions to expect particular literals in `syn::Expr`
macro_rules! expect_lit_fn {
($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => {
$(
pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> {
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e {
Some(inner)
} else {
self.unexpected_lit($lit_name, e);
None
}
}
)*
}
}
/// Produce functions to expect particular variants of `syn::Meta`
macro_rules! expect_meta_fn {
($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => {
$(
pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> {
if let syn::Meta::$variant(inner) = meta {
Some(inner)
} else {
self.unexpected_meta($meta_name, meta);
None
}
}
)*
}
}
impl Errors {
/// Issue an error like:
///
/// Duplicate foo attribute
/// First foo attribute here
pub fn duplicate_attrs(
&self,
attr_kind: &str,
first: &impl syn::spanned::Spanned,
second: &impl syn::spanned::Spanned,
) {
self.duplicate_attrs_inner(attr_kind, first.span(), second.span())
}
fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) {
self.err_span(second, &["Duplicate ", attr_kind, " attribute"].concat());
self.err_span(first, &["First ", attr_kind, " attribute here"].concat());
}
expect_lit_fn![
(expect_lit_str, LitStr, Str, "string"),
(expect_lit_char, LitChar, Char, "character"),
(expect_lit_int, LitInt, Int, "integer"),
];
expect_meta_fn![
(expect_meta_word, Path, Path, "path"),
(expect_meta_list, MetaList, List, "list"),
(
expect_meta_name_value,
MetaNameValue,
NameValue,
"name-value pair"
),
];
fn unexpected_lit(&self, expected: &str, found: &syn::Expr) {
fn lit_kind(lit: &syn::Lit) -> &'static str {
use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim};
match lit {
Str(_) => "string",
ByteStr(_) => "bytestring",
Byte(_) => "byte",
Char(_) => "character",
Int(_) => "integer",
Float(_) => "float",
Bool(_) => "boolean",
Verbatim(_) => "unknown (possibly extra-large integer)",
_ => "unknown literal kind",
}
}
if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found {
self.err(
found,
&[
"Expected ",
expected,
" literal, found ",
lit_kind(lit),
" literal",
]
.concat(),
)
} else {
self.err(
found,
&[
"Expected ",
expected,
" literal, found non-literal expression.",
]
.concat(),
)
}
}
fn unexpected_meta(&self, expected: &str, found: &syn::Meta) {
fn meta_kind(meta: &syn::Meta) -> &'static str {
use syn::Meta::{List, NameValue, Path};
match meta {
Path(_) => "path",
List(_) => "list",
NameValue(_) => "name-value pair",
}
}
self.err(
found,
&[
"Expected ",
expected,
" attribute, found ",
meta_kind(found),
" attribute",
]
.concat(),
)
}
/// Issue an error relating to a particular `Spanned` structure.
pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {
self.err_span(spanned.span(), msg);
}
/// Issue an error relating to a particular `Span`.
pub fn err_span(&self, span: Span, msg: &str) {
self.push(syn::Error::new(span, msg));
}
/// Issue an error spanning over the given syntax tree node.
pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {
self.push(syn::Error::new_spanned(tokens, msg));
}
/// Push a `syn::Error` onto the list of errors to issue.
pub fn push(&self, err: syn::Error) {
self.errors.borrow_mut().push(err);
}
/// Convert a `syn::Result` to an `Option`, logging the error if present.
pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> {
match r {
Ok(v) => Some(v),
Err(e) => {
self.push(e);
None
}
}
}
}
impl ToTokens for Errors {
/// Convert the errors into tokens that, when emit, will cause
/// the user of the macro to receive compiler errors.
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error()));
}
}

View File

@@ -0,0 +1,911 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use syn::ext::IdentExt as _;
/// Implementation of the `FromArgs` and `argh(...)` derive attributes.
///
/// For more thorough documentation, see the `argh` crate itself.
extern crate proc_macro;
use errors::Errors;
use parse_attrs::{FieldAttrs, FieldKind, TypeAttrs, check_long_name};
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote, quote_spanned};
use std::collections::HashMap;
use std::str::FromStr;
use syn::spanned::Spanned;
use syn::{GenericArgument, LitStr, PathArguments, Type};
mod errors;
mod parse_attrs;
/// Transform the input into a token stream containing any generated implementations,
/// as well as all errors that occurred.
pub(crate) fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {
let errors = &Errors::default();
let type_attrs = &TypeAttrs::parse(errors, input);
let mut output_tokens = match &input.data {
syn::Data::Struct(ds) => {
impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds)
}
syn::Data::Enum(de) => {
impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de)
}
syn::Data::Union(_) => {
errors.err(input, "`#[derive(FromArgs)]` cannot be applied to unions");
TokenStream::new()
}
};
errors.to_tokens(&mut output_tokens);
output_tokens
}
/// The kind of optionality a parameter has.
enum Optionality {
None,
Defaulted(TokenStream),
Optional,
Repeating,
DefaultedRepeating(TokenStream),
}
impl PartialEq<Optionality> for Optionality {
fn eq(&self, other: &Optionality) -> bool {
use Optionality::*;
// NB: (Defaulted, Defaulted) can't contain the same token streams
matches!((self, other), (Optional, Optional) | (Repeating, Repeating))
}
}
impl Optionality {
/// Whether or not this is `Optionality::None`
fn is_required(&self) -> bool {
matches!(self, Optionality::None)
}
}
/// A field of a `#![derive(FromArgs)]` struct with attributes and some other
/// notable metadata appended.
struct StructField<'a> {
/// The original parsed field
field: &'a syn::Field,
/// The parsed attributes of the field
attrs: FieldAttrs,
/// The field name. This is contained optionally inside `field`,
/// but is duplicated non-optionally here to indicate that all field that
/// have reached this point must have a field name, and it no longer
/// needs to be unwrapped.
name: &'a syn::Ident,
/// Similar to `name` above, this is contained optionally inside `FieldAttrs`,
/// but here is fully present to indicate that we only have to consider fields
/// with a valid `kind` at this point.
kind: FieldKind,
// If `field.ty` is `Vec<T>` or `Option<T>`, this is `T`, otherwise it's `&field.ty`.
// This is used to enable consistent parsing code between optional and non-optional
// keyed and subcommand fields.
ty_without_wrapper: &'a syn::Type,
// Whether the field represents an optional value, such as an `Option` subcommand field
// or an `Option` or `Vec` keyed argument, or if it has a `default`.
optionality: Optionality,
// The `--`-prefixed name of the option, if one exists.
long_name: Option<String>,
}
impl<'a> StructField<'a> {
/// Attempts to parse a field of a `#[derive(FromArgs)]` struct, pulling out the
/// fields required for code generation.
fn new(errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option<Self> {
let name = field.ident.as_ref().expect("missing ident for named field");
// Ensure that one "kind" is present (switch, option, subcommand, positional)
let kind = if let Some(field_type) = &attrs.field_type {
field_type.kind
} else {
errors.err(
field,
concat!(
"Missing `argh` field kind attribute.\n",
"Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`",
),
);
return None;
};
// Parse out whether a field is optional (`Option` or `Vec`).
let optionality;
let ty_without_wrapper;
match kind {
FieldKind::Switch => {
if !ty_expect_switch(errors, &field.ty) {
return None;
}
optionality = Optionality::Optional;
ty_without_wrapper = &field.ty;
}
FieldKind::Option | FieldKind::Positional => {
if let Some(default) = &attrs.default {
let tokens = match TokenStream::from_str(&default.value()) {
Ok(tokens) => tokens,
Err(_) => {
errors.err(&default, "Invalid tokens: unable to lex `default` value");
return None;
}
};
// Set the span of the generated tokens to the string literal
let tokens: TokenStream = tokens
.into_iter()
.map(|mut tree| {
tree.set_span(default.span());
tree
})
.collect();
let inner = if let Some(x) = ty_inner(&["Vec"], &field.ty) {
optionality = Optionality::DefaultedRepeating(tokens);
x
} else {
optionality = Optionality::Defaulted(tokens);
&field.ty
};
ty_without_wrapper = inner;
} else {
let mut inner = None;
optionality = if let Some(x) = ty_inner(&["Option"], &field.ty) {
inner = Some(x);
Optionality::Optional
} else if let Some(x) = ty_inner(&["Vec"], &field.ty) {
inner = Some(x);
Optionality::Repeating
} else {
Optionality::None
};
ty_without_wrapper = inner.unwrap_or(&field.ty);
}
}
FieldKind::SubCommand => {
let inner = ty_inner(&["Option"], &field.ty);
optionality = if inner.is_some() {
Optionality::Optional
} else {
Optionality::None
};
ty_without_wrapper = inner.unwrap_or(&field.ty);
}
}
// Determine the "long" name of options and switches.
// Defaults to the kebab-case'd field name if `#[argh(long = "...")]` is omitted.
let long_name = match kind {
FieldKind::Switch | FieldKind::Option => {
let long_name = attrs
.long
.as_ref()
.map(syn::LitStr::value)
.unwrap_or_else(|| {
let kebab_name = to_kebab_case(&name.unraw().to_string());
check_long_name(errors, name, &kebab_name);
kebab_name
});
if long_name == "help" {
errors.err(field, "Custom `--help` flags are not supported.");
}
let long_name = format!("--{}", long_name);
Some(long_name)
}
FieldKind::SubCommand | FieldKind::Positional => None,
};
Some(StructField {
field,
attrs,
kind,
optionality,
ty_without_wrapper,
name,
long_name,
})
}
pub(crate) fn positional_arg_name(&self) -> String {
self.attrs
.arg_name
.as_ref()
.map(LitStr::value)
.unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())
}
}
fn to_kebab_case(s: &str) -> String {
let words = s.split('_').filter(|word| !word.is_empty());
let mut res = String::with_capacity(s.len());
for word in words {
if !res.is_empty() {
res.push('-')
}
res.push_str(word)
}
res
}
#[test]
fn test_kebabs() {
#[track_caller]
fn check(s: &str, want: &str) {
let got = to_kebab_case(s);
assert_eq!(got.as_str(), want)
}
check("", "");
check("_", "");
check("foo", "foo");
check("__foo_", "foo");
check("foo_bar", "foo-bar");
check("foo__Bar", "foo-Bar");
check("foo_bar__baz_", "foo-bar-baz");
}
/// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct.
fn impl_from_args_struct(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
ds: &syn::DataStruct,
) -> TokenStream {
let fields = match &ds.fields {
syn::Fields::Named(fields) => fields,
syn::Fields::Unnamed(_) => {
errors.err(
&ds.struct_token,
"`#![derive(FromArgs)]` is not currently supported on tuple structs",
);
return TokenStream::new();
}
syn::Fields::Unit => {
errors.err(
&ds.struct_token,
"#![derive(FromArgs)]` cannot be applied to unit structs",
);
return TokenStream::new();
}
};
let fields: Vec<_> = fields
.named
.iter()
.filter_map(|field| {
let attrs = FieldAttrs::parse(errors, field);
StructField::new(errors, field, attrs)
})
.collect();
ensure_unique_names(errors, &fields);
ensure_only_last_positional_is_optional(errors, &fields);
let impl_span = Span::call_site();
let from_args_method = impl_from_args_struct_from_args(errors, type_attrs, &fields);
let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args);
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
let trait_impl = quote_spanned! { impl_span =>
#[automatically_derived]
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
#from_args_method
}
#top_or_sub_cmd_impl
};
trait_impl
}
fn impl_from_args_struct_from_args<'a>(
errors: &Errors,
type_attrs: &TypeAttrs,
fields: &'a [StructField<'a>],
) -> TokenStream {
let init_fields = declare_local_storage_for_from_args_fields(fields);
let unwrap_fields = unwrap_from_args_fields(fields);
let positional_fields: Vec<&StructField<'_>> = fields
.iter()
.filter(|field| field.kind == FieldKind::Positional)
.collect();
let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);
let positional_field_names = positional_fields.iter().map(|field| field.name.to_string());
let last_positional_is_repeating = positional_fields
.last()
.map(|field| field.optionality == Optionality::Repeating)
.unwrap_or(false);
let last_positional_is_greedy = positional_fields
.last()
.map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())
.unwrap_or(false);
let flag_output_table = fields.iter().filter_map(|field| {
let field_name = &field.field.ident;
match field.kind {
FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }),
FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }),
FieldKind::SubCommand | FieldKind::Positional => None,
}
});
let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);
let mut subcommands_iter = fields
.iter()
.filter(|field| field.kind == FieldKind::SubCommand)
.fuse();
let subcommand: Option<&StructField<'_>> = subcommands_iter.next();
for dup_subcommand in subcommands_iter {
errors.duplicate_attrs(
"subcommand",
subcommand.unwrap().field,
dup_subcommand.field,
);
}
let impl_span = Span::call_site();
let missing_requirements_ident = syn::Ident::new("__missing_requirements", impl_span);
let append_missing_requirements =
append_missing_requirements(&missing_requirements_ident, fields);
let parse_subcommands = if let Some(subcommand) = subcommand {
let name = subcommand.name;
let ty = subcommand.ty_without_wrapper;
quote_spanned! { impl_span =>
Some(argh::ParseStructSubCommand {
subcommands: <#ty as argh::SubCommands>::COMMANDS,
dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),
parse_func: &mut |__command, __remaining_args| {
#name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?);
Ok(())
},
})
}
} else {
quote_spanned! { impl_span => None }
};
let help_triggers = get_help_triggers(type_attrs);
let help = quote! { String::new() };
let method_impl = quote_spanned! { impl_span =>
fn from_args(__cmd_name: &[&str], __args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
{
#![allow(clippy::unwrap_in_result)]
#( #init_fields )*
argh::parse_struct_args(
__cmd_name,
__args,
argh::ParseStructOptions {
arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ],
slots: &mut [ #( #flag_output_table, )* ],
help_triggers: &[ #( #help_triggers ),* ],
},
argh::ParseStructPositionals {
positionals: &mut [
#(
argh::ParseStructPositional {
name: #positional_field_names,
slot: &mut #positional_field_idents as &mut dyn argh::ParseValueSlot,
},
)*
],
last_is_repeating: #last_positional_is_repeating,
last_is_greedy: #last_positional_is_greedy,
},
#parse_subcommands,
&|| #help,
)?;
let mut #missing_requirements_ident = argh::MissingRequirements::default();
#(
#append_missing_requirements
)*
#missing_requirements_ident.err_on_any()?;
Ok(Self {
#( #unwrap_fields, )*
})
}
};
method_impl
}
/// get help triggers vector from type_attrs.help_triggers as a [`Vec<String>`]
///
/// Defaults to vec!["--help", "help"] if type_attrs.help_triggers is None
fn get_help_triggers(type_attrs: &TypeAttrs) -> Vec<String> {
type_attrs.help_triggers.as_ref().map_or_else(
|| vec!["--help".to_owned(), "help".to_owned()],
|s| {
s.iter()
.filter_map(|s| {
let trigger = s.value();
let trigger_trimmed = trigger.trim().to_owned();
if trigger_trimmed.is_empty() {
None
} else {
Some(trigger_trimmed)
}
})
.collect::<Vec<_>>()
},
)
}
/// Ensures that only the last positional arg is non-required.
fn ensure_only_last_positional_is_optional(errors: &Errors, fields: &[StructField<'_>]) {
let mut first_non_required_span = None;
for field in fields {
if field.kind == FieldKind::Positional {
if let Some(first) = first_non_required_span {
errors.err_span(
first,
"Only the last positional argument may be `Option`, `Vec`, or defaulted.",
);
errors.err(&field.field, "Later positional argument declared here.");
return;
}
if !field.optionality.is_required() {
first_non_required_span = Some(field.field.span());
}
}
}
}
/// Ensures that only one short or long name is used.
fn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {
let mut seen_short_names = HashMap::new();
let mut seen_long_names = HashMap::new();
for field in fields {
if let Some(short_name) = &field.attrs.short {
let short_name = short_name.value();
if let Some(first_use_field) = seen_short_names.get(&short_name) {
errors.err_span_tokens(
first_use_field,
&format!(
"The short name of \"-{}\" was already used here.",
short_name
),
);
errors.err_span_tokens(field.field, "Later usage here.");
}
seen_short_names.insert(short_name, &field.field);
}
if let Some(long_name) = &field.long_name {
if let Some(first_use_field) = seen_long_names.get(&long_name) {
errors.err_span_tokens(
*first_use_field,
&format!("The long name of \"{}\" was already used here.", long_name),
);
errors.err_span_tokens(field.field, "Later usage here.");
}
seen_long_names.insert(long_name, field.field);
}
}
}
/// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate.
fn top_or_sub_cmd_impl(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
) -> TokenStream {
let description = String::new();
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
if type_attrs.is_subcommand.is_none() {
// Not a subcommand
quote! {
#[automatically_derived]
impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {}
}
} else {
let empty_str = syn::LitStr::new("", Span::call_site());
let subcommand_name = type_attrs.name.as_ref().unwrap_or_else(|| {
errors.err(
name,
"`#[argh(name = \"...\")]` attribute is required for subcommands",
);
&empty_str
});
quote! {
#[automatically_derived]
impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause {
const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {
name: #subcommand_name,
description: #description,
};
}
}
}
}
/// Declare a local slots to store each field in during parsing.
///
/// Most fields are stored in `Option<FieldType>` locals.
/// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a
/// function that knows how to decode the appropriate value.
fn declare_local_storage_for_from_args_fields<'a>(
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
let field_name = &field.field.ident;
let field_type = &field.ty_without_wrapper;
// Wrap field types in `Option` if they aren't already `Option` or `Vec`-wrapped.
let field_slot_type = match field.optionality {
Optionality::Optional | Optionality::Repeating => (&field.field.ty).into_token_stream(),
Optionality::None | Optionality::Defaulted(_) => {
quote! { std::option::Option<#field_type> }
}
Optionality::DefaultedRepeating(_) => {
quote! { std::option::Option<std::vec::Vec<#field_type>> }
}
};
match field.kind {
FieldKind::Option | FieldKind::Positional => {
let from_str_fn = match &field.attrs.from_str_fn {
Some(from_str_fn) => from_str_fn.into_token_stream(),
None => {
quote! {
<#field_type as argh::FromArgValue>::from_arg_value
}
}
};
quote! {
let mut #field_name: argh::ParseValueSlotTy<#field_slot_type, #field_type>
= argh::ParseValueSlotTy {
slot: std::default::Default::default(),
parse_func: |_, value| { #from_str_fn(value) },
};
}
}
FieldKind::SubCommand => {
quote! { let mut #field_name: #field_slot_type = None; }
}
FieldKind::Switch => {
quote! { let mut #field_name: #field_slot_type = argh::Flag::default(); }
}
}
})
}
/// Unwrap non-optional fields and take options out of their tuple slots.
fn unwrap_from_args_fields<'a>(
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
fields.iter().map(|field| {
let field_name = field.name;
match field.kind {
FieldKind::Option | FieldKind::Positional => match &field.optionality {
Optionality::None => quote! {
#field_name: #field_name.slot.unwrap()
},
Optionality::Optional | Optionality::Repeating => {
quote! { #field_name: #field_name.slot }
}
Optionality::Defaulted(tokens) | Optionality::DefaultedRepeating(tokens) => {
quote! {
#field_name: #field_name.slot.unwrap_or_else(|| #tokens)
}
}
},
FieldKind::Switch => field_name.into_token_stream(),
FieldKind::SubCommand => match field.optionality {
Optionality::None => quote! { #field_name: #field_name.unwrap() },
Optionality::Optional | Optionality::Repeating => field_name.into_token_stream(),
Optionality::Defaulted(_) | Optionality::DefaultedRepeating(_) => unreachable!(),
},
}
})
}
/// Entries of tokens like `("--some-flag-key", 5)` that map from a flag key string
/// to an index in the output table.
fn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Vec<TokenStream> {
let mut flag_str_to_output_table_map = vec![];
for (i, (field, long_name)) in fields
.iter()
.filter_map(|field| field.long_name.as_ref().map(|long_name| (field, long_name)))
.enumerate()
{
if let Some(short) = &field.attrs.short {
let short = format!("-{}", short.value());
flag_str_to_output_table_map.push(quote! { (#short, #i) });
}
flag_str_to_output_table_map.push(quote! { (#long_name, #i) });
}
flag_str_to_output_table_map
}
/// For each non-optional field, add an entry to the `argh::MissingRequirements`.
fn append_missing_requirements<'a>(
// missing_requirements_ident
mri: &syn::Ident,
fields: &'a [StructField<'a>],
) -> impl Iterator<Item = TokenStream> + 'a {
let mri = mri.clone();
fields
.iter()
.filter(|f| f.optionality.is_required())
.map(move |field| {
let field_name = field.name;
match field.kind {
FieldKind::Switch => unreachable!("switches are always optional"),
FieldKind::Positional => {
let name = field.positional_arg_name();
quote! {
if #field_name.slot.is_none() {
#mri.missing_positional_arg(#name)
}
}
}
FieldKind::Option => {
let name = field
.long_name
.as_ref()
.expect("options always have a long name");
quote! {
if #field_name.slot.is_none() {
#mri.missing_option(#name)
}
}
}
FieldKind::SubCommand => {
let ty = field.ty_without_wrapper;
quote! {
if #field_name.is_none() {
#mri.missing_subcommands(
<#ty as argh::SubCommands>::COMMANDS
.iter()
.cloned()
.chain(
<#ty as argh::SubCommands>::dynamic_commands()
.iter()
.copied()
),
)
}
}
}
}
})
}
/// Require that a type can be a `switch`.
/// Throws an error for all types except booleans and integers
fn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool {
fn ty_can_be_switch(ty: &syn::Type) -> bool {
if let syn::Type::Path(path) = ty {
if path.qself.is_some() {
return false;
}
if path.path.segments.len() != 1 {
return false;
}
let ident = &path.path.segments[0].ident;
// `Option<bool>` can be used as a `switch`.
if ident == "Option"
&& let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments
&& let GenericArgument::Type(Type::Path(p)) = &args.args[0]
&& p.path.segments[0].ident == "bool"
{
return true;
}
[
"bool", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128",
]
.iter()
.any(|path| ident == path)
} else {
false
}
}
let res = ty_can_be_switch(ty);
if !res {
errors.err(
ty,
"switches must be of type `bool`, `Option<bool>`, or integer type",
);
}
res
}
/// Returns `Some(T)` if a type is `wrapper_name<T>` for any `wrapper_name` in `wrapper_names`.
fn ty_inner<'a>(wrapper_names: &[&str], ty: &'a syn::Type) -> Option<&'a syn::Type> {
if let syn::Type::Path(path) = ty {
if path.qself.is_some() {
return None;
}
// Since we only check the last path segment, it isn't necessarily the case that
// we're referring to `std::vec::Vec` or `std::option::Option`, but there isn't
// a fool proof way to check these since name resolution happens after macro expansion,
// so this is likely "good enough" (so long as people don't have their own types called
// `Option` or `Vec` that take one generic parameter they're looking to parse).
let last_segment = path.path.segments.last()?;
if !wrapper_names.iter().any(|name| last_segment.ident == *name) {
return None;
}
if let syn::PathArguments::AngleBracketed(gen_args) = &last_segment.arguments {
let generic_arg = gen_args.args.first()?;
if let syn::GenericArgument::Type(ty) = &generic_arg {
return Some(ty);
}
}
}
None
}
/// Implements `FromArgs` and `SubCommands` for a `#![derive(FromArgs)]` enum.
fn impl_from_args_enum(
errors: &Errors,
name: &syn::Ident,
type_attrs: &TypeAttrs,
generic_args: &syn::Generics,
de: &syn::DataEnum,
) -> TokenStream {
parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);
// An enum variant like `<name>(<ty>)`
struct SubCommandVariant<'a> {
name: &'a syn::Ident,
ty: &'a syn::Type,
}
let mut dynamic_type_and_variant = None;
let variants: Vec<SubCommandVariant<'_>> = de
.variants
.iter()
.filter_map(|variant| {
let name = &variant.ident;
let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;
if parse_attrs::VariantAttrs::parse(errors, variant)
.is_dynamic
.is_some()
{
if dynamic_type_and_variant.is_some() {
errors.err(variant, "Only one variant can have the `dynamic` attribute");
}
dynamic_type_and_variant = Some((ty, name));
None
} else {
Some(SubCommandVariant { name, ty })
}
})
.collect();
let name_repeating = std::iter::repeat(name.clone());
let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();
let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();
let dynamic_from_args =
dynamic_type_and_variant
.as_ref()
.map(|(dynamic_type, dynamic_variant)| {
quote! {
if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args(
command_name, args) {
return result.map(#name::#dynamic_variant);
}
}
});
let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {
quote! {
fn dynamic_commands() -> &'static [&'static argh::CommandInfo] {
<#dynamic_type as argh::DynamicSubCommand>::commands()
}
}
});
let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();
quote! {
impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {
fn from_args(command_name: &[&str], args: &[&str])
-> std::result::Result<Self, argh::EarlyExit>
{
let subcommand_name = if let Some(subcommand_name) = command_name.last() {
*subcommand_name
} else {
return Err(argh::EarlyExit::from("no subcommand name".to_owned()));
};
#(
if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {
return Ok(#name_repeating::#variant_names(
<#variant_ty as argh::FromArgs>::from_args(command_name, args)?
));
}
)*
#dynamic_from_args
Err(argh::EarlyExit::from("no subcommand matched".to_owned()))
}
}
impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause {
const COMMANDS: &'static [&'static argh::CommandInfo] = &[#(
<#variant_ty as argh::SubCommand>::COMMAND,
)*];
#dynamic_commands
}
}
}
/// Returns `Some(Bar)` if the field is a single-field unnamed variant like `Foo(Bar)`.
/// Otherwise, generates an error.
fn enum_only_single_field_unnamed_variants<'a>(
errors: &Errors,
variant_fields: &'a syn::Fields,
) -> Option<&'a syn::Type> {
macro_rules! with_enum_suggestion {
($help_text:literal) => {
concat!(
$help_text,
"\nInstead, use a variant with a single unnamed field for each subcommand:\n",
" enum MyCommandEnum {\n",
" SubCommandOne(SubCommandOne),\n",
" SubCommandTwo(SubCommandTwo),\n",
" }",
)
};
}
match variant_fields {
syn::Fields::Named(fields) => {
errors.err(
fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` `enum`s do not support variants with named fields."
),
);
None
}
syn::Fields::Unit => {
errors.err(
variant_fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` does not support `enum`s with no variants."
),
);
None
}
syn::Fields::Unnamed(fields) => {
if fields.unnamed.len() != 1 {
errors.err(
fields,
with_enum_suggestion!(
"`#![derive(FromArgs)]` `enum` variants must only contain one field."
),
);
None
} else {
// `unwrap` is okay because of the length check above.
let first_field = fields.unnamed.first().unwrap();
Some(&first_field.ty)
}
}
}
}

View File

@@ -0,0 +1,671 @@
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use super::errors::Errors;
use proc_macro2::Span;
use std::collections::hash_map::{Entry, HashMap};
/// Attributes applied to a field of a `#![derive(FromArgs)]` struct.
#[derive(Default)]
pub struct FieldAttrs {
pub default: Option<syn::LitStr>,
pub description: Option<Description>,
pub from_str_fn: Option<syn::ExprPath>,
pub field_type: Option<FieldType>,
pub long: Option<syn::LitStr>,
pub short: Option<syn::LitChar>,
pub arg_name: Option<syn::LitStr>,
pub greedy: Option<syn::Path>,
pub hidden_help: bool,
}
/// The purpose of a particular field on a `#![derive(FromArgs)]` struct.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum FieldKind {
/// Switches are booleans that are set to "true" by passing the flag.
Switch,
/// Options are `--key value`. They may be optional (using `Option`),
/// or repeating (using `Vec`), or required (neither `Option` nor `Vec`)
Option,
/// Subcommand fields (of which there can be at most one) refer to enums
/// containing one of several potential subcommands. They may be optional
/// (using `Option`) or required (no `Option`).
SubCommand,
/// Positional arguments are parsed literally if the input
/// does not begin with `-` or `--` and is not a subcommand.
/// They are parsed in declaration order, and only the last positional
/// argument in a type may be an `Option`, `Vec`, or have a default value.
Positional,
}
/// The type of a field on a `#![derive(FromArgs)]` struct.
///
/// This is a simple wrapper around `FieldKind` which includes the `syn::Ident`
/// of the attribute containing the field kind.
pub struct FieldType {
pub kind: FieldKind,
pub ident: syn::Ident,
}
/// A description of a `#![derive(FromArgs)]` struct.
///
/// Defaults to the docstring if one is present, or `#[argh(description = "...")]`
/// if one is provided.
pub struct Description {
/// Whether the description was an explicit annotation or whether it was a doc string.
pub explicit: bool,
pub content: syn::LitStr,
}
impl FieldAttrs {
pub fn parse(errors: &Errors, field: &syn::Field) -> Self {
let mut this = Self::default();
for attr in &field.attrs {
if is_doc_attr(attr) {
parse_attr_doc(errors, attr, &mut this.description);
continue;
}
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml
} else {
continue;
};
for meta in ml {
let name = meta.path();
if name.is_ident("arg_name") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_arg_name(errors, m);
}
} else if name.is_ident("default") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_default(errors, m);
}
} else if name.is_ident("description") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
parse_attr_description(errors, m, &mut this.description);
}
} else if name.is_ident("from_str_fn") {
if let Some(m) = errors.expect_meta_list(&meta) {
this.parse_attr_from_str_fn(errors, m);
}
} else if name.is_ident("long") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_long(errors, m);
}
} else if name.is_ident("option") {
parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type);
} else if name.is_ident("short") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_short(errors, m);
}
} else if name.is_ident("subcommand") {
parse_attr_field_type(
errors,
&meta,
FieldKind::SubCommand,
&mut this.field_type,
);
} else if name.is_ident("switch") {
parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type);
} else if name.is_ident("positional") {
parse_attr_field_type(
errors,
&meta,
FieldKind::Positional,
&mut this.field_type,
);
} else if name.is_ident("greedy") {
this.greedy = Some(name.clone());
} else if name.is_ident("hidden_help") {
this.hidden_help = true;
} else {
errors.err(
&meta,
concat!(
"Invalid field-level `argh` attribute\n",
"Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, ",
"`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`",
),
);
}
}
}
if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) {
match field_type.kind {
FieldKind::Option | FieldKind::Positional => {}
FieldKind::SubCommand | FieldKind::Switch => errors.err(
default,
"`default` may only be specified on `#[argh(option)]` \
or `#[argh(positional)]` fields",
),
}
}
match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {
(Some(_), Some(FieldKind::Positional)) => {}
(Some(greedy), Some(_)) => errors.err(
&greedy,
"`greedy` may only be specified on `#[argh(positional)]` \
fields",
),
_ => {}
}
if let Some(d) = &this.description {
check_option_description(errors, d.content.value().trim(), d.content.span());
}
this
}
fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) {
parse_attr_fn_name(errors, m, "from_str_fn", &mut self.from_str_fn)
}
fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "default", &mut self.default);
}
fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "arg_name", &mut self.arg_name);
}
fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "long", &mut self.long);
let long = self.long.as_ref().unwrap();
let value = long.value();
check_long_name(errors, long, &value);
}
fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
if let Some(first) = &self.short {
errors.duplicate_attrs("short", first, m);
} else if let Some(lit_char) = errors.expect_lit_char(&m.value) {
self.short = Some(lit_char.clone());
if !lit_char.value().is_ascii() {
errors.err(lit_char, "Short names must be ASCII");
}
}
}
}
pub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {
if !value.is_ascii() {
errors.err(spanned, "Long names must be ASCII");
}
if !value
.chars()
.all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit())
{
errors.err(
spanned,
"Long names may only contain lowercase letters, digits, and dashes",
);
}
}
fn parse_attr_fn_name(
errors: &Errors,
m: &syn::MetaList,
attr_name: &str,
slot: &mut Option<syn::ExprPath>,
) {
if let Some(first) = slot {
errors.duplicate_attrs(attr_name, first, m);
}
*slot = errors.ok(m.parse_args());
}
fn parse_attr_field_type(
errors: &Errors,
meta: &syn::Meta,
kind: FieldKind,
slot: &mut Option<FieldType>,
) {
if let Some(path) = errors.expect_meta_word(meta) {
if let Some(first) = slot {
errors.duplicate_attrs("field kind", &first.ident, path);
} else if let Some(word) = path.get_ident() {
*slot = Some(FieldType {
kind,
ident: word.clone(),
});
}
}
}
// Whether the attribute is one like `#[<name> ...]`
fn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool {
attr.path().segments.len() == 1 && attr.path().segments[0].ident == name
}
/// Checks for `#[doc ...]`, which is generated by doc comments.
fn is_doc_attr(attr: &syn::Attribute) -> bool {
is_matching_attr("doc", attr)
}
/// Checks for `#[argh ...]`
fn is_argh_attr(attr: &syn::Attribute) -> bool {
is_matching_attr("argh", attr)
}
/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`.
fn argh_attr_to_meta_list(
errors: &Errors,
attr: &syn::Attribute,
) -> Option<impl IntoIterator<Item = syn::Meta>> {
if !is_argh_attr(attr) {
return None;
}
let ml = errors.expect_meta_list(&attr.meta)?;
errors.ok(ml.parse_args_with(
syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
))
}
/// Represents a `#[derive(FromArgs)]` type's top-level attributes.
#[derive(Default)]
pub struct TypeAttrs {
pub is_subcommand: Option<syn::Ident>,
pub name: Option<syn::LitStr>,
pub description: Option<Description>,
pub examples: Vec<syn::LitStr>,
pub notes: Vec<syn::LitStr>,
pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,
/// Arguments that trigger printing of the help message
pub help_triggers: Option<Vec<syn::LitStr>>,
}
impl TypeAttrs {
/// Parse top-level `#[argh(...)]` attributes
pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self {
let mut this = TypeAttrs::default();
for attr in &derive_input.attrs {
if is_doc_attr(attr) {
parse_attr_doc(errors, attr, &mut this.description);
continue;
}
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml
} else {
continue;
};
for meta in ml {
let name = meta.path();
if name.is_ident("description") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
parse_attr_description(errors, m, &mut this.description);
}
} else if name.is_ident("error_code") {
if let Some(m) = errors.expect_meta_list(&meta) {
this.parse_attr_error_code(errors, m);
}
} else if name.is_ident("example") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_example(errors, m);
}
} else if name.is_ident("name") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_name(errors, m);
}
} else if name.is_ident("note") {
if let Some(m) = errors.expect_meta_name_value(&meta) {
this.parse_attr_note(errors, m);
}
} else if name.is_ident("subcommand") {
if let Some(ident) = errors.expect_meta_word(&meta).and_then(|p| p.get_ident())
{
this.parse_attr_subcommand(errors, ident);
}
} else if name.is_ident("help_triggers") {
if let Some(m) = errors.expect_meta_list(&meta) {
Self::parse_help_triggers(m, errors, &mut this);
}
} else {
errors.err(
&meta,
concat!(
"Invalid type-level `argh` attribute\n",
"Expected one of: `description`, `error_code`, `example`, `name`, ",
"`note`, `subcommand`",
),
);
}
}
}
this.check_error_codes(errors);
this
}
/// Checks that error codes are within range for `i32` and that they are
/// never duplicated.
fn check_error_codes(&self, errors: &Errors) {
// map from error code to index
let mut map: HashMap<u64, usize> = HashMap::new();
for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() {
let value = match lit_int.base10_parse::<u64>() {
Ok(v) => v,
Err(e) => {
errors.push(e);
continue;
}
};
if value > (i32::MAX as u64) {
errors.err(lit_int, "Error code out of range for `i32`");
}
match map.entry(value) {
Entry::Occupied(previous) => {
let previous_index = *previous.get();
let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index];
errors.err(lit_int, &format!("Duplicate error code {}", value));
errors.err(
previous_lit_int,
&format!("Error code {} previously defined here", value),
);
}
Entry::Vacant(slot) => {
slot.insert(index);
}
}
}
}
fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) {
errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| {
let err_code = input.parse()?;
input.parse::<syn::Token![,]>()?;
let err_msg = input.parse()?;
if let (Some(err_code), Some(err_msg)) = (
errors.expect_lit_int(&err_code),
errors.expect_lit_str(&err_msg),
) {
self.error_codes.push((err_code.clone(), err_msg.clone()));
}
Ok(())
}));
}
fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_multi_string(errors, m, &mut self.examples)
}
fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_single_string(errors, m, "name", &mut self.name);
if let Some(name) = &self.name
&& name.value() == "help"
{
errors.err(name, "Custom `help` commands are not supported.");
}
}
fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) {
parse_attr_multi_string(errors, m, &mut self.notes)
}
fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) {
if let Some(first) = &self.is_subcommand {
errors.duplicate_attrs("subcommand", first, ident);
} else {
self.is_subcommand = Some(ident.clone());
}
}
// get the list of arguments that trigger printing of the help message as a vector of strings (help_arguments("-h", "--help", "help"))
fn parse_help_triggers(m: &syn::MetaList, errors: &Errors, this: &mut TypeAttrs) {
let parser = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
match parser.parse(m.tokens.clone().into()) {
Ok(args) => {
let mut triggers = Vec::new();
for arg in args {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit_str),
..
}) = arg
{
triggers.push(lit_str);
}
}
this.help_triggers = Some(triggers);
}
Err(err) => errors.push(err),
}
}
}
/// Represents an enum variant's attributes.
#[derive(Default)]
pub struct VariantAttrs {
pub is_dynamic: Option<syn::Path>,
}
impl VariantAttrs {
/// Parse enum variant `#[argh(...)]` attributes
pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {
let mut this = VariantAttrs::default();
let fields = match &variant.fields {
syn::Fields::Named(fields) => Some(&fields.named),
syn::Fields::Unnamed(fields) => Some(&fields.unnamed),
syn::Fields::Unit => None,
};
for field in fields.into_iter().flatten() {
for attr in &field.attrs {
if is_argh_attr(attr) {
err_unused_enum_attr(errors, attr);
}
}
}
for attr in &variant.attrs {
let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {
ml
} else {
continue;
};
for meta in ml {
let name = meta.path();
if name.is_ident("dynamic") {
if let Some(prev) = this.is_dynamic.as_ref() {
errors.duplicate_attrs("dynamic", prev, &meta);
} else {
this.is_dynamic = errors.expect_meta_word(&meta).cloned();
}
} else {
errors.err(
&meta,
"Invalid variant-level `argh` attribute\n\
Variants can only have the #[argh(dynamic)] attribute.",
);
}
}
}
this
}
}
fn check_option_description(errors: &Errors, desc: &str, span: Span) {
let chars = &mut desc.trim().chars();
match (chars.next(), chars.next()) {
(Some(x), _) if x.is_lowercase() => {}
// If both the first and second letter are not lowercase,
// this is likely an initialism which should be allowed.
(Some(x), Some(y)) if !x.is_lowercase() && (y.is_alphanumeric() && !y.is_lowercase()) => {}
_ => {
errors.err_span(span, "Descriptions must begin with a lowercase letter");
}
}
}
fn parse_attr_single_string(
errors: &Errors,
m: &syn::MetaNameValue,
name: &str,
slot: &mut Option<syn::LitStr>,
) {
if let Some(first) = slot {
errors.duplicate_attrs(name, first, m);
} else if let Some(lit_str) = errors.expect_lit_str(&m.value) {
*slot = Some(lit_str.clone());
}
}
fn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) {
if let Some(lit_str) = errors.expect_lit_str(&m.value) {
list.push(lit_str.clone());
}
}
fn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) {
let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) {
nv
} else {
return;
};
// Don't replace an existing explicit description.
if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {
return;
}
if let Some(lit_str) = errors.expect_lit_str(&nv.value) {
let lit_str = if let Some(previous) = slot {
let previous = &previous.content;
let previous_span = previous.span();
syn::LitStr::new(
&(previous.value() + &unescape_doc(lit_str.value())),
previous_span,
)
} else {
syn::LitStr::new(&unescape_doc(lit_str.value()), lit_str.span())
};
*slot = Some(Description {
explicit: false,
content: lit_str,
});
}
}
/// Replaces escape sequences in doc-comments with the characters they represent.
///
/// Rustdoc understands CommonMark escape sequences consisting of a backslash followed by an ASCII
/// punctuation character. Any other backslash is treated as a literal backslash.
fn unescape_doc(s: String) -> String {
let mut result = String::with_capacity(s.len());
let mut characters = s.chars().peekable();
while let Some(mut character) = characters.next() {
if character == '\\'
&& let Some(next_character) = characters.peek()
&& next_character.is_ascii_punctuation()
{
character = *next_character;
characters.next();
}
// Braces must be escaped as this string will be used as a format string
if character == '{' || character == '}' {
result.push(character);
}
result.push(character);
}
result
}
fn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {
let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.value) {
lit_str
} else {
return;
};
// Don't allow multiple explicit (non doc-comment) descriptions
if let Some(description) = slot
&& description.explicit
{
errors.duplicate_attrs("description", &description.content, lit_str);
}
*slot = Some(Description {
explicit: true,
content: lit_str.clone(),
});
}
/// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]`
/// attribute and that it does not have any other type-level `#[argh(...)]` attributes.
pub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) {
let TypeAttrs {
is_subcommand,
name,
description,
examples,
notes,
error_codes,
help_triggers,
} = type_attrs;
// Ensure that `#[argh(subcommand)]` is present.
if is_subcommand.is_none() {
errors.err_span(
*type_span,
concat!(
"`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\n",
"Consider adding `#[argh(subcommand)]` to the `enum` declaration.",
),
);
}
// Error on all other type-level attributes.
if let Some(name) = name {
err_unused_enum_attr(errors, name);
}
if let Some(description) = description
&& description.explicit
{
err_unused_enum_attr(errors, &description.content);
}
if let Some(example) = examples.first() {
err_unused_enum_attr(errors, example);
}
if let Some(note) = notes.first() {
err_unused_enum_attr(errors, note);
}
if let Some(err_code) = error_codes.first() {
err_unused_enum_attr(errors, &err_code.0);
}
if let Some(triggers) = help_triggers
&& let Some(trigger) = triggers.first()
{
err_unused_enum_attr(errors, trigger);
}
}
fn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {
errors.err(
location,
concat!(
"Unused `argh` attribute on `#![derive(FromArgs)]` enum. ",
"Such `enum`s can only be used to dispatch to subcommands, ",
"and should only contain the #[argh(subcommand)] attribute.",
),
);
}

View File

@@ -0,0 +1,19 @@
#![recursion_limit = "256"]
use proc_macro::TokenStream;
mod argh;
mod decodable;
#[proc_macro_derive(Decodable)]
pub fn derive_decodable(input: TokenStream) -> TokenStream {
decodable::derive_decodable(input)
}
/// Entrypoint for `#[derive(FromArgs)]`.
#[proc_macro_derive(FromArgs, attributes(argh))]
pub fn argh_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let token = argh::impl_from_args(&ast);
token.into()
}

View File

@@ -7,6 +7,7 @@ pub use cstr::{
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
};
use cxx_extern::*;
pub use derive;
pub use dir::*;
pub use ffi::{Utf8CStrRef, fork_dont_care, set_nice_name};
pub use files::*;
@@ -14,6 +15,7 @@ pub use logging::*;
pub use misc::*;
pub use result::*;
pub mod argh;
pub mod cstr;
mod cxx_extern;
mod dir;

View File

@@ -1,5 +1,5 @@
use super::argh::{EarlyExit, MissingRequirements};
use crate::{Utf8CStr, Utf8CString, cstr, ffi};
use argh::{EarlyExit, MissingRequirements};
use libc::c_char;
use std::fmt::Arguments;
use std::io::Write;

View File

@@ -17,7 +17,6 @@ cxx = { workspace = true }
byteorder = { workspace = true }
size = { workspace = true }
quick-protobuf = { workspace = true }
argh = { workspace = true }
sha1 = { workspace = true }
sha2 = { workspace = true }
digest = { workspace = true }

View File

@@ -10,7 +10,7 @@ use base::libc::umask;
use base::nix::fcntl::OFlag;
use base::{
CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr,
Utf8CString, WriteExt, cmdline_logging, cstr, log_err,
Utf8CString, WriteExt, argh, cmdline_logging, cstr, log_err,
};
use std::ffi::c_char;
use std::io::{Seek, SeekFrom, Write};

View File

@@ -1,5 +1,10 @@
#![allow(clippy::useless_conversion)]
use argh::FromArgs;
use base::argh;
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap};
use std::fmt::{Display, Formatter};
@@ -9,11 +14,6 @@ use std::mem::size_of;
use std::process::exit;
use std::str;
use argh::FromArgs;
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use crate::check_env;
use crate::compress::{get_decoder, get_encoder};
use crate::ffi::FileFormat;

View File

@@ -1,10 +1,8 @@
use std::cell::UnsafeCell;
use argh::FromArgs;
use base::{LoggedResult, MappedFile, Utf8CStr, argh};
use fdt::node::{FdtNode, NodeProperty};
use fdt::{Fdt, FdtError};
use base::{LoggedResult, MappedFile, Utf8CStr};
use std::cell::UnsafeCell;
use crate::check_env;
use crate::patch::patch_verity;

View File

@@ -21,7 +21,6 @@ pb-rs = { workspace = true }
[dependencies]
base = { workspace = true, features = ["selinux"] }
derive = { workspace = true }
cxx = { workspace = true }
num-traits = { workspace = true }
num-derive = { workspace = true }
@@ -29,6 +28,5 @@ quick-protobuf = { workspace = true }
bytemuck = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
bit-set = { workspace = true }
argh = { workspace = true }
nix = { workspace = true, features = ["fs", "mount", "poll", "signal", "term", "user", "zerocopy"] }
bitflags = { workspace = true }

View File

@@ -1,8 +0,0 @@
use proc_macro::TokenStream;
mod decodable;
#[proc_macro_derive(Decodable)]
pub fn derive_decodable(input: TokenStream) -> TokenStream {
decodable::derive_decodable(input)
}

View File

@@ -9,8 +9,8 @@
use crate::ffi::SuRequest;
use crate::socket::Encodable;
use base::derive::Decodable;
use daemon::{MagiskD, connect_daemon_for_cxx};
use derive::Decodable;
use logging::{android_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
use magisk::magisk_main;
use mount::revert_unmount;

View File

@@ -5,7 +5,7 @@ use crate::mount::find_preinit_device;
use crate::selinux::restorecon;
use crate::socket::{Decodable, Encodable};
use argh::FromArgs;
use base::{CmdArgs, EarlyExitExt, LoggedResult, Utf8CString, clone_attr};
use base::{CmdArgs, EarlyExitExt, LoggedResult, Utf8CString, argh, clone_attr};
use nix::poll::{PollFd, PollFlags, PollTimeout};
use std::ffi::c_char;
use std::os::fd::AsFd;

View File

@@ -6,7 +6,7 @@ use argh::{EarlyExit, FromArgs, MissingRequirements};
use base::libc::PROP_VALUE_MAX;
use base::{
BufReadExt, CmdArgs, EarlyExitExt, LogLevel, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf,
Utf8CString, cstr, debug, log_err, set_log_level_state,
Utf8CString, argh, cstr, debug, log_err, set_log_level_state,
};
use nix::fcntl::OFlag;
use std::collections::BTreeMap;

View File

@@ -16,4 +16,3 @@ no-main = []
[dependencies]
base = { workspace = true }
cxx = { workspace = true }
argh = { workspace = true }

View File

@@ -3,7 +3,8 @@ use crate::statement::format_statement_help;
use argh::FromArgs;
use base::libc::umask;
use base::{
CmdArgs, EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CString, cmdline_logging, cstr, log_err,
CmdArgs, EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CString, argh, cmdline_logging, cstr,
log_err,
};
use std::ffi::c_char;
use std::io::stderr;