importsort-d

Sort and format imports in DLang
Log | Files | Refs | README

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 });