1 module workspaced.com.dfmt; 2 3 import fs = std.file; 4 import std.array; 5 import std.conv; 6 import std.getopt; 7 import std.json; 8 import std.stdio : stderr; 9 10 import dfmt.config; 11 import dfmt.editorconfig; 12 import dfmt.formatter : fmt = format; 13 14 import core.thread; 15 16 import painlessjson; 17 18 import workspaced.api; 19 20 @component("dfmt") 21 class DfmtComponent : ComponentWrapper 22 { 23 mixin DefaultComponentWrapper; 24 25 /// Will format the code passed in asynchronously. 26 /// Returns: the formatted code as string 27 Future!string format(scope const(char)[] code, string[] arguments = []) 28 { 29 mixin(gthreadsAsyncProxy!`formatSync(code, arguments)`); 30 } 31 32 /// Will format the code passed in synchronously. Might take a short moment on larger documents. 33 /// Returns: the formatted code as string 34 string formatSync(scope const(char)[] code, string[] arguments = []) 35 { 36 Config config; 37 config.initializeWithDefaults(); 38 string configPath; 39 if (getConfigPath("dfmt.json", configPath)) 40 { 41 stderr.writeln("Overriding dfmt arguments with workspace-d dfmt.json config file"); 42 try 43 { 44 auto json = parseJSON(fs.readText(configPath)); 45 json.tryFetchProperty(config.dfmt_align_switch_statements, "align_switch_statements"); 46 json.tryFetchProperty(config.dfmt_brace_style, "brace_style"); 47 json.tryFetchProperty(config.end_of_line, "end_of_line"); 48 json.tryFetchProperty(config.indent_size, "indent_size"); 49 json.tryFetchProperty(config.indent_style, "indent_style"); 50 json.tryFetchProperty(config.max_line_length, "max_line_length"); 51 json.tryFetchProperty(config.dfmt_soft_max_line_length, "soft_max_line_length"); 52 json.tryFetchProperty(config.dfmt_outdent_attributes, "outdent_attributes"); 53 json.tryFetchProperty(config.dfmt_space_after_cast, "space_after_cast"); 54 json.tryFetchProperty(config.dfmt_space_after_keywords, "space_after_keywords"); 55 json.tryFetchProperty(config.dfmt_split_operator_at_line_end, "split_operator_at_line_end"); 56 json.tryFetchProperty(config.tab_width, "tab_width"); 57 json.tryFetchProperty(config.dfmt_selective_import_space, "selective_import_space"); 58 json.tryFetchProperty(config.dfmt_compact_labeled_statements, "compact_labeled_statements"); 59 json.tryFetchProperty(config.dfmt_template_constraint_style, "template_constraint_style"); 60 } 61 catch (Exception e) 62 { 63 stderr.writeln("dfmt.json in workspace-d config folder is malformed"); 64 stderr.writeln(e); 65 } 66 } 67 else if (arguments.length) 68 { 69 void handleBooleans(string option, string value) 70 { 71 import dfmt.editorconfig : OptionalBoolean; 72 import std.exception : enforce; 73 74 enforce!GetOptException(value == "true" || value == "false", "Invalid argument"); 75 immutable OptionalBoolean val = value == "true" ? OptionalBoolean.t : OptionalBoolean.f; 76 switch (option) 77 { 78 case "align_switch_statements": 79 config.dfmt_align_switch_statements = val; 80 break; 81 case "outdent_attributes": 82 config.dfmt_outdent_attributes = val; 83 break; 84 case "space_after_cast": 85 config.dfmt_space_after_cast = val; 86 break; 87 case "split_operator_at_line_end": 88 config.dfmt_split_operator_at_line_end = val; 89 break; 90 case "selective_import_space": 91 config.dfmt_selective_import_space = val; 92 break; 93 case "compact_labeled_statements": 94 config.dfmt_compact_labeled_statements = val; 95 break; 96 default: 97 throw new Exception("Invalid command-line switch"); 98 } 99 } 100 101 arguments = "dfmt" ~ arguments; 102 //dfmt off 103 getopt(arguments, 104 "align_switch_statements", &handleBooleans, 105 "brace_style", &config.dfmt_brace_style, 106 "end_of_line", &config.end_of_line, 107 "indent_size", &config.indent_size, 108 "indent_style|t", &config.indent_style, 109 "max_line_length", &config.max_line_length, 110 "soft_max_line_length", &config.dfmt_soft_max_line_length, 111 "outdent_attributes", &handleBooleans, 112 "space_after_cast", &handleBooleans, 113 "selective_import_space", &handleBooleans, 114 "split_operator_at_line_end", &handleBooleans, 115 "compact_labeled_statements", &handleBooleans, 116 "tab_width", &config.tab_width, 117 "template_constraint_style", &config.dfmt_template_constraint_style); 118 //dfmt on 119 } 120 auto output = appender!string; 121 fmt("stdin", cast(ubyte[]) code, output, &config); 122 if (output.data.length) 123 return output.data; 124 else 125 return code.idup; 126 } 127 } 128 129 private: 130 131 void tryFetchProperty(T = string)(ref JSONValue json, ref T ret, string name) 132 { 133 auto ptr = name in json; 134 if (ptr) 135 { 136 auto val = *ptr; 137 static if (is(T == string) || is(T == enum)) 138 { 139 if (val.type != JSONType..string) 140 throw new Exception("dfmt config value '" ~ name ~ "' must be a string"); 141 static if (is(T == enum)) 142 ret = val.str.to!T; 143 else 144 ret = val.str; 145 } 146 else static if (is(T == uint)) 147 { 148 if (val.type != JSONType.integer) 149 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 150 if (val.integer < 0) 151 throw new Exception("dfmt config value '" ~ name ~ "' must be a positive number"); 152 ret = cast(T) val.integer; 153 } 154 else static if (is(T == int)) 155 { 156 if (val.type != JSONType.integer) 157 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 158 ret = cast(T) val.integer; 159 } 160 else static if (is(T == OptionalBoolean)) 161 { 162 if (val.type != JSONType.true_ && val.type != JSONType.false_) 163 throw new Exception("dfmt config value '" ~ name ~ "' must be a boolean"); 164 ret = val.type == JSONType.true_ ? OptionalBoolean.t : OptionalBoolean.f; 165 } 166 else 167 static assert(false); 168 } 169 }