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 static immutable VERSION = () { 8 debug 9 { 10 enum DEBUG_SUFFIX = "-debug"; 11 } 12 else 13 { 14 enum DEBUG_SUFFIX = ""; 15 } 16 17 static if (is(typeof(import("VERSION")))) 18 { 19 // takes the `git describe --tags` output and removes the leading 20 // 'v' as well as any kind of newline 21 // if the tag is considered malformed it gets used verbatim 22 23 enum gitDescribeOutput = import("VERSION"); 24 25 string result; 26 27 if (gitDescribeOutput[0] == 'v') 28 result = gitDescribeOutput[1 .. $]; 29 else 30 result = null; 31 32 uint minusCount; 33 34 foreach (i, c; result) 35 { 36 if (c == '\n' || c == '\r') 37 { 38 result = result[0 .. i]; 39 break; 40 } 41 42 if (c == '-') 43 { 44 ++minusCount; 45 } 46 } 47 48 if (minusCount > 1) 49 result = null; 50 51 return result ? result ~ DEBUG_SUFFIX 52 : gitDescribeOutput ~ DEBUG_SUFFIX; 53 54 } 55 else 56 { 57 return "unknown" ~ DEBUG_SUFFIX ~ "-version"; 58 } 59 60 } (); 61 62 63 version (NoMain) 64 { 65 } 66 else 67 { 68 import std.array : front, popFront; 69 import std.stdio : stdout, stdin, stderr, writeln, File; 70 import dfmt.config : Config; 71 import dfmt.formatter : format; 72 import std.path : buildPath, dirName, expandTilde; 73 import dfmt.editorconfig : getConfigFor; 74 import std.getopt : getopt, GetOptException; 75 76 int main(string[] args) 77 { 78 bool inplace = false; 79 Config optConfig; 80 optConfig.pattern = "*.d"; 81 bool showHelp; 82 bool showVersion; 83 string explicitConfigDir; 84 85 void handleBooleans(string option, string value) 86 { 87 import dfmt.editorconfig : OptionalBoolean; 88 import std.exception : enforceEx; 89 90 enforceEx!GetOptException(value == "true" || value == "false", "Invalid argument"); 91 immutable OptionalBoolean optVal = value == "true" ? OptionalBoolean.t 92 : OptionalBoolean.f; 93 switch (option) 94 { 95 case "align_switch_statements": 96 optConfig.dfmt_align_switch_statements = optVal; 97 break; 98 case "outdent_attributes": 99 optConfig.dfmt_outdent_attributes = optVal; 100 break; 101 case "space_after_cast": 102 optConfig.dfmt_space_after_cast = optVal; 103 break; 104 case "space_before_function_parameters": 105 optConfig.dfmt_space_before_function_parameters = optVal; 106 break; 107 case "split_operator_at_line_end": 108 optConfig.dfmt_split_operator_at_line_end = optVal; 109 break; 110 case "selective_import_space": 111 optConfig.dfmt_selective_import_space = optVal; 112 break; 113 case "compact_labeled_statements": 114 optConfig.dfmt_compact_labeled_statements = optVal; 115 break; 116 case "single_template_constraint_indent": 117 optConfig.dfmt_single_template_constraint_indent = optVal; 118 break; 119 default: 120 assert(false, "Invalid command-line switch"); 121 } 122 } 123 124 try 125 { 126 // dfmt off 127 getopt(args, 128 "version", &showVersion, 129 "align_switch_statements", &handleBooleans, 130 "brace_style", &optConfig.dfmt_brace_style, 131 "config|c", &explicitConfigDir, 132 "end_of_line", &optConfig.end_of_line, 133 "help|h", &showHelp, 134 "indent_size", &optConfig.indent_size, 135 "indent_style|t", &optConfig.indent_style, 136 "inplace|i", &inplace, 137 "max_line_length", &optConfig.max_line_length, 138 "soft_max_line_length", &optConfig.dfmt_soft_max_line_length, 139 "outdent_attributes", &handleBooleans, 140 "space_after_cast", &handleBooleans, 141 "selective_import_space", &handleBooleans, 142 "space_before_function_parameters", &handleBooleans, 143 "split_operator_at_line_end", &handleBooleans, 144 "compact_labeled_statements", &handleBooleans, 145 "single_template_constraint_indent", &handleBooleans, 146 "tab_width", &optConfig.tab_width, 147 "template_constraint_style", &optConfig.dfmt_template_constraint_style); 148 // dfmt on 149 } 150 catch (GetOptException e) 151 { 152 stderr.writeln(e.msg); 153 return 1; 154 } 155 156 if (showVersion) 157 { 158 writeln(VERSION); 159 return 0; 160 } 161 162 if (showHelp) 163 { 164 printHelp(); 165 return 0; 166 } 167 168 args.popFront(); 169 immutable bool readFromStdin = args.length == 0; 170 171 File output = stdout; 172 version (Windows) 173 { 174 // On Windows, set stdout to binary mode (needed for correct EOL writing) 175 // See Phobos' stdio.File.rawWrite 176 { 177 import std.stdio : _O_BINARY; 178 immutable fd = output.fileno; 179 _setmode(fd, _O_BINARY); 180 version (CRuntime_DigitalMars) 181 { 182 import core.atomic : atomicOp; 183 import core.stdc.stdio : __fhnd_info, FHND_TEXT; 184 185 atomicOp!"&="(__fhnd_info[fd], ~FHND_TEXT); 186 } 187 } 188 } 189 190 ubyte[] buffer; 191 192 Config explicitConfig; 193 if (explicitConfigDir) 194 { 195 import std.file : exists, isDir; 196 197 if (!exists(explicitConfigDir) || !isDir(explicitConfigDir)) 198 { 199 stderr.writeln("--config_dir|c must specify existing directory path"); 200 return 1; 201 } 202 explicitConfig = getConfigFor!Config(explicitConfigDir); 203 explicitConfig.pattern = "*.d"; 204 } 205 206 if (readFromStdin) 207 { 208 import std.file : getcwd; 209 210 auto cwdDummyPath = buildPath(getcwd(), "dummy.d"); 211 212 Config config; 213 config.initializeWithDefaults(); 214 if (explicitConfigDir != "") 215 { 216 config.merge(explicitConfig, buildPath(explicitConfigDir, "dummy.d")); 217 } 218 else 219 { 220 Config fileConfig = getConfigFor!Config(getcwd()); 221 fileConfig.pattern = "*.d"; 222 config.merge(fileConfig, cwdDummyPath); 223 } 224 config.merge(optConfig, cwdDummyPath); 225 if (!config.isValid()) 226 return 1; 227 ubyte[4096] inputBuffer; 228 ubyte[] b; 229 while (true) 230 { 231 b = stdin.rawRead(inputBuffer); 232 if (b.length) 233 buffer ~= b; 234 else 235 break; 236 } 237 format("stdin", buffer, output.lockingTextWriter(), &config); 238 } 239 else 240 { 241 import std.file : dirEntries, isDir, SpanMode; 242 243 if (args.length >= 2) 244 inplace = true; 245 while (args.length > 0) 246 { 247 const path = args.front; 248 args.popFront(); 249 if (isDir(path)) 250 { 251 inplace = true; 252 foreach (string name; dirEntries(path, "*.d", SpanMode.depth)) 253 args ~= name; 254 continue; 255 } 256 Config config; 257 config.initializeWithDefaults(); 258 if (explicitConfigDir != "") 259 { 260 config.merge(explicitConfig, buildPath(explicitConfigDir, "dummy.d")); 261 } 262 else 263 { 264 Config fileConfig = getConfigFor!Config(path); 265 fileConfig.pattern = "*.d"; 266 config.merge(fileConfig, path); 267 } 268 config.merge(optConfig, path); 269 if (!config.isValid()) 270 return 1; 271 File f = File(path); 272 // ignore empty files 273 if (f.size) 274 { 275 buffer = new ubyte[](cast(size_t) f.size); 276 f.rawRead(buffer); 277 if (inplace) 278 output = File(path, "wb"); 279 format(path, buffer, output.lockingTextWriter(), &config); 280 } 281 } 282 } 283 return 0; 284 } 285 } 286 287 private version (Windows) 288 { 289 version(CRuntime_DigitalMars) 290 { 291 extern(C) int setmode(int, int) nothrow @nogc; 292 alias _setmode = setmode; 293 } 294 else version(CRuntime_Microsoft) 295 { 296 extern(C) int _setmode(int, int) nothrow @nogc; 297 } 298 } 299 300 template optionsToString(E) if (is(E == enum)) 301 { 302 enum optionsToString = () { 303 304 string result = "("; 305 foreach (s; [__traits(allMembers, E)]) 306 { 307 if (s != "unspecified") 308 result ~= s ~ "|"; 309 } 310 result = result[0 .. $ - 1] ~ ")"; 311 return result; 312 } (); 313 } 314 315 private void printHelp() 316 { 317 writeln(`dfmt `, VERSION, ` 318 https://github.com/Hackerpilot/dfmt 319 320 Options: 321 --help, -h Print this help message 322 --inplace, -i Edit files in place 323 --config_dir, -c Path to directory to load .editorconfig file from. 324 --version Print the version number and then exit 325 326 Formatting Options: 327 --align_switch_statements 328 --brace_style `, optionsToString!(typeof(Config.dfmt_brace_style)), 329 ` 330 --end_of_line `, optionsToString!(typeof(Config.end_of_line)), ` 331 --indent_size 332 --indent_style, -t `, 333 optionsToString!(typeof(Config.indent_style)), ` 334 --soft_max_line_length 335 --max_line_length 336 --outdent_attributes 337 --space_after_cast 338 --space_before_function_parameters 339 --selective_import_space 340 --single_template_constraint_indent 341 --split_operator_at_line_end 342 --compact_labeled_statements 343 --template_constraint_style 344 `, 345 optionsToString!(typeof(Config.dfmt_template_constraint_style))); 346 } 347 348 private string createFilePath(bool readFromStdin, string fileName) 349 { 350 import std.file : getcwd; 351 import std.path : isRooted; 352 353 immutable string cwd = getcwd(); 354 if (readFromStdin) 355 return buildPath(cwd, "dummy.d"); 356 if (isRooted(fileName)) 357 return fileName; 358 else 359 return buildPath(cwd, fileName); 360 }