Skip to content

Commit ebf0750

Browse files
shroffniigaw
authored andcommitted
nvme: add common APIs for printing tabular format output
Some nvme-cli commands, such as nvme list and nvme list -v, support output in tabular format. Currently, the table output is not well aligned because column widths are fixed at print time, regardless of the length of the data in each column. This often results in uneven and hard-to-read output.For any new CLI command that requires tabular output, developers must manually specify the column width and row value width, which is both error-prone and inconsistent. This patch introduces a set of common table APIs that: - Automatically calculate column widths based on the content - Maintain proper alignment regardless of value length - Simplify adding tabular output support to new and existing commands The new APIs are: 1. table_init() — Allocate a table instance. 2. table_add_columns() — Add column definitions (struct table_column), including name and alignment (LEFT, RIGHT, CENTERED). 3. table_add_columns_filter() - Same as table_add_columns() but also provide a filter function callback which could be then used by the caller for filtering any column. 3. table_get_row_id() — Reserve a row index for inserting values. 4. table_add_row() — Add a row to the table. 5. table_print() — Print the table with auto-calculated widths. 6. table_free() — Free resources allocated for the table. For adding values, the following setter APIs are provided, each supporting alignment types (LEFT, RIGHT, or CENTERED): - table_set_value_str() - table_set_value_int() - table_set_value_unsigned() - table_set_value_long() - table_set_value_unsigned_long() Usage steps: 1. Call table_init() to create a table handle. 2. Define an array of struct table_column specifying column names and alignment, then call table_add_columns() or if you want to filter column then table_add_columns_filter(). 3. Obtain a row ID using table_get_row_id() and set values using the appropriate setter table APIs : table_set_value_*() function. 4. Add the completed row using table_add_row(). 5. Repeat steps 3–4 for each additional row. 5. Call table_print() to display the table. 6. Call table_free() to release table resources. With these APIs, developers no longer need to pre-calculate column or row widths. The output is consistently aligned and easy to read. Suggested-by: Daniel Wagner <dwagner@suse.de> Signed-off-by: Nilay Shroff <nilay@linux.ibm.com> Signed-off-by: Daniel Wagner <wagi@kernel.org>
1 parent d0b4c6c commit ebf0750

File tree

3 files changed

+471
-1
lines changed

3 files changed

+471
-1
lines changed

util/meson.build

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ sources += [
88
'util/sighdl.c',
99
'util/suffix.c',
1010
'util/types.c',
11-
'util/utils.c'
11+
'util/utils.c',
12+
'util/table.c'
1213
]
1314

1415
if json_c_dep.found()

