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.

328 lines
7.1KB

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