misc/importsort-d

src/sort.d in v0.3.1
Repositories | Summary | Log | Files | README.md

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}