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 }