You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

283 lines
6.2KB

  1. #include <ctime>
  2. #include <cctype> // for tolower and toupper
  3. #include <algorithm> // for transform and equal
  4. #include <libgen.h> // for dirname and basename
  5. #include <stdarg.h>
  6. #if defined ARCH_WIN
  7. #include <windows.h> // for MultiByteToWideChar
  8. #endif
  9. #include <string.hpp>
  10. namespace rack {
  11. namespace string {
  12. std::string f(const char* format, ...) {
  13. va_list args;
  14. va_start(args, format);
  15. std::string s = fV(format, args);
  16. va_end(args);
  17. return s;
  18. }
  19. std::string fV(const char* format, va_list args) {
  20. // va_lists cannot be reused but we need it twice, so clone args.
  21. va_list args2;
  22. va_copy(args2, args);
  23. // Compute size of required buffer
  24. int size = vsnprintf(NULL, 0, format, args);
  25. if (size < 0)
  26. return "";
  27. // Create buffer
  28. std::string s;
  29. s.resize(size);
  30. vsnprintf(&s[0], size + 1, format, args2);
  31. return s;
  32. }
  33. std::string lowercase(const std::string& s) {
  34. std::string r = s;
  35. std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c) {
  36. return std::tolower(c);
  37. });
  38. return r;
  39. }
  40. std::string uppercase(const std::string& s) {
  41. std::string r = s;
  42. std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c) {
  43. return std::toupper(c);
  44. });
  45. return r;
  46. }
  47. std::string trim(const std::string& s) {
  48. const std::string whitespace = " \n\r\t";
  49. size_t first = s.find_first_not_of(whitespace);
  50. if (first == std::string::npos)
  51. return "";
  52. size_t last = s.find_last_not_of(whitespace);
  53. if (last == std::string::npos)
  54. return "";
  55. return s.substr(first, last - first + 1);
  56. }
  57. std::string ellipsize(const std::string& s, size_t len) {
  58. if (s.size() <= len)
  59. return s;
  60. else
  61. return s.substr(0, len - 3) + "...";
  62. }
  63. std::string ellipsizePrefix(const std::string& s, size_t len) {
  64. if (s.size() <= len)
  65. return s;
  66. else
  67. return "..." + s.substr(s.size() - (len - 3));
  68. }
  69. bool startsWith(const std::string& str, const std::string& prefix) {
  70. if (str.size() < prefix.size())
  71. return false;
  72. return std::equal(prefix.begin(), prefix.end(), str.begin());
  73. }
  74. bool endsWith(const std::string& str, const std::string& suffix) {
  75. if (str.size() < suffix.size())
  76. return false;
  77. return std::equal(suffix.begin(), suffix.end(), str.end() - suffix.size());
  78. }
  79. float fuzzyScore(const std::string& s, const std::string& query) {
  80. size_t pos = s.find(query);
  81. if (pos == std::string::npos)
  82. return 0.f;
  83. return (float)(query.size() + 1) / (s.size() + 1);
  84. }
  85. std::string toBase64(const uint8_t* data, size_t dataLen) {
  86. static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  87. size_t numBlocks = (dataLen + 2) / 3;
  88. size_t strLen = numBlocks * 4;
  89. std::string str;
  90. str.reserve(strLen);
  91. for (size_t b = 0; b < numBlocks; b++) {
  92. // Encode block
  93. uint32_t block = 0;
  94. int i;
  95. for (i = 0; i < 3 && 3 * b + i < dataLen; i++) {
  96. block |= uint32_t(data[3 * b + i]) << (8 * (2 - i));
  97. }
  98. // Decode block
  99. str += alphabet[(block >> 18) & 0x3f];
  100. str += alphabet[(block >> 12) & 0x3f];
  101. str += (i > 1) ? alphabet[(block >> 6) & 0x3f] : '=';
  102. str += (i > 2) ? alphabet[(block >> 0) & 0x3f] : '=';
  103. }
  104. return str;
  105. }
  106. std::string toBase64(const std::vector<uint8_t>& data) {
  107. return toBase64(data.data(), data.size());
  108. }
  109. std::vector<uint8_t> fromBase64(const std::string& str) {
  110. std::vector<uint8_t> data;
  111. uint32_t block = 0;
  112. int i = 0;
  113. int padding = 0;
  114. for (char c : str) {
  115. uint8_t d = 0;
  116. if ('A' <= c && c <= 'Z') {
  117. d = c - 'A';
  118. }
  119. else if ('a' <= c && c <= 'z') {
  120. d = c - 'a' + 26;
  121. }
  122. else if ('0' <= c && c <= '9') {
  123. d = c - '0' + 52;
  124. }
  125. else if (c == '+') {
  126. d = 62;
  127. }
  128. else if (c == '/') {
  129. d = 63;
  130. }
  131. else if (c == '=') {
  132. padding++;
  133. }
  134. else {
  135. // Ignore whitespace and non-base64 characters
  136. continue;
  137. }
  138. block |= uint32_t(d) << (6 * (3 - i));
  139. i++;
  140. if (i >= 4) {
  141. // Decode block
  142. data.push_back((block >> (8 * (2 - 0))) & 0xff);
  143. if (padding < 2)
  144. data.push_back((block >> (8 * (2 - 1))) & 0xff);
  145. if (padding < 1)
  146. data.push_back((block >> (8 * (2 - 2))) & 0xff);
  147. // Reset block
  148. block = 0;
  149. i = 0;
  150. padding = 0;
  151. }
  152. }
  153. return data;
  154. }
  155. bool CaseInsensitiveCompare::operator()(const std::string& a, const std::string& b) const {
  156. for (size_t i = 0;; i++) {
  157. char ai = std::tolower(a[i]);
  158. char bi = std::tolower(b[i]);
  159. if (ai < bi)
  160. return true;
  161. if (ai > bi)
  162. return false;
  163. if (!ai || !bi)
  164. return false;
  165. }
  166. }
  167. std::vector<std::string> split(const std::string& s, const std::string& separator, size_t maxTokens) {
  168. if (separator.empty())
  169. throw Exception("split(): separator cannot be empty string");
  170. // Special case of empty string
  171. if (s == "")
  172. return {};
  173. if (maxTokens == 1)
  174. return {s};
  175. std::vector<std::string> v;
  176. size_t sepLen = separator.size();
  177. size_t start = 0;
  178. size_t end;
  179. while ((end = s.find(separator, start)) != std::string::npos) {
  180. // Add token to vector
  181. std::string token = s.substr(start, end - start);
  182. v.push_back(token);
  183. // Don't include delimiter
  184. start = end + sepLen;
  185. // Stop searching for tokens if we're at the token limit
  186. if (maxTokens == v.size() + 1)
  187. break;
  188. }
  189. v.push_back(s.substr(start));
  190. return v;
  191. }
  192. std::string formatTime(const char* format, double timestamp) {
  193. time_t t = timestamp;
  194. char str[1024];
  195. size_t s = std::strftime(str, sizeof(str), format, std::localtime(&t));
  196. return std::string(str, s);
  197. }
  198. std::string formatTimeISO(double timestamp) {
  199. return formatTime("%FT%T%z", timestamp);
  200. }
  201. #if defined ARCH_WIN
  202. std::string UTF16toUTF8(const std::wstring& w) {
  203. if (w.empty())
  204. return "";
  205. // Compute length of output buffer
  206. int len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), NULL, 0, NULL, NULL);
  207. assert(len > 0);
  208. std::string s;
  209. // Allocate enough space for null character
  210. s.resize(len);
  211. len = WideCharToMultiByte(CP_UTF8, 0, &w[0], w.size(), &s[0], len, 0, 0);
  212. assert(len > 0);
  213. return s;
  214. }
  215. std::wstring UTF8toUTF16(const std::string& s) {
  216. if (s.empty())
  217. return L"";
  218. // Compute length of output buffer
  219. int len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), NULL, 0);
  220. assert(len > 0);
  221. std::wstring w;
  222. // Allocate enough space for null character
  223. w.resize(len);
  224. len = MultiByteToWideChar(CP_UTF8, 0, &s[0], s.size(), &w[0], len);
  225. assert(len > 0);
  226. return w;
  227. }
  228. #endif
  229. } // namespace string
  230. } // namespace rack