names.ha (5147B)
1 // License: MPL-2.0 2 // (c) 2022 Alexey Yerin <yyp@disroot.org> 3 // (c) 2021-2022 Drew DeVault <sir@cmpwn.com> 4 // (c) 2021 Ember Sawady <ecs@d2evs.net> 5 use bytes; 6 use strings; 7 8 // Returns the directory name for a given path. For a path to a file name, this 9 // returns the directory in which that file resides. For a path to a directory, 10 // this returns the path to its parent directory. If the path consists solely of 11 // the target's path separator, a string to the path is returned unmodified. If 12 // the path is empty, "." is returned. The return value is either borrowed from 13 // the input or statically allocated; use [[strings::dup]] to extend its 14 // lifetime or modify it. 15 export fn dirname(path: (str | *buffer)) const str = { 16 let path = getstring(path); 17 if (path == "") { 18 return "."; 19 }; 20 let trimmed = strings::rtrim(path, PATHSEP: u32: rune); 21 if (trimmed == "") { 22 return pathsepstr; 23 }; 24 let b = strings::toutf8(trimmed); 25 let i = match (bytes::rindex(b, PATHSEP)) { 26 case void => 27 return "."; 28 case let z: size => 29 yield z; 30 }; 31 if (i == 0) { 32 i += 1; 33 }; 34 path = strings::fromutf8_unsafe(b[..i]); 35 path = strings::rtrim(path, PATHSEP: u32: rune); 36 if (path == "") { 37 return pathsepstr; 38 }; 39 return path; 40 }; 41 42 @test fn dirname() void = { 43 assertpatheql(&dirname, pathsepstr, "", "foo"); 44 assertpatheql(&dirname, pathsepstr, pathsepstr); 45 assertpatheql(&dirname, pathsepstr, "", "", ""); 46 assertpatheql(&dirname, pathsepstr, "", "", "", ""); 47 assertpatheql(&dirname, "foo", "foo", "bar"); 48 assertpatheql(&dirname, ".", ""); 49 assertpatheql(&dirname, ".", "foo"); 50 assertpatheql(&dirname, ".", "foo", ""); 51 assertpatheql(&dirname, ".", "foo", "", ""); 52 assertpatheql(&dirname, pathsepstr, "", "", "", "foo"); 53 assertpatheql(&dirname, pathsepstr, "", "", "", "foo", "", ""); 54 let expected = strings::concat(pathsepstr, "foo"); 55 assertpatheql(&dirname, expected, "", "foo", "bar"); 56 free(expected); 57 expected = strings::concat(pathsepstr, pathsepstr, "foo"); 58 assertpatheql(&dirname, expected, "", "", "foo", "", "", "bar", "", ""); 59 free(expected); 60 }; 61 62 // Returns the final component of a given path. For a path to a file name, this 63 // returns the file name. For a path to a directory, this returns the directory 64 // name. If the path consists solely of the target's path separator, a string of 65 // the path is returned unmodified. If the path is empty, "." is returned. The 66 // return value is either borrowed from the input or statically allocated; use 67 // [[strings::dup]] to extend its lifetime or modify it. 68 export fn basename(path: (str | *buffer)) const str = { 69 let path = getstring(path); 70 if (path == "") { 71 return "."; 72 }; 73 let trimmed = strings::rtrim(path, PATHSEP: u32: rune); 74 if (trimmed == "") { 75 return pathsepstr; 76 }; 77 let b = strings::toutf8(trimmed); 78 let i = match (bytes::rindex(b, PATHSEP)) { 79 case void => 80 return trimmed; 81 case let z: size => 82 yield if (z + 1 < len(b)) z + 1z else 0z; 83 }; 84 return strings::fromutf8_unsafe(b[i..]); 85 }; 86 87 @test fn basename() void = { 88 assertpatheql(&basename, "bar", "", "foo", "bar"); 89 assertpatheql(&basename, "foo", "", "foo"); 90 assertpatheql(&basename, pathsepstr, pathsepstr); 91 assertpatheql(&basename, pathsepstr, "", "", ""); 92 assertpatheql(&basename, pathsepstr, "", "", "", ""); 93 assertpatheql(&basename, "bar", "foo", "bar"); 94 assertpatheql(&basename, "bar", "foo", "bar", "", ""); 95 assertpatheql(&basename, "foo", "foo"); 96 assertpatheql(&basename, "foo", "foo", ""); 97 assertpatheql(&basename, "bar", "foo", "bar", ""); 98 assertpatheql(&basename, ".", ""); 99 }; 100 101 // Returns the file name and extension for a path. The return value is borrowed 102 // from the input, see [[strings::dup]] to extend its lifetime. 103 // 104 // The extension includes the '.' character. 105 // 106 // extension("foo/example") => ("example", "") 107 // extension("foo/example.txt") => ("example", ".txt") 108 // extension("foo/example.tar.gz") => ("example.tar", ".gz") 109 export fn extension(p: (str | *buffer)) (str, str) = { 110 let p = getstring(p); 111 if (p == "") { 112 return ("", ""); 113 }; 114 let p = basename(p); 115 let b = strings::toutf8(p); 116 if (len(b) == 0 || b[len(b) - 1] == PATHSEP) { 117 return (p, ""); 118 }; 119 let i = match (bytes::rindex(b, '.')) { 120 case void => 121 return (p, ""); 122 case let z: size => 123 yield z; 124 }; 125 let e = b[i..]; 126 let n = b[..i]; 127 return (strings::fromutf8_unsafe(n), strings::fromutf8_unsafe(e)); 128 }; 129 130 @test fn extension() void = { 131 assertpatheql(&ext0, "", ""); 132 assertpatheql(&ext1, "", ""); 133 assertpatheql(&ext0, "bar", "foo", "bar"); 134 assertpatheql(&ext1, "", "foo", "bar"); 135 assertpatheql(&ext0, "bar", "foo", "bar.txt"); 136 assertpatheql(&ext1, ".txt", "foo", "bar.txt"); 137 assertpatheql(&ext0, "bar.tar", "foo", "bar.tar.gz"); 138 assertpatheql(&ext1, ".gz", "foo", "bar.tar.gz"); 139 assertpatheql(&ext0, "baz", "foo.bar", "baz.ha"); 140 assertpatheql(&ext1, ".ha", "foo.bar", "baz.ha"); 141 }; 142 143 fn assertpatheql( 144 func: *fn(path: (str | *buffer)) const str, 145 expected: str, 146 path: str... 147 ) void = { 148 const s = strings::join(pathsepstr, path...); 149 assert(func(s) == expected); 150 free(s); 151 }; 152 153 fn ext0(p: (str | *buffer)) const str = extension(p).0; 154 fn ext1(p: (str | *buffer)) const str = extension(p).1;