format: implement tabular output of fx objects
This commit is contained in:
@@ -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],
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
#include <fx/term/print.h>
|
||||||
|
#include <fx/type.h>
|
||||||
|
#include <fx/value-type.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#ifndef FORMAT_H_
|
||||||
|
#define FORMAT_H_
|
||||||
|
|
||||||
|
#include "../status.h"
|
||||||
|
|
||||||
|
#include <fx/namemap.h>
|
||||||
|
#include <fx/object.h>
|
||||||
|
#include <fx/queue.h>
|
||||||
|
#include <fx/reflection/property.h>
|
||||||
|
#include <fx/reflection/type.h>
|
||||||
|
#include <fx/stream.h>
|
||||||
|
#include <fx/value.h>
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -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],
|
||||||
|
};
|
||||||
@@ -0,0 +1,353 @@
|
|||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
#include <fx/collections/array.h>
|
||||||
|
#include <fx/iterator.h>
|
||||||
|
#include <fx/reflection/type.h>
|
||||||
|
#include <fx/stringstream.h>
|
||||||
|
#include <fx/term/print.h>
|
||||||
|
#include <fx/term/tty.h>
|
||||||
|
#include <fx/value.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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],
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user