Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
7 changes: 7 additions & 0 deletions bytescale/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ mod tests {
assert!(parse("1 000 B").is_err());
}

#[test]
#[should_panic(expected = "byte size overflows u64")]
fn test_constructor_overflow() {
// 20,000 PB doesn't fit in u64
let _ = ByteScale::pb(20_000);
}

#[test]
fn test_default() {
assert_eq!(ByteScale::b(0), ByteScale::default());
Expand Down
101 changes: 53 additions & 48 deletions humanbyte-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@ use syn::{DeriveInput, parse_macro_input};

#[proc_macro_derive(HumanByte)]
pub fn humanbyte(input: TokenStream) -> TokenStream {
let input_str = input.to_string();
let constructor = humanbyte_constructor(input_str.parse().unwrap());
let display = humanbyte_display(input_str.parse().unwrap());
let parse = humanbyte_parse(input_str.parse().unwrap());
let ops = humanbyte_ops(input_str.parse().unwrap());
let fromstr = humanbyte_fromstr(input_str.parse().unwrap());

let mut combined = format!("{}{}{}{}{}", constructor, display, parse, ops, fromstr);
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;

let mut combined = constructor_tokens(name);
combined.extend(display_tokens(name));
combined.extend(parse_tokens(name));
combined.extend(ops_tokens(name));
combined.extend(fromstr_tokens(name));
if cfg!(feature = "serde") {
let serde = humanbyte_serde(input_str.parse().unwrap());
combined = format!("{}{}", combined, serde);
combined.extend(serde_tokens(name));
}
combined.parse().unwrap()
TokenStream::from(combined)
}

#[proc_macro_derive(HumanByteConstructor)]
pub fn humanbyte_constructor(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(constructor_tokens(&input.ident))
}

fn constructor_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
// Define units with their multipliers and descriptions
let units = vec![
("b", "1", "bytes"),
Expand All @@ -49,19 +50,25 @@ pub fn humanbyte_constructor(input: TokenStream) -> TokenStream {
let multiplier_expr: syn::Expr = syn::parse_str(multiplier).unwrap();

// Generate the documentation comment
let doc_comment = format!("Construct `{}` given an amount of {}.", name, description);
let doc_comment = format!(
"Construct `{}` given an amount of {}.\n\nPanics if the total byte count overflows `u64`.",
name, description
);

// Generate the method using quote!
quote! {
#[doc = #doc_comment]
#[inline(always)]
pub const fn #method_name(size: u64) -> Self {
Self(size * #multiplier_expr)
match size.checked_mul(#multiplier_expr) {
Some(bytes) => Self(bytes),
None => panic!("byte size overflows u64"),
}
}
}
});

let expanded = quote! {
quote! {
impl #name {
#(#methods)*
}
Expand All @@ -71,17 +78,17 @@ pub fn humanbyte_constructor(input: TokenStream) -> TokenStream {
Self(size)
}
}
};

TokenStream::from(expanded)
}
}

#[proc_macro_derive(HumanByteOps)]
pub fn humanbyte_ops(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(ops_tokens(&input.ident))
}

let expanded = quote! {
fn ops_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl core::ops::Add<#name> for #name {
type Output = #name;

Expand Down Expand Up @@ -281,22 +288,22 @@ pub fn humanbyte_ops(input: TokenStream) -> TokenStream {
::humanbyte::HumanByteRange::new(Some(start), None)
}

/// Provides `HumanByteRange` with explicit lower bound. Upper bound is set to `u64::MAX`.
/// Provides `HumanByteRange` with explicit upper bound. Lower bound is set to `0`.
pub fn range_stop<I: Into<Self>>(stop: I) -> ::humanbyte::HumanByteRange<Self> {
::humanbyte::HumanByteRange::new(None, Some(stop.into()))
}
}
};

TokenStream::from(expanded)
}
}

#[proc_macro_derive(HumanByteDisplay)]
pub fn humanbyte_display(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(display_tokens(&input.ident))
}

let expanded = quote! {
fn display_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl core::fmt::Display for #name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.pad(&::humanbyte::to_string(self.0, ::humanbyte::Format::IEC))
Expand All @@ -308,17 +315,17 @@ pub fn humanbyte_display(input: TokenStream) -> TokenStream {
write!(f, "{}", self)
}
}
};

TokenStream::from(expanded)
}
}

#[proc_macro_derive(HumanByteFromStr)]
pub fn humanbyte_fromstr(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(fromstr_tokens(&input.ident))
}

let expanded = quote! {
fn fromstr_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl core::str::FromStr for #name {
type Err = ::humanbyte::String;

Expand All @@ -345,17 +352,17 @@ pub fn humanbyte_fromstr(input: TokenStream) -> TokenStream {
}
}
}
};

TokenStream::from(expanded)
}
}

#[proc_macro_derive(HumanByteParse)]
pub fn humanbyte_parse(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(parse_tokens(&input.ident))
}

let expanded = quote! {
fn parse_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl #name {
/// Returns the size as a string with an optional SI unit.
#[inline(always)]
Expand All @@ -376,25 +383,25 @@ pub fn humanbyte_parse(input: TokenStream) -> TokenStream {
self.0 as usize
}
}
};

TokenStream::from(expanded)
}
}

#[proc_macro_derive(HumanByteSerde)]
pub fn humanbyte_serde(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
TokenStream::from(serde_tokens(&input.ident))
}

let expanded = quote! {
fn serde_tokens(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl<'de> ::humanbyte::serde::Deserialize<'de> for #name {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: ::humanbyte::serde::Deserializer<'de>,
{
struct ByteSizeVistor;
struct ByteSizeVisitor;

impl<'de> ::humanbyte::serde::de::Visitor<'de> for ByteSizeVistor {
impl<'de> ::humanbyte::serde::de::Visitor<'de> for ByteSizeVisitor {
type Value = #name;

fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Expand Down Expand Up @@ -429,9 +436,9 @@ pub fn humanbyte_serde(input: TokenStream) -> TokenStream {
}

if deserializer.is_human_readable() {
deserializer.deserialize_any(ByteSizeVistor)
deserializer.deserialize_any(ByteSizeVisitor)
} else {
deserializer.deserialize_u64(ByteSizeVistor)
deserializer.deserialize_u64(ByteSizeVisitor)
}
}
}
Expand All @@ -447,7 +454,5 @@ pub fn humanbyte_serde(input: TokenStream) -> TokenStream {
}
}
}
};

TokenStream::from(expanded)
}
}
Loading