diff --git a/pacdef_macro/src/action.rs b/pacdef_macro/src/action.rs new file mode 100644 index 0000000..f12a6a5 --- /dev/null +++ b/pacdef_macro/src/action.rs @@ -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::(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 + '_ { + 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 + '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( + name: &syn::Ident, + variant_description: T, + variant_constants: U, +) -> TokenStream2 +where + T: Iterator, + U: Iterator, +{ + let expanded = quote! { + impl #name { + pub const fn name(&self) -> &'static str { + match self { + #(#variant_description)* + } + } + } + + #(#variant_constants)* + }; + expanded +} diff --git a/pacdef_macro/src/lib.rs b/pacdef_macro/src/lib.rs index c9687ca..dbf6439 100644 --- a/pacdef_macro/src/lib.rs +++ b/pacdef_macro/src/lib.rs @@ -1,115 +1,14 @@ +mod action; +mod register; + use proc_macro::TokenStream; -use quote::quote; -use syn::DeriveInput; -use syn::__private::TokenStream2; #[proc_macro_derive(Register)] pub fn register(input: TokenStream) -> TokenStream { - let input = syn::parse::(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) + register::register(input) } -fn compile_output( - name: &syn::Ident, - first_variant: &syn::Ident, - variant_backend: T, - variant_next: U, - variant_imports: V, -) -> TokenStream2 -where - T: Iterator, - U: Iterator, - V: Iterator, -{ - let expanded = quote! { - #(#variant_imports)* - - impl #name { - pub fn iter() -> BackendIter { - BackendIter { - next: Some(Self::#first_variant), - } - } - fn get_backend(&self) -> Box { - match self { - #(#variant_backend)* - } - } - fn next(&self) -> Option { - match self { - #(#variant_next)* - } - } - } - }; - expanded -} - -fn generate_variant_imports(enum_data: &syn::DataEnum) -> impl Iterator + '_ { - 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 + '_ { - 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 + '_ { - 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 +#[proc_macro_derive(Action)] +pub fn action(input: TokenStream) -> TokenStream { + action::action(input) } diff --git a/pacdef_macro/src/register.rs b/pacdef_macro/src/register.rs new file mode 100644 index 0000000..cdd9787 --- /dev/null +++ b/pacdef_macro/src/register.rs @@ -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::(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( + name: &syn::Ident, + first_variant: &syn::Ident, + variant_backend: T, + variant_next: U, + variant_imports: V, +) -> TokenStream2 +where + T: Iterator, + U: Iterator, + V: Iterator, +{ + let expanded = quote! { + #(#variant_imports)* + + impl #name { + pub fn iter() -> BackendIter { + BackendIter { + next: Some(Self::#first_variant), + } + } + fn get_backend(&self) -> Box { + match self { + #(#variant_backend)* + } + } + fn next(&self) -> Option { + match self { + #(#variant_next)* + } + } + } + }; + expanded +} + +fn generate_variant_imports(enum_data: &syn::DataEnum) -> impl Iterator + '_ { + 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 + '_ { + 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 + '_ { + 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 +} diff --git a/src/action.rs b/src/action.rs index 432ac4f..26b15a1 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,11 +1,16 @@ -pub(crate) const CLEAN: &str = "clean"; -pub(crate) const EDIT: &str = "edit"; -pub(crate) const GROUPS: &str = "groups"; -pub(crate) const IMPORT: &str = "import"; -pub(crate) const NEW: &str = "new"; -pub(crate) const REMOVE: &str = "remove"; -pub(crate) const SEARCH: &str = "search"; -pub(crate) const SHOW: &str = "show"; -pub(crate) const SYNC: &str = "sync"; -pub(crate) const UNMANAGED: &str = "unmanaged"; -pub(crate) const VERSION: &str = "version"; +use pacdef_macro::Action; + +#[derive(Debug, Action)] +pub(crate) enum Actions { + Clean, + Edit, + Groups, + Import, + New, + Remove, + Search, + Show, + Sync, + Unmanaged, + Version, +} diff --git a/src/core.rs b/src/core.rs index 9c1eb3a..23dce67 100644 --- a/src/core.rs +++ b/src/core.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use anyhow::{ensure, Context, Result}; use clap::ArgMatches; -use crate::action; +use crate::action::*; use crate::args; use crate::backend::{Backend, Backends, ToDoPerBackend}; use crate::cmd::run_edit_command; @@ -21,6 +21,7 @@ pub struct Pacdef { groups: HashSet, } +// TODO review impl Pacdef { #[must_use] pub fn new(args: ArgMatches, groups: HashSet) -> Self { @@ -29,27 +30,20 @@ impl Pacdef { #[allow(clippy::unit_arg)] pub fn run_action_from_arg(self) -> Result<()> { - // TODO review match self.args.subcommand() { - Some((action::CLEAN, _)) => Ok(self.clean_packages()), - Some((action::EDIT, groups)) => { - self.edit_group_files(groups).context("editing group files") - } - Some((action::GROUPS, _)) => Ok(self.show_groups()), - Some((action::IMPORT, files)) => self.import_groups(files).context("importing groups"), - Some((action::NEW, files)) => { - self.new_groups(files).context("creating new group files") - } - 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)) => { + Some((CLEAN, _)) => Ok(self.clean_packages()), + Some((EDIT, args)) => self.edit_group_files(args).context("editing group files"), + Some((GROUPS, _)) => Ok(self.show_groups()), + Some((IMPORT, args)) => self.import_groups(args).context("importing groups"), + Some((NEW, args)) => self.new_groups(args).context("creating new group files"), + Some((REMOVE, args)) => self.remove_groups(args).context("removing groups"), + Some((SHOW, args)) => self.show_group_content(args).context("showing groups"), + Some((SEARCH, args)) => { search::search_packages(args, &self.groups).context("searching packages") } - Some((action::SYNC, _)) => Ok(self.install_packages()), - Some((action::UNMANAGED, _)) => Ok(self.show_unmanaged_packages()), - Some((action::VERSION, _)) => Ok(self.show_version()), + Some((SYNC, _)) => Ok(self.install_packages()), + Some((UNMANAGED, _)) => Ok(self.show_unmanaged_packages()), + Some((VERSION, _)) => Ok(self.show_version()), Some((_, _)) => todo!(), None => { unreachable!("argument parser requires some subcommand to return an `ArgMatches`")