Fix building with libxml2-2.12.0
[shigofumi.git] / src / utils.c
blob3f556aa0930a7c7a8d1858e410b813c772de0eb1
1 #define _XOPEN_SOURCE 500 /* For strdup(3) */
2 #define _POSIX_C_SOURCE 200112L /* For setenv() */
3 #include <string.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <iconv.h>
7 #include <langinfo.h>
8 #include <time.h>
9 #include <wchar.h>
10 #include <errno.h>
12 #include "utils.h"
14 char *tz_orig; /* Copy of original TZ variable */
16 /* Concatenate two strings into newly allocated buffer.
17 * You must free() them, when you don't need it anymore.
18 * Any of the arguments can be NULL meaning empty string.
19 * In case of error returns NULL.
20 * Empty string is always returned as allocated empty string. */
21 _hidden char *astrcat(const char *first, const char *second) {
22 size_t first_len, second_len;
23 char *buf;
25 first_len = (first) ? strlen(first) : 0;
26 second_len = (second) ? strlen(second) : 0;
27 buf = malloc(1 + first_len + second_len);
28 if (buf) {
29 buf[0] = '\0';
30 if (first) strcpy(buf, first);
31 if (second) strcpy(buf + first_len, second);
33 return buf;
37 /* Concatenate three strings into newly allocated buffer.
38 * You must free() them, when you don't need it anymore.
39 * Any of the arguments can be NULL meaning empty string.
40 * In case of error returns NULL.
41 * Empty string is always returned as allocated empty string. */
42 _hidden char *astrcat3(const char *first, const char *second,
43 const char *third) {
44 size_t first_len, second_len, third_len;
45 char *buf, *next;
47 first_len = (first) ? strlen(first) : 0;
48 second_len = (second) ? strlen(second) : 0;
49 third_len = (third) ? strlen(third) : 0;
50 buf = malloc(1 + first_len + second_len + third_len);
51 if (buf) {
52 buf[0] = '\0';
53 next = buf;
54 if (first) {
55 strcpy(next, first);
56 next += first_len;
58 if (second) {
59 strcpy(next, second);
60 next += second_len;
62 if (third) {
63 strcpy(next, third);
66 return buf;
70 /* Print formatted string into automatically reallocated @buffer.
71 * @buffer automatically reallocated buffer. Must be &NULL or preallocated
72 * memory.
73 * @format format string as for printf(3)
74 * @ap list of variadic arguments, after call will be in undefined state
75 * @Returns number of bytes printed. In case of error, -1 and NULL @buffer*/
76 _hidden int shi_vasprintf(char **buffer, const char *format, va_list ap) {
77 va_list aq;
78 int length, new_length;
79 char *new_buffer;
81 if (!buffer || !format) {
82 if (buffer) {
83 free(*buffer);
84 *buffer = NULL;
86 return -1;
89 va_copy(aq, ap);
90 length = vsnprintf(NULL, 0, format, aq) + 1;
91 va_end(aq);
92 if (length <= 0) {
93 free(*buffer);
94 *buffer = NULL;
95 return -1;
98 new_buffer = realloc(*buffer, length);
99 if (!new_buffer) {
100 free(*buffer);
101 *buffer = NULL;
102 return -1;
104 *buffer = new_buffer;
106 new_length = vsnprintf(*buffer, length, format, ap);
107 if (new_length >= length) {
108 free(*buffer);
109 *buffer = NULL;
110 return -1;
113 return new_length;
117 /* Print formatted string into automatically reallocated @buffer.
118 * @buffer automatically reallocated buffer. Must be &NULL or preallocated
119 * memory.
120 * @format format string as for printf(3)
121 * @... variadic arguments
122 * @Returns number of bytes printed. In case of error, -1 and NULL @buffer*/
123 _hidden int shi_asprintf(char **buffer, const char *format, ...) {
124 int ret;
125 va_list ap;
126 va_start(ap, format);
127 ret = shi_vasprintf(buffer, format, ap);
128 va_end(ap);
129 return ret;
133 /* Converts UTF8 string into locale encoded string.
134 * @utf string in UTF-8 terminated by zero byte
135 * @return allocated string encoded in locale specific encoding. You must free
136 * it. In case of error or NULL @utf returns NULL. */
137 _hidden char *utf82locale(const char *utf) {
138 iconv_t state;
139 size_t utf_length;
140 char *buffer = NULL, *new_buffer;
141 size_t buffer_length = 0, buffer_used = 0;
142 char *inbuf, *outbuf;
143 size_t inleft, outleft;
145 if (!utf) return NULL;
147 /* nl_langinfo() is not thread-safe */
148 state = iconv_open(nl_langinfo(CODESET), "UTF-8");
149 if (state == (iconv_t) -1) return NULL;
151 /* Get the initial output buffer length */
152 utf_length = strlen(utf);
153 buffer_length = utf_length + 1;
155 inbuf = (char *) utf;
156 inleft = utf_length + 1;
158 while (inleft > 0) {
159 /* Extend buffer */
160 new_buffer = realloc(buffer, buffer_length);
161 if (!new_buffer) {
162 free(buffer);
163 buffer = NULL;
164 goto leave;
166 buffer = new_buffer;
168 /* FIXME */
169 outbuf = buffer + buffer_used;
170 outleft = buffer_length - buffer_used;
172 /* Convert chunk of data */
173 if ((size_t) -1 == iconv(state, &inbuf, &inleft, &outbuf, &outleft) &&
174 errno != E2BIG) {
175 free(buffer);
176 buffer = NULL;
177 goto leave;
180 /* Update positions */
181 buffer_length += 1024;
182 buffer_used = outbuf - buffer;
185 leave:
186 iconv_close(state);
187 return buffer;
191 /* Converts locale encoded string into UTF8.
192 * @locale string in locale encoded string terminated by zero byte
193 * @return allocated string encoded in UTF-8 encoding. You must free
194 * it. In case of error or NULL @locale returns NULL. */
195 _hidden char *locale2utf8(const char *locale) {
196 iconv_t state;
197 size_t locale_length;
198 char *buffer = NULL, *new_buffer;
199 size_t buffer_length = 0, buffer_used = 0;
200 char *inbuf, *outbuf;
201 size_t inleft, outleft;
203 if (!locale) return NULL;
205 /* nl_langinfo() is not thread-safe */
206 state = iconv_open("UTF-8", nl_langinfo(CODESET));
207 if (state == (iconv_t) -1) return NULL;
209 /* Get the initial output buffer length */
210 locale_length = strlen(locale);
211 buffer_length = locale_length + 1;
213 inbuf = (char *) locale;
214 inleft = locale_length + 1;
216 while (inleft > 0) {
217 /* Extend buffer */
218 new_buffer = realloc(buffer, buffer_length);
219 if (!new_buffer) {
220 free(buffer);
221 buffer = NULL;
222 goto leave;
224 buffer = new_buffer;
226 /* FIXME */
227 outbuf = buffer + buffer_used;
228 outleft = buffer_length - buffer_used;
230 /* Convert chunk of data */
231 if ((size_t) -1 == iconv(state, &inbuf, &inleft, &outbuf, &outleft) &&
232 errno != E2BIG) {
233 zfree(buffer);
234 goto leave;
237 /* Update positions */
238 buffer_length += 1024;
239 buffer_used = outbuf - buffer;
242 leave:
243 iconv_close(state);
244 return buffer;
248 /* Determine how many columns occupies given locale encoded string.
249 * Return number of columns or -1 */
250 static int localewidth(const char *string) {
251 mbstate_t mbstate;
252 wchar_t wide_char;
253 int wchar_width;
254 int offset = 0, columns = 0;
255 size_t wc_size;
256 size_t length;
258 if (!string || !*string) return 0;
260 memset(&mbstate, 0, sizeof(mbstate));
261 length = strlen(string);
263 /* Step on each multibyte character */
264 for (offset = 0; offset < length;) {
265 wc_size = mbrtowc(&wide_char, string + offset, length, &mbstate);
266 if (wc_size > 0) {
267 wchar_width = wcwidth(wide_char);
268 if (wchar_width < 0) break; /* Non-printable character? */
270 columns += wchar_width;
271 offset += wc_size;
272 } else break; /* L'\0' or incomplete multibyte character */
275 return columns;
279 /* Determine how many columns occupies given UTF-8 encoded string.
280 * Return number of columns or -1 */
281 int utf8width(const char *string) {
282 char *locale_string;
283 size_t columns = 0;
285 if (!string || !*string) return 0;
287 locale_string = utf82locale(string);
288 if (!locale_string) return -1;
290 columns = localewidth(locale_string);
292 free(locale_string);
293 return columns;
297 /* Determine how many columns occupies given number.
298 * Return number of columns or -1 */
299 int numberwidth(const size_t number) {
300 int columns;
301 char *buffer = NULL;
303 shi_asprintf(&buffer, "%zu", number);
304 if (!buffer) return -1;
306 columns = localewidth(buffer);
308 free(buffer);
309 return columns;
313 /* Print locale-encoded string occupying exactly given terminal width.
314 * @width is desired column number. If negative, do not limit the width, but
315 * fill to spaces to absolute width if string is shorter */
316 void fnprint(FILE *stream, const char *locale_string, int width) {
317 int abs_width = abs(width);
318 mbstate_t mbstate;
319 wchar_t wide_char;
320 int wchar_width;
321 int offset = 0, columns = 0;
322 size_t wc_size;
323 size_t locale_length;
325 if (!locale_string) {
326 /* FIXME: a space can occupy more than one column */
327 for (columns = 0; columns < abs_width; columns++) fprintf(stream, " ");
328 return;
331 memset(&mbstate, 0, sizeof(mbstate));
332 locale_length = strlen(locale_string);
334 /* Step on each multibyte character */
335 for (offset = 0; offset < locale_length;) {
336 wc_size = mbrtowc(&wide_char, locale_string + offset, locale_length,
337 &mbstate);
338 if (wc_size > 0) {
339 wchar_width = wcwidth(wide_char);
340 if (wchar_width < 0) break; /* Non-printable character? */
342 if (columns + wchar_width > abs_width)
343 /* This character overflows desired display width */
344 break;
346 columns += wchar_width;
347 offset += wc_size;
348 } else break; /* L'\0' or incomplete multibyte character */
351 /* Print fitting prefix of locale_string */
352 if (width >= 0)
353 fprintf(stream, "%.*s", offset, locale_string);
354 else
355 fprintf(stream, "%s", locale_string);
357 /* And pad to width spaces */
358 /* FIXME: a space can occupy more than one column */
359 while (columns < abs_width) {
360 fprintf(stream, " ");
361 columns++;
366 /* Print locale-encoded string occupying at lest given terminal width.
367 * If the string is shorter, it will pad the string with spaces.
368 * If the string is wider, it will move cursor to next line on column with
369 * given width. */
370 void fhprint(FILE *stream, const char *locale_string, size_t width) {
371 mbstate_t mbstate;
372 wchar_t wide_char;
373 int wchar_width;
374 size_t offset = 0, columns = 0;
375 size_t wc_size;
376 size_t locale_length;
378 if (!locale_string) return;
380 memset(&mbstate, 0, sizeof(mbstate));
381 locale_length = strlen(locale_string);
383 /* Step on each multibyte character */
384 for (offset = 0; offset < locale_length;) {
385 wc_size = mbrtowc(&wide_char, locale_string + offset, locale_length,
386 &mbstate);
387 if (wc_size > 0) {
388 wchar_width = wcwidth(wide_char);
389 if (wchar_width < 0) break; /* Non-printable character? */
391 if (columns + wchar_width > width)
392 /* This character overflows desired display width */
393 break;
395 columns += wchar_width;
396 offset += wc_size;
397 } else break; /* L'\0' or incomplete multibyte character */
400 /* Print fitting prefix of locale_string */
401 fprintf(stream, "%s", locale_string);
403 /* FIXME: a space can occupy more than one column */
404 if (columns < width) {
405 /* And pad with spaces to width */
406 while (columns < width) {
407 fprintf(stream, " ");
408 columns++;
410 } else {
411 /* Shift to next line */
412 fprintf(stream, "\n");
413 for (columns = 0; columns < width; columns++) {
414 fprintf(stream, " ");
420 /* Switches time zone to UTC.
421 * XXX: This is not reentrant and not thread-safe */
422 _hidden void switch_tz_to_utc(void) {
423 char *tz;
425 tz = getenv("TZ");
426 if (tz) {
427 tz_orig = strdup(tz);
428 if (!tz_orig)
429 PANIC("Can not back original time zone up");
430 } else {
431 tz_orig = NULL;
434 if (setenv("TZ", "", 1))
435 PANIC("Can not change time zone to UTC temporarily");
437 tzset();
441 /* Switches time zone to original value.
442 * XXX: This is not reentrant and not thread-safe */
443 _hidden void switch_tz_to_native(void) {
444 if (tz_orig) {
445 if (setenv("TZ", tz_orig, 1))
446 PANIC("Can not restore time zone by setting TZ variable");
447 free(tz_orig);
448 tz_orig = NULL;
449 } else {
450 if(unsetenv("TZ"))
451 PANIC("Can not restore time zone by unsetting TZ variable");
453 tzset();