Subject: [PATCH 03/12] Add columnizer

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Sun, 7 Mar 2010 19:09:36 +0700

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 .gitignore       |    1 +
 Makefile         |    2 +
 column.c         |  164 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 column.h         |   23 ++++++++
 command-list.txt |    1 +
 5 files changed, 191 insertions(+), 0 deletions(-)
 create mode 100644 column.c
 create mode 100644 column.h

diff --git a/.gitignore b/.gitignore
index 7b3acb7..8e087e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@
 /git-cherry-pick
 /git-clean
 /git-clone
+/git-column
 /git-commit
 /git-commit-tree
 /git-config
diff --git a/Makefile b/Makefile
index 7358a20..337f5bf 100644
--- a/Makefile
+++ b/Makefile
@@ -454,6 +454,7 @@ LIB_H += builtin.h
 LIB_H += cache.h
 LIB_H += cache-tree.h
 LIB_H += color.h
+LIB_H += column.h
 LIB_H += commit.h
 LIB_H += compat/bswap.h
 LIB_H += compat/cygwin.h
@@ -526,6 +527,7 @@ LIB_OBJS += branch.o
 LIB_OBJS += bundle.o
 LIB_OBJS += cache-tree.o
 LIB_OBJS += color.o
+LIB_OBJS += column.o
 LIB_OBJS += combine-diff.o
 LIB_OBJS += commit.o
 LIB_OBJS += config.o
