format: do not force user to provide an alignment field when it's not necessary (#19049)

* format: fix default character when no alignment

When no alignment is specified, the character that should be used is the
fill character that is otherwise provided, not space.

This is closer to the default that C programmers (and other languages)
use: "04x" fills with zeroes (in zig as of today x:04 fills with spaces)

Test:

    const std = @import("std");
    const expectFmt = std.testing.expectFmt;

    test "fmt.defaultchar.no-alignment" {

        // as of today the following test passes:
        try expectFmt("0x00ff", "0x{x:0>4}", .{255});

        // as of today the following test fails (returns "0x  ff" instead)
        try expectFmt("0x00ff", "0x{x:04}", .{255});
    }

* non breaking improvement of string formatting

* improved comment

* simplify the code a little

* small improvement around how  characters identified as valid are consumed
This commit is contained in:
eric-saintetienne 2024-07-17 20:02:10 +01:00 committed by GitHub
parent 9d9b5a11e8
commit c3faae6bf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -20,11 +20,14 @@ pub const Alignment = enum {
right,
};
const default_alignment = .right;
const default_fill_char = ' ';
pub const FormatOptions = struct {
precision: ?usize = null,
width: ?usize = null,
alignment: Alignment = .right,
fill: u21 = ' ',
alignment: Alignment = default_alignment,
fill: u21 = default_fill_char,
};
/// Renders fmt string with args, calling `writer` with slices of bytes.
@ -48,8 +51,8 @@ pub const FormatOptions = struct {
///
/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when
/// all parameters after the separator are omitted.
/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise
/// the digits after `:` is interpreted as *width*, not *fill*.
/// Only exception is the *fill* parameter. If a non-zero *fill* character is required at the same time as *width* is specified,
/// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*.
///
/// The *specifier* has several options for types:
/// - `x` and `X`: output numeric value in hexadecimal notation
@ -239,29 +242,38 @@ pub const Placeholder = struct {
}
}
// Parse the fill character
// The fill parameter requires the alignment parameter to be specified
// too
const fill = comptime if (parser.peek(1)) |ch|
// Parse the fill character, if present.
// When the width field is also specified, the fill character must
// be followed by an alignment specifier, unless it's '0' (zero)
// (in which case it's handled as part of the width specifier)
var fill: ?u21 = comptime if (parser.peek(1)) |ch|
switch (ch) {
'<', '^', '>' => parser.char().?,
else => ' ',
'<', '^', '>' => parser.char(),
else => null,
}
else
' ';
null;
// Parse the alignment parameter
const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: {
const alignment: ?Alignment = comptime if (parser.peek(0)) |ch| init: {
switch (ch) {
'<', '^', '>' => _ = parser.char(),
else => {},
'<', '^', '>' => {
// consume the character
break :init switch (parser.char().?) {
'<' => .left,
'^' => .center,
else => .right,
};
},
else => break :init null,
}
break :init switch (ch) {
'<' => .left,
'^' => .center,
else => .right,
};
} else .right;
} else null;
// When none of the fill character and the alignment specifier have
// been provided, check whether the width starts with a zero.
if (fill == null and alignment == null) {
fill = comptime if (parser.peek(0) == '0') '0' else null;
}
// Parse the width parameter
const width = comptime parser.specifier() catch |err|
@ -284,8 +296,8 @@ pub const Placeholder = struct {
return Placeholder{
.specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*),
.fill = fill,
.alignment = alignment,
.fill = fill orelse default_fill_char,
.alignment = alignment orelse default_alignment,
.arg = arg,
.width = width,
.precision = precision,
@ -2648,6 +2660,15 @@ test "sci float padding" {
try expectFmt("right-pad: 3.142e0****\n", "right-pad: {e:*<11.3}\n", .{number});
}
test "padding.zero" {
try expectFmt("zero-pad: '0042'", "zero-pad: '{:04}'", .{42});
try expectFmt("std-pad: ' 42'", "std-pad: '{:10}'", .{42});
try expectFmt("std-pad-1: '001'", "std-pad-1: '{:0>3}'", .{1});
try expectFmt("std-pad-2: '911'", "std-pad-2: '{:1<03}'", .{9});
try expectFmt("std-pad-3: ' 1'", "std-pad-3: '{:>03}'", .{1});
try expectFmt("center-pad: '515'", "center-pad: '{:5^03}'", .{1});
}
test "null" {
const inst = null;
try expectFmt("null", "{}", .{inst});