Support VERSION_REVTYPE git builds on cleanup_checkout.sh
[freeciv.git] / utility / astring.c
blobd3c9745d331f849d4afbbebe635b0b9b7ed3ff92
1 /**********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 /**********************************************************************
15 Allocated/allocatable strings
16 original author: David Pfitzner <dwp@mso.anu.edu.au>
18 A common technique is to have some memory dynamically allocated
19 (using malloc etc), to avoid compiled-in limits, but only allocate
20 enough space as initially needed, and then realloc later if/when
21 require more space. Typically, the realloc is made a bit more than
22 immediately necessary, to avoid frequent reallocs if the object
23 grows incrementally. Also, don't usually realloc at all if the
24 object shrinks. This is straightforward, but just requires a bit
25 of book-keeping to keep track of how much has been allocated etc.
26 This module provides some tools to make this a bit easier.
28 This is deliberately simple and light-weight. The user is allowed
29 full access to the struct elements rather than use accessor
30 functions etc.
32 Note one potential hazard: when the size is increased (astr_reserve()),
33 realloc (really fc_realloc) is used, which retains any data which
34 was there previously, _but_: any external pointers into the allocated
35 memory may then become wild. So you cannot safely use such external
36 pointers into the astring data, except strictly between times when
37 the astring size may be changed.
39 There are two ways of getting the resulting string as a char *:
41 - astr_str() returns a const char *. This should not be modified
42 or freed by the caller; the storage remains owned by the
43 struct astring, which should be freed with astr_free().
45 - astr_to_str() returns a char * and destroys the struct astring.
46 Responsibility for freeing the storage becomes the caller's.
48 One pattern for using astr_str() is to replace static buffers in
49 functions that return a pointer to static storage. Where previously
50 you would have had e.g. "static struct buf[128]" with an arbitrary
51 size limit, you can have "static struct astring buf", and re-use the
52 same astring on subsequent calls; the caller should behave the
53 same (only reading the string and not freeing it).
55 ***********************************************************************/
57 #ifdef HAVE_CONFIG_H
58 #include <fc_config.h>
59 #endif
61 #include "fc_prehdrs.h"
63 #include <stdarg.h>
64 #include <stdlib.h>
65 #include <string.h>
67 /* utility */
68 #include "fcintl.h"
69 #include "log.h" /* fc_assert */
70 #include "mem.h"
71 #include "support.h" /* fc_vsnprintf, fc_strlcat */
73 #include "astring.h"
75 #define str _private_str_
76 #define n _private_n_
77 #define n_alloc _private_n_alloc_
79 static const struct astring zero_astr = ASTRING_INIT;
80 static char *astr_buffer = NULL;
81 static size_t astr_buffer_alloc = 0;
83 static inline char *astr_buffer_get(size_t *alloc);
84 static inline char *astr_buffer_grow(size_t *alloc);
85 static void astr_buffer_free(void);
88 /****************************************************************************
89 Returns the astring buffer. Create it if necessary.
90 ****************************************************************************/
91 static inline char *astr_buffer_get(size_t *alloc)
93 if (!astr_buffer) {
94 astr_buffer_alloc = 65536;
95 astr_buffer = fc_malloc(astr_buffer_alloc);
96 atexit(astr_buffer_free);
99 *alloc = astr_buffer_alloc;
100 return astr_buffer;
103 /****************************************************************************
104 Grow the astring buffer.
105 ****************************************************************************/
106 static inline char *astr_buffer_grow(size_t *alloc)
108 astr_buffer_alloc *= 2;
109 astr_buffer = fc_realloc(astr_buffer, astr_buffer_alloc);
111 *alloc = astr_buffer_alloc;
112 return astr_buffer;
115 /****************************************************************************
116 Grow the astring buffer.
117 ****************************************************************************/
118 static void astr_buffer_free(void)
120 free(astr_buffer);
124 /****************************************************************************
125 Initialize the struct.
126 ****************************************************************************/
127 void astr_init(struct astring *astr)
129 *astr = zero_astr;
132 /****************************************************************************
133 Free the memory associated with astr, and return astr to same
134 state as after astr_init.
135 ****************************************************************************/
136 void astr_free(struct astring *astr)
138 if (astr->n_alloc > 0) {
139 fc_assert_ret(NULL != astr->str);
140 free(astr->str);
142 *astr = zero_astr;
145 /****************************************************************************
146 Return the raw string to the caller, and return astr to same state as
147 after astr_init().
148 Freeing the string's storage becomes the caller's responsibility.
149 ****************************************************************************/
150 char *astr_to_str(struct astring *astr)
152 char *str = astr->str;
153 *astr = zero_astr;
154 return str;
157 /****************************************************************************
158 Check that astr has enough size to hold n, and realloc to a bigger
159 size if necessary. Here n must be big enough to include the trailing
160 ascii-null if required. The requested n is stored in astr->n.
161 The actual amount allocated may be larger than n, and is stored
162 in astr->n_alloc.
163 ****************************************************************************/
164 void astr_reserve(struct astring *astr, size_t n)
166 int n1;
167 bool was_null = (astr->n == 0);
169 fc_assert_ret(NULL != astr);
171 astr->n = n;
172 if (n <= astr->n_alloc) {
173 return;
176 /* Allocated more if this is only a small increase on before: */
177 n1 = (3 * (astr->n_alloc + 10)) / 2;
178 astr->n_alloc = (n > n1) ? n : n1;
179 astr->str = (char *) fc_realloc(astr->str, astr->n_alloc);
180 if (was_null) {
181 astr_clear(astr);
185 /****************************************************************************
186 Sets the content to the empty string.
187 ****************************************************************************/
188 void astr_clear(struct astring *astr)
190 if (astr->n == 0) {
191 /* astr_reserve is really astr_size, so we don't want to reduce the
192 * size. */
193 astr_reserve(astr, 1);
195 astr->str[0] = '\0';
198 /****************************************************************************
199 Add the text to the string.
200 ****************************************************************************/
201 static void astr_vadd(struct astring *astr, size_t at,
202 const char *format, va_list ap)
204 char *buffer;
205 size_t buffer_size;
206 size_t new_len;
208 buffer = astr_buffer_get(&buffer_size);
209 for (;;) {
210 new_len = fc_vsnprintf(buffer, buffer_size, format, ap);
211 if (new_len < buffer_size && (size_t) -1 != new_len) {
212 break;
214 buffer = astr_buffer_grow(&buffer_size);
217 new_len += at + 1;
219 astr_reserve(astr, new_len);
220 fc_strlcpy(astr->str + at, buffer, astr->n_alloc - at);
223 /****************************************************************************
224 Set the text to the string.
225 ****************************************************************************/
226 void astr_set(struct astring *astr, const char *format, ...)
228 va_list args;
230 va_start(args, format);
231 astr_vadd(astr, 0, format, args);
232 va_end(args);
236 /****************************************************************************
237 Add the text to the string.
238 ****************************************************************************/
239 void astr_add(struct astring *astr, const char *format, ...)
241 va_list args;
243 va_start(args, format);
244 astr_vadd(astr, astr_len(astr), format, args);
245 va_end(args);
248 /****************************************************************************
249 Add the text to the string in a new line.
250 ****************************************************************************/
251 void astr_add_line(struct astring *astr, const char *format, ...)
253 size_t len = astr_len(astr);
254 va_list args;
256 va_start(args, format);
257 if (0 < len) {
258 astr_vadd(astr, len + 1, format, args);
259 astr->str[len] = '\n';
260 } else {
261 astr_vadd(astr, len, format, args);
263 va_end(args);
266 /****************************************************************************
267 Replace the spaces by line breaks when the line lenght is over the desired
268 one.
269 ****************************************************************************/
270 void astr_break_lines(struct astring *astr, size_t desired_len)
272 fc_break_lines(astr->str, desired_len);
275 /****************************************************************************
276 Build a localized string with the given items. Items will be
277 "or"-separated.
279 See also astr_build_and_list(), strvec_to_or_list().
280 ****************************************************************************/
281 const char *astr_build_or_list(struct astring *astr,
282 const char *const *items, size_t number)
284 fc_assert_ret_val(NULL != astr, NULL);
285 fc_assert_ret_val(0 < number, NULL);
286 fc_assert_ret_val(NULL != items, NULL);
288 if (1 == number) {
289 /* TRANS: "or"-separated string list with one single item. */
290 astr_set(astr, Q_("?or-list-single:%s"), *items);
291 } else if (2 == number) {
292 /* TRANS: "or"-separated string list with 2 items. */
293 astr_set(astr, Q_("?or-list:%s or %s"), items[0], items[1]);
294 } else {
295 /* Estimate the space we need. */
296 astr_reserve(astr, number * 64);
297 /* TRANS: start of an "or"-separated string list with more than two
298 * items. */
299 astr_set(astr, Q_("?or-list:%s"), *items++);
300 while (1 < --number) {
301 /* TRANS: next elements of an "or"-separated string list with more
302 * than two items. */
303 astr_add(astr, Q_("?or-list:, %s"), *items++);
305 /* TRANS: end of an "or"-separated string list with more than two
306 * items. */
307 astr_add(astr, Q_("?or-list:, or %s"), *items);
310 return astr->str;
313 /****************************************************************************
314 Build a localized string with the given items. Items will be
315 "and"-separated.
317 See also astr_build_or_list(), strvec_to_and_list().
318 ****************************************************************************/
319 const char *astr_build_and_list(struct astring *astr,
320 const char *const *items, size_t number)
322 fc_assert_ret_val(NULL != astr, NULL);
323 fc_assert_ret_val(0 < number, NULL);
324 fc_assert_ret_val(NULL != items, NULL);
326 if (1 == number) {
327 /* TRANS: "and"-separated string list with one single item. */
328 astr_set(astr, Q_("?and-list-single:%s"), *items);
329 } else if (2 == number) {
330 /* TRANS: "and"-separated string list with 2 items. */
331 astr_set(astr, Q_("?and-list:%s and %s"), items[0], items[1]);
332 } else {
333 /* Estimate the space we need. */
334 astr_reserve(astr, number * 64);
335 /* TRANS: start of an "and"-separated string list with more than two
336 * items. */
337 astr_set(astr, Q_("?and-list:%s"), *items++);
338 while (1 < --number) {
339 /* TRANS: next elements of an "and"-separated string list with more
340 * than two items. */
341 astr_add(astr, Q_("?and-list:, %s"), *items++);
343 /* TRANS: end of an "and"-separated string list with more than two
344 * items. */
345 astr_add(astr, Q_("?and-list:, and %s"), *items);
348 return astr->str;
351 /****************************************************************************
352 Copy one astring in another.
353 ****************************************************************************/
354 void astr_copy(struct astring *dest, const struct astring *src)
356 if (astr_empty(src)) {
357 astr_clear(dest);
358 } else {
359 astr_set(dest, "%s", src->str);