main.d (3808B) download
1// (c) 2022-2023 Friedel Schon <[email protected]>
2
3module importsort.main;
4
5import argparse;
6import core.stdc.stdlib : exit;
7import importsort.sort : Import, sortImports;
8import std.array : replace;
9import std.file : DirEntry, SpanMode, dirEntries, exists, isDir, isFile;
10import std.functional : unaryFun;
11import std.range : empty;
12import std.stdio : File, stderr, stdin, stdout;
13import std.string : endsWith;
14import std.typecons : nullable;
15
16/// current version (and something I always forget to update oops)
17enum VERSION = "0.3.2";
18
19/// configuration for sorting imports
20@(Command("importsort-d").Description("Sorts dlang imports").Epilog("Version: v" ~ VERSION))
21struct SortConfig {
22 @(ArgumentGroup("Input/Output arguments")
23 .Description(
24 "Define in- and output behavior")) {
25 @(NamedArgument(["recursive", "r"]).Description("recursively search in directories"))
26 bool recursive = false;
27
28 @(NamedArgument(["inplace", "i"]).Description("writes to the input"))
29 bool inplace = false;
30
31 @(NamedArgument(["force", "f"])
32 .Description("always write file, don't check if sorting is required"))
33 bool force = false;
34
35 @(NamedArgument(["output", "o"]).Description("writes to `path` instead of stdout"))
36 string output;
37
38 @(PositionalArgument(0).Description("inputfiles or directories, use - for stdin"))
39 string[] inputs;
40 }
41
42 @(ArgumentGroup("Sorting arguments").Description("Tune import sorting algorithms")) {
43 /// won't format the line, keep it as-is
44 @(NamedArgument(["keep", "k"]).Description("keeps the line as-is instead of formatting"))
45 bool keepLine = false;
46
47 @(NamedArgument(["attribute", "a"]).Description("public and static imports first"))
48 /// sort by attributes (public/static first)
49 bool byAttribute = false;
50
51 @(NamedArgument(["binding", "b"]).Description("sorts by binding rather then the original"))
52 /// sort by binding instead of the original
53 bool byBinding = false;
54
55 @(NamedArgument(["merge", "m"]).Description("merge imports which uses same file"))
56 /// merges imports of the same source
57 bool merge = false;
58
59 /// ignore case when sorting
60 @(NamedArgument(["ignore-case", "c"]).Description("ignore case when comparing elements"))
61 bool ignoreCase = false;
62 }
63}
64
65/// list entries (`ls`) from all arguments
66DirEntry[] listEntries(alias F = "true")(string[] input, bool recursive) {
67 alias filterFunc = unaryFun!F;
68
69 DirEntry[] entries;
70
71 foreach (path; input) {
72 if (!exists(path)) {
73 stderr.writef("error: '%s' does not exist\n", path);
74 exit(19);
75 } else if (isDir(path)) {
76 foreach (entry; dirEntries(path, recursive ? SpanMode.depth : SpanMode.shallow)) {
77 if (entry.isFile && entry.name.endsWith(".d") && filterFunc(entry.name))
78 entries ~= entry;
79 }
80 } else if (isFile(path)) {
81 if (!path.endsWith(".d")) {
82 stderr.writef("error: '%s' is not a .d-file\n", path);
83 exit(11);
84 }
85 if (filterFunc(path))
86 entries ~= DirEntry(path);
87 } else {
88 stderr.writef("error: '%s' is not a file or directory\n", path);
89 exit(12);
90 }
91 }
92 return entries;
93}
94
95mixin CLI!(SortConfig).main!((SortConfig config) {
96 if (config.recursive && config.inputs.empty) {
97 stderr.writeln("error: cannot use '--recursive' and specify no input");
98 exit(1);
99 }
100
101 if (config.inplace && config.inputs.empty) {
102 stderr.writeln("error: cannot use inplace and read from stdin");
103 exit(2);
104 }
105
106 if (!config.inputs.empty && (!config.inplace || !config.output.empty)) {
107 stderr.writeln(
108 "error: if you use inputs you must use inplace sorting or provide an output");
109 exit(3);
110 }
111
112 if (config.inputs.empty) {
113 auto outfile = config.output.empty ? stdout : File(config.output);
114 sortImports(config, stdin, nullable(outfile));
115 } else {
116 listEntries(config.inputs, config.recursive).sortImports(config);
117 }
118 return 0;
119});