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