refactor!: Rewrote device implementation macro once again
This time with a bit more though put into the design of the code, as a result the macro should be a lot more robust. This did result in the macro getting renamed from LuaDevice to Device as this should be _the_ Device macro. The attribute also got renamed from traits() to device(traits()) and the syntax got overhauled to allow for a bit more expression.
This commit is contained in:
@@ -54,7 +54,7 @@ rumqttc = "0.24.0"
|
|||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
serde_repr = "0.1.20"
|
serde_repr = "0.1.20"
|
||||||
syn = { version = "2.0.106", features = ["extra-traits", "full"] }
|
syn = { version = "2.0.106" }
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
tokio-cron-scheduler = "0.14.0"
|
tokio-cron-scheduler = "0.14.0"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::config::InfoConfig;
|
use automation_lib::config::InfoConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::device::Name;
|
use google_home::device::Name;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{
|
use google_home::traits::{
|
||||||
@@ -19,8 +19,8 @@ pub struct Config {
|
|||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(OnOff)]
|
#[device(traits(OnOff))]
|
||||||
pub struct AirFilter {
|
pub struct AirFilter {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use automation_lib::error::DeviceConfigError;
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::ContactMessage;
|
use automation_lib::messages::ContactMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
use google_home::errors::{DeviceError, ErrorCode};
|
use google_home::errors::{DeviceError, ErrorCode};
|
||||||
use google_home::traits::OpenClose;
|
use google_home::traits::OpenClose;
|
||||||
@@ -48,8 +48,8 @@ struct State {
|
|||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(OpenClose)]
|
#[device(traits(OpenClose))]
|
||||||
pub struct ContactSensor {
|
pub struct ContactSensor {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::net::SocketAddr;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::lua::traits::AddAdditionalMethods;
|
use automation_lib::lua::traits::AddAdditionalMethods;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
@@ -31,8 +31,8 @@ pub struct Config {
|
|||||||
pub flags: FlagIDs,
|
pub flags: FlagIDs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(AddAdditionalMethods)]
|
#[device(traits(AddAdditionalMethods))]
|
||||||
pub struct HueBridge {
|
pub struct HueBridge {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::net::SocketAddr;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
@@ -19,8 +19,8 @@ pub struct Config {
|
|||||||
pub scene_id: String,
|
pub scene_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(OnOff)]
|
#[device(traits(OnOff))]
|
||||||
pub struct HueGroup {
|
pub struct HueGroup {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
|||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
@@ -55,7 +55,7 @@ struct State {
|
|||||||
battery: Option<f32>,
|
battery: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct HueSwitch {
|
pub struct HueSwitch {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::{RemoteAction, RemoteMessage};
|
use automation_lib::messages::{RemoteAction, RemoteMessage};
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ pub struct Config {
|
|||||||
pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
|
pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct IkeaRemote {
|
pub struct IkeaRemote {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::str::Utf8Error;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use bytes::{Buf, BufMut};
|
use bytes::{Buf, BufMut};
|
||||||
use google_home::errors::{self, DeviceError};
|
use google_home::errors::{self, DeviceError};
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
@@ -21,8 +21,8 @@ pub struct Config {
|
|||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(OnOff)]
|
#[device(traits(OnOff))]
|
||||||
pub struct KasaOutlet {
|
pub struct KasaOutlet {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::BrightnessMessage;
|
use automation_lib::messages::BrightnessMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
@@ -34,7 +34,7 @@ pub struct State {
|
|||||||
is_dark: bool,
|
is_dark: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct LightSensor {
|
pub struct LightSensor {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::convert::Infallible;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::lua::traits::AddAdditionalMethods;
|
use automation_lib::lua::traits::AddAdditionalMethods;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::*;
|
use serde_repr::*;
|
||||||
@@ -118,8 +118,8 @@ pub struct Config {
|
|||||||
pub topic: String,
|
pub topic: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(AddAdditionalMethods)]
|
#[device(traits(AddAdditionalMethods))]
|
||||||
pub struct Ntfy {
|
pub struct Ntfy {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use automation_lib::event::OnMqtt;
|
|||||||
use automation_lib::lua::traits::AddAdditionalMethods;
|
use automation_lib::lua::traits::AddAdditionalMethods;
|
||||||
use automation_lib::messages::PresenceMessage;
|
use automation_lib::messages::PresenceMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
@@ -34,8 +34,8 @@ pub struct State {
|
|||||||
current_overall_presence: bool,
|
current_overall_presence: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(AddAdditionalMethods)]
|
#[device(traits(AddAdditionalMethods))]
|
||||||
pub struct Presence {
|
pub struct Presence {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::ActivateMessage;
|
use automation_lib::messages::ActivateMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use eui48::MacAddress;
|
use eui48::MacAddress;
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
@@ -28,7 +28,7 @@ pub struct Config {
|
|||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct WakeOnLAN {
|
pub struct WakeOnLAN {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::messages::PowerMessage;
|
use automation_lib::messages::PowerMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
@@ -33,7 +33,7 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add google home integration
|
// TODO: Add google home integration
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct Washer {
|
pub struct Washer {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::helpers::serialization::state_deserializer;
|
use automation_lib::helpers::serialization::state_deserializer;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig, LuaSerialize};
|
use automation_macro::{Device, LuaDeviceConfig, LuaSerialize};
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff};
|
use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff};
|
||||||
@@ -88,10 +88,10 @@ impl From<StateColorTemperature> for StateBrightness {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(<StateOnOff>: OnOff)]
|
#[device(traits(OnOff for <StateOnOff>, <StateBrightness>, <StateColorTemperature>))]
|
||||||
#[traits(<StateBrightness>: OnOff, Brightness)]
|
#[device(traits(Brightness for <StateBrightness>, <StateColorTemperature>))]
|
||||||
#[traits(<StateColorTemperature>: OnOff, Brightness, ColorSetting)]
|
#[device(traits(ColorSetting for <StateColorTemperature>))]
|
||||||
pub struct Light<T: LightState> {
|
pub struct Light<T: LightState> {
|
||||||
config: Config<T>,
|
config: Config<T>,
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::helpers::serialization::state_deserializer;
|
use automation_lib::helpers::serialization::state_deserializer;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig, LuaSerialize};
|
use automation_macro::{Device, LuaDeviceConfig, LuaSerialize};
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
@@ -80,9 +80,8 @@ impl From<StatePower> for StateOnOff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[traits(<StateOnOff>: OnOff)]
|
#[device(traits(OnOff for <StateOnOff>, <StatePower>))]
|
||||||
#[traits(<StatePower>: OnOff)]
|
|
||||||
pub struct Outlet<T: OutletState> {
|
pub struct Outlet<T: OutletState> {
|
||||||
config: Config<T>,
|
config: Config<T>,
|
||||||
|
|
||||||
|
|||||||
198
automation_macro/src/device.rs
Normal file
198
automation_macro/src/device.rs
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::{Attribute, DeriveInput, Token, parenthesized};
|
||||||
|
|
||||||
|
enum Attr {
|
||||||
|
Trait(TraitAttr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Attr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let ident: syn::Ident = input.parse()?;
|
||||||
|
|
||||||
|
let attr;
|
||||||
|
_ = parenthesized!(attr in input);
|
||||||
|
|
||||||
|
let attr = match ident.to_string().as_str() {
|
||||||
|
"traits" => Attr::Trait(attr.parse()?),
|
||||||
|
_ => return Err(syn::Error::new(ident.span(), "Expected 'traits'")),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TraitAttr {
|
||||||
|
traits: Traits,
|
||||||
|
generics: Generics,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for TraitAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
traits: input.parse()?,
|
||||||
|
generics: input.parse()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Traits(Vec<syn::Ident>);
|
||||||
|
|
||||||
|
impl Traits {
|
||||||
|
fn extend(&mut self, other: &Traits) {
|
||||||
|
self.0.extend_from_slice(&other.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Traits {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
input
|
||||||
|
.call(Punctuated::<_, Token![,]>::parse_separated_nonempty)
|
||||||
|
.map(|traits| traits.into_iter().collect())
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Traits {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let Self(traits) = &self;
|
||||||
|
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#(
|
||||||
|
::automation_lib::lua::traits::#traits::add_methods(methods);
|
||||||
|
)*
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Generics(Vec<syn::AngleBracketedGenericArguments>);
|
||||||
|
|
||||||
|
impl Generics {
|
||||||
|
fn has_generics(&self) -> bool {
|
||||||
|
!self.0.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Generics {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
if !input.peek(Token![for]) {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(Default::default());
|
||||||
|
} else {
|
||||||
|
return Err(input.error("Expected ')' or 'for'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = input.parse::<syn::Token![for]>()?;
|
||||||
|
|
||||||
|
input
|
||||||
|
.call(Punctuated::<_, Token![,]>::parse_separated_nonempty)
|
||||||
|
.map(|generics| generics.into_iter().collect())
|
||||||
|
.map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Implementation {
|
||||||
|
generics: Option<syn::AngleBracketedGenericArguments>,
|
||||||
|
traits: Traits,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(Option<syn::AngleBracketedGenericArguments>, Traits)> for Implementation {
|
||||||
|
fn from(value: (Option<syn::AngleBracketedGenericArguments>, Traits)) -> Self {
|
||||||
|
Self {
|
||||||
|
generics: value.0,
|
||||||
|
traits: value.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl quote::ToTokens for Implementation {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let Self { generics, traits } = &self;
|
||||||
|
|
||||||
|
tokens.extend(quote! {
|
||||||
|
#generics {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_async_function("new", async |_lua, config| {
|
||||||
|
let device: Self = LuaDeviceCreate::create(config)
|
||||||
|
.await
|
||||||
|
.map_err(mlua::ExternalError::into_lua_err)?;
|
||||||
|
|
||||||
|
Ok(device)
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_method("__box", |_lua, this, _: ()| {
|
||||||
|
let b: Box<dyn Device> = Box::new(this.clone());
|
||||||
|
Ok(b)
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) });
|
||||||
|
|
||||||
|
#traits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Implementations(Vec<Implementation>);
|
||||||
|
|
||||||
|
impl From<Vec<Attr>> for Implementations {
|
||||||
|
fn from(attributes: Vec<Attr>) -> Self {
|
||||||
|
let mut all = Traits::default();
|
||||||
|
let mut implementations: HashMap<_, Traits> = HashMap::new();
|
||||||
|
for attribute in attributes {
|
||||||
|
match attribute {
|
||||||
|
Attr::Trait(attribute) => {
|
||||||
|
if attribute.generics.has_generics() {
|
||||||
|
for generic in &attribute.generics.0 {
|
||||||
|
implementations
|
||||||
|
.entry(Some(generic.clone()))
|
||||||
|
.or_default()
|
||||||
|
.extend(&attribute.traits);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
all.extend(&attribute.traits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if implementations.is_empty() {
|
||||||
|
implementations.entry(None).or_default().extend(&all);
|
||||||
|
} else {
|
||||||
|
for traits in implementations.values_mut() {
|
||||||
|
traits.extend(&all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self(implementations.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn device(input: &DeriveInput) -> TokenStream2 {
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let Implementations(imp) = match input
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path().is_ident("device"))
|
||||||
|
.map(Attribute::parse_args)
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
|
{
|
||||||
|
Ok(result) => result.into(),
|
||||||
|
Err(err) => return err.into_compile_error(),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#(
|
||||||
|
impl mlua::UserData for #name #imp
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::{ToTokens, quote};
|
|
||||||
use syn::parse::Parse;
|
|
||||||
use syn::punctuated::Punctuated;
|
|
||||||
use syn::{AngleBracketedGenericArguments, Attribute, DeriveInput, Ident, Path, Token};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Impl {
|
|
||||||
generics: Option<AngleBracketedGenericArguments>,
|
|
||||||
traits: Vec<Path>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Impl {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let generics = if input.peek(Token![<]) {
|
|
||||||
let generics = input.parse()?;
|
|
||||||
input.parse::<Token![:]>()?;
|
|
||||||
|
|
||||||
Some(generics)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let traits: Punctuated<_, _> = input.parse_terminated(Path::parse, Token![,])?;
|
|
||||||
let traits = traits.into_iter().collect();
|
|
||||||
|
|
||||||
Ok(Impl { generics, traits })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Impl {
|
|
||||||
fn generate(&self, name: &Ident) -> TokenStream {
|
|
||||||
let generics = &self.generics;
|
|
||||||
|
|
||||||
// If an identifier is specified, assume it is placed in ::automation_lib::lua::traits,
|
|
||||||
// otherwise use the provided path
|
|
||||||
let traits = self.traits.iter().map(|t| {
|
|
||||||
if let Some(ident) = t.get_ident() {
|
|
||||||
quote! {::automation_lib::lua::traits::#ident }
|
|
||||||
} else {
|
|
||||||
t.to_token_stream()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
impl mlua::UserData for #name #generics {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_async_function("new", async |_lua, config| {
|
|
||||||
let device: Self = LuaDeviceCreate::create(config)
|
|
||||||
.await
|
|
||||||
.map_err(mlua::ExternalError::into_lua_err)?;
|
|
||||||
|
|
||||||
Ok(device)
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_method("__box", |_lua, this, _: ()| {
|
|
||||||
let b: Box<dyn Device> = Box::new(this.clone());
|
|
||||||
Ok(b)
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_async_method("get_id", async |_lua, this, _: ()| { Ok(this.get_id()) });
|
|
||||||
|
|
||||||
#(
|
|
||||||
#traits::add_methods(methods);
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn impl_device_macro(ast: &DeriveInput) -> TokenStream {
|
|
||||||
let name = &ast.ident;
|
|
||||||
|
|
||||||
let impls: TokenStream = ast
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.filter(|attr| attr.path().is_ident("traits"))
|
|
||||||
.flat_map(Attribute::parse_args::<Impl>)
|
|
||||||
.map(|im| im.generate(name))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if impls.is_empty() {
|
|
||||||
Impl::default().generate(name)
|
|
||||||
} else {
|
|
||||||
impls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
#![feature(iter_intersperse)]
|
#![feature(iter_intersperse)]
|
||||||
mod impl_device;
|
#![feature(iterator_try_collect)]
|
||||||
|
mod device;
|
||||||
mod lua_device_config;
|
mod lua_device_config;
|
||||||
|
|
||||||
use lua_device_config::impl_lua_device_config_macro;
|
use lua_device_config::impl_lua_device_config_macro;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{DeriveInput, parse_macro_input};
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
use crate::impl_device::impl_device_macro;
|
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDeviceConfig, attributes(device_config))]
|
#[proc_macro_derive(LuaDeviceConfig, attributes(device_config))]
|
||||||
pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
@@ -15,13 +14,6 @@ pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::T
|
|||||||
impl_lua_device_config_macro(&ast).into()
|
impl_lua_device_config_macro(&ast).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDevice, attributes(traits))]
|
|
||||||
pub fn impl_device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
|
|
||||||
impl_device_macro(&ast).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(LuaSerialize, attributes(traits))]
|
#[proc_macro_derive(LuaSerialize, attributes(traits))]
|
||||||
pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
@@ -37,3 +29,31 @@ pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream
|
|||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derive macro generating an impl for the trait `::mlua::UserData`
|
||||||
|
///
|
||||||
|
/// The `device(traits)` attribute can be used to tell the macro what traits are implemented so that
|
||||||
|
/// the appropriate methods can automatically be registered.
|
||||||
|
/// If the struct does not have any type parameters the syntax is very simple:
|
||||||
|
/// ```
|
||||||
|
/// #[device(traits(TraitA, TraitB))]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// If the type does have type parameters you will have to manually specify all variations that
|
||||||
|
/// have the trait available:
|
||||||
|
/// ```
|
||||||
|
/// #[device(traits(TraitA, TraitB for <StateA>, <StateB>))]
|
||||||
|
/// ```
|
||||||
|
/// If multiple of these attributes are specified they will all combined appropriately.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// # NOTE
|
||||||
|
/// If your type _has_ type parameters any instance of the traits attribute that does not specify
|
||||||
|
/// any type parameters will have the traits applied to _all_ other type parameter variations
|
||||||
|
/// listed in the other trait attributes. This behavior only applies if there is at least one
|
||||||
|
/// instance with type parameters specified.
|
||||||
|
#[proc_macro_derive(Device, attributes(device))]
|
||||||
|
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
|
device::device(&ast).into()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user