3 unstable releases
0.2.1 | Sep 11, 2024 |
---|---|
0.2.0 | Apr 25, 2024 |
0.1.0 | Dec 9, 2023 |
#1189 in Rust patterns
65KB
997 lines
Racros
Collection of rust macros.
Content
AutoDebug
Works on structs and enums, similar to std::fmt::Debug
but support some customization:
- Specify field name or value in print message.
- Ignore specified field.
- Use
std::fmt::Display
instead of [std::fmt:;Debug
] on specified field. - Set print style similar to printing tuple or struct.
Basic Usage
#[derive(AutoDebug)]
makes a struct style debug implementation.
Struct Attributes
#[debug_style = tuple]
makes a tuple style debug implementation. Default is struct style.#[debug_format = display]
usesDisplay
trait on fields. Default is debug format.
Struct Field Attributes
#[debug_name = "foo"]
override field name with "foo", if in structdebug_style
.#[debug_value = "foo"]
override field value with "foo".#[debug_ignore]
will ignore this field in the output.#[debug_debug]
will use [Debug] trait implementation for this field in output.#[debug_display]
will use [Display] trait implementation for this field in output.
Example
For a custom type MyType
that print display MyType
in Display
and debug MyType
in Debug
:
struct MyType {}
impl std::fmt::Debug for MyType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("debug MyType")
}
}
impl std::fmt::Display for MyType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("display MyType")
}
}
Generate implementation with "{:#?}" for each field in debug_struct
on default.
#[derive(AutoDebug)]
struct Foo1 {
#[debug_name = "my_foo1"] // Use "my_foo1" as strcut field name.
foo1: MyType,
#[debug_ignore] // Ignore this field.
foo2: MyType,
#[debug_display] // Use `Display` implementation for this field.
foo3: MyType,
#[debug_value = "foo4, MyType"] // Use "foo4, MyType" as struct field value.
foo4: MyType,
}
#[derive(AutoDebug)]
#[debug_format = "display"] // Default implementation use "{}" for each field.
#[debug_style = "tuple"] // Output style set to `debug_tuple`.
struct Foo2 {
#[debug_debug] // Use `Debug` implementation for this field.
foo1: MyType,
foo2: MyType,
}
let foo1 = Foo1 {
foo1: MyType {},
foo2: MyType {},
foo3: MyType {},
foo4: MyType {},
};
assert_eq!(
std::fmt::format(format_args!("{:#?}", foo1)),
r#"Foo1 {
my_foo1: debug MyType,
foo3: display MyType,
foo4: "foo4, MyType",
}"#
);
let foo2 = Foo2 {
foo1: MyType {},
foo2: MyType {},
};
assert_eq!(
std::fmt::format(format_args!("{:#?}", foo2)),
r#"Foo2(
debug MyType,
display MyType,
)"#
);
AutoStr
Implement TryFrom
String
and ToString
for enums with following features:
- Specify what
String
value can convert from/to. - Allow convert from multiple
String
values. - Set default convert style:
lowercase
UPPERCASE
camelCase
PascalCase
snake_case
SCREAMING_CASE
For the following code:
#[derive(AutoStr)]
enum MyEnum {
E1,
E2,
E3,
}
AutoStr
will generate codes:
impl TryFrom<&str> for MyEnum {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"E1" => Ok(MyEnum::E1),
"E2" => Ok(MyEnum::E2),
"E3" => Ok(MyEnum::E3),
_ => Err(Self::Error::from(
format("failed to convert to {0} :invalid value","MyEnum")
))
}
}
}
impl ToString for MyEnum {
fn to_string(&self) -> String {
match self {
MyEnum::E1 => "E1".to_string(),
MyEnum::E2 => "E2".to_string(),
MyEnum::E3 => "E3".to_string(),
}
}
}
The string format can be set to
lowercase
UPPERCASE
camelCase
PascalCase
snake_case
SCREAMING_CASE
by adding a#[autorule = "xxxx"]
attribute to the enum:
#[derive(AutoStr)]
#[autorule = "lowercase"]
enum MyEnum {
E1,
E2,
E3,
}
impl TryFrom<&str> for MyEnum {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"e1" => Ok(MyEnum::E1),
"e2" => Ok(MyEnum::E2),
"e3" => Ok(MyEnum::E3),
_ => Err(Self::Error::from(
format("failed to convert to {} :invalid value","MyEnum")
))
}
}
}
impl ToString for MyEnum {
fn to_string(&self) -> String {
match self {
MyEnum::E1 => "e1".to_string(),
MyEnum::E2 => "e2".to_string(),
MyEnum::E3 => "e3".to_string(),
}
}
}
In addition, adding the #[str(...)]
attribute to enum field will override the default format.
#[derive(AutoStr)]
#[autorule = "lowercase"]
enum MyEnum {
#[str("e1", "E1")]
E1,
E2,
#[str("e3", "ee")]
E3,
}
impl TryFrom<&str> for MyEnum {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"e1" | "E1" => Ok(MyEnum::E1),
"e2" => Ok(MyEnum::E2),
"e3" | "ee"=> Ok(MyEnum::E3),
_ => Err(Self::Error::from(
format("failed to convert to {} :invalid value","MyEnum")
))
}
}
}
impl ToString for MyEnum {
fn to_string(&self) -> String {
match self {
MyEnum::E1 => "e1".to_string(),
MyEnum::E2 => "e2".to_string(),
MyEnum::E3 => "e3".to_string(),
}
}
}
Support embedded enums:
enum MyEnum4 {
E41(MyEnum),
E42(MyEnum2),
}
impl TryFrom<&str> for MyEnum4 {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
_ => {
let mut fallback_field: Option<&str> = None;
let mut fallback_result: Option<Self> = None;
if let Ok(v) = MyEnum::try_from(value) {
if fallback_result.is_some() {
return Err(
Self::Error::from({
format!(
"#[str(...)] attribute not set and fallback guess is ambiguous: both {} and {} can accept this convert from \"{}\"",
fallback_field.unwrap(),
"MyEnum",
value,
)
}),
);
}
fallback_field = Some("MyEnum");
fallback_result = Some(MyEnum4::E41(v));
}
if let Ok(v) = MyEnum2::try_from(value) {
if fallback_result.is_some() {
return Err(
Self::Error::from({
format_args!(
"#[str(...)] attribute not set and fallback guess is ambiguous: both {} and {} can accept this convert from \"{}\"",
fallback_field.unwrap(),
"MyEnum2",
value,
)
}),
);
}
fallback_field = Some("MyEnum2");
fallback_result = Some(MyEnum4::E42(v));
}
match fallback_result {
Some(v) => Ok(v),
None => {
Err(
Self::Error::from({
format_args!(
"failed to convert to {} :invalid value \"{}\"",
"MyEnum4",
value,
)
}),
)
}
}
}
}
}
}
impl ToString for MyEnum4 {
fn to_string(&self) -> String {
match self {
MyEnum4::E41(v) => v.to_string(),
MyEnum4::E42(v) => v.to_string(),
}
}
}
CopyWith
Add a copy_with
function for decorated type, copy value from another Self
if that value is not default
value.
Basic Usage
For the following struct, generate:
struct MyStruct {
foo1: i8,
foo2: String,
foo3: Option<String>,
}
impl MyStruct {
fn copy_with(&mut self, other: &Self) {
if other.foo1 != i8::default() {
self.foo1 = other.foo1.clone();
}
if other.foo2 != String::default() {
self.foo2 = other.foo2.clone();
}
if other.foo3 != Option::default() {
self.foo3 = other.foo3.clone();
}
}
}
Field Attributes
Add #[copy]
attribute to a field will try to call .copy_with()
on that field instead of directly comparing values.
Example
use racros::CopyWith;
#[derive(Clone, Default, CopyWith)]
struct MyStruct {
foo1: i8,
foo2: String,
foo3: Option<String>,
}
#[derive(CopyWith)]
struct MyStruct2 {
#[copy]
bar1: MyStruct,
}
let s1 = MyStruct::default();
let mut s11 = MyStruct::default();
let s2 = MyStruct {
foo1: 64,
foo2: String::from("hello world"),
foo3: Some(String::from("hello world")),
};
let mut s21 = MyStruct {
foo1: 64,
foo2: String::from("hello world"),
foo3: Some(String::from("hello world")),
};
s11.copy_with(&s2);
assert_eq!(s11.foo1, s2.foo1); // Copy s2.foo1 to s11.foo1
assert_eq!(s11.foo2, s2.foo2); // Copy s2.foo2 to s11.foo2
assert_eq!(s11.foo3, s2.foo3); // Copy s2.foo3 to s11.foo3
s21.copy_with(&s1);
assert_eq!(s21.foo1, s2.foo1); // Not copy because s1.foo1 is default value.
assert_eq!(s21.foo2, s2.foo2); // Not copy because s1.foo2 is default value.
assert_eq!(s21.foo3, s2.foo3); // Not copy because s1.foo3 is default value.
let mut s31 = MyStruct2 {
bar1: MyStruct::default(),
};
let s32 = MyStruct2 {
bar1: MyStruct {
foo1: 64,
foo2: String::from("hello world"),
foo3: Some(String::from("hello world")),
},
};
s31.copy_with(&s32); // Here use `s32.copy_with()` because bar1 has `#[copy]` attribute.
assert_eq!(s31.bar1.foo1, s2.foo1);
assert_eq!(s31.bar1.foo2, s2.foo2);
assert_eq!(s31.bar1.foo3, s2.foo3);
BundleText
Bundle text content or command output into static str at compile time and use in runtime.
Usage
- Bundle file: #[bundle(name = "get_file", file = "file/path")]
- Bundle command: #[bundle(name = "get_rustc_version", command = "rustc --version")]
Example
use std::io::Read;
use std::process::Stdio;
use racros::BundleText;
#[derive(BundleText)]
#[bundle(name = "some_file", file = "data/text")]
#[bundle(name = "my_rustc_version", command = "rustc --version")]
enum Bundler{}
assert_eq!(
Bundler::some_file(),
r#"Some Text
To Read"#
);
let mut stdout = String::new();
std::process::Command::new("rustc")
.arg("--version")
.stdout(Stdio::piped())
.spawn()
.unwrap()
.stdout
.unwrap()
.read_to_string(&mut stdout)
.expect("failed to run rustc");
assert_eq!(Bundler::my_rustc_version(), stdout);
Dependencies
~250–700KB
~17K SLoC