abscissa_derive-0.9.0/.cargo_vcs_info.json0000644000000001440000000000100141670ustar { "git": { "sha1": "4bc14dee9827f8cef7ea270456536a73faae1525" }, "path_in_vcs": "derive" }abscissa_derive-0.9.0/Cargo.lock0000644000000030750000000000100121500ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "abscissa_derive" version = "0.9.0" dependencies = [ "ident_case", "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" abscissa_derive-0.9.0/Cargo.toml0000644000000024720000000000100121730ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.85" name = "abscissa_derive" version = "0.9.0" authors = ["Tony Arcieri "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Custom derive support for the abscissa application microframework" homepage = "https://github.com/iqlusioninc/abscissa" readme = "README.md" license = "Apache-2.0" repository = "https://github.com/iqlusioninc/abscissa/tree/main/derive" resolver = "2" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--document-private-items"] [lib] name = "abscissa_derive" path = "src/lib.rs" proc-macro = true [dependencies.ident_case] version = "1" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "2" [dependencies.synstructure] version = "0.13" abscissa_derive-0.9.0/Cargo.toml.orig000064400000000000000000000012311046102023000156440ustar 00000000000000[package] name = "abscissa_derive" description = "Custom derive support for the abscissa application microframework" version = "0.9.0" license = "Apache-2.0" authors = ["Tony Arcieri "] homepage = "https://github.com/iqlusioninc/abscissa" repository = "https://github.com/iqlusioninc/abscissa/tree/main/derive" readme = "README.md" edition = "2024" rust-version = "1.85" [lib] proc-macro = true [dependencies] ident_case = "1" proc-macro2 = "1" quote = "1" syn = "2" synstructure = "0.13" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--document-private-items"] # Document all modules abscissa_derive-0.9.0/README.md000064400000000000000000000036311046102023000142420ustar 00000000000000![Abscissa](https://raw.githubusercontent.com/iqlusioninc/abscissa/main/img/abscissa.svg) # abscissa_derive: custom derive macros for Abscissa [![Crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] [![Apache 2.0 Licensed][license-image]][license-link] This crate provides the custom derive implementations used by the [Abscissa] command-line app microframework. Note that this crate isn't meant to be used directly, and you don't need to add it to your `Cargo.toml` file. Instead, just import the relevant types from Abscissa, and the proc macros will be in scope. ## License The **abscissa_derive** crate is distributed under the terms of the Apache License (Version 2.0). Copyright © 2018-2025 iqlusion Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. [//]: # (badges) [crate-image]: https://img.shields.io/crates/v/abscissa_derive.svg?logo=rust [crate-link]: https://crates.io/crates/abscissa_derive [docs-image]: https://docs.rs/abscissa_core/badge.svg [docs-link]: https://docs.rs/abscissa_core/ [build-image]: https://github.com/iqlusioninc/abscissa/actions/workflows/derive.yml/badge.svg [build-link]: https://github.com/iqlusioninc/abscissa/actions/workflows/derive.yml [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg [license-link]: https://github.com/iqlusioninc/abscissa/blob/main/LICENSE [//]: # (general links) [Abscissa]: https://github.com/iqlusioninc/abscissa abscissa_derive-0.9.0/src/command.rs000064400000000000000000000064141046102023000155400ustar 00000000000000//! Custom derive support for `abscissa_core::command::Command`. use proc_macro2::TokenStream; use quote::quote; use synstructure::Structure; /// Custom derive for `abscissa_core::command::Command` pub fn derive_command(s: Structure<'_>) -> TokenStream { let subcommand_usage = quote!(); s.gen_impl(quote! { #[allow(unknown_lints)] #[allow(non_local_definitions)] gen impl Command for @Self { #[doc = "Name of this program as a string"] fn name() -> &'static str { env!("CARGO_PKG_NAME") } #[doc = "Description of this program"] fn description() -> &'static str { env!("CARGO_PKG_DESCRIPTION").trim() } #[doc = "Authors of this program"] fn authors() -> &'static str { env!("CARGO_PKG_AUTHORS") } #subcommand_usage } }) } #[cfg(test)] mod tests { use super::*; use synstructure::test_derive; #[test] fn derive_command_on_struct() { test_derive! { derive_command { struct MyCommand {} } expands to { const _: () = { #[allow(unknown_lints)] #[allow(non_local_definitions)] impl Command for MyCommand { #[doc = "Name of this program as a string"] fn name() -> & 'static str { env!("CARGO_PKG_NAME") } #[doc = "Description of this program"] fn description () -> & 'static str { env!("CARGO_PKG_DESCRIPTION" ).trim() } #[doc = "Authors of this program"] fn authors() -> & 'static str { env!("CARGO_PKG_AUTHORS") } } }; } no_build // tests the code compiles are in the `abscissa` crate } } #[test] fn derive_command_on_enum() { test_derive! { derive_command { enum MyCommand { Foo(A), Bar(B), Baz(C), } } expands to { const _: () = { #[allow(unknown_lints)] #[allow(non_local_definitions)] impl Command for MyCommand { #[doc = "Name of this program as a string"] fn name() -> & 'static str { env!("CARGO_PKG_NAME") } #[doc = "Description of this program"] fn description () -> & 'static str { env!("CARGO_PKG_DESCRIPTION" ).trim() } #[doc = "Authors of this program"] fn authors() -> & 'static str { env!("CARGO_PKG_AUTHORS") } } }; } no_build // tests the code compiles are in the `abscissa` crate } } } abscissa_derive-0.9.0/src/component.rs000064400000000000000000000230011046102023000161130ustar 00000000000000//! Custom derive support for `abscissa_core::component::Component`. use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{DeriveInput, Lit, meta::ParseNestedMeta}; use synstructure::Structure; /// Custom derive for `abscissa_core::component::Component` pub fn derive_component(s: Structure<'_>) -> TokenStream { let attrs = ComponentAttributes::from_derive_input(s.ast()); let abscissa_core = attrs.abscissa_core_crate(); let mut ret = derive_injectable_inner(&s); ret.extend(s.gen_impl(quote! { #[allow(unknown_lints)] #[allow(non_local_definitions)] gen impl Component for @Self where A: #abscissa_core::Application { } })); ret } /// Custom derive for `abscissa_core::component::Injectable` pub fn derive_injectable(s: Structure<'_>) -> TokenStream { derive_injectable_inner(&s) } /// Custom derive for `abscissa_core::component::Injectable` fn derive_injectable_inner(s: &Structure<'_>) -> TokenStream { let attrs = ComponentAttributes::from_derive_input(s.ast()); let name = &s.ast().ident; let abscissa_core = attrs.abscissa_core_crate(); let dependency_methods = attrs.dependency_methods(); s.gen_impl(quote! { #[allow(unknown_lints)] #[allow(non_local_definitions)] gen impl #abscissa_core::component::Injectable for @Self where A: #abscissa_core::Application { #[doc = "Identifier for this component"] fn id(&self) -> #abscissa_core::component::Id { // TODO(tarcieri): use `core::any::type_name` here when stable #abscissa_core::component::Id::new(concat!(module_path!(), "::", stringify!(#name))) } #[doc = "Version of this component"] fn version(&self) -> #abscissa_core::Version { #abscissa_core::Version::parse(env!("CARGO_PKG_VERSION")).unwrap() } #dependency_methods } }) } /// Parsed `#[component(...)]` attribute fields #[derive(Debug)] struct ComponentAttributes { /// Special attribute used by `abscissa_core` to `derive(Component)`. /// /// Workaround for using custom derive on traits defined in the same crate: /// core: bool, /// Dependent components to inject into the current component inject: Vec, } impl ComponentAttributes { /// Parse component attributes from custom derive input. pub fn from_derive_input(input: &DeriveInput) -> Self { let mut core = false; let mut inject = Vec::new(); for attr in &input.attrs { if !attr.path().is_ident("component") { continue; } attr.parse_nested_meta(|nested| { if nested.path.is_ident("core") { core = true; Ok(()) } else if nested.path.is_ident("inject") { inject.push(InjectAttribute::from_nested_meta(&nested)?); Ok(()) } else { Err(nested.error("malformed `component` attribute")) } }) .expect("error parsing meta"); } Self { core, inject } } /// Ident for the `abscissa_core` crate. /// /// Allows `abscissa_core` itself to override this so it can consume its /// own traits/custom derives. pub fn abscissa_core_crate(&self) -> Ident { let crate_name = if self.core { "crate" } else { "abscissa_core" }; Ident::new(crate_name, Span::call_site()) } /// Generate `Component::dependencies()` and `register_dependencies()` pub fn dependency_methods(&self) -> TokenStream { if self.inject.is_empty() { return quote!(); } let abscissa_core = self.abscissa_core_crate(); let ids = self .inject .iter() .map(|inject| inject.id_tokens(&abscissa_core)); let match_arms = self.inject.iter().map(|inject| inject.match_arm()); quote! { fn dependencies(&self) -> std::slice::Iter<'_, #abscissa_core::component::Id> { const DEPENDENCIES: &[#abscissa_core::component::Id] = &[#(#ids),*]; DEPENDENCIES.iter() } fn register_dependency( &mut self, handle: #abscissa_core::component::Handle, dependency: &mut dyn Component, ) -> Result<(), FrameworkError> { match dependency.id().as_ref() { #(#match_arms),* _ => unreachable!() } } } } } /// Attribute declaring a dependency which should be injected #[derive(Debug)] pub struct InjectAttribute(String); impl InjectAttribute { /// Parse an [`InjectAttribute`] from [`NestedMeta`]. pub fn from_nested_meta(meta: &ParseNestedMeta<'_>) -> Result { let Ok(value) = meta.value() else { return Err(meta.error("expected value for `inject` attribute")); }; let Ok(lit) = value.parse::() else { return Err(value.error("expected literal for `inject` value")); }; match lit { Lit::Str(lit_str) => Ok(Self(lit_str.value())), _ => Err(value.error("expected string literal for `inject` value")), } } /// Parse the callback and component ID of the value of an inject attribute. fn parse_value(&self) -> (&str, &str) { assert!( self.0.ends_with(')'), "expected {} to end with ')'", &self.0 ); let mut paren_parts = self.0[..(self.0.len() - 1)].split('('); let callback = paren_parts.next().unwrap(); let component_id = paren_parts.next().unwrap(); assert_eq!(paren_parts.next(), None); (callback, component_id) } /// Get the callback associated with this inject attribute pub fn callback(&self) -> Ident { Ident::new(self.parse_value().0, Span::call_site()) } /// Get the component ID associated with this inject attribute pub fn component_id(&self) -> &str { self.parse_value().1 } /// Get the tokens representing a component ID pub fn id_tokens(&self, abscissa_core: &Ident) -> TokenStream { let component_id = self.component_id(); quote! { #abscissa_core::component::Id::new(#component_id) } } /// Get match arm that invokes a concrete callback pub fn match_arm(&self) -> TokenStream { let id_str = self.component_id(); let callback = self.callback(); quote! { #id_str => { let component_ref = (*dependency).as_mut_any().downcast_mut().unwrap(); self.#callback(component_ref) } } } } #[cfg(test)] mod tests { use super::*; use synstructure::test_derive; #[test] fn derive_component_struct() { // const _: () = { // #[allow(unknown_lints)] // #[allow(non_local_definitions)] // impl abscissa_core::component::Injectable for MyComponent // where // A: abscissa_core::Application, // { // #[doc = "Identifier for this component"] // fn id(&self) -> abscissa_core::component::Id { // abscissa_core::component::Id::new(concat!( // module_path!(), // "::", // stringify!(MyComponent) // )) // } // #[doc = "Version of this component"] // fn version(&self) -> abscissa_core::Version { // abscissa_core::Version::parse(env!("CARGO_PKG_VERSION")).unwrap() // } // } // }; // const _: () = { // #[allow(unknown_lints)] // #[allow(non_local_definitions)] // impl Component for MyComponent where A: abscissa_core::Application {} // }; test_derive! { derive_component { struct MyComponent {} } expands to { const _: () = { #[allow(unknown_lints)] #[allow(non_local_definitions)] impl abscissa_core::component::Injectable for MyComponent where A: abscissa_core::Application { #[doc = "Identifier for this component"] fn id(&self) -> abscissa_core::component::Id { abscissa_core::component::Id::new(concat!( module_path!(), "::", stringify!(MyComponent) )) } #[doc = "Version of this component"] fn version(&self) -> abscissa_core::Version { abscissa_core::Version::parse(env!("CARGO_PKG_VERSION")).unwrap() } } }; const _: () = { #[allow(unknown_lints)] #[allow(non_local_definitions)] impl Component for MyComponent where A: abscissa_core::Application {} }; } no_build // tests the code compiles are in the `abscissa` crate } } } abscissa_derive-0.9.0/src/lib.rs000064400000000000000000000011171046102023000146630ustar 00000000000000#![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/abscissa/main/img/abscissa-sq.svg" )] #![forbid(unsafe_code)] #![warn(rust_2018_idioms, unused_lifetimes, unused_qualifications)] mod command; mod component; mod runnable; use synstructure::decl_derive; decl_derive!([Command] => command::derive_command); decl_derive!([Component, attributes(component)] => component::derive_component); decl_derive!([Injectable, attributes(component)] => component::derive_injectable); decl_derive!([Runnable] => runnable::derive_runnable); abscissa_derive-0.9.0/src/runnable.rs000064400000000000000000000033651046102023000157320ustar 00000000000000use quote::quote; /// Custom derive for `abscissa_core::runnable::Runnable` pub fn derive_runnable(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream { let body = s.each(|bi| { quote! { #bi.run() } }); s.gen_impl(quote! { #[allow(unknown_lints)] #[allow(non_local_definitions)] gen impl Runnable for @Self { fn run(&self) { match *self { #body } } } }) } #[cfg(test)] mod tests { use super::*; use synstructure::test_derive; #[test] fn derive_runnable_on_enum() { test_derive! { derive_runnable { enum MyRunnable { A(VariantA), B(VariantB), C(VariantC), } } expands to { const _: () = { #[allow(unknown_lints)] #[allow(non_local_definitions)] impl Runnable for MyRunnable { fn run(&self) { match *self { MyRunnable::A(ref __binding_0,) => { { __binding_0.run() } } MyRunnable::B(ref __binding_0,) => { { __binding_0.run() } } MyRunnable::C(ref __binding_0,) => { { __binding_0.run() } } } } } }; } no_build // tests the code compiles are in the `abscissa` crate } } }