Introduce (and start using) functions to autoexpand a strbuf
[clinfo] / src / strbuf.h
1 /* multi-purpose string _strbuf, will be initialized to be
2  * at least 1024 bytes long.
3  */
4
5 #ifndef STRBUF_H
6 #define STRBUF_H
7
8 #include <ctype.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include "memory.h"
14 #include "fmtmacros.h"
15
16 struct _strbuf
17 {
18         char *buf;
19         size_t sz; /* allocated size */
20         size_t end; /* offset to terminating null byte */
21 };
22
23 static inline void realloc_strbuf(struct _strbuf *str, size_t nusz, const char* what)
24 {
25         if (nusz > str->sz) {
26                 REALLOC(str->buf, nusz, what);
27                 str->sz = nusz;
28         }
29 }
30
31 static inline void reset_strbuf(struct _strbuf *str)
32 {
33         str->end = 0;
34         if (str->buf) str->buf[0] = '\0';
35 }
36
37 static inline void init_strbuf(struct _strbuf *str, const char *what)
38 {
39         str->end = 0;
40         str->sz = 0;
41         str->buf = NULL;
42         realloc_strbuf(str, 1024, what);
43 }
44
45 static inline void free_strbuf(struct _strbuf *str)
46 {
47         free(str->buf);
48         str->buf = NULL;
49         reset_strbuf(str);
50 }
51
52 static inline void strbuf_append(const char *what, struct _strbuf *str, const char *fmt, ...)
53 {
54         va_list ap;
55         size_t room = str->sz - str->end - 1;
56         size_t written = 0;
57
58         /* write if we have room */
59         va_start(ap, fmt);
60         written = vsnprintf(str->buf + str->end, room, fmt, ap);
61         va_end(ap);
62
63         /* if we would have written more, we need to expand the storage */
64         if (written >= room) {
65                 realloc_strbuf(str, str->end + written + 1, what);
66                 room = str->sz - str->end;
67
68                 /* and re-write */
69                 va_start(ap, fmt);
70                 written = vsnprintf(str->buf + str->end, room, fmt, ap);
71                 va_end(ap);
72         }
73         str->end += written;
74 }
75
76 static inline void strbuf_append_str_len(const char *what, struct _strbuf *str,
77         const char *to_append, /* string to append */
78         size_t len) /* length of string to append */
79 {
80         size_t room = str->sz - str->end - 1;
81
82         if (len >= room) {
83                 realloc_strbuf(str, str->end + len + 1, what);
84         }
85         /* copy up to the terminating NULL */
86         memcpy(str->buf + str->end, to_append, len + 1);
87         str->end += len;
88 }
89
90 static inline void strbuf_append_str(const char *what, struct _strbuf *str, const char *to_append)
91 {
92         strbuf_append_str_len(what, str, to_append, strlen(to_append));
93 }
94
95 #define strbuf_printf(str, ...) snprintf((str)->buf, (str)->sz, __VA_ARGS__)
96
97 #define GET_STRING(str, err, cmd, param, param_str, ...) do { \
98         size_t nusz; \
99         err = cmd(__VA_ARGS__, param, 0, NULL, &nusz); \
100         if (REPORT_ERROR(str, err, "get " param_str " size")) break; \
101         realloc_strbuf(str, nusz, #param); \
102         err = cmd(__VA_ARGS__, param, (str)->sz, (str)->buf, NULL); \
103         if (REPORT_ERROR(str, err, "get " param_str)) break; \
104         (str)->end = nusz; \
105 } while (0)
106
107 #define GET_STRING_LOC(ret, loc, cmd, ...) do { \
108         size_t nusz; \
109         ret->err = REPORT_ERROR_LOC(ret, \
110                 cmd(__VA_ARGS__, 0, NULL, &nusz), \
111                 loc, "get %s size"); \
112         if (!ret->err) { \
113                 realloc_strbuf(&ret->str, nusz, loc->sname); \
114                 ret->err = REPORT_ERROR_LOC(ret, \
115                         cmd(__VA_ARGS__, ret->str.sz, ret->str.buf, NULL), \
116                         loc, "get %s"); \
117         } \
118         if (!ret->err) { \
119                 ret->str.end = nusz; \
120         } \
121 } while (0)
122
123 /* Skip leading whitespace in a string */
124 static inline const char* skip_leading_ws(const char *str)
125 {
126         const char *ret = str;
127         while (isspace(*ret)) ++ret;
128         return ret;
129 }
130
131 /* replace last 3 chars in _strbuf with ... */
132 static const char ellip[] = "...";
133
134 static inline void trunc_strbuf(struct _strbuf *str)
135 {
136         memcpy(str->buf + str->sz - 4, ellip, 4);
137 }
138
139 /* copy a string to _strbuf, at the given offset,
140  * returning the amount of bytes written (excluding the
141  * closing NULL byte)
142  */
143 static inline size_t bufcpy_len(struct _strbuf *str,
144         size_t offset, const char *src, size_t len)
145 {
146         size_t maxlen = str->sz - offset - 1;
147         char *dst = str->buf + offset;
148         int trunc = 0;
149         if (str->sz < offset) {
150                 fprintf(stderr, "bufcpy overflow copying %s at offset %" PRIuS "/%" PRIuS " (%s)\n",
151                         src, offset, str->sz, str->buf);
152                 maxlen = 0;
153                 trunc = 1;
154         }
155         if (len > maxlen) {
156                 len = maxlen;
157                 trunc = 1;
158                 /* TODO enlarge str->buf instead, if maxlen > 0 */
159         }
160         memcpy(dst, src, len);
161         offset += len;
162         if (trunc)
163                 trunc_strbuf(str);
164         else
165                 str->buf[offset] = '\0';
166         return len;
167 }
168
169 /* As above, auto-compute string length */
170 static inline size_t bufcpy(struct _strbuf *str, size_t offset, const char *src)
171 {
172         return bufcpy_len(str, offset, src, strlen(src));
173 }
174
175
176 /* Separators: we want to be able to prepend separators as needed to _strbuf,
177  * which we do only if halfway through the buffer. The callers should first
178  * call a 'set_separator' and then use add_separator(&offset) to add it, where szval
179  * is an offset inside the buffer, which will be incremented as needed
180  */
181
182 const char *sep;
183 size_t sepsz;
184
185 void set_separator(const char* _sep)
186 {
187         sep = _sep;
188         sepsz = strlen(sep);
189 }
190
191 /* Note that no overflow check is done: it is assumed that _strbuf will have enough room */
192 void add_separator(struct _strbuf *str, size_t *offset)
193 {
194         if (*offset)
195                 *offset += bufcpy_len(str, *offset, sep, sepsz);
196 }
197
198 #endif