2 unstable releases

new 0.2.0 Apr 25, 2024
0.1.0 Dec 9, 2023

#1042 in Rust patterns

MIT license

65KB
996 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] uses Display trait on fields. Default is debug format.

Struct Field Attributes

  • #[debug_name = "foo"] override field name with "foo", if in struct debug_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

~325–780KB
~19K SLoC