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