common: compile libstring-utils as C++
[lttng-tools.git] / src / common / string-utils / string-utils.cpp
diff --git a/src/common/string-utils/string-utils.cpp b/src/common/string-utils/string-utils.cpp
new file mode 100644 (file)
index 0000000..3051644
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2017 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-only
+ *
+ */
+
+#define _LGPL_SOURCE
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <type_traits>
+
+#include "string-utils.h"
+#include "../macros.h"
+
+enum star_glob_pattern_type_flags {
+       STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0,
+       STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1,
+       STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2,
+};
+
+static
+star_glob_pattern_type_flags &operator|=(star_glob_pattern_type_flags &l,
+               star_glob_pattern_type_flags r)
+{
+       using T = std::underlying_type<star_glob_pattern_type_flags>::type;
+       l = static_cast<star_glob_pattern_type_flags> (
+               static_cast<T> (l) | static_cast<T> (r));
+       return l;
+}
+
+/*
+ * Normalizes the star-only globbing pattern `pattern`, that is, crushes
+ * consecutive `*` characters into a single `*`, avoiding `\*`.
+ */
+void strutils_normalize_star_glob_pattern(char *pattern)
+{
+       const char *p;
+       char *np;
+       bool got_star = false;
+
+       LTTNG_ASSERT(pattern);
+
+       for (p = pattern, np = pattern; *p != '\0'; p++) {
+               switch (*p) {
+               case '*':
+                       if (got_star) {
+                               /* Avoid consecutive stars. */
+                               continue;
+                       }
+
+                       got_star = true;
+                       break;
+               case '\\':
+                       /* Copy backslash character. */
+                       *np = *p;
+                       np++;
+                       p++;
+
+                       if (*p == '\0') {
+                               goto end;
+                       }
+
+                       /* Fall through default case. */
+               default:
+                       got_star = false;
+                       break;
+               }
+
+               /* Copy single character. */
+               *np = *p;
+               np++;
+       }
+
+end:
+       *np = '\0';
+}
+
+static
+enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern)
+{
+       enum star_glob_pattern_type_flags ret =
+               STAR_GLOB_PATTERN_TYPE_FLAG_NONE;
+       const char *p;
+
+       LTTNG_ASSERT(pattern);
+
+       for (p = pattern; *p != '\0'; p++) {
+               switch (*p) {
+               case '*':
+                       ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
+
+                       if (p[1] == '\0') {
+                               ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
+                       }
+
+                       goto end;
+               case '\\':
+                       p++;
+
+                       if (*p == '\0') {
+                               goto end;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+end:
+       return ret;
+}
+
+/*
+ * Returns true if `pattern` is a star-only globbing pattern, that is,
+ * it contains at least one non-escaped `*`.
+ */
+bool strutils_is_star_glob_pattern(const char *pattern)
+{
+       return strutils_test_glob_pattern(pattern) &
+               STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN;
+}
+
+/*
+ * Returns true if `pattern` is a globbing pattern with a globbing,
+ * non-escaped star only at its very end.
+ */
+bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern)
+{
+       return strutils_test_glob_pattern(pattern) &
+               STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY;
+}
+
+/*
+ * Unescapes the input string `input`, that is, in a `\x` sequence,
+ * removes `\`. If `only_char` is not 0, only this character is
+ * escaped.
+ */
+char *strutils_unescape_string(const char *input, char only_char)
+{
+       char *output;
+       char *o;
+       const char *i;
+
+       LTTNG_ASSERT(input);
+       output = (char *) zmalloc(strlen(input) + 1);
+       if (!output) {
+               goto end;
+       }
+
+       for (i = input, o = output; *i != '\0'; i++) {
+               switch (*i) {
+               case '\\':
+                       if (only_char && i[1] != only_char) {
+                               break;
+                       }
+
+                       i++;
+
+                       if (*i == '\0') {
+                               /* Copy last `\`. */
+                               *o = '\\';
+                               o++;
+                               goto end;
+                       }
+               default:
+                       break;
+               }
+
+               /* Copy single character. */
+               *o = *i;
+               o++;
+       }
+
+end:
+       return output;
+}
+
+/*
+ * Frees a null-terminated array of strings, including each contained
+ * string.
+ */
+void strutils_free_null_terminated_array_of_strings(char **array)
+{
+       char **item;
+
+       if (!array) {
+               return;
+       }
+
+       for (item = array; *item; item++) {
+               free(*item);
+       }
+
+       free(array);
+}
+
+/*
+ * Splits the input string `input` using the given delimiter `delim`.
+ *
+ * The return value is a dynamic pointer array that is assumed to be empty. The
+ * array must be discarded by the caller by invoking
+ * lttng_dynamic_pointer_array_reset().
+ *
+ * Empty substrings are part of the result. For example:
+ *
+ *     Input: ,hello,,there,
+ *     Result:
+ *       ``
+ *       `hello`
+ *       ``
+ *       `there`
+ *       ``
+ *
+ * If `escape_delim` is true, then `\,`, where `,` is the delimiter,
+ * escapes the delimiter and is copied as `,` only in the resulting
+ * substring. For example:
+ *
+ *     Input: hello\,world,zoom,\,hi
+ *     Result:
+ *       `hello,world`
+ *       `zoom`
+ *       `,hi`
+ *
+ * Other characters are not escaped (this is the caller's job if
+ * needed). However they are considering during the parsing, that is,
+ * `\x`, where `x` is any character, is copied as is to the resulting
+ * substring, e.g.:
+ *
+ *     Input: hello\,wo\rld\\,zoom\,
+ *     Result:
+ *       `hello,wo\rld\\`
+ *       `zoom,`
+ *
+ * If `escape_delim` is false, nothing at all is escaped, and `delim`,
+ * when found in `input`, is always a delimiter, e.g.:
+ *
+ *     Input: hello\,world,zoom,\,hi
+ *     Result:
+ *       `hello\`
+ *       `world`
+ *       `zoom`
+ *       `\`
+ *       `hi`
+ *
+ * Returns -1 if there's an error.
+ */
+int strutils_split(const char *input,
+               char delim,
+               bool escape_delim,
+               struct lttng_dynamic_pointer_array *out_strings)
+{
+       int ret;
+       size_t at;
+       size_t number_of_substrings = 1;
+       size_t longest_substring_len = 0;
+       const char *s;
+       const char *last;
+
+       LTTNG_ASSERT(input);
+       LTTNG_ASSERT(!(escape_delim && delim == '\\'));
+       LTTNG_ASSERT(delim != '\0');
+       lttng_dynamic_pointer_array_init(out_strings, free);
+
+       /* First pass: count the number of substrings. */
+       for (s = input, last = input - 1; *s != '\0'; s++) {
+               if (escape_delim && *s == '\\') {
+                       /* Ignore following (escaped) character. */
+                       s++;
+
+                       if (*s == '\0') {
+                               break;
+                       }
+
+                       continue;
+               }
+
+               if (*s == delim) {
+                       size_t last_len = s - last - 1;
+                       last = s;
+                       number_of_substrings++;
+
+                       if (last_len > longest_substring_len) {
+                               longest_substring_len = last_len;
+                       }
+               }
+       }
+
+       if ((s - last - 1) > longest_substring_len) {
+               longest_substring_len = s - last - 1;
+       }
+
+       /* Second pass: actually split and copy substrings. */
+       for (at = 0, s = input; at < number_of_substrings; at++) {
+               const char *ss;
+               char *d;
+               char *substring = (char *) zmalloc(longest_substring_len + 1);
+
+               if (!substring) {
+                       goto error;
+               }
+
+               ret = lttng_dynamic_pointer_array_add_pointer(
+                               out_strings, substring);
+               if (ret) {
+                       free(substring);
+                       goto error;
+               }
+
+               /*
+                * Copy characters to substring until we find the next
+                * delimiter or the end of the input string.
+                */
+               for (ss = s, d = substring; *ss != '\0'; ss++) {
+                       if (escape_delim && *ss == '\\') {
+                               if (ss[1] == delim) {
+                                       /*
+                                        * '\' followed by delimiter and
+                                        * we need to escape this ('\'
+                                        * won't be part of the
+                                        * resulting substring).
+                                        */
+                                       ss++;
+                                       *d = *ss;
+                                       d++;
+                                       continue;
+                               } else {
+                                       /*
+                                        * Copy '\' and the following
+                                        * character.
+                                        */
+                                       *d = *ss;
+                                       d++;
+                                       ss++;
+
+                                       if (*ss == '\0') {
+                                               break;
+                                       }
+                               }
+                       } else if (*ss == delim) {
+                               /* We're done with this substring. */
+                               break;
+                       }
+
+                       *d = *ss;
+                       d++;
+               }
+
+               /* Next substring starts after the last delimiter. */
+               s = ss + 1;
+       }
+
+       ret = 0;
+       goto end;
+
+error:
+       ret = -1;
+end:
+       return ret;
+}
+
+size_t strutils_array_of_strings_len(char * const *array)
+{
+       char * const *item;
+       size_t count = 0;
+
+       LTTNG_ASSERT(array);
+
+       for (item = array; *item; item++) {
+               count++;
+       }
+
+       return count;
+}
This page took 0.026978 seconds and 4 git commands to generate.