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 }