feat: implement StringLiteral macro
This commit is contained in:
60
Cargo.lock
generated
60
Cargo.lock
generated
@@ -428,8 +428,18 @@ version = "0.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.21.3",
|
||||||
"darling_macro",
|
"darling_macro 0.21.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.23.0",
|
||||||
|
"darling_macro 0.23.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -446,13 +456,37 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||||
|
dependencies = [
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.21.3",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.23.0",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
@@ -885,6 +919,7 @@ dependencies = [
|
|||||||
"pyo3-async-runtimes",
|
"pyo3-async-runtimes",
|
||||||
"python-utils",
|
"python-utils",
|
||||||
"snafu",
|
"snafu",
|
||||||
|
"string-literal",
|
||||||
"strum",
|
"strum",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2046,7 +2081,7 @@ version = "3.16.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.21.3",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -2188,6 +2223,23 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e"
|
checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string-literal"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"string-literal-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string-literal-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.23.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ members = [
|
|||||||
"python-utils",
|
"python-utils",
|
||||||
"python-utils-macros",
|
"python-utils-macros",
|
||||||
"python-utils-macros-impl",
|
"python-utils-macros-impl",
|
||||||
|
"string-literal",
|
||||||
|
"string-literal-macros",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pyo3 = { workspace = true }
|
|||||||
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
||||||
python-utils = { path = "../python-utils", features = ["macros"] }
|
python-utils = { path = "../python-utils", features = ["macros"] }
|
||||||
snafu = { workspace = true }
|
snafu = { workspace = true }
|
||||||
|
string-literal = { path = "../string-literal", features = ["macros"] }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { optional = true, workspace = true }
|
tracing = { optional = true, workspace = true }
|
||||||
|
|||||||
@@ -4,39 +4,14 @@ use std::str::FromStr;
|
|||||||
use pyo3::FromPyObject;
|
use pyo3::FromPyObject;
|
||||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
|
use string_literal::StringLiteral;
|
||||||
|
|
||||||
use crate::{entity_id::EntityId, state_object::StateObject};
|
use crate::{entity_id::EntityId, state_object::StateObject};
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStrLiteral) / #[literal = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
|
#[derive(Debug, Clone, StringLiteral, FromPyFromStr, ToStrToPy)]
|
||||||
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
#[string_literal(value = "state_changed")]
|
||||||
pub struct Type;
|
pub struct Type;
|
||||||
|
|
||||||
/// expected a string of value "state_changed", but got {actual}
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub struct ParseTypeError {
|
|
||||||
actual: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Type {
|
|
||||||
type Err = ParseTypeError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
if s == "state_changed" {
|
|
||||||
Ok(Self)
|
|
||||||
} else {
|
|
||||||
Err(ParseTypeError {
|
|
||||||
actual: s.to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Type {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "state_changed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, FromPyObject)]
|
#[derive(Debug, FromPyObject)]
|
||||||
#[pyo3(from_item_all)]
|
#[pyo3(from_item_all)]
|
||||||
pub struct Data<
|
pub struct Data<
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
use std::{future::Future, sync::Arc};
|
use std::{future::Future, sync::Arc};
|
||||||
|
|
||||||
use emitter_and_signal::{Signal, SignalExt};
|
use emitter_and_signal::{Signal, SignalExt};
|
||||||
use pyo3::{
|
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
||||||
exceptions::{PyException, PyValueError},
|
use python_utils::{FromPyFromStr, FromPyObjectViaParse, ToStrToPy};
|
||||||
prelude::*,
|
use snafu::{ResultExt, Snafu};
|
||||||
};
|
use string_literal::StringLiteral;
|
||||||
use python_utils::FromPyObjectViaParse;
|
|
||||||
use snafu::{ensure, ResultExt, Snafu};
|
|
||||||
|
|
||||||
use super::super::state_classes::measurement::Measurement;
|
use super::super::state_classes::measurement::Measurement;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -18,45 +16,10 @@ use crate::{
|
|||||||
unit_of_measurement::power::UnitOfMeasurement,
|
unit_of_measurement::power::UnitOfMeasurement,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy, StringLiteral, FromPyFromStr, ToStrToPy)]
|
||||||
|
#[string_literal(value = "power")]
|
||||||
struct Power;
|
struct Power;
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractPowerError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// the string {actual:?} is not "power" like it's supposed to be
|
|
||||||
NotPower { actual: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractPowerError> for PyErr {
|
|
||||||
fn from(error: ExtractPowerError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractPowerError::ExtractStringError { .. } => PyException::new_err(error.to_string()),
|
|
||||||
ExtractPowerError::NotPower { .. } => PyValueError::new_err(error.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStrLiteral) / #[literal = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for Power {
|
|
||||||
type Error = ExtractPowerError;
|
|
||||||
|
|
||||||
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let string: &str = obj.extract().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
string == "power",
|
|
||||||
NotPowerSnafu {
|
|
||||||
actual: string.to_owned()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, FromPyObject)]
|
#[derive(Debug, FromPyObject)]
|
||||||
#[pyo3(from_item_all)]
|
#[pyo3(from_item_all)]
|
||||||
pub struct PowerSensorAttributes {
|
pub struct PowerSensorAttributes {
|
||||||
|
|||||||
@@ -1,48 +1,6 @@
|
|||||||
use pyo3::{
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
exceptions::{PyException, PyValueError},
|
use string_literal::StringLiteral;
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use snafu::{ensure, ResultExt, Snafu};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy, StringLiteral, FromPyFromStr, ToStrToPy)]
|
||||||
|
#[string_literal(value = "measurement")]
|
||||||
pub struct Measurement;
|
pub struct Measurement;
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub enum ExtractMeasurementError {
|
|
||||||
/// couldn't extract the object as a string
|
|
||||||
ExtractStringError { source: PyErr },
|
|
||||||
|
|
||||||
/// the string {actual:?} is not "measurement" like it's supposed to be
|
|
||||||
NotMeasurement { actual: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ExtractMeasurementError> for PyErr {
|
|
||||||
fn from(error: ExtractMeasurementError) -> Self {
|
|
||||||
match &error {
|
|
||||||
ExtractMeasurementError::ExtractStringError { .. } => {
|
|
||||||
PyException::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
ExtractMeasurementError::NotMeasurement { .. } => {
|
|
||||||
PyValueError::new_err(error.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: replace with a derive(PyFromStrLiteral) / #[literal = "state_changed"] once I learn how to make something like that and see about serde or strum integration or inspiration
|
|
||||||
impl<'a, 'py> FromPyObject<'a, 'py> for Measurement {
|
|
||||||
type Error = ExtractMeasurementError;
|
|
||||||
|
|
||||||
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
|
||||||
let string: &str = obj.extract().context(ExtractStringSnafu)?;
|
|
||||||
|
|
||||||
ensure!(
|
|
||||||
string == "measurement",
|
|
||||||
NotMeasurementSnafu {
|
|
||||||
actual: string.to_owned()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
14
string-literal-macros/Cargo.toml
Normal file
14
string-literal-macros/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "string-literal-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
darling = "0.23.0"
|
||||||
|
proc-macro2 = { workspace = true }
|
||||||
|
quote = { workspace = true }
|
||||||
|
syn = { workspace = true }
|
||||||
88
string-literal-macros/src/lib.rs
Normal file
88
string-literal-macros/src/lib.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use darling::FromDeriveInput;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use syn::{Ident, LitStr, Path, parse_quote, parse2};
|
||||||
|
|
||||||
|
#[derive(FromDeriveInput)]
|
||||||
|
#[darling(supports(struct_unit))]
|
||||||
|
#[darling(attributes(string_literal))]
|
||||||
|
struct StringLiteral {
|
||||||
|
ident: Ident,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for StringLiteral {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
|
let Self { ident, value } = self;
|
||||||
|
|
||||||
|
dbg!("reached this point");
|
||||||
|
|
||||||
|
let as_ref_trait: Path = parse_quote!(::core::convert::AsRef);
|
||||||
|
let display_trait: Path = parse_quote!(::std::fmt::Display);
|
||||||
|
let from_trait: Path = parse_quote!(::core::convert::From);
|
||||||
|
let from_str_trait: Path = parse_quote!(::std::str::FromStr);
|
||||||
|
let result: Path = parse_quote!(::core::result::Result);
|
||||||
|
let wrong_literal_error_generic: Path = parse_quote!(::string_literal::WrongLiteralError);
|
||||||
|
let wrong_literal_error: Path = parse_quote!(#wrong_literal_error_generic<#ident>);
|
||||||
|
let write_macro: Path = parse_quote!(::std::write);
|
||||||
|
|
||||||
|
let value_literal = LitStr::new(value.as_str(), Span2::call_site());
|
||||||
|
|
||||||
|
dbg!("reached this point");
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
impl #from_trait<#ident> for &'static str {
|
||||||
|
fn from(unit: #ident) -> Self {
|
||||||
|
#value_literal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #from_str_trait for #ident {
|
||||||
|
type Err = #wrong_literal_error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> #result<Self, Self::Err> {
|
||||||
|
if s == #value_literal {
|
||||||
|
Ok(Self)
|
||||||
|
} else {
|
||||||
|
Err(#wrong_literal_error_generic { actual: s.to_owned(), expected: #ident })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #display_trait for #ident {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||||
|
#write_macro!(f, "state_changed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #as_ref_trait<str> for #ident {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
#value_literal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
dbg!(&output.to_string());
|
||||||
|
|
||||||
|
tokens.extend(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_literal_impl(input: TokenStream2) -> TokenStream2 {
|
||||||
|
let derive_input = match parse2(input) {
|
||||||
|
Ok(derive_input) => derive_input,
|
||||||
|
Err(error) => return error.into_compile_error(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let string_literal = match StringLiteral::from_derive_input(&derive_input) {
|
||||||
|
Ok(string_literal) => string_literal,
|
||||||
|
Err(error) => return error.write_errors(),
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! { #string_literal }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro_derive(StringLiteral, attributes(string_literal))]
|
||||||
|
pub fn string_literal(input: TokenStream) -> TokenStream {
|
||||||
|
string_literal_impl(input.into()).into()
|
||||||
|
}
|
||||||
11
string-literal/Cargo.toml
Normal file
11
string-literal/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "string-literal"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
macros = ["dep:string-literal-macros"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
string-literal-macros = { optional = true, path = "../string-literal-macros" }
|
||||||
24
string-literal/src/lib.rs
Normal file
24
string-literal/src/lib.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
#[cfg(feature = "macros")]
|
||||||
|
pub use string_literal_macros::StringLiteral;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WrongLiteralError<S> {
|
||||||
|
pub actual: String,
|
||||||
|
pub expected: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Display for WrongLiteralError<S>
|
||||||
|
where
|
||||||
|
S: AsRef<str>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let Self { actual, expected } = self;
|
||||||
|
let expected_str = expected.as_ref();
|
||||||
|
|
||||||
|
write!(f, "expected {expected_str:?} but got {actual:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> std::error::Error for WrongLiteralError<S> where WrongLiteralError<S>: Debug + Display {}
|
||||||
Reference in New Issue
Block a user