Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a parser for flags formatted as bar-separated-values #297

Merged
merged 16 commits into from Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 21 additions & 12 deletions .github/workflows/rust.yml
Expand Up @@ -40,24 +40,36 @@ jobs:
uses: actions/checkout@v2

- name: Install Rust Toolchain
uses: actions-rs/toolchain@v1
with:
override: true
profile: minimal
toolchain: ${{ matrix.channel }}-${{ matrix.rust_target }}
run: rustup default ${{ matrix.channel }}-${{ matrix.rust_target }}

- name: Install cargo-hack
run: cargo install cargo-hack

- name: Powerset
run: cargo hack test --feature-powerset --lib --optional-deps "serde" --depth 3 --skip rustc-dep-of-std
run: cargo hack test --feature-powerset --lib --optional-deps "std serde" --depth 3 --skip rustc-dep-of-std

- name: Docs
run: cargo doc --features example_generated

- name: Smoke test
run: cargo run --manifest-path tests/smoke-test/Cargo.toml

benches:
name: Benches
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2

- name: Install Rust toolchain
run: rustup default nightly

- name: Default features
uses: actions-rs/cargo@v1
with:
command: bench
args: --no-run

embedded:
name: Build (embedded)
runs-on: ubuntu-latest
Expand All @@ -66,12 +78,9 @@ jobs:
uses: actions/checkout@v2

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: thumbv6m-none-eabi
override: true
run: |
rustup default nightly
rustup target add thumbv6m-none-eabi

- name: Default features
uses: actions-rs/cargo@v1
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Expand Up @@ -28,8 +28,10 @@ trybuild = "1.0"
rustversion = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde_test = "1.0"

[features]
std = []
example_generated = []
rustc-dep-of-std = ["core", "compiler_builtins"]

Expand Down
96 changes: 96 additions & 0 deletions benches/parse.rs
@@ -0,0 +1,96 @@
#![feature(test)]

extern crate test;

use std::{
fmt::{self, Display},
str::FromStr,
};

bitflags::bitflags! {
struct Flags10: u32 {
const A = 0b0000_0000_0000_0001;
const B = 0b0000_0000_0000_0010;
const C = 0b0000_0000_0000_0100;
const D = 0b0000_0000_0000_1000;
const E = 0b0000_0000_0001_0000;
const F = 0b0000_0000_0010_0000;
const G = 0b0000_0000_0100_0000;
const H = 0b0000_0000_1000_0000;
const I = 0b0000_0001_0000_0000;
const J = 0b0000_0010_0000_0000;
}
}

impl FromStr for Flags10 {
type Err = bitflags::parser::ParseError;

fn from_str(flags: &str) -> Result<Self, Self::Err> {
Ok(Flags10(flags.parse()?))
}
}

impl Display for Flags10 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

#[bench]
fn format_flags_1_present(b: &mut test::Bencher) {
b.iter(|| Flags10::J.to_string())
}

#[bench]
fn format_flags_5_present(b: &mut test::Bencher) {
b.iter(|| (Flags10::F | Flags10::G | Flags10::H | Flags10::I | Flags10::J).to_string())
}

#[bench]
fn format_flags_10_present(b: &mut test::Bencher) {
b.iter(|| {
(Flags10::A
| Flags10::B
| Flags10::C
| Flags10::D
| Flags10::E
| Flags10::F
| Flags10::G
| Flags10::H
| Flags10::I
| Flags10::J)
.to_string()
})
}

#[bench]
fn parse_flags_1_10(b: &mut test::Bencher) {
b.iter(|| {
let flags: Flags10 = "J".parse().unwrap();
flags
})
}

#[bench]
fn parse_flags_5_10(b: &mut test::Bencher) {
b.iter(|| {
let flags: Flags10 = "F | G | H | I | J".parse().unwrap();
flags
})
}

#[bench]
fn parse_flags_10_10(b: &mut test::Bencher) {
b.iter(|| {
let flags: Flags10 = "A | B | C | D | E | F | G | H | I | J".parse().unwrap();
flags
})
}

#[bench]
fn parse_flags_1_10_hex(b: &mut test::Bencher) {
b.iter(|| {
let flags: Flags10 = "0xFF".parse().unwrap();
flags
})
}
49 changes: 49 additions & 0 deletions examples/fmt.rs
@@ -0,0 +1,49 @@
//! An example of implementing Rust's standard formatting and parsing traits for flags types.

use core::{fmt, str};

fn main() -> Result<(), bitflags::parser::ParseError> {
bitflags::bitflags! {
// You can `#[derive]` the `Debug` trait, but implementing it manually
// can produce output like `A | B` instead of `Flags(A | B)`.
// #[derive(Debug)]
#[derive(PartialEq, Eq)]
pub struct Flags: u32 {
const A = 1;
const B = 2;
const C = 4;
const D = 8;
}
}

impl fmt::Debug for Flags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}

impl fmt::Display for Flags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

impl str::FromStr for Flags {
type Err = bitflags::parser::ParseError;

fn from_str(flags: &str) -> Result<Self, Self::Err> {
Ok(Self(flags.parse()?))
}
}

let flags = Flags::A | Flags::B;

println!("{}", flags);

let formatted = flags.to_string();
let parsed: Flags = formatted.parse()?;

assert_eq!(flags, parsed);

Ok(())
}
36 changes: 36 additions & 0 deletions examples/serde.rs
@@ -0,0 +1,36 @@
//! An example of implementing `serde::Serialize` and `serde::Deserialize`.
//! The `#[serde(transparent)]` attribute is recommended to serialize directly
//! to the underlying bits type without wrapping it in a `serde` newtype.

#[cfg(feature = "serde")]
fn main() {
use serde_derive::*;

bitflags::bitflags! {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct Flags: u32 {
const A = 1;
const B = 2;
const C = 4;
const D = 8;
}
}

let flags = Flags::A | Flags::B;

let serialized = serde_json::to_string(&flags).unwrap();

println!("{:?} -> {}", flags, serialized);

assert_eq!(serialized, r#""A | B""#);

let deserialized: Flags = serde_json::from_str(&serialized).unwrap();

println!("{} -> {:?}", serialized, flags);

assert_eq!(deserialized, flags);
}

#[cfg(not(feature = "serde"))]
fn main() {}
14 changes: 4 additions & 10 deletions src/external.rs
Expand Up @@ -48,9 +48,8 @@ macro_rules! __impl_external_bitflags_serde {
&self,
serializer: S,
) -> $crate::__private::core::result::Result<S::Ok, S::Error> {
$crate::__private::serde_support::serialize_bits_default(
$crate::__private::core::stringify!($InternalBitFlags),
&self.bits,
$crate::__private::serde_support::serialize_bits_default::<$InternalBitFlags, $T, S>(
&self,
serializer,
)
}
Expand All @@ -60,14 +59,9 @@ macro_rules! __impl_external_bitflags_serde {
fn deserialize<D: $crate::__private::serde::Deserializer<'de>>(
deserializer: D,
) -> $crate::__private::core::result::Result<Self, D::Error> {
let bits = $crate::__private::serde_support::deserialize_bits_default(
$crate::__private::core::stringify!($InternalBitFlags),
$crate::__private::serde_support::deserialize_bits_default::<$InternalBitFlags, $T, D>(
deserializer,
)?;

$crate::__private::core::result::Result::Ok($InternalBitFlags::from_bits_retain(
bits,
))
)
}
}
};
Expand Down