4343 *
4444 * Changelog:
4545 *
46+ * v1.3.1:
47+ * - Bugfixes in path handling functions, especially around win32 drive letters and UCN paths
48+ * - Fixed `ext_new_array` macro
49+ * - Minor other bugfixes
50+ *
4651 * v1.3.0:
4752 * - New `StringSlice` functions: `ss_strip_prefix`, `ss_strip_suffix` (and `_cstr`
4853 * variants), `ss_eq_ignore_case`, `ss_cmp_ignore_case`, `ss_starts_with_ignore_case`,
@@ -522,7 +527,7 @@ typedef struct Ext_Allocator {
522527// ext_clone:
523528// Creates a copy of the provided pointer using `ext_memdup`
524529#define ext_new (T ) ext_alloc(sizeof(T))
525- #define ext_new_array (T , count ) ext_alloc(sizeof(int ) * count)
530+ #define ext_new_array (T , count ) ext_alloc(sizeof(T ) * count)
526531#define ext_delete (T , ptr ) ext_free(ptr, sizeof(T))
527532#define ext_delete_array (T , count , ptr ) ext_free(ptr, sizeof(T) * count);
528533#define ext_clone (T , ptr ) ext_memdup(ptr, sizeof(T));
@@ -1956,7 +1961,7 @@ static void *ext_default_realloc(Ext_Allocator *a, void *ptr, size_t old_size, s
19561961#else
19571962 (void )ptr ;
19581963 (void )new_size ;
1959- return NULL
1964+ return NULL ;
19601965#endif
19611966}
19621967
@@ -2668,8 +2673,9 @@ Ext_StringSlice ext_ss_strip_suffix_cstr(Ext_StringSlice ss, const char *suffix)
26682673}
26692674
26702675int ext_ss_cmp (Ext_StringSlice s1 , Ext_StringSlice s2 ) {
2671- size_t min_sz = s1 .size < s2 .size ? s1 .size : s2 .size ;
2672- return memcmp (s1 .data , s2 .data , min_sz );
2676+ if (s1 .size < s2 .size ) return -1 ;
2677+ else if (s1 .size > s2 .size ) return 1 ;
2678+ else return memcmp (s1 .data , s2 .data , s1 .size );
26732679}
26742680
26752681bool ext_ss_eq (Ext_StringSlice s1 , Ext_StringSlice s2 ) {
@@ -2746,12 +2752,75 @@ static bool ext__is_path_sep(char c) {
27462752#endif
27472753}
27482754
2755+ #ifdef EXT_WINDOWS
2756+ static bool ext__is_drive_letter (Ext_StringSlice path ) {
2757+ if (path .size < 2 ) return false;
2758+ char c = path .data [0 ];
2759+ return ((c >= 'a' && c <= 'z' ) || (c >= 'A' && c <= 'Z' )) && path .data [1 ] == ':' ;
2760+ }
2761+
2762+ static bool ext__is_unc_path (Ext_StringSlice path ) {
2763+ return path .size >= 2 && ext__is_path_sep (path .data [0 ]) && ext__is_path_sep (path .data [1 ]);
2764+ }
2765+
2766+ // Find the UNC root (e.g., server and share from path)
2767+ // Returns the length of the root, or 0 if not a valid UNC path
2768+ static size_t ext__unc_root_length (Ext_StringSlice path ) {
2769+ if (!ext__is_unc_path (path )) return 0 ;
2770+ size_t pos = 2 ; // Skip initial separators
2771+
2772+ // Special cases: extended-length and device paths
2773+ if (pos < path .size && path .data [pos ] == '?' ) {
2774+ pos ++ ; // Skip '?'
2775+ if (pos < path .size && ext__is_path_sep (path .data [pos ])) {
2776+ pos ++ ; // Skip separator
2777+ // Check for drive letter format
2778+ if (pos + 1 < path .size && path .data [pos + 1 ] == ':' ) {
2779+ return pos + 2 ; // Include drive letter and colon
2780+ }
2781+ // Check for UNC format
2782+ if (pos + 3 < path .size && (path .data [pos ] == 'U' || path .data [pos ] == 'u' ) &&
2783+ (path .data [pos + 1 ] == 'N' || path .data [pos + 1 ] == 'n' ) &&
2784+ (path .data [pos + 2 ] == 'C' || path .data [pos + 2 ] == 'c' ) &&
2785+ ext__is_path_sep (path .data [pos + 3 ])) {
2786+ pos += 4 ; // Skip "UNC" and separator
2787+ // Fall through to find server and share
2788+ }
2789+ }
2790+ } else if (pos < path .size && path .data [pos ] == '.' ) {
2791+ pos ++ ; // Skip '.'
2792+ if (pos < path .size && ext__is_path_sep (path .data [pos ])) {
2793+ pos ++ ; // Skip separator
2794+ // Device format - find next separator or end
2795+ while (pos < path .size && !ext__is_path_sep (path .data [pos ])) {
2796+ pos ++ ;
2797+ }
2798+ return pos ;
2799+ }
2800+ }
2801+
2802+ // Standard UNC: find server name (up to next separator)
2803+ while (pos < path .size && !ext__is_path_sep (path .data [pos ])) {
2804+ pos ++ ;
2805+ }
2806+ if (pos >= path .size ) return 0 ; // Invalid: no share name
2807+
2808+ pos ++ ; // Skip separator after server
2809+
2810+ // Find share name (up to next separator or end)
2811+ while (pos < path .size && !ext__is_path_sep (path .data [pos ])) {
2812+ pos ++ ;
2813+ }
2814+
2815+ return pos ;
2816+ }
2817+ #endif
2818+
27492819Ext_StringSlice ext_ss_basename (Ext_StringSlice path ) {
27502820 // Strip trailing separators
27512821 while (path .size > 0 && ext__is_path_sep (path .data [path .size - 1 ])) {
27522822 path .size -- ;
27532823 }
2754- // Find last separator
27552824 for (size_t i = path .size ; i > 0 ; i -- ) {
27562825 if (ext__is_path_sep (path .data [i - 1 ])) {
27572826 return ext_ss_cut (path , i );
@@ -2761,23 +2830,65 @@ Ext_StringSlice ext_ss_basename(Ext_StringSlice path) {
27612830}
27622831
27632832Ext_StringSlice ext_ss_dirname (Ext_StringSlice path ) {
2833+ #ifdef EXT_WINDOWS
2834+ size_t unc_root = ext__unc_root_length (path );
2835+ if (unc_root > 0 ) {
2836+ // Strip trailing separators after UNC root
2837+ size_t end = path .size ;
2838+ while (end > unc_root && ext__is_path_sep (path .data [end - 1 ])) {
2839+ end -- ;
2840+ }
2841+ for (size_t i = end ; i > unc_root ; i -- ) {
2842+ if (ext__is_path_sep (path .data [i - 1 ])) {
2843+ size_t dir_end = i - 1 ;
2844+ while (dir_end > unc_root && ext__is_path_sep (path .data [dir_end - 1 ])) {
2845+ dir_end -- ;
2846+ }
2847+ return ext_ss_trunc (path , dir_end );
2848+ }
2849+ }
2850+
2851+ // No separator after UNC root - return the root itself
2852+ return ext_ss_trunc (path , unc_root );
2853+ }
2854+ #endif
2855+
27642856 // Strip trailing separators (but keep at least one char for root paths)
27652857 size_t end = path .size ;
27662858 while (end > 1 && ext__is_path_sep (path .data [end - 1 ])) {
27672859 end -- ;
27682860 }
2861+
27692862 // Find last separator
27702863 for (size_t i = end ; i > 0 ; i -- ) {
27712864 if (ext__is_path_sep (path .data [i - 1 ])) {
2772- // Strip trailing separators from result, but keep at least one for root "/"
27732865 size_t dir_end = i - 1 ;
27742866 while (dir_end > 0 && ext__is_path_sep (path .data [dir_end - 1 ])) {
27752867 dir_end -- ;
27762868 }
2777- if (dir_end == 0 ) dir_end = 1 ; /* root "/" */
2869+
2870+ // Handle root paths
2871+ if (dir_end == 0 ) {
2872+ dir_end = 1 ; // Unix root "/"
2873+ }
2874+ #ifdef EXT_WINDOWS
2875+ // Windows: if we're at position 1 and have a drive letter, include the colon
2876+ else if (dir_end == 1 && path .size >= 2 && path .data [1 ] == ':' ) {
2877+ dir_end = 2 ; // Windows drive "C:"
2878+ }
2879+ #endif
27782880 return ext_ss_trunc (path , dir_end );
27792881 }
27802882 }
2883+
2884+ #ifdef EXT_WINDOWS
2885+ // If path is just a drive letter (e.g., "C:"), return it as-is
2886+ if (ext__is_drive_letter (path )) {
2887+ return ext_ss_trunc (path , 2 );
2888+ }
2889+ #endif
2890+
2891+ // No separator found
27812892 return (Ext_StringSlice ){0 , path .data };
27822893}
27832894
@@ -2795,7 +2906,19 @@ Ext_StringSlice ext_ss_extension(Ext_StringSlice path) {
27952906
27962907void ext_sb_append_path (Ext_StringBuffer * sb , Ext_StringSlice component ) {
27972908 if (sb -> size > 0 && !ext__is_path_sep (sb -> items [sb -> size - 1 ])) {
2909+ #ifdef EXT_WINDOWS
2910+ // Use the same separator style as the existing path
2911+ char sep = '/' ;
2912+ for (size_t i = 0 ; i < sb -> size ; i ++ ) {
2913+ if (sb -> items [i ] == '\\' ) {
2914+ sep = '\\' ;
2915+ break ;
2916+ }
2917+ }
2918+ ext_sb_append_char (sb , sep );
2919+ #else
27982920 ext_sb_append_char (sb , '/' );
2921+ #endif
27992922 }
28002923 ext_sb_append (sb , component .data , component .size );
28012924}
0 commit comments