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