misc/importsort-d

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

main.d (5504B) download


  1// (c) 2022 Friedel Schon <[email protected]>
  2
  3module importsort;
  4
  5import core.stdc.stdlib : exit;
  6import std.algorithm : map, sort, findSplit;
  7import std.array : array;
  8import std.file : copy, remove;
  9import std.regex : ctRegex, matchFirst;
 10import std.stdio : File, stderr, stdin, stdout;
 11import std.string : format, split, strip, stripLeft, indexOf;
 12import std.typecons : Yes, Tuple, tuple;
 13
 14struct Identifier {
 15	string original;
 16	string alias_;
 17
 18	string sortBy() {
 19		if (sortOriginal)
 20			return original;
 21		else
 22			return hasAlias ? alias_ : original;
 23	}
 24
 25	bool hasAlias() {
 26		return alias_ != null;
 27	}
 28}
 29
 30struct Import {
 31	bool public_;
 32	bool static_;
 33	Identifier name;
 34	Identifier[] idents;
 35	string begin;
 36	string end;
 37
 38	string sortBy() {
 39		if (special && (public_ || static_))
 40			return '\0' ~ name.sortBy;
 41		return name.sortBy;
 42	}
 43}
 44
 45const 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]*)$`;
 46
 47const help = (string arg0) => "Usage: " ~ arg0 ~ " [--inline [--keep]] [--out <output>] [--original] [--special] [input]
 48  <path> can be ommitted or set to '-' to read from stdin
 49
 50Options:
 51  -k, --keep ....... keeps a backup if using '--inline'
 52  -i, --inline ..... writes to the input
 53  -o, --out <path> . writes to `path` instead of stdout
 54
 55  -s, --special .... public and static imports first
 56  -r, --original ... sort by original not by binding";
 57
 58bool inline = false;
 59bool keep = false;
 60bool special = false;
 61string output = null;
 62string path = null;
 63bool sortOriginal = false;
 64
 65void writeImports(File outfile, Import[] matches) {
 66	if (!matches)
 67		return;
 68
 69	matches.sort!((a, b) => a.sortBy < b.sortBy);
 70	foreach (m; matches) {
 71		outfile.write(m.begin);
 72		if (m.public_)
 73			outfile.write("public ");
 74		if (m.static_)
 75			outfile.write("static ");
 76		if (m.name.hasAlias) {
 77			outfile.writef("import %s = %s", m.name.alias_, m.name.original);
 78		} else {
 79			outfile.write("import " ~ m.name.original);
 80		}
 81		foreach (i, ident; m.idents) {
 82			auto begin = i == 0 ? " : " : ", ";
 83			if (ident.hasAlias) { // hasAlias
 84				outfile.writef("%s%s = %s", begin, ident.alias_, ident.original);
 85			} else {
 86				outfile.write(begin ~ ident.original);
 87			}
 88		}
 89		outfile.writef(";%s", m.end);
 90	}
 91}
 92
 93void main(string[] args) {
 94	bool nextout = false;
 95
 96	foreach (arg; args[1 .. $]) {
 97		if (nextout) {
 98			output = arg;
 99			nextout = false;
100		}
101		if (arg == "--help" || arg == "-h") {
102			stdout.writeln(help(args[0]));
103			return;
104		} else if (arg == "--keep" || arg == "-k") {
105			keep = true;
106		} else if (arg == "--special" || arg == "-s") {
107			special = true;
108		} else if (arg == "--inline" || arg == "-i") {
109			inline = true;
110		} else if (arg == "--original" || arg == "-r") {
111			sortOriginal = true;
112		} else if (arg == "--out" || arg == "-o") {
113			if (output != null) {
114				stderr.writeln("error: output already specified");
115				stderr.writeln(help(args[0]));
116				exit(1);
117			}
118			nextout = true;
119		} else if (arg[0] == '-') {
120			stderr.writef("error: unknown option '%s'\n", arg);
121			stderr.writeln(help(args[0]));
122			exit(1);
123		} else {
124			if (path != null) {
125				stderr.writeln("error: input already specified");
126				stderr.writeln(help(args[0]));
127				exit(1);
128			}
129			path = arg;
130		}
131	}
132	if (output != null && output == path) {
133		stderr.writeln("error: input and output cannot be the same; use '--inline'");
134		stderr.writeln(help(args[0]));
135		exit(1);
136	}
137	if (!inline && keep) {
138		stderr.writeln("error: you have to specify '--keep' in combination with '--inline'");
139		exit(1);
140	}
141	if (inline && output != null) {
142		stderr.writeln("error: you cannot specify '--inline' and '--out' at the same time");
143		exit(1);
144	}
145	if (!path) {
146		path = "-";
147	}
148	if (inline && path == "-") {
149		stderr.writeln("error: you cannot specify '--inline' and read from stdin");
150		exit(1);
151	}
152
153	File infile, outfile;
154	if (inline) {
155		copy(path, path ~ ".bak");
156		infile = File(path ~ ".bak");
157	} else if (path == "-") {
158		infile = stdin;
159	} else {
160		infile = File(path);
161	}
162
163	if (inline)
164		outfile = File(path, "w");
165	else if (output)
166		outfile = File(output, "w");
167	else
168		outfile = stdout;
169
170	string softEnd = null;
171	Import[] matches;
172
173	foreach (line; infile.byLine(Yes.keepTerminator)) {
174		auto match = matchFirst(line, pattern);
175		if (!match.empty) { // is import
176			if (softEnd) {
177				if (!matches)
178					outfile.write(softEnd);
179				softEnd = null;
180			}
181
182			Import im;
183			if (match[3]) {
184				im.name = Identifier(match[4].idup, match[3].idup);
185			} else {
186				im.name = Identifier(match[4].idup);
187			}
188			im.begin = match[1].idup;
189			im.end = match[6].idup;
190
191			if (match[2] == "static")
192				im.static_ = true;
193			else if (match[2] == "public")
194				im.public_ = true;
195
196			if (match[5]) {
197				foreach (id; match[5][1 .. $].split(",")) {
198					if (auto pair = id.idup.findSplit("=")) { // has alias
199						im.idents ~= Identifier(pair[2].strip, pair[0].strip);
200					} else {
201						im.idents ~= Identifier(id.idup.strip);
202					}
203				}
204				im.idents.sort!((a, b) => a.sortBy < b.sortBy);
205			}
206			matches ~= im;
207		} else {
208			if (!softEnd && line.stripLeft == "") {
209				softEnd = line.idup;
210			} else {
211				if (matches) {
212					outfile.writeImports(matches);
213					matches = [];
214				}
215
216				if (softEnd) {
217					outfile.write(softEnd);
218					softEnd = null;
219				}
220				outfile.write(line);
221			}
222		}
223	}
224
225	outfile.writeImports(matches);
226
227	infile.close();
228	outfile.close();
229
230	if (inline && !keep)
231		remove(path ~ ".bak");
232}