sortbyderive

This crate provides 3 derive macros SortBy, EnumAccessor and EnumSequence.

Usage

SortBy

Fields that should be used for sorting are marked with the attribute #[sort_by]. Other fields will be ignored.

Alternatively, or in combination with, a struct-level or enum-level #[sort_by(method1(),method2(),attr1,nested.attr)] can be declared. This top-level declaration takes precedence, fields comparison will be considered if top-level comparisons are all eq. The top-level sort_by attribute takes a list of attributes or method calls; items will be prepended with self..

Example

```rust

[derive(SortBy)]

[sort_by(somemethod())]

struct Something { #[sortby] a: u16, #[sortby] c: u32, b: f32 } ```

will expand to:

rust impl std::hash::Hash for Something { fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.somemethod().hash(state); self.a.hash(state); self.c.hash(state); } } impl core::cmp::Eq for Something {} impl core::cmp::PartialEq<Self> for Something { fn eq(&self, other: &Self) -> bool { self.cmp(other).is_eq() } } impl core::cmp::PartialOrd<Self> for Something { fn partial_cmp( &self, other: &Self, ) -> core::option::Option<core::cmp::Ordering> { std::option::Option::Some(self.cmp(other)) } } impl core::cmp::Ord for Something { fn cmp(&self, other: &Self) -> core::cmp::Ordering { core::cmp::Ord::cmp(&self.somemethod(), &other.somemethod()) .then_with(|| self.a.cmp(&other.a)) .then_with(|| self.c.cmp(&other.c)) } }

EnumAccessor

Attributes are declared at top-level.

Methods without arguments ( i.e. only &self are also supported ). It takes the form: #[accessor(method_name(): type)].

Example

Say we have a series of midi events, they are very similar but with slight variations - they always have some timing information but they may not always have a pitch or channel.

Using #[accessor(global_time: usize)], a global_time(&self) method is derived, along with a global_time_mut(&mut self), so without any boilerplate you can access the timing.

By declaring #[accessor(channel: u8, (CC))], channel(&self) and channel_mut(&mut self) are derived, but they return Some for NoteOn and NoteOff, and None for CC and Unsupported.

```rust

[derive(EnumAccessor)]

[accessor(global_time: usize)]

[accessor(channel: u8, (CC))]

[accessor(pitch: u8, (CC, Unsupported))]

enum Note { NoteOn(NoteOn), NoteOff(NoteOff), CC(CC), Unsupported { global_time: usize, rawdata: Vec } } ```

expands to:

rust pub trait NoteAccessor { fn global_time(&self) -> &usize; fn global_time_mut(&mut self) -> &mut usize; fn channel(&self) -> std::option::Option<&u8>; fn channel_mut(&mut self) -> std::option::Option<&mut u8>; fn pitch(&self) -> std::option::Option<&u8>; fn pitch_mut(&mut self) -> std::option::Option<&mut u8>; } impl NoteAccessor for Note { fn global_time(&self) -> &usize { match self { Self::NoteOn(x) => &x.global_time, Self::NoteOff(x) => &x.global_time, Self::CC(x) => &x.global_time, Self::Unsupported { global_time, .. } => global_time, } } fn global_time_mut(&mut self) -> &mut usize { match self { Self::NoteOn(x) => &mut x.global_time, Self::NoteOff(x) => &mut x.global_time, Self::CC(x) => &mut x.global_time, Self::Unsupported { global_time, .. } => global_time, } } fn channel(&self) -> std::option::Option<&u8> { match self { Self::NoteOn(x) => std::option::Option::Some(&x.channel), Self::NoteOff(x) => std::option::Option::Some(&x.channel), Self::CC(x) => std::option::Option::Some(&x.channel), Self::Unsupported { .. } => std::option::Option::None, } } fn channel_mut(&mut self) -> std::option::Option<&mut u8> { match self { Self::NoteOn(x) => std::option::Option::Some(&mut x.channel), Self::NoteOff(x) => std::option::Option::Some(&mut x.channel), Self::CC(x) => std::option::Option::Some(&mut x.channel), Self::Unsupported { .. } => std::option::Option::None, } } fn pitch(&self) -> std::option::Option<&u8> { match self { Self::NoteOn(x) => std::option::Option::Some(&x.pitch), Self::NoteOff(x) => std::option::Option::Some(&x.pitch), Self::CC(_) => std::option::Option::None, Self::Unsupported { .. } => std::option::Option::None, } } fn pitch_mut(&mut self) -> std::option::Option<&mut u8> { match self { Self::NoteOn(x) => std::option::Option::Some(&mut x.pitch), Self::NoteOff(x) => std::option::Option::Some(&mut x.pitch), Self::CC(_) => std::option::Option::None, Self::Unsupported { .. } => std::option::Option::None, } } }

Example of method call

The General form is #[accessor(method():type)] :

```rust

[derive(EnumAccessor)]

[accessor(method():type)]

enum E {

} ```

As for field access, declaring an exception will make the actual return type an Option<type>.

Named fields is supported, it will consider that the named field is of type Fn() -> type, and call it.

An intricate example:

```rust struct A { f1: u8, f2: u8 }

impl A { fn sum(&self) -> u8 { self.f1 + self.f2 } }

struct B { values: Vec }

impl B { fn sum(&self) -> u8 { self.values.iter().sum() } }

[derive(EnumAccessor)]

[accessor(sum():u8)]

enum E { A(A), B(B), C{sum: Box u8>} }

[test]

fn test_sum() { let a = E::A(A{ f1: 10, f2: 22 }); let b = E::B(B{ values: vec![9,4,3,2] }); let factor = Arc::new(AtomicU8::new(1));

let c = {
    let factor = factor.clone();
    E::C { sum: Box::new(move || 21 * factor.load(Ordering::Relaxed)) }
};

assert_eq!(32, a.sum());
assert_eq!(18, b.sum());
assert_eq!(21, c.sum());
factor.store(2, Ordering::Relaxed);
assert_eq!(42, c.sum());

} ```

EnumSequence

Simply derive EnumSequence, and you get enum_sequence(&self) which returns a usize, starting from 0 and incrementing for each variant.

Example

```rust

[derive(EnumSequence)]

enum ABC { A(u8), B(String), C{f: String, g: usize} } ```

expands to

rust pub trait ABCEnumSequence { fn enum_sequence(&self) -> usize; } impl ABCEnumSequence for ABC { fn enum_sequence(&self) -> usize { match self { Self::A(..) => 0usize, Self::B(..) => 1usize, Self::C { .. } => 2usize, } } }

All together

Imagine the following :

```rust

[derive(EnumSequence, EnumAccessor, SortBy, Debug)]

[accessor(global_time: usize)]

[accessor(channel: u8, (CC))]

[accessor(pitch: u8, (CC,SomethingElse))]

[sortby(globaltime(), channel(), pitch(), enum_sequence())]

enum Note { NoteOn(NoteOn), NoteOff(NoteOff), CC(CC), SomethingElse { global_time: usize, channel: u8, } } ```

Now I have a Note enum that will sort by global_time, channel, pitch, and lastly by variant order ( enum_sequence ). Note that None is always less than Some.

Conversely, separate structs such as NoteOn may derive from SortBy in order to ignore some fields ( ex: velocity may be a f32, so we can't directly derive Ord ).

Limitations