abscissa_derive-0.7.0/.cargo_vcs_info.json0000644000000001440000000000100141650ustar { "git": { "sha1": "1c9a4952a7052d86d0de13d9ea577a28c4d9fc69" }, "path_in_vcs": "derive" }abscissa_derive-0.7.0/Cargo.toml0000644000000022350000000000100121660ustar # 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 = "2021" rust-version = "1.60" name = "abscissa_derive" version = "0.7.0" authors = ["Tony Arcieri "] 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/abscissa_derive" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--document-private-items"] [lib] proc-macro = true [dependencies.ident_case] version = "1" [dependencies.proc-macro2] version = "1" [dependencies.quote] version = "1" [dependencies.syn] version = "1" [dependencies.synstructure] version = "0.12" abscissa_derive-0.7.0/Cargo.toml.orig000064400000000000000000000012421046102023000156440ustar 00000000000000[package] name = "abscissa_derive" description = "Custom derive support for the abscissa application microframework" version = "0.7.0" license = "Apache-2.0" authors = ["Tony Arcieri "] homepage = "https://github.com/iqlusioninc/abscissa" repository = "https://github.com/iqlusioninc/abscissa/tree/main/abscissa_derive" readme = "README.md" edition = "2021" rust-version = "1.60" [lib] proc-macro = true [dependencies] ident_case = "1" proc-macro2 = "1" quote = "1" syn = "1" synstructure = "0.12" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--document-private-items"] # Document all modules abscissa_derive-0.7.0/README.md000064400000000000000000000036331046102023000142420ustar 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] [![Apache 2.0 Licensed][license-image]][license-link] [![Build Status][build-image]][build-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-2022 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 [crate-link]: https://crates.io/crates/abscissa_derive [docs-image]: https://docs.rs/abscissa_core/badge.svg [docs-link]: https://docs.rs/abscissa_core/ [license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg [license-link]: https://github.com/iqlusioninc/abscissa/blob/main/LICENSE [build-image]: https://github.com/iqlusioninc/abscissa/workflows/derive/badge.svg?branch=main&event=push [build-link]: https://github.com/iqlusioninc/abscissa/actions?query=workflow:derive [//]: # (general links) [Abscissa]: https://github.com/iqlusioninc/abscissa abscissa_derive-0.7.0/src/command.rs000064400000000000000000000062361046102023000155400ustar 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! { 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 { #[allow(non_upper_case_globals)] const _DERIVE_Command_FOR_MyCommand: () = { 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 { #[allow(non_upper_case_globals)] const _DERIVE_Command_FOR_MyCommand: () = { 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.7.0/src/component.rs000064400000000000000000000167571046102023000161350ustar 00000000000000//! Custom derive support for `abscissa_core::component::Component`. use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta}; 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 name = &s.ast().ident; let abscissa_core = attrs.abscissa_core_crate(); let dependency_methods = attrs.dependency_methods(); s.gen_impl(quote! { gen impl Component 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; } match attr.parse_meta().expect("error parsing meta") { Meta::List(MetaList { nested, .. }) => { for meta in &nested { match meta { NestedMeta::Meta(Meta::Path(path)) if path.is_ident("core") => { core = true } NestedMeta::Meta(Meta::NameValue { .. }) => { inject.push(InjectAttribute::from_nested_meta(meta)) } _ => panic!("malformed `component` attribute: {:?}", meta), } } } other => panic!("malformed `component` attribute: {:?}", other), }; } 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: &NestedMeta) -> Self { match meta { NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit: Lit::Str(lit_str), .. })) if path.is_ident("inject") => Self(lit_str.value()), _ => panic!("malformed `component` attribute: {:?}", meta), } } /// 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() { test_derive! { derive_component { struct MyComponent {} } expands to { #[allow(non_upper_case_globals)] const _DERIVE_Component_A_FOR_MyComponent: () = { impl Component 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() } } }; } no_build // tests the code compiles are in the `abscissa` crate } } } abscissa_derive-0.7.0/src/lib.rs000064400000000000000000000007741046102023000146710ustar 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!([Runnable] => runnable::derive_runnable); abscissa_derive-0.7.0/src/runnable.rs000064400000000000000000000032341046102023000157230ustar 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! { 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 { #[allow(non_upper_case_globals)] const _DERIVE_Runnable_FOR_MyRunnable: () = { 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 } } }