From 854569fdedaa7c59e034e902d4fc729a936ead48 Mon Sep 17 00:00:00 2001 From: Hackerpilot Date: Thu, 16 Apr 2015 18:18:44 -0700 Subject: [PATCH] Add .editorconfig reading support. #122 --- src/dfmt/editorconfig.d | 167 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/dfmt/editorconfig.d diff --git a/src/dfmt/editorconfig.d b/src/dfmt/editorconfig.d new file mode 100644 index 0000000..e5024c3 --- /dev/null +++ b/src/dfmt/editorconfig.d @@ -0,0 +1,167 @@ +module dfmt.editorconfig; +import std.regex : ctRegex; + +private auto headerRe = ctRegex!(`^\s*\[([^\n]+)\]\s*(:?#.*)?$`); +private auto propertyRe = ctRegex!(`^\s*([\w_]+)\s*=\s*([\w_]+)\s*[#;]?.*$`); +private auto commentRe = ctRegex!(`^\s*[#;].*$`); + +enum OptionalBoolean : ubyte +{ + unspecified = 3, + t = 1, + f = 0 +} + +enum IndentStyle : ubyte +{ + unspecified, + tab, + space +} + +enum EOL : ubyte +{ + unspecified, + lf, + cr, + crlf +} + +mixin template StandardEditorConfigFields() +{ + string pattern; + OptionalBoolean root; + EOL end_of_line; + OptionalBoolean insert_final_newline; + string charset; + IndentStyle indent_style; + int indent_size = -1; + int tab_width = -1; + OptionalBoolean trim_trailing_whitespace; + int max_line_length = -1; + + void merge(ref const typeof(this) other, const string fileName) + { + import std.path : globMatch; + import std.traits : FieldNameTuple; + + if (other.pattern is null || !fileName.globMatch(other.pattern)) + return; + foreach (N; FieldNameTuple!(typeof(this))) + { + alias T = typeof(mixin(N)); + const otherN = mixin("other." ~ N); + auto thisN = &mixin("this." ~ N); + static if (N == "pattern") + continue; + else static if (is (T == enum)) + *thisN = otherN != T.unspecified ? otherN : *thisN; + else static if (is (T == int)) + *thisN = otherN != -1 ? otherN : *thisN; + else static if (is (T == string)) + *thisN = otherN !is null ? otherN : *thisN; + else + static assert(false); + } + } +} + +/** + * Params: + * path = the path to the file + * Returns: + * The configuration for the file at the given path + */ +EC getConfigFor(EC)(string path) +{ + import std.stdio : File; + import std.regex : regex, match; + import std.path : globMatch, dirName, baseName, pathSplitter, buildPath; + import std.algorithm : reverse, map, filter, each; + import std.array : array; + + EC result; + EC[][] configs; + string dir = dirName(path); + immutable string fileName = baseName(path); + string[] pathParts = cast(string[]) pathSplitter(dir).array(); + for (size_t i = pathParts.length; i > 1; i--) + { + EC[] sections = parseConfig!EC(buildPath(pathParts[0 .. i])); + if (sections.length) + configs ~= sections; + if (!sections.map!(a => a.root).filter!(a => a == OptionalBoolean.t).empty) + break; + } + reverse(configs); + configs.each!(a => a.each!(b => result.merge(b, fileName)))(); + return result; +} + +private EC[] parseConfig(EC)(string dir) +{ + import std.stdio : File; + import std.file : exists; + import std.path : buildPath; + import std.regex : matchAll; + import std.traits : FieldNameTuple; + import std.conv : to; + import std.uni : toLower; + + EC section; + EC[] sections; + immutable string path = buildPath(dir, ".editorconfig"); + if (!exists(path)) + return sections; + + File f = File(path); + foreach (line; f.byLineCopy()) + { + auto headerMatch = line.matchAll(headerRe); + if (headerMatch) + { + sections ~= section; + section = EC.init; + auto c = headerMatch.captures; + c.popFront(); + section.pattern = c.front(); + } + else + { + auto propertyMatch = line.matchAll(propertyRe); + if (propertyMatch) + { + auto c = propertyMatch.captures; + c.popFront(); + immutable string propertyName = c.front(); + c.popFront(); + immutable string propertyValue = toLower(c.front()); + foreach (F; FieldNameTuple!EC) + { + enum configDot = "section." ~ F; + alias FieldType = typeof(mixin(configDot)); + if (F == propertyName) + { + static if (is(FieldType == OptionalBoolean)) + mixin(configDot) = propertyValue == "true" ? OptionalBoolean.t + : OptionalBoolean.f; + else + mixin(configDot) = to!(FieldType)(propertyValue); + } + } + } + } + } + sections ~= section; + return sections; +} + +version (editorconfig_main) void main() +{ + import std.stdio : writeln; + + static struct EditorConfig { mixin StandardEditorConfigFields; } + + auto c = getConfigFor!EditorConfig("/home/brian/src/aias/src/dummy.d"); + writeln(c); +}