mirror of
https://github.com/radareorg/radare2.git
synced 2024-11-28 23:50:40 +00:00
Initial implementation of the RTable API with filter, sorting and query APIs ##util
This commit is contained in:
parent
e4bb767d54
commit
ff36c12ea2
@ -47,6 +47,7 @@ int gettimeofday (struct timeval* p, void* tz);
|
||||
#include "r_util/r_mem.h"
|
||||
#include "r_util/r_name.h"
|
||||
#include "r_util/r_num.h"
|
||||
#include "r_util/r_table.h"
|
||||
#include "r_util/r_graph.h"
|
||||
#include "r_util/r_panels.h"
|
||||
#include "r_util/r_pool.h"
|
||||
|
@ -21,6 +21,7 @@ R_API bool r_strbuf_setf(RStrBuf *sb, const char *fmt, ...);
|
||||
R_API bool r_strbuf_vsetf(RStrBuf *sb, const char *fmt, va_list ap);
|
||||
R_API bool r_strbuf_append(RStrBuf *sb, const char *s);
|
||||
R_API bool r_strbuf_append_n(RStrBuf *sb, const char *s, int l);
|
||||
R_API bool r_strbuf_prepend(RStrBuf *sb, const char *s);
|
||||
R_API bool r_strbuf_appendf(RStrBuf *sb, const char *fmt, ...);
|
||||
R_API bool r_strbuf_vappendf(RStrBuf *sb, const char *fmt, va_list ap);
|
||||
R_API char *r_strbuf_get(RStrBuf *sb);
|
||||
|
61
libr/include/r_util/r_table.h
Normal file
61
libr/include/r_util/r_table.h
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef R_UTIL_TABLE_H
|
||||
#define R_UTIL_TABLE_H
|
||||
|
||||
#include <r_util.h>
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
RListComparator cmp;
|
||||
} RTableColumnType;
|
||||
|
||||
extern RTableColumnType r_table_type_string;
|
||||
extern RTableColumnType r_table_type_number;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
RTableColumnType *type;
|
||||
int align; // left, right, center (TODO: unused)
|
||||
int width; // computed
|
||||
int maxWidth;
|
||||
bool forceUppercase;
|
||||
} RTableColumn;
|
||||
|
||||
typedef struct {
|
||||
// TODO: use RVector
|
||||
RList *items;
|
||||
} RTableRow;
|
||||
|
||||
typedef struct {
|
||||
RList *rows;
|
||||
RList *cols;
|
||||
int totalCols;
|
||||
bool showHeader;
|
||||
bool adjustedCols;
|
||||
} RTable;
|
||||
|
||||
R_API void r_table_row_free(void *_row);
|
||||
R_API void r_table_column_free(void *_col);
|
||||
R_API RTable *r_table_new();
|
||||
R_API void r_table_free(RTable *t);
|
||||
R_API void r_table_add_column(RTable *t, RTableColumnType *type, const char *name, int maxWidth);
|
||||
R_API RTableRow *r_table_row_new(RList *items);
|
||||
R_API void r_table_add_row(RTable *t, const char *name, ...);
|
||||
R_API char *r_table_tofancystring(RTable *t);
|
||||
R_API char *r_table_tostring(RTable *t);
|
||||
R_API char *r_table_tocsv(RTable *t);
|
||||
R_API char *r_table_tojson(RTable *t);
|
||||
R_API void r_table_filter(RTable *t, int nth, int op, const char *un);
|
||||
R_API void r_table_sort(RTable *t, int nth, bool inc);
|
||||
R_API void r_table_query(RTable *t, const char *q);
|
||||
R_API RTable *r_table_clone(RTable *t);
|
||||
R_API RTable *r_table_push(RTable *t);
|
||||
R_API RTable *r_table_pop(RTable *t);
|
||||
R_API void r_table_fromjson(RTable *t, const char *csv);
|
||||
R_API void r_table_fromcsv(RTable *t, const char *csv);
|
||||
R_API char *r_table_tohtml(RTable *t);
|
||||
R_API void r_table_transpose(RTable *t);
|
||||
R_API void r_table_format(RTable *t, int nth, RTableColumnType *type);
|
||||
R_API ut64 r_table_reduce(RTable *t, int nth);
|
||||
R_API void r_table_columns(RTable *t, const char *name, ...);
|
||||
|
||||
#endif
|
@ -17,7 +17,7 @@ OBJS+=list.o flist.o chmod.o graph.o event.o alloc.o donut.o
|
||||
OBJS+=regex/regcomp.o regex/regerror.o regex/regexec.o uleb128.o
|
||||
OBJS+=sandbox.o calc.o thread.o thread_sem.o thread_lock.o thread_cond.o
|
||||
OBJS+=strpool.o bitmap.o date.o format.o pie.o print.o ctype.o
|
||||
OBJS+=seven.o randomart.o zip.o debruijn.o log.o getopt.o
|
||||
OBJS+=seven.o randomart.o zip.o debruijn.o log.o getopt.o table.o
|
||||
OBJS+=utf8.o utf16.o utf32.o strbuf.o lib.o name.o spaces.o signal.o syscmd.o
|
||||
OBJS+=diff.o bdiff.o stack.o queue.o tree.o idpool.o assert.o
|
||||
OBJS+=punycode.o pkcs7.o x509.o asn1.o astr.o json_indent.o skiplist.o pj.o
|
||||
|
@ -64,11 +64,9 @@ R_API int r_list_length(const RList *list) {
|
||||
|
||||
/* remove all elements of a list */
|
||||
R_API void r_list_purge(RList *list) {
|
||||
RListIter *it;
|
||||
|
||||
r_return_if_fail (list);
|
||||
|
||||
it = list->head;
|
||||
RListIter *it = list->head;
|
||||
while (it) {
|
||||
RListIter *next = it->n;
|
||||
r_list_delete (list, it);
|
||||
@ -111,11 +109,9 @@ R_API void r_list_delete(RList *list, RListIter *iter) {
|
||||
}
|
||||
|
||||
R_API void r_list_split(RList *list, void *ptr) {
|
||||
RListIter *iter;
|
||||
|
||||
r_return_if_fail (list);
|
||||
|
||||
iter = r_list_iterator (list);
|
||||
RListIter *iter = r_list_iterator (list);
|
||||
while (iter) {
|
||||
void *item = iter->data;
|
||||
if (ptr == item) {
|
||||
@ -217,11 +213,9 @@ R_API RListIter *r_list_append(RList *list, void *data) {
|
||||
}
|
||||
|
||||
R_API RListIter *r_list_prepend(RList *list, void *data) {
|
||||
RListIter *item;
|
||||
|
||||
r_return_val_if_fail (list, NULL);
|
||||
|
||||
item = R_NEW0 (RListIter);
|
||||
RListIter *item = R_NEW0 (RListIter);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
@ -293,12 +287,11 @@ R_API void *r_list_pop(RList *list) {
|
||||
|
||||
R_API void *r_list_pop_head(RList *list) {
|
||||
void *data = NULL;
|
||||
RListIter *iter;
|
||||
|
||||
r_return_val_if_fail (list, NULL);
|
||||
|
||||
if (list->head) {
|
||||
iter = list->head;
|
||||
RListIter *iter = list->head;
|
||||
if (list->head == list->tail) {
|
||||
list->head = list->tail = NULL;
|
||||
} else {
|
||||
|
@ -4,6 +4,7 @@ r_util_sources = [
|
||||
'assert.c',
|
||||
'alloc.c',
|
||||
'donut.c',
|
||||
'table.c',
|
||||
'getopt.c',
|
||||
'base85.c',
|
||||
'base91.c',
|
||||
|
@ -108,6 +108,27 @@ done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
R_API bool r_strbuf_prepend(RStrBuf *sb, const char *s) {
|
||||
r_return_val_if_fail (sb && s, false);
|
||||
int l = strlen (s);
|
||||
// fast path if no chars to append
|
||||
if (l == 0) {
|
||||
return true;
|
||||
}
|
||||
int newlen = l + sb->len;
|
||||
char *ns = malloc (newlen + 1);
|
||||
if (ns) {
|
||||
memcpy (ns, s, l);
|
||||
char *s = sb->ptr ? sb->ptr: sb->buf;
|
||||
memcpy (ns + l, s, sb->len);
|
||||
free (sb->ptr);
|
||||
sb->ptr = ns;
|
||||
sb->len = newlen;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
R_API bool r_strbuf_append(RStrBuf *sb, const char *s) {
|
||||
r_return_val_if_fail (sb && s, false);
|
||||
|
||||
|
421
libr/util/table.c
Normal file
421
libr/util/table.c
Normal file
@ -0,0 +1,421 @@
|
||||
/* radare - LGPL - Copyright 2019 - pancake */
|
||||
|
||||
#include <r_util/r_table.h>
|
||||
|
||||
// cant do that without globals because RList doesnt have void *user :(
|
||||
static bool Ginc = false;
|
||||
static int Gnth = 0;
|
||||
static RListComparator Gcmp = NULL;
|
||||
|
||||
static int sortString(const void *a, const void *b) {
|
||||
return strcmp (a, b);
|
||||
}
|
||||
|
||||
static int sortNumber(const void *a, const void *b) {
|
||||
return r_num_get (NULL, a) - r_num_get (NULL, b);
|
||||
}
|
||||
|
||||
// maybe just index by name instead of exposing those symbols as global
|
||||
R_API RTableColumnType r_table_type_string = { "string", sortString };
|
||||
R_API RTableColumnType r_table_type_number = { "number", sortNumber };
|
||||
|
||||
// TODO: unused for now, maybe good to call after filter :?
|
||||
static void __table_adjust(RTable *t) {
|
||||
RListIter *iter, *iter2;
|
||||
RTableColumn *col;
|
||||
RTableRow *row;
|
||||
r_list_foreach (t->cols, iter, col) {
|
||||
col->width = 0;
|
||||
}
|
||||
r_list_foreach (t->rows, iter, row) {
|
||||
const char *item;
|
||||
int ncol = 0;
|
||||
r_list_foreach (row->items, iter2, item) {
|
||||
int itemLength = r_str_len_utf8 (item);
|
||||
RTableColumn *c = r_list_get_n (t->cols, ncol);
|
||||
if (c) {
|
||||
c->width = R_MAX (c->width, itemLength);
|
||||
}
|
||||
ncol ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
R_API void r_table_row_free(void *_row) {
|
||||
RTableRow *row = _row;
|
||||
free (row);
|
||||
}
|
||||
|
||||
R_API void r_table_column_free(void *_col) {
|
||||
RTableColumn *col = _col;
|
||||
free (col->name);
|
||||
free (col);
|
||||
}
|
||||
|
||||
R_API RTable *r_table_new() {
|
||||
RTable *t = R_NEW0 (RTable);
|
||||
t->showHeader = true;
|
||||
t->cols = r_list_newf (r_table_column_free);
|
||||
t->rows = r_list_newf (r_table_row_free);
|
||||
return t;
|
||||
}
|
||||
|
||||
R_API void r_table_free(RTable *t) {
|
||||
r_list_free (t->cols);
|
||||
r_list_free (t->rows);
|
||||
free (t);
|
||||
}
|
||||
|
||||
R_API void r_table_add_column(RTable *t, RTableColumnType *type, const char *name, int maxWidth) {
|
||||
RTableColumn *c = R_NEW0 (RTableColumn);
|
||||
if (c) {
|
||||
c->name = strdup (name);
|
||||
c->maxWidth = maxWidth;
|
||||
c->type = type;
|
||||
int itemLength = r_str_len_utf8 (name);
|
||||
c->width = itemLength + 1;
|
||||
r_list_append (t->cols, c);
|
||||
}
|
||||
}
|
||||
|
||||
R_API RTableRow *r_table_row_new(RList *items) {
|
||||
RTableRow *row = R_NEW (RTableRow);
|
||||
row->items = items;
|
||||
return row;
|
||||
}
|
||||
|
||||
static bool addRow (RTable *t, RList *items, const char *arg, int col) {
|
||||
int itemLength = r_str_len_utf8 (arg);
|
||||
RTableColumn *c = r_list_get_n (t->cols, col);
|
||||
if (c) {
|
||||
c->width = R_MAX (c->width, itemLength);
|
||||
r_list_append (items, strdup (arg));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
R_API void r_table_add_row(RTable *t, const char *name, ...) {
|
||||
va_list ap;
|
||||
va_start (ap, name);
|
||||
int col = 0;
|
||||
RList *items = r_list_newf (free);
|
||||
addRow (t, items, name, col++);
|
||||
for (;;) {
|
||||
const char *arg = va_arg (ap, const char *);
|
||||
if (!arg) {
|
||||
break;
|
||||
}
|
||||
addRow (t, items, arg, col);
|
||||
// TODO: assert if number of columns doesnt match t->cols
|
||||
col++;
|
||||
}
|
||||
va_end (ap);
|
||||
RTableRow *row = r_table_row_new (items);
|
||||
r_list_append (t->rows, row);
|
||||
// throw warning if not enough columns defined in header
|
||||
t->totalCols = R_MAX (t->totalCols, r_list_length (items));
|
||||
}
|
||||
|
||||
// import / export
|
||||
|
||||
R_API char *r_table_tofancystring(RTable *t) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
RTableRow *row;
|
||||
RTableColumn *col;
|
||||
RListIter *iter, *iter2;
|
||||
|
||||
r_list_foreach (t->cols, iter, col) {
|
||||
r_strbuf_appendf (sb, "| %*s ", col->width, col->name);
|
||||
}
|
||||
int len = r_strbuf_length (sb) - 1;
|
||||
{
|
||||
char *s = r_str_newf (".%s.\n", r_str_pad ('-', len));
|
||||
r_strbuf_prepend (sb, s);
|
||||
free (s);
|
||||
}
|
||||
|
||||
r_strbuf_appendf (sb, "|\n)%s(\n", r_str_pad ('-', len));
|
||||
r_list_foreach (t->rows, iter, row) {
|
||||
char *item;
|
||||
int c = 0;
|
||||
r_list_foreach (row->items, iter2, item) {
|
||||
RTableColumn *col = r_list_get_n (t->cols, c);
|
||||
if (col) {
|
||||
r_strbuf_appendf (sb, "| %*s ", col->width, item);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
r_strbuf_append (sb, "|\n");
|
||||
}
|
||||
r_strbuf_appendf (sb, "`%s'\n", r_str_pad ('-', len));
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
R_API char *r_table_tostring(RTable *t) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
RTableRow *row;
|
||||
RTableColumn *col;
|
||||
RListIter *iter, *iter2;
|
||||
if (t->showHeader) {
|
||||
r_list_foreach (t->cols, iter, col) {
|
||||
r_strbuf_appendf (sb, "%*s", col->width, col->name);
|
||||
}
|
||||
int len = r_strbuf_length (sb);
|
||||
r_strbuf_appendf (sb, "\n%s\n", r_str_pad ('-', len));
|
||||
}
|
||||
r_list_foreach (t->rows, iter, row) {
|
||||
char *item;
|
||||
int c = 0;
|
||||
r_list_foreach (row->items, iter2, item) {
|
||||
RTableColumn *col = r_list_get_n (t->cols, c);
|
||||
if (col) {
|
||||
r_strbuf_appendf (sb, "%*s", col->width, item);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
r_strbuf_append (sb, "\n");
|
||||
}
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
R_API char *r_table_tocsv(RTable *t) {
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
RTableRow *row;
|
||||
RTableColumn *col;
|
||||
RListIter *iter, *iter2;
|
||||
if (t->showHeader) {
|
||||
const char *comma = "";
|
||||
r_list_foreach (t->cols, iter, col) {
|
||||
if (strchr (col->name, ',')) {
|
||||
// TODO. escaped string?
|
||||
r_strbuf_appendf (sb, "%s\"%s\"", comma, col->name);
|
||||
} else {
|
||||
r_strbuf_appendf (sb, "%s%s", comma, col->name);
|
||||
}
|
||||
comma = ",";
|
||||
}
|
||||
r_strbuf_append (sb, "\n");
|
||||
}
|
||||
r_list_foreach (t->rows, iter, row) {
|
||||
char *item;
|
||||
int c = 0;
|
||||
const char *comma = "";
|
||||
r_list_foreach (row->items, iter2, item) {
|
||||
RTableColumn *col = r_list_get_n (t->cols, c);
|
||||
if (col) {
|
||||
if (strchr (col->name, ',')) {
|
||||
r_strbuf_appendf (sb, "%s\"%s\"", comma, col->name);
|
||||
} else {
|
||||
r_strbuf_appendf (sb, "%s%s", comma, item);
|
||||
}
|
||||
comma = ",";
|
||||
}
|
||||
c++;
|
||||
}
|
||||
r_strbuf_append (sb, "\n");
|
||||
}
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
R_API char *r_table_tojson(RTable *t) {
|
||||
PJ *pj = pj_new ();
|
||||
RTableRow *row;
|
||||
RListIter *iter, *iter2;
|
||||
pj_a (pj);
|
||||
r_list_foreach (t->rows, iter, row) {
|
||||
char *item;
|
||||
int c = 0;
|
||||
pj_o (pj);
|
||||
r_list_foreach (row->items, iter2, item) {
|
||||
RTableColumn *col = r_list_get_n (t->cols, c);
|
||||
if (col) {
|
||||
pj_ks (pj, col->name, item);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
pj_end (pj);
|
||||
}
|
||||
pj_end (pj);
|
||||
return pj_drain (pj);
|
||||
}
|
||||
|
||||
R_API void r_table_filter(RTable *t, int nth, int op, const char *un) {
|
||||
RTableRow *row;
|
||||
RListIter *iter, *iter2;
|
||||
ut64 uv = r_num_get (NULL, un);
|
||||
r_list_foreach_safe (t->rows, iter, iter2, row) {
|
||||
const char *nn = r_list_get_n (row->items, nth);
|
||||
ut64 nv = r_num_get (NULL, nn);
|
||||
bool match = true;
|
||||
switch (op) {
|
||||
case '>':
|
||||
match = (nv > uv);
|
||||
break;
|
||||
case '<':
|
||||
match = (nv < uv);
|
||||
break;
|
||||
case '=':
|
||||
match = (nv == uv);
|
||||
break;
|
||||
case '!':
|
||||
match = (nv == uv);
|
||||
break;
|
||||
case '~':
|
||||
match = strstr (nn, un) != NULL;
|
||||
case '\0':
|
||||
break;
|
||||
}
|
||||
if (!match) {
|
||||
r_list_delete (t->rows, iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int cmp(const void *_a, const void *_b) {
|
||||
RTableRow *a = (RTableRow*)_a;
|
||||
RTableRow *b = (RTableRow*)_b;
|
||||
const char *wa = r_list_get_n (a->items, Gnth);
|
||||
const char *wb = r_list_get_n (b->items, Gnth);
|
||||
int res = Gcmp (wa, wb);
|
||||
if (Ginc) {
|
||||
res = -res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
R_API void r_table_sort(RTable *t, int nth, bool inc) {
|
||||
RTableColumn *col = r_list_get_n (t->cols, nth);
|
||||
Ginc = inc;
|
||||
Gnth = nth;
|
||||
Gcmp = col->type->cmp;
|
||||
r_list_sort (t->rows, cmp);
|
||||
Gnth = Ginc = 0;
|
||||
Gcmp = NULL;
|
||||
}
|
||||
|
||||
static int __columnByName(RTable *t, const char *name) {
|
||||
RListIter *iter;
|
||||
RTableColumn *col;
|
||||
int n = 0;
|
||||
r_list_foreach (t->cols, iter, col) {
|
||||
if (!strcmp (name, col->name)) {
|
||||
return n;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int __resolveOperation(const char *op) {
|
||||
if (!strcmp (op, "gt")) {
|
||||
return '>';
|
||||
}
|
||||
if (!strcmp (op, "lt")) {
|
||||
return '<';
|
||||
}
|
||||
if (!strcmp (op, "eq")) {
|
||||
return '=';
|
||||
}
|
||||
if (!strcmp (op, "ne")) {
|
||||
return '!';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
R_API void r_table_query(RTable *t, const char *q) {
|
||||
// TODO support parenthesis and (or)||
|
||||
// split by "&&" (or comma) -> run .filter on each
|
||||
// addr/gt/200,addr/lt/400,addr/sort/dec,offset/sort/inc
|
||||
RListIter *iter;
|
||||
char *qq = strdup (q);
|
||||
RList *queries = r_str_split_list (qq, ",");
|
||||
char *query;
|
||||
r_list_foreach (queries, iter, query) {
|
||||
RList *q = r_str_split_list (query, "/");
|
||||
const char *columnName = r_list_get_n (q, 0);
|
||||
const char *operation = r_list_get_n (q, 1);
|
||||
const char *operand = r_list_get_n (q, 2);
|
||||
int col = __columnByName (t, columnName);
|
||||
if (col == -1) {
|
||||
if (*columnName == '[') {
|
||||
col = atoi (columnName + 1);
|
||||
} else {
|
||||
eprintf ("Invalid column name (%s)\n", columnName);
|
||||
}
|
||||
}
|
||||
if (!strcmp (operation, "sort")) {
|
||||
r_table_sort (t, col, !strcmp (operation, "inc"));
|
||||
} else {
|
||||
int op = __resolveOperation (operation);
|
||||
if (op == -1) {
|
||||
eprintf ("Invalid operation (%s)\n", operation);
|
||||
} else {
|
||||
r_table_filter (t, col, op, operand);
|
||||
}
|
||||
}
|
||||
r_list_free (q);
|
||||
}
|
||||
r_list_free (queries);
|
||||
free (qq);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO: to be implemented
|
||||
R_API RTable *r_table_clone(RTable *t) {
|
||||
// TODO: implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API RTable *r_table_push(RTable *t) {
|
||||
// TODO: implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API RTable *r_table_pop(RTable *t) {
|
||||
// TODO: implement
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API void r_table_fromjson(RTable *t, const char *csv) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
R_API void r_table_fromcsv(RTable *t, const char *csv) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
R_API char *r_table_tohtml(RTable *t) {
|
||||
// TODO
|
||||
return NULL;
|
||||
}
|
||||
|
||||
R_API void r_table_transpose(RTable *t) {
|
||||
// When the music stops rows will be cols and cols... rows!
|
||||
}
|
||||
|
||||
R_API void r_table_format(RTable *t, int nth, RTableColumnType *type) {
|
||||
// change the format of a specific column
|
||||
// change imm base, decimal precission, ...
|
||||
}
|
||||
|
||||
// to compute sum result of all the elements in a column
|
||||
R_API ut64 r_table_reduce(RTable *t, int nth) {
|
||||
// When the music stops rows will be cols and cols... rows!
|
||||
return 0;
|
||||
}
|
||||
|
||||
R_API void r_table_columns(RTable *t, const char *name, ...) {
|
||||
va_list ap;
|
||||
va_start (ap, fmt);
|
||||
r_list_free (t->cols);
|
||||
t->cols = r_list_newf (__table_column_free);
|
||||
for (;;) {
|
||||
const char *n = va_arg (ap, const char *);
|
||||
if (!n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
va_end (ap);
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user