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(string code, string[] arguments = []) 28 { 29 auto ret = new Future!string; 30 threads.create({ 31 try 32 { 33 Config config; 34 config.initializeWithDefaults(); 35 string configPath; 36 if (getConfigPath("dfmt.json", configPath)) 37 { 38 stderr.writeln("Overriding dfmt arguments with workspace-d dfmt.json config file"); 39 try 40 { 41 auto json = parseJSON(fs.readText(configPath)); 42 json.tryFetchProperty(config.dfmt_align_switch_statements, "align_switch_statements"); 43 json.tryFetchProperty(config.dfmt_brace_style, "brace_style"); 44 json.tryFetchProperty(config.end_of_line, "end_of_line"); 45 json.tryFetchProperty(config.indent_size, "indent_size"); 46 json.tryFetchProperty(config.indent_style, "indent_style"); 47 json.tryFetchProperty(config.max_line_length, "max_line_length"); 48 json.tryFetchProperty(config.dfmt_soft_max_line_length, "soft_max_line_length"); 49 json.tryFetchProperty(config.dfmt_outdent_attributes, "outdent_attributes"); 50 json.tryFetchProperty(config.dfmt_space_after_cast, "space_after_cast"); 51 json.tryFetchProperty(config.dfmt_space_after_keywords, "space_after_keywords"); 52 json.tryFetchProperty(config.dfmt_split_operator_at_line_end, 53 "split_operator_at_line_end"); 54 json.tryFetchProperty(config.tab_width, "tab_width"); 55 json.tryFetchProperty(config.dfmt_selective_import_space, "selective_import_space"); 56 json.tryFetchProperty(config.dfmt_compact_labeled_statements, 57 "compact_labeled_statements"); 58 json.tryFetchProperty(config.dfmt_template_constraint_style, 59 "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 ret.finish(output.data); 124 else 125 ret.finish(code); 126 } 127 catch (Throwable e) 128 { 129 ret.error(e); 130 } 131 }); 132 return ret; 133 } 134 } 135 136 private: 137 138 void tryFetchProperty(T = string)(ref JSONValue json, ref T ret, string name) 139 { 140 auto ptr = name in json; 141 if (ptr) 142 { 143 auto val = *ptr; 144 static if (is(T == string) || is(T == enum)) 145 { 146 if (val.type != JSON_TYPE.STRING) 147 throw new Exception("dfmt config value '" ~ name ~ "' must be a string"); 148 static if (is(T == enum)) 149 ret = val.str.to!T; 150 else 151 ret = val.str; 152 } 153 else static if (is(T == uint)) 154 { 155 if (val.type != JSON_TYPE.INTEGER) 156 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 157 if (val.integer < 0) 158 throw new Exception("dfmt config value '" ~ name ~ "' must be a positive number"); 159 ret = cast(T) val.integer; 160 } 161 else static if (is(T == int)) 162 { 163 if (val.type != JSON_TYPE.INTEGER) 164 throw new Exception("dfmt config value '" ~ name ~ "' must be a number"); 165 ret = cast(T) val.integer; 166 } 167 else static if (is(T == OptionalBoolean)) 168 { 169 if (val.type != JSON_TYPE.TRUE && val.type != JSON_TYPE.FALSE) 170 throw new Exception("dfmt config value '" ~ name ~ "' must be a boolean"); 171 ret = val.type == JSON_TYPE.TRUE ? OptionalBoolean.t : OptionalBoolean.f; 172 } 173 else 174 static assert(false); 175 } 176 }