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}