Skip to content

realth000/racros

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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);