Mercurial > foo_out_sdl
comparison foosdk/sdk/pfc/pathUtils.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "pfc-lite.h" | |
| 2 #include "pathUtils.h" | |
| 3 | |
| 4 static_assert(L'Ö' == 0xD6, "Compile as Unicode!!!"); | |
| 5 | |
| 6 namespace pfc { namespace io { namespace path { | |
| 7 | |
| 8 #ifdef _WINDOWS | |
| 9 #define KPathSeparators "\\/|" | |
| 10 #else | |
| 11 #define KPathSeparators "/" | |
| 12 #endif | |
| 13 | |
| 14 string getFileName(string path) { | |
| 15 t_size split = path.lastIndexOfAnyChar(KPathSeparators); | |
| 16 if (split == SIZE_MAX) return path; | |
| 17 else return path.subString(split+1); | |
| 18 } | |
| 19 string getFileNameWithoutExtension(string path) { | |
| 20 string fn = getFileName(path); | |
| 21 t_size split = fn.lastIndexOf('.'); | |
| 22 if (split == SIZE_MAX) return fn; | |
| 23 else return fn.subString(0,split); | |
| 24 } | |
| 25 string getFileExtension(string path) { | |
| 26 string fn = getFileName(path); | |
| 27 t_size split = fn.lastIndexOf('.'); | |
| 28 if (split == SIZE_MAX) return ""; | |
| 29 else return fn.subString(split); | |
| 30 } | |
| 31 string getDirectory(string filePath) {return getParent(filePath);} | |
| 32 | |
| 33 string getParent(string filePath) { | |
| 34 t_size split = filePath.lastIndexOfAnyChar(KPathSeparators); | |
| 35 if (split == SIZE_MAX) return ""; | |
| 36 #ifdef _WINDOWS | |
| 37 if (split > 0 && getIllegalNameChars().contains(filePath[split-1])) { | |
| 38 if (split + 1 < filePath.length()) return filePath.subString(0,split+1); | |
| 39 else return ""; | |
| 40 } | |
| 41 #endif | |
| 42 return filePath.subString(0,split); | |
| 43 } | |
| 44 string combine(string basePath,string fileName) { | |
| 45 if (basePath.length() > 0) { | |
| 46 if (!isSeparator(basePath.lastChar())) { | |
| 47 basePath.add_byte( getDefaultSeparator() ); | |
| 48 } | |
| 49 return basePath + fileName; | |
| 50 } else { | |
| 51 //todo? | |
| 52 return fileName; | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 bool isSeparator(char c) { | |
| 57 return strchr(KPathSeparators, c) != nullptr; | |
| 58 } | |
| 59 string getSeparators() { | |
| 60 return KPathSeparators; | |
| 61 } | |
| 62 | |
| 63 const char * charReplaceDefault(char c) { | |
| 64 switch (c) { | |
| 65 case '*': | |
| 66 return "x"; | |
| 67 case '\"': | |
| 68 return "\'\'"; | |
| 69 case ':': | |
| 70 case '/': | |
| 71 case '\\': | |
| 72 return "-"; | |
| 73 case '?': | |
| 74 return ""; | |
| 75 default: | |
| 76 return "_"; | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 const char * charReplaceModern(char c) { | |
| 81 switch (c) { | |
| 82 case '*': | |
| 83 return reinterpret_cast<const char*>( u8"∗" ); | |
| 84 case '\"': | |
| 85 return reinterpret_cast<const char*>( u8"''" ); | |
| 86 case ':': | |
| 87 return reinterpret_cast<const char*>( u8"∶" ); | |
| 88 case '/': | |
| 89 return reinterpret_cast<const char*>( u8"\u2215" ); | |
| 90 case '\\': | |
| 91 return reinterpret_cast<const char*>( u8"⧵" ); | |
| 92 case '?': | |
| 93 return reinterpret_cast<const char*>( u8"?" ); | |
| 94 case '<': | |
| 95 return reinterpret_cast<const char*>( u8"˂" ); | |
| 96 case '>': | |
| 97 return reinterpret_cast<const char*>( u8"˃" ); | |
| 98 case '|': | |
| 99 return reinterpret_cast<const char*>( u8"∣" ); | |
| 100 default: | |
| 101 return "_"; | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 string replaceIllegalPathChars(string fn, charReplace_t replaceIllegalChar) { | |
| 106 string illegal = getIllegalNameChars(); | |
| 107 string separators = getSeparators(); | |
| 108 string_formatter output; | |
| 109 for(t_size walk = 0; walk < fn.length(); ++walk) { | |
| 110 const char c = fn[walk]; | |
| 111 if (separators.contains(c)) { | |
| 112 output.add_byte(getDefaultSeparator()); | |
| 113 } else if (string::isNonTextChar(c) || illegal.contains(c)) { | |
| 114 string replacement = replaceIllegalChar(c); | |
| 115 if (replacement.containsAnyChar(illegal)) /*per-OS weirdness security*/ replacement = "_"; | |
| 116 output << replacement.ptr(); | |
| 117 } else { | |
| 118 output.add_byte(c); | |
| 119 } | |
| 120 } | |
| 121 return output.toString(); | |
| 122 } | |
| 123 | |
| 124 string replaceIllegalNameChars(string fn, bool allowWC, charReplace_t replaceIllegalChar) { | |
| 125 const string illegal = getIllegalNameChars(allowWC); | |
| 126 string_formatter output; | |
| 127 for(t_size walk = 0; walk < fn.length(); ++walk) { | |
| 128 const char c = fn[walk]; | |
| 129 if (string::isNonTextChar(c) || illegal.contains(c)) { | |
| 130 string replacement = replaceIllegalChar(c); | |
| 131 if (replacement.containsAnyChar(illegal)) /*per-OS weirdness security*/ replacement = "_"; | |
| 132 output << replacement.ptr(); | |
| 133 } else { | |
| 134 output.add_byte(c); | |
| 135 } | |
| 136 } | |
| 137 return output.toString(); | |
| 138 } | |
| 139 | |
| 140 bool isInsideDirectory(pfc::string directory, pfc::string inside) { | |
| 141 //not very efficient | |
| 142 string walk = inside; | |
| 143 for(;;) { | |
| 144 walk = getParent(walk); | |
| 145 if (walk == "") return false; | |
| 146 if (equals(directory,walk)) return true; | |
| 147 } | |
| 148 } | |
| 149 bool isDirectoryRoot(string path) { | |
| 150 return getParent(path).isEmpty(); | |
| 151 } | |
| 152 //OS-dependant part starts here | |
| 153 | |
| 154 | |
| 155 char getDefaultSeparator() { | |
| 156 #ifdef _WINDOWS | |
| 157 return '\\'; | |
| 158 #else | |
| 159 return '/'; | |
| 160 #endif | |
| 161 } | |
| 162 | |
| 163 #ifdef _WINDOWS | |
| 164 #define KIllegalNameCharsEx ":<>\"" | |
| 165 #else | |
| 166 // Mac OS allows : in filenames but does funny things presenting them in Finder, so don't use it | |
| 167 #define KIllegalNameCharsEx ":" | |
| 168 #endif | |
| 169 | |
| 170 #define KWildcardChars "*?" | |
| 171 | |
| 172 #define KIllegalNameChars KPathSeparators KIllegalNameCharsEx KWildcardChars | |
| 173 #define KIllegalNameChars_noWC KPathSeparators KIllegalNameCharsEx | |
| 174 | |
| 175 static string g_illegalNameChars ( KIllegalNameChars ); | |
| 176 static string g_illegalNameChars_noWC ( KIllegalNameChars_noWC ); | |
| 177 | |
| 178 string getIllegalNameChars(bool allowWC) { | |
| 179 return allowWC ? g_illegalNameChars_noWC : g_illegalNameChars; | |
| 180 } | |
| 181 | |
| 182 #ifdef _WINDOWS | |
| 183 static const char * const specialIllegalNames[] = { | |
| 184 "con", "aux", "lst", "prn", "nul", "eof", "inp", "out" | |
| 185 }; | |
| 186 #endif // _WINDOWS | |
| 187 | |
| 188 #ifdef _WIN32 | |
| 189 static constexpr unsigned maxPathComponent = 255; | |
| 190 #else | |
| 191 static constexpr unsigned maxPathComponent = NAME_MAX; | |
| 192 #endif | |
| 193 | |
| 194 static size_t safeTruncat( const char * str, size_t maxLen ) { | |
| 195 size_t i = 0; | |
| 196 size_t ret = 0; | |
| 197 for( ; i < maxLen; ++ i ) { | |
| 198 auto d = pfc::utf8_char_len( str + ret ); | |
| 199 if ( d == 0 ) break; | |
| 200 ret += d; | |
| 201 } | |
| 202 return ret; | |
| 203 } | |
| 204 | |
| 205 static size_t utf8_length( const char * str ) { | |
| 206 size_t ret = 0; | |
| 207 for (; ++ret;) { | |
| 208 size_t d = pfc::utf8_char_len( str ); | |
| 209 if ( d == 0 ) break; | |
| 210 str += d; | |
| 211 } | |
| 212 return ret; | |
| 213 } | |
| 214 static string truncatePathComponent( string name, bool preserveExt ) { | |
| 215 | |
| 216 if (name.length() <= maxPathComponent) return name; | |
| 217 if (preserveExt) { | |
| 218 auto dot = name.lastIndexOf('.'); | |
| 219 if (dot != pfc_infinite) { | |
| 220 const auto ext = name.subString(dot); | |
| 221 const auto extLen = utf8_length( ext.c_str() ); | |
| 222 if (extLen < maxPathComponent) { | |
| 223 auto lim = maxPathComponent - extLen; | |
| 224 lim = safeTruncat( name.c_str(), lim ); | |
| 225 if (lim < dot) { | |
| 226 return name.subString(0, lim) + ext; | |
| 227 } | |
| 228 } | |
| 229 } | |
| 230 } | |
| 231 | |
| 232 size_t truncat = safeTruncat( name.c_str(), maxPathComponent ); | |
| 233 return name.subString(0, truncat); | |
| 234 } | |
| 235 | |
| 236 static string trailingSanity(string name, bool preserveExt, const char * lstIllegal) { | |
| 237 | |
| 238 const auto isIllegalTrailingChar = [lstIllegal](char c) { | |
| 239 return strchr(lstIllegal, c) != nullptr; | |
| 240 }; | |
| 241 | |
| 242 t_size end = name.length(); | |
| 243 if (preserveExt) { | |
| 244 size_t offset = pfc::string_find_last(name.c_str(), '.'); | |
| 245 if (offset < end) end = offset; | |
| 246 } | |
| 247 const size_t endEx = end; | |
| 248 while (end > 0) { | |
| 249 if (!isIllegalTrailingChar(name[end - 1])) break; | |
| 250 --end; | |
| 251 } | |
| 252 t_size begin = 0; | |
| 253 while (begin < end) { | |
| 254 if (!isIllegalTrailingChar(name[begin])) break; | |
| 255 ++begin; | |
| 256 } | |
| 257 if (end < endEx || begin > 0) { | |
| 258 name = name.subString(begin, end - begin) + name.subString(endEx); | |
| 259 } | |
| 260 return name; | |
| 261 } | |
| 262 string validateFileName(string name, bool allowWC, bool preserveExt, charReplace_t replaceIllegalChar) { | |
| 263 if (!allowWC) { // special fix for filenames that consist only of question marks | |
| 264 size_t end = name.length(); | |
| 265 if (preserveExt) { | |
| 266 size_t offset = pfc::string_find_last(name.c_str(), '.'); | |
| 267 if (offset < end) end = offset; | |
| 268 } | |
| 269 bool unnamed = true; | |
| 270 for (size_t walk = 0; walk < end; ++walk) { | |
| 271 if (name[walk] != '?') unnamed = false; | |
| 272 } | |
| 273 if (unnamed) { | |
| 274 name = string("[unnamed]") + name.subString(end); | |
| 275 } | |
| 276 } | |
| 277 | |
| 278 // Trailing sanity AFTER replaceIllegalNameChars | |
| 279 // replaceIllegalNameChars may remove chars exposing illegal prefix/suffix chars | |
| 280 name = replaceIllegalNameChars(name, allowWC, replaceIllegalChar); | |
| 281 if (name.length() > 0 && !allowWC) { | |
| 282 const char* lstIllegal = preserveExt ? "" : " ."; | |
| 283 name = trailingSanity(name, preserveExt, lstIllegal); | |
| 284 } | |
| 285 | |
| 286 name = truncatePathComponent(name, preserveExt); | |
| 287 | |
| 288 #ifdef _WINDOWS | |
| 289 for( auto p : specialIllegalNames ) { | |
| 290 if (pfc::stringEqualsI_ascii( name.c_str(), p ) ) { | |
| 291 name += "-"; | |
| 292 break; | |
| 293 } | |
| 294 } | |
| 295 #endif | |
| 296 | |
| 297 if (name.isEmpty()) name = "_"; | |
| 298 return name; | |
| 299 } | |
| 300 | |
| 301 }}} // namespaces |
