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>
This commit is contained in:
Nguyễn Thái Ngọc Duy 2012-04-21 11:44:32 +07:00 committed by Junio C Hamano
parent b082687cba
commit 7e29b8254f
12 changed files with 391 additions and 0 deletions

1
.gitignore vendored
View file

@ -26,6 +26,7 @@
/git-cherry-pick /git-cherry-pick
/git-clean /git-clean
/git-clone /git-clone
/git-column
/git-commit /git-commit
/git-commit-tree /git-commit-tree
/git-config /git-config

View file

@ -836,6 +836,24 @@ color.ui::
`never` if you prefer git commands not to use color unless enabled `never` if you prefer git commands not to use color unless enabled
explicitly with some other configuration or the `--color` option. explicitly with some other configuration or the `--color` option.
column.ui::
Specify whether supported commands should output in columns.
This variable consists of a list of tokens separated by spaces
or commas:
+
--
`always`;;
always show in columns
`never`;;
never show in columns
`auto`;;
show in columns if the output is to the terminal
`plain`;;
show in one column
--
+
This option defaults to 'never'.
commit.status:: commit.status::
A boolean to enable/disable inclusion of status information in the A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit commit message template when using an editor to prepare the commit

View file

@ -0,0 +1,53 @@
git-column(1)
=============
NAME
----
git-column - Display data in columns
SYNOPSIS
--------
[verse]
'git column' [--command=<name>] [--[raw-]mode=<mode>] [--width=<width>]
[--indent=<string>] [--nl=<string>] [--pading=<n>]
DESCRIPTION
-----------
This command formats its input into multiple columns.
OPTIONS
-------
--command=<name>::
Look up layout mode using configuration variable column.<name> and
column.ui.
--mode=<mode>::
Specify layout mode. See configuration variable column.ui for option
syntax.
--raw-mode=<n>::
Same as --mode but take mode encoded as a number. This is mainly used
by other commands that have already parsed layout mode.
--width=<width>::
Specify the terminal width. By default 'git column' will detect the
terminal width, or fall back to 80 if it is unable to do so.
--indent=<string>::
String to be printed at the beginning of each line.
--nl=<N>::
String to be printed at the end of each line,
including newline character.
--padding=<N>::
The number of spaces between columns. One space by default.
Author
------
Written by Nguyen Thai Ngoc Duy <pclouds@gmail.com>
GIT
---
Part of the linkgit:git[1] suite

View file

@ -646,6 +646,7 @@ LIB_OBJS += bulk-checkin.o
LIB_OBJS += bundle.o LIB_OBJS += bundle.o
LIB_OBJS += cache-tree.o LIB_OBJS += cache-tree.o
LIB_OBJS += color.o LIB_OBJS += color.o
LIB_OBJS += column.o
LIB_OBJS += combine-diff.o LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o LIB_OBJS += compat/obstack.o
@ -774,6 +775,7 @@ BUILTIN_OBJS += builtin/checkout-index.o
BUILTIN_OBJS += builtin/checkout.o BUILTIN_OBJS += builtin/checkout.o
BUILTIN_OBJS += builtin/clean.o BUILTIN_OBJS += builtin/clean.o
BUILTIN_OBJS += builtin/clone.o BUILTIN_OBJS += builtin/clone.o
BUILTIN_OBJS += builtin/column.o
BUILTIN_OBJS += builtin/commit-tree.o BUILTIN_OBJS += builtin/commit-tree.o
BUILTIN_OBJS += builtin/commit.o BUILTIN_OBJS += builtin/commit.o
BUILTIN_OBJS += builtin/config.o BUILTIN_OBJS += builtin/config.o
@ -2166,6 +2168,7 @@ builtin/prune.o builtin/reflog.o reachable.o: reachable.h
builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
builtin/tar-tree.o archive-tar.o: tar.h builtin/tar-tree.o archive-tar.o: tar.h
connect.o transport.o url.o http-backend.o: url.h connect.o transport.o url.o http-backend.o: url.h
column.o help.o pager.o: column.h
http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h

View file

@ -61,6 +61,7 @@ extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
extern int cmd_clone(int argc, const char **argv, const char *prefix); extern int cmd_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix); extern int cmd_clean(int argc, const char **argv, const char *prefix);
extern int cmd_column(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix); extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix);

59
builtin/column.c Normal file
View file

