1 /** FileExtensions to std.file. 2 Copyright: Per Nordlöw 2024-. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: $(WEB Per Nordlöw) 5 */ 6 module nxt.file; 7 8 import std.stdio : File; 9 import nxt.path : FileName, FilePath, DirPath; 10 import nxt.pattern : Node; 11 12 @safe: 13 14 /++ Type of file. 15 +/ 16 struct FileType { 17 DataFormat format; ///< Format associated with file type. 18 alias format this; 19 Node fileNamePattern; ///< Pattern matching file name(s) often associated. 20 } 21 22 typeof(FileName.str) matchFirst(scope return /+ref+/ FileName input, const Node node) pure nothrow /+@nogc+/ { 23 import nxt.pattern : matchFirst; 24 return input.str.matchFirst(node); 25 } 26 27 /++ Get pattern that matches a file name ending with '.'~`s`. +/ 28 static Node fileExtension(string s) pure nothrow { 29 import nxt.pattern : seq, lit, eob; 30 return seq(lit('.'), lit(s), eob()); 31 } 32 33 /// ditto 34 @safe pure unittest { 35 auto app_d = FileName("app.d"); 36 assert( app_d.matchFirst(fileExtension("d"))); 37 assert(!app_d.matchFirst(fileExtension("c"))); 38 assert(!app_d.matchFirst(fileExtension("cpp"))); 39 auto app = FileName("app"); 40 assert(!app.matchFirst(fileExtension("d"))); 41 assert(!app.matchFirst(fileExtension("c"))); 42 assert(!app.matchFirst(fileExtension("cpp"))); 43 auto app_c = FileName("app.c"); 44 assert(!app_c.matchFirst(fileExtension("d"))); 45 assert( app_c.matchFirst(fileExtension("c"))); 46 assert(!app_c.matchFirst(fileExtension("cpp"))); 47 } 48 49 /++ (Data) Format of file contents. 50 51 The `name` can be either a programming language such as "C", 52 "C++", "D", etc or a data format such as "JSON", "XML" etc. 53 54 See: https://en.wikipedia.org/wiki/File_format 55 +/ 56 struct DataFormat { 57 string name; 58 alias name this; 59 /// Pattern matching file contents often associated. 60 Node contentPattern; 61 } 62 63 /++ Extension of filename. 64 See: https://en.wikipedia.org/wiki/Filename_extension 65 +/ 66 struct FileExtension { 67 string value; 68 alias value this; 69 } 70 71 /** Read file $(D path) into raw array with one extra terminating zero byte. 72 * 73 * This extra terminating zero (`null`) byte at the end is typically used as a 74 * sentinel value to speed up textual parsers or when characters are sent to a 75 * C-function taking a zero-terminated string as input. 76 * 77 * TODO: Add or merge to Phobos? 78 * 79 * See_Also: https://en.wikipedia.org/wiki/Sentinel_value 80 * See_Also: http://forum.dlang.org/post/pdzxpkusvifelumkrtdb@forum.dlang.org 81 */ 82 immutable(void)[] rawReadZ(FilePath path) @safe { 83 return File(path.str, `rb`).rawReadZ(); 84 } 85 /// ditto 86 immutable(void)[] rawReadZ(scope File file) @trusted 87 { 88 import std.array : uninitializedArray; 89 90 alias Data = ubyte[]; 91 Data data = uninitializedArray!(Data)(file.size + 1); // one extra for terminator 92 93 file.rawRead(data); 94 data[file.size] = '\0'; // zero terminator for sentinel 95 96 import std.exception : assumeUnique; 97 return assumeUnique(data); 98 } 99 100 /// 101 version (Posix) 102 @safe unittest { 103 import nxt.algorithm.searching : endsWith; 104 const d = cast(const(char)[]) FilePath(`/etc/passwd`).rawReadZ(); 105 assert(d.endsWith('\0')); // has 0-terminator 106 } 107 108 /++ Find path for `a` (or `FilePath.init` if not found) in `pathVariableName`. 109 TODO: Add caching of result and detect changes via inotify. 110 +/ 111 FilePath findExecutable(FileName a, scope const(char)[] pathVariableName = "PATH") { 112 return findFileInPath(a, "PATH"); 113 } 114 115 /// 116 version (none) 117 @safe unittest { 118 assert(findExecutable(FileName("ls")) == FilePath("/usr/bin/ls")); 119 } 120 121 /++ Find path for `a` (or `FilePath.init` if not found) in `pathVariableName`. 122 TODO: Add caching of result and detect changes via inotify. 123 +/ 124 FilePath findFileInPath(FileName a, scope const(char)[] pathVariableName) { 125 import std.algorithm : splitter; 126 import std.process : environment; 127 const envPATH = environment.get(pathVariableName, ""); 128 foreach (const p; envPATH.splitter(':')) { 129 import nxt.path : buildPath, exists; 130 const path = DirPath(p).buildPath(a); 131 if (path.exists) 132 return path; // pick first match 133 } 134 return typeof(return).init; 135 } 136 137 /++ Get path to default temporary directory. 138 See_Also: `std.file.tempDir` 139 See: https://forum.dlang.org/post/gg9kds$1at0$1@digitalmars.com 140 +/ 141 DirPath tempDir() { 142 import std.file : std_tempDir = tempDir; 143 return typeof(return)(std_tempDir); 144 } 145 146 /// 147 @safe unittest { 148 version (Posix) { 149 assert(tempDir().str == "/tmp/"); 150 } 151 } 152 153 /++ Get path to home directory. 154 See_Also: `tempDir` 155 See: https://forum.dlang.org/post/gg9kds$1at0$1@digitalmars.com 156 +/ 157 DirPath homeDir() { 158 import std.process : environment; 159 version(Windows) { 160 // On Windows, USERPROFILE is typically used, but HOMEPATH is an alternative 161 if (const home = environment.get("USERPROFILE")) 162 return typeof(return)(home); 163 // Fallback to HOMEDRIVE + HOMEPATH 164 const homeDrive = environment.get("HOMEDRIVE"); 165 const homePath = environment.get("HOMEPATH"); 166 if (homeDrive && homePath) 167 return typeof(return)(buildPath(homeDrive, homePath)); 168 } else { 169 if (const home = environment.get("HOME")) 170 return typeof(return)(home); 171 } 172 throw new Exception("Home directory environment variable is not set."); 173 } 174 175 /// 176 @safe unittest { 177 version (Posix) { 178 import std.path : expandTilde; 179 assert(homeDir().str == "~".expandTilde); 180 } 181 }