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, canFind;
88 	import std.path : dirSeparator;
89 
90 	// isFile can throw if it's a broken symlink.
91 	bool isFileSafe(T)(T a)
92 	{
93 		try
94 			return isFile(a);
95 		catch (FileException)
96 			return false;
97 	}
98 
99 	string[] rVal;
100 	if (args.length == 1)
101 		args ~= ".";
102 	foreach (arg; args[1 .. $])
103 	{
104 		if (arg == "stdin" || isFileSafe(arg))
105 			rVal ~= arg;
106 		else
107 			foreach (item; dirEntries(arg, SpanMode.breadth).map!(a => a.name))
108 			{
109 				if (isFileSafe(item) && (item.endsWith(`.d`) || item.endsWith(`.di`)) && !item.canFind(dirSeparator ~ '.'))
110 					rVal ~= item;
111 				else
112 					continue;
113 			}
114 	}
115 	return rVal;
116 }
117 
118 /**
119  * Allows to build access chains of class members as done with the $(D ?.) operator
120  * in other languages. In the chain, any $(D null) member that is a class instance
121  * or that returns one, has for effect to shortcut the complete evaluation.
122  *
123  * This function is copied from https://github.com/BBasile/iz to avoid a new submodule.
124  * Any change made to this copy should also be applied to the origin.
125  *
126  * Params:
127  *      M = The class type of the chain entry point.
128  *
129  * Bugs:
130  *      Assigning a member only works with $(D unwrap).
131  *
132  */
133 struct SafeAccess(M)
134 if (is(M == class))
135 {
136 	M m;
137 
138 	@disable this();
139 
140 	/**
141 	 * Instantiate.
142 	 *
143 	 * Params:
144 	 *      m = An instance of the entry point type. It is usually only
145 	 *      $(D null) when the constructor is used internally, to build
146 	 *      the chain.
147 	 */
148 	this(M m)
149 	{
150 		this.m = m;
151 	}
152 
153 	alias m this;
154 	/// Unprotect the class instance.
155 	alias unwrap = m;
156 
157 	/// Allows cast to interfaces and classes inside the chain.
158 	auto ref as(A)() @trusted
159 	if (!__traits(hasMember, M, "as") && (is(A == class) || is(A == interface)))
160 	{
161 		return SafeAccess!(A)(cast(A) m);
162 	}
163 
164 	/// Handles safe access.
165 	auto ref opDispatch(string member, A...)(auto ref A a)
166 	{
167 		import std.traits : ReturnType;
168 		alias T = typeof(__traits(getMember, m, member));
169 		static if (is(T == class))
170 		{
171 			return (!m || !__traits(getMember, m, member))
172 				? SafeAccess!T(null)
173 				: SafeAccess!T(__traits(getMember, m, member));
174 		}
175 		else
176 		{
177 			import std.traits : ReturnType, Parameters, isFunction;
178 			static if (isFunction!T)
179 			{
180 				// otherwise there's a missing return statement.
181 				alias R = ReturnType!T;
182 				static if (!is(R == void) &&
183 					!(is(R == class) && Parameters!T.length == 0))
184 						pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ "): error, " ~
185 						"only `void function`s or `class` getters can be called without unwrap");
186 
187 				static if (is(R == class))
188 				{
189 					return (m is null)
190 						? SafeAccess!R(null)
191 						: SafeAccess!R(__traits(getMember, m, member)(a));
192 				}
193 				else
194 				{
195 					if (m)
196 						__traits(getMember, m, member)(a);
197 				}
198 			}
199 			else
200 			{
201 				if (m)
202 					__traits(getMember, m, member) = a;
203 			}
204 		}
205 	}
206 }
207 /// General usage
208 @safe unittest
209 {
210 	class LongLineOfIdent3{int foo; void setFoo(int v) @safe{foo = v;}}
211 	class LongLineOfIdent2{LongLineOfIdent3 longLineOfIdent3;}
212 	class LongLineOfIdent1{LongLineOfIdent2 longLineOfIdent2;}
213 	class Root {LongLineOfIdent1 longLineOfIdent1;}
214 
215 	SafeAccess!Root sar = SafeAccess!Root(new Root);
216 	// without the SafeAccess we would receive a SIGSEGV here
217 	sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(0xDEADBEEF);
218 
219 	bool notAccessed = true;
220 	// the same with `&&` whould be much longer
221 	if (LongLineOfIdent3 a = sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3)
222 	{
223 		notAccessed = false;
224 	}
225 	assert(notAccessed);
226 
227 	// checks that forwarding actually works
228 	sar.m.longLineOfIdent1 = new LongLineOfIdent1;
229 	sar.m.longLineOfIdent1.longLineOfIdent2 = new LongLineOfIdent2;
230 	sar.m.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3 = new LongLineOfIdent3;
231 
232 	sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.setFoo(42);
233 	assert(sar.longLineOfIdent1.longLineOfIdent2.longLineOfIdent3.unwrap.foo == 42);
234 }
235 
236 /**
237  * IFTI helper for $(D SafeAccess).
238  *
239  * Returns:
240  *      $(D m) with the ability to safely access its members that are class
241  *      instances.
242  */
243 auto ref safeAccess(M)(M m)
244 {
245 	return SafeAccess!M(m);
246 }