diff options
Diffstat (limited to 'string_buf.c')
-rw-r--r-- | string_buf.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/string_buf.c b/string_buf.c new file mode 100644 index 0000000..332d023 --- /dev/null +++ b/string_buf.c @@ -0,0 +1,308 @@ +/** + * C string buffers for easy construction of complex strings + * + * Copyright (C) 2021 Wojtek Kosior + * Redistribution terms are gathered in the `copyright' file. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdarg.h> +#include <unistd.h> + +#define STRING_BUF_C +#include "string_buf.h" + +#define MINIMUM_EXTEND_BYTES 63 + +void stringbuf_init(struct stringbuf *sb) +{ + sb->buf = NULL; + sb->buf_len = 0; + sb->buf_filled = 0; +} + +void stringbuf_destroy(struct stringbuf *sb) +{ + free(sb->buf); +} + +void stringbuf_truncate(struct stringbuf *sb, size_t len) +{ + if (sb->buf_len <= len) + return; + + sb->buf_filled = len; + sb->buf[len] = '\0'; +} + +#define _UNPACKED_ARGS &sb->buf, &sb->buf_len, &sb->buf_filled + +int extend_buf(struct stringbuf *sb, size_t extend_len) +{ + return extend_buf_raw(_UNPACKED_ARGS, extend_len); +} + +int extend_buf_raw(_RAW_BUF_ARGS, size_t extend_len) +{ + ssize_t space_left = *buf_len - *buf_filled - 1; + size_t new_size, size_required, size_more_space; + char *new_buf; + + if (space_left >= 0 && space_left >= extend_len) + return 0; + + size_required = *buf_filled + extend_len + 1; + size_more_space = size_required + MINIMUM_EXTEND_BYTES; + new_size = *buf_len * 2; + if (new_size < size_more_space) + new_size = size_more_space; + + new_buf = realloc(*buf, new_size); + if (!new_buf) { + new_size = size_required; + new_buf = realloc(*buf, new_size); + } + if (!new_buf) + return -1; + + *buf = new_buf; + *buf_len = new_size; + return 0; +} + +_SB_HEAD(bytes, const void *bytes, size_t bytes_len) +{ + return sb_raw_bytes(_UNPACKED_ARGS, bytes, bytes_len); +} + +_SB_RAW_HEAD(bytes, const void *bytes, size_t bytes_len) +{ + if (extend_buf_raw(buf, buf_len, buf_filled, bytes_len)) + return -1; + + memcpy(*buf + *buf_filled, bytes, bytes_len); + (*buf)[*buf_filled + bytes_len] = '\0'; + *buf_filled += bytes_len; + + return 0; +} + +_SB_HEAD(string, const char *string) +{ + return sb_raw_string(_UNPACKED_ARGS, string); +} + +_SB_RAW_HEAD(string, const char *string) +{ + size_t string_len = strlen(string); + + return sb_raw_bytes(buf, buf_len, buf_filled, string, string_len); +} + +_SB_HEAD(char, char c) +{ + return sb_raw_char(_UNPACKED_ARGS, c); +} + +_SB_RAW_HEAD(char, char c) +{ + return sb_raw_bytes(buf, buf_len, buf_filled, &c, 1); +} + +_SB_HEAD(long, long num) +{ + return sb_raw_long(_UNPACKED_ARGS, num); +} + +_SB_RAW_HEAD(long, long num) +{ + unsigned char repr[3 * sizeof(long) + 1]; + int i; + bool neg = num < 0; + + for (i = sizeof(repr); num; num /= 10) + repr[--i] = '0' + num % 10; + + if (i == sizeof(repr)) + repr[--i] = '0'; + else if (neg) + repr[--i] = '-'; + + sb_raw_bytes(buf, buf_len, buf_filled, + repr + i, sizeof(repr) - i); + + return 0; +} + +_SB_HEAD(file, FILE *file) +{ + return sb_raw_file(_UNPACKED_ARGS, file); +} + +_SB_RAW_HEAD(file, FILE *file) +{ + long file_size; + + if (fseek(file, 0, SEEK_END)) + return -1; + + file_size = ftell(file); + if (file_size < 0) + return -1; + + if (extend_buf_raw(buf, buf_len, buf_filled, file_size)) + return -1; + + rewind(file); + + if (fread(*buf + *buf_filled, file_size, 1, file) != 1) + return -1; + + (*buf)[*buf_filled + file_size] = '\0'; + *buf_filled += file_size; + + return 0; +} + +_SB_HEAD(filepath, const char *path) +{ + return sb_raw_filepath(_UNPACKED_ARGS, path); +} + +_SB_RAW_HEAD(filepath, const char *path) +{ + FILE *file; + int retval; + + file = fopen(path, "r"); + if (!file) + return -1; + + retval = sb_raw_file(buf, buf_len, buf_filled, file); + + fclose(file); + return retval; +} + +_SB_HEAD(vsprintf, const char *fmt, va_list ap) +{ + return sb_raw_vsprintf(_UNPACKED_ARGS, fmt, ap); +} + +_SB_RAW_HEAD(vsprintf, const char *fmt, va_list ap) +{ + const unsigned char *in_pos = (const unsigned char*) fmt; + char c; + size_t i = 0; + bool percent = false; + + long num_arg; + int (*sb_cb)(char**, size_t*, size_t*, void*); + + while (in_pos[i]) { + c = in_pos[i++]; + + if (!percent) { + if (c == '%') { + percent = true; + if (sb_raw_bytes(buf, buf_len, buf_filled, + in_pos, i - 1)) + return -1; + } + + continue; + } + + percent = false; + in_pos += i; + i = 0; + + switch (c) { + case 'd': + case 'u': + num_arg = c == 'd' ? + va_arg(ap, int) : va_arg(ap, unsigned); + + if (sb_raw_long(buf, buf_len, buf_filled, num_arg)) + return -1; + break; + case 'f': + if (sb_raw_file(buf, buf_len, buf_filled, + va_arg(ap, FILE*))) + return -1; + break; + case 'p': + if (sb_raw_filepath(buf, buf_len, buf_filled, + va_arg(ap, const char*))) + return -1; + break; + case 's': + if (sb_raw_string(buf, buf_len, buf_filled, + va_arg(ap, const char*))) + return -1; + break; + case '_': + sb_cb = va_arg(ap, int (*)(char**, size_t*, + size_t*, void*)); + if (sb_cb(buf, buf_len, buf_filled, + va_arg(ap, void*))) + return -1; + break; + case '%': + in_pos--; + i++; + } + } + + if (!percent && sb_raw_bytes(buf, buf_len, buf_filled, in_pos, i)) + return -1; + + return 0; +} + +_SB_HEAD(sprintf, const char *fmt, ...) +{ + va_list ap; + int res; + + va_start(ap, fmt); + res = sb_raw_vsprintf(_UNPACKED_ARGS, fmt, ap); + va_end(ap); + + return res; +} + +_SB_RAW_HEAD(sprintf, const char *fmt, ...) +{ + va_list ap; + int res; + + va_start(ap, fmt); + res = sb_raw_vsprintf(buf, buf_len, buf_filled, fmt, ap); + va_end(ap); + + return res; +} + +int crop_buf(struct stringbuf *sb) +{ + return crop_buf_raw(_UNPACKED_ARGS); +} + +int crop_buf_raw(_RAW_BUF_ARGS) +{ + char *new_buf; + + if (*buf_len <= *buf_filled + 1) + return 0; + + new_buf = realloc(*buf, *buf_filled + 1); + if (!new_buf) + return -1; + + *buf = new_buf; + *buf_len = *buf_filled + 1; + return 0; +} |