1 // Copyright Brian Schott 2015. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module dfmt.main; 7 8 private enum VERSION = "0.5.0"; 9 10 version (NoMain) 11 { 12 } 13 else 14 { 15 import std.array : front, popFront; 16 import std.stdio : stdout, stdin, stderr, writeln, File; 17 import dfmt.config : Config; 18 import dfmt.formatter : format; 19 import std.path : buildPath, dirName, expandTilde; 20 import dfmt.editorconfig : getConfigFor; 21 import std.getopt : getopt, GetOptException; 22 23 int main(string[] args) 24 { 25 bool inplace = false; 26 Config optConfig; 27 optConfig.pattern = "*.d"; 28 bool showHelp; 29 bool showVersion; 30 string explicitConfigDir; 31 32 void handleBooleans(string option, string value) 33 { 34 import dfmt.editorconfig : OptionalBoolean; 35 import std.exception : enforceEx; 36 37 enforceEx!GetOptException(value == "true" || value == "false", "Invalid argument"); 38 immutable OptionalBoolean optVal = value == "true" ? OptionalBoolean.t 39 : OptionalBoolean.f; 40 switch (option) 41 { 42 case "align_switch_statements": 43 optConfig.dfmt_align_switch_statements = optVal; 44 break; 45 case "outdent_attributes": 46 optConfig.dfmt_outdent_attributes = optVal; 47 break; 48 case "space_after_cast": 49 optConfig.dfmt_space_after_cast = optVal; 50 break; 51 case "split_operator_at_line_end": 52 optConfig.dfmt_split_operator_at_line_end = optVal; 53 break; 54 case "selective_import_space": 55 optConfig.dfmt_selective_import_space = optVal; 56 break; 57 case "compact_labeled_statements": 58 optConfig.dfmt_compact_labeled_statements = optVal; 59 break; 60 default: 61 assert(false, "Invalid command-line switch"); 62 } 63 } 64 65 try 66 { 67 // dfmt off 68 getopt(args, 69 "version", &showVersion, 70 "align_switch_statements", &handleBooleans, 71 "brace_style", &optConfig.dfmt_brace_style, 72 "config|c", &explicitConfigDir, 73 "end_of_line", &optConfig.end_of_line, 74 "help|h", &showHelp, 75 "indent_size", &optConfig.indent_size, 76 "indent_style|t", &optConfig.indent_style, 77 "inplace|i", &inplace, 78 "max_line_length", &optConfig.max_line_length, 79 "soft_max_line_length", &optConfig.dfmt_soft_max_line_length, 80 "outdent_attributes", &handleBooleans, 81 "space_after_cast", &handleBooleans, 82 "selective_import_space", &handleBooleans, 83 "split_operator_at_line_end", &handleBooleans, 84 "compact_labeled_statements", &handleBooleans, 85 "tab_width", &optConfig.tab_width, 86 "template_constraint_style", &optConfig.dfmt_template_constraint_style); 87 // dfmt on 88 } 89 catch (GetOptException e) 90 { 91 stderr.writeln(e.msg); 92 return 1; 93 } 94 95 if (showVersion) 96 { 97 writeln(VERSION); 98 return 0; 99 } 100 101 if (showHelp) 102 { 103 printHelp(); 104 return 0; 105 } 106 107 args.popFront(); 108 immutable bool readFromStdin = args.length == 0; 109 110 File output = stdout; 111 version (Windows) 112 { 113 // On Windows, set stdout to binary mode (needed for correct EOL writing) 114 // See Phobos' stdio.File.rawWrite 115 { 116 import std.stdio : _O_BINARY; 117 immutable fd = output.fileno; 118 _setmode(fd, _O_BINARY); 119 version (CRuntime_DigitalMars) 120 { 121 import core.atomic : atomicOp; 122 import core.stdc.stdio : __fhnd_info, FHND_TEXT; 123 124 atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); 125 } 126 } 127 } 128 129 ubyte[] buffer; 130 131 Config explicitConfig; 132 if (explicitConfigDir) 133 { 134 import std.path : exists, isDir; 135 136 if (!exists(explicitConfigDir) || !isDir(explicitConfigDir)) 137 { 138 stderr.writeln("--config_dir|c must specify existing directory path"); 139 return 1; 140 } 141 explicitConfig = getConfigFor!Config(explicitConfigDir); 142 explicitConfig.pattern = "*.d"; 143 } 144 145 if (readFromStdin) 146 { 147 import std.file : getcwd; 148 149 Config config; 150 config.initializeWithDefaults(); 151 if (explicitConfigDir != "") 152 { 153 config.merge(explicitConfig, buildPath(explicitConfigDir, "dummy.d")); 154 } 155 config.merge(optConfig, buildPath(getcwd(), "dummy.d")); 156 if (!config.isValid()) 157 return 1; 158 ubyte[4096] inputBuffer; 159 ubyte[] b; 160 while (true) 161 { 162 b = stdin.rawRead(inputBuffer); 163 if (b.length) 164 buffer ~= b; 165 else 166 break; 167 } 168 format("stdin", buffer, output.lockingTextWriter(), &config); 169 } 170 else 171 { 172 import std.file : dirEntries, isDir, SpanMode; 173 174 if (args.length >= 2) 175 inplace = true; 176 while (args.length > 0) 177 { 178 const path = args.front; 179 args.popFront(); 180 if (isDir(path)) 181 { 182 inplace = true; 183 foreach (string name; dirEntries(path, "*.d", SpanMode.depth)) 184 args ~= name; 185 continue; 186 } 187 Config config; 188 config.initializeWithDefaults(); 189 if (explicitConfigDir != "") 190 { 191 config.merge(explicitConfig, buildPath(explicitConfigDir, "dummy.d")); 192 } 193 else 194 { 195 Config fileConfig = getConfigFor!Config(path); 196 fileConfig.pattern = "*.d"; 197 config.merge(fileConfig, path); 198 } 199 config.merge(optConfig, path); 200 if (!config.isValid()) 201 return 1; 202 File f = File(path); 203 buffer = new ubyte[](cast(size_t) f.size); 204 f.rawRead(buffer); 205 if (inplace) 206 output = File(path, "wb"); 207 format(path, buffer, output.lockingTextWriter(), &config); 208 } 209 } 210 return 0; 211 } 212 } 213 214 private version (Windows) 215 { 216 version(CRuntime_DigitalMars) 217 { 218 extern(C) int setmode(int, int) nothrow @nogc; 219 alias _setmode = setmode; 220 } 221 else version(CRuntime_Microsoft) 222 { 223 extern(C) int _setmode(int, int) nothrow @nogc; 224 } 225 } 226 227 private string optionsToString(E)() if (is(E == enum)) 228 { 229 import std.traits : EnumMembers; 230 import std.conv : to; 231 232 string result = "("; 233 foreach (i, option; EnumMembers!E) 234 { 235 immutable s = to!string(option); 236 if (s != "unspecified") 237 result ~= s ~ "|"; 238 } 239 result = result[0 .. $ - 1] ~ ")"; 240 return result; 241 } 242 243 private void printHelp() 244 { 245 writeln(`dfmt `, VERSION, ` 246 https://github.com/Hackerpilot/dfmt 247 248 Options: 249 --help, -h Print this help message 250 --inplace, -i Edit files in place 251 --config_dir, -c Path to directory to load .editorconfig file from. 252 --version Print the version number and then exit 253 254 Formatting Options: 255 --align_switch_statements 256 --brace_style `, optionsToString!(typeof(Config.dfmt_brace_style))(), 257 ` 258 --end_of_line `, optionsToString!(typeof(Config.end_of_line))(), ` 259 --indent_size 260 --indent_style, -t `, 261 optionsToString!(typeof(Config.indent_style))(), ` 262 --soft_max_line_length 263 --max_line_length 264 --outdent_attributes 265 --space_after_cast 266 --selective_import_space 267 --split_operator_at_line_end 268 --compact_labeled_statements 269 --template_constraint_style 270 `, 271 optionsToString!(typeof(Config.dfmt_template_constraint_style))()); 272 } 273 274 private string createFilePath(bool readFromStdin, string fileName) 275 { 276 import std.file : getcwd; 277 import std.path : isRooted; 278 279 immutable string cwd = getcwd(); 280 if (readFromStdin) 281 return buildPath(cwd, "dummy.d"); 282 if (isRooted(fileName)) 283 return fileName; 284 else 285 return buildPath(cwd, fileName); 286 }