diff --git a/column.c b/column.c
new file mode 100644
index 0000000..0e88763
--- /dev/null
+++ b/column.c
@@ -0,0 +1,164 @@
+#include "cache.h"
+#include "column.h"
+
+static int ansi_length(const char *s)
+{
+	int a_len = 0;
+
+	while ((s = strstr(s, "\033[")) != NULL) {
+		int len = strspn(s+2, "0123456789;");
+		s += len+3; /* \033[<len><func char> */
+		a_len += len+3;
+	}
+	return a_len;
+}
+
+static void print_row(struct columnizer *cp, int row)
+{
+	int mode = cp->flags & COLUMNIZER_MODE_MASK;
+	int j;
+
+	if (cp->left_space)
+		printf("%-*s", cp->left_space, "");
+
+	for (j = 0; j < cp->nr_cols; j++) {
+		int n, size;
+
+		switch (mode) {
+		case COLUMNIZER_COLUMN_FIRST:
+			n = j * cp->nr_rows + row;
+			size = cp->column_width;
+			break;
+		case COLUMNIZER_ROW_FIRST:
+			n = row * cp->nr_cols + j;
+			size = cp->column_width;
+			break;
+		}
+
+		if (n >= cp->cells_nr)
+			break;
+
+		size += cp->space;
+		if (cp->flags & COLUMNIZER_HAVE_ANSI)
+			size += ansi_length(cp->cells[n]);
+		if (j == cp->nr_cols-1)
+			size = 1;
+		printf("%-*s", size, cp->cells[n]);
+	}
+	putchar('\n');
+}
+
+static void calculate_column_width(struct columnizer *cp)
+{
+	int max_cols, longest;
+
+	if (cp->flags & COLUMNIZER_READY)
+		return;
+
+	if (!cp->terminal_width)
+		cp->terminal_width = term_columns();
+
+	if (cp->column_width)
+		longest = cp->column_width;
+	else {
+		int i, len;
+		longest = 0;
+		for (i = 0; i < cp->cells_nr; i++) {
+			len = strlen(cp->cells[i]);
+			if (cp->flags & COLUMNIZER_HAVE_ANSI)
+				len -= ansi_length(cp->cells[i]);
+			if (longest < len)
+				longest = len;
+		}
+	}
+
+	max_cols = cp->terminal_width - 1; /* don't print *on* the edge */
+	max_cols -= cp->left_space + cp->right_space;
+
+	/* we don't have space after the last column */
+	max_cols += cp->space;
+
+	cp->nr_cols = (longest + cp->space) < max_cols ? max_cols / (longest + cp->space) : 1;
+	cp->nr_rows = DIV_ROUND_UP(cp->cells_nr, cp->nr_cols);
+	if (!cp->column_width)
+		cp->column_width = max_cols / cp->nr_cols - cp->space;
+	cp->flags |= COLUMNIZER_READY;
+}
+
+static int feed_cell(struct columnizer *cp, char *cell)
+{
+	int len;
+
+	/* Non-homogeneous column is not supported yet */
+	if (!(cp->flags & COLUMNIZER_HOMOGENEOUS))
+		return -1;
+
+	if (!cell) {		/* EOF */
+		int i;
+
+		calculate_column_width(cp);
+		for (i = 0;i < cp->nr_rows; i++)
+			print_row(cp, i);
+		return 0;
+	}
+
+	cp->cells_nr++;
+	ALLOC_GROW(cp->cells, cp->cells_nr, cp->cells_alloc);
+	cp->cells[cp->cells_nr-1] = cell;
+
+	len = strlen(cell);
+	if (cp->flags & COLUMNIZER_HAVE_ANSI)
+		len -= ansi_length(cell);
+
+	if (!cp->terminal_width)
+		cp->terminal_width = term_columns();
+	if (cp->longest < len)
+		cp->longest = len;
+
+	/* Can't have more than one column? */
+	if (cp->terminal_width < cp->left_space+cp->longest*2+cp->space+cp->right_space) {
+		int i;
+		for (i = 0;i < cp->cells_nr;i++) {
+			if (cp->left_space)
+				printf("%-*s", cp->left_space, "");
+			printf("%s\n", cp->cells[i]);
+			if (cp->detach)
+				cp->detach(cp->cells[i]);
+		}
+		free(cp->cells);
+		cp->cells_alloc = cp->cells_nr = 0;
+
+		cp->nr_cols = 1;
+		cp->flags |= COLUMNIZER_READY;
+	}
+	return 0;
+}
+
+int feed_columnizer(struct columnizer *cp, char *cell)
+{
+	int ret;
+
+	/* Degradation case, no special layout needed */
+	if (cp->flags & COLUMNIZER_READY && cp->nr_cols == 1) {
+		if (cell) {
+			if (cp->left_space)
+				printf("%-*s", cp->left_space, "");
+			printf("%s\n", cell);
+		}
+		ret = 0;
+	}
+	else
+		ret = feed_cell(cp, cell);
+
+	if (!cell) {		/* EOF, cleanup */
+		if (cp->detach) {
+			int i;
+			for (i = 0;i < cp->cells_nr;i++)
+				cp->detach(cp->cells[i]);
+		}
+		if (cp->cells_alloc)
+			free(cp->cells);
+	}
+
+	return ret;
+}
diff --git a/column.h b/column.h
new file mode 100644
index 0000000..28122e0
--- /dev/null
+++ b/column.h
@@ -0,0 +1,23 @@
+#define COLUMNIZER_MODE_MASK    0x000F
+#define COLUMNIZER_COLUMN_FIRST      0
+#define COLUMNIZER_ROW_FIRST         1
+
+#define COLUMNIZER_HOMOGENEOUS  0x0010
+#define COLUMNIZER_HAVE_ANSI    0x0020
+#define COLUMNIZER_READY        0x0040
+
+struct columnizer {
+	int flags;
+	int terminal_width, column_width;
+	int left_space, right_space, space;
+	void (*detach)(void *p);
+
+	/* private variables */
+	char **cells;
+	int cells_nr, cells_alloc;
+	int nr_cols, nr_rows;
+
+	int longest;
+};
+
+extern int feed_columnizer(struct columnizer *cp, char *cell);
diff --git a/command-list.txt b/command-list.txt
index 95bf18c..5d6bfc8 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -20,6 +20,7 @@ git-cherry-pick                         mainporcelain
 git-citool                              mainporcelain
 git-clean                               mainporcelain
 git-clone                               mainporcelain common
+git-column                              purehelpers
 git-commit                              mainporcelain common
 git-commit-tree                         plumbingmanipulators
 git-config                              ancillarymanipulators
-- 
1.7.0.1.370.gd3c5