#derive #enum #from-str #display #regex

parse-display

Procedural macro to implement Display and FromStr using common settings

15 releases

Uses new Rust 2021

0.6.0 Sep 1, 2022
0.5.5 Mar 10, 2022
0.5.4 Feb 15, 2022
0.5.3 Oct 18, 2021
0.1.0 Jun 15, 2019

#22 in Rust patterns

Download history 8835/week @ 2022-06-09 9674/week @ 2022-06-16 13248/week @ 2022-06-23 11058/week @ 2022-06-30 12370/week @ 2022-07-07 15188/week @ 2022-07-14 9567/week @ 2022-07-21 8577/week @ 2022-07-28 9066/week @ 2022-08-04 8899/week @ 2022-08-11 8079/week @ 2022-08-18 8178/week @ 2022-08-25 10378/week @ 2022-09-01 9765/week @ 2022-09-08 9268/week @ 2022-09-15 6865/week @ 2022-09-22

38,065 downloads per month
Used in 53 crates (29 directly)

MIT/Apache

49KB
263 lines

parse-display

Crates.io Docs.rs Actions Status

This crate provides derive macro Display and FromStr. These macros use common helper attributes to specify the format.

Install

Add this to your Cargo.toml:

[dependencies]
parse-display = "0.6.0"

Example

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("{a}-{b}")]
struct MyStruct {
  a: u32,
  b: u32,
}
assert_eq!(MyStruct { a:10, b:20 }.to_string(), "10-20");
assert_eq!("10-20".parse(), Ok(MyStruct { a:10, b:20 }));


#[derive(Display, FromStr, PartialEq, Debug)]
#[display(style = "snake_case")]
enum MyEnum {
  VarA,
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "var_a");
assert_eq!("var_a".parse(), Ok(MyEnum::VarA));

Helper attributes

Helper attributes can be written in the following positions.

attribute struct enum variant field
#[display("...")]
#[display(style = "...")]
#[display(bound(...))]
#[from_str(bound(...))]
#[from_str(regex = "...")]
#[from_str(new = ...)]
#[from_str(ignore)]
#[from_str(default)]
#[from_str(default_fields(...))]

#[derive(Display)] use #[display].
#[derive(FromStr)] use both #[display] and #[from_str].

key = value style parameter can be specified only once for each key.
key(value1, value2, ...) style parameter can be specified multiple times.

#[display("...")]

Specifies the format using a syntax similar to std::format!(). However, unlike std::format!(), field name is specified in {}.

Struct format

By writing #[display("..")], you can specify the format used by Display and FromStr.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("{a}-{b}")]
struct MyStruct {
  a: u32,
  b: u32,
}
assert_eq!(MyStruct { a:10, b:20 }.to_string(), "10-20");
assert_eq!("10-20".parse(), Ok(MyStruct { a:10, b:20 }));

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("{0}+{1}")]
struct MyTuple(u32, u32);
assert_eq!(MyTuple(10, 20).to_string(), "10+20");
assert_eq!("10+20".parse(), Ok(MyTuple(10, 20)));

Newtype pattern

If the struct has only one field, the format can be omitted. In this case, the only field is used.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
struct NewType(u32);
assert_eq!(NewType(10).to_string(), "10");
assert_eq!("10".parse(), Ok(NewType(10)));

Enum format

In enum, you can specify the format for each variant.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
enum MyEnum {
  #[display("aaa")]
  VarA,
  #[display("bbb")]
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "aaa");
assert_eq!(MyEnum::VarB.to_string(), "bbb");
assert_eq!("aaa".parse(), Ok(MyEnum::VarA));
assert_eq!("bbb".parse(), Ok(MyEnum::VarB));

In enum format, {} means variant name. Variant name style (e.g. snake_case, camelCase, ...) can be specified by #[from_str(style = "...")].

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
enum MyEnum {
  #[display("aaa-{}")]
  VarA,
  #[display("bbb-{}")]
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "aaa-VarA");
assert_eq!(MyEnum::VarB.to_string(), "bbb-VarB");
assert_eq!("aaa-VarA".parse(), Ok(MyEnum::VarA));
assert_eq!("bbb-VarB".parse(), Ok(MyEnum::VarB));

#[derive(Display, FromStr, PartialEq, Debug)]
#[display(style = "snake_case")]
enum MyEnumSnake {
  #[display("{}")]
  VarA,
}
assert_eq!(MyEnumSnake::VarA.to_string(), "var_a");
assert_eq!("var_a".parse(), Ok(MyEnumSnake::VarA));

