1 // (c) 2022 Friedel Schon <derfriedmundschoen@gmail.com>
2 
3 module importsort.main;
4 
5 import core.stdc.stdlib : exit;
6 import importsort.sort : Import, SortConfig, sortImports;
7 import std.array : replace;
8 import std.conv : ConvException, parse;
9 import std.file : DirEntry, SpanMode, dirEntries, exists, isDir, isFile;
10 import std.functional : unaryFun;
11 import std.stdio : File, stderr, stdin, stdout;
12 import std.string : endsWith;
13 
14 /// name of binary (for help)
15 enum BINARY = "importsort-d";
16 
17 /// current version (and something I always forget to update oops)
18 enum VERSION = "0.3.0";
19 
20 /// the help-message from `help.txt`
21 enum HELP = import("help.txt")
22 		.replace("{binary}", BINARY)
23 		.replace("{version}", VERSION);
24 
25 /// list entries (`ls`) from all arguments
26 DirEntry[] listEntries(alias F = "true")(string[] input, bool recursive) {
27 	alias filterFunc = unaryFun!F;
28 
29 	DirEntry[] entries;
30 
31 	foreach (path; input) {
32 		if (!exists(path)) {
33 			stderr.writef("error: '%s' does not exist\n", path);
34 			exit(1);
35 		} else if (isDir(path)) {
36 			foreach (entry; dirEntries(path, recursive ? SpanMode.depth : SpanMode.shallow)) {
37 				if (entry.isFile && entry.name.endsWith(".d") && filterFunc(entry.name))
38 					entries ~= entry;
39 			}
40 		} else if (isFile(path)) {
41 			if (!path.endsWith(".d")) {
42 				stderr.writef("error: '%s' is not a .d-file\n", path);
43 				exit(1);
44 			}
45 			if (filterFunc(path))
46 				entries ~= DirEntry(path);
47 		} else {
48 			stderr.writef("error: '%s' is not a file or directory\n", path);
49 			exit(1);
50 		}
51 	}
52 	return entries;
53 }
54 
55 /// the main-function (nothing to explain)
56 void main(string[] args) {
57 	SortConfig config;
58 	bool inline;
59 	string output;
60 	string[] input;
61 	bool watcher;
62 	bool watcherDelaySet;
63 	double watcherDelay = 0.1; // sec
64 	bool recursive;
65 
66 	// -*- option parser -*-
67 
68 	bool nextOutput;
69 	bool nextWatcherDelay;
70 	foreach (arg; args[1 .. $]) {
71 		if (nextOutput) {
72 			output = arg;
73 			nextOutput = false;
74 		} else if (nextWatcherDelay) {
75 			try {
76 				watcherDelay = parse!double(arg);
77 			} catch (ConvException) {
78 				stderr.writef("error: cannot parse delay '%s' to an integer\n", arg);
79 				exit(1);
80 			}
81 			watcherDelaySet = true;
82 			nextWatcherDelay = false;
83 		} else if (arg == "--help" || arg == "-h") {
84 			stdout.writeln(HELP);
85 			return;
86 		} else if (arg == "--verbose" || arg == "-v") {
87 			config.verbose = true;
88 		} else if (arg == "--keep" || arg == "-k") {
89 			config.keepLine = true;
90 		} else if (arg == "--attribute" || arg == "-a") {
91 			config.byAttribute = true;
92 		} else if (arg == "--binding" || arg == "-b") {
93 			config.byBinding = true;
94 		} else if (arg == "--merge" || arg == "-m") {
95 			config.merge = true;
96 		} else if (arg == "--inline" || arg == "-i") {
97 			inline = true;
98 		} else if (arg == "--recursive" || arg == "-r") {
99 			recursive = true;
100 			// TODO: --watch
101 			/*} else if (arg == "--watch" || arg == "-w") {
102 			watcher = true;
103 		} else if (arg == "--delay" || arg == "-d") {
104 			if (watcherDelaySet) {
105 				stderr.writeln("error: watcher-delay already specified");
106 				stderr.writeln(HELP);
107 				exit(1);
108 			}
109 			nextWatcherDelay = true;*/
110 		} else if (arg == "--output" || arg == "-o") {
111 			if (output != null) {
112 				stderr.writeln("error: output already specified");
113 				stderr.writeln(HELP);
114 				exit(1);
115 			}
116 			nextOutput = true;
117 		} else if (arg[0] == '-') {
118 			stderr.writef("error: unknown option '%s'\n", arg);
119 			stderr.writeln(HELP);
120 			exit(1);
121 		} else {
122 			input ~= arg;
123 		}
124 	}
125 	if (recursive && input.length == 0) {
126 		stderr.writeln("error: cannot use '--recursive' and specify no input");
127 		exit(1);
128 	}
129 	if (inline && input.length == 0) {
130 		stderr.writeln("error: cannot use '--inline' and read from stdin");
131 		exit(1);
132 	}
133 	if ((!inline || output.length > 0) && input.length > 0) {
134 		stderr.writeln("error: if you use inputs you must use '--inline'");
135 		exit(1);
136 	}
137 	// -*- operation -*-
138 
139 	/*	if (watcher) {
140 		stderr.writeln("\033[1;34mwatching files...\033[0m");
141 		SysTime[string] lastModified;
142 		for (;;) {
143 			auto entries = listEntries!(x => x !in lastModified
144 					|| lastModified[x] != x.timeLastModified)(input, recursive);
145 
146 			foreach (entry; entries) {
147 				lastModified[entry.name] = entry.timeLastModified;
148 			}
149 			entries.sortImports(config);
150 			Thread.sleep(Duration!"msecs"(cast(long) watcherDelay * 1000));
151 		}
152 	} else 
153 	*/
154 	if (input == null) {
155 		File outfile = (output == null) ? stdout : File(output);
156 
157 		sortImports(stdin, outfile, config);
158 		if (output)
159 			outfile.close();
160 	} else {
161 		listEntries(input, recursive).sortImports(config);
162 	}
163 }