misc/importsort-d

src/main.d in v0.2.0
Repositories | Summary | Log | Files | README.md

main.d (8083B) download


  1// (c) 2022 Friedel Schon <[email protected]>
  2
  3module test.main;
  4
  5import core.stdc.stdlib : exit;
  6import core.thread : Thread;
  7import core.time : Duration = dur;
  8import std.algorithm : canFind, each, endsWith, filter, findSplit, map, sort;
  9import std.array : array, replace;
 10import std.conv : ConvException, parse;
 11import std.datetime : SysTime;
 12import std.file : DirEntry, SpanMode, dirEntries, exists, isDir, isFile, rename, timeLastModified;
 13import std.functional : unaryFun;
 14import std.range : ElementType, empty;
 15import std.regex : ctRegex, matchFirst;
 16import std.stdio : File, stderr, stdin, stdout;
 17import std.string : format, indexOf, split, strip, stripLeft;
 18import std.traits : isIterable;
 19import std.typecons : Tuple, Yes, tuple;
 20
 21struct Identifier {
 22	bool byBinding;
 23	string original;
 24	string binding;
 25
 26	string sortBy() {
 27		if (byBinding)
 28			return hasBinding ? binding : original;
 29		else
 30			return original;
 31	}
 32
 33	bool hasBinding() {
 34		return binding != null;
 35	}
 36}
 37
 38struct Import {
 39	bool byAttribute;
 40	string line;
 41
 42	bool public_;
 43	bool static_;
 44	Identifier name;
 45	Identifier[] idents;
 46	string begin;
 47	string end;
 48
 49	string sortBy() {
 50		if (byAttribute && (public_ || static_))
 51			return '\0' ~ name.sortBy;
 52		return name.sortBy;
 53	}
 54}
 55
 56enum PATTERN = ctRegex!`^(\s*)(?:(public|static)\s+)?import\s+(?:(\w+)\s*=\s*)?([a-zA-Z._]+)\s*(:\s*\w+(?:\s*=\s*\w+)?(?:\s*,\s*\w+(?:\s*=\s*\w+)?)*)?\s*;[ \t]*([\n\r]*)$`;
 57
 58enum BINARY = "importsort-d";
 59enum VERSION = "0.1.0";
 60enum HELP = import("help.txt")
 61		.replace("{binary}", BINARY)
 62		.replace("{version}", VERSION);
 63
 64struct SortConfig {
 65	bool keepLine = false;
 66	bool byAttribute = false;
 67	bool byBinding = false;
 68	bool verbose = false;
 69}
 70
 71void writeImports(File outfile, SortConfig config, Import[] matches) {
 72	if (!matches)
 73		return;
 74
 75	matches.sort!((a, b) => a.sortBy < b.sortBy);
 76	foreach (m; matches) {
 77		if (config.keepLine) {
 78			outfile.write(m.line);
 79		} else {
 80			outfile.write(m.begin);
 81			if (m.public_)
 82				outfile.write("public ");
 83			if (m.static_)
 84				outfile.write("static ");
 85			if (m.name.hasBinding) {
 86				outfile.writef("import %s = %s", m.name.binding, m.name.original);
 87			} else {
 88				outfile.write("import " ~ m.name.original);
 89			}
 90			foreach (i, ident; m.idents) {
 91				auto begin = i == 0 ? " : " : ", ";
 92				if (ident.hasBinding) { // hasBinding
 93					outfile.writef("%s%s = %s", begin, ident.binding, ident.original);
 94				} else {
 95					outfile.write(begin ~ ident.original);
 96				}
 97			}
 98			outfile.writef(";%s", m.end);
 99		}
100	}
101}
102
103void sortImports(alias P = "true", R)(R entries, SortConfig config)
104		if (isIterable!R && is(ElementType!R == DirEntry)) {
105	alias postFunc = unaryFun!P;
106
107	File infile, outfile;
108	foreach (entry; entries) {
109		stderr.writef("\033[34msorting \033[0;1m%s\033[0m\n", entry.name);
110
111		infile = File(entry.name);
112		outfile = File(entry.name ~ ".new", "w");
113
114		sortImports(infile, outfile, config);
115
116		infile.close();
117		outfile.close();
118
119		rename(entry.name ~ ".new", entry.name);
120
121		cast(void) postFunc(entry.name);
122	}
123}
124
125void sortImports(File infile, File outfile, SortConfig config) {
126	string softEnd = null;
127	Import[] matches;
128
129	foreach (line; infile.byLine(Yes.keepTerminator)) {
130		auto linestr = line.idup;
131		if (auto match = linestr.matchFirst(PATTERN)) { // is import
132			if (softEnd) {
133				if (!matches)
134					outfile.write(softEnd);
135				softEnd = null;
136			}
137
138			auto im = Import(config.byAttribute, linestr);
139			if (match[3]) {
140				im.name = Identifier(config.byBinding, match[4], match[3]);
141			} else {
142				im.name = Identifier(config.byBinding, match[4]);
143			}
144			im.begin = match[1];
145			im.end = match[6];
146
147			if (match[2] == "static")
148				im.static_ = true;
149			else if (match[2] == "public")
150				im.public_ = true;
151
152			if (match[5]) {
153				foreach (id; match[5][1 .. $].split(",")) {
154					if (auto pair = id.findSplit("=")) { // has alias
155						im.idents ~= Identifier(config.byBinding, pair[2].strip, pair[0].strip);
156					} else {
157						im.idents ~= Identifier(config.byBinding, id.strip);
158					}
159				}
160				im.idents.sort!((a, b) => a.sortBy < b.sortBy);
161			}
162			matches ~= im;
163		} else {
164			if (!softEnd && linestr.stripLeft == "") {
165				softEnd = linestr;
166			} else {
167				if (matches) {
168					outfile.writeImports(config, matches);
169					matches = [];
170				}
171				if (softEnd) {
172					outfile.write(softEnd);
173					softEnd = null;
174				}
175				outfile.write(line);
176			}
177		}
178	}
179	outfile.writeImports(config, matches);
180}
181
182DirEntry[] listEntries(alias F = "true")(string[] input, bool recursive) {
183	alias filterFunc = unaryFun!F;
184
185	DirEntry[] entries;
186
187	foreach (path; input) {
188		if (!exists(path)) {
189			stderr.writef("error: '%s' does not exist\n", path);
190			exit(1);
191		} else if (isDir(path)) {
192			foreach (entry; dirEntries(path, recursive ? SpanMode.depth : SpanMode.shallow)) {
193				if (entry.isFile && entry.name.endsWith(".d") && filterFunc(entry.name))
194					entries ~= entry;
195			}
196		} else if (isFile(path)) {
197			if (!path.endsWith(".d")) {
198				stderr.writef("error: '%s' is not a .d-file\n", path);
199				exit(1);
200			}
201			if (filterFunc(path))
202				entries ~= DirEntry(path);
203		} else {
204			stderr.writef("error: '%s' is not a file or directory\n", path);
205			exit(1);
206		}
207	}
208	return entries;
209}
210
211void main(string[] args) {
212	SortConfig config;
213	bool inline;
214	string output;
215	string[] input;
216	bool watcher;
217	bool watcherDelaySet;
218	double watcherDelay = 0.1; // sec
219	bool recursive;
220
221	// -*- option parser -*-
222
223	bool nextOutput;
224	bool nextWatcherDelay;
225	foreach (arg; args[1 .. $]) {
226		if (nextOutput) {
227			output = arg;
228			nextOutput = false;
229		} else if (nextWatcherDelay) {
230			try {
231				watcherDelay = parse!double(arg);
232			} catch (ConvException) {
233				stderr.writef("error: cannot parse delay '%s' to an integer\n", arg);
234				exit(1);
235			}
236			watcherDelaySet = true;
237			nextWatcherDelay = false;
238		} else if (arg == "--help" || arg == "-h") {
239			stdout.writeln(HELP);
240			return;
241		} else if (arg == "--verbose" || arg == "-v") {
242			config.verbose = true;
243		} else if (arg == "--keep" || arg == "-k") {
244			config.keepLine = true;
245		} else if (arg == "--attribute" || arg == "-a") {
246			config.byAttribute = true;
247		} else if (arg == "--binding" || arg == "-b") {
248			config.byBinding = true;
249		} else if (arg == "--inline" || arg == "-i") {
250			inline = true;
251		} else if (arg == "--recursive" || arg == "-r") {
252			recursive = true;
253			// TODO: --watch
254			/*} else if (arg == "--watch" || arg == "-w") {
255			watcher = true;
256		} else if (arg == "--delay" || arg == "-d") {
257			if (watcherDelaySet) {
258				stderr.writeln("error: watcher-delay already specified");
259				stderr.writeln(HELP);
260				exit(1);
261			}
262			nextWatcherDelay = true;*/
263		} else if (arg == "--output" || arg == "-o") {
264			if (output != null) {
265				stderr.writeln("error: output already specified");
266				stderr.writeln(HELP);
267				exit(1);
268			}
269			nextOutput = true;
270		} else if (arg[0] == '-') {
271			stderr.writef("error: unknown option '%s'\n", arg);
272			stderr.writeln(HELP);
273			exit(1);
274		} else {
275			input ~= arg;
276		}
277	}
278	if (recursive && input.length == 0) {
279		stderr.writeln("error: cannot use '--recursive' and specify no input");
280		exit(1);
281	}
282	if (inline && input.length == 0) {
283		stderr.writeln("error: cannot use '--inline' and read from stdin");
284		exit(1);
285	}
286	if ((!inline || output.length > 0) && input.length > 0) {
287		stderr.writeln("error: if you use inputs you must use '--inline'");
288		exit(1);
289	}
290	// -*- operation -*-
291
292	/*	if (watcher) {
293		stderr.writeln("\033[1;34mwatching files...\033[0m");
294		SysTime[string] lastModified;
295		for (;;) {
296			auto entries = listEntries!(x => x !in lastModified
297					|| lastModified[x] != x.timeLastModified)(input, recursive);
298
299			foreach (entry; entries) {
300				lastModified[entry.name] = entry.timeLastModified;
301			}
302			entries.sortImports(config);
303			Thread.sleep(Duration!"msecs"(cast(long) watcherDelay * 1000));
304		}
305	} else 
306	*/
307	if (input == null) {
308		File outfile = (output == null) ? stdout : File(output);
309
310		sortImports(stdin, outfile, config);
311		if (output)
312			outfile.close();
313	} else {
314		listEntries(input, recursive).sortImports(config);
315	}
316}