By writing a format on enum instead of variant, you can specify the format common to multiple variants.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("xxx-{}")]
enum MyEnum {
  VarA,
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "xxx-VarA");
assert_eq!(MyEnum::VarB.to_string(), "xxx-VarB");
assert_eq!("xxx-VarA".parse(), Ok(MyEnum::VarA));
assert_eq!("xxx-VarB".parse(), Ok(MyEnum::VarB));

Unit variants

If all variants has no field, format can be omitted. In this case, variant name is used.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
enum MyEnum {
  VarA,
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "VarA");
assert_eq!(MyEnum::VarB.to_string(), "VarB");
assert_eq!("VarA".parse(), Ok(MyEnum::VarA));
assert_eq!("VarB".parse(), Ok(MyEnum::VarB));

Field format

You can specify the format of the field. In field format, {} means the field itself.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("{a}, {b}")]
struct MyStruct {
  #[display("a is {}")]
  a: u32,
  #[display("b is {}")]
  b: u32,
}
assert_eq!(MyStruct { a:10, b:20 }.to_string(), "a is 10, b is 20");
assert_eq!("a is 10, b is 20".parse(), Ok(MyStruct { a:10, b:20 }));

#[derive(Display, FromStr, PartialEq, Debug)]
#[display("{0}, {1}")]
struct MyTuple(#[display("first is {}")] u32, #[display("next is {}")] u32);
assert_eq!(MyTuple(10, 20).to_string(), "first is 10, next is 20");
assert_eq!("first is 10, next is 20".parse(), Ok(MyTuple(10, 20)));

#[derive(Display, FromStr, PartialEq, Debug)]
enum MyEnum {
  #[display("this is A {0}")]
  VarA(#[display("___{}___")] u32),
}
assert_eq!(MyEnum::VarA(10).to_string(), "this is A ___10___");
assert_eq!("this is A ___10___".parse(), Ok(MyEnum::VarA(10)));

Display field chain

You can use "field chain", e.g. {x.a} .

use parse_display::{Display, FromStr};

#[derive(PartialEq, Debug, Default)]
struct MyStruct {
  a: u32,
  b: u32,
}

#[derive(FromStr, Display, PartialEq, Debug)]
#[display("{x.a}")]
struct FieldChain {
  #[from_str(default)]
  x: MyStruct,
}
assert_eq!(FieldChain { x:MyStruct { a:10, b:20 } }.to_string(), "10");
assert_eq!("10".parse(), Ok(FieldChain { x:MyStruct { a:10, b:0 } }));

When using "field chain", you need to use #[from_str(default)] to implement FromStr.

Format parameter

Like std::format!(), format parameter can be specified.

use parse_display::{Display, FromStr};

#[derive(Display, PartialEq, Debug)]
#[display("{a:>04}")]
struct WithFormatParameter {
  a: u32,
}
assert_eq!(WithFormatParameter { a:5 }.to_string(), "0005");

#[display(style = "...")]

By writing #[display(style = "...")], you can specify the variant name style. The following styles are available.

  • none
  • lowercase
  • UPPERCASE
  • snake_case
  • SNAKE_CASE
  • camelCase
  • CamelCase
  • kebab-case
  • KEBAB-CASE
  • Title Case
  • Title case
  • title case
  • TITLE CASE
use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display(style = "snake_case")]
enum MyEnum {
  VarA,
  VarB,
}
assert_eq!(MyEnum::VarA.to_string(), "var_a");
assert_eq!("var_a".parse(), Ok(MyEnum::VarA));

#[derive(Display, FromStr, PartialEq, Debug)]
enum StyleExample {
  #[display(style = "none")]
  VarA1,
  #[display(style = "none")]
  varA2,
  #[display(style = "lowercase")]
  VarB,
  #[display(style = "UPPERCASE")]
  VarC,
  #[display(style = "snake_case")]
  VarD,
  #[display(style = "SNAKE_CASE")]
  VarE,
  #[display(style = "camelCase")]
  VarF,
  #[display(style = "CamelCase")]
  VarG1,
  #[display(style = "CamelCase")]
  varG2,
  #[display(style = "kebab-case")]
  VarH,
  #[display(style = "KEBAB-CASE")]
  VarI,
  #[display(style = "Title Case")]
  VarJ,
  #[display(style = "Title case")]
  VarK,
  #[display(style = "title case")]
  VarL,
  #[display(style = "TITLE CASE")]
  VarM,
}
assert_eq!(StyleExample::VarA1.to_string(), "VarA1");
assert_eq!(StyleExample::varA2.to_string(), "varA2");
assert_eq!(StyleExample::VarB.to_string(), "varb");
assert_eq!(StyleExample::VarC.to_string(), "VARC");
assert_eq!(StyleExample::VarD.to_string(), "var_d");
assert_eq!(StyleExample::VarE.to_string(), "VAR_E");
assert_eq!(StyleExample::VarF.to_string(), "varF");
assert_eq!(StyleExample::VarG1.to_string(), "VarG1");
assert_eq!(StyleExample::varG2.to_string(), "VarG2");
assert_eq!(StyleExample::VarH.to_string(), "var-h");
assert_eq!(StyleExample::VarI.to_string(), "VAR-I");
assert_eq!(StyleExample::VarJ.to_string(), "Var J");
assert_eq!(StyleExample::VarK.to_string(), "Var k");
assert_eq!(StyleExample::VarL.to_string(), "var l");
assert_eq!(StyleExample::VarM.to_string(), "VAR M");

#[display(bound(...))]

By default, the type of field used in the format is added to the trait bound.

In Rust prior to 1.59, this behavior causes a compile error if you use fields of non public type in public struct.

#![deny(private_in_public)]
use parse_display::Display;

// private type `Inner<T>` in public interface (error E0446)
#[derive(Display)]
pub struct Outer<T>(Inner<T>);

#[derive(Display)]
struct Inner<T>(T);

By writing #[display(bound(...))], you can override the default behavior.

Specify trait bound type

By specifying the type, you can specify the type that need to implement Display and FromStr.

use parse_display::{Display, FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display(bound(T))]
pub struct Outer<T>(Inner<T>);

#[derive(Display, FromStr, PartialEq, Debug)]
struct Inner<T>(T);

assert_eq!(Outer(Inner(10)).to_string(), "10");
assert_eq!("10".parse(), Ok(Outer(Inner(10))));

Specify where predicate

You can also specify the where predicate.

use parse_display::Display;

#[derive(Display)]
#[display(bound(T : std::fmt::Debug))]
pub struct Outer<T>(Inner<T>);

#[derive(Display)]
#[display("{0:?}")]
struct Inner<T>(T);

assert_eq!(Outer(Inner(10)).to_string(), "10");

No trait bounds

You can also remove all trait bounds.

use parse_display::Display;

#[derive(Display)]
#[display(bound())]
pub struct Outer<T>(Inner<T>);

#[derive(Display)]
#[display("ABC")]
struct Inner<T>(T);

assert_eq!(Outer(Inner(10)).to_string(), "ABC");

Default trait bounds

.. means default (automatically generated) trait bounds.

The following example specifies T1 as a trait bound in addition to the default trait bound T2.

use parse_display::Display;

pub struct Inner<T>(T);

#[derive(Display)]
#[display("{0.0}, {1}", bound(T1, ..))]
pub struct Outer<T1, T2>(Inner<T1>, T2);

assert_eq!(Outer(Inner(10), 20).to_string(), "10, 20");

#[from_str(bound(...))]

You can use a different trait bound for Display and FromStr by specifying both #[display(bound(...))] and #[from_str(bound(...))].

use parse_display::*;
use std::{fmt::Display, str::FromStr};

#[derive(Display, FromStr, PartialEq, Debug)]
#[display(bound("T : Display"))]
#[from_str(bound("T : FromStr"))]
pub struct Outer<T>(Inner<T>);

#[derive(Display, FromStr, PartialEq, Debug)]
struct Inner<T>(T);

assert_eq!(Outer(Inner(10)).to_string(), "10");
assert_eq!("10".parse(), Ok(Outer(Inner(10))));

#[from_str(regex = "...")]

Specify the format of the string to be input with FromStr. #[display("...")] is ignored, when this attribute is specified.

Capture name

The capture name corresponds to the field name.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[from_str(regex = "(?P<a>[0-9]+)__(?P<b>[0-9]+)")]
struct MyStruct {
  a: u8,
  b: u8,
}

assert_eq!("10__20".parse(), Ok(MyStruct { a:10, b:20 }));

Field regex

Set #[display("...")] to struct and set #[from_str(regex = "...")] to field, regex is used in the position where field name is specified in #[display("...")].

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[display("{a}__{b}")]
struct MyStruct {
  #[from_str(regex = "[0-9]+")]
  a: u8,

  #[from_str(regex = "[0-9]+")]
  b: u8,
}
assert_eq!("10__20".parse(), Ok(MyStruct { a:10, b:20 }));

If #[from_str(regex = "...")] is not set to field , it operates in the same way as when #[from_str(regex = ".*?")] is set.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[display("{a}{b}")]
struct MyStruct {
  a: String,
  b: String,
}
assert_eq!("abcdef".parse(), Ok(MyStruct { a:"".into(), b:"abcdef".into() }));

Variant name

In the regex specified for enum or variant, empty name capture means variant name.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[from_str(regex = "___(?P<>)___")]
enum MyEnum {
  VarA,

  #[from_str(regex = "xxx(?P<>)xxx")]
  VarB,
}
assert_eq!("___VarA___".parse(), Ok(MyEnum::VarA));
assert_eq!("xxxVarBxxx".parse(), Ok(MyEnum::VarB));

Regex field chain

You can use "field chain" in regex.

use parse_display::FromStr;

#[derive(PartialEq, Debug, Default)]
struct MyStruct {
  a: u32,
}

#[derive(FromStr, PartialEq, Debug)]
#[from_str(regex = "___(?P<x.a>[0-9]+)")]
struct FieldChain {
  #[from_str(default)]
  x: MyStruct,
}
assert_eq!("___10".parse(), Ok(FieldChain { x:MyStruct { a:10 } }));

When using "field chain", you need to use #[from_str(default)].

#[from_str(new = ...)]

If #[from_str(new = ...)] is specified, the value will be initialized with the specified expression instead of the constructor.

The expression must return a value that implement [IntoResult] (e.g. Self, Option<Self>, Result<Self, E>).

In the expression, you can use a variable with the same name as the field name.

use parse_display::FromStr;
#[derive(FromStr, Debug, PartialEq)]
#[from_str(new = Self::new(value))]
struct MyNonZeroUSize {
    value: usize,
}

impl MyNonZeroUSize {
    fn new(value: usize) -> Option<Self> {
        if value == 0 {
            None
        } else {
            Some(Self { value })
        }
    }
}

assert_eq!("1".parse(), Ok(MyNonZeroUSize { value: 1 }));
assert_eq!("0".parse::<MyNonZeroUSize>().is_err(), true);

In tuple struct, variables are named with a leading underscore and their index. (e.g. _0, _1).

use parse_display::FromStr;
#[derive(FromStr, Debug, PartialEq)]
#[from_str(new = Self::new(_0))]
struct MyNonZeroUSize(usize);

impl MyNonZeroUSize {
    fn new(value: usize) -> Option<Self> {
        if value == 0 {
            None
        } else {
            Some(Self(value))
        }
    }
}

assert_eq!("1".parse(), Ok(MyNonZeroUSize(1)));
assert_eq!("0".parse::<MyNonZeroUSize>().is_err(), true);

#[from_str(ignore)]

Specifying this attribute for a variant will not generate FromStr implementation for that variant.

use parse_display::FromStr;

#[derive(Debug, Eq, PartialEq)]
struct CanNotFromStr;

#[derive(FromStr, Debug, Eq, PartialEq)]
#[allow(dead_code)]
enum HasIgnore {
    #[from_str(ignore)]
    A(CanNotFromStr),
    #[display("{0}")]
    B(u32),
}

assert_eq!("1".parse(), Ok(HasIgnore::B(1)));

#[from_str(default)]

If this attribute is specified, the default value is used for fields not included in the input.

If an attribute is specified for struct, the struct's default value is used.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[display("{b}")]
#[from_str(default)]
struct MyStruct {
  a: u32,
  b: u32,
}

impl Default for MyStruct {
  fn default() -> Self {
    Self { a:99, b:99 }
  }
}
assert_eq!("10".parse(), Ok(MyStruct { a:99, b:10 }));

If an attribute is specified for field, the field type's default value is used.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[display("{b}")]
struct MyStruct {
  #[from_str(default)]
  a: u32,
  b: u32,
}

impl Default for MyStruct {
  fn default() -> Self {
    Self { a:99, b:99 }
  }
}
assert_eq!("10".parse(), Ok(MyStruct { a:0, b:10 }));

#[from_str(default_fields(...))]

You can use #[from_str(default_fields(...))] if you want to set default values for the same-named fields of multiple variants.

use parse_display::FromStr;

#[derive(FromStr, PartialEq, Debug)]
#[display("{}-{a}")]
#[from_str(default_fields("b", "c"))]
enum MyEnum {
  VarA { a:u8, b:u8, c:u8 },
  VarB { a:u8, b:u8, c:u8 },
}

assert_eq!("VarA-10".parse(), Ok(MyEnum::VarA { a:10, b:0, c:0 }));
assert_eq!("VarB-10".parse(), Ok(MyEnum::VarB { a:10, b:0, c:0 }));

License

This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-* files for details.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~1.2–2MB
~54K SLoC