diff --git a/docs/input/live/progress.md b/docs/input/live/progress.md
index ecb8587..ef46eaa 100644
--- a/docs/input/live/progress.md
+++ b/docs/input/live/progress.md
@@ -85,6 +85,8 @@ AnsiConsole.Progress()
new PercentageColumn(), // Percentage
new RemainingTimeColumn(), // Remaining time
new SpinnerColumn(), // Spinner
+ new DownloadedColumn(), // Downloaded
+ new TransferSpeedColumn(), // Transfer speed
})
.Start(ctx =>
{
diff --git a/src/Spectre.Console/Internal/FileSize.cs b/src/Spectre.Console/Internal/FileSize.cs
index 4a8254a..b47a612 100644
--- a/src/Spectre.Console/Internal/FileSize.cs
+++ b/src/Spectre.Console/Internal/FileSize.cs
@@ -3,33 +3,71 @@ namespace Spectre.Console;
internal struct FileSize
{
public double Bytes { get; }
- public FileSizeUnit Unit { get; }
+ public double Bits => Bytes * 8;
+
+ public FileSizePrefix Prefix { get; } = FileSizePrefix.None;
+
+ private readonly FileSizeBase _prefixBase = FileSizeBase.Binary;
+
+ ///
+ /// If enabled, will display the output in bits, rather than bytes.
+ ///
+ private readonly bool _showBits = false;
+
public string Suffix => GetSuffix();
public FileSize(double bytes)
{
Bytes = bytes;
- Unit = Detect(bytes);
+ Prefix = DetectPrefix(bytes);
}
- public FileSize(double bytes, FileSizeUnit unit)
+ public FileSize(double bytes, FileSizeBase @base)
{
Bytes = bytes;
- Unit = unit;
+ _prefixBase = @base;
+ Prefix = DetectPrefix(bytes);
+ }
+
+ public FileSize(double bytes, FileSizeBase @base, bool showBits)
+ {
+ Bytes = bytes;
+ _showBits = showBits;
+
+ _prefixBase = @base;
+ Prefix = DetectPrefix(bytes);
+ }
+
+ public FileSize(double bytes, FileSizePrefix prefix)
+ {
+ Bytes = bytes;
+ Prefix = prefix;
+ }
+
+ public FileSize(double bytes, FileSizePrefix prefix, FileSizeBase @base, bool showBits)
+ {
+ Bytes = bytes;
+ _showBits = showBits;
+
+ _prefixBase = @base;
+ Prefix = prefix;
}
public string Format(CultureInfo? culture = null)
{
- var @base = GetBase(Unit);
- if (@base == 0)
+ var unitBase = Math.Pow((int)_prefixBase, (int)Prefix);
+
+ if (_showBits)
{
- @base = 1;
+ var bits = Bits / unitBase;
+ return Prefix == FileSizePrefix.None ?
+ ((int)bits).ToString(culture ?? CultureInfo.InvariantCulture)
+ : bits.ToString("F1", culture ?? CultureInfo.InvariantCulture);
}
- var bytes = Bytes / @base;
-
- return Unit == FileSizeUnit.Byte
- ? ((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
+ var bytes = Bytes / unitBase;
+ return Prefix == FileSizePrefix.None ?
+ ((int)bytes).ToString(culture ?? CultureInfo.InvariantCulture)
: bytes.ToString("F1", culture ?? CultureInfo.InvariantCulture);
}
@@ -50,36 +88,67 @@ internal struct FileSize
private string GetSuffix()
{
- return (Bytes, Unit) switch
+ return (Bytes, Unit: Prefix, PrefixBase: _prefixBase, ShowBits: _showBits) switch
{
- (_, FileSizeUnit.KiloByte) => "KB",
- (_, FileSizeUnit.MegaByte) => "MB",
- (_, FileSizeUnit.GigaByte) => "GB",
- (_, FileSizeUnit.TeraByte) => "TB",
- (_, FileSizeUnit.PetaByte) => "PB",
- (_, FileSizeUnit.ExaByte) => "EB",
- (_, FileSizeUnit.ZettaByte) => "ZB",
- (_, FileSizeUnit.YottaByte) => "YB",
- (1, _) => "byte",
- (_, _) => "bytes",
+ (_, FileSizePrefix.Kilo, FileSizeBase.Binary, false) => "KiB",
+ (_, FileSizePrefix.Mega, FileSizeBase.Binary, false) => "MiB",
+ (_, FileSizePrefix.Giga, FileSizeBase.Binary, false) => "GiB",
+ (_, FileSizePrefix.Tera, FileSizeBase.Binary, false) => "TiB",
+ (_, FileSizePrefix.Peta, FileSizeBase.Binary, false) => "PiB",
+ (_, FileSizePrefix.Exa, FileSizeBase.Binary, false) => "EiB",
+ (_, FileSizePrefix.Zetta, FileSizeBase.Binary, false) => "ZiB",
+ (_, FileSizePrefix.Yotta, FileSizeBase.Binary, false) => "YiB",
+
+ (_, FileSizePrefix.Kilo, FileSizeBase.Binary, true) => "Kibit",
+ (_, FileSizePrefix.Mega, FileSizeBase.Binary, true) => "Mibit",
+ (_, FileSizePrefix.Giga, FileSizeBase.Binary, true) => "Gibit",
+ (_, FileSizePrefix.Tera, FileSizeBase.Binary, true) => "Tibit",
+ (_, FileSizePrefix.Peta, FileSizeBase.Binary, true) => "Pibit",
+ (_, FileSizePrefix.Exa, FileSizeBase.Binary, true) => "Eibit",
+ (_, FileSizePrefix.Zetta, FileSizeBase.Binary, true) => "Zibit",
+ (_, FileSizePrefix.Yotta, FileSizeBase.Binary, true) => "Yibit",
+
+ (_, FileSizePrefix.Kilo, FileSizeBase.Decimal, false) => "KB",
+ (_, FileSizePrefix.Mega, FileSizeBase.Decimal, false) => "MB",
+ (_, FileSizePrefix.Giga, FileSizeBase.Decimal, false) => "GB",
+ (_, FileSizePrefix.Tera, FileSizeBase.Decimal, false) => "TB",
+ (_, FileSizePrefix.Peta, FileSizeBase.Decimal, false) => "PB",
+ (_, FileSizePrefix.Exa, FileSizeBase.Decimal, false) => "EB",
+ (_, FileSizePrefix.Zetta, FileSizeBase.Decimal, false) => "ZB",
+ (_, FileSizePrefix.Yotta, FileSizeBase.Decimal, false) => "YB",
+
+ (_, FileSizePrefix.Kilo, FileSizeBase.Decimal, true) => "Kbit",
+ (_, FileSizePrefix.Mega, FileSizeBase.Decimal, true) => "Mbit",
+ (_, FileSizePrefix.Giga, FileSizeBase.Decimal, true) => "Gbit",
+ (_, FileSizePrefix.Tera, FileSizeBase.Decimal, true) => "Tbit",
+ (_, FileSizePrefix.Peta, FileSizeBase.Decimal, true) => "Pbit",
+ (_, FileSizePrefix.Exa, FileSizeBase.Decimal, true) => "Ebit",
+ (_, FileSizePrefix.Zetta, FileSizeBase.Decimal, true) => "Zbit",
+ (_, FileSizePrefix.Yotta, FileSizeBase.Decimal, true) => "Ybit",
+
+ (1, _, _, true) => "bit",
+ (_, _, _, true) => "bits",
+ (1, _, _, false) => "byte",
+ (_, _, _, false) => "bytes",
};
}
- private static FileSizeUnit Detect(double bytes)
+ private FileSizePrefix DetectPrefix(double bytes)
{
- foreach (var unit in (FileSizeUnit[])Enum.GetValues(typeof(FileSizeUnit)))
+ if (_showBits)
{
- if (bytes < (GetBase(unit) * 1024))
+ bytes *= 8;
+ }
+
+ foreach (var prefix in (FileSizePrefix[])Enum.GetValues(typeof(FileSizePrefix)))
+ {
+ // Trying to find the largest unit, that the number of bytes can fit under. Ex. 40kb < 1mb
+ if (bytes < Math.Pow((int)_prefixBase, (int)prefix + 1))
{
- return unit;
+ return prefix;
}
}
- return FileSizeUnit.Byte;
- }
-
- private static double GetBase(FileSizeUnit unit)
- {
- return Math.Pow(1024, (int)unit);
+ return FileSizePrefix.None;
}
}
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/FileSizeBase.cs b/src/Spectre.Console/Internal/FileSizeBase.cs
new file mode 100644
index 0000000..8dd73cb
--- /dev/null
+++ b/src/Spectre.Console/Internal/FileSizeBase.cs
@@ -0,0 +1,17 @@
+namespace Spectre.Console;
+
+///
+/// Determines possible file size base prefixes. (base 2/base 10).
+///
+public enum FileSizeBase
+{
+ ///
+ /// The SI prefix definition (base 10) of kilobyte, megabyte, etc.
+ ///
+ Decimal = 1000,
+
+ ///
+ /// The IEC binary prefix definition (base 2) of kibibyte, mebibyte, etc.
+ ///
+ Binary = 1024,
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/FileSizePrefix.cs b/src/Spectre.Console/Internal/FileSizePrefix.cs
new file mode 100644
index 0000000..36def53
--- /dev/null
+++ b/src/Spectre.Console/Internal/FileSizePrefix.cs
@@ -0,0 +1,14 @@
+namespace Spectre.Console;
+
+internal enum FileSizePrefix
+{
+ None = 0,
+ Kilo = 1,
+ Mega = 2,
+ Giga = 3,
+ Tera = 4,
+ Peta = 5,
+ Exa = 6,
+ Zetta = 7,
+ Yotta = 8,
+}
\ No newline at end of file
diff --git a/src/Spectre.Console/Internal/FileSizeUnit.cs b/src/Spectre.Console/Internal/FileSizeUnit.cs
deleted file mode 100644
index aca46e5..0000000
--- a/src/Spectre.Console/Internal/FileSizeUnit.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Spectre.Console;
-
-internal enum FileSizeUnit
-{
- Byte = 0,
- KiloByte = 1,
- MegaByte = 2,
- GigaByte = 3,
- TeraByte = 4,
- PetaByte = 5,
- ExaByte = 6,
- ZettaByte = 7,
- YottaByte = 8,
-}
\ No newline at end of file
diff --git a/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs b/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
index 6162986..99e6939 100644
--- a/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/DownloadedColumn.cs
@@ -10,10 +10,20 @@ public sealed class DownloadedColumn : ProgressColumn
///
public CultureInfo? Culture { get; set; }
+ ///
+ /// Gets or sets the to use.
+ ///
+ public FileSizeBase Base { get; set; } = FileSizeBase.Binary;
+
+ ///
+ /// Gets or sets a value indicating whether to display the transfer speed in bits.
+ ///
+ public bool ShowBits { get; set; }
+
///
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
- var total = new FileSize(task.MaxValue);
+ var total = new FileSize(task.MaxValue, Base, ShowBits);
if (task.IsFinished)
{
@@ -24,7 +34,7 @@ public sealed class DownloadedColumn : ProgressColumn
}
else
{
- var downloaded = new FileSize(task.Value, total.Unit);
+ var downloaded = new FileSize(task.Value, total.Prefix, Base, ShowBits);
return new Markup(string.Format(
"{0}[grey]/[/]{1} [grey]{2}[/]",
diff --git a/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs b/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
index 84f2743..5d253d1 100644
--- a/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
+++ b/src/Spectre.Console/Live/Progress/Columns/TransferSpeedColumn.cs
@@ -10,6 +10,16 @@ public sealed class TransferSpeedColumn : ProgressColumn
///
public CultureInfo? Culture { get; set; }
+ ///
+ /// Gets or sets the to use.
+ ///
+ public FileSizeBase Base { get; set; } = FileSizeBase.Binary;
+
+ ///
+ /// Gets or sets a value indicating whether to display the transfer speed in bits.
+ ///
+ public bool ShowBits { get; set; }
+
///
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
{
@@ -18,7 +28,14 @@ public sealed class TransferSpeedColumn : ProgressColumn
return new Text("?/s");
}
- var size = new FileSize(task.Speed.Value);
- return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
+ if (task.IsFinished)
+ {
+ return new Markup(string.Empty, Style.Plain);
+ }
+ else
+ {
+ var size = new FileSize(task.Speed.Value, Base, ShowBits);
+ return new Markup(string.Format("{0}/s", size.ToString(suffix: true, Culture)));
+ }
}
}
\ No newline at end of file
diff --git a/src/Tests/Spectre.Console.Tests/Unit/Internal/FileSizeTests.cs b/src/Tests/Spectre.Console.Tests/Unit/Internal/FileSizeTests.cs
new file mode 100644
index 0000000..92c7653
--- /dev/null
+++ b/src/Tests/Spectre.Console.Tests/Unit/Internal/FileSizeTests.cs
@@ -0,0 +1,85 @@
+namespace Spectre.Console.Tests.Unit.Internal;
+
+public sealed class FileSizeTests
+{
+ [Theory]
+ [InlineData(0, "0 bytes")]
+ [InlineData(37, "37 bytes")]
+ [InlineData(512, "512 bytes")]
+ [InlineData(15 * 1024, "15.0 KiB")]
+ [InlineData(1024 * 512, "512.0 KiB")]
+ [InlineData(5 * 1024 * 1024, "5.0 MiB")]
+ [InlineData(9 * 1024 * 1024, "9.0 MiB")]
+ public void Binary_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
+ {
+ // Given
+ var filesize = new FileSize(bytes, FileSizeBase.Binary);
+
+ // When
+ var result = filesize.ToString();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(0, "0 bits")]
+ [InlineData(37, "296 bits")]
+ [InlineData(512, "4.0 Kibit")]
+ [InlineData(15 * 1024, "120.0 Kibit")]
+ [InlineData(1024 * 512, "4.0 Mibit")]
+ [InlineData(5 * 1024 * 1024, "40.0 Mibit")]
+ [InlineData(210 * 1024 * 1024, "1.6 Gibit")]
+ [InlineData(900 * 1024 * 1024, "7.0 Gibit")]
+ public void Binary_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
+ {
+ // Given
+ var filesize = new FileSize(bytes, FileSizeBase.Binary, showBits: true);
+
+ // When
+ var result = filesize.ToString();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(0, "0 bytes")]
+ [InlineData(37, "37 bytes")]
+ [InlineData(512, "512 bytes")]
+ [InlineData(15 * 1024, "15.4 KB")]
+ [InlineData(1024 * 512, "524.3 KB")]
+ [InlineData(5 * 1024 * 1024, "5.2 MB")]
+ [InlineData(9 * 1024 * 1024, "9.4 MB")]
+ public void Decimal_Unit_In_Bytes_Should_Return_Expected(double bytes, string expected)
+ {
+ // Given
+ var filesize = new FileSize(bytes, FileSizeBase.Decimal);
+
+ // When
+ var result = filesize.ToString();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(0, "0 bits")]
+ [InlineData(37, "296 bits")]
+ [InlineData(512, "4.1 Kbit")]
+ [InlineData(15 * 1024, "122.9 Kbit")]
+ [InlineData(1024 * 512, "4.2 Mbit")]
+ [InlineData(5 * 1024 * 1024, "41.9 Mbit")]
+ [InlineData(900 * 1024 * 1024, "7.5 Gbit")]
+ public void Decimal_Unit_In_Bits_Should_Return_Expected(double bytes, string expected)
+ {
+ // Given
+ var filesize = new FileSize(bytes, FileSizeBase.Decimal, showBits: true);
+
+ // When
+ var result = filesize.ToString();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/Spectre.Console.Tests/Unit/Live/Progress/DownloadedColumnTests.cs b/src/Tests/Spectre.Console.Tests/Unit/Live/Progress/DownloadedColumnTests.cs
index 6b2b76f..452eaad 100644
--- a/src/Tests/Spectre.Console.Tests/Unit/Live/Progress/DownloadedColumnTests.cs
+++ b/src/Tests/Spectre.Console.Tests/Unit/Live/Progress/DownloadedColumnTests.cs
@@ -6,15 +6,71 @@ public sealed class DownloadedColumnTests
[InlineData(0, 1, "0/1 byte")]
[InlineData(37, 101, "37/101 bytes")]
[InlineData(101, 101, "101 bytes")]
- [InlineData(512, 1024, "0.5/1.0 KB")]
- [InlineData(1024, 1024, "1.0 KB")]
- [InlineData(1024 * 512, 5 * 1024 * 1024, "0.5/5.0 MB")]
- [InlineData(5 * 1024 * 1024, 5 * 1024 * 1024, "5.0 MB")]
- public void Should_Return_Correct_Value(double value, double total, string expected)
+ [InlineData(512, 1024, "0.5/1.0 KiB")]
+ [InlineData(1024, 1024, "1.0 KiB")]
+ [InlineData(1024 * 512, 5 * 1024 * 1024, "0.5/5.0 MiB")]
+ [InlineData(5 * 1024 * 1024, 5 * 1024 * 1024, "5.0 MiB")]
+ public void Binary_Unit_In_Bytes_Should_Return_Expected(double value, double total, string expected)
{
// Given
var fixture = new ProgressColumnFixture(value, total);
fixture.Column.Culture = CultureInfo.InvariantCulture;
+ fixture.Column.Base = FileSizeBase.Binary;
+ fixture.Column.ShowBits = false;
+
+ // When
+ var result = fixture.Render();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(512, 1024, "4.0/8.0 Kibit")]
+ [InlineData(1024, 1024, "8.0 Kibit")]
+ public void Binary_Unit_In_Bits_Should_Return_Expected(double value, double total, string expected)
+ {
+ // Given
+ var fixture = new ProgressColumnFixture(value, total);
+ fixture.Column.Culture = CultureInfo.InvariantCulture;
+ fixture.Column.Base = FileSizeBase.Binary;
+ fixture.Column.ShowBits = true;
+
+ // When
+ var result = fixture.Render();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(500, 1000, "0.5/1.0 KB")]
+ [InlineData(1000, 1000, "1.0 KB")]
+ public void Decimal_Unit_In_Bytes_Should_Return_Expected(double value, double total, string expected)
+ {
+ // Given
+ var fixture = new ProgressColumnFixture(value, total);
+ fixture.Column.Culture = CultureInfo.InvariantCulture;
+ fixture.Column.Base = FileSizeBase.Decimal;
+ fixture.Column.ShowBits = false;
+
+ // When
+ var result = fixture.Render();
+
+ // Then
+ result.ShouldBe(expected);
+ }
+
+ [Theory]
+ [InlineData(500, 1000, "4.0/8.0 Kbit")]
+ [InlineData(1000, 1000, "8.0 Kbit")]
+ public void Decimal_Unit_In_Bits_Should_Return_Expected(double value, double total, string expected)
+ {
+ // Given
+ var fixture = new ProgressColumnFixture(value, total);
+ fixture.Column.Culture = CultureInfo.InvariantCulture;
+ fixture.Column.Base = FileSizeBase.Decimal;
+ fixture.Column.ShowBits = true;
// When
var result = fixture.Render();