sort.d (5443B) download
1module importsort.sort;
2
3import std.algorithm : findSplit, remove, sort;
4import std.array : split;
5import std.file : DirEntry, rename;
6import std.functional : unaryFun;
7import std.range : ElementType;
8import std.regex : ctRegex, matchFirst;
9import std.stdio : File, stderr;
10import std.string : strip, stripLeft;
11import std.traits : isIterable;
12import std.typecons : Yes;
13
14/// the pattern to determinate a line is an import or not
15enum 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]*)$`;
16
17/// configuration for sorting imports
18struct SortConfig {
19 /// won't format the line, keep it as-is
20 bool keepLine = false;
21
22 /// sort by attributes (public/static first)
23 bool byAttribute = false;
24
25 /// sort by binding instead of the original
26 bool byBinding = false;
27
28 /// print interesting messages (TODO)
29 bool verbose = false;
30
31 /// merges imports of the same source
32 bool merge = false;
33}
34
35/// helper-struct for identifiers and its bindings
36struct Identifier {
37 /// SortConfig::byBinding
38 bool byBinding;
39
40 /// the original e. g. 'std.stdio'
41 string original;
42
43 /// the binding (alias) e. g. 'io = std.stdio'
44 string binding;
45
46 /// wether this import has a binding or not
47 @property
48 bool hasBinding() {
49 return binding != null;
50 }
51
52 /// the string to sort
53 string sortBy() {
54 if (byBinding)
55 return hasBinding ? binding : original;
56 else
57 return original;
58 }
59}
60
61/// the import statement description
62struct Import {
63 /// SortConfig::byAttribute
64 bool byAttribute;
65
66 /// the original line (is `null` if merges)
67 string line;
68
69 /// is a public-import
70 bool public_;
71
72 /// is a static-import
73 bool static_;
74
75 /// origin of the import e. g. `import std.stdio : ...;`
76 Identifier name;
77
78 /// symbols of the import e. g. `import ... : File, stderr, in = stdin;`
79 Identifier[] idents;
80
81 /// spaces before the import (indentation)
82 string begin;
83
84 /// the string to sort
85 string sortBy() {
86 if (byAttribute && (public_ || static_))
87 return '\0' ~ name.sortBy;
88 return name.sortBy;
89 }
90}
91
92/// write import-statements to `outfile` with `config`
93void writeImports(File outfile, SortConfig config, Import[] matches) {
94 if (!matches)
95 return;
96
97 if (config.merge) {
98 for (int i = 0; i < matches.length; i++) {
99 for (int j = i + 1; j < matches.length; j++) {
100 if (matches[i].name.original == matches[j].name.original
101 && matches[i].name.binding == matches[j].name.binding) {
102
103 matches[i].line = null;
104 matches[i].idents ~= matches[j].idents;
105 matches = matches.remove(j);
106 j--;
107 }
108 }
109 }
110 }
111
112 matches.sort!((a, b) => a.sortBy < b.sortBy);
113 bool first;
114
115 foreach (m; matches) {
116 if (config.keepLine && m.line.length > 0) {
117 outfile.write(m.line);
118 } else {
119 outfile.write(m.begin);
120 if (m.public_)
121 outfile.write("public ");
122 if (m.static_)
123 outfile.write("static ");
124 if (m.name.hasBinding) {
125 outfile.writef("import %s = %s", m.name.binding, m.name.original);
126 } else {
127 outfile.write("import " ~ m.name.original);
128 }
129 first = true;
130 foreach (ident; m.idents) {
131 auto begin = first ? " : " : ", ";
132 first = false;
133 if (ident.hasBinding) { // hasBinding
134 outfile.writef("%s%s = %s", begin, ident.binding, ident.original);
135 } else {
136 outfile.write(begin ~ ident.original);
137 }
138 }
139 outfile.writef(";%s", m.end);
140 }
141 }
142}
143
144/// sort imports of an entry (file) (entries: DirEntry[])
145void sortImports(alias P = "true", R)(R entries, SortConfig config)
146 if (isIterable!R && is(ElementType!R == DirEntry)) {
147 alias postFunc = unaryFun!P;
148
149 File infile, outfile;
150 foreach (entry; entries) {
151 stderr.writef("\033[34msorting \033[0;1m%s\033[0m\n", entry.name);
152
153 infile = File(entry.name);
154 outfile = File(entry.name ~ ".new", "w");
155
156 sortImports(infile, outfile, config);
157
158 infile.close();
159 outfile.close();
160
161 rename(entry.name ~ ".new", entry.name);
162
163 cast(void) postFunc(entry.name);
164 }
165}
166
167/// raw-implementation of sort file (infile -> outfile)
168void sortImports(File infile, File outfile, SortConfig config) {
169 string softEnd = null;
170 Import[] matches;
171
172 foreach (line; infile.byLine(Yes.keepTerminator)) {
173 auto linestr = line.idup;
174 if (auto match = linestr.matchFirst(PATTERN)) { // is import
175 if (softEnd) {
176 if (!matches)
177 outfile.write(softEnd);
178 softEnd = null;
179 }
180
181 auto im = Import(config.byAttribute, linestr);
182 if (match[3]) {
183 im.name = Identifier(config.byBinding, match[4], match[3]);
184 } else {
185 im.name = Identifier(config.byBinding, match[4]);
186 }
187 im.begin = match[1];
188 im.end = match[6];
189
190 if (match[2] == "static")
191 im.static_ = true;
192 else if (match[2] == "public")
193 im.public_ = true;
194
195 if (match[5]) {
196 foreach (id; match[5][1 .. $].split(",")) {
197 if (auto pair = id.findSplit("=")) { // has alias
198 im.idents ~= Identifier(config.byBinding, pair[2].strip, pair[0].strip);
199 } else {
200 im.idents ~= Identifier(config.byBinding, id.strip);
201 }
202 }
203 im.idents.sort!((a, b) => a.sortBy < b.sortBy);
204 }
205 matches ~= im;
206 } else {
207 if (!softEnd && linestr.stripLeft == "") {
208 softEnd = linestr;
209 } else {
210 if (matches) {
211 outfile.writeImports(config, matches);
212 matches = [];
213 }
214 if (softEnd) {
215 outfile.write(softEnd);
216 softEnd = null;
217 }
218 outfile.write(line);
219 }
220 }
221 }
222 outfile.writeImports(config, matches);
223}