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 auto ret = new Future!string; 30 gthreads.create({ 31 mixin(traceTask); 32 try 33 { 34 Config config; 35 config.initializeWithDefaults(); 36 string configPath; 37 if (getConfigPath("dfmt.json", configPath)) 38 { 39 stderr.writeln("Overriding dfmt arguments with workspace-d dfmt.json config file"); 40 try 41 { 42 auto json = parseJSON(fs.readText(configPath)); 43 json.tryFetchProperty(config.dfmt_align_switch_statements, "align_switch_statements"); 44 json.tryFetchProperty(config.dfmt_brace_style, "brace_style"); 45 json.tryFetchProperty(config.end_of_line, "end_of_line"); 46 json.tryFetchProperty(config.indent_size, "indent_size"); 47 json.tryFetchProperty(config.indent_style, "indent_style"); 48 json.tryFetchProperty(config.max_line_length, "max_line_length"); 49 json.tryFetchProperty(config.dfmt_soft_max_line_length, "soft_max_line_length"); 50 json.tryFetchProperty(config.dfmt_outdent_attributes, "outdent_attributes"); 51 json.tryFetchProperty(config.dfmt_space_after_cast, "space_after_cast"); 52 json.tryFetchProperty(config.dfmt_space_after_keywords, "space_after_keywords"); 53 json.tryFetchProperty(config.dfmt_split_operator_at_line_end, 54 "split_operator_at_line_end"); 55 json.tryFetchProperty(config.tab_width, "tab_width"); 56 json.tryFetchProperty(config.dfmt_selective_import_space, "selective_import_space"); 57 json.tryFetchProperty(config.dfmt_compact_labeled_statements, 58 "compact_labeled_statements"); 59 json.tryFetchProperty(config.dfmt_template_constraint_style, 60 "template_constraint_style"); 61 } 62 catch (Exception e) 63 { 64 stderr.writeln("dfmt.json in workspace-d config folder is malformed"); 65 stderr.writeln(e); 66 } 67 } 68 else if (arguments.length) 69 { 70 void handleBooleans(string option, string value) 71 { 72 import dfmt.editorconfig : OptionalBoolean; 73 import std.exception : enforce; 74 75 enforce!GetOptException(value == "true" || value == "false", "Invalid argument"); 76 immutable OptionalBoolean val = value == "true" ? OptionalBoolean.t : OptionalBoolean.f; 77 switch (option) 78 { 79 case "align_switch_statements": 80 config.dfmt_align_switch_statements = val; 81 break; 82 case "outdent_attributes": 83 config.dfmt_outdent_attributes = val; 84 break; 85 case "space_after_cast": 86 config.dfmt_space_after_cast = val; 87 break; 88 case "split_operator_at_line_end": 89 config.dfmt_split_operator_at_line_end = val; 90 break; 91 case "selective_import_space": 92 config.dfmt_selective_import_space = val; 93 break; 94 case "compact_labeled_statements": 95 config.dfmt_compact_labeled_statements = val; 96 break; 97 default: 98 throw new Exception("Invalid command-line switch"); 99 } 100 } 101 102 arguments = "dfmt" ~ arguments; 103 //dfmt off 104 getopt(arguments, 105 "align_switch_statements", &handleBooleans, 106 "brace_style", &config.dfmt_brace_style, 107 "end_of_line", &config.end_of_line, 108 "indent_size", &config.indent_size, 109 "indent_style|t", &config.indent_style, 110 "max_line_length", &config.max_line_length, 111 "soft_max_line_length", &config.dfmt_soft_max_line_length, 112 "outdent_attributes", &handleBooleans, 113 "space_after_cast", &handleBooleans, 114 "selective_import_space", &handleBooleans, 115 "split_operator_at_line_end", &handleBooleans, 116 "compact_labeled_statements", &handleBooleans, 117 "tab_width", &config.tab_width, 118 "template_constraint_style", &config.dfmt_template_constraint_style); 119 //dfmt on 120 } 121 auto output = appender!string; 122 fmt("stdin", cast(ubyte[]) code, output, &config); 123 if (output.data.length) 124 ret.finish(output.data); 125 else 126 ret.finish(code.idup); 127 } 128 catch (Throwable e) 129 { 130 ret.error(e); 131 } 132 }); 133 return ret; 134 } 135 } 136 137 private: 138 139 void tryFetchProperty(T = string)(ref JSONValue json, ref T ret, string name) 140 { 141 auto ptr = name in json; 142 if (ptr) 143 { 144 auto val = *ptr; 145 static if (is(T == string) || is(T == enum)) 146 { 147 if (val.type != JSON_TYPE.STRING) 148 throw new Exception("dfmt config value '" ~ name ~ "' must be a string"); 149 static if (is(T == enum)) 150 ret = val.str.to!T; 151 else 152 ret = val.str; 153 } 154 else static if (is(T == uint)) 155 { 156 if (val.type != JSON_TYPE.INTEGER) 157 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 158 if (val.integer < 0) 159 throw new Exception("dfmt config value '" ~ name ~ "' must be a positive number"); 160 ret = cast(T) val.integer; 161 } 162 else static if (is(T == int)) 163 { 164 if (val.type != JSON_TYPE.INTEGER) 165 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 166 ret = cast(T) val.integer; 167 } 168 else static if (is(T == OptionalBoolean)) 169 { 170 if (val.type != JSON_TYPE.TRUE && val.type != JSON_TYPE.FALSE) 171 throw new Exception("dfmt config value '" ~ name ~ "' must be a boolean"); 172 ret = val.type == JSON_TYPE.TRUE ? OptionalBoolean.t : OptionalBoolean.f; 173 } 174 else 175 static assert(false); 176 } 177 }