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 }