Skip to content

Commit 077539d

Browse files
pcloudsgitster
authored andcommitted
column: add columnar layout
COL_COLUMN and COL_ROW fill column by column (or row by row respectively), given the terminal width and how many space between columns. All cells have equal width. Strings are supposed to be in UTF-8. Valid ANSI escape strings are OK. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 88e8f90 commit 077539d

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed

Documentation/config.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,10 @@ column.ui::
848848
never show in columns
849849
`auto`;;
850850
show in columns if the output is to the terminal
851+
`column`;;
852+
fill columns before rows (default)
853+
`row`;;
854+
fill rows before columns
851855
`plain`;;
852856
show in one column
853857
--

column.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,59 @@
22
#include "column.h"
33
#include "string-list.h"
44
#include "parse-options.h"
5+
#include "utf8.h"
6+
7+
#define XY2LINEAR(d, x, y) (COL_LAYOUT((d)->colopts) == COL_COLUMN ? \
8+
(x) * (d)->rows + (y) : \
9+
(y) * (d)->cols + (x))
10+
11+
struct column_data {
12+
const struct string_list *list;
13+
unsigned int colopts;
14+
struct column_options opts;
15+
16+
int rows, cols;
17+
int *len; /* cell length */
18+
};
19+
20+
/* return length of 's' in letters, ANSI escapes stripped */
21+
static int item_length(unsigned int colopts, const char *s)
22+
{
23+
int len, i = 0;
24+
struct strbuf str = STRBUF_INIT;
25+
26+
strbuf_addstr(&str, s);
27+
while ((s = strstr(str.buf + i, "\033[")) != NULL) {
28+
int len = strspn(s + 2, "0123456789;");
29+
i = s - str.buf;
30+
strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
31+
}
32+
len = utf8_strwidth(str.buf);
33+
strbuf_release(&str);
34+
return len;
35+
}
36+
37+
/*
38+
* Calculate cell width, rows and cols for a table of equal cells, given
39+
* table width and how many spaces between cells.
40+
*/
41+
static void layout(struct column_data *data, int *width)
42+
{
43+
int i;
44+
45+
*width = 0;
46+
for (i = 0; i < data->list->nr; i++)
47+
if (*width < data->len[i])
48+
*width = data->len[i];
49+
50+
*width += data->opts.padding;
51+
52+
data->cols = (data->opts.width - strlen(data->opts.indent)) / *width;
53+
if (data->cols == 0)
54+
data->cols = 1;
55+
56+
data->rows = DIV_ROUND_UP(data->list->nr, data->cols);
57+
}
558

