From 4a567a1bb91f1110e6d6ad92298595813c71e374 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Sat, 20 Jun 2026 15:10:15 +0100 Subject: [PATCH] runtime: format: add truncation and single-line support to value_writer --- bshell.runtime/command/native-command.c | 6 +- bshell.runtime/format/format.c | 2 +- bshell.runtime/format/table.c | 53 +++++----- bshell.runtime/format/value-writer.c | 126 ++++++++++++++++++++---- bshell.runtime/format/verb.c | 10 +- bshell.runtime/include/bshell/format.h | 14 ++- bshell.runtime/runtime/output.c | 2 +- 7 files changed, 160 insertions(+), 53 deletions(-) diff --git a/bshell.runtime/command/native-command.c b/bshell.runtime/command/native-command.c index 60a3793..6dc7eea 100644 --- a/bshell.runtime/command/native-command.c +++ b/bshell.runtime/command/native-command.c @@ -125,7 +125,11 @@ static enum bshell_status process_record( fx_stream *in_stream = fx_process_get_stdin(cmd_p->cmd_proc); if (!cmd_p->cmd_writer_init) { - bshell_value_writer_init(&cmd_p->cmd_writer, in_stream); + bshell_value_writer_init( + &cmd_p->cmd_writer, + 0, + 0, + in_stream); cmd_p->cmd_writer_init = true; } diff --git a/bshell.runtime/format/format.c b/bshell.runtime/format/format.c index e94d480..14b2a68 100644 --- a/bshell.runtime/format/format.c +++ b/bshell.runtime/format/format.c @@ -22,7 +22,7 @@ static void format_value_fallback(const fx_value *value, fx_stream *dest) enum bshell_status format_value_default(const fx_value *value, fx_stream *dest) { struct bshell_value_writer writer; - bshell_value_writer_init(&writer, dest); + bshell_value_writer_init(&writer, 0, 0, dest); bshell_value_writer_write(&writer, value); bshell_value_writer_cleanup(&writer); diff --git a/bshell.runtime/format/table.c b/bshell.runtime/format/table.c index 26de402..3e764b2 100644 --- a/bshell.runtime/format/table.c +++ b/bshell.runtime/format/table.c @@ -167,7 +167,7 @@ enum bshell_status bshell_table_ctx_prepare_columns_from_format( if (column_defs[i].col_width <= tty_width) { columns[i].col_width = column_defs[i].col_width; - tty_width -= columns[i].col_width; + tty_width -= (columns[i].col_width + 1); continue; } @@ -289,6 +289,10 @@ enum bshell_status bshell_table_ctx_print_headers(struct bshell_table_ctx *ctx) fx_stream_write_char(ctx->ctx_out, ' '); } + if (i < ctx->ctx_columns_count - 1) { + fx_stream_write_char(ctx->ctx_out, ' '); + } + if (to_write != title_chars_remaining) { repeat = true; } @@ -316,6 +320,10 @@ enum bshell_status bshell_table_ctx_print_headers(struct bshell_table_ctx *ctx) for (; x < c->col_width; x++) { fx_stream_write_char(ctx->ctx_out, ' '); } + + if (i < ctx->ctx_columns_count - 1) { + fx_stream_write_char(ctx->ctx_out, ' '); + } } reset_tty_colour(); @@ -327,14 +335,22 @@ enum bshell_status bshell_table_ctx_print_record( struct bshell_table_ctx *ctx, const fx_value *record) { - fx_stringstream *strm = fx_stringstream_create(); + struct bshell_value_writer writer; + size_t written = 0; + for (size_t i = 0; i < ctx->ctx_columns_count; i++) { struct bshell_table_ctx_column *c = &ctx->ctx_columns[i]; if (c->col_width == 0) { continue; } - fx_stringstream_reset(strm); + bshell_value_writer_init( + &writer, + BSHELL_VALUE_WRITER_NO_NEWLINE + | BSHELL_VALUE_WRITER_ONELINE + | BSHELL_VALUE_WRITER_TRUNCATE, + c->col_width, + ctx->ctx_out); fx_value column_value = FX_VALUE_EMPTY; if (c->col_property) { fx_status status = fx_property_get_value( @@ -342,34 +358,23 @@ enum bshell_status bshell_table_ctx_print_record( record, &column_value); if (FX_OK(status)) { - fx_value_to_string(&column_value, strm, NULL); + written = bshell_value_writer_write( + &writer, + &column_value); } } - size_t to_write = fx_stringstream_get_length(strm); - const char *s = fx_stringstream_get_cstr(strm); - bool overflow = false; - if (to_write > c->col_width) { - to_write = c->col_width - 2; - overflow = true; - } - - size_t x; - for (x = 0; x < to_write; x++) { - fx_stream_write_char(ctx->ctx_out, s[x]); - } - - if (overflow) { - fx_stream_write_char(ctx->ctx_out, L'…'); - x++; - } - - for (; x < c->col_width; x++) { + for (size_t x = written; x < c->col_width; x++) { fx_stream_write_char(ctx->ctx_out, ' '); } + + if (i < ctx->ctx_columns_count - 1) { + fx_stream_write_char(ctx->ctx_out, ' '); + } + + bshell_value_writer_cleanup(&writer); } fx_stream_write_char(ctx->ctx_out, '\n'); - fx_stringstream_unref(strm); return BSHELL_SUCCESS; } diff --git a/bshell.runtime/format/value-writer.c b/bshell.runtime/format/value-writer.c index 69b1444..51ab1f4 100644 --- a/bshell.runtime/format/value-writer.c +++ b/bshell.runtime/format/value-writer.c @@ -15,17 +15,22 @@ static void initialise_table( w->w_table_initialised = true; } -static void write_iterable(struct bshell_value_writer *w, const fx_value *value) +static size_t write_iterable( + struct bshell_value_writer *w, + const fx_value *value) { + size_t written = 0; fx_array *array = NULL; fx_value_get_object(value, &array); const fx_iterator *it = fx_iterator_begin(array); fx_foreach(v, it) { - bshell_value_writer_write(w, v); + written += bshell_value_writer_write(w, v); } fx_iterator_unref(it); + + return written; } static bool string_has_trailing_newline(const char *s) @@ -38,34 +43,105 @@ static bool string_has_trailing_newline(const char *s) return s[len - 1] == '\n'; } -static void write_string(struct bshell_value_writer *w, const fx_value *value) +static size_t write_control_char(struct bshell_value_writer *w, fx_wchar c) { - const char *str = NULL; - fx_value_get_cstr(value, &str); - fx_stream_write_cstr(w->w_dest, str, NULL); + bool oneline = (w->w_flags & BSHELL_VALUE_WRITER_ONELINE) != 0; + size_t written = 1; - if (!string_has_trailing_newline(str)) { - fx_stream_write_char(w->w_dest, '\n'); + switch (c) { + case '\n': + if (oneline) { + fx_stream_write_cstr(w->w_dest, "\\n", &written); + } else { + fx_stream_write_char(w->w_dest, '\n'); + } + break; + case '\r': + fx_stream_write_cstr(w->w_dest, "\\r", NULL); + break; + case '\t': + fx_stream_write_cstr(w->w_dest, "\\t", NULL); + break; + case '\b': + fx_stream_write_cstr(w->w_dest, "\\b", NULL); + break; + default: + written = 0; + break; } + + return written; } -static void write_value(struct bshell_value_writer *w, const fx_value *value) +static size_t write_string(struct bshell_value_writer *w, const fx_value *value) +{ + const char *s = NULL; + size_t written = 0; + fx_value_get_cstr(value, &s); + bool trailing_newline = string_has_trailing_newline(s); + bool inhibit_newline + = (w->w_flags & BSHELL_VALUE_WRITER_NO_NEWLINE) != 0; + bool truncate = (w->w_flags & BSHELL_VALUE_WRITER_TRUNCATE) != 0; + + for (written = 0; *s;) { + if (truncate && written == w->w_max_width - 1) { + fx_stream_write_char(w->w_dest, L'…'); + written++; + break; + } + + fx_wchar c = fx_wchar_utf8_codepoint_decode(s); + unsigned int stride = fx_wchar_utf8_codepoint_size(c); + if (stride == 0) { + break; + } + + if (!fx_wchar_is_graph(c)) { + written += write_control_char(w, c); + goto next; + } + + char cs[5] = {0}; + fx_wchar_utf8_codepoint_encode(c, cs); + + size_t n = 0; + fx_stream_write_cstr(w->w_dest, cs, &n); + written += n; + + next: + s += stride; + } + + if (!trailing_newline && !inhibit_newline) { + fx_stream_write_char(w->w_dest, '\n'); + written++; + } + + return written; +} + +static size_t write_value(struct bshell_value_writer *w, const fx_value *value) { const fx_type *ty = fx_type_get_by_id(value->v_type); - if (fx_value_is_type(value, FX_TYPE_STRING)) { - write_string(w, value); - return; + if (fx_value_is_type(value, FX_TYPE_STRING) + || fx_value_is_type(value, FX_TYPE_CSTR)) { + return write_string(w, value); } if (fx_type_get_interface(ty, FX_TYPE_ITERABLE)) { - write_iterable(w, value); - return; + return write_iterable(w, value); } if (fx_type_is_value_type(value->v_type)) { - fx_value_to_string(value, w->w_dest, NULL); - fx_stream_write_char(w->w_dest, '\n'); - return; + fx_stringstream_reset(w->w_tmp); + fx_value_to_string(value, w->w_tmp, NULL); + fx_stream_write_char(w->w_tmp, '\n'); + size_t result = fx_stringstream_get_length(w->w_tmp); + fx_stream_write_cstr( + w->w_dest, + fx_stringstream_get_cstr(w->w_tmp), + NULL); + return result; } if (!w->w_table_initialised) { @@ -73,19 +149,27 @@ static void write_value(struct bshell_value_writer *w, const fx_value *value) } bshell_table_ctx_print_record(&w->w_table_ctx, value); + return 0; } -void bshell_value_writer_init(struct bshell_value_writer *w, fx_stream *dest) +void bshell_value_writer_init( + struct bshell_value_writer *w, + enum bshell_value_writer_flags flags, + size_t max_width, + fx_stream *dest) { memset(w, 0x00, sizeof *w); w->w_dest = dest; + w->w_flags = flags; + w->w_max_width = max_width; + w->w_tmp = fx_stringstream_create(); } -void bshell_value_writer_write( +size_t bshell_value_writer_write( struct bshell_value_writer *w, const fx_value *value) { - write_value(w, value); + return write_value(w, value); } void bshell_value_writer_cleanup(struct bshell_value_writer *w) @@ -94,5 +178,7 @@ void bshell_value_writer_cleanup(struct bshell_value_writer *w) bshell_table_ctx_cleanup(&w->w_table_ctx); } + fx_stringstream_unref(w->w_tmp); + w->w_table_initialised = false; } diff --git a/bshell.runtime/format/verb.c b/bshell.runtime/format/verb.c index d43e272..3bcbe12 100644 --- a/bshell.runtime/format/verb.c +++ b/bshell.runtime/format/verb.c @@ -1,14 +1,14 @@ #include static struct bshell_table_column verb_table_columns[] = { - TABLE_COLUMN("verb", "verb", 13), - TABLE_COLUMN("alias_prefix", "alias_prefix", 13), - TABLE_COLUMN("group", "group", 13), + TABLE_COLUMN("verb", "verb", 12), + TABLE_COLUMN("alias_prefix", "alias_prefix", 12), + TABLE_COLUMN("group", "group", 12), TABLE_COLUMN("description", "description", COL_WIDTH_INFINITE), }; struct bshell_table verb_bshell_table = { .fmt_columns = verb_table_columns, - .fmt_column_count = sizeof verb_table_columns - / sizeof verb_table_columns[0], + .fmt_column_count + = sizeof verb_table_columns / sizeof verb_table_columns[0], }; diff --git a/bshell.runtime/include/bshell/format.h b/bshell.runtime/include/bshell/format.h index bb450f2..384fab4 100644 --- a/bshell.runtime/include/bshell/format.h +++ b/bshell.runtime/include/bshell/format.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #define TABLE_COLUMN(display_name, property_name, width) \ @@ -18,6 +19,12 @@ } #define COL_WIDTH_INFINITE ((size_t)-1) +enum bshell_value_writer_flags { + BSHELL_VALUE_WRITER_ONELINE = 0x01u, + BSHELL_VALUE_WRITER_TRUNCATE = 0x02u, + BSHELL_VALUE_WRITER_NO_NEWLINE = 0x04u, +}; + struct bshell_table_column { const char *col_display_name; const char *col_property_name; @@ -49,7 +56,10 @@ struct bshell_table_ctx { struct bshell_value_writer { struct bshell_table_ctx w_table_ctx; + enum bshell_value_writer_flags w_flags; + size_t w_max_width; bool w_table_initialised; + fx_stringstream *w_tmp; fx_stream *w_dest; }; @@ -82,8 +92,10 @@ extern enum bshell_status bshell_table_ctx_print_record( extern void bshell_value_writer_init( struct bshell_value_writer *w, + enum bshell_value_writer_flags flags, + size_t max_width, fx_stream *dest); -extern void bshell_value_writer_write( +extern size_t bshell_value_writer_write( struct bshell_value_writer *w, const fx_value *value); extern void bshell_value_writer_cleanup(struct bshell_value_writer *w); diff --git a/bshell.runtime/runtime/output.c b/bshell.runtime/runtime/output.c index b0b8c2a..3ad105a 100644 --- a/bshell.runtime/runtime/output.c +++ b/bshell.runtime/runtime/output.c @@ -4,7 +4,7 @@ void bshell_runtime_begin_output(struct bshell_runtime *rt) { - bshell_value_writer_init(&rt->rt_writer, fx_stdout); + bshell_value_writer_init(&rt->rt_writer, 0, 0, fx_stdout); } void bshell_runtime_output_value(