partial_cmp_derive/lib.rs
1//! # partial-cmp-derive
2//!
3//! A procedural macro crate for deriving `PartialOrd` and `Ord` with fine-grained
4//! control over field comparison behavior.
5//!
6//! ## Features
7//!
8//! - **Skip fields**: Use `#[ord(skip)]` to exclude fields from comparison
9//! - **Sort order**: Use `#[ord(order = "asc")]` or `#[ord(order = "desc")]` per field
10//! - **Explicit ordering**: Use `#[ord(by = [field1(desc), field2(asc)])]` at struct level
11//! - **Field priority**: Use `#[ord(priority = N)]` for implicit ordering (lower = first)
12//! - **Custom comparators**: Use `#[ord(compare_with = "path::to::fn")]`
13//! - **Reverse all**: Use `#[ord(reverse)]` at struct level to reverse entire comparison
14//! - **Enum ranking**: Use `#[ord(rank = N)]` to control variant ordering
15//! - **Option handling**: Use `#[ord(none_order = "first")]` or `"last"`
16//!
17//! ## Example
18//!
19//! ```rust
20//! use partial_cmp_derive::PartialCmpDerive;
21//!
22//! #[derive(PartialEq, Eq, PartialCmpDerive)]
23//! struct Player {
24//! #[ord(skip)]
25//! id: u64,
26//! #[ord(order = "asc")]
27//! name: String,
28//! #[ord(order = "desc")]
29//! score: u32,
30//! }
31//! ```
32//!
33//! This generates `PartialOrd` and `Ord` implementations that compare `name`
34//! ascending, then `score` descending, while ignoring `id` entirely.
35
36mod expand;
37mod types;
38
39use darling::FromDeriveInput;
40use proc_macro::TokenStream;
41use syn::{DeriveInput, parse_macro_input};
42
43use crate::expand::expand_derive;
44use crate::types::OrdDerive;
45
46/// Derives `PartialOrd` and `Ord` with customizable field comparison behavior.
47///
48/// # Struct-Level Attributes
49///
50/// - `#[ord(reverse)]` — Reverses the final comparison result
51/// - `#[ord(by = [field1(asc), field2(desc)])]` — Explicit field comparison order
52///
53/// # Field-Level Attributes
54///
55/// - `#[ord(skip)]` — Exclude this field from comparison
56/// - `#[ord(order = "asc")]` or `#[ord(order = "desc")]` — Sort direction
57/// - `#[ord(priority = N)]` — Comparison priority (lower = compared first)
58/// - `#[ord(compare_with = "path::to::fn")]` — Custom comparison function
59/// - `#[ord(none_order = "first")]` or `#[ord(none_order = "last")]` — Option handling
60///
61/// # Variant-Level Attributes (Enums)
62///
63/// - `#[ord(rank = N)]` — Explicit variant ranking (lower = less than)
64///
65/// # Requirements
66///
67/// - The type must also derive or implement `PartialEq` and `Eq`
68/// - All compared fields must implement `Ord` (or provide `compare_with`)
69///
70/// # Example
71///
72/// ```rust
73/// use partial_cmp_derive::PartialCmpDerive;
74///
75/// #[derive(PartialEq, Eq, PartialCmpDerive)]
76/// #[ord(by = [score(desc), name(asc)])]
77/// struct Player {
78/// id: u64, // Not compared (not in `by` list)
79/// name: String,
80/// score: u32,
81/// }
82/// ```
83#[proc_macro_derive(PartialCmp, attributes(ord))]
84pub fn partial_cmp_derive(input: TokenStream) -> TokenStream {
85 let input = parse_macro_input!(input as DeriveInput);
86
87 let ord_derive = match OrdDerive::from_derive_input(&input) {
88 Ok(v) => v,
89 Err(e) => return e.write_errors().into(),
90 };
91
92 match expand_derive(&ord_derive) {
93 Ok(tokens) => tokens.into(),
94 Err(e) => e.write_errors().into(),
95 }
96}