Skip to content

Commit 6e86c54

Browse files
author
AndrewMorgan1
committed
Added Customizable suffix sets (allowing non-standard units) and Added Additional helpers for formatting with alignment/padding
1 parent 35946dc commit 6e86c54

File tree

2 files changed

+78
-46
lines changed

2 files changed

+78
-46
lines changed

src/ByteFlow/ByteFlow/HumanBytesExtensions.cs

Lines changed: 34 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,26 @@ namespace ByteFlow
77
/// <summary>
88
/// Provides extension methods for converting between raw byte counts and human-readable sizes.
99
/// Supports both IEC (binary: KiB, MiB, GiB) and SI (decimal: KB, MB, GB) unit standards,
10-
/// and allows culture-aware formatting and parsing.
10+
/// allows culture-aware formatting and parsing, and supports custom suffix sets.
1111
/// </summary>
1212
public static class HumanBytesExtensions
1313
{
1414
/// <summary>
1515
/// Converts a number of bytes into a human-readable string using either
16-
/// SI (decimal: KB, MB, GB) or IEC (binary: KiB, MiB, GiB) units.
16+
/// SI (decimal: KB, MB, GB) or IEC (binary: KiB, MiB, GiB) units,
17+
/// or a custom suffix set if provided.
1718
/// </summary>
18-
/// <param name="bytes">The size in bytes.</param>
19-
/// <param name="decimalPlaces">Number of decimal places to display.</param>
20-
/// <param name="standard">Whether to use SI (base 1000) or IEC (base 1024) units.</param>
21-
/// <param name="formatProvider">
22-
/// The culture to use for formatting (e.g. decimal separator).
23-
/// Defaults to <see cref="CultureInfo.InvariantCulture"/>.
24-
/// </param>
25-
/// <returns>
26-
/// A formatted string such as "1.23 MB" (SI, en-US) or "1,23 MB" (SI, de-DE).
27-
/// </returns>
28-
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="bytes"/> is negative.</exception>
2919
public static string ToHumanBytes(
3020
this long bytes,
3121
int decimalPlaces = 2,
3222
UnitStandard standard = UnitStandard.IEC,
33-
IFormatProvider formatProvider = null)
23+
IFormatProvider formatProvider = null,
24+
(string Symbol, double Factor)[] customSuffixes = null)
3425
{
3526
if (bytes < 0)
3627
throw new ArgumentOutOfRangeException(nameof(bytes), "Value must be non-negative.");
3728

38-
var suffixes = standard == UnitStandard.SI ? SiSuffixes : IecSuffixes;
29+
var suffixes = customSuffixes ?? (standard == UnitStandard.SI ? SiSuffixes : IecSuffixes);
3930

4031
if (bytes == 0)
4132
return $"0 {suffixes[0].Symbol}";
@@ -57,28 +48,37 @@ public static string ToHumanBytes(
5748
}
5849

5950
/// <summary>
60-
/// Parses a human-readable size string (e.g. "2.5 GB" or "2,5 GiB") back into bytes,
61-
/// using either SI (decimal) or IEC (binary) interpretation.
51+
/// Converts a number of bytes into a human-readable string,
52+
/// padded to a given width for alignment.
53+
/// </summary>
54+
public static string ToHumanBytesAligned(
55+
this long bytes,
56+
int decimalPlaces = 2,
57+
UnitStandard standard = UnitStandard.IEC,
58+
int width = 10,
59+
char paddingChar = ' ',
60+
IFormatProvider formatProvider = null,
61+
(string Symbol, double Factor)[] customSuffixes = null)
62+
{
63+
var s = ToHumanBytes(bytes, decimalPlaces, standard, formatProvider, customSuffixes);
64+
return s.PadLeft(width, paddingChar);
65+
}
66+
67+
/// <summary>
68+
/// Parses a human-readable size string back into bytes,
69+
/// using either SI (decimal), IEC (binary), or a custom suffix set if provided.
6270
/// </summary>
63-
/// <param name="input">The input string, e.g. "1 KB", "1 KiB", "2.5 MB".</param>
64-
/// <param name="standard">Whether to interpret units as SI (base 1000) or IEC (base 1024).</param>
65-
/// <param name="formatProvider">
66-
/// The culture to use for parsing (e.g. decimal separator).
67-
/// Defaults to <see cref="CultureInfo.InvariantCulture"/>.
68-
/// </param>
69-
/// <returns>The size in bytes.</returns>
70-
/// <exception cref="ArgumentNullException">Thrown if <paramref name="input"/> is null or whitespace.</exception>
71-
/// <exception cref="FormatException">Thrown if the input string cannot be parsed.</exception>
7271
public static long ToBytes(
7372
this string input,
7473
UnitStandard standard = UnitStandard.IEC,
75-
IFormatProvider formatProvider = null)
74+
IFormatProvider formatProvider = null,
75+
(string Symbol, double Factor)[] customSuffixes = null)
7676
{
7777
if (string.IsNullOrWhiteSpace(input))
7878
throw new ArgumentNullException(nameof(input));
7979

8080
input = input.Trim();
81-
var suffixes = standard == UnitStandard.SI ? SiSuffixes : IecSuffixes;
81+
var suffixes = customSuffixes ?? (standard == UnitStandard.SI ? SiSuffixes : IecSuffixes);
8282

8383
foreach (var (symbol, factor) in suffixes.OrderByDescending(s => s.Symbol.Length))
8484
{
@@ -101,11 +101,7 @@ public static long ToBytes(
101101

102102
/// <summary>
103103
/// Safely parses a human-readable string into bytes.
104-
/// Returns <c>true</c> if parsing succeeds; otherwise <c>false</c>.
105104
/// </summary>
106-
/// <param name="input">The input string.</param>
107-
/// <param name="result">The parsed byte value if successful, or 0 otherwise.</param>
108-
/// <returns><c>true</c> if parsing was successful; otherwise <c>false</c>.</returns>
109105
public static bool TryParseHumanBytes(this string input, out long result)
110106
{
111107
try
@@ -121,25 +117,19 @@ public static bool TryParseHumanBytes(this string input, out long result)
121117
}
122118

123119
/// <summary>
124-
/// Safely parses a human-readable string into bytes, using either SI (decimal) or IEC (binary) units.
120+
/// Safely parses a human-readable string into bytes,
121+
/// supporting SI, IEC, or custom suffix sets.
125122
/// </summary>
126-
/// <param name="input">The input string (e.g. "1 KB", "1 KiB", "2.5 MB").</param>
127-
/// <param name="result">The parsed byte value if successful, or 0 otherwise.</param>
128-
/// <param name="standard">Whether to interpret units as SI (base 1000) or IEC (base 1024).</param>
129-
/// <param name="formatProvider">
130-
/// The culture to use for parsing (e.g. decimal separator).
131-
/// Defaults to <see cref="CultureInfo.InvariantCulture"/>.
132-
/// </param>
133-
/// <returns><c>true</c> if parsing was successful; otherwise <c>false</c>.</returns>
134123
public static bool TryParseHumanBytes(
135124
this string input,
136125
out long result,
137126
UnitStandard standard = UnitStandard.IEC,
138-
IFormatProvider formatProvider = null)
127+
IFormatProvider formatProvider = null,
128+
(string Symbol, double Factor)[] customSuffixes = null)
139129
{
140130
try
141131
{
142-
result = input.ToBytes(standard, formatProvider);
132+
result = input.ToBytes(standard, formatProvider, customSuffixes);
143133
return true;
144134
}
145135
catch
@@ -149,7 +139,7 @@ public static bool TryParseHumanBytes(
149139
}
150140
}
151141

152-
// --- Unit suffix definitions ---
142+
// --- Default suffix definitions ---
153143

154144
private static readonly (string Symbol, double Factor)[] SiSuffixes =
155145
{

tests/ByteFlow.Tests/HumanBytesExtensionsTests.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ public void ToHumanBytes_ShouldRespectCulture()
3636
Assert.Equal("1,23 MB", result);
3737
}
3838

39+
[Fact]
40+
public void ToHumanBytes_ShouldSupportCustomSuffixes()
41+
{
42+
var custom = new[] { ("X", 1d), ("KX", 1000d), ("MX", 1000000d) };
43+
string result = 2000L.ToHumanBytes(2, UnitStandard.SI, null, custom);
44+
Assert.Equal("2.00 KX", result);
45+
}
46+
47+
[Fact]
48+
public void ToHumanBytesAligned_ShouldPadCorrectly()
49+
{
50+
string result = 1234L.ToHumanBytesAligned(2, UnitStandard.SI, width: 12, paddingChar: '_');
51+
Assert.Equal("_____1.23 KB", result); // 5 underscores, total length 12
52+
}
53+
3954
[Fact]
4055
public void ToHumanBytes_ShouldThrowOnNegativeInput()
4156
{
@@ -85,6 +100,14 @@ public void ToBytes_ShouldParseAccordingToCulture(string input, long expected, s
85100
Assert.Equal(expected, result);
86101
}
87102

103+
[Fact]
104+
public void ToBytes_ShouldSupportCustomSuffixes()
105+
{
106+
var custom = new[] { ("X", 1d), ("KX", 1000d), ("MX", 1000000d) };
107+
long result = "2 KX".ToBytes(UnitStandard.SI, null, custom);
108+
Assert.Equal(2000, result);
109+
}
110+
88111
[Fact]
89112
public void ToBytes_InvalidString_ShouldThrow()
90113
{
@@ -148,7 +171,6 @@ public void ToBytes_InputWithoutSuffix_ShouldThrow()
148171
public void TryParseHumanBytes_DefaultOverload_ShouldReturnTrue()
149172
{
150173
bool success = "1 KiB".TryParseHumanBytes(out long result);
151-
152174
Assert.True(success);
153175
Assert.Equal(1024, result); // uses default IEC
154176
}
@@ -157,7 +179,6 @@ public void TryParseHumanBytes_DefaultOverload_ShouldReturnTrue()
157179
public void TryParseHumanBytes_DefaultOverload_ShouldReturnFalseOnInvalid()
158180
{
159181
bool success = "invalid".TryParseHumanBytes(out long result);
160-
161182
Assert.False(success);
162183
Assert.Equal(0, result);
163184
}
@@ -182,6 +203,16 @@ public void TryParseHumanBytes_ShouldRespectCulture()
182203
Assert.Equal((long)(2.5 * 1024 * 1024), result);
183204
}
184205

206+
[Fact]
207+
public void TryParseHumanBytes_ShouldSupportCustomSuffixes()
208+
{
209+
var custom = new[] { ("X", 1d), ("KX", 1000d) };
210+
bool success = "5 KX".TryParseHumanBytes(out long result, UnitStandard.SI, null, custom);
211+
212+
Assert.True(success);
213+
Assert.Equal(5000, result);
214+
}
215+
185216
[Theory]
186217
[InlineData("1 XB", UnitStandard.SI)]
187218
[InlineData("ten MB", UnitStandard.SI)]
@@ -209,5 +240,16 @@ public void RoundTrip_BytesToHumanAndBack_ShouldBeConsistent(long original, Unit
209240
long parsed = human.ToBytes(standard);
210241
Assert.Equal(original, parsed);
211242
}
243+
244+
[Fact]
245+
public void RoundTrip_WithCustomSuffixes_ShouldBeConsistent()
246+
{
247+
var custom = new[] { ("X", 1d), ("KX", 1000d), ("MX", 1000000d) };
248+
long original = 5000;
249+
string human = original.ToHumanBytes(2, UnitStandard.SI, null, custom);
250+
long parsed = human.ToBytes(UnitStandard.SI, null, custom);
251+
252+
Assert.Equal(original, parsed);
253+
}
212254
}
213255
}

0 commit comments

Comments
 (0)