diff --git a/bshell/format/command.c b/bshell/format/command.c new file mode 100644 index 0000000..10c45b7 --- /dev/null +++ b/bshell/format/command.c @@ -0,0 +1,14 @@ +#include "format.h" + +static struct table_format_column command_table_columns[] = { + TABLE_COLUMN("command_type", "command_type", 13), + TABLE_COLUMN("name", "name", COL_WIDTH_INFINITE), + TABLE_COLUMN("version", "version", 12), + TABLE_COLUMN("source", "source", 35), +}; + +struct table_format command_table_format = { + .fmt_columns = command_table_columns, + .fmt_column_count = sizeof command_table_columns + / sizeof command_table_columns[0], +}; diff --git a/bshell/format/format.c b/bshell/format/format.c new file mode 100644 index 0000000..a5c1c71 --- /dev/null +++ b/bshell/format/format.c @@ -0,0 +1,29 @@ +#include "format.h" + +#include +#include +#include + +static void format_value_fallback(const fx_value *value, fx_stream *dest) +{ + fx_printf("[yellow]"); + fx_value_to_string(value, dest, NULL); + fx_printf("[reset]\n"); +} + +enum bshell_status format_value_default(const fx_value *value, fx_stream *dest) +{ + if (fx_type_is_value_type(value->v_type)) { + format_value_fallback(value, dest); + return BSHELL_SUCCESS; + } + + fx_object *object = NULL; + fx_value_get_object(value, &object); + enum bshell_status status = format_object_table(object, dest); + if (status != BSHELL_SUCCESS) { + format_value_fallback(value, dest); + } + + return BSHELL_SUCCESS; +} diff --git a/bshell/format/format.h b/bshell/format/format.h new file mode 100644 index 0000000..1e74bce --- /dev/null +++ b/bshell/format/format.h @@ -0,0 +1,78 @@ +#ifndef FORMAT_H_ +#define FORMAT_H_ + +#include "../status.h" + +#include +#include +#include +#include +#include +#include +#include + +#define TABLE_COLUMN(display_name, property_name, width) \ + { \ + .col_display_name = (display_name), \ + .col_property_name = (property_name), \ + .col_width = (width), \ + } +#define COL_WIDTH_INFINITE ((size_t)-1) + +struct table_format_column { + const char *col_display_name; + const char *col_property_name; + size_t col_width; +}; + +struct table_format { + fx_namemap_entry fmt_entry; + const struct table_format_column *fmt_columns; + size_t fmt_column_count; +}; + +struct table_format_ctx_column { + const char *col_title; + size_t col_width; + size_t col_title_chars_written; + const char *col_title_ptr; + const fx_property *col_property; +}; + +struct table_format_ctx { + const struct table_format *ctx_fmt; + struct table_format_ctx_column *ctx_columns; + size_t ctx_columns_count; + unsigned int ctx_tty_width; + size_t ctx_variable_columns_count; + fx_stream *ctx_out; +}; + +extern enum bshell_status table_format_init(void); +extern const struct table_format *table_format_get_for_type(const fx_type *ty); +extern enum bshell_status format_object_table( + fx_object *object, + fx_stream *dest); +extern enum bshell_status format_value_default( + const fx_value *value, + fx_stream *dest); + +extern enum bshell_status table_format_ctx_init( + struct table_format_ctx *ctx, + fx_stream *out); +extern enum bshell_status table_format_ctx_cleanup( + struct table_format_ctx *ctx); +extern enum bshell_status table_format_ctx_prepare_columns_from_format( + struct table_format_ctx *ctx, + const fx_type *record_type, + const struct table_format *fmt); +extern enum bshell_status table_format_ctx_prepare_columns_from_object( + struct table_format_ctx *ctx, + const fx_type *record_type); +extern enum bshell_status table_format_ctx_print_headers( + struct table_format_ctx *ctx); +extern enum bshell_status table_format_ctx_print_record( + struct table_format_ctx *ctx, + const fx_value *record); + +#endif diff --git a/bshell/format/hashtable.c b/bshell/format/hashtable.c new file mode 100644 index 0000000..61c7885 --- /dev/null +++ b/bshell/format/hashtable.c @@ -0,0 +1,12 @@ +#include "format.h" + +static struct table_format_column hashtable_table_columns[] = { + TABLE_COLUMN("key", "key", 37), + TABLE_COLUMN("value", "value", COL_WIDTH_INFINITE), +}; + +struct table_format hashtable_table_format = { + .fmt_columns = hashtable_table_columns, + .fmt_column_count = sizeof hashtable_table_columns + / sizeof hashtable_table_columns[0], +}; diff --git a/bshell/format/table.c b/bshell/format/table.c new file mode 100644 index 0000000..d847fa4 --- /dev/null +++ b/bshell/format/table.c @@ -0,0 +1,353 @@ +#include "format.h" + +#include +#include +#include +#include +#include +#include +#include + +static fx_namemap table_formats = FX_NAMEMAP_INIT; + +extern struct table_format hashtable_table_format; +extern struct table_format verb_table_format; +extern struct table_format command_table_format; + +enum bshell_status table_format_init(void) +{ + fx_namemap_put( + &table_formats, + "fx.collections.hashtable", + &hashtable_table_format.fmt_entry); + fx_namemap_put( + &table_formats, + "bshell.verb", + &verb_table_format.fmt_entry); + fx_namemap_put( + &table_formats, + "bshell.command", + &command_table_format.fmt_entry); + return BSHELL_SUCCESS; +} + +const struct table_format *table_format_get_for_type(const fx_type *ty) +{ + while (ty) { + const char *name = fx_type_get_name(ty); + fx_namemap_entry *entry = fx_namemap_get(&table_formats, name); + if (entry) { + return fx_unbox(struct table_format, 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 table_format *fmt = table_format_get_for_type(type); + if (!fmt) { + return BSHELL_ERR_NOT_SUPPORTED; + } + + struct table_format_ctx ctx; + table_format_ctx_init(&ctx, dest); + fx_iterator *it = fx_iterator_begin(object); + if (!it) { + table_format_ctx_prepare_columns_from_format( + &ctx, + fx_type_get_by_id(fx_object_query_type(object)), + fmt); + table_format_ctx_print_headers(&ctx); + table_format_ctx_print_record(&ctx, &FX_VALUE_OBJECT(object)); + } else { + fx_value first = fx_iterator_get_value(it); + if (first.v_type) { + table_format_ctx_prepare_columns_from_format( + &ctx, + fx_type_get_by_id(first.v_type), + fmt); + } + table_format_ctx_print_headers(&ctx); + fx_foreach(value, it) + { + table_format_ctx_print_record(&ctx, &value); + } + } + + table_format_ctx_cleanup(&ctx); + + return BSHELL_SUCCESS; +} + +enum bshell_status table_format_ctx_init( + struct table_format_ctx *ctx, + fx_stream *out) +{ + memset(ctx, 0x0, sizeof *ctx); + ctx->ctx_out = out; + return BSHELL_SUCCESS; +} + +enum bshell_status table_format_ctx_cleanup(struct table_format_ctx *ctx) +{ + if (ctx->ctx_columns) { + free(ctx->ctx_columns); + } + + memset(ctx, 0x0, sizeof *ctx); + return BSHELL_SUCCESS; +} + +enum bshell_status table_format_ctx_prepare_columns_from_format( + struct table_format_ctx *ctx, + const fx_type *record_type, + const struct table_format *fmt) +{ + if (!fmt && !record_type) { + return BSHELL_ERR_INVALID_ARGUMENT; + } + + if (!fmt) { + fmt = table_format_get_for_type(record_type); + } + + if (!fmt) { + return table_format_ctx_prepare_columns_from_object( + ctx, + record_type); + } + + ctx->ctx_fmt = fmt; + struct table_format_ctx_column *columns = calloc( + fmt->fmt_column_count, + sizeof *columns); + + if (!columns) { + return BSHELL_ERR_NO_MEMORY; + } + + const struct table_format_column *column_defs = fmt->fmt_columns; + + ctx->ctx_columns = columns; + ctx->ctx_columns_count = fmt->fmt_column_count; + + fx_tty_get_dimensions(fx_stdtty, &ctx->ctx_tty_width, NULL); + if (!ctx->ctx_tty_width) { + ctx->ctx_tty_width = 150; + } + + 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 table_format_ctx_prepare_columns_from_object( + struct table_format_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 table_format_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; + + fx_tty_get_dimensions(fx_stdtty, &ctx->ctx_tty_width, NULL); + if (!ctx->ctx_tty_width) { + ctx->ctx_tty_width = 150; + } + + 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; +} + +enum bshell_status table_format_ctx_print_headers(struct table_format_ctx *ctx) +{ + fx_printf("[bold,bright_green]"); + bool repeat = false; + struct table_format_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 table_format_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, ' '); + } + } + + fx_printf("[reset]"); + fx_stream_write_char(ctx->ctx_out, '\n'); + return BSHELL_SUCCESS; +} + +enum bshell_status table_format_ctx_print_record( + struct table_format_ctx *ctx, + const fx_value *record) +{ + fx_stringstream *strm = fx_stringstream_create(); + for (size_t i = 0; i < ctx->ctx_columns_count; i++) { + struct table_format_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_ptr(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; +} diff --git a/bshell/format/verb.c b/bshell/format/verb.c new file mode 100644 index 0000000..46731c1 --- /dev/null +++ b/bshell/format/verb.c @@ -0,0 +1,14 @@ +#include "format.h" + +static struct table_format_column verb_table_columns[] = { + TABLE_COLUMN("verb", "verb", 13), + TABLE_COLUMN("alias_prefix", "alias_prefix", 13), + TABLE_COLUMN("group", "group", 13), + TABLE_COLUMN("description", "description", COL_WIDTH_INFINITE), +}; + +struct table_format verb_table_format = { + .fmt_columns = verb_table_columns, + .fmt_column_count = sizeof verb_table_columns + / sizeof verb_table_columns[0], +};