string.h raw

   1  // Copyright (c) 2019-present The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_UTIL_STRING_H
   6  #define LIMENKA_UTIL_STRING_H
   7  
   8  #include <span.h>
   9  
  10  #include <array>
  11  #include <cstdint>
  12  #include <cstring>
  13  #include <locale>
  14  #include <sstream>
  15  #include <string>      // IWYU pragma: export
  16  #include <string_view> // IWYU pragma: export
  17  #include <vector>
  18  
  19  namespace util {
  20  namespace detail {
  21  template <unsigned num_params>
  22  constexpr static void CheckNumFormatSpecifiers(const char* str)
  23  {
  24      unsigned count_normal{0}; // Number of "normal" specifiers, like %s
  25      unsigned count_pos{0};    // Max number in positional specifier, like %8$s
  26      for (auto it{str}; *it != '\0'; ++it) {
  27          if (*it != '%' || *++it == '%') continue; // Skip escaped %%
  28  
  29          auto add_arg = [&] {
  30              unsigned maybe_num{0};
  31              while ('0' <= *it && *it <= '9') {
  32                  maybe_num *= 10;
  33                  maybe_num += *it - '0';
  34                  ++it;
  35              }
  36  
  37              if (*it == '$') {
  38                  ++it;
  39                  // Positional specifier, like %8$s
  40                  if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
  41                  count_pos = std::max(count_pos, maybe_num);
  42              } else {
  43                  // Non-positional specifier, like %s
  44                  ++count_normal;
  45              }
  46          };
  47  
  48          // Increase argument count and consume positional specifier, if present.
  49          add_arg();
  50  
  51          // Consume flags.
  52          while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
  53  
  54          auto parse_size = [&] {
  55              if (*it == '*') {
  56                  ++it;
  57                  add_arg();
  58              } else {
  59                  while ('0' <= *it && *it <= '9') ++it;
  60              }
  61          };
  62  
  63          // Consume dynamic or static width value.
  64          parse_size();
  65  
  66          // Consume dynamic or static precision value.
  67          if (*it == '.') {
  68              ++it;
  69              parse_size();
  70          }
  71  
  72          if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
  73  
  74          // Length and type in "[flags][width][.precision][length]type"
  75          // is not checked. Parsing continues with the next '%'.
  76      }
  77      if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
  78      unsigned count{count_normal | count_pos};
  79      if (num_params != count) throw "Format specifier count must match the argument count!";
  80  }
  81  } // namespace detail
  82  
  83  /**
  84   * @brief A wrapper for a compile-time partially validated format string
  85   *
  86   * This struct can be used to enforce partial compile-time validation of format
  87   * strings, to reduce the likelihood of tinyformat throwing exceptions at
  88   * run-time. Validation is partial to try and prevent the most common errors
  89   * while avoiding re-implementing the entire parsing logic.
  90   */
  91  template <unsigned num_params>
  92  struct ConstevalFormatString {
  93      const char* const fmt;
  94      consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
  95  };
  96  
  97  void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
  98  
  99  /** Split a string on any char found in separators, returning a vector.
 100   *
 101   * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 102   *
 103   * Note that this function does not care about braces, so splitting
 104   * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 105   */
 106  template <typename T = Span<const char>>
 107  std::vector<T> Split(const Span<const char>& sp, std::string_view separators)
 108  {
 109      std::vector<T> ret;
 110      auto it = sp.begin();
 111      auto start = it;
 112      while (it != sp.end()) {
 113          if (separators.find(*it) != std::string::npos) {
 114              ret.emplace_back(start, it);
 115              start = it + 1;
 116          }
 117          ++it;
 118      }
 119      ret.emplace_back(start, it);
 120      return ret;
 121  }
 122  
 123  /** Split a string on every instance of sep, returning a vector.
 124   *
 125   * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 126   *
 127   * Note that this function does not care about braces, so splitting
 128   * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 129   */
 130  template <typename T = Span<const char>>
 131  std::vector<T> Split(const Span<const char>& sp, char sep)
 132  {
 133      return Split<T>(sp, std::string_view{&sep, 1});
 134  }
 135  
 136  [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
 137  {
 138      return Split<std::string>(str, sep);
 139  }
 140  
 141  [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
 142  {
 143      return Split<std::string>(str, separators);
 144  }
 145  
 146  [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
 147  {
 148      std::string::size_type front = str.find_first_not_of(pattern);
 149      if (front == std::string::npos) {
 150          return {};
 151      }
 152      std::string::size_type end = str.find_last_not_of(pattern);
 153      return str.substr(front, end - front + 1);
 154  }
 155  
 156  [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
 157  {
 158      return std::string(TrimStringView(str, pattern));
 159  }
 160  
 161  [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
 162  {
 163      if (str.ends_with(suffix)) {
 164          return str.substr(0, str.size() - suffix.size());
 165      }
 166      return str;
 167  }
 168  
 169  [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
 170  {
 171      if (str.substr(0, prefix.size()) == prefix) {
 172          return str.substr(prefix.size());
 173      }
 174      return str;
 175  }
 176  
 177  [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
 178  {
 179      return std::string(RemovePrefixView(str, prefix));
 180  }
 181  
 182  /**
 183   * Join all container items. Typically used to concatenate strings but accepts
 184   * containers with elements of any type.
 185   *
 186   * @param container The items to join
 187   * @param separator The separator
 188   * @param unary_op  Apply this operator to each item
 189   */
 190  template <typename C, typename S, typename UnaryOp>
 191  // NOLINTNEXTLINE(misc-no-recursion)
 192  auto Join(const C& container, const S& separator, UnaryOp unary_op)
 193  {
 194      decltype(unary_op(*container.begin())) ret;
 195      bool first{true};
 196      for (const auto& item : container) {
 197          if (!first) ret += separator;
 198          ret += unary_op(item);
 199          first = false;
 200      }
 201      return ret;
 202  }
 203  
 204  template <typename C, typename S>
 205  auto Join(const C& container, const S& separator)
 206  {
 207      return Join(container, separator, [](const auto& i) { return i; });
 208  }
 209  
 210  /**
 211   * Create an unordered multi-line list of items.
 212   */
 213  inline std::string MakeUnorderedList(const std::vector<std::string>& items)
 214  {
 215      return Join(items, "\n", [](const std::string& item) { return "- " + item; });
 216  }
 217  
 218  /**
 219   * Check if a string does not contain any embedded NUL (\0) characters
 220   */
 221  [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
 222  {
 223      for (auto c : str) {
 224          if (c == 0) return false;
 225      }
 226      return true;
 227  }
 228  
 229  /**
 230   * Locale-independent version of std::to_string
 231   */
 232  template <typename T>
 233  std::string ToString(const T& t)
 234  {
 235      std::ostringstream oss;
 236      oss.imbue(std::locale::classic());
 237      oss << t;
 238      return oss.str();
 239  }
 240  
 241  /**
 242   * Check whether a container begins with the given prefix.
 243   */
 244  template <typename T1, size_t PREFIX_LEN>
 245  [[nodiscard]] inline bool HasPrefix(const T1& obj,
 246                                  const std::array<uint8_t, PREFIX_LEN>& prefix)
 247  {
 248      return obj.size() >= PREFIX_LEN &&
 249             std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
 250  }
 251  } // namespace util
 252  
 253  #endif // LIMENKA_UTIL_STRING_H
 254