Add column layout skeleton and git-column
A column option string consists of many token separated by either a space or a comma. A token belongs to one of three groups: - enabling: always, never and auto - layout mode: currently plain (which does not layout at all) - other future tuning flags git-column can be used to pipe output to from a command that wants column layout, but not to mess with its own output code. Simpler output code can be changed to use column layout code directly. Thanks-to: Ramsay Jones <ramsay@ramsay1.demon.co.uk> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Nguyễn Thái Ngọc Duy committed
Apr 21, 2012 at 11:44 UTC
7e29b8254f08af820b2f0c3770836638ffe517ab
12 files changed
+391
.gitignore
+1
@@ -26,6 +26,7 @@
26
/git-cherry-pick
27
/git-clean
28
/git-clone
29
+/git-column
30
/git-commit
31
/git-commit-tree
32
/git-config
Documentation/config.txt
+18
@@ -836,6 +836,24 @@ color.ui::
836
`never` if you prefer git commands not to use color unless enabled
837
explicitly with some other configuration or the `--color` option.
838
839
+column.ui::
840
+ Specify whether supported commands should output in columns.
841
+ This variable consists of a list of tokens separated by spaces
842
+ or commas:
843
++
844
+--
845
+`always`;;
846
+ always show in columns
847
+`never`;;
848
+ never show in columns
849
+`auto`;;
850
+ show in columns if the output is to the terminal
851
+`plain`;;
852
+ show in one column
853
+--
854
++
855
+ This option defaults to 'never'.
856
+
857
commit.status::
858
A boolean to enable/disable inclusion of status information in the
859
commit message template when using an editor to prepare the commit
Documentation/git-column.txt
new
+53
@@ -0,0 +1,53 @@
1
+git-column(1)
2
+=============
3
+
4
+NAME
5
+----
6
+git-column - Display data in columns
7
+
8
+SYNOPSIS
9
+--------
10
+[verse]
11
+'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
12
+ [--indent=<string>] [--nl=<string>] [--pading=<n>]
13
+
14
+DESCRIPTION
15
+-----------
16
+This command formats its input into multiple columns.
17
+
18
+OPTIONS
19
+-------
20
+--command=<name>::
21
+ Look up layout mode using configuration variable column.<name> and
22
+ column.ui.
23
+
24
+--mode=<mode>::
25
+ Specify layout mode. See configuration variable column.ui for option
26
+ syntax.
27
+
28
+--raw-mode=<n>::
29
+ Same as --mode but take mode encoded as a number. This is mainly used
30
+ by other commands that have already parsed layout mode.
31
+
32
+--width=<width>::
33
+ Specify the terminal width. By default 'git column' will detect the
34
+ terminal width, or fall back to 80 if it is unable to do so.
35
+
36
+--indent=<string>::
37
+ String to be printed at the beginning of each line.
38
+
39
+--nl=<N>::
40
+ String to be printed at the end of each line,
41
+ including newline character.
42
+
43
+--padding=<N>::
44
+ The number of spaces between columns. One space by default.
45
+
46
+
47
+Author
48
+------
49
+Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
50
+
51
+GIT
52
+---
53
+Part of the linkgit:git[1] suite
Makefile
+3
@@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o
646
LIB_OBJS += bundle.o
647
LIB_OBJS += cache-tree.o
648
LIB_OBJS += color.o
649
+LIB_OBJS += column.o
650
LIB_OBJS += combine-diff.o
651
LIB_OBJS += commit.o
652
LIB_OBJS += compat/obstack.o
@@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o
775
BUILTIN_OBJS += builtin/checkout.o
776
BUILTIN_OBJS += builtin/clean.o
777
BUILTIN_OBJS += builtin/clone.o
778
+BUILTIN_OBJS += builtin/column.o
779
BUILTIN_OBJS += builtin/commit-tree.o
780
BUILTIN_OBJS += builtin/commit.o
781
BUILTIN_OBJS += builtin/config.o
@@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
2168
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
2169
builtin/tar-tree.o archive-tar.o: tar.h
2170
connect.o transport.o url.o http-backend.o: url.h
2171
+column.o help.o pager.o: column.h
2172
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
2173
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
2174
builtin.h
+1
@@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);
61
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
62
extern int cmd_clone(int argc, const char **argv, const char *prefix);
63
extern int cmd_clean(int argc, const char **argv, const char *prefix);
64
+extern int cmd_column(int argc, const char **argv, const char *prefix);
65
extern int cmd_commit(int argc, const char **argv, const char *prefix);
66
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
67
extern int cmd_config(int argc, const char **argv, const char *prefix);
builtin/column.c
new
+59
@@ -0,0 +1,59 @@
1
+#include "builtin.h"
2
+#include "cache.h"
3
+#include "strbuf.h"
4
+#include "parse-options.h"
5
+#include "string-list.h"
6
+#include "column.h"
7
+
8
+static const char * const builtin_column_usage[] = {
9
+ "git column [options]",
10
+ NULL
11
+};
12
+static unsigned int colopts;
13
+
14
+static int column_config(const char *var, const char *value, void *cb)
15
+{
16
+ return git_column_config(var, value, cb, &colopts);
17
+}
18
+
19
+int cmd_column(int argc, const char **argv, const char *prefix)
20
+{
21
+ struct string_list list = STRING_LIST_INIT_DUP;
22
+ struct strbuf sb = STRBUF_INIT;
23
+ struct column_options copts;
24
+ const char *command = NULL, *real_command = NULL;
25
+ struct option options[] = {
26
+ OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
27
+ OPT_COLUMN(0, "mode", &colopts, "layout to use"),
28
+ OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
29
+ OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
30
+ OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
31
+ OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
32
+ OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
33
+ OPT_END()
34
+ };
35
+
36
+ /* This one is special and must be the first one */
37
+ if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
38
+ command = argv[1] + 10;
39
+ git_config(column_config, (void *)command);
40
+ } else
41
+ git_config(column_config, NULL);
42
+
43
+ memset(&copts, 0, sizeof(copts));
44
+ copts.width = term_columns();
45
+ copts.padding = 1;
46
+ argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
47
+ if (argc)
48
+ usage_with_options(builtin_column_usage, options);
49
+ if (real_command || command) {
50
+ if (!real_command || !command || strcmp(real_command, command))
51
+ die(_("--command must be the first argument"));
52
+ }
53
+ finalize_colopts(&colopts, -1);
54
+ while (!strbuf_getline(&sb, stdin, '\n'))
55
+ string_list_append(&list, sb.buf);
56
+
57
+ print_columns(&list, colopts, &copts);
58
+ return 0;
59
+}
column.c
new
+169
@@ -0,0 +1,169 @@
1
+#include "cache.h"
2
+#include "column.h"
3
+#include "string-list.h"
4
+#include "parse-options.h"
5
+
6
+/* Display without layout when not enabled */
7
+static void display_plain(const struct string_list *list,
8
+ const char *indent, const char *nl)
9
+{
10
+ int i;
11
+
12
+ for (i = 0; i < list->nr; i++)
13
+ printf("%s%s%s", indent, list->items[i].string, nl);
14
+}
15
+
16
+void print_columns(const struct string_list *list, unsigned int colopts,
17
+ const struct column_options *opts)
18
+{
19
+ struct column_options nopts;
20
+
21
+ if (!list->nr)
22
+ return;
23
+ assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
24
+
25
+ memset(&nopts, 0, sizeof(nopts));
26
+ nopts.indent = opts && opts->indent ? opts->indent : "";
27
+ nopts.nl = opts && opts->nl ? opts->nl : "\n";
28
+ nopts.padding = opts ? opts->padding : 1;
29
+ nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
30
+ if (!column_active(colopts)) {
31
+ display_plain(list, "", "\n");
32
+ return;
33
+ }
34
+ switch (COL_LAYOUT(colopts)) {
35
+ case COL_PLAIN:
36
+ display_plain(list, nopts.indent, nopts.nl);
37
+ break;
38
+ default:
39
+ die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
40
+ }
41
+}
42
+
43
+int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
44
+{
45
+ if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
46
+ if (stdout_is_tty < 0)
47
+ stdout_is_tty = isatty(1);
48
+ *colopts &= ~COL_ENABLE_MASK;
49
+ if (stdout_is_tty)
50
+ *colopts |= COL_ENABLED;
51
+ }
52
+ return 0;
53
+}
54
+
55
+struct colopt {
56
+ const char *name;
57
+ unsigned int value;
58
+ unsigned int mask;
59
+};
60
+
61
+#define LAYOUT_SET 1
62
+#define ENABLE_SET 2
63
+
64
+static int parse_option(const char *arg, int len, unsigned int *colopts,
65
+ int *group_set)
66
+{
67
+ struct colopt opts[] = {
68
+ { "always", COL_ENABLED, COL_ENABLE_MASK },
69
+ { "never", COL_DISABLED, COL_ENABLE_MASK },
70
+ { "auto", COL_AUTO, COL_ENABLE_MASK },
71
+ { "plain", COL_PLAIN, COL_LAYOUT_MASK },
72
+ };
73
+ int i;
74
+
75
+ for (i = 0; i < ARRAY_SIZE(opts); i++) {
76
+ int arg_len = len, name_len;
77
+ const char *arg_str = arg;
78
+
79
+ name_len = strlen(opts[i].name);
80
+ if (arg_len != name_len ||
81
+ strncmp(arg_str, opts[i].name, name_len))
82
+ continue;
83
+
84
+ switch (opts[i].mask) {
85
+ case COL_ENABLE_MASK:
86
+ *group_set |= ENABLE_SET;
87
+ break;
88
+ case COL_LAYOUT_MASK:
89
+ *group_set |= LAYOUT_SET;
90
+ break;
91
+ }
92
+
93
+ if (opts[i].mask)
94
+ *colopts = (*colopts & ~opts[i].mask) | opts[i].value;
95
+ return 0;
96
+ }
97
+
98
+ return error("unsupported option '%s'", arg);
99
+}
100
+
101
+static int parse_config(unsigned int *colopts, const char *value)
102
+{
103
+ const char *sep = " ,";
104
+ int group_set = 0;
105
+
106
+ while (*value) {
107
+ int len = strcspn(value, sep);
108
+ if (len) {
109
+ if (parse_option(value, len, colopts, &group_set))
110
+ return -1;
111
+
112
+ value += len;
113
+ }
114
+ value += strspn(value, sep);
115
+ }
116
+ /*
117
+ * Setting layout implies "always" if neither always, never
118
+ * nor auto is specified.
119
+ *
120
+ * Current value in COL_ENABLE_MASK is disregarded. This means if
121
+ * you set column.ui = auto and pass --column=row, then "auto"
122
+ * will become "always".
123
+ */
124
+ if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
125
+ *colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
126
+ return 0;
127
+}
128
+
129
+static int column_config(const char *var, const char *value,
130
+ const char *key, unsigned int *colopts)
131
+{
132
+ if (!value)
133
+ return config_error_nonbool(var);
134
+ if (parse_config(colopts, value))
135
+ return error("invalid column.%s mode %s", key, value);
136
+ return 0;
137
+}
138
+
139
+int git_column_config(const char *var, const char *value,
140
+ const char *command, unsigned int *colopts)
141
+{
142
+ const char *it = skip_prefix(var, "column.");
143
+ if (!it)
144
+ return 0;
145
+
146
+ if (!strcmp(it, "ui"))
147
+ return column_config(var, value, "ui", colopts);
148
+
149
+ if (command && !strcmp(it, command))
150
+ return column_config(var, value, it, colopts);
151
+
152
+ return 0;
153
+}
154
+
155
+int parseopt_column_callback(const struct option *opt,
156
+ const char *arg, int unset)
157
+{
158
+ unsigned int *colopts = opt->value;
159
+ *colopts |= COL_PARSEOPT;
160
+ *colopts &= ~COL_ENABLE_MASK;
161
+ if (unset) /* --no-column == never */
162
+ return 0;
163
+ /* --column == always unless "arg" states otherwise */
164
+ *colopts |= COL_ENABLED;
165
+ if (arg)
166
+ return parse_config(colopts, arg);
167
+
168
+ return 0;
169
+}
column.h
new
+38
@@ -0,0 +1,38 @@
1
+#ifndef COLUMN_H
2
+#define COLUMN_H
3
+
4
+#define COL_LAYOUT_MASK 0x000F
5
+#define COL_ENABLE_MASK 0x0030 /* always, never or auto */
6
+#define COL_PARSEOPT 0x0040 /* --column is given from cmdline */
7
+
8
+#define COL_DISABLED 0x0000 /* must be zero */
9
+#define COL_ENABLED 0x0010
10
+#define COL_AUTO 0x0020
11
+
12
+#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
13
+#define COL_PLAIN 15 /* one column */
14
+
15
+#define explicitly_enable_column(c) \
16
+ (((c) & COL_PARSEOPT) && column_active(c))
17
+
18
+struct column_options {
19
+ int width;
20
+ int padding;
21
+ const char *indent;
22
+ const char *nl;
23
+};
24
+
25
+struct option;
26
+extern int parseopt_column_callback(const struct option *, const char *, int);
27
+extern int git_column_config(const char *var, const char *value,
28
+ const char *command, unsigned int *colopts);
29
+extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
30
+static inline int column_active(unsigned int colopts)
31
+{
32
+ return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
33
+}
34
+
35
+extern void print_columns(const struct string_list *list, unsigned int colopts,
36
+ const struct column_options *opts);
37
+
38
+#endif
command-list.txt
+1
@@ -20,6 +20,7 @@ git-cherry-pick mainporcelain
20
git-citool mainporcelain
21
git-clean mainporcelain
22
git-clone mainporcelain common
23
+git-column purehelpers
24
git-commit mainporcelain common
25
git-commit-tree plumbingmanipulators
26
git-config ancillarymanipulators
git.c
+1
@@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)
348
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
349
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
350
{ "clone", cmd_clone },
351
+ { "column", cmd_column, RUN_SETUP_GENTLY },
352
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
353
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
354
{ "config", cmd_config, RUN_SETUP_GENTLY },
parse-options.h
+2
@@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
238
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
239
#define OPT__COLOR(var, h) \
240
OPT_COLOR_FLAG(0, "color", (var), (h))
241
+#define OPT_COLUMN(s, l, v, h) \
242
+ { OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
243
244
#endif
t/t9002-column.sh
new
+45
@@ -0,0 +1,45 @@
1
+#!/bin/sh
2
+
3
+test_description='git column'
4
+. ./test-lib.sh
5
+
6
+test_expect_success 'setup' '
7
+ cat >lista <<\EOF
8
+one
9
+two
10
+three
11
+four
12
+five
13
+six
14
+seven
15
+eight
16
+nine
17
+ten
18
+eleven
19
+EOF
20
+'
21
+
22
+test_expect_success 'never' '
23
+ git column --indent=Z --mode=never <lista >actual &&
24
+ test_cmp lista actual
25
+'
26
+
27
+test_expect_success 'always' '
28
+ cat >expected <<\EOF &&
29
+Zone
30
+Ztwo
31
+Zthree
32
+Zfour
33
+Zfive
34
+Zsix
35
+Zseven
36
+Zeight
37
+Znine
38
+Zten
39
+Zeleven
40
+EOF
41
+ git column --indent=Z --mode=plain <lista >actual &&
42
+ test_cmp expected actual
43
+'
44
+
45
+test_done