Add strbuf API
authorMichael Spang <mspang@csclub.uwaterloo.ca>
Sat, 31 Jan 2009 05:56:52 +0000 (00:56 -0500)
committerMichael Spang <mspang@csclub.uwaterloo.ca>
Sat, 31 Jan 2009 06:57:04 +0000 (01:57 -0500)
Shamelessly stolen from Git.

src/strbuf.c [new file with mode: 0644]
src/strbuf.h [new file with mode: 0644]

diff --git a/src/strbuf.c b/src/strbuf.c
new file mode 100644 (file)
index 0000000..fff3a48
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ Copyright (c) 2005-2008 Junio C. Hamano et al.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License (version 2)
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "strbuf.h"
+#include "util.h"
+
+#define die fatal
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/*
+ *  * Realloc the buffer pointed at by variable 'x' so that it can hold
+ *   * at least 'nr' entries; the number of entries currently allocated
+ *    * is 'alloc', using the standard growing factor alloc_nr() macro.
+ *     *
+ *      * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ *       */
+#define ALLOC_GROW(x, nr, alloc) \
+        do { \
+                    if ((nr) > alloc) { \
+                                    if (alloc_nr(alloc) < (nr)) \
+                                        alloc = (nr); \
+                                    else \
+                                        alloc = alloc_nr(alloc); \
+                                    x = xrealloc((x), alloc * sizeof(*(x))); \
+                                } \
+                } while(0)
+
+
+static inline char *gitstrchrnul(const char *s, int c)
+{
+        while (*s && *s != c)
+                    s++;
+            return (char *)s;
+}
+
+
+int prefixcmp(const char *str, const char *prefix)
+{
+       for (; ; str++, prefix++)
+               if (!*prefix)
+                       return 0;
+               else if (*str != *prefix)
+                       return (unsigned char)*prefix - (unsigned char)*str;
+}
+
+/*
+ * Used as the default ->buf value, so that people can always assume
+ * buf is non NULL and ->buf is NUL terminated even for a freshly
+ * initialized strbuf.
+ */
+char strbuf_slopbuf[1];
+
+void strbuf_init(struct strbuf *sb, size_t hint)
+{
+       sb->alloc = sb->len = 0;
+       sb->buf = strbuf_slopbuf;
+       if (hint)
+               strbuf_grow(sb, hint);
+}
+
+void strbuf_release(struct strbuf *sb)
+{
+       if (sb->alloc) {
+               free(sb->buf);
+               strbuf_init(sb, 0);
+       }
+}
+
+char *strbuf_detach(struct strbuf *sb, size_t *sz)
+{
+       char *res = sb->alloc ? sb->buf : NULL;
+       if (sz)
+               *sz = sb->len;
+       strbuf_init(sb, 0);
+       return res;
+}
+
+void strbuf_attach(struct strbuf *sb, void *buf, size_t len, size_t alloc)
+{
+       strbuf_release(sb);
+       sb->buf   = buf;
+       sb->len   = len;
+       sb->alloc = alloc;
+       strbuf_grow(sb, 0);
+       sb->buf[sb->len] = '\0';
+}
+
+void strbuf_grow(struct strbuf *sb, size_t extra)
+{
+       if (sb->len + extra + 1 <= sb->len)
+               die("you want to use way too much memory");
+       if (!sb->alloc)
+               sb->buf = NULL;
+       ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+}
+
+void strbuf_trim(struct strbuf *sb)
+{
+       char *b = sb->buf;
+       while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
+               sb->len--;
+       while (sb->len > 0 && isspace(*b)) {
+               b++;
+               sb->len--;
+       }
+       memmove(sb->buf, b, sb->len);
+       sb->buf[sb->len] = '\0';
+}
+void strbuf_rtrim(struct strbuf *sb)
+{
+       while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
+               sb->len--;
+       sb->buf[sb->len] = '\0';
+}
+
+void strbuf_ltrim(struct strbuf *sb)
+{
+       char *b = sb->buf;
+       while (sb->len > 0 && isspace(*b)) {
+               b++;
+               sb->len--;
+       }
+       memmove(sb->buf, b, sb->len);
+       sb->buf[sb->len] = '\0';
+}
+
+void strbuf_tolower(struct strbuf *sb)
+{
+       int i;
+       for (i = 0; i < sb->len; i++)
+               sb->buf[i] = tolower(sb->buf[i]);
+}
+
+struct strbuf **strbuf_split(const struct strbuf *sb, int delim)
+{
+       int alloc = 2, pos = 0;
+       char *n, *p;
+       struct strbuf **ret;
+       struct strbuf *t;
+
+       ret = xcalloc(alloc, sizeof(struct strbuf *));
+       p = n = sb->buf;
+       while (n < sb->buf + sb->len) {
+               int len;
+               n = memchr(n, delim, sb->len - (n - sb->buf));
+               if (pos + 1 >= alloc) {
+                       alloc = alloc * 2;
+                       ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
+               }
+               if (!n)
+                       n = sb->buf + sb->len - 1;
+               len = n - p + 1;
+               t = xmalloc(sizeof(struct strbuf));
+               strbuf_init(t, len);
+               strbuf_add(t, p, len);
+               ret[pos] = t;
+               ret[++pos] = NULL;
+               p = ++n;
+       }
+       return ret;
+}
+
+struct strbuf **strbuf_splitws(const struct strbuf *sb)
+{
+       int alloc = 2, pos = 0;
+       struct strbuf **ret;
+        int prev_ws = 1, ws;
+        int i, start = 0;
+
+       ret = xcalloc(alloc, sizeof(struct strbuf *));
+
+        for (i = 0; i <= sb->len; i++) {
+            ws = !sb->buf[i] || isspace(sb->buf[i]);
+            if (prev_ws == ws)
+                continue;
+            if (!ws) {
+                start = i;
+            } else {
+               if (pos + 1 >= alloc) {
+                    alloc = alloc * 2;
+                    ret = xrealloc(ret, sizeof(struct strbuf *) * alloc);
+               }
+                ret[pos] = xmalloc(sizeof(struct strbuf));
+                strbuf_init(ret[pos], i - start);
+                strbuf_add(ret[pos], &sb->buf[start], i - start);
+                pos++;
+            }
+            prev_ws = ws;
+        }
+
+        ret[pos] = NULL;
+       return ret;
+}
+
+void strbuf_list_free(struct strbuf **sbs)
+{
+       struct strbuf **s = sbs;
+
+       while (*s) {
+               strbuf_release(*s);
+               free(*s++);
+       }
+       free(sbs);
+}
+
+size_t strbuf_list_len(struct strbuf **sbs)
+{
+    size_t n = 0;
+    while (*sbs++)
+        n++;
+    return n;
+}
+
+int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
+{
+       int cmp;
+       if (a->len < b->len) {
+               cmp = memcmp(a->buf, b->buf, a->len);
+               return cmp ? cmp : -1;
+       } else {
+               cmp = memcmp(a->buf, b->buf, b->len);
+               return cmp ? cmp : a->len != b->len;
+       }
+}
+
+void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
+                                  const void *data, size_t dlen)
+{
+       if (pos + len < pos)
+               die("you want to use way too much memory");
+       if (pos > sb->len)
+               die("`pos' is too far after the end of the buffer");
+       if (pos + len > sb->len)
+               die("`pos + len' is too far after the end of the buffer");
+
+       if (dlen >= len)
+               strbuf_grow(sb, dlen - len);
+       memmove(sb->buf + pos + dlen,
+                       sb->buf + pos + len,
+                       sb->len - pos - len);
+       memcpy(sb->buf + pos, data, dlen);
+       strbuf_setlen(sb, sb->len + dlen - len);
+}
+
+void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len)
+{
+       strbuf_splice(sb, pos, 0, data, len);
+}
+
+void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
+{
+       strbuf_splice(sb, pos, len, NULL, 0);
+}
+
+void strbuf_add(struct strbuf *sb, const void *data, size_t len)
+{
+       strbuf_grow(sb, len);
+       memcpy(sb->buf + sb->len, data, len);
+       strbuf_setlen(sb, sb->len + len);
+}
+
+void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)
+{
+       strbuf_grow(sb, len);
+       memcpy(sb->buf + sb->len, sb->buf + pos, len);
+       strbuf_setlen(sb, sb->len + len);
+}
+
+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);
+       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,
+                  void *context)
+{
+       for (;;) {
+               const char *percent;
+               size_t consumed;
+
+               percent = gitstrchrnul(format, '%');
+               strbuf_add(sb, format, percent - format);
+               if (!*percent)
+                       break;
+               format = percent + 1;
+
+               consumed = fn(sb, format, context);
+               if (consumed)
+                       format += consumed;
+               else
+                       strbuf_addch(sb, '%');
+       }
+}
+
+size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,
+               void *context)
+{
+       struct strbuf_expand_dict_entry *e = context;
+       size_t len;
+
+       for (; e->placeholder && (len = strlen(e->placeholder)); e++) {
+               if (!strncmp(placeholder, e->placeholder, len)) {
+                       if (e->value)
+                               strbuf_addstr(sb, e->value);
+                       return len;
+               }
+       }
+       return 0;
+}
+
+size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f)
+{
+       size_t res;
+
+       strbuf_grow(sb, size);
+       res = fread(sb->buf + sb->len, 1, size, f);
+       if (res > 0) {
+               strbuf_setlen(sb, sb->len + res);
+       }
+       return res;
+}
+
+ssize_t strbuf_write(struct strbuf *sb, int fd) {
+    ssize_t total = 0;
+
+    while (total < sb->len) {
+        ssize_t cnt = write(fd, sb->buf + total, sb->len - total);
+        if (cnt <= 0)
+            return -1;
+        total += cnt;
+    }
+
+    return total;
+}
+
+ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint)
+{
+       size_t oldlen = sb->len;
+
+       strbuf_grow(sb, hint ? hint : 8192);
+       for (;;) {
+               ssize_t cnt;
+
+               cnt = read(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
+               if (cnt < 0) {
+                       strbuf_setlen(sb, oldlen);
+                       return -1;
+               }
+               if (!cnt)
+                       break;
+               sb->len += cnt;
+               strbuf_grow(sb, 8192);
+       }
+
+       sb->buf[sb->len] = '\0';
+       return sb->len - oldlen;
+}
+
+int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
+{
+       int ch;
+
+       strbuf_grow(sb, 0);
+       if (feof(fp))
+               return EOF;
+
+       strbuf_reset(sb);
+       while ((ch = fgetc(fp)) != EOF) {
+               if (ch == term)
+                       break;
+               strbuf_grow(sb, 1);
+               sb->buf[sb->len++] = ch;
+       }
+       if (ch == EOF && sb->len == 0)
+               return EOF;
+
+       sb->buf[sb->len] = '\0';
+       return 0;
+}
+
+int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
+{
+       int fd, len;
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       len = strbuf_read(sb, fd, hint);
+       close(fd);
+       if (len < 0)
+               return -1;
+
+       return len;
+}
diff --git a/src/strbuf.h b/src/strbuf.h
new file mode 100644 (file)
index 0000000..fe9f528
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ Copyright (c) 2005-2008 Junio C. Hamano et al.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License (version 2)
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef STRBUF_H
+#define STRBUF_H
+
+/*
+ * Strbuf's can be use in many ways: as a byte array, or to store arbitrary
+ * long, overflow safe strings.
+ *
+ * Strbufs has some invariants that are very important to keep in mind:
+ *
+ * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
+ *    build complex strings/buffers whose final size isn't easily known.
+ *
+ *    It is NOT legal to copy the ->buf pointer away.
+ *    `strbuf_detach' is the operation that detachs a buffer from its shell
+ *    while keeping the shell valid wrt its invariants.
+ *
+ * 2. the ->buf member is a byte array that has at least ->len + 1 bytes
+ *    allocated. The extra byte is used to store a '\0', allowing the ->buf
+ *    member to be a valid C-string. Every strbuf function ensure this
+ *    invariant is preserved.
+ *
+ *    Note that it is OK to "play" with the buffer directly if you work it
+ *    that way:
+ *
+ *    strbuf_grow(sb, SOME_SIZE);
+ *       ... Here, the memory array starting at sb->buf, and of length
+ *       ... strbuf_avail(sb) is all yours, and you are sure that
+ *       ... strbuf_avail(sb) is at least SOME_SIZE.
+ *    strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
+ *
+ *    Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
+ *
+ *    Doing so is safe, though if it has to be done in many places, adding the
+ *    missing API to the strbuf module is the way to go.
+ *
+ *    XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
+ *         even if it's true in the current implementation. Alloc is somehow a
+ *         "private" member that should not be messed with.
+ */
+
+#include <assert.h>
+#include <sys/types.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+extern char strbuf_slopbuf[];
+struct strbuf {
+       size_t alloc;
+       size_t len;
+       char *buf;
+};
+
+#define STRBUF_INIT  { 0, 0, strbuf_slopbuf }
+
+/*----- strbuf life cycle -----*/
+extern void strbuf_init(struct strbuf *, size_t);
+extern void strbuf_release(struct strbuf *);
+extern char *strbuf_detach(struct strbuf *, size_t *);
+extern void strbuf_attach(struct strbuf *, void *, size_t, size_t);
+static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) {
+       struct strbuf tmp = *a;
+       *a = *b;
+       *b = tmp;
+}
+
+/*----- strbuf size related -----*/
+static inline size_t strbuf_avail(const struct strbuf *sb) {
+       return sb->alloc ? sb->alloc - sb->len - 1 : 0;
+}
+
+extern void strbuf_grow(struct strbuf *, size_t);
+
+static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
+       if (!sb->alloc)
+               strbuf_grow(sb, 0);
+       assert(len < sb->alloc);
+       sb->len = len;
+       sb->buf[len] = '\0';
+}
+#define strbuf_reset(sb)  strbuf_setlen(sb, 0)
+
+/*----- content related -----*/
+extern void strbuf_trim(struct strbuf *);
+extern void strbuf_rtrim(struct strbuf *);
+extern void strbuf_ltrim(struct strbuf *);
+extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
+extern void strbuf_tolower(struct strbuf *);
+
+extern struct strbuf **strbuf_split(const struct strbuf *, int delim);
+extern struct strbuf **strbuf_splitws(const struct strbuf *);
+extern void strbuf_list_free(struct strbuf **);
+extern size_t strbuf_list_len(struct strbuf **);
+
+/*----- add data in your buffer -----*/
+static inline void strbuf_addch(struct strbuf *sb, int c) {
+       strbuf_grow(sb, 1);
+       sb->buf[sb->len++] = c;
+       sb->buf[sb->len] = '\0';
+}
+
+extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t);
+extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
+
+/* splice pos..pos+len with given data */
+extern void strbuf_splice(struct strbuf *, size_t pos, size_t len,
+                          const void *, size_t);
+
+extern void strbuf_add(struct strbuf *, const void *, size_t);
+static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
+       strbuf_add(sb, s, strlen(s));
+}
+static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) {
+       strbuf_add(sb, sb2->buf, sb2->len);
+}
+extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len);
+
+typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context);
+extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context);
+struct strbuf_expand_dict_entry {
+       const char *placeholder;
+       const char *value;
+};
+extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context);
+
+__attribute__((format(printf,2,3)))
+extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);
+
+extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);
+/* XXX: if read fails, any partial read is undone */
+extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint);
+extern ssize_t strbuf_write(struct strbuf *sb, int fd);
+extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
+
+extern int strbuf_getline(struct strbuf *, FILE *, int);
+
+extern void stripspace(struct strbuf *buf, int skip_comments);
+extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
+
+#endif /* STRBUF_H */