pretty: support padding placeholders, %< %> and %><

Either %<, %> or %>< standing before a placeholder specifies how many columns (at least as the placeholder can exceed it) it takes. Each differs on how spaces are padded: %< pads on the right (aka left alignment) %> pads on the left (aka right alignment) %>< pads both ways equally (aka centered) The (<N>) follows them, e.g. `%<(100)', to specify the number of columns the next placeholder takes. However, if '|' stands before (<N>), e.g. `%>|(100)', then the number of columns is calculated so that it reaches the Nth column on screen. 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 19, 2013 at 09:08 UTC a57523428b4c08795e793356e12afc3de812f84a
3 files changed +246 -1
Documentation/pretty-formats.txt
+8
@@ -164,6 +164,14 @@ The placeholders are:
164 - '%x00': print a byte from a hex code
165 - '%w([<w>[,<i1>[,<i2>]]])': switch line wrapping, like the -w option of
166 linkgit:git-shortlog[1].
167 +- '%<(<N>)': make the next placeholder take at least N columns,
168 + padding spaces on the right if necessary
169 +- '%<|(<N>)': make the next placeholder take at least until Nth
170 + columns, padding spaces on the right if necessary
171 +- '%>(<N>)', '%>|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
172 + respectively, but padding spaces on the left
173 +- '%><(<N>)', '%><|(<N>)': similar to '%<(<N>)', '%<|(<N>)'
174 + respectively, but padding both sides (i.e. the text is centered)
175
176 NOTE: Some placeholders may depend on other options given to the
177 revision traversal engine. For example, the `%g*` reflog options will
pretty.c
+116 -1
@@ -769,16 +769,25 @@ struct chunk {
769 size_t len;
770 };
771
772 +enum flush_type {
773 + no_flush,
774 + flush_right,
775 + flush_left,
776 + flush_both
777 +};
778 +
779 struct format_commit_context {
780 const struct commit *commit;
781 const struct pretty_print_context *pretty_ctx;
782 unsigned commit_header_parsed:1;
783 unsigned commit_message_parsed:1;
784 struct signature_check signature_check;
785 + enum flush_type flush_type;
786 char *message;
787 char *commit_encoding;
788 size_t width, indent1, indent2;
789 int auto_color;
790 + int padding;
791
792 /* These offsets are relative to the start of the commit message. */
793 struct chunk author;
@@ -993,6 +1002,52 @@ static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
1002 return 0;
1003 }
1004
1005 +static size_t parse_padding_placeholder(struct strbuf *sb,
1006 + const char *placeholder,
1007 + struct format_commit_context *c)
1008 +{
1009 + const char *ch = placeholder;
1010 + enum flush_type flush_type;
1011 + int to_column = 0;
1012 +
1013 + switch (*ch++) {
1014 + case '<':
1015 + flush_type = flush_right;
1016 + break;
1017 + case '>':
1018 + if (*ch == '<') {
1019 + flush_type = flush_both;
1020 + ch++;
1021 + } else
1022 + flush_type = flush_left;
1023 + break;
1024 + default:
1025 + return 0;
1026 + }
1027 +
1028 + /* the next value means "wide enough to that column" */
1029 + if (*ch == '|') {
1030 + to_column = 1;
1031 + ch++;
1032 + }
1033 +
1034 + if (*ch == '(') {
1035 + const char *start = ch + 1;
1036 + const char *end = strchr(start, ')');
1037 + char *next;
1038 + int width;
1039 + if (!end || end == start)
1040 + return 0;
1041 + width = strtoul(start, &next, 10);
1042 + if (next == start || width == 0)
1043 + return 0;
1044 + c->padding = to_column ? -width : width;
1045 + c->flush_type = flush_type;
1046 + return end - placeholder + 1;
1047 + }
1048 + return 0;
1049 +}
1050 +
1051 static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1052 const char *placeholder,
1053 void *context)
@@ -1057,6 +1112,10 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1112 return end - placeholder + 1;
1113 } else
1114 return 0;
1115 +
1116 + case '<':
1117 + case '>':
1118 + return parse_padding_placeholder(sb, placeholder, c);
1119 }
1120
1121 /* these depend on the commit */
@@ -1221,6 +1280,59 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
1280 return 0; /* unknown placeholder */
1281 }
1282
1283 +static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
1284 + const char *placeholder,
1285 + struct format_commit_context *c)
1286 +{
1287 + struct strbuf local_sb = STRBUF_INIT;
1288 + int total_consumed = 0, len, padding = c->padding;
1289 + if (padding < 0) {
1290 + const char *start = strrchr(sb->buf, '\n');
1291 + int occupied;
1292 + if (!start)
1293 + start = sb->buf;
1294 + occupied = utf8_strnwidth(start, -1, 1);
1295 + padding = (-padding) - occupied;
1296 + }
1297 + while (1) {
1298 + int modifier = *placeholder == 'C';
1299 + int consumed = format_commit_one(&local_sb, placeholder, c);
1300 + total_consumed += consumed;
1301 +
1302 + if (!modifier)
1303 + break;
1304 +
1305 + placeholder += consumed;
1306 + if (*placeholder != '%')
1307 + break;
1308 + placeholder++;
1309 + total_consumed++;
1310 + }
1311 + len = utf8_strnwidth(local_sb.buf, -1, 1);
1312 + if (len > padding)
1313 + strbuf_addstr(sb, local_sb.buf);
1314 + else {
1315 + int sb_len = sb->len, offset = 0;
1316 + if (c->flush_type == flush_left)
1317 + offset = padding - len;
1318 + else if (c->flush_type == flush_both)
1319 + offset = (padding - len) / 2;
1320 + /*
1321 + * we calculate padding in columns, now
1322 + * convert it back to chars
1323 + */
1324 + padding = padding - len + local_sb.len;
1325 + strbuf_grow(sb, padding);
1326 + strbuf_setlen(sb, sb_len + padding);
1327 + memset(sb->buf + sb_len, ' ', sb->len - sb_len);
1328 + memcpy(sb->buf + sb_len + offset, local_sb.buf,
1329 + local_sb.len);
1330 + }
1331 + strbuf_release(&local_sb);
1332 + c->flush_type = no_flush;
1333 + return total_consumed;
1334 +}
1335 +
1336 static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
1337 const char *placeholder,
1338 void *context)
@@ -1251,7 +1363,10 @@ static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
1363 placeholder++;
1364
1365 orig_len = sb->len;
1254 - consumed = format_commit_one(sb, placeholder, context);
1366 + if (((struct format_commit_context *)context)->flush_type != no_flush)
1367 + consumed = format_and_pad_commit(sb, placeholder, context);
1368 + else
1369 + consumed = format_commit_one(sb, placeholder, context);
1370 if (magic == NO_MAGIC)
1371 return consumed;
1372
t/t4205-log-pretty-formats.sh
+122
@@ -99,4 +99,126 @@ test_expect_failure 'NUL termination with --stat' '
99 test_i18ncmp expected actual
100 '
101
102 +test_expect_success 'setup more commits' '
103 + test_commit "message one" one one message-one &&
104 + test_commit "message two" two two message-two
105 +'
106 +
107 +test_expect_success 'left alignment formatting' '
108 + git log --pretty="format:%<(40)%s" >actual &&
109 + # complete the incomplete line at the end
110 + echo >>actual &&
111 + qz_to_tab_space <<\EOF >expected &&
112 +message two Z
113 +message one Z
114 +add bar Z
115 +initial Z
116 +EOF
117 + test_cmp expected actual
118 +'
119 +
120 +test_expect_success 'left alignment formatting at the nth column' '
121 + git log --pretty="format:%h %<|(40)%s" >actual &&
122 + # complete the incomplete line at the end
123 + echo >>actual &&
124 + qz_to_tab_space <<\EOF >expected &&
125 +fa33ab1 message two Z
126 +7cd6c63 message one Z
127 +1711bf9 add bar Z
128 +af20c06 initial Z
129 +EOF
130 + test_cmp expected actual
131 +'
132 +
133 +test_expect_success 'left alignment formatting with no padding' '
134 + git log --pretty="format:%<(1)%s" >actual &&
135 + # complete the incomplete line at the end
136 + echo >>actual &&
137 + cat <<\EOF >expected &&
138 +message two
139 +message one
140 +add bar
141 +initial
142 +EOF
143 + test_cmp expected actual
144 +'
145 +
146 +test_expect_success 'right alignment formatting' '
147 + git log --pretty="format:%>(40)%s" >actual &&
148 + # complete the incomplete line at the end
149 + echo >>actual &&
150 + qz_to_tab_space <<\EOF >expected &&
151 +Z message two
152 +Z message one
153 +Z add bar
154 +Z initial
155 +EOF
156 + test_cmp expected actual
157 +'
158 +
159 +test_expect_success 'right alignment formatting at the nth column' '
160 + git log --pretty="format:%h %>|(40)%s" >actual &&
161 + # complete the incomplete line at the end
162 + echo >>actual &&
163 + qz_to_tab_space <<\EOF >expected &&
164 +fa33ab1 message two
165 +7cd6c63 message one
166 +1711bf9 add bar
167 +af20c06 initial
168 +EOF
169 + test_cmp expected actual
170 +'
171 +
172 +test_expect_success 'right alignment formatting with no padding' '
173 + git log --pretty="format:%>(1)%s" >actual &&
174 + # complete the incomplete line at the end
175 + echo >>actual &&
176 + cat <<\EOF >expected &&
177 +message two
178 +message one
179 +add bar
180 +initial
181 +EOF
182 + test_cmp expected actual
183 +'
184 +
185 +test_expect_success 'center alignment formatting' '
186 + git log --pretty="format:%><(40)%s" >actual &&
187 + # complete the incomplete line at the end
188 + echo >>actual &&
189 + qz_to_tab_space <<\EOF >expected &&
190 +Z message two Z
191 +Z message one Z
192 +Z add bar Z
193 +Z initial Z
194 +EOF
195 + test_cmp expected actual
196 +'
197 +
198 +test_expect_success 'center alignment formatting at the nth column' '
199 + git log --pretty="format:%h %><|(40)%s" >actual &&
200 + # complete the incomplete line at the end
201 + echo >>actual &&
202 + qz_to_tab_space <<\EOF >expected &&
203 +fa33ab1 message two Z
204 +7cd6c63 message one Z
205 +1711bf9 add bar Z
206 +af20c06 initial Z
207 +EOF
208 + test_cmp expected actual
209 +'
210 +
211 +test_expect_success 'center alignment formatting with no padding' '
212 + git log --pretty="format:%><(1)%s" >actual &&
213 + # complete the incomplete line at the end
214 + echo >>actual &&
215 + cat <<\EOF >expected &&
216 +message two
217 +message one
218 +add bar
219 +initial
220 +EOF
221 + test_cmp expected actual
222 +'
223 +
224 test_done