659
/* Display without layout when not enabled */
760
static void display_plain(const struct string_list *list,
@@ -13,6 +66,61 @@ static void display_plain(const struct string_list *list,
1366
printf("%s%s%s", indent, list->items[i].string, nl);
1467
}
1568

69+
/* Print a cell to stdout with all necessary leading/traling space */
70+
static int display_cell(struct column_data *data, int initial_width,
71+
const char *empty_cell, int x, int y)
72+
{
73+
int i, len, newline;
74+
75+
i = XY2LINEAR(data, x, y);
76+
if (i >= data->list->nr)
77+
return -1;
78+
len = data->len[i];
79+
if (COL_LAYOUT(data->colopts) == COL_COLUMN)
80+
newline = i + data->rows >= data->list->nr;
81+
else
82+
newline = x == data->cols - 1 || i == data->list->nr - 1;
83+
84+
printf("%s%s%s",
85+
x == 0 ? data->opts.indent : "",
86+
data->list->items[i].string,
87+
newline ? data->opts.nl : empty_cell + len);
88+
return 0;
89+
}
90+
91+
/* Display COL_COLUMN or COL_ROW */
92+
static void display_table(const struct string_list *list,
93+
unsigned int colopts,
94+
const struct column_options *opts)
95+
{
96+
struct column_data data;
97+
int x, y, i, initial_width;
98+
char *empty_cell;
99+
100+
memset(&data, 0, sizeof(data));
101+
data.list = list;
102+
data.colopts = colopts;
103+
data.opts = *opts;
104+
105+
data.len = xmalloc(sizeof(*data.len) * list->nr);
106+
for (i = 0; i < list->nr; i++)
107+
data.len[i] = item_length(colopts, list->items[i].string);
108+
109+
layout(&data, &initial_width);
110+
111+
empty_cell = xmalloc(initial_width + 1);
112+
memset(empty_cell, ' ', initial_width);
113+
empty_cell[initial_width] = '\0';
114+
for (y = 0; y < data.rows; y++) {
115+
for (x = 0; x < data.cols; x++)
116+
if (display_cell(&data, initial_width, empty_cell, x, y))
117+
break;
118+
}
119+
120+
free(data.len);
121+
free(empty_cell);
122+
}
123+
16124
void print_columns(const struct string_list *list, unsigned int colopts,
17125
const struct column_options *opts)
18126
{
@@ -35,6 +143,10 @@ void print_columns(const struct string_list *list, unsigned int colopts,
35143
case COL_PLAIN:
36144
display_plain(list, nopts.indent, nopts.nl);
37145
break;
146+
case COL_ROW:
147+
case COL_COLUMN:
148+
display_table(list, colopts, &nopts);
149+
break;
38150
default:
39151
die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
40152
}
@@ -69,6 +181,8 @@ static int parse_option(const char *arg, int len, unsigned int *colopts,
69181
{ "never", COL_DISABLED, COL_ENABLE_MASK },
70182
{ "auto", COL_AUTO, COL_ENABLE_MASK },
71183
{ "plain", COL_PLAIN, COL_LAYOUT_MASK },
184+
{ "column", COL_COLUMN, COL_LAYOUT_MASK },
185+
{ "row", COL_ROW, COL_LAYOUT_MASK },
72186
};
73187
int i;
74188

column.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#define COL_AUTO 0x0020
1111

1212
#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
13+
#define COL_COLUMN 0 /* Fill columns before rows */
14+
#define COL_ROW 1 /* Fill rows before columns */
1315
#define COL_PLAIN 15 /* one column */
1416

1517
#define explicitly_enable_column(c) \

t/t9002-column.sh

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,90 @@ EOF
4242
test_cmp expected actual
4343
'
4444

45+
test_expect_success '80 columns' '
46+
cat >expected <<\EOF &&
47+
one two three four five six seven eight nine ten eleven
48+
EOF
49+
COLUMNS=80 git column --mode=column <lista >actual &&
50+
test_cmp expected actual
51+
'
52+
53+
test_expect_success 'COLUMNS = 1' '
54+
cat >expected <<\EOF &&
55+
one
56+
two
57+
three
58+
four
59+
five
60+
six
61+
seven
62+
eight
63+
nine
64+
ten
65+
eleven
66+
EOF
67+
COLUMNS=1 git column --mode=column <lista >actual &&
68+
test_cmp expected actual
69+
'
70+
71+
test_expect_success 'width = 1' '
72+
git column --mode=column --width=1 <lista >actual &&
73+
test_cmp expected actual
74+
'
75+
76+
COLUMNS=20
77+
export COLUMNS
78+
79+
test_expect_success '20 columns' '
80+
cat >expected <<\EOF &&
81+
one seven
82+
two eight
83+
three nine
84+
four ten
85+
five eleven
86+
six
87+
EOF
88+
git column --mode=column <lista >actual &&
89+
test_cmp expected actual
90+
'
91+
92+
test_expect_success '20 columns, padding 2' '
93+
cat >expected <<\EOF &&
94+
one seven
95+
two eight
96+
three nine
97+
four ten
98+
five eleven
99+
six
100+
EOF
101+
git column --mode=column --padding 2 <lista >actual &&
102+
test_cmp expected actual
103+
'
104+
105+
test_expect_success '20 columns, indented' '
106+
cat >expected <<\EOF &&
107+
one seven
108+
two eight
109+
three nine
110+
four ten
111+
five eleven
112+
six
113+
EOF
114+
git column --mode=column --indent=" " <lista >actual &&
115+
test_cmp expected actual
116+
'
117+
118+
test_expect_success '20 columns, row first' '
119+
cat >expected <<\EOF &&
120+
one two
121+
three four
122+
five six
123+
seven eight
124+
nine ten
125+
eleven
126+
EOF
127+
git column --mode=row <lista >actual &&
128+
test_cmp expected actual
129+
'
130+
45131
test_done

0 commit comments

Comments
 (0)