From e5fc24889d7f8662e5b0092c186924babf889d33 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Tue, 1 Jul 2025 16:05:53 +0000 Subject: [PATCH] cli: Create a thing to format tables Signed-off-by: Michael Tremer --- Makefile.am | 2 + src/cli/lib/table.c | 394 ++++++++++++++++++++++++++++++++++++++++++++ src/cli/lib/table.h | 50 ++++++ 3 files changed, 446 insertions(+) create mode 100644 src/cli/lib/table.c create mode 100644 src/cli/lib/table.h diff --git a/Makefile.am b/Makefile.am index 94678914..0e11a360 100644 --- a/Makefile.am +++ b/Makefile.am @@ -554,6 +554,8 @@ libcli_la_SOURCES = \ src/cli/lib/snapshot_update.h \ src/cli/lib/sync.c \ src/cli/lib/sync.h \ + src/cli/lib/table.c \ + src/cli/lib/table.h \ src/cli/lib/terminal.c \ src/cli/lib/terminal.h \ src/cli/lib/transaction.c \ diff --git a/src/cli/lib/table.c b/src/cli/lib/table.c new file mode 100644 index 00000000..b21ac834 --- /dev/null +++ b/src/cli/lib/table.c @@ -0,0 +1,394 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2025 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include +#include + +#include + +#include "table.h" +#include "terminal.h" + +typedef struct cli_table_col { + STAILQ_ENTRY(cli_table_col) nodes; + + // Name + const char* name; + + // Type + cli_table_col_type type; + + // Alignment + cli_table_col_align align; + + // Required Width + size_t width; +} cli_table_col; + +typedef struct cli_table_row { + STAILQ_ENTRY(cli_table_row) nodes; + + // Values for all cols + char** cols; +} cli_table_row; + +struct cli_table { + // Terminal dimensions + struct { + int rows; + int cols; + } terminal; + + // Columns + STAILQ_HEAD(cols, cli_table_col) cols; + unsigned int num_cols; + + // Rows + STAILQ_HEAD(rows, cli_table_row) rows; +}; + +int cli_table_create(cli_table** table) { + cli_table* self = NULL; + int r; + + // Allocate a new table + self = calloc(1, sizeof(*self)); + if (!self) + return -errno; + + // Initialize cols + STAILQ_INIT(&self->cols); + + // Initialize rows + STAILQ_INIT(&self->rows); + + // Fetch the terminal dimensions + r = cli_term_get_dimensions(&self->terminal.rows, &self->terminal.cols); + if (r < 0) + goto ERROR; + + // Return the pointer + *table = self; + return 0; + +ERROR: + if (self) + cli_table_free(self); + + return r; +} + +static void cli_table_col_free(cli_table_col* col) { + free(col); +} + +static void cli_table_row_free(cli_table_row* row) { + if (row->cols) + pakfire_strings_free(row->cols); + free(row); +} + +void cli_table_free(cli_table* self) { + cli_table_col* col = NULL; + cli_table_row* row = NULL; + + // Free all cols + while (!STAILQ_EMPTY(&self->cols)) { + col = STAILQ_FIRST(&self->cols); + if (!col) + break; + + STAILQ_REMOVE_HEAD(&self->cols, nodes); + + // Free the cols + cli_table_col_free(col); + } + + // Free all rows + while (!STAILQ_EMPTY(&self->rows)) { + row = STAILQ_FIRST(&self->rows); + if (!row) + break; + + STAILQ_REMOVE_HEAD(&self->rows, nodes); + + // Free the rows + cli_table_row_free(row); + } + + free(self); +} + +int cli_table_add_col(cli_table* self, + const char* name, cli_table_col_type type, cli_table_col_align align) { + cli_table_col* col = NULL; + + // Allocate a new col + col = calloc(1, sizeof(*col)); + if (!col) + return -errno; + + // Store the name + col->name = name; + + // Store the type + col->type = type; + + // Store the alignment + col->align = align; + + // The column needs to be wide enough to fit the title + col->width = strlen(col->name); + + // Append the column + STAILQ_INSERT_TAIL(&self->cols, col, nodes); + self->num_cols++; + + return 0; +} + +int cli_table_add_row(cli_table* self, ...) { + cli_table_col* col = NULL; + cli_table_row* row = NULL; + char buffer[1024]; + size_t width = 0; + va_list args; + int r = 0; + + va_start(args, self); + + // Allocate a new row + row = calloc(1, sizeof(*row)); + if (!row) + return -errno; + + // Format all arguments + STAILQ_FOREACH(col, &self->cols, nodes) { + switch (col->type) { + case CLI_TABLE_STRING: + r = pakfire_string_format(buffer, "%s", va_arg(args, const char*)); + break; + + case CLI_TABLE_INTEGER: + r = pakfire_string_format(buffer, "%d", va_arg(args, int)); + break; + + case CLI_TABLE_FLOAT: + r = pakfire_string_format(buffer, "%f", va_arg(args, double)); + break; + } + + // Break if we could not format the argument + if (r < 0) + goto ERROR; + + // Append the string + r = pakfire_strings_append(&row->cols, buffer); + if (r < 0) + goto ERROR; + + // Determine the width of the argument + width = strlen(buffer); + + // Adjust the required width of the column + if (width > col->width) + col->width = width; + } + + va_end(args); + + // Append the row + STAILQ_INSERT_TAIL(&self->rows, row, nodes); + + return 0; + +ERROR: + if (row) + cli_table_row_free(row); + + return r; +} + +static size_t cli_table_width(cli_table* self) { + cli_table_col* col = NULL; + size_t width = 0; + + // An empty table has no width + if (STAILQ_EMPTY(&self->cols)) + return 0; + + STAILQ_FOREACH(col, &self->cols, nodes) + width += col->width + 1; + + return width - 1; +} + +static int cli_table_col_is_last(cli_table* self, cli_table_col* col) { + cli_table_col* c = NULL; + unsigned int i = 0; + + // Count all columns until we have found the column in question + STAILQ_FOREACH(c, &self->cols, nodes) { + i++; + + if (col == c) + break; + } + + return (i >= self->num_cols); +} + +static int cli_table_print_newline(cli_table* self, FILE* f) { + int r; + + // Terminate the line + r = fputc('\n', f); + if (r < 0) + return -errno; + + return 0; +} + +static int cli_table_print_cell(cli_table* self, cli_table_col* col, FILE* f, const char* s) { + int r = 0; + + // Print the cell + switch (col->align) { + case CLI_TABLE_ALIGN_LEFT: + r = fprintf(f, "%-*s", (int)col->width, s); + break; + + case CLI_TABLE_ALIGN_RIGHT: + r = fprintf(f, "%*s", (int)col->width, s); + break; + } + + // Return any errors + if (r < 0) + return -errno; + + // Add an extra space unless this is the last column + if (!cli_table_col_is_last(self, col)) { + r = fputc(' ', f); + if (r < 0) + return -errno; + } + + return 0; +} + +static int cli_table_print_header(cli_table* self, FILE* f) { + cli_table_col* col = NULL; + int r; + + // Print the header + STAILQ_FOREACH(col, &self->cols, nodes) { + r = cli_table_print_cell(self, col, f, col->name); + if (r < 0) + return r; + } + + // Terminate the line + return cli_table_print_newline(self, f); +} + +static int cli_table_print_separator(cli_table* self, FILE* f) { + cli_table_col* col = NULL; + int r; + + // Print the separator + STAILQ_FOREACH(col, &self->cols, nodes) { + for (size_t i = 0; i < col->width; i++) { + r = fputc('-', f); + if (r < 0) + return -errno; + } + + r = fputc(' ', f); + if (r < 0) + return -errno; + } + + // Terminate the line + return cli_table_print_newline(self, f); +} + +static int cli_table_print_rows(cli_table* self, FILE* f) { + cli_table_col* col = NULL; + cli_table_row* row = NULL; + const char* cell = NULL; + int i; + int r; + + STAILQ_FOREACH(row, &self->rows, nodes) { + // Reset the index of the columns + i = 0; + + STAILQ_FOREACH(col, &self->cols, nodes) { + cell = row->cols[i++]; + if (!cell) + break; + + // Print the cell + r = cli_table_print_cell(self, col, f, cell); + if (r < 0) + return r; + } + + // Terminate the line + r = cli_table_print_newline(self, f); + if (r < 0) + return r; + } + + return 0; +} + +int cli_table_print(cli_table* self, FILE* f) { + int r; + + // We don't need to print a table with no columns + if (STAILQ_EMPTY(&self->cols)) + return 0; + + // Determine the minimum width that we + size_t width = cli_table_width(self); + + // XXX What do we do when we are larger than the terminal is wide? + + // Print the header + r = cli_table_print_header(self, f); + if (r < 0) + return r; + + // Print a separator + r = cli_table_print_separator(self, f); + if (r < 0) + return r; + + // Print the rows + r = cli_table_print_rows(self, f); + if (r < 0) + return r; + + return 0; +} diff --git a/src/cli/lib/table.h b/src/cli/lib/table.h new file mode 100644 index 00000000..bda3d417 --- /dev/null +++ b/src/cli/lib/table.h @@ -0,0 +1,50 @@ +/*############################################################################# +# # +# Pakfire - The IPFire package management system # +# Copyright (C) 2025 Pakfire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef PAKFIRE_CLI_TABLE_H +#define PAKFIRE_CLI_TABLE_H + +#include + +typedef struct cli_table cli_table; + +int cli_table_create(cli_table** table); + +void cli_table_free(cli_table* self); + +typedef enum { + CLI_TABLE_STRING, + CLI_TABLE_INTEGER, + CLI_TABLE_FLOAT, +} cli_table_col_type; + +typedef enum { + CLI_TABLE_ALIGN_LEFT, + CLI_TABLE_ALIGN_RIGHT, +} cli_table_col_align; + +int cli_table_add_col(cli_table* self, + const char* name, cli_table_col_type type, cli_table_col_align align); + +int cli_table_add_row(cli_table* self, ...); + +int cli_table_print(cli_table* self, FILE* f); + +#endif /* PAKFIRE_CLI_TABLE_H */ -- 2.47.3