Files
bshell/bshell.runtime/format/table.c
T
2026-06-15 17:41:08 +01:00

376 lines
8.3 KiB
C

#include <bshell/format.h>
#include <fx/collections/array.h>
#include <fx/iterator.h>
#include <fx/reflection/type.h>
#include <fx/stringstream.h>
#include <fx/value.h>
#if BSHELL_INTERACTIVE == 1
#include <fx/term/print.h>
#include <fx/term/tty.h>
#endif
static fx_namemap bshell_tables = FX_NAMEMAP_INIT;
extern struct bshell_table hashtable_bshell_table;
extern struct bshell_table verb_bshell_table;
extern struct bshell_table command_bshell_table;
enum bshell_status bshell_table_init(void)
{
fx_namemap_put(
&bshell_tables,
"fx.collections.hashtable",
&hashtable_bshell_table.fmt_entry);
fx_namemap_put(
&bshell_tables,
"bshell.verb",
&verb_bshell_table.fmt_entry);
fx_namemap_put(
&bshell_tables,
"bshell.command",
&command_bshell_table.fmt_entry);
return BSHELL_SUCCESS;
}
const struct bshell_table *bshell_table_get_for_type(const fx_type *ty)
{
while (ty) {
const char *name = fx_type_get_name(ty);
fx_namemap_entry *entry = fx_namemap_get(&bshell_tables, name);
if (entry) {
return fx_unbox(struct bshell_table, entry, fmt_entry);
}
ty = fx_type_get_parent(ty);
}
return NULL;
}
enum bshell_status format_object_table(fx_object *object, fx_stream *dest)
{
const fx_type *type = fx_type_get_by_id(fx_object_query_type(object));
if (!type) {
return BSHELL_ERR_NOT_SUPPORTED;
}
const struct bshell_table *fmt = bshell_table_get_for_type(type);
if (!fmt) {
return BSHELL_ERR_NOT_SUPPORTED;
}
struct bshell_table_ctx ctx;
bshell_table_ctx_init(&ctx, dest);
const fx_iterator *it = fx_iterator_begin(object);
if (!it) {
bshell_table_ctx_prepare_columns_from_format(
&ctx,
fx_type_get_by_id(fx_object_query_type(object)),
fmt);
bshell_table_ctx_print_headers(&ctx);
bshell_table_ctx_print_record(&ctx, &FX_VALUE_OBJECT(object));
} else {
const fx_value *first = fx_iterator_get_value(it);
if (first) {
bshell_table_ctx_prepare_columns_from_format(
&ctx,
fx_type_get_by_id(first->v_type),
fmt);
}
bshell_table_ctx_print_headers(&ctx);
fx_foreach(value, it)
{
bshell_table_ctx_print_record(&ctx, value);
}
}
bshell_table_ctx_cleanup(&ctx);
return BSHELL_SUCCESS;
}
enum bshell_status bshell_table_ctx_init(
struct bshell_table_ctx *ctx,
fx_stream *out)
{
memset(ctx, 0x0, sizeof *ctx);
ctx->ctx_out = out;
return BSHELL_SUCCESS;
}
enum bshell_status bshell_table_ctx_cleanup(struct bshell_table_ctx *ctx)
{
if (ctx->ctx_columns) {
free(ctx->ctx_columns);
}
memset(ctx, 0x0, sizeof *ctx);
return BSHELL_SUCCESS;
}
enum bshell_status bshell_table_ctx_prepare_columns_from_format(
struct bshell_table_ctx *ctx,
const fx_type *record_type,
const struct bshell_table *fmt)
{
if (!fmt && !record_type) {
return BSHELL_ERR_INVALID_ARGUMENT;
}
if (!fmt) {
fmt = bshell_table_get_for_type(record_type);
}
if (!fmt) {
return bshell_table_ctx_prepare_columns_from_object(
ctx,
record_type);
}
ctx->ctx_fmt = fmt;
struct bshell_table_ctx_column *columns
= calloc(fmt->fmt_column_count, sizeof *columns);
if (!columns) {
return BSHELL_ERR_NO_MEMORY;
}
const struct bshell_table_column *column_defs = fmt->fmt_columns;
ctx->ctx_columns = columns;
ctx->ctx_columns_count = fmt->fmt_column_count;
ctx->ctx_tty_width = 0;
#if BSHELL_INTERACTIVE == 1
fx_tty_get_dimensions(fx_stdtty, &ctx->ctx_tty_width, NULL);
#endif
if (!ctx->ctx_tty_width) {
ctx->ctx_tty_width = 80;
}
ctx->ctx_variable_columns_count = 0;
unsigned int tty_width = ctx->ctx_tty_width;
for (size_t i = 0; i < fmt->fmt_column_count; i++) {
columns[i].col_property = fx_type_get_property(
record_type,
column_defs[i].col_property_name);
columns[i].col_title = column_defs[i].col_display_name;
columns[i].col_title_ptr = column_defs[i].col_display_name;
if (column_defs[i].col_width == COL_WIDTH_INFINITE) {
ctx->ctx_variable_columns_count++;
continue;
}
if (column_defs[i].col_width <= tty_width) {
columns[i].col_width = column_defs[i].col_width;
tty_width -= columns[i].col_width;
continue;
}
columns[i].col_width = tty_width;
tty_width = 0;
break;
}
for (size_t i = 0; i < fmt->fmt_column_count; i++) {
if (column_defs[i].col_width == COL_WIDTH_INFINITE) {
columns[i].col_width
= tty_width / ctx->ctx_variable_columns_count;
}
}
return BSHELL_SUCCESS;
}
enum bshell_status bshell_table_ctx_prepare_columns_from_object(
struct bshell_table_ctx *ctx,
const fx_type *record_type)
{
fx_array *properties = fx_array_create();
fx_iterator *it = fx_type_get_properties(record_type);
fx_foreach(prop_v, it)
{
fx_array_push_back(properties, *prop_v);
}
fx_iterator_unref(it);
size_t column_count = fx_array_get_size(properties);
if (!column_count) {
fx_array_unref(properties);
return BSHELL_SUCCESS;
}
struct bshell_table_ctx_column *columns
= calloc(column_count, sizeof *columns);
if (!columns) {
fx_array_unref(properties);
return BSHELL_ERR_NO_MEMORY;
}
ctx->ctx_columns = columns;
ctx->ctx_columns_count = column_count;
ctx->ctx_tty_width = 0;
#if BSHELL_INTERACTIVE == 1
fx_tty_get_dimensions(fx_stdtty, &ctx->ctx_tty_width, NULL);
#endif
if (!ctx->ctx_tty_width) {
ctx->ctx_tty_width = 80;
}
ctx->ctx_variable_columns_count = 0;
unsigned int tty_width = ctx->ctx_tty_width;
for (size_t i = 0; i < column_count; i++) {
fx_value *prop_v = fx_array_get_ref(properties, i);
fx_property *prop = NULL;
fx_value_get_object(prop_v, &prop);
columns[i].col_property = prop;
columns[i].col_title = fx_property_get_name(prop);
columns[i].col_title_ptr = columns[i].col_title;
columns[i].col_width = tty_width / column_count;
}
return BSHELL_SUCCESS;
}
static void set_table_header_tty_colour(void)
{
#if BSHELL_INTERACTIVE == 1
fx_printf("[bold,bright_green]");
#endif
}
static void reset_tty_colour(void)
{
#if BSHELL_INTERACTIVE == 1
fx_printf("[reset]");
#endif
}
enum bshell_status bshell_table_ctx_print_headers(struct bshell_table_ctx *ctx)
{
set_table_header_tty_colour();
bool repeat = false;
struct bshell_table_ctx_column *c = NULL;
do {
repeat = false;
for (size_t i = 0; i < ctx->ctx_columns_count; i++) {
c = &ctx->ctx_columns[i];
if (c->col_width == 0) {
continue;
}
size_t title_chars_remaining = strlen(c->col_title_ptr);
size_t to_write = title_chars_remaining;
if (to_write > c->col_width) {
to_write = c->col_width;
}
size_t x;
for (x = 0; x < to_write; x++) {
fx_stream_write_char(
ctx->ctx_out,
*c->col_title_ptr);
c->col_title_ptr++;
}
for (; x < c->col_width; x++) {
fx_stream_write_char(ctx->ctx_out, ' ');
}
if (to_write != title_chars_remaining) {
repeat = true;
}
}
} while (repeat);
fx_stream_write_char(ctx->ctx_out, '\n');
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;
}
size_t title_len = strlen(c->col_title);
if (title_len > c->col_width) {
title_len = c->col_width;
}
size_t x;
for (x = 0; x < title_len; x++) {
fx_stream_write_char(ctx->ctx_out, '-');
}
for (; x < c->col_width; x++) {
fx_stream_write_char(ctx->ctx_out, ' ');
}
}
reset_tty_colour();
fx_stream_write_char(ctx->ctx_out, '\n');
return BSHELL_SUCCESS;
}
enum bshell_status bshell_table_ctx_print_record(
struct bshell_table_ctx *ctx,
const fx_value *record)
{
fx_stringstream *strm = fx_stringstream_create();
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);
fx_value column_value = FX_VALUE_EMPTY;
if (c->col_property) {
fx_status status = fx_property_get_value(
c->col_property,
record,
&column_value);
if (FX_OK(status)) {
fx_value_to_string(&column_value, strm, NULL);
}
}
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++) {
fx_stream_write_char(ctx->ctx_out, ' ');
}
}
fx_stream_write_char(ctx->ctx_out, '\n');
fx_stringstream_unref(strm);
return BSHELL_SUCCESS;
}