1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::quote;
4use syn::{DeriveInput, parse_macro_input};
5
6#[proc_macro_derive(HumanByte)]
7pub fn humanbyte(input: TokenStream) -> TokenStream {
8 let input = parse_macro_input!(input as DeriveInput);
9 let name = &input.ident;
10
11 let mut combined = constructor_tokens(name);
12 combined.extend(display_tokens(name));
13 combined.extend(parse_tokens(name));
14 combined.extend(ops_tokens(name));
15 combined.extend(fromstr_tokens(name));
16 if cfg!(feature = "serde") {
17 combined.extend(serde_tokens(name));
18 }
19 if cfg!(feature = "schemars") {
20 combined.extend(schemars_tokens(name));
21 }
22 TokenStream::from(combined)
23}
24
25#[proc_macro_derive(HumanByteConstructor)]
26pub fn humanbyte_constructor(input: TokenStream) -> TokenStream {
27 let input = parse_macro_input!(input as DeriveInput);
28 TokenStream::from(constructor_tokens(&input.ident))
29}
30
31fn constructor_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
32 let units = vec![
34 ("b", "1", "bytes"),
35 ("kb", "::humanbyte::KB", "kilobytes"),
36 ("kib", "::humanbyte::KIB", "kibibytes"),
37 ("mb", "::humanbyte::MB", "megabytes"),
38 ("mib", "::humanbyte::MIB", "mebibytes"),
39 ("gb", "::humanbyte::GB", "gigabytes"),
40 ("gib", "::humanbyte::GIB", "gibibytes"),
41 ("tb", "::humanbyte::TB", "terabytes"),
42 ("tib", "::humanbyte::TIB", "tebibytes"),
43 ("pb", "::humanbyte::PB", "petabytes"),
44 ("pib", "::humanbyte::PIB", "pebibytes"),
45 ("eb", "::humanbyte::EB", "exabytes"),
46 ("eib", "::humanbyte::EIB", "exbibytes"),
47 ];
48
49 let methods = units.iter().map(|(fn_name, multiplier, description)| {
51 let method_name = syn::Ident::new(fn_name, Span::call_site());
53
54 let multiplier_expr: syn::Expr = syn::parse_str(multiplier).unwrap();
56
57 let doc_comment = format!(
59 "Construct `{}` given an amount of {}.\n\nPanics if the total byte count overflows `u64`.",
60 name, description
61 );
62
63 quote! {
65 #[doc = #doc_comment]
66 #[inline(always)]
67 pub const fn #method_name(size: u64) -> Self {
68 match size.checked_mul(#multiplier_expr) {
69 Some(bytes) => Self(bytes),
70 None => panic!("byte size overflows u64"),
71 }
72 }
73 }
74 });
75
76 let accessors = units[1..].iter().map(|(unit, multiplier, description)| {
78 let method_name = syn::Ident::new(&format!("as_{}", unit), Span::call_site());
79 let multiplier_expr: syn::Expr = syn::parse_str(multiplier).unwrap();
80 let doc_comment = format!("Returns the size in {} as a float.", description);
81
82 quote! {
83 #[doc = #doc_comment]
84 #[inline(always)]
85 pub fn #method_name(&self) -> f64 {
86 self.0 as f64 / #multiplier_expr as f64
87 }
88 }
89 });
90
91 quote! {
92 impl #name {
93 #(#methods)*
94 #(#accessors)*
95 }
96
97 impl From<u64> for #name {
98 fn from(size: u64) -> #name {
99 Self(size)
100 }
101 }
102 }
103}
104
105#[proc_macro_derive(HumanByteOps)]
106pub fn humanbyte_ops(input: TokenStream) -> TokenStream {
107 let input = parse_macro_input!(input as DeriveInput);
108 TokenStream::from(ops_tokens(&input.ident))
109}
110
111fn ops_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
112 quote! {
113 impl core::ops::Add<#name> for #name {
114 type Output = #name;
115
116 #[inline(always)]
117 fn add(self, rhs: #name) -> #name {
118 #name(self.0 + rhs.0)
119 }
120 }
121
122 impl core::ops::AddAssign<#name> for #name {
123 #[inline(always)]
124 fn add_assign(&mut self, rhs: #name) {
125 self.0 += rhs.0
126 }
127 }
128
129 impl<T> core::ops::Add<T> for #name
130 where
131 T: Into<u64>,
132 {
133 type Output = #name;
134 #[inline(always)]
135 fn add(self, rhs: T) -> #name {
136 #name(self.0 + (rhs.into()))
137 }
138 }
139
140 impl<T> core::ops::AddAssign<T> for #name
141 where
142 T: Into<u64>,
143 {
144 #[inline(always)]
145 fn add_assign(&mut self, rhs: T) {
146 self.0 += rhs.into();
147 }
148 }
149
150 impl core::ops::Sub<#name> for #name {
151 type Output = #name;
152
153 #[inline(always)]
154 fn sub(self, rhs: #name) -> #name {
155 #name(self.0 - rhs.0)
156 }
157 }
158
159 impl core::ops::SubAssign<#name> for #name {
160 #[inline(always)]
161 fn sub_assign(&mut self, rhs: #name) {
162 self.0 -= rhs.0
163 }
164 }
165
166 impl<T> core::ops::Sub<T> for #name
167 where
168 T: Into<u64>,
169 {
170 type Output = #name;
171
172 #[inline(always)]
173 fn sub(self, rhs: T) -> #name {
174 #name(self.0 - (rhs.into()))
175 }
176 }
177
178 impl<T> core::ops::SubAssign<T> for #name
179 where
180 T: Into<u64>,
181 {
182 #[inline(always)]
183 fn sub_assign(&mut self, rhs: T) {
184 self.0 -= rhs.into();
185 }
186 }
187
188 impl<T> core::ops::Mul<T> for #name
189 where
190 T: Into<u64>,
191 {
192 type Output = #name;
193 #[inline(always)]
194 fn mul(self, rhs: T) -> #name {
195 #name(self.0 * rhs.into())
196 }
197 }
198
199 impl<T> core::ops::MulAssign<T> for #name
200 where
201 T: Into<u64>,
202 {
203 #[inline(always)]
204 fn mul_assign(&mut self, rhs: T) {
205 self.0 *= rhs.into();
206 }
207 }
208
209 impl core::ops::Div<#name> for #name {
210 type Output = u64;
213
214 #[inline(always)]
215 fn div(self, rhs: #name) -> u64 {
216 self.0 / rhs.0
217 }
218 }
219
220 impl<T> core::ops::Div<T> for #name
221 where
222 T: Into<u64>,
223 {
224 type Output = #name;
225 #[inline(always)]
226 fn div(self, rhs: T) -> #name {
227 #name(self.0 / rhs.into())
228 }
229 }
230
231 impl<T> core::ops::DivAssign<T> for #name
232 where
233 T: Into<u64>,
234 {
235 #[inline(always)]
236 fn div_assign(&mut self, rhs: T) {
237 self.0 /= rhs.into();
238 }
239 }
240
241 impl core::ops::Rem<#name> for #name {
242 type Output = #name;
243
244 #[inline(always)]
245 fn rem(self, rhs: #name) -> #name {
246 #name(self.0 % rhs.0)
247 }
248 }
249
250 impl core::iter::Sum<#name> for #name {
251 fn sum<I: Iterator<Item = #name>>(iter: I) -> #name {
252 iter.fold(#name(0), |acc, x| #name(acc.0 + x.0))
253 }
254 }
255
256 impl<'a> core::iter::Sum<&'a #name> for #name {
257 fn sum<I: Iterator<Item = &'a #name>>(iter: I) -> #name {
258 iter.fold(#name(0), |acc, x| #name(acc.0 + x.0))
259 }
260 }
261
262 impl core::ops::Add<#name> for u64 {
263 type Output = #name;
264 #[inline(always)]
265 fn add(self, rhs: #name) -> #name {
266 #name(rhs.0 + self)
267 }
268 }
269
270 impl core::ops::Add<#name> for u32 {
271 type Output = #name;
272 #[inline(always)]
273 fn add(self, rhs: #name) -> #name {
274 #name(rhs.0 + (self as u64))
275 }
276 }
277
278 impl core::ops::Add<#name> for u16 {
279 type Output = #name;
280 #[inline(always)]
281 fn add(self, rhs: #name) -> #name {
282 #name(rhs.0 + (self as u64))
283 }
284 }
285
286 impl core::ops::Add<#name> for u8 {
287 type Output = #name;
288 #[inline(always)]
289 fn add(self, rhs: #name) -> #name {
290 #name(rhs.0 + (self as u64))
291 }
292 }
293
294 impl core::ops::Mul<#name> for u64 {
295 type Output = #name;
296 #[inline(always)]
297 fn mul(self, rhs: #name) -> #name {
298 #name(rhs.0 * self)
299 }
300 }
301
302 impl core::ops::Mul<#name> for u32 {
303 type Output = #name;
304 #[inline(always)]
305 fn mul(self, rhs: #name) -> #name {
306 #name(rhs.0 * (self as u64))
307 }
308 }
309
310 impl core::ops::Mul<#name> for u16 {
311 type Output = #name;
312 #[inline(always)]
313 fn mul(self, rhs: #name) -> #name {
314 #name(rhs.0 * (self as u64))
315 }
316 }
317
318 impl core::ops::Mul<#name> for u8 {
319 type Output = #name;
320 #[inline(always)]
321 fn mul(self, rhs: #name) -> #name {
322 #name(rhs.0 * (self as u64))
323 }
324 }
325
326 #[cfg(target_pointer_width = "64")]
327 impl core::ops::Add<#name> for usize {
328 type Output = #name;
329 #[inline(always)]
330 fn add(self, rhs: #name) -> #name {
331 #name(rhs.0 + (self as u64))
332 }
333 }
334
335
336 #[cfg(target_pointer_width = "64")]
337 impl core::ops::Sub<#name> for usize {
338 type Output = #name;
339 #[inline(always)]
340 fn sub(self, rhs: #name) -> #name {
341 #name(self as u64 - rhs.0)
342 }
343 }
344
345 #[cfg(target_pointer_width = "64")]
346 impl core::ops::Mul<#name> for usize {
347 type Output = #name;
348 #[inline(always)]
349 fn mul(self, rhs: #name) -> #name {
350 #name(rhs.0 * (self as u64))
351 }
352 }
353
354 impl #name {
355 pub fn range<I: Into<Self>>(start: I, stop: I) -> ::humanbyte::HumanByteRange<Self> {
357 ::humanbyte::HumanByteRange::new(Some(start), Some(stop))
358 }
359
360 pub fn range_start<I: Into<Self>>(start: I) -> ::humanbyte::HumanByteRange<Self> {
362 ::humanbyte::HumanByteRange::new(Some(start), None)
363 }
364
365 pub fn range_stop<I: Into<Self>>(stop: I) -> ::humanbyte::HumanByteRange<Self> {
367 ::humanbyte::HumanByteRange::new(None, Some(stop.into()))
368 }
369 }
370 }
371}
372
373#[proc_macro_derive(HumanByteDisplay)]
374pub fn humanbyte_display(input: TokenStream) -> TokenStream {
375 let input = parse_macro_input!(input as DeriveInput);
376 TokenStream::from(display_tokens(&input.ident))
377}
378
379fn display_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
380 quote! {
381 impl core::fmt::Display for #name {
382 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
383 use core::fmt::Write as _;
384
385 let precision = f.precision().unwrap_or(1);
389 let s = ::humanbyte::to_string_with_precision(
390 self.0,
391 ::humanbyte::Format::IEC,
392 precision,
393 );
394 let pad = f.width().unwrap_or(0).saturating_sub(s.len());
395 let (left, right) = match f.align() {
396 Some(core::fmt::Alignment::Right) => (pad, 0),
397 Some(core::fmt::Alignment::Center) => (pad / 2, pad - pad / 2),
398 _ => (0, pad),
400 };
401 for _ in 0..left {
402 f.write_char(f.fill())?;
403 }
404 f.write_str(&s)?;
405 for _ in 0..right {
406 f.write_char(f.fill())?;
407 }
408 Ok(())
409 }
410 }
411
412 impl core::fmt::Debug for #name {
413 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
414 write!(f, "{}", self)
415 }
416 }
417 }
418}
419
420#[proc_macro_derive(HumanByteFromStr)]
421pub fn humanbyte_fromstr(input: TokenStream) -> TokenStream {
422 let input = parse_macro_input!(input as DeriveInput);
423 TokenStream::from(fromstr_tokens(&input.ident))
424}
425
426fn fromstr_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
427 quote! {
428 impl core::str::FromStr for #name {
429 type Err = ::humanbyte::ParseError;
430
431 fn from_str(value: &str) -> core::result::Result<Self, Self::Err> {
432 ::humanbyte::parse(value).map(Self)
433 }
434 }
435 }
436}
437
438#[proc_macro_derive(HumanByteParse)]
439pub fn humanbyte_parse(input: TokenStream) -> TokenStream {
440 let input = parse_macro_input!(input as DeriveInput);
441 TokenStream::from(parse_tokens(&input.ident))
442}
443
444fn parse_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
445 quote! {
446 impl #name {
447 #[inline(always)]
449 pub fn to_string_as(&self, format: ::humanbyte::Format) -> ::humanbyte::String {
450 ::humanbyte::to_string(self.0, format)
451 }
452
453 #[inline(always)]
455 pub fn to_string_with_precision(
456 &self,
457 format: ::humanbyte::Format,
458 precision: usize,
459 ) -> ::humanbyte::String {
460 ::humanbyte::to_string_with_precision(self.0, format, precision)
461 }
462
463 #[inline(always)]
465 pub const fn as_u64(&self) -> u64 {
466 self.0
467 }
468
469 #[cfg(target_pointer_width = "64")]
471 #[inline(always)]
472 pub const fn as_usize(&self) -> usize {
473 self.0 as usize
474 }
475 }
476 }
477}
478
479#[proc_macro_derive(HumanByteSerde)]
480pub fn humanbyte_serde(input: TokenStream) -> TokenStream {
481 let input = parse_macro_input!(input as DeriveInput);
482 TokenStream::from(serde_tokens(&input.ident))
483}
484
485fn serde_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
486 quote! {
487 impl<'de> ::humanbyte::serde_crate::Deserialize<'de> for #name {
488 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
489 where
490 D: ::humanbyte::serde_crate::Deserializer<'de>,
491 {
492 struct ByteSizeVisitor;
493
494 impl<'de> ::humanbyte::serde_crate::de::Visitor<'de> for ByteSizeVisitor {
495 type Value = #name;
496
497 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
498 formatter.write_str("an integer or string")
499 }
500
501 fn visit_i64<E: ::humanbyte::serde_crate::de::Error>(self, value: i64) -> core::result::Result<Self::Value, E> {
502 if let Ok(val) = u64::try_from(value) {
503 Ok(#name(val))
504 } else {
505 Err(E::invalid_value(
506 ::humanbyte::serde_crate::de::Unexpected::Signed(value),
507 &"integer overflow",
508 ))
509 }
510 }
511
512 fn visit_u64<E: ::humanbyte::serde_crate::de::Error>(self, value: u64) -> core::result::Result<Self::Value, E> {
513 Ok(#name(value))
514 }
515
516 fn visit_str<E: ::humanbyte::serde_crate::de::Error>(self, value: &str) -> core::result::Result<Self::Value, E> {
517 if let Ok(val) = value.parse() {
518 Ok(val)
519 } else {
520 Err(E::invalid_value(
521 ::humanbyte::serde_crate::de::Unexpected::Str(value),
522 &"parsable string",
523 ))
524 }
525 }
526 }
527
528 if deserializer.is_human_readable() {
529 deserializer.deserialize_any(ByteSizeVisitor)
530 } else {
531 deserializer.deserialize_u64(ByteSizeVisitor)
532 }
533 }
534 }
535 impl ::humanbyte::serde_crate::Serialize for #name {
536 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
537 where
538 S: ::humanbyte::serde_crate::Serializer,
539 {
540 if serializer.is_human_readable() {
541 <str>::serialize(self.to_string().as_str(), serializer)
542 } else {
543 self.0.serialize(serializer)
544 }
545 }
546 }
547 }
548}
549
550#[proc_macro_derive(HumanByteSchema)]
551pub fn humanbyte_schema(input: TokenStream) -> TokenStream {
552 let input = parse_macro_input!(input as DeriveInput);
553 TokenStream::from(schemars_tokens(&input.ident))
554}
555
556fn schemars_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
557 quote! {
558 impl ::humanbyte::schemars_crate::JsonSchema for #name {
559 fn schema_name() -> ::humanbyte::Cow<'static, str> {
560 ::humanbyte::Cow::Borrowed(stringify!(#name))
561 }
562
563 fn schema_id() -> ::humanbyte::Cow<'static, str> {
564 ::humanbyte::Cow::Borrowed(concat!(module_path!(), "::", stringify!(#name)))
565 }
566
567 fn json_schema(
568 _generator: &mut ::humanbyte::schemars_crate::SchemaGenerator,
569 ) -> ::humanbyte::schemars_crate::Schema {
570 ::humanbyte::schemars_crate::json_schema!({
571 "type": ["string", "integer"],
572 "description": "A byte size, as either a human-readable string (e.g. \"1.5 KiB\") or a number of bytes",
573 })
574 }
575 }
576 }
577}