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