source: cpp/common/util-string.cpp @ 1307

Last change on this file since 1307 was 1288, checked in by Maciej Komosinski, 11 months ago

Added helper functions

  • Property svn:eol-style set to native
File size: 6.3 KB
RevLine 
[286]1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
[1276]2// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
[286]3// See LICENSE.txt for details.
[196]4
[860]5#include "util-string.h"
[180]6#include <stdarg.h>
7#include "nonstd_stdio.h"
8#include "nonstd.h"
[220]9#include <assert.h>
[862]10#include <cstdlib> //malloc()
[1130]11#include <algorithm>
[247]12#ifdef USE_VIRTFILE
[382]13#include <common/virtfile/virtfile.h>
[247]14#endif
[892]15#ifdef __ANDROID__
16#include <android/log.h> //only needed to print error messages related to a workaround for Android bug
17#endif
[180]18
[1025]19string repr(const char *str)
20{
21        string out = "";
22        char hex[4];
23        while (*str)
24        {
[1036]25                unsigned char ch = *str;
26                if (ch >= 32 && ch < 128)
27                        out += ch;
[1025]28                else
29                {
[1036]30                        if (ch == 10) out += "\\n"; else
31                                if (ch == 13) out += "\\r"; else
[1025]32                                {
[1036]33                                        sprintf(hex, "%X", ch);
[1025]34                                        out += "\\x";
35                                        out += hex;
36                                }
37                }
38                str++;
39        }
40        return out;
41}
42
43
[180]44string ssprintf_va(const char* format, va_list ap)
45{
[220]46        string s; //clang crashed when this declaration was in s=buf
[319]47        int size = 256;
[180]48        char* buf;
[247]49        va_list ap_copy; // "va_list ap" can only by used once by printf-type functions as they advance the current argument pointer (crashed on linux x86_64)
50        // (does not apply to SString::sprintf, it does not have the va_list variant)
[220]51
52        //almost like SString::sprintf, but there is no common code to share because SString can use its directWrite to avoid double allocating/copying
53#ifdef USE_VSCPRINTF
[257]54        va_copy(ap_copy, ap);
[247]55        size = _vscprintf(format, ap_copy) + 1; //+1 for terminating null character
56        va_end(ap_copy);
[220]57#endif
58
[1025]59        while (true)
[246]60        {
61                buf = (char*)malloc(size);
62                assert(buf != NULL);
[257]63                va_copy(ap_copy, ap);
[247]64                int n = vsnprintf(buf, size, format, ap_copy);
65                va_end(ap_copy);
[892]66
67#ifdef __ANDROID__
68                //Workaround for Android bug. /system/lib64/libc.so? maybe only arm 64-bit? "If an encoding error occurs, a negative number is returned". On some devices keeps returning -1 forever.
69                //https://github.com/android-ndk/ndk/issues/879 but unfortunately during google play tests (Firebase Test Lab) this problem turned out to be not limited to Chinese devices and occurred in Mate 9, Galaxy S9, Pixel, Pixel 2, Moto Z (even with the en_GB locale; the locale is not important but the problem seem to be utf8 non-ascii chars in the format string).
70                if (n < 0 && size >= (1 << 24)) //wants more than 16M
71                {
72                        buf[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
73                        __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "Giving up due to Android bug: vsnprintf() wants more than %d bytes, it used %zu bytes, for format='%s'", size, strlen(buf), format);
74                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
75                        va_copy(ap_copy, ap);
76                        n = vsprintf(buf, format, ap_copy); //hoping 16M is enough
77                        va_end(ap_copy);
78                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", buf);
79                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
80                        {
[897]81                                strcpy(buf, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
82                                strcat(buf, format); //append and return the original formatting string
83                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", buf);
[892]84                        }
[897]85                        n = strlen(buf); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
[892]86                }
87#endif
88
[246]89                if (n > -1 && n < size)
[220]90                {
[246]91                        s = buf;
[220]92                        free(buf);
93                        return s;
[246]94                }
[220]95#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
96                if (n > -1)    /* glibc 2.1 */
[867]97                        size = n + 1; /* precisely what is needed */
[220]98                else           /* glibc 2.0 */
99#endif
100                        size *= 2;  /* twice the old size */
101                free(buf);
[246]102        }
[180]103}
104
[913]105bool str_starts_with(const char *str, const char *prefix)
106{
[1016]107        return strncmp(str, prefix, strlen(prefix)) == 0;
[913]108}
109
[1276]110bool ends_with(std::string_view str, std::string_view suffix)
111{
112        return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
113}
114
[1288]115bool strip_prefix(string& modify_me, const char* prefix)
116{
117        if (starts_with(modify_me, prefix))
118        {
119                modify_me = modify_me.substr(strlen(prefix));
120                return true;
121        }
122        return false;
123}
[1276]124
[257]125char* strmove(char *a, char *b) //strcpy that works well for overlapping strings ("Source and destination overlap")
126{
127        if (a == NULL || b == NULL)
128                return NULL;
129        memmove(a, b, strlen(b) + 1);
130        return a;
131}
132
[180]133string ssprintf(const char* format, ...)
134{
135        va_list ap;
136        va_start(ap, format);
[246]137        string ret = ssprintf_va(format, ap); //is it too wasteful? copying the string again... unless the compiler can handle it better
[180]138        va_end(ap);
139        return ret;
140}
141
[246]142string stripExt(const string& filename)
[180]143{
[319]144        size_t dot = filename.rfind('.');
[246]145        if (dot == string::npos) return filename;
[319]146        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
[246]147        if ((sep == string::npos) || (sep < dot))
148                return filename.substr(0, dot);
[180]149        return filename;
150}
151
[460]152string stripFileDir(const string& filename)
153{
154        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
155        if (sep == string::npos) return filename;
[867]156        return filename.substr(sep + 1);
[460]157}
158
[246]159string getFileExt(const string& filename)
[180]160{
[319]161        size_t dot = filename.rfind('.');
[246]162        if (dot == string::npos) return string("");
[319]163        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
[246]164        if ((sep == string::npos) || (sep < dot))
[180]165                return filename.substr(dot);
166        return string("");
167}
[246]168
169string getFileDir(const string& filename)
170{
[319]171        size_t slash = filename.rfind(PATH_SEPARATOR_CHAR);
[246]172        if (slash == string::npos) return string("");
[1288]173        return (slash == 0) ? string(PATH_SEPARATOR_STRING) : filename.substr(0, slash);
[246]174}
[1016]175
[1288]176string concatPath(const string& path, const string& other_path)
177{
178        string result = path;
179        if (!result.empty() && result.back() != PATH_SEPARATOR_CHAR && !other_path.empty())
180                result += PATH_SEPARATOR_CHAR;
181        result += other_path;
182        return result;
183}
184
[1016]185//trimming functions, https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
186
187void ltrim_inplace(string &s)
188{
189        s.erase(s.begin(), find_if(s.begin(), s.end(), [](int ch) {
190                return !isspace(ch);
191                }));
192}
193
194void rtrim_inplace(string &s)
195{
196        s.erase(find_if(s.rbegin(), s.rend(), [](int ch) {
197                return !isspace(ch);
198                }).base(), s.end());
199}
200
201void trim_inplace(string &s)
202{
203        ltrim_inplace(s);
204        rtrim_inplace(s);
205}
206
207string ltrim(string s)
208{
209        ltrim_inplace(s);
210        return s;
211}
212
213string rtrim(string s)
214{
215        rtrim_inplace(s);
216        return s;
217}
218
219string trim(string s)
220{
221        trim_inplace(s);
222        return s;
223}
Note: See TracBrowser for help on using the repository browser.