num_ordinal/
lib.rs

1/*! Ordinal number types
2
3Ordinal numbers (_first, second, third, ..._) are usually represented
4as 0-based or 1-based integers. In English and most other natural
5languages, they're represented as 1-based numbers:
6first = 1st, second = 2nd, third = 3rd and so on.
7However, most programming languages are zero-based, i.e. when getting
8the first element in array or list, the index is 0. This is also true for Rust.
9
10# Usage
11
12To make working with ordinal numbers more explicit and less error-prone,
13this library provides ordinal number types that can be converted to/from
14cardinal numbers while specifying if it is 0- or 1-based:
15
16```rust
17use num_ordinal::{Ordinal, Osize};
18
19// Osize is an ordinal usize
20let o = Osize::from0(3);
21assert_eq!(&o.to_string(), "4th");
22
23let o = Osize::from1(3);
24assert_eq!(&o.to_string(), "third");
25```
26
27There are also two convenience functions to create ordinal numbers
28when the return type can be inferred:
29
30```rust
31use num_ordinal::{Osize, ordinal0, ordinal1};
32
33// Osize is an ordinal usize
34let o: Osize = ordinal0(3);
35assert_eq!(&o.to_string(), "4th");
36
37let o: Osize = ordinal1(3);
38assert_eq!(&o.to_string(), "third");
39```
40
41And [a macro](ordinal):
42
43```rust
44use num_ordinal::{O32, ordinal};
45
46// type is inferred:
47let o: O32 = ordinal!(4-th);
48
49// type can also be specified:
50let o = ordinal!(4-th O32);
51```
52
53# Implemented traits
54
55Ordinal numbers implement a number of traits, so they can be
56compared, hashed, copied and formatted. Also, you can add or
57subtract an integer from an ordinal number:
58
59```rust
60use num_ordinal::ordinal;
61
62assert_eq!(ordinal!(5-th O32) - 3, ordinal!(second O32));
63```
64
65Subtracting an ordinal from an ordinal produces an integer:
66
67```rust
68use num_ordinal::ordinal;
69
70assert_eq!(ordinal!(5-th O32) - ordinal!(second O32), 3);
71```
72
73The default value is _first_.
74
75# Features
76
77* `serde`: Implement `Serialize` and `Deserialize` for ordinals
78
79# License
80
81MIT
82*/
83
84#[cfg(feature = "serde")]
85mod serde_impl;
86
87use std::fmt;
88use std::ops::{Add, Sub};
89
90/// [Ordinal] number represented by [usize]
91#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
92#[repr(transparent)]
93pub struct Osize(usize);
94
95/// [Ordinal] number represented by [u128]
96#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
97#[repr(transparent)]
98pub struct O128(u128);
99
100/// [Ordinal] number represented by [u64]
101#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
102#[repr(transparent)]
103pub struct O64(u64);
104
105/// [Ordinal] number represented by [u32]
106#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
107#[repr(transparent)]
108pub struct O32(u32);
109
110/// [Ordinal] number represented by [u16]
111#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
112#[repr(transparent)]
113pub struct O16(u16);
114
115/// [Ordinal] number represented by [u8]
116#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, Default)]
117#[repr(transparent)]
118pub struct O8(u8);
119
120/// An ordinal number type
121///
122/// See the [module-level documentation](index.html) for more.
123pub trait Ordinal:
124    Sized
125    + Eq
126    + PartialEq
127    + Ord
128    + PartialOrd
129    + std::hash::Hash
130    + Clone
131    + Copy
132    + Default
133    + fmt::Display
134    + fmt::Debug
135{
136    /// This type by which this ordinal type is represented
137    type IntegerType: Copy + fmt::Display;
138
139    /// The first ordinal number
140    fn first() -> Self;
141
142    /// Computes the ordinal number that comes after this one
143    fn next(self) -> Self;
144
145    /// Returns the equivalent integer assuming the ordinal number is 0-based
146    fn into0(self) -> Self::IntegerType;
147
148    /// Returns the equivalent integer assuming the ordinal number is 1-based
149    fn into1(self) -> Self::IntegerType;
150
151    /// Tries to convert an integer to a 0-based ordinal number.
152    ///
153    /// It returns [None] if the provided number is the highest number of that integer type.
154    /// This fails because that number can't be incremented by 1.
155    fn try_from0(t: Self::IntegerType) -> Option<Self>;
156
157    /// Tries to convert an integer to a 1-based ordinal number.
158    ///
159    /// It returns [None] if the provided number is 0.
160    fn try_from1(t: Self::IntegerType) -> Option<Self>;
161
162    /// Converts an integer to a 0-based ordinal number.
163    ///
164    /// ### Panics
165    ///
166    /// Panics if the provided number is the highest number of that integer type.
167    /// This fails because that number can't be incremented by 1.
168    fn from0(t: Self::IntegerType) -> Self {
169        Self::try_from0(t).unwrap_or_else(|| panic!("value {} is too big for this ordinal type", t))
170    }
171
172    /// Converts an integer to a 1-based ordinal number.
173    ///
174    /// ### Panics
175    ///
176    /// Panics if the provided number is 0.
177    fn from1(t: Self::IntegerType) -> Self {
178        Self::try_from1(t).expect("0 is not a valid 1-based ordinal.")
179    }
180}
181
182macro_rules! impl_ordinal {
183    ($t:ident, $int:ident) => {
184        impl Ordinal for $t {
185            type IntegerType = $int;
186
187            fn first() -> Self {
188                Self(0)
189            }
190
191            fn next(self) -> Self {
192                Self::from0(self.0 + 1)
193            }
194
195            fn into0(self) -> Self::IntegerType {
196                self.0
197            }
198
199            fn into1(self) -> Self::IntegerType {
200                self.0 + 1
201            }
202
203            fn try_from0(t: Self::IntegerType) -> Option<Self> {
204                match t {
205                    $int::MAX => None,
206                    _ => Some($t(t)),
207                }
208            }
209
210            fn try_from1(t: Self::IntegerType) -> Option<Self> {
211                t.checked_sub(1).map($t)
212            }
213        }
214
215        impl fmt::Debug for $t {
216            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217                match self.0 + 1 {
218                    1 => write!(f, "first"),
219                    2 => write!(f, "second"),
220                    3 => write!(f, "third"),
221                    n => {
222                        let two_digits = n % 100;
223                        let digit = two_digits % 10;
224                        if digit == 1 && two_digits != 11 {
225                            write!(f, "{}st", n)
226                        } else if digit == 2 && two_digits != 12 {
227                            write!(f, "{}nd", n)
228                        } else if digit == 3 && two_digits != 13 {
229                            write!(f, "{}rd", n)
230                        } else {
231                            write!(f, "{}th", n)
232                        }
233                    }
234                }
235            }
236        }
237
238        impl fmt::Display for $t {
239            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240                write!(f, "{:?}", self)
241            }
242        }
243
244        impl Add<$int> for $t {
245            type Output = $t;
246
247            fn add(self, rhs: $int) -> Self::Output {
248                Self::from0(self.0 + rhs)
249            }
250        }
251
252        impl Sub<$int> for $t {
253            type Output = $t;
254
255            fn sub(self, rhs: $int) -> Self::Output {
256                Self::from0(self.0 - rhs)
257            }
258        }
259
260        impl Sub<$t> for $t {
261            type Output = $int;
262
263            fn sub(self, rhs: $t) -> Self::Output {
264                self.0 - rhs.0
265            }
266        }
267    };
268}
269
270impl_ordinal!(Osize, usize);
271impl_ordinal!(O128, u128);
272impl_ordinal!(O64, u64);
273impl_ordinal!(O32, u32);
274impl_ordinal!(O16, u16);
275impl_ordinal!(O8, u8);
276
277/// Creates a 1-based ordinal number. For example, `ordinal1(4)` is the 4th ordinal number.
278pub fn ordinal1<O: Ordinal>(n: O::IntegerType) -> O {
279    O::from1(n)
280}
281
282/// Creates a 0-based ordinal number. For example, `ordinal0(4)` is the 5th ordinal number.
283pub fn ordinal0<O: Ordinal>(n: O::IntegerType) -> O {
284    O::from0(n)
285}
286
287/// Creates a 1-based ordinal number. Examples:
288///
289/// ```
290/// use num_ordinal::{O32, ordinal};
291///
292/// let mut o: O32 = ordinal!(first);
293/// o = ordinal!(second);
294/// o = ordinal!(third);
295///
296/// // Other numbers must use the following syntax:
297/// o = ordinal!(4-th);
298/// // the dash can be omitted, but then a space is required to make the Rust parser happy:
299/// o = ordinal!(4 th);
300/// // alternatively, a dot can be written after the number:
301/// o = ordinal!(4 .);
302///
303/// // When necessary, the type can be ascribed:
304/// let o = ordinal!(5-th O32);
305/// ```
306///
307/// Note that only `first`, `second` and `third` can be written as a full word:
308///
309/// ```compile_fail
310/// use num_ordinal::{O32, ordinal};
311///
312/// // doesn't compile!
313/// let _: O32 = ordinal!(fifth);
314/// ```
315#[macro_export]
316macro_rules! ordinal {
317    (first $($ty:ident)?) => {
318        $crate::ordinal1 $(::<$crate::$ty>)? (1)
319    };
320    (second $($ty:ident)?) => {
321        $crate::ordinal1 $(::<$crate::$ty>)? (2)
322    };
323    (third $($ty:ident)?) => {
324        $crate::ordinal1 $(::<$crate::$ty>)? (3)
325    };
326    ($n:literal $(-)? $suffix:ident $($ty:ident)?) => {
327        $crate::ordinal1 $(::<$crate::$ty>)? ($n)
328    };
329    ($n:literal . $($ty:ident)?) => {
330        $crate::ordinal1 $(::<$crate::$ty>)? ($n)
331    };
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn first_from0() {
340        let first_o_from0 = Osize::from0(0);
341
342        assert_eq!(&first_o_from0.to_string(), "first");
343    }
344
345    #[test]
346    fn first_from1() {
347        let first_o_from1 = Osize::from1(1);
348        assert_eq!(&first_o_from1.to_string(), "first");
349    }
350
351    #[test]
352    fn second_from0() {
353        let second_o_from0 = Osize::from0(1);
354        assert_eq!(&second_o_from0.to_string(), "second");
355    }
356
357    #[test]
358    fn second_from1() {
359        let second_o_from1 = Osize::from1(2);
360
361        assert_eq!(&second_o_from1.to_string(), "second");
362    }
363
364    #[test]
365    fn third_from0() {
366        let third_o_from0 = Osize::from0(2);
367        assert_eq!(&third_o_from0.to_string(), "third");
368    }
369
370    #[test]
371    fn third_from1() {
372        let third_o_from1 = Osize::from1(3);
373        assert_eq!(&third_o_from1.to_string(), "third");
374    }
375
376    #[test]
377    fn fourth_from0() {
378        let fourth_o_from0 = Osize::from0(3);
379        assert_eq!(&fourth_o_from0.to_string(), "4th");
380    }
381
382    #[test]
383    fn fourth_from1() {
384        let fourth_o_from1 = Osize::from1(4);
385        assert_eq!(&fourth_o_from1.to_string(), "4th");
386    }
387}