1 module dscanner.utils; 2 3 import std.array : appender, uninitializedArray; 4 import std.stdio : stdin, stderr, File; 5 import std.conv : to; 6 import std.encoding : BOM, BOMSeq, EncodingException, getBOM; 7 import std.format : format; 8 import std.file : exists, read; 9 10 private void processBOM(ubyte[] sourceCode, string fname) 11 { 12 enum spec = "D-Scanner does not support %s-encoded files (%s)"; 13 const BOMSeq bs = sourceCode.getBOM; 14 with(BOM) switch (bs.schema) 15 { 16 case none, utf8: 17 break; 18 default: 19 throw new EncodingException(spec.format(bs.schema, fname)); 20 } 21 sourceCode = sourceCode[bs.sequence.length .. $]; 22 } 23 24 unittest 25 { 26 import std.exception : assertThrown, assertNotThrown; 27 import std.encoding : bomTable; 28 import std.traits : EnumMembers; 29 30 foreach(m ; EnumMembers!BOM) 31 { 32 auto sc = bomTable[m].sequence.dup; 33 if (m != BOM.none && m != BOM.utf8) 34 { 35 assertThrown!(EncodingException)(processBOM(sc, "")); 36 } 37 else 38 { 39 assertNotThrown!(EncodingException)(processBOM(sc, "")); 40 } 41 } 42 } 43 44 ubyte[] readStdin() 45 { 46 auto sourceCode = appender!(ubyte[])(); 47 ubyte[4096] buf; 48 while (true) 49 { 50 auto b = stdin.rawRead(buf); 51 if (b.length == 0) 52 break; 53 sourceCode.put(b); 54 } 55 sourceCode.data.processBOM("stdin"); 56 return sourceCode.data; 57 } 58 59 ubyte[] readFile(string fileName) 60 { 61 if (fileName == "stdin") 62 return readStdin(); 63 if (!exists(fileName)) 64 { 65 stderr.writefln("%s does not exist", fileName); 66 return []; 67 } 68 File f = File(fileName); 69 ubyte[] sourceCode; 70 sourceCode = cast(ubyte[]) fileName.read(); 71 sourceCode.processBOM(fileName); 72 return sourceCode; 73 } 74 75 string[] expandArgs(string[] args) 76 { 77 import std.file : isFile, FileException, dirEntries, SpanMode; 78 import std.algorithm.iteration : map; 79 import std.algorithm.searching : endsWith; 80 81 // isFile can throw if it's a broken symlink. 82 bool isFileSafe(T)(T a) 83 { 84 try 85 return isFile(a); 86 catch (FileException) 87 return false; 88 } 89 90 string[] rVal; 91 if (args.length == 1) 92 args ~= "."; 93 foreach (arg; args[1 .. $]) 94 { 95 if (arg == "stdin" || isFileSafe(arg)) 96 rVal ~= arg; 97 else 98 foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name)) 99 { 100 if (isFileSafe(item) && (item.endsWith(`.d`) || item.endsWith(`.di`))) 101 rVal ~= item; 102 else 103 continue; 104 } 105 } 106 return rVal; 107 } 108 109 /** 110 * Allows to build access chains of class members as done with the $(D ?.) operator 111 * in other languages. In the chain, any $(D null) member that is a class instance 112 * or that returns one, has for effect to shortcut the complete evaluation. 113 * 114 * This function is copied from https://github.com/BBasile/iz to avoid a new submodule. 115 * Any change made to this copy should also be applied to the origin. 116 * 117 * Params: 118 * M = The class type of the chain entry point. 119 * 120 * Bugs: 121 * Assigning a member only works with $(D unwrap). 122 * 123 */ 124 struct SafeAccess(M) 125 if (is(M == class)) 126 { 127 M m; 128 129 @disable this(); 130 131 /** 132 * Instantiate. 133 * 134 * Params: 135 * m = An instance of the entry point type. It is usually only 136 * $(D null) when the constructor is used internally, to build 137 * the chain. 138 */ 139 this(M m) 140 { 141 this.m = m; 142 } 143 144 alias m this; 145 /// Unprotect the class instance. 146 alias unwrap = m; 147 148 /// Handles safe access. 149 auto ref opDispatch(string member, A...)(auto ref A a) 150 { 151 import std.traits : ReturnType; 152 alias T = typeof(__traits(getMember, m, member)); 153 static if (is(T == class)) 154 { 155 return (!m || !__traits(getMember, m, member)) 156 ? SafeAccess!T(null) 157 : SafeAccess!T(__traits(getMember, m, member)); 158 } 159 else 160 { 161 import std.traits : ReturnType, Parameters, isFunction; 162 static if (isFunction!T) 163 { 164 // otherwise there's a missing return statement. 165 alias R = ReturnType!T; 166 static if (!is(R == void) && 167 !(is(R == class) && Parameters!T.length == 0)) 168 pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ "): error, " ~ 169 "only `void function`s or `class` getters can be called without unwrap"); 170 171 static if (is(R == class)) 172 { 173 return (m is null) 174 ? SafeAccess!R(null) 175 : SafeAccess!R(__traits(getMember, m, member)(a)); 176 } 177 else 178 { 179 if (m) 180 __traits(getMember, m, member)(a); 181 } 182 } 183 else 184 { 185 if (m) 186 __traits(getMember, m, member) = a; 187 } 188 } 189 } 190 } 191 /// General usage 192 @safe unittest 193 { 194 class LongLineOfIdent3{int foo; void setFoo(int v) @safe{foo = v;}} 195 class LongLineOfIdent2{LongLineOfIdent3 longLineOfIdent3;} 196 class LongLineOfIdent1{LongLineOfIdent2 longLineOfIdent2;} 197 class Root {LongLineOfIdent1 longLineOfIdent1;} 198 199 SafeAccess!Root sar = SafeAccess!Root(new Root); 200 // without the SafeAccess we would receive a SIGSEGV here 201 sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(0xDEADBEEF); 202 203 bool notAccessed = true; 204 // the same with `&&` whould be much longer 205 if (LongLineOfIdent3 a = sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3) 206 { 207 notAccessed = false; 208 } 209 assert(notAccessed); 210 211 // checks that forwarding actually works 212 sar.m.longLineOfIdent1 = new LongLineOfIdent1; 213 sar.m.longLineOfIdent1.longLineOfIdent2 = new LongLineOfIdent2; 214 sar.m.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3 = new LongLineOfIdent3; 215 216 sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(42); 217 assert(sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.unwrap.foo == 42); 218 } 219 220 /** 221 * IFTI helper for $(D SafeAccess). 222 * 223 * Returns: 224 * $(D m) with the ability to safely access its members that are class 225 * instances. 226 */ 227 auto ref safeAccess(M)(M m) 228 { 229 return SafeAccess!M(m); 230 }