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 }