human_bytesize_procmacro/
lib.rs

1//! A procedural macro for parsing and computing human-readable byte sizes with support for various units like KB, KiB, MB, MiB, and more.
2//!
3//! # Setup
4//!
5//! To use this crate, add the following entry to your `Cargo.toml` file in the `dependencies` section:
6//!
7//! ```toml
8//! [dependencies]
9//! human-bytesize-procmacro = "0.0.0"
10//! ```
11//!
12//! Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand:
13//!
14//! ```shell
15//! cargo add human-bytesize-procmacro
16//! ```
17//!
18//! # Usage
19//!
20//! Use the [`human_bytesize!`] macro to convert human-readable byte sizes into their byte equivalents by passing a size with its unit:
21//!
22//! ```rust
23//! use human_bytesize_procmacro::human_bytesize;
24//!
25//! assert_eq!(human_bytesize!(10KB), 10 * 1000);
26//! assert_eq!(human_bytesize!(16 KiB), 16 * 1024);
27//! let variable = 160;
28//! assert_eq!(human_bytesize!({ variable } MB), variable * 1000 * 1000);
29//! ```
30//!
31//! # Supported units
32//!
33//! ## Decimal Units (Base 10)
34//!
35//! - B (Byte)
36//! - K, KB (Kilobyte)
37//! - M, MB (Megabyte)
38//! - G, GB (Gigabyte)
39//! - T, TB (Terabyte)
40//! - P, PB (Petabyte)
41//! - E, EB (Exabyte)
42//! - Z, ZB (Zettabyte)
43//! - Y, YB (Yottabyte)
44//!
45//! ## Binary Units (Base 2)
46//!
47//! - B (Byte)
48//! - Ki, KiB (Kibibyte)
49//! - Mi, MiB (Mebibyte)
50//! - Gi, GiB (Gibibyte)
51//! - Ti, TiB (Tebibyte)
52//! - Pi, PiB (Pebibyte)
53//! - Ei, EiB (Exbibyte)
54//! - Zi, ZiB (Zebibyte)
55//! - Yi, YiB (Yobibyte)
56//!
57//! # License
58//!
59//! This crate is licensed under the MIT License.
60
61#![forbid(unsafe_code)]
62
63use std::ops::Mul;
64
65use nom::branch::alt;
66use nom::bytes::complete::{is_not, tag};
67use nom::character::complete::{alpha0, i128, space0};
68use nom::combinator::{all_consuming, map, map_parser, map_res, opt, value};
69use nom::sequence::{delimited, tuple};
70use nom::{Finish, IResult};
71use proc_macro::TokenStream;
72use quote::quote;
73use syn::{parse_str, Error as SynError, Expr};
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76enum Unit {
77    Byte,
78    Kilobyte,
79    Kibibyte,
80    Megabyte,
81    Mebibyte,
82    Gigabyte,
83    Gibibyte,
84    Terabyte,
85    Tebibyte,
86    Petabyte,
87    Pebibyte,
88    Exabyte,
89    Exbibyte,
90    Zettabyte,
91    Zebibyte,
92    Yottabyte,
93    Yobibyte,
94}
95
96impl Unit {
97    const fn multiplier(&self) -> i128 {
98        match self {
99            Self::Byte => 1,
100            Self::Kilobyte => 1000,
101            Self::Kibibyte => 1024,
102            Self::Megabyte => 1000 * 1000,
103            Self::Mebibyte => 1024 * 1024,
104            Self::Gigabyte => 1000 * 1000 * 1000,
105            Self::Gibibyte => 1024 * 1024 * 1024,
106            Self::Terabyte => 1000 * 1000 * 1000 * 1000,
107            Self::Tebibyte => 1024 * 1024 * 1024 * 1024,
108            Self::Petabyte => 1000 * 1000 * 1000 * 1000 * 1000,
109            Self::Pebibyte => 1024 * 1024 * 1024 * 1024 * 1024,
110            Self::Exabyte => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
111            Self::Exbibyte => 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
112            Self::Zettabyte => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
113            Self::Zebibyte => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
114            Self::Yottabyte => 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
115            Self::Yobibyte => 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
116        }
117    }
118
119    fn parse(input: &str) -> IResult<&str, Self> {
120        let mut parser = alt((
121            Self::parse_byte,
122            Self::parse_kilobyte,
123            Self::parse_kibibyte,
124            Self::parse_megabyte,
125            Self::parse_mebibyte,
126            Self::parse_gigabyte,
127            Self::parse_gibibyte,
128            Self::parse_terabyte,
129            Self::parse_tebibyte,
130            Self::parse_petabyte,
131            Self::parse_pebibyte,
132            Self::parse_exabyte,
133            Self::parse_exbibyte,
134            Self::parse_zettabyte,
135            Self::parse_zebibyte,
136            Self::parse_yottabyte,
137            Self::parse_yobibyte,
138        ));
139        parser(input)
140    }
141
142    fn parse_byte(input: &str) -> IResult<&str, Self> {
143        let parser = all_consuming(opt(tag("B")));
144        let mut parser = value(Self::Byte, parser);
145        parser(input)
146    }
147
148    fn parse_kilobyte(input: &str) -> IResult<&str, Self> {
149        let parser = alt((all_consuming(tag("K")), all_consuming(tag("KB"))));
150        let mut parser = value(Self::Kilobyte, parser);
151        parser(input)
152    }
153
154    fn parse_kibibyte(input: &str) -> IResult<&str, Self> {
155        let parser = alt((all_consuming(tag("Ki")), all_consuming(tag("KiB"))));
156        let mut parser = value(Self::Kibibyte, parser);
157        parser(input)
158    }
159
160    fn parse_megabyte(input: &str) -> IResult<&str, Self> {
161        let parser = alt((all_consuming(tag("M")), all_consuming(tag("MB"))));
162        let mut parser = value(Self::Megabyte, parser);
163        parser(input)
164    }
165
166    fn parse_mebibyte(input: &str) -> IResult<&str, Self> {
167        let parser = alt((all_consuming(tag("Mi")), all_consuming(tag("MiB"))));
168        let mut parser = value(Self::Mebibyte, parser);
169        parser(input)
170    }
171
172    fn parse_gigabyte(input: &str) -> IResult<&str, Self> {
173        let parser = alt((all_consuming(tag("G")), all_consuming(tag("GB"))));
174        let mut parser = value(Self::Gigabyte, parser);
175        parser(input)
176    }
177
178    fn parse_gibibyte(input: &str) -> IResult<&str, Self> {
179        let parser = alt((all_consuming(tag("Gi")), all_consuming(tag("GiB"))));
180        let mut parser = value(Self::Gibibyte, parser);
181        parser(input)
182    }
183
184    fn parse_terabyte(input: &str) -> IResult<&str, Self> {
185        let parser = alt((all_consuming(tag("T")), all_consuming(tag("TB"))));
186        let mut parser = value(Self::Terabyte, parser);
187        parser(input)
188    }
189
190    fn parse_tebibyte(input: &str) -> IResult<&str, Self> {
191        let parser = alt((all_consuming(tag("Ti")), all_consuming(tag("TiB"))));
192        let mut parser = value(Self::Tebibyte, parser);
193        parser(input)
194    }
195
196    fn parse_petabyte(input: &str) -> IResult<&str, Self> {
197        let parser = alt((all_consuming(tag("P")), all_consuming(tag("PB"))));
198        let mut parser = value(Self::Petabyte, parser);
199        parser(input)
200    }
201
202    fn parse_pebibyte(input: &str) -> IResult<&str, Self> {
203        let parser = alt((all_consuming(tag("Pi")), all_consuming(tag("PiB"))));
204        let mut parser = value(Self::Pebibyte, parser);
205        parser(input)
206    }
207
208    fn parse_exabyte(input: &str) -> IResult<&str, Self> {
209        let parser = alt((all_consuming(tag("E")), all_consuming(tag("EB"))));
210        let mut parser = value(Self::Exabyte, parser);
211        parser(input)
212    }
213
214    fn parse_exbibyte(input: &str) -> IResult<&str, Self> {
215        let parser = alt((all_consuming(tag("Ei")), all_consuming(tag("EiB"))));
216        let mut parser = value(Self::Exbibyte, parser);
217        parser(input)
218    }
219
220    fn parse_zettabyte(input: &str) -> IResult<&str, Self> {
221        let parser = alt((all_consuming(tag("Z")), all_consuming(tag("ZB"))));
222        let mut parser = value(Self::Zettabyte, parser);
223        parser(input)
224    }
225
226    fn parse_zebibyte(input: &str) -> IResult<&str, Self> {
227        let parser = alt((all_consuming(tag("Zi")), all_consuming(tag("ZiB"))));
228        let mut parser = value(Self::Zebibyte, parser);
229        parser(input)
230    }
231
232    fn parse_yottabyte(input: &str) -> IResult<&str, Self> {
233        let parser = alt((all_consuming(tag("Y")), all_consuming(tag("YB"))));
234        let mut parser = value(Self::Yottabyte, parser);
235        parser(input)
236    }
237
238    fn parse_yobibyte(input: &str) -> IResult<&str, Self> {
239        let parser = alt((all_consuming(tag("Yi")), all_consuming(tag("YiB"))));
240        let mut parser = value(Self::Yobibyte, parser);
241        parser(input)
242    }
243}
244
245macro_rules! impl_mul {
246    ($($t:ty),+ => $o:ty) => {
247        $(
248            impl Mul<Unit> for $t {
249                type Output = $o;
250
251                fn mul(self, rhs: Unit) -> Self::Output {
252                    (self as Self::Output) * (rhs.multiplier() as Self::Output)
253                }
254            }
255        )*
256    };
257}
258impl_mul!(u8, u16, u32, u64, u128, usize => u128);
259impl_mul!(i8, i16, i32, i64, i128, isize => i128);
260
261enum Value {
262    Number(i128),
263    Expression(Expr),
264}
265
266impl Value {
267    fn parse(input: &str) -> IResult<&str, Self> {
268        let number_parser = map(i128, Self::Number);
269        let identifier_parser = {
270            let parser = delimited(tag("{"), is_not("}"), tag("}"));
271            map_res(parser, |expr| -> Result<Self, SynError> {
272                let expr = parse_str::<Expr>(expr)?;
273                let expression = Self::Expression(expr);
274                Ok(expression)
275            })
276        };
277        let mut parser = alt((number_parser, identifier_parser));
278        parser(input)
279    }
280}
281
282fn parse_human_bytesize(input: &str) -> IResult<&str, (Value, Unit)> {
283    let parser = tuple((space0, Value::parse, space0, map_parser(alpha0, Unit::parse), space0));
284    let parser = all_consuming(parser);
285    let mut parser = map(parser, |(_, value, _, unit, _)| (value, unit)); // .unwrap_or(Unit::Byte)
286    parser(input)
287}
288
289/// A procedural macro for converting human-readable byte size expressions into their corresponding byte values at compile time.
290///
291/// - **Macro evaluation**: Numeric literals are evaluated directly by the macro at compile time, producing a constant value.
292/// - **Compiler evaluation**: Expressions are expanded into code, leaving the final computation to the Rust compiler at compile time.
293///
294/// Returns a value of type [`prim@i128`] or a multiplication expression that evaluates to [`prim@i128`], representing the computed size in bytes.
295///
296/// # Usage
297///
298/// - **Numeric literals**: Use numbers followed by a unit, e.g., `100KB` or `16 KiB`.
299/// - **Expressions**: Enclose a valid Rust expression in curly braces, followed by the unit.
300///
301/// # Example
302///
303/// ```rust
304/// use human_bytesize_procmacro::human_bytesize;
305///
306/// // Using numeric literals
307/// assert_eq!(human_bytesize!(-4B), -4);
308/// assert_eq!(human_bytesize!(10KB), 10 * 1000);
309/// assert_eq!(human_bytesize!(16 KiB), 16 * 1024);
310///
311/// // Using expressions
312/// let variable = 320;
313/// assert_eq!(human_bytesize!({ variable } MiB), variable * 1024 * 1024);
314///
315/// const TOTAL_MEGABYTES: i128 = 16;
316/// assert_eq!(human_bytesize!({ TOTAL_MEGABYTES } MB), TOTAL_MEGABYTES * 1000 * 1000);
317/// ```
318///
319/// # Limitations
320///
321/// ## Exponent Notation Conflict
322///
323/// The `E` unit (e.g., `100EB` for exabytes) may conflict with Rust's scientific notation parser, which expects an exponent after `E`.
324///
325/// For example:
326///
327/// ```rust,compile_fail
328/// # use human_bytesize_procmacro::human_bytesize;
329/// let x = human_bytesize!(100EB); // This results in a parsing error.
330/// ```
331///
332/// To work around this limitation, use a space between the number and the unit:
333///
334/// ```rust
335/// # use human_bytesize_procmacro::human_bytesize;
336/// let x = human_bytesize!(100 EB); // This compiles correctly.
337/// ```
338///
339/// ## Error Handling
340///
341/// The macro will panic if the input format is invalid. Ensure you use one of the following formats:
342///
343/// - `<number><unit>`: Example: `100KB`, `16 KiB`
344/// - `{<expression>}<unit>`: Example: `{variable}MB`, `{ CONST_VAL } G`
345///
346/// ## Common Pitfalls
347///
348/// - **Spacing**: While both `100KB` and `100 KB` are valid, ensure consistency in formatting.
349/// - **Expression Wrapping**: When using a variable or an expression, wrap it in `{}` before specifying the unit.
350#[proc_macro]
351pub fn human_bytesize(input: TokenStream) -> TokenStream {
352    let input = input.to_string();
353    match parse_human_bytesize(&input).finish() {
354        Ok((_, (Value::Number(value), unit))) => {
355            let result = value * unit;
356            let result = quote! {
357                #result
358            };
359            TokenStream::from(result)
360        },
361        Ok((_, (Value::Expression(expr), unit))) => {
362            let unit = unit.multiplier();
363            let result = quote! {
364                ((#expr) * #unit)
365            };
366            TokenStream::from(result)
367        },
368        Err(_) => panic!("Invalid format! Please use format like '100 KiB' or '100KiB'"),
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_unit_parse() {
378        // Valid use cases
379        assert!(matches!(Unit::parse(""), Ok((_, Unit::Byte))));
380        assert!(matches!(Unit::parse("B"), Ok((_, Unit::Byte))));
381        assert!(matches!(Unit::parse("K"), Ok((_, Unit::Kilobyte))));
382        assert!(matches!(Unit::parse("KB"), Ok((_, Unit::Kilobyte))));
383        assert!(matches!(Unit::parse("Ki"), Ok((_, Unit::Kibibyte))));
384        assert!(matches!(Unit::parse("KiB"), Ok((_, Unit::Kibibyte))));
385        assert!(matches!(Unit::parse("M"), Ok((_, Unit::Megabyte))));
386        assert!(matches!(Unit::parse("MB"), Ok((_, Unit::Megabyte))));
387        assert!(matches!(Unit::parse("Mi"), Ok((_, Unit::Mebibyte))));
388        assert!(matches!(Unit::parse("MiB"), Ok((_, Unit::Mebibyte))));
389        assert!(matches!(Unit::parse("G"), Ok((_, Unit::Gigabyte))));
390        assert!(matches!(Unit::parse("GB"), Ok((_, Unit::Gigabyte))));
391        assert!(matches!(Unit::parse("Gi"), Ok((_, Unit::Gibibyte))));
392        assert!(matches!(Unit::parse("GiB"), Ok((_, Unit::Gibibyte))));
393        assert!(matches!(Unit::parse("T"), Ok((_, Unit::Terabyte))));
394        assert!(matches!(Unit::parse("TB"), Ok((_, Unit::Terabyte))));
395        assert!(matches!(Unit::parse("Ti"), Ok((_, Unit::Tebibyte))));
396        assert!(matches!(Unit::parse("TiB"), Ok((_, Unit::Tebibyte))));
397        assert!(matches!(Unit::parse("P"), Ok((_, Unit::Petabyte))));
398        assert!(matches!(Unit::parse("PB"), Ok((_, Unit::Petabyte))));
399        assert!(matches!(Unit::parse("Pi"), Ok((_, Unit::Pebibyte))));
400        assert!(matches!(Unit::parse("PiB"), Ok((_, Unit::Pebibyte))));
401        assert!(matches!(Unit::parse("E"), Ok((_, Unit::Exabyte))));
402        assert!(matches!(Unit::parse("EB"), Ok((_, Unit::Exabyte))));
403        assert!(matches!(Unit::parse("Ei"), Ok((_, Unit::Exbibyte))));
404        assert!(matches!(Unit::parse("EiB"), Ok((_, Unit::Exbibyte))));
405        assert!(matches!(Unit::parse("Z"), Ok((_, Unit::Zettabyte))));
406        assert!(matches!(Unit::parse("ZB"), Ok((_, Unit::Zettabyte))));
407        assert!(matches!(Unit::parse("Zi"), Ok((_, Unit::Zebibyte))));
408        assert!(matches!(Unit::parse("ZiB"), Ok((_, Unit::Zebibyte))));
409        assert!(matches!(Unit::parse("Y"), Ok((_, Unit::Yottabyte))));
410        assert!(matches!(Unit::parse("YB"), Ok((_, Unit::Yottabyte))));
411        assert!(matches!(Unit::parse("Yi"), Ok((_, Unit::Yobibyte))));
412        assert!(matches!(Unit::parse("YiB"), Ok((_, Unit::Yobibyte))));
413
414        // Invalid use cases
415        assert!(matches!(Unit::parse(" "), Err(_)));
416        assert!(matches!(Unit::parse(" B"), Err(_)));
417        assert!(matches!(Unit::parse("   B"), Err(_)));
418        assert!(matches!(Unit::parse("B "), Err(_)));
419        assert!(matches!(Unit::parse("B       "), Err(_)));
420        assert!(matches!(Unit::parse("XYZ"), Err(_)));
421    }
422
423    #[test]
424    fn test_parse_human_bytesize() {
425        // Valid use cases
426        assert!(matches!(
427            parse_human_bytesize("8"),
428            Ok((_, (Value::Number(8), Unit::Byte)))
429        ));
430        assert!(matches!(
431            parse_human_bytesize("34 B"),
432            Ok((_, (Value::Number(34), Unit::Byte)))
433        ));
434        assert!(matches!(
435            parse_human_bytesize("82KB"),
436            Ok((_, (Value::Number(82), Unit::Kilobyte)))
437        ));
438        assert!(matches!(
439            parse_human_bytesize("7 KiB"),
440            Ok((_, (Value::Number(7), Unit::Kibibyte)))
441        ));
442        assert!(matches!(
443            parse_human_bytesize("987 MB"),
444            Ok((_, (Value::Number(987), Unit::Megabyte)))
445        ));
446        assert!(matches!(
447            parse_human_bytesize("150MiB"),
448            Ok((_, (Value::Number(150), Unit::Mebibyte)))
449        ));
450        assert!(matches!(
451            parse_human_bytesize("99 GB"),
452            Ok((_, (Value::Number(99), Unit::Gigabyte)))
453        ));
454        assert!(matches!(
455            parse_human_bytesize("1 GiB"),
456            Ok((_, (Value::Number(1), Unit::Gibibyte)))
457        ));
458        assert!(matches!(
459            parse_human_bytesize("678TB"),
460            Ok((_, (Value::Number(678), Unit::Terabyte)))
461        ));
462        assert!(matches!(
463            parse_human_bytesize("123TiB"),
464            Ok((_, (Value::Number(123), Unit::Tebibyte)))
465        ));
466        assert!(matches!(
467            parse_human_bytesize("54 PB"),
468            Ok((_, (Value::Number(54), Unit::Petabyte)))
469        ));
470        assert!(matches!(
471            parse_human_bytesize("19 PiB"),
472            Ok((_, (Value::Number(19), Unit::Pebibyte)))
473        ));
474        assert!(matches!(
475            parse_human_bytesize("556 EB"),
476            Ok((_, (Value::Number(556), Unit::Exabyte)))
477        ));
478        assert!(matches!(
479            parse_human_bytesize("153EiB"),
480            Ok((_, (Value::Number(153), Unit::Exbibyte)))
481        ));
482        assert!(matches!(
483            parse_human_bytesize("4ZB"),
484            Ok((_, (Value::Number(4), Unit::Zettabyte)))
485        ));
486        assert!(matches!(
487            parse_human_bytesize("34ZiB"),
488            Ok((_, (Value::Number(34), Unit::Zebibyte)))
489        ));
490        assert!(matches!(
491            parse_human_bytesize("750YB"),
492            Ok((_, (Value::Number(750), Unit::Yottabyte)))
493        ));
494        assert!(matches!(
495            parse_human_bytesize("56YiB"),
496            Ok((_, (Value::Number(56), Unit::Yobibyte)))
497        ));
498        assert!(matches!(
499            parse_human_bytesize("{ var } EB"),
500            Ok((_, (Value::Expression(_), Unit::Exabyte)))
501        ));
502        assert!(matches!(
503            parse_human_bytesize("0  "),
504            Ok((_, (Value::Number(0), Unit::Byte)))
505        ));
506        assert!(matches!(
507            parse_human_bytesize("   13M"),
508            Ok((_, (Value::Number(13), Unit::Megabyte)))
509        ));
510        assert!(matches!(
511            parse_human_bytesize("230  G"),
512            Ok((_, (Value::Number(230), Unit::Gigabyte)))
513        ));
514        assert!(matches!(
515            parse_human_bytesize(" 2 PiB "),
516            Ok((_, (Value::Number(2), Unit::Pebibyte)))
517        ));
518
519        // Invalid use cases
520        assert!(matches!(parse_human_bytesize("18BB"), Err(_)));
521        assert!(matches!(parse_human_bytesize("1 0TB"), Err(_)));
522        assert!(matches!(parse_human_bytesize("10 XB"), Err(_)));
523        assert!(matches!(parse_human_bytesize("ABC XYZ"), Err(_)));
524    }
525}