1 /++ File system path and name types and their operations. 2 3 See: https://en.cppreference.com/w/cpp/filesystem/path 4 See: https://hackage.haskell.org/package/FileSystem-1.0.0/docs/System-FileSystem-Types.html 5 See_Also: `dmd.root.filename` 6 +/ 7 module nxt.path; 8 9 @safe: 10 11 /++ Path. 12 13 The concept of a "pure path" doesn't need to be modelled in D as 14 it has `pure` functions. See 15 https://docs.python.org/3/library/pathlib.html#pure-paths. 16 17 See: SUMO:`ComputerPath`. 18 +/ 19 struct Path { 20 this(string str, in bool normalize = false) pure nothrow @nogc { 21 import nxt.algorithm.mutation : stripRight; 22 import std.path : dirSeparator; 23 this.str = normalize ? str.stripRight(dirSeparator) : str; 24 } 25 string str; 26 pure nothrow @nogc: 27 bool opCast(T : bool)() const scope => str !is null; 28 string toString() const @property => str; 29 } 30 31 /// 32 @safe pure nothrow unittest { 33 assert(Path("/usr/bin/").toString == "/usr/bin/"); 34 } 35 36 /++ (Regular) file path (on local file system). 37 See: https://hackage.haskell.org/package/filepath-1.5.0.0/docs/System-FilePath.html#t:FilePath 38 +/ 39 struct FilePath { 40 this(string str, in bool normalize = false) pure nothrow @nogc { 41 this.path = Path(str, normalize); 42 } 43 Path path; 44 alias path this; 45 } 46 47 /// 48 @safe pure nothrow unittest { 49 assert(FilePath("/usr/bin/") == FilePath("/usr/bin/")); 50 assert(FilePath("/usr/bin/") == Path("/usr/bin/")); 51 assert(FilePath("foo") == Path("foo")); 52 assert(FilePath("foo", false).str == "foo"); 53 assert(FilePath("foo", true).str == "foo"); 54 assert(Path("/etc/", false).str == "/etc/"); 55 assert(Path("/etc/", true).str == "/etc"); 56 assert(Path("foo", true).str == "foo"); 57 } 58 59 /++ Directory path (on local file system). 60 See: SUMO:`ComputerDirectory`. 61 +/ 62 struct DirPath { 63 this(string path, in bool normalize = false) pure nothrow @nogc { 64 this.path = Path(path, normalize); 65 } 66 Path path; 67 alias path this; 68 } 69 70 /// 71 @safe pure nothrow unittest { 72 assert(DirPath("/etc") == Path("/etc")); 73 assert(DirPath("/etc") == DirPath("/etc")); 74 assert(DirPath("/etc/", false).str == "/etc/"); 75 assert(DirPath("/etc/", true).str == "/etc"); 76 } 77 78 /++ File name (either local or remote). 79 +/ 80 struct FileName { 81 import nxt.algorithm.searching : canFind; 82 this(string str) pure nothrow @nogc in (!str.canFind('/')) { 83 this.str = str; 84 } 85 string str; 86 pure nothrow @nogc: 87 bool opCast(T : bool)() const scope => str !is null; 88 string toString() const @property => str; 89 } 90 91 /// 92 pure nothrow @safe unittest { 93 assert(FileName(".emacs").str == ".emacs"); 94 assert(FileName(".emacs").toString == ".emacs"); 95 } 96 97 /++ Directory name (either local or remote). 98 +/ 99 struct DirName { 100 import nxt.algorithm.searching : canFind; 101 this(string str) pure nothrow @nogc in (!str.canFind('/')) { 102 this.str = str; 103 } 104 string str; 105 pure nothrow @nogc: 106 bool opCast(T : bool)() const scope => str !is null; 107 string toString() const @property => str; 108 } 109 110 /// 111 pure nothrow @safe unittest { 112 assert(DirName(".emacs.d").str == ".emacs.d"); 113 assert(DirName(".emacs.d").toString == ".emacs.d"); 114 } 115 116 /++ Execute file path (on local file system). 117 +/ 118 struct ExePath { 119 this(string path) pure nothrow @nogc { 120 this.path = Path(path); 121 } 122 Path path; 123 alias path this; 124 } 125 126 /// 127 @safe pure nothrow unittest { 128 assert(ExePath("/usr/bin/") == Path("/usr/bin/")); 129 } 130 131 /++ Written (input) path (on local file system). +/ 132 struct RdPath { 133 this(string str) pure nothrow @nogc { 134 this.path = Path(str); 135 } 136 Path path; 137 alias path this; 138 } 139 140 /// 141 @safe pure nothrow unittest { 142 assert(RdPath("/usr/bin/") == Path("/usr/bin/")); 143 } 144 145 /++ Written (output) path (on local file system). +/ 146 struct WrPath { 147 this(string str) pure nothrow @nogc { 148 this.path = Path(str); 149 } 150 Path path; 151 alias path this; 152 } 153 154 /// 155 @safe pure nothrow unittest { 156 assert(WrPath("/usr/bin/") == Path("/usr/bin/")); 157 } 158 159 private import std.path : std_expandTilde = expandTilde; 160 161 /++ Expand tilde in `a`. 162 TODO: remove `@trusted` when scope inference is works. 163 +/ 164 FilePath expandTilde(scope return FilePath a) nothrow @trusted => typeof(return)(std_expandTilde(a.path.str)); 165 /// ditto 166 DirPath expandTilde(scope return DirPath a) nothrow @trusted => typeof(return)(std_expandTilde(a.path.str)); 167 168 /// 169 @safe nothrow unittest { 170 assert(FilePath("~").expandTilde); 171 assert(DirPath("~").expandTilde); 172 } 173 174 private import std.path : std_buildPath = buildPath; 175 176 /// Build path `a`/`b`. 177 FilePath buildPath(DirPath a, FilePath b) pure nothrow => typeof(return)(std_buildPath(a.path.str, b.path.str)); 178 /// ditto 179 DirPath buildPath(DirPath a, DirPath b) pure nothrow => typeof(return)(std_buildPath(a.path.str, b.path.str)); 180 /// ditto 181 DirPath buildPath(DirPath a, DirName b) pure nothrow => typeof(return)(std_buildPath(a.path.str, b.str)); 182 /// ditto 183 FilePath buildPath(DirPath a, FileName b) pure nothrow => typeof(return)(std_buildPath(a.path.str, b.str)); 184 185 /// 186 @safe pure nothrow unittest { 187 assert(DirPath("/etc").buildPath(FileName("foo")) == FilePath("/etc/foo")); 188 assert(DirPath("/etc").buildPath(FilePath("foo")) == FilePath("/etc/foo")); 189 assert(DirPath("/usr").buildPath(DirName("bin")) == DirPath("/usr/bin")); 190 assert(DirPath("/usr").buildPath(DirPath("bin")) == DirPath("/usr/bin")); 191 assert(DirPath("/usr").buildPath(DirPath("/bin")) == DirPath("/bin")); 192 } 193 194 private import std.path : std_buildNormalizedPath = buildNormalizedPath; 195 196 /// Build path `a`/`b`. 197 FilePath buildNormalizedPath(DirPath a, FilePath b) pure nothrow => typeof(return)(std_buildNormalizedPath(a.path.str, b.path.str)); 198 /// ditto 199 DirPath buildNormalizedPath(DirPath a, DirPath b) pure nothrow => typeof(return)(std_buildNormalizedPath(a.path.str, b.path.str)); 200 /// ditto 201 DirPath buildNormalizedPath(DirPath a, DirName b) pure nothrow => typeof(return)(std_buildNormalizedPath(a.path.str, b.str)); 202 /// ditto 203 FilePath buildNormalizedPath(DirPath a, FileName b) pure nothrow => typeof(return)(std_buildNormalizedPath(a.path.str, b.str)); 204 205 /// 206 @safe pure nothrow unittest { 207 assert(DirPath("/etc").buildNormalizedPath(FileName("foo")) == FilePath("/etc/foo")); 208 assert(DirPath("/etc").buildNormalizedPath(FilePath("foo")) == FilePath("/etc/foo")); 209 assert(DirPath("/usr").buildNormalizedPath(DirName("bin")) == DirPath("/usr/bin")); 210 assert(DirPath("/usr").buildNormalizedPath(DirPath("bin")) == DirPath("/usr/bin")); 211 assert(DirPath("/usr").buildNormalizedPath(DirPath("/bin")) == DirPath("/bin")); 212 } 213 214 /++ URL. 215 +/ 216 struct URL { 217 string str; 218 pure nothrow @nogc: 219 bool opCast(T : bool)() const scope => str !is null; 220 string toString() const @property => str; 221 } 222 223 /// 224 @safe pure nothrow unittest { 225 assert(URL("www.sunet.se").toString == "www.sunet.se"); 226 } 227 228 /++ File URL. 229 +/ 230 struct FileURL { 231 URL url; 232 pure nothrow @nogc: 233 this(string str) { this.url = URL(str); } 234 this(FilePath path) { this.url = URL(path.str); } 235 bool opCast(T : bool)() const scope => url.str !is null; 236 string toString() const @property => url.str; 237 } 238 239 /// 240 @safe pure nothrow unittest { 241 assert(FileURL("www.sunet.se").toString == "www.sunet.se"); 242 assert(FileURL(FilePath("/etc/passwd")).toString == "/etc/passwd"); 243 } 244 245 /++ Directory URL. 246 +/ 247 struct DirURL { 248 URL url; 249 pure nothrow @nogc: 250 this(string str) { this.url = URL(str); } 251 this(DirPath path) { this.url = URL(path.str); } 252 bool opCast(T : bool)() const scope => url.str !is null; 253 string toString() const @property => url.str; 254 } 255 256 /// 257 @safe pure nothrow unittest { 258 assert(DirURL("www.sunet.se").toString == "www.sunet.se"); 259 assert(DirURL(DirPath("/etc/")).toString == "/etc/"); 260 } 261 262 /++ File URL and Offset (in bytes). 263 +/ 264 struct FileURLOffset { 265 import nxt.offset : Offset; 266 FileURL url; 267 Offset offset; 268 } 269 270 /++ File URL and Region (in bytes). 271 +/ 272 struct FileURLRegion { 273 import nxt.region : Region; 274 FileURL url; 275 Region region; 276 } 277 278 private import std.path : std_baseName = baseName; 279 280 /// Get basename of `a`. 281 FileName baseName(FilePath a) pure nothrow @nogc => typeof(return)(std_baseName(a.str)); 282 /// ditto 283 DirName baseName(DirPath a) pure nothrow @nogc => typeof(return)(std_baseName(a.str)); 284 /// ditto 285 DirName baseName(URL a) pure nothrow @nogc => typeof(return)(std_baseName(a.str)); 286 /// ditto 287 FileName baseName(FileURL a) pure nothrow @nogc => typeof(return)(std_baseName(a.url.str)); 288 /// ditto 289 DirName baseName(DirURL a) pure nothrow @nogc => typeof(return)(std_baseName(a.url.str)); 290 291 /// 292 version (Posix) nothrow @safe unittest { 293 assert(FilePath("/etc/foo").baseName.str == "foo"); 294 assert(DirPath("/etc/").baseName.str == "etc"); 295 const dmd = "https://github.com/dlang/dmd/"; 296 assert(URL(dmd).baseName.str == "dmd"); 297 assert(FileURL(dmd).baseName.str == "dmd"); 298 assert(DirURL(dmd).baseName.str == "dmd"); 299 } 300 301 private import std.file : std_exists = exists; 302 303 /// Check if `a` exists. 304 bool exists(in Path a) nothrow @nogc => typeof(return)(std_exists(a.str)); 305 /// ditto 306 bool exists(in FilePath a) nothrow @nogc => typeof(return)(std_exists(a.str)); 307 /// ditto 308 bool exists(in DirPath a) nothrow @nogc => typeof(return)(std_exists(a.str)); 309 /// ditto 310 bool exists(in FileURL a) nothrow @nogc => typeof(return)(std_exists(a.url.str)); 311 /// ditto 312 bool exists(in DirURL a) nothrow @nogc => typeof(return)(std_exists(a.url.str)); 313 /// ditto 314 bool exists(in FileName a) nothrow @nogc => typeof(return)(std_exists(a.str)); 315 /// ditto 316 bool exists(in DirName a) nothrow @nogc => typeof(return)(std_exists(a.str)); 317 318 /// verify `a.toString` when `a` being `scope` parameter 319 version (none) // TODO: Remove or make compile 320 @safe pure nothrow @nogc unittest { 321 import std.meta : AliasSeq; 322 323 static foreach (T; AliasSeq!(Path, FilePath, DirPath, FileName, DirName)) {{ 324 static void f(in T a) { const _ = a.toString; } 325 f(T.init); 326 }} 327 } 328 329 /// 330 version (Posix) nothrow @safe unittest { 331 assert( Path("/etc/").exists); 332 assert(!Path("/etcxyz/").exists); 333 assert( DirPath("/etc/").exists); 334 assert( DirURL("/etc/").exists); 335 assert(!DirPath("/etcxyz/").exists); 336 assert( FilePath("/etc/passwd").exists); 337 assert( FileURL("/etc/passwd").exists); 338 assert(!FileName("dsfdsfdsfdsfdfdsf").exists); 339 assert(!DirName("dsfdsfdsfdsfdfdsf").exists); 340 }