1 //          Copyright Brian Schott (Hackerpilot) 2012.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 module dscanner.imports;
7 
8 import dparse.ast;
9 import dparse.lexer;
10 import dparse.parser;
11 import dparse.rollback_allocator;
12 import std.stdio;
13 import std.container.rbtree;
14 import std.functional : toDelegate;
15 import dscanner.utils;
16 
17 /**
18  * AST visitor that collects modules imported to an R-B tree.
19  */
20 class ImportPrinter : ASTVisitor
21 {
22 	this()
23 	{
24 		imports = new RedBlackTree!string;
25 	}
26 
27 	override void visit(const SingleImport singleImport)
28 	{
29 		ignore = false;
30 		singleImport.accept(this);
31 		ignore = true;
32 	}
33 
34 	override void visit(const IdentifierChain identifierChain)
35 	{
36 		if (ignore)
37 			return;
38 		bool first = true;
39 		string s;
40 		foreach (ident; identifierChain.identifiers)
41 		{
42 			if (!first)
43 				s ~= ".";
44 			s ~= ident.text;
45 			first = false;
46 		}
47 		imports.insert(s);
48 	}
49 
50 	alias visit = ASTVisitor.visit;
51 
52 	/// Collected imports
53 	RedBlackTree!string imports;
54 
55 private:
56 	bool ignore = true;
57 }
58 
59 private void visitFile(bool usingStdin, string fileName, RedBlackTree!string importedModules, StringCache* cache)
60 {
61 	RollbackAllocator rba;
62 	LexerConfig config;
63 	config.fileName = fileName;
64 	config.stringBehavior = StringBehavior.source;
65 	auto visitor = new ImportPrinter;
66 	auto tokens = getTokensForParser(usingStdin ? readStdin() : readFile(fileName), config, cache);
67 	auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
68 	visitor.visit(mod);
69 	importedModules.insert(visitor.imports[]);
70 }
71 
72 private void doNothing(string, size_t, size_t, string, bool)
73 {
74 }
75 
76 void printImports(bool usingStdin, string[] args, string[] importPaths, StringCache* cache, bool recursive)
77 {
78 	string[] fileNames = usingStdin ? ["stdin"] : expandArgs(args);
79 	import std.path : buildPath, dirSeparator;
80 	import std.file : isFile, exists;
81 	import std.array : replace, empty;
82 	import std.range : chain, only;
83 
84 	auto resolvedModules = new RedBlackTree!(string);
85 	auto resolvedLocations = new RedBlackTree!(string);
86 	auto importedFiles = new RedBlackTree!(string);
87 	foreach (name; fileNames)
88 		visitFile(usingStdin, name, importedFiles, cache);
89 	if (importPaths.empty)
90 	{
91 		foreach (item; importedFiles[])
92 			writeln(item);
93 		return;
94 	}
95 	while (!importedFiles.empty)
96 	{
97 		auto newlyDiscovered = new RedBlackTree!(string);
98 		itemLoop: foreach (item; importedFiles[])
99 		{
100 			foreach (path; importPaths)
101 			{
102 				auto d = buildPath(path, item.replace(".", dirSeparator) ~ ".d");
103 				auto di = buildPath(path, item.replace(".", dirSeparator) ~ ".di");
104 				auto p = buildPath(path, item.replace(".", dirSeparator), "package.d");
105 				auto pi = buildPath(path, item.replace(".", dirSeparator), "package.di");
106 				foreach (alt; [d, di, p, pi])
107 				{
108 					if (exists(alt) && isFile(alt))
109 					{
110 						resolvedModules.insert(item);
111 						resolvedLocations.insert(alt);
112 						if (recursive)
113 							visitFile(false, alt, newlyDiscovered, cache);
114 						continue itemLoop;
115 					}
116 				}
117 			}
118 			writeln("Could not resolve location of ", item);
119 		}
120 		foreach (item; importedFiles[])
121 			newlyDiscovered.removeKey(item);
122 		foreach (resolved; resolvedModules[])
123 			newlyDiscovered.removeKey(resolved);
124 		importedFiles = newlyDiscovered;
125 	}
126 	foreach (resolved; resolvedLocations[])
127 		writeln(resolved);
128 }