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