From bbb4b8f4496d178d0729c32fd224d4192a0b4ebc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 6 Mar 2008 01:37:16 +0100 Subject: [PATCH] Add strbuf_vaddf(), use it in strbuf_addf(), and add strbuf_initf() The most common use of addf() was to init a strbuf and addf() right away. Since it is so common, it makes sense to have a function strbuf_initf() to wrap both calls into one. To do that, we implement a (really minimal) vaddf() lookalike to vsprintf(). At the moment, it only handles %u, %i, %d, %l, %o, %x and %X with size indicators '', ' ' and '0', as well as %c and %s, the latter with size indicators '.*' and '-*' in addition to the same size indicators as for numbers. Signed-off-by: Johannes Schindelin --- .gitignore | 1 + Makefile | 3 ++ strbuf.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++++------ strbuf.h | 5 +- t/t0000-basic.sh | 8 +++ test-strbuf.c | 17 ++++++ 6 files changed, 176 insertions(+), 17 deletions(-) create mode 100644 test-strbuf.c diff --git a/.gitignore b/.gitignore index 8df8f88bea..bdfe1be8da 100644 --- a/.gitignore +++ b/.gitignore @@ -169,6 +169,7 @@ /test-run-command /test-sha1 /test-sigchain +/test-strbuf /common-cmds.h *.tar.gz *.dsc diff --git a/Makefile b/Makefile index 0498628dc8..174aeb5d2a 100644 --- a/Makefile +++ b/Makefile @@ -1728,6 +1728,7 @@ TEST_PROGRAMS_NEED_X += test-path-utils TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sigchain +TEST_PROGRAMS_NEED_X += test-strbuf TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@ -1762,6 +1763,8 @@ test-parse-options$X: parse-options.o test-parse-options.o: parse-options.h +test-strbuf$X: strbuf.o + .PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) test-%$X: test-%.o $(GITLIBS) diff --git a/strbuf.c b/strbuf.c index bc3a0802ea..c95dd54c75 100644 --- a/strbuf.c +++ b/strbuf.c @@ -192,28 +192,155 @@ void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len) strbuf_setlen(sb, sb->len + len); } +static int number_length(unsigned long number, long base) +{ + int length = 1; + while (number >= base) { + number /= base; + length++; + } + return length; +} + +/* + * Only supports %u, %i, %d, %l, %o, %x and %X with size indicators + * '', '0', '-' and ' ', + * as well as %c, + * and %s with size indicators '', ' ' and '.*'. + */ +void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap) +{ + while (*fmt) { + char fill = '\0'; + int size = -1, max_size = -1, left_align = 0; + char *p = (char *)fmt; + + if (*p != '%') { + p = strchrnul(fmt, '%'); + strbuf_add(sb, fmt, p - fmt); + fmt = p; + continue; + } + + if (*(++p) == '%') { + strbuf_addch(sb, *p++); + fmt = p; + continue; + } + if (*p == '-') { + p++; + left_align = 1; + } + if (*p == ' ' || *p == '0') + fill = *p++; + if (isdigit(*p)) + size = (int)strtol(p, &p, 10); + else if (*p == '.' && p[1] == '*') { + max_size = va_arg(ap, int); + p += 2; + } + else if (*p == '*') { + if (!fill) + fill = ' '; + size = va_arg(ap, int); + p++; + } + + switch (*p) { + case 's': { + const char *s = va_arg(ap, const char *); + if (fill && !left_align) { + int len = size - strlen(s); + while (len-- > 0) + strbuf_addch(sb, fill); + } + while (*s && max_size--) + strbuf_addch(sb, *s++); + if (fill && left_align) { + int len = size - strlen(s); + while (len-- > 0) + strbuf_addch(sb, ' '); + } + break; + } + case 'c': + strbuf_addch(sb, va_arg(ap, int)); + break; + case 'u': + case 'i': + case 'l': + case 'd': + case 'o': + case 'x': + case 'X': { + int base = *p == 'x' || *p == 'X' ? 16 : + *p == 'o' ? 8 : 10; + int negative = 0, len; + unsigned long number, power; + + if (*p == 'u') + number = va_arg(ap, unsigned int); + else { + long signed_number; + if (*p == 'l') + signed_number = va_arg(ap, long); + else + signed_number = va_arg(ap, int); + if (signed_number < 0) { + negative = 1; + number = -signed_number; + } else + number = signed_number; + } + + /* pad */ + len = number_length(number, base); + while (size-- > len + negative) + strbuf_addch(sb, fill ? fill : ' '); + if (negative) + strbuf_addch(sb, '-'); + + /* output number */ + power = 1; + while (len-- > 1) + power *= base; + while (power) { + int digit = number / power; + strbuf_addch(sb, digit < 10 ? '0' + digit + : *p + 'A' - 'X' + digit - 10); + number -= digit * power; + power /= base; + } + + break; + } + case '\0': + break; + default: + /* unknown / invalid format: copy verbatim */ + strbuf_insert(sb, sb->len, fmt, p - fmt + 1); + } + fmt = p + (*p != '\0'); + } +} + void strbuf_addf(struct strbuf *sb, const char *fmt, ...) { - int len; va_list ap; - if (!strbuf_avail(sb)) - strbuf_grow(sb, 64); va_start(ap, fmt); - len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); + strbuf_vaddf(sb, fmt, ap); + va_end(ap); +} + +void strbuf_initf(struct strbuf *sb, const char *fmt, ...) +{ + va_list ap; + + strbuf_init(sb, strlen(fmt) + 64); + va_start(ap, fmt); + strbuf_vaddf(sb, fmt, ap); va_end(ap); - if (len < 0) - die("your vsnprintf is broken"); - if (len > strbuf_avail(sb)) { - strbuf_grow(sb, len); - va_start(ap, fmt); - len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); - va_end(ap); - if (len > strbuf_avail(sb)) { - die("this should not happen, your snprintf is broken"); - } - } - strbuf_setlen(sb, sb->len + len); } void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, diff --git a/strbuf.h b/strbuf.h index fac2dbc24f..20e9453733 100644 --- a/strbuf.h +++ b/strbuf.h @@ -118,8 +118,11 @@ struct strbuf_expand_dict_entry { extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); -__attribute__((format (printf,2,3))) +extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list args); +__attribute__((format(printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); +__attribute__((format(printf,2,3))) +extern void strbuf_initf(struct strbuf *sb, const char *fmt, ...); extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); /* XXX: if read fails, any partial read is undone */ diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index f4ca4fc85c..a1e04189cc 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -345,6 +345,14 @@ test_expect_success SYMLINKS 'absolute path works as expected' ' test "$sym" = "$(test-path-utils make_absolute_path "$dir2/syml")" ' +test_expect_success 'strbuf_initf() works as expected' ' + + eval $(test-strbuf) && + test ! -z "$result" && + test "$result" = "$expect" + +' + test_expect_success 'very long name in the index handled sanely' ' a=a && # 1 diff --git a/test-strbuf.c b/test-strbuf.c new file mode 100644 index 0000000000..479fa08f20 --- /dev/null +++ b/test-strbuf.c @@ -0,0 +1,17 @@ +#include "cache.h" +#include "strbuf.h" + +int main(int argc, char **argv) +{ + struct strbuf buf; +#define TEST_FORMAT \ + "'%%%.*s,%x,%05X,%u,%i,% 4d,%3d,%c,%3d'", \ + 5, "Hello, World!", 27, 27, -1, -1, 1, 5, ':', 1234 + + strbuf_initf(&buf, TEST_FORMAT); + printf("result=%s\n", buf.buf); + printf("expect=" TEST_FORMAT); + strbuf_release(&buf); + + return 0; +} -- 2.11.4.GIT