@ -0,0 +1,59 @@
#include "builtin.h"
#include "cache.h"
#include "strbuf.h"
#include "parse-options.h"
#include "string-list.h"
#include "column.h"
static const char * const builtin_column_usage[] = {
"git column [options]",
NULL
};
static unsigned int colopts;
static int column_config(const char *var, const char *value, void *cb)
{
return git_column_config(var, value, cb, &colopts);
}
int cmd_column(int argc, const char **argv, const char *prefix)
{
struct string_list list = STRING_LIST_INIT_DUP;
struct strbuf sb = STRBUF_INIT;
struct column_options copts;
const char *command = NULL, *real_command = NULL;
struct option options[] = {
OPT_STRING(0, "command", &real_command, "name", "lookup config vars"),
OPT_COLUMN(0, "mode", &colopts, "layout to use"),
OPT_INTEGER(0, "raw-mode", &colopts, "layout to use"),
OPT_INTEGER(0, "width", &copts.width, "Maximum width"),
OPT_STRING(0, "indent", &copts.indent, "string", "Padding space on left border"),
OPT_INTEGER(0, "nl", &copts.nl, "Padding space on right border"),
OPT_INTEGER(0, "padding", &copts.padding, "Padding space between columns"),
OPT_END()
};
/* This one is special and must be the first one */
if (argc > 1 && !prefixcmp(argv[1], "--command=")) {
command = argv[1] + 10;
git_config(column_config, (void *)command);
} else
git_config(column_config, NULL);
memset(&copts, 0, sizeof(copts));
copts.width = term_columns();
copts.padding = 1;
argc = parse_options(argc, argv, "", options, builtin_column_usage, 0);
if (argc)
usage_with_options(builtin_column_usage, options);
if (real_command || command) {
if (!real_command || !command || strcmp(real_command, command))
die(_("--command must be the first argument"));
}
finalize_colopts(&colopts, -1);
while (!strbuf_getline(&sb, stdin, '\n'))
string_list_append(&list, sb.buf);
print_columns(&list, colopts, &copts);
return 0;
}

169
column.c Normal file
View file

