add actions derive macro

This commit is contained in:
steven-omaha
2023-01-13 15:25:46 +01:00
parent 3124741efe
commit 9d1b94dca4
5 changed files with 229 additions and 138 deletions
+79
View File
@@ -0,0 +1,79 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::__private::TokenStream2;
pub fn action(input: TokenStream) -> TokenStream {
let input = syn::parse::<DeriveInput>(input).unwrap();
let name = &input.ident;
let enum_data = if let syn::Data::Enum(enum_data) = &input.data {
enum_data
} else {
panic!("`Register` can only be used on enums");
};
let variant_description = generate_variant_description(&enum_data);
let variant_constants = generate_variant_constants(&name, &enum_data);
let expanded = compile_output(name, variant_description, variant_constants);
TokenStream::from(expanded)
}
fn generate_variant_description(
enum_data: &syn::DataEnum,
) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_backend = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_lowercase =
proc_macro2::Literal::string(&variant_name.to_string().to_lowercase());
quote! {
Self::#variant_name => #variant_lowercase,
}
});
variant_matches_backend
}
fn generate_variant_constants<'a>(
name: &'a syn::Ident,
enum_data: &'a syn::DataEnum,
) -> impl Iterator<Item = TokenStream2> + 'a {
let result = enum_data.variants.iter().map(move |variant| {
let variant_name = &variant.ident;
let variant_uppercase = proc_macro2::Ident::new(
&variant_name.to_string().to_uppercase(),
proc_macro2::Span::call_site(),
);
quote! {
pub(crate) const #variant_uppercase: &str = #name::#variant_name.name();
}
});
result
}
fn compile_output<T, U>(
name: &syn::Ident,
variant_description: T,
variant_constants: U,
) -> TokenStream2
where
T: Iterator<Item = TokenStream2>,
U: Iterator<Item = TokenStream2>,
{
let expanded = quote! {
impl #name {
pub const fn name(&self) -> &'static str {
match self {
#(#variant_description)*
}
}
}
#(#variant_constants)*
};
expanded
}
+7 -108
View File
@@ -1,115 +1,14 @@
mod action;
mod register;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::__private::TokenStream2;
#[proc_macro_derive(Register)] #[proc_macro_derive(Register)]
pub fn register(input: TokenStream) -> TokenStream { pub fn register(input: TokenStream) -> TokenStream {
let input = syn::parse::<DeriveInput>(input).unwrap(); register::register(input)
let name = &input.ident;
let enum_data = if let syn::Data::Enum(enum_data) = &input.data {
enum_data
} else {
panic!("`Register` can only be used on enums");
};
let first_variant = &enum_data.variants[0].ident;
let variant_matches_backend = generate_variant_backend(enum_data);
let variant_matches_next = generate_variant_matches_next(enum_data);
let variant_imports = generate_variant_imports(enum_data);
let expanded = compile_output(
name,
first_variant,
variant_matches_backend,
variant_matches_next,
variant_imports,
);
TokenStream::from(expanded)
} }
fn compile_output<T, U, V>( #[proc_macro_derive(Action)]
name: &syn::Ident, pub fn action(input: TokenStream) -> TokenStream {
first_variant: &syn::Ident, action::action(input)
variant_backend: T,
variant_next: U,
variant_imports: V,
) -> TokenStream2
where
T: Iterator<Item = TokenStream2>,
U: Iterator<Item = TokenStream2>,
V: Iterator<Item = TokenStream2>,
{
let expanded = quote! {
#(#variant_imports)*
impl #name {
pub fn iter() -> BackendIter {
BackendIter {
next: Some(Self::#first_variant),
}
}
fn get_backend(&self) -> Box<dyn Backend> {
match self {
#(#variant_backend)*
}
}
fn next(&self) -> Option<Self> {
match self {
#(#variant_next)*
}
}
}
};
expanded
}
fn generate_variant_imports(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_imports = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_module = proc_macro2::Ident::new(
&variant_name.to_string().to_lowercase(),
proc_macro2::Span::call_site(),
);
quote! {
pub(crate) use actual::#variant_module::#variant_name;
}
});
variant_imports
}
fn generate_variant_matches_next(
enum_data: &syn::DataEnum,
) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_next = enum_data.variants.iter().enumerate().map(|(i, variant)| {
let variant_name = &variant.ident;
if i == enum_data.variants.len() - 1 {
quote! {
Self::#variant_name => None,
}
} else {
let next_variant = &enum_data.variants[i + 1].ident;
quote! {
Self::#variant_name => Some(Self::#next_variant),
}
}
});
variant_matches_next
}
fn generate_variant_backend(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_backend = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
quote! {
Self::#variant_name => Box::new(#variant_name::new()),
}
});
variant_matches_backend
} }
+114
View File
@@ -0,0 +1,114 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::__private::TokenStream2;
pub fn register(input: TokenStream) -> TokenStream {
let input = syn::parse::<DeriveInput>(input).unwrap();
let name = &input.ident;
let enum_data = if let syn::Data::Enum(enum_data) = &input.data {
enum_data
} else {
panic!("`Register` can only be used on enums");
};
let first_variant = &enum_data.variants[0].ident;
let variant_matches_backend = generate_variant_backend(enum_data);
let variant_matches_next = generate_variant_matches_next(enum_data);
let variant_imports = generate_variant_imports(enum_data);
let expanded = compile_output(
name,
first_variant,
variant_matches_backend,
variant_matches_next,
variant_imports,
);
TokenStream::from(expanded)
}
fn compile_output<T, U, V>(
name: &syn::Ident,
first_variant: &syn::Ident,
variant_backend: T,
variant_next: U,
variant_imports: V,
) -> TokenStream2
where
T: Iterator<Item = TokenStream2>,
U: Iterator<Item = TokenStream2>,
V: Iterator<Item = TokenStream2>,
{
let expanded = quote! {
#(#variant_imports)*
impl #name {
pub fn iter() -> BackendIter {
BackendIter {
next: Some(Self::#first_variant),
}
}
fn get_backend(&self) -> Box<dyn Backend> {
match self {
#(#variant_backend)*
}
}
fn next(&self) -> Option<Self> {
match self {
#(#variant_next)*
}
}
}
};
expanded
}
fn generate_variant_imports(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_imports = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_module = proc_macro2::Ident::new(
&variant_name.to_string().to_lowercase(),
proc_macro2::Span::call_site(),
);
quote! {
pub(crate) use actual::#variant_module::#variant_name;
}
});
variant_imports
}
fn generate_variant_matches_next(
enum_data: &syn::DataEnum,
) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_next = enum_data.variants.iter().enumerate().map(|(i, variant)| {
let variant_name = &variant.ident;
if i == enum_data.variants.len() - 1 {
quote! {
Self::#variant_name => None,
}
} else {
let next_variant = &enum_data.variants[i + 1].ident;
quote! {
Self::#variant_name => Some(Self::#next_variant),
}
}
});
variant_matches_next
}
fn generate_variant_backend(enum_data: &syn::DataEnum) -> impl Iterator<Item = TokenStream2> + '_ {
let variant_matches_backend = enum_data.variants.iter().map(|variant| {
let variant_name = &variant.ident;
quote! {
Self::#variant_name => Box::new(#variant_name::new()),
}
});
variant_matches_backend
}
+16 -11
View File
@@ -1,11 +1,16 @@
pub(crate) const CLEAN: &str = "clean"; use pacdef_macro::Action;
pub(crate) const EDIT: &str = "edit";
pub(crate) const GROUPS: &str = "groups"; #[derive(Debug, Action)]
pub(crate) const IMPORT: &str = "import"; pub(crate) enum Actions {
pub(crate) const NEW: &str = "new"; Clean,
pub(crate) const REMOVE: &str = "remove"; Edit,
pub(crate) const SEARCH: &str = "search"; Groups,
pub(crate) const SHOW: &str = "show"; Import,
pub(crate) const SYNC: &str = "sync"; New,
pub(crate) const UNMANAGED: &str = "unmanaged"; Remove,
pub(crate) const VERSION: &str = "version"; Search,
Show,
Sync,
Unmanaged,
Version,
}
+13 -19
View File
@@ -6,7 +6,7 @@ use std::path::PathBuf;
use anyhow::{ensure, Context, Result}; use anyhow::{ensure, Context, Result};
use clap::ArgMatches; use clap::ArgMatches;
use crate::action; use crate::action::*;
use crate::args; use crate::args;
use crate::backend::{Backend, Backends, ToDoPerBackend}; use crate::backend::{Backend, Backends, ToDoPerBackend};
use crate::cmd::run_edit_command; use crate::cmd::run_edit_command;
@@ -21,6 +21,7 @@ pub struct Pacdef {
groups: HashSet<Group>, groups: HashSet<Group>,
} }
// TODO review
impl Pacdef { impl Pacdef {
#[must_use] #[must_use]
pub fn new(args: ArgMatches, groups: HashSet<Group>) -> Self { pub fn new(args: ArgMatches, groups: HashSet<Group>) -> Self {
@@ -29,27 +30,20 @@ impl Pacdef {
#[allow(clippy::unit_arg)] #[allow(clippy::unit_arg)]
pub fn run_action_from_arg(self) -> Result<()> { pub fn run_action_from_arg(self) -> Result<()> {
// TODO review
match self.args.subcommand() { match self.args.subcommand() {
Some((action::CLEAN, _)) => Ok(self.clean_packages()), Some((CLEAN, _)) => Ok(self.clean_packages()),
Some((action::EDIT, groups)) => { Some((EDIT, args)) => self.edit_group_files(args).context("editing group files"),
self.edit_group_files(groups).context("editing group files") Some((GROUPS, _)) => Ok(self.show_groups()),
} Some((IMPORT, args)) => self.import_groups(args).context("importing groups"),
Some((action::GROUPS, _)) => Ok(self.show_groups()), Some((NEW, args)) => self.new_groups(args).context("creating new group files"),
Some((action::IMPORT, files)) => self.import_groups(files).context("importing groups"), Some((REMOVE, args)) => self.remove_groups(args).context("removing groups"),
Some((action::NEW, files)) => { Some((SHOW, args)) => self.show_group_content(args).context("showing groups"),
self.new_groups(files).context("creating new group files") Some((SEARCH, args)) => {
}
Some((action::REMOVE, groups)) => self.remove_groups(groups).context("removing groups"),
Some((action::SHOW, groups)) => {
self.show_group_content(groups).context("showing groups")
}
Some((action::SEARCH, args)) => {
search::search_packages(args, &self.groups).context("searching packages") search::search_packages(args, &self.groups).context("searching packages")
} }
Some((action::SYNC, _)) => Ok(self.install_packages()), Some((SYNC, _)) => Ok(self.install_packages()),
Some((action::UNMANAGED, _)) => Ok(self.show_unmanaged_packages()), Some((UNMANAGED, _)) => Ok(self.show_unmanaged_packages()),
Some((action::VERSION, _)) => Ok(self.show_version()), Some((VERSION, _)) => Ok(self.show_version()),
Some((_, _)) => todo!(), Some((_, _)) => todo!(),
None => { None => {
unreachable!("argument parser requires some subcommand to return an `ArgMatches`") unreachable!("argument parser requires some subcommand to return an `ArgMatches`")