]> git.ipfire.org Git - people/ms/pakfire.git/commitdiff
cli: Create a thing to format tables
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 1 Jul 2025 16:05:53 +0000 (16:05 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 1 Jul 2025 16:05:53 +0000 (16:05 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/cli/lib/table.c [new file with mode: 0644]
src/cli/lib/table.h [new file with mode: 0644]

index 94678914f4049c8aec5f69dff1c5e55fc183fc2c..0e11a36076f25c61dda468aa311050a7dc2e70dd 100644 (file)
@@ -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 (file)
index 0000000..b21ac83
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/queue.h>
+
+#include <pakfire/string.h>
+
+#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 (file)
index 0000000..bda3d41
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#ifndef PAKFIRE_CLI_TABLE_H
+#define PAKFIRE_CLI_TABLE_H
+
+#include <stdio.h>
+
+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 */