util/table.c

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* table.c : Common APIs for printing tabular format output.
4+
*
5+
* Copyright (c) 2025 Nilay Shroff, IBM
6+
*
7+
* This program is free software; you can redistribute it and/or
8+
* modify it under the terms of the GNU General Public License
9+
* as published by the Free Software Foundation; either version 2
10+
* of the License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
#include <stdio.h>
19+
#include <stdlib.h>
20+
#include <errno.h>
21+
#include <string.h>
22+
23+
#include "table.h"
24+
25+
static int table_get_value_width(struct value *v)
26+
{
27+
char buf[64];
28+
int len = 0;
29+
30+
switch (v->type) {
31+
case FMT_STRING:
32+
len = strlen((const char *)v->s);
33+
break;
34+
case FMT_INT:
35+
len = snprintf(buf, sizeof(buf), "%d", v->i);
36+
break;
37+
default:
38+
printf("Invalid print format!\n");
39+
break;
40+
}
41+
return len;
42+
}
43+
44+
static void table_print_centered(struct value *val, int width, enum fmt_type type)
45+
{
46+
int i, len, left_pad, right_pad;
47+
char buf[64];
48+
49+
if (type == FMT_STRING)
50+
len = strlen(val->s);
51+
else if (type == FMT_INT)
52+
len = snprintf(buf, sizeof(buf), "%d", val->i);
53+
else if (type == FMT_UNSIGNED)
54+
len = snprintf(buf, sizeof(buf), "%u", val->u);
55+
else if (type == FMT_LONG)
56+
len = snprintf(buf, sizeof(buf), "%ld", val->ld);
57+
else if (type == FMT_UNSIGNED_LONG)
58+
len = snprintf(buf, sizeof(buf), "%lu", val->lu);
59+
else {
60+
fprintf(stderr, "Invalid format!\n");
61+
return;
62+
}
63+
64+
left_pad = (width - len) / 2;
65+
right_pad = width - len - left_pad;
66+
67+
/* add left padding */
68+
for (i = 0; i < left_pad; i++)
69+
putchar(' ');
70+
71+
/* print value */
72+
if (type == FMT_STRING)
73+
printf("%s ", val->s);
74+
else if (type == FMT_INT)
75+
printf("%d ", val->i);
76+
else if (type == FMT_UNSIGNED)
77+
printf("%u ", val->u);
78+
else if (type == FMT_LONG)
79+
printf("%ld ", val->ld);
80+
else if (type == FMT_UNSIGNED_LONG)
81+
printf("%lu", val->lu);
82+
83+
/* add right padding */
84+
for (i = 0; i < right_pad; i++)
85+
putchar(' ');
86+
}
87+
88+
static void table_print_columns(const struct table *t)
89+
{
90+
int col, j, width;
91+
struct table_column *c;
92+
struct value v;
93+
94+
for (col = 0; col < t->num_columns; col++) {
95+
c = &t->columns[col];
96+
width = c->width;
97+
if (c->align == LEFT)
98+
width *= -1;
99+
100+
if (c->align == CENTERED) {
101+
v.s = c->name;
102+
v.align = c->align;
103+
table_print_centered(&v, width, FMT_STRING);
104+
} else
105+
printf("%*s ", width, c->name);
106+
}
107+
108+
printf("\n");
109+
110+
for (col = 0; col < t->num_columns; col++) {
111+
for (j = 0; j < t->columns[col].width; j++)
112+
putchar('-');
113+
printf(" ");
114+
}
115+
116+
printf("\n");
117+
}
118+
119+
static void table_print_rows(const struct table *t)
120+
{
121+
int row, col;
122+
struct table_column *c;
123+
struct table_row *r;
124+
int width;
125+
struct value *v;
126+
127+
for (row = 0; row < t->num_rows; row++) {
128+
for (col = 0; col < t->num_columns; col++) {
129+
c = &t->columns[col];
130+
r = &t->rows[row];
131+
v = &r->val[col];
132+
133+
width = c->width;
134+
if (v->align == LEFT)
135+
width *= -1;
136+
137+
if (v->align == CENTERED)
138+
table_print_centered(v, width, v->type);
139+
else {
140+
switch (v->type) {
141+
case FMT_STRING:
142+
printf("%*s ", width, v->s);
143+
break;
144+
145+
case FMT_INT:
146+
printf("%*d ", width, v->i);
147+
break;
148+
149+
case FMT_UNSIGNED:
150+
printf("%*u ", width, v->u);
151+
break;
152+
153+
case FMT_LONG:
154+
printf("%*ld ", width, v->ld);
155+
break;
156+
157+
case FMT_UNSIGNED_LONG:
158+
printf("%*lu ", width, v->lu);
159+
break;
160+
161+
default:
162+
fprintf(stderr, "Invalid format!\n");
163+
break;
164+
}
165+
}
166+
}
167+
printf("\n");
168+
}
169+
}
170+
171+
void table_print(struct table *t)
172+
{
173+
/* first print columns */
174+
table_print_columns(t);
175+
176+
/* next print rows */
177+
table_print_rows(t);
178+
}
179+
180+
int table_get_row_id(struct table *t)
181+
{
182+
struct table_row *new_rows;
183+
int row = t->num_rows;
184+
185+
new_rows = reallocarray(t->rows, (row + 1), sizeof(struct table_row));
186+
if (!new_rows)
187+
return -ENOMEM;
188+
189+
t->rows = new_rows;
190+
t->rows[row].val = calloc(t->num_columns, sizeof(struct value));
191+
if (!t->rows->val)
192+
return -ENOMEM;
193+
194+
t->num_rows++;
195+
return row;
196+
}
197+
198+
void table_add_row(struct table *t, int row_id)
199+
{
200+
int col, max_width, width;
201+
struct table_row *row = &t->rows[row_id];
202+
203+
/* Adjust the column width based on the row value. */
204+
for (col = 0; col < t->num_columns; col++) {
205+
max_width = t->columns[col].width;
206+
width = table_get_value_width(&row->val[col]);
207+
if (width > max_width)
208+
t->columns[col].width = width;
209+
}
210+
}
211+
212+
struct table *table_init(void)
213+
{
214+
struct table *t;
215+
216+
t = malloc(sizeof(struct table));
217+
if (!t)
218+
return NULL;
219+
220+
memset(t, 0, sizeof(struct table));
221+
return t;
222+
}
223+
224+
static int table_add_column(struct table *t, struct table_column *c)
225+
{
226+
struct table_column *new_columns;
227+
int col = t->num_columns;
228+
229+
new_columns = reallocarray(t->columns, t->num_columns + 1,
230+
sizeof(struct table_column));
231+
if (!new_columns)
232+
return -ENOMEM;
233+
234+
t->columns = new_columns;
235+
t->columns[col].name = strdup(c->name);
236+
if (!t->columns[col].name)
237+
return -ENOMEM;
238+
t->columns[col].align = c->align;
239+
t->columns[col].width = strlen(c->name);
240+
t->num_columns++;
241+
242+
return 0;
243+
}
244+
245+
int table_add_columns_filter(struct table *t, struct table_column *c,
246+
int num_columns,
247+
bool (*filter)(const char *name, void *arg),
248+
void *arg)
249+
{
250+
int col;
251+
252+
if (!filter)
253+
return table_add_columns(t, c, num_columns);
254+
255+
for (col = 0; col < num_columns; col++) {
256+
if (!filter(c[col].name, arg))
257+
continue; /* skip this column */
258+
259+
if (table_add_column(t, &c[col]))
260+
goto out;
261+
}
262+
return 0;
263+
out:
264+
return -ENOMEM;
265+
}
266+
267+
int table_add_columns(struct table *t, struct table_column *c, int num_columns)
268+
{
269+
int col;
270+
271+
t->columns = calloc(num_columns, sizeof(struct table_column));
272+
if (!t->columns)
273+
return -ENOMEM;
274+
275+
for (col = 0; col < num_columns; col++) {
276+
t->columns[col].name = strdup(c[col].name);
277+
if (!t->columns[col].name)
278+
goto free_col;
279+
280+
t->columns[col].align = c[col].align;
281+
t->columns[col].width = strlen(t->columns[col].name);
282+
}
283+
t->num_columns = num_columns;
284+
285+
return 0;
286+
free_col:
287+
while (--col >= 0)
288+
free(t->columns[col].name);
289+
free(t->columns);
290+
t->columns = NULL;
291+
return -ENOMEM;
292+
}
293+
294+
void table_free(struct table *t)
295+
{
296+
int row, col;
297+
struct table_row *r;
298+
struct value *v;
299+
300+
/* free rows */
301+
for (row = 0; row < t->num_rows; row++) {
302+
r = &t->rows[row];
303+
for (col = 0; col < t->num_columns; col++) {
304+
v = &r->val[col];
305+
306+
if (v->type == FMT_STRING)
307+
free(v->s);
308+
}
309+
free(r->val);
310+
}
311+
free(t->rows);
312+
313+
/* free columns */
314+
for (col = 0; col < t->num_columns; col++)
315+
free(t->columns[col].name);
316+
free(t->columns);
317+
318+
/* free table */
319+
free(t);
320+
}

0 commit comments

Comments
 (0)