| ... | ... | @@ -35,11 +35,11 @@ pub const FormatOptions = struct { |
| 35 | 35 | /// |
| 36 | 36 | /// The format string must be comptime known and may contain placeholders following |
| 37 | 37 | /// this format: |
| 38 | | /// `{[position][specifier]:[fill][alignment][width].[precision]}` |
| 38 | /// `{[argument][specifier]:[fill][alignment][width].[precision]}` |
| 39 | 39 | /// |
| 40 | 40 | /// Each word between `[` and `]` is a parameter you have to replace with something: |
| 41 | 41 | /// |
| 42 | | /// - *position* is the index of the argument that should be inserted |
| 42 | /// - *argument* is either the index or the name of the argument that should be inserted |
| 43 | 43 | /// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) |
| 44 | 44 | /// - *fill* is a single character which is used to pad the formatted text |
| 45 | 45 | /// - *alignment* is one of the three characters `<`, `^` or `>`. they define if the text is *left*, *center*, or *right* aligned |
| ... | ... | @@ -52,16 +52,10 @@ pub const FormatOptions = struct { |
| 52 | 52 | /// the digits after `:` is interpreted as *width*, not *fill*. |
| 53 | 53 | /// |
| 54 | 54 | /// The *specifier* has several options for types: |
| 55 | | /// - `x` and `X`: |
| 56 | | /// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case |
| 57 | | /// - output numeric value in hexadecimal notation |
| 55 | /// - `x` and `X`: output numeric value in hexadecimal notation |
| 58 | 56 | /// - `s`: |
| 59 | 57 | /// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination |
| 60 | 58 | /// - for slices of u8, print the entire slice as a string without zero-termination |
| 61 | | /// - `z`: escape the string with @"" syntax if it is not a valid Zig identifier. |
| 62 | | /// - `Z`: print the string escaping non-printable characters using Zig escape sequences. |
| 63 | | /// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values. |
| 64 | | /// - `e` and `E`: if printing a string, escape non-printable characters |
| 65 | 59 | /// - `e`: output floating point value in scientific notation |
| 66 | 60 | /// - `d`: output numeric value in decimal notation |
| 67 | 61 | /// - `b`: output integer value in binary notation |
| ... | ... | @@ -620,9 +614,9 @@ fn formatValue( |
| 620 | 614 | writer: anytype, |
| 621 | 615 | ) !void { |
| 622 | 616 | if (comptime std.mem.eql(u8, fmt, "B")) { |
| 623 | | return formatBytes(value, options, 1000, writer); |
| 617 | @compileError("specifier 'B' has been deprecated, wrap your argument in std.fmt.fmtIntSizeDec instead"); |
| 624 | 618 | } else if (comptime std.mem.eql(u8, fmt, "Bi")) { |
| 625 | | return formatBytes(value, options, 1024, writer); |
| 619 | @compileError("specifier 'Bi' has been deprecated, wrap your argument in std.fmt.fmtIntSizeBin instead"); |
| 626 | 620 | } |
| 627 | 621 | |
| 628 | 622 | const T = @TypeOf(value); |
| ... | ... | @@ -790,6 +784,67 @@ pub fn fmtSliceEscapeUpper(bytes: []const u8) std.fmt.Formatter(formatSliceEscap |
| 790 | 784 | return .{ .data = bytes }; |
| 791 | 785 | } |
| 792 | 786 | |
| 787 | fn formatSizeImpl(comptime radix: comptime_int) type { |
| 788 | return struct { |
| 789 | fn f( |
| 790 | value: u64, |
| 791 | comptime fmt: []const u8, |
| 792 | options: FormatOptions, |
| 793 | writer: anytype, |
| 794 | ) !void { |
| 795 | if (value == 0) { |
| 796 | return writer.writeAll("0B"); |
| 797 | } |
| 798 | |
| 799 | const mags_si = " kMGTPEZY"; |
| 800 | const mags_iec = " KMGTPEZY"; |
| 801 | |
| 802 | const log2 = math.log2(value); |
| 803 | const magnitude = switch (radix) { |
| 804 | 1000 => math.min(log2 / comptime math.log2(1000), mags_si.len - 1), |
| 805 | 1024 => math.min(log2 / 10, mags_iec.len - 1), |
| 806 | else => unreachable, |
| 807 | }; |
| 808 | const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, radix), lossyCast(f64, magnitude)); |
| 809 | const suffix = switch (radix) { |
| 810 | 1000 => mags_si[magnitude], |
| 811 | 1024 => mags_iec[magnitude], |
| 812 | else => unreachable, |
| 813 | }; |
| 814 | |
| 815 | try formatFloatDecimal(new_value, options, writer); |
| 816 | |
| 817 | if (suffix == ' ') { |
| 818 | return writer.writeAll("B"); |
| 819 | } |
| 820 | |
| 821 | const buf = switch (radix) { |
| 822 | 1000 => &[_]u8{ suffix, 'B' }, |
| 823 | 1024 => &[_]u8{ suffix, 'i', 'B' }, |
| 824 | else => unreachable, |
| 825 | }; |
| 826 | return writer.writeAll(buf); |
| 827 | } |
| 828 | }; |
| 829 | } |
| 830 | |
| 831 | const formatSizeDec = formatSizeImpl(1000).f; |
| 832 | const formatSizeBin = formatSizeImpl(1024).f; |
| 833 | |
| 834 | /// Return a Formatter for a u64 value representing a file size. |
| 835 | /// This formatter represents the number as multiple of 1000 and uses the SI |
| 836 | /// measurement units (kB, MB, GB, ...). |
| 837 | pub fn fmtIntSizeDec(value: u64) std.fmt.Formatter(formatSizeDec) { |
| 838 | return .{ .data = value }; |
| 839 | } |
| 840 | |
| 841 | /// Return a Formatter for a u64 value representing a file size. |
| 842 | /// This formatter represents the number as multiple of 1024 and uses the IEC |
| 843 | /// measurement units (KiB, MiB, GiB, ...). |
| 844 | pub fn fmtIntSizeBin(value: u64) std.fmt.Formatter(formatSizeBin) { |
| 845 | return .{ .data = value }; |
| 846 | } |
| 847 | |
| 793 | 848 | pub fn formatText( |
| 794 | 849 | bytes: []const u8, |
| 795 | 850 | comptime fmt: []const u8, |
| ... | ... | @@ -1111,47 +1166,6 @@ pub fn formatFloatDecimal( |
| 1111 | 1166 | } |
| 1112 | 1167 | } |
| 1113 | 1168 | |
| 1114 | | pub fn formatBytes( |
| 1115 | | value: anytype, |
| 1116 | | options: FormatOptions, |
| 1117 | | comptime radix: usize, |
| 1118 | | writer: anytype, |
| 1119 | | ) !void { |
| 1120 | | if (value == 0) { |
| 1121 | | return writer.writeAll("0B"); |
| 1122 | | } |
| 1123 | | |
| 1124 | | const is_float = comptime std.meta.trait.is(.Float)(@TypeOf(value)); |
| 1125 | | const mags_si = " kMGTPEZY"; |
| 1126 | | const mags_iec = " KMGTPEZY"; |
| 1127 | | |
| 1128 | | const log2 = if (is_float) @floatToInt(usize, math.log2(value)) else math.log2(value); |
| 1129 | | const magnitude = switch (radix) { |
| 1130 | | 1000 => math.min(log2 / comptime math.log2(1000), mags_si.len - 1), |
| 1131 | | 1024 => math.min(log2 / 10, mags_iec.len - 1), |
| 1132 | | else => unreachable, |
| 1133 | | }; |
| 1134 | | const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, radix), lossyCast(f64, magnitude)); |
| 1135 | | const suffix = switch (radix) { |
| 1136 | | 1000 => mags_si[magnitude], |
| 1137 | | 1024 => mags_iec[magnitude], |
| 1138 | | else => unreachable, |
| 1139 | | }; |
| 1140 | | |
| 1141 | | try formatFloatDecimal(new_value, options, writer); |
| 1142 | | |
| 1143 | | if (suffix == ' ') { |
| 1144 | | return writer.writeAll("B"); |
| 1145 | | } |
| 1146 | | |
| 1147 | | const buf = switch (radix) { |
| 1148 | | 1000 => &[_]u8{ suffix, 'B' }, |
| 1149 | | 1024 => &[_]u8{ suffix, 'i', 'B' }, |
| 1150 | | else => unreachable, |
| 1151 | | }; |
| 1152 | | return writer.writeAll(buf); |
| 1153 | | } |
| 1154 | | |
| 1155 | 1169 | pub fn formatInt( |
| 1156 | 1170 | value: anytype, |
| 1157 | 1171 | base: u8, |
| ... | ... | @@ -1806,8 +1820,12 @@ test "cstr" { |
| 1806 | 1820 | } |
| 1807 | 1821 | |
| 1808 | 1822 | test "filesize" { |
| 1809 | | try expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{@as(usize, 63 * 1024 * 1024)}); |
| 1810 | | try expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{@as(usize, 63 * 1024 * 1024)}); |
| 1823 | try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeDec(42)}); |
| 1824 | try expectFmt("file size: 42B\n", "file size: {}\n", .{fmtIntSizeBin(42)}); |
| 1825 | try expectFmt("file size: 63MB\n", "file size: {}\n", .{fmtIntSizeDec(63 * 1000 * 1000)}); |
| 1826 | try expectFmt("file size: 63MiB\n", "file size: {}\n", .{fmtIntSizeBin(63 * 1024 * 1024)}); |
| 1827 | try expectFmt("file size: 66.06MB\n", "file size: {:.2}\n", .{fmtIntSizeDec(63 * 1024 * 1024)}); |
| 1828 | try expectFmt("file size: 60.08MiB\n", "file size: {:.2}\n", .{fmtIntSizeBin(63 * 1000 * 1000)}); |
| 1811 | 1829 | } |
| 1812 | 1830 | |
| 1813 | 1831 | test "struct" { |
| ... | ... | @@ -2213,8 +2231,6 @@ test "vector" { |
| 2213 | 2231 | try expectFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64}); |
| 2214 | 2232 | try expectFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); |
| 2215 | 2233 | try expectFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); |
| 2216 | | try expectFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); |
| 2217 | | try expectFmt("{ 1000B, 1.953125KiB, 2.9296875KiB, 3.90625KiB }", "{Bi}", .{vu64}); |
| 2218 | 2234 | } |
| 2219 | 2235 | |
| 2220 | 2236 | test "enum-literal" { |