@ -0,0 +1,169 @@
#include "cache.h"
#include "column.h"
#include "string-list.h"
#include "parse-options.h"
/* Display without layout when not enabled */
static void display_plain(const struct string_list *list,
const char *indent, const char *nl)
{
int i;
for (i = 0; i < list->nr; i++)
printf("%s%s%s", indent, list->items[i].string, nl);
}
void print_columns(const struct string_list *list, unsigned int colopts,
const struct column_options *opts)
{
struct column_options nopts;
if (!list->nr)
return;
assert((colopts & COL_ENABLE_MASK) != COL_AUTO);
memset(&nopts, 0, sizeof(nopts));
nopts.indent = opts && opts->indent ? opts->indent : "";
nopts.nl = opts && opts->nl ? opts->nl : "\n";
nopts.padding = opts ? opts->padding : 1;
nopts.width = opts && opts->width ? opts->width : term_columns() - 1;
if (!column_active(colopts)) {
display_plain(list, "", "\n");
return;
}
switch (COL_LAYOUT(colopts)) {
case COL_PLAIN:
display_plain(list, nopts.indent, nopts.nl);
break;
default:
die("BUG: invalid layout mode %d", COL_LAYOUT(colopts));
}
}
int finalize_colopts(unsigned int *colopts, int stdout_is_tty)
{
if ((*colopts & COL_ENABLE_MASK) == COL_AUTO) {
if (stdout_is_tty < 0)
stdout_is_tty = isatty(1);
*colopts &= ~COL_ENABLE_MASK;
if (stdout_is_tty)
*colopts |= COL_ENABLED;
}
return 0;
}
struct colopt {
const char *name;
unsigned int value;
unsigned int mask;
};
#define LAYOUT_SET 1
#define ENABLE_SET 2
static int parse_option(const char *arg, int len, unsigned int *colopts,
int *group_set)
{
struct colopt opts[] = {
{ "always", COL_ENABLED, COL_ENABLE_MASK },
{ "never", COL_DISABLED, COL_ENABLE_MASK },
{ "auto", COL_AUTO, COL_ENABLE_MASK },
{ "plain", COL_PLAIN, COL_LAYOUT_MASK },
};
int i;
for (i = 0; i < ARRAY_SIZE(opts); i++) {
int arg_len = len, name_len;
const char *arg_str = arg;
name_len = strlen(opts[i].name);
if (arg_len != name_len ||
strncmp(arg_str, opts[i].name, name_len))
continue;
switch (opts[i].mask) {
case COL_ENABLE_MASK:
*group_set |= ENABLE_SET;
break;
case COL_LAYOUT_MASK:
*group_set |= LAYOUT_SET;
break;
}
if (opts[i].mask)
*colopts = (*colopts & ~opts[i].mask) | opts[i].value;
return 0;
}
return error("unsupported option '%s'", arg);
}
static int parse_config(unsigned int *colopts, const char *value)
{
const char *sep = " ,";
int group_set = 0;
while (*value) {
int len = strcspn(value, sep);
if (len) {
if (parse_option(value, len, colopts, &group_set))
return -1;
value += len;
}
value += strspn(value, sep);
}
/*
* Setting layout implies "always" if neither always, never
* nor auto is specified.
*
* Current value in COL_ENABLE_MASK is disregarded. This means if
* you set column.ui = auto and pass --column=row, then "auto"
* will become "always".
*/
if ((group_set & LAYOUT_SET) && !(group_set & ENABLE_SET))
*colopts = (*colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
return 0;
}
static int column_config(const char *var, const char *value,
const char *key, unsigned int *colopts)
{
if (!value)
return config_error_nonbool(var);
if (parse_config(colopts, value))
return error("invalid column.%s mode %s", key, value);
return 0;
}
int git_column_config(const char *var, const char *value,
const char *command, unsigned int *colopts)
{
const char *it = skip_prefix(var, "column.");
if (!it)
return 0;
if (!strcmp(it, "ui"))
return column_config(var, value, "ui", colopts);
if (command && !strcmp(it, command))
return column_config(var, value, it, colopts);
return 0;
}
int parseopt_column_callback(const struct option *opt,
const char *arg, int unset)
{
unsigned int *colopts = opt->value;
*colopts |= COL_PARSEOPT;
*colopts &= ~COL_ENABLE_MASK;
if (unset) /* --no-column == never */
return 0;
/* --column == always unless "arg" states otherwise */
*colopts |= COL_ENABLED;
if (arg)
return parse_config(colopts, arg);
return 0;
}

38
column.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef COLUMN_H
#define COLUMN_H
#define COL_LAYOUT_MASK 0x000F
#define COL_ENABLE_MASK 0x0030 /* always, never or auto */
#define COL_PARSEOPT 0x0040 /* --column is given from cmdline */
#define COL_DISABLED 0x0000 /* must be zero */
#define COL_ENABLED 0x0010
#define COL_AUTO 0x0020
#define COL_LAYOUT(c) ((c) & COL_LAYOUT_MASK)
#define COL_PLAIN 15 /* one column */
#define explicitly_enable_column(c) \
(((c) & COL_PARSEOPT) && column_active(c))
struct column_options {
int width;
int padding;
const char *indent;
const char *nl;
};
struct option;
extern int parseopt_column_callback(const struct option *, const char *, int);
extern int git_column_config(const char *var, const char *value,
const char *command, unsigned int *colopts);
extern int finalize_colopts(unsigned int *colopts, int stdout_is_tty);
static inline int column_active(unsigned int colopts)
{
return (colopts & COL_ENABLE_MASK) == COL_ENABLED;
}
extern void print_columns(const struct string_list *list, unsigned int colopts,
const struct column_options *opts);
#endif

View file

@ -20,6 +20,7 @@ git-cherry-pick mainporcelain
git-citool mainporcelain git-citool mainporcelain
git-clean mainporcelain git-clean mainporcelain
git-clone mainporcelain common git-clone mainporcelain common
git-column purehelpers
git-commit mainporcelain common git-commit mainporcelain common
git-commit-tree plumbingmanipulators git-commit-tree plumbingmanipulators
git-config ancillarymanipulators git-config ancillarymanipulators

1
git.c
View file

@ -348,6 +348,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
{ "clone", cmd_clone }, { "clone", cmd_clone },
{ "column", cmd_column, RUN_SETUP_GENTLY },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP }, { "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "config", cmd_config, RUN_SETUP_GENTLY }, { "config", cmd_config, RUN_SETUP_GENTLY },

View file

@ -238,5 +238,7 @@ extern int parse_opt_noop_cb(const struct option *, const char *, int);
PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
#define OPT__COLOR(var, h) \ #define OPT__COLOR(var, h) \
OPT_COLOR_FLAG(0, "color", (var), (h)) OPT_COLOR_FLAG(0, "color", (var), (h))
#define OPT_COLUMN(s, l, v, h) \
{ OPTION_CALLBACK, (s), (l), (v), "style", (h), PARSE_OPT_OPTARG, parseopt_column_callback }
#endif #endif

45
t/t9002-column.sh Executable file
View file

@ -0,0 +1,45 @@
#!/bin/sh
test_description='git column'
. ./test-lib.sh
test_expect_success 'setup' '
cat >lista <<\EOF
one
two
three
four
five
six
seven
eight
nine
ten
eleven
EOF
'
test_expect_success 'never' '
git column --indent=Z --mode=never <lista >actual &&
test_cmp lista actual
'
test_expect_success 'always' '
cat >expected <<\EOF &&
Zone
Ztwo
Zthree
Zfour
Zfive
Zsix
Zseven
Zeight
Znine
Zten
Zeleven
EOF
git column --indent=Z --mode=plain <lista >actual &&
test_cmp expected actual
'
test_done