main.d (3808B)
1 // (c) 2022-2023 Friedel Schon <[email protected]> 2 3 module importsort.main; 4 5 import argparse; 6 import core.stdc.stdlib : exit; 7 import importsort.sort : Import, sortImports; 8 import std.array : replace; 9 import std.file : DirEntry, SpanMode, dirEntries, exists, isDir, isFile; 10 import std.functional : unaryFun; 11 import std.range : empty; 12 import std.stdio : File, stderr, stdin, stdout; 13 import std.string : endsWith; 14 import std.typecons : nullable; 15 16 /// current version (and something I always forget to update oops) 17 enum VERSION = "0.3.2"; 18 19 /// configuration for sorting imports 20 @(Command("importsort-d").Description("Sorts dlang imports").Epilog("Version: v" ~ VERSION)) 21 struct 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 66 DirEntry[] 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 95 mixin 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 });