According to https://zig.guide/standard-library/advanced-formatting/ strings can be formatted for example like this:
bufPrint(&b, "{s:_^6}", .{"hi!"})
However, this seems to require that the _
and 6
are constants.
Is there a way to change the fill character and the length dynamically? Something like in sprintf
in C?
yataro
October 31, 2024, 10:11am
2
const std = @import("std");
fn print_str(str: []const u8, fill: u21, len: usize) !void {
try std.fmt.formatText(str, "s", std.fmt.FormatOptions{
.alignment = .center,
.fill = fill,
.width = len,
}, std.io.getStdErr().writer());
}
pub fn main() !void {
try print_str("hi!", '_', 6);
}
Something like this? It’s a little inconvenient but I see no other solution.
3 Likes
dimdin
October 31, 2024, 10:47am
3
argument
(position), width
, and precision
can be specified at runtime as named format arguments.
The fill
character is specified only at comptime
as part of the format string.
Example:
const std = @import("std");
pub fn main() void {
std.debug.print("{[value]s:_^[width]}\n", .{
.value = "hi",
.width = 10,
});
}
prints: ____hi____
5 Likes
yataro
October 31, 2024, 10:58am
4
btw, in my opinion the documentation is not very clear about this feature (named format arguments not only for [argument]
)
2 Likes
dimdin
October 31, 2024, 11:16am
5
It is not documented at all. It is discovered by digging in the code
2 Likes
This is pretty cool hack!
However, how can I change the _
? what if I wanted to use a fill with character that the user can choose?
yataro
October 31, 2024, 11:43am
7
It is not possible with named arguments, due to limitation in fmt.Placeholder.parse
, I believe. It could be implemented, but will require parser fallback due to the optionality of [fill]
and [alignment]
. I see it as too complex to bother with, but maybe you should file an issue (or find one that explains why it’s like this).
I feel I’m so close, but my limited understanding of zig is still stopping me.
I loved the one line solution by dimdin. I could modify it to write into a buffer string:
_ = try std.fmt.bufPrint(&buf1, "{[left]s}{[title]s:_^[width]}{[right]s}\n", .{.left=[2]u8{s1,0}, .title=s, .width=c, .right=[2]u8{s0, 0},});
But as it turns out I cannot make the fill character dynamic.
Your solution, however, works with dynamic fill character. But I don’t understand how I can convert the print_str
to print_buf
so that I can store the formatted string. Because I am looking for a way to implement sprintf
, not just printf
.
Can you point me to the correct direction?
yataro
October 31, 2024, 12:26pm
9
convert the print_str
to print_buf
so that I can store the formatted string.
Adapt print_str
to something like (std.fmt.bufPrint
does almost the same):
const std = @import("std");
const fmt = std.fmt;
const io = std.io;
fn print_str(str: []const u8, fill: u21, len: usize, writer: anytype) !void {
try fmt.formatText(str, "s", fmt.FormatOptions{
.alignment = .center,
.fill = fill,
.width = len,
}, writer);
}
fn print_str_buf(buf: []u8, str: []const u8, fill: u21, len: usize) ![]u8 {
var stream = io.fixedBufferStream(buf);
try print_str(str, fill, len, stream.writer());
return stream.getWritten();
}
pub fn main() !void {
var buf: [6]u8 = undefined;
const str = try print_str_buf(&buf, "hi!", '_', buf.len);
std.debug.print("{s}\n", .{str});
}
1 Like
Thanks a million, this works!