diff --git a/src/dfmt/config.d b/src/dfmt/config.d index 585514a..0cfdad3 100644 --- a/src/dfmt/config.d +++ b/src/dfmt/config.d @@ -5,26 +5,102 @@ module dfmt.config; -/// The only good brace styles +/// Brace styles enum BraceStyle { + /// $(LINK https://en.wikipedia.org/wiki/Indent_style#Allman_style) allman, - otbs + /// $(LINK https://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS) + otbs, + /// $(LINK https://en.wikipedia.org/wiki/Indent_style#Variant:_Stroustrup) + stroustrup +} + +/// Newline styles +enum Newlines +{ + /// Old Mac + cr, + /// UNIX, Linux, BSD, New Mac, iOS, Android, etc... + lf, + /// Windows + crlf +} + +template getHelp(alias S) +{ + enum getHelp = __traits(getAttributes, S)[0].text; } /// Configuration options for formatting -struct FormatterConfig +struct Config { - /// Number of spaces used for indentation + /// + @Help("Number of spaces used for indentation") uint indentSize = 4; - /// Use tabs or spaces + + /// + @Help("Use tabs or spaces") bool useTabs = false; - /// Size of a tab character + + /// + @Help("Size of a tab character") uint tabSize = 4; - /// Soft line wrap limit + + /// + @Help("Soft line wrap limit") uint columnSoftLimit = 80; - /// Hard line wrap limit + + /// + @Help("Hard line wrap limit") uint columnHardLimit = 120; - /// Use the One True Brace Style + + /// + @Help("Brace style can be 'otbs', 'allman', or 'stroustrup'") BraceStyle braceStyle = BraceStyle.allman; + + /// + @Help("Align labels, cases, and defaults with their enclosing switch") + bool alignSwitchStatements = true; + + /// + @Help("Decrease the indentation of labels") + bool outdentLabels = true; + + /// + @Help("Decrease the indentation level of attributes") + bool outdentAttributes = true; + + /// + @Help("Place operators on the end of the previous line when splitting lines") + bool splitOperatorAtEnd = false; + + /// + @Help("Insert spaces after the closing paren of a cast expression") + bool spaceAfterCast = true; + + /// + @Help("Newline style can be 'cr', 'lf', or 'crlf'") + Newlines newlineType; + + /** + * Returns: + * true if the configuration is valid + */ + bool isValid() + { + import std.stdio : stderr; + + if (columnSoftLimit > columnHardLimit) + { + stderr.writeln("Column hard limit must be greater than or equal to column soft limit"); + return false; + } + return true; + } +} + +private struct Help +{ + string text; } diff --git a/src/dfmt/main.d b/src/dfmt/main.d index 3905921..5967e32 100644 --- a/src/dfmt/main.d +++ b/src/dfmt/main.d @@ -24,20 +24,34 @@ else { int main(string[] args) { - import std.getopt : getopt; + import std.getopt : getopt, defaultGetoptPrinter; bool inplace = false; - bool show_usage = false; - FormatterConfig formatterConfig; - getopt(args, "help|h", &show_usage, "inplace", &inplace, "tabs|t", - &formatterConfig.useTabs, "braces", &formatterConfig.braceStyle); - if (show_usage) - { - import std.path : baseName; + Config config; + auto getOptResult = getopt(args, + "inplace", "Modify files in-place", &inplace, + "tabs|t", getHelp!(Config.useTabs), &config.useTabs, + "braces", getHelp!(Config.braceStyle), &config.braceStyle, + "colSoft", getHelp!(Config.columnSoftLimit), &config.columnSoftLimit, + "colHard", getHelp!(Config.columnHardLimit), &config.columnHardLimit, + "tabSize", getHelp!(Config.tabSize), &config.tabSize, + "indentSize", getHelp!(Config.indentSize), &config.indentSize, + "alignSwitchCases", getHelp!(Config.alignSwitchStatements), &config.alignSwitchStatements, + "outdentLabels", getHelp!(Config.outdentLabels), &config.outdentLabels, + "outdentAttributes", getHelp!(Config.outdentAttributes), &config.outdentAttributes, + "splitOperatorAtEnd", getHelp!(Config.splitOperatorAtEnd), &config.splitOperatorAtEnd, + "spaceAfterCast", getHelp!(Config.spaceAfterCast), &config.spaceAfterCast, + "newlineType", getHelp!(Config.newlineType), &config.newlineType); - writef(USAGE, baseName(args[0])); + if (getOptResult.helpWanted) + { + defaultGetoptPrinter("dfmt 0.3.0-dev\nOptions:", getOptResult.options); return 0; } + + if (!config.isValid()) + return 1; + File output = stdout; ubyte[] buffer; args.popFront(); @@ -53,7 +67,7 @@ else else break; } - format("stdin", buffer, output.lockingTextWriter(), &formatterConfig); + format("stdin", buffer, output.lockingTextWriter(), &config); } else { @@ -79,7 +93,7 @@ else f.rawRead(buffer); if (inplace) output = File(path, "wb"); - format(path, buffer, output.lockingTextWriter(), &formatterConfig); + format(path, buffer, output.lockingTextWriter(), &config); } } return 0; @@ -88,19 +102,8 @@ else private: -immutable USAGE = "usage: %s [--inplace] [...] -Formats D code. - - --inplace Change file in-place instead of outputing to stdout - (implicit in case of multiple files) - --tabs | -t Use tabs instead of spaces for indentation - --braces=allman Use Allman indent style (default) - --braces=otbs Use the One True Brace Style - --help | -h Display this help and exit -"; - void format(OutputRange)(string source_desc, ubyte[] buffer, OutputRange output, - FormatterConfig* formatterConfig) + Config* formatterConfig) { LexerConfig config; config.stringBehavior = StringBehavior.source; @@ -160,7 +163,7 @@ struct TokenFormatter(OutputRange) * decisions. */ this(const(Token)[] tokens, immutable short[] depths, OutputRange output, - ASTInformation* astInformation, FormatterConfig* config) + ASTInformation* astInformation, Config* config) { this.tokens = tokens; this.depths = depths; @@ -206,7 +209,7 @@ private: IndentStack indents; /// Configuration - const FormatterConfig* config; + const Config* config; /// Keep track of whether or not an extra newline was just added because of /// an import statement. @@ -601,7 +604,8 @@ private: } else { - if (!justAddedExtraNewline && !peekBackIsOneOf(false, tok!"{", tok!"}", tok!";", tok!";")) + if (!justAddedExtraNewline && !peekBackIsOneOf(false, tok!"{", + tok!"}", tok!";", tok!";")) { if (config.braceStyle == BraceStyle.otbs) { @@ -611,7 +615,8 @@ private: { indents.popWrapIndents(); indents.push(tok!"{"); - if (index == 1 || peekBackIsOneOf(true, tok!":", tok!"{", tok!"}", tok!")", tok!";")) + if (index == 1 || peekBackIsOneOf(true, tok!":", tok!"{", + tok!"}", tok!")", tok!";")) { indentLevel = indents.indentSize - 1; } @@ -653,8 +658,8 @@ private: else { // Silly hack to format enums better. - if ((peekBackIsLiteralOrIdent() || peekBackIsOneOf(true, tok!")", tok!",")) - && !peekBackIsSlashSlash()) + if ((peekBackIsLiteralOrIdent() || peekBackIsOneOf(true, tok!")", + tok!",")) && !peekBackIsSlashSlash()) newline(); write("}"); if (index + 1 < tokens.length @@ -950,7 +955,8 @@ private: else { writeToken(); - if (!currentIs(tok!")") && !currentIs(tok!"]") && !currentIs(tok!"}") && !currentIs(tok!"comment")) + if (!currentIs(tok!")") && !currentIs(tok!"]") + && !currentIs(tok!"}") && !currentIs(tok!"comment")) { write(" "); } @@ -1034,8 +1040,8 @@ private: } else if (currentIs(tok!"case") || currentIs(tok!"default")) { - while (indents.length && (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)) - && isTempIndent(indents.top())) + while (indents.length && (peekBackIs(tok!"}", true) + || peekBackIs(tok!";", true)) && isTempIndent(indents.top())) { indents.pop(); } @@ -1049,7 +1055,8 @@ private: { indents.popWrapIndents(); indents.push(tok!"{"); - if (index == 1 || peekBackIsOneOf(true, tok!":", tok!"{", tok!"}", tok!")", tok!";")) + if (index == 1 || peekBackIsOneOf(true, tok!":", tok!"{", + tok!"}", tok!")", tok!";")) { indentLevel = indents.indentSize - 1; } @@ -1086,8 +1093,8 @@ private: } else { - while (indents.length && (peekBackIsOneOf(true, tok!"}", tok!";") - && indents.top != tok!";") && isTempIndent(indents.top())) + while (indents.length && (peekBackIsOneOf(true, tok!"}", + tok!";") && indents.top != tok!";") && isTempIndent(indents.top())) { indents.pop(); } @@ -1216,8 +1223,7 @@ const pure @safe @nogc: return tokenLength(tokens[i]); } - ref current() nothrow - in + ref current() nothrow in { assert(index < tokens.length); } diff --git a/src/dfmt/wrapping.d b/src/dfmt/wrapping.d index c1cd003..926308b 100644 --- a/src/dfmt/wrapping.d +++ b/src/dfmt/wrapping.d @@ -12,7 +12,7 @@ import dfmt.config; struct State { this(size_t[] breaks, const Token[] tokens, immutable short[] depths, int depth, - const FormatterConfig* formatterConfig, int currentLineLength, int indentLevel) pure @safe + const Config* config, int currentLineLength, int indentLevel) pure @safe { import std.math : abs; @@ -39,9 +39,9 @@ struct State { immutable int l = currentLineLength + tokens.map!(a => tokenLength(a)).sum(); _cost = l; - if (l > formatterConfig.columnSoftLimit) + if (l > config.columnSoftLimit) { - immutable longPenalty = (l - formatterConfig.columnSoftLimit) * remainingCharsMultiplier; + immutable longPenalty = (l - config.columnSoftLimit) * remainingCharsMultiplier; _cost += longPenalty; this._solved = longPenalty < newlinePenalty; } @@ -54,15 +54,15 @@ struct State { immutable size_t j = breakIndex < breaks.length ? breaks[breakIndex] : tokens.length; ll += tokens[i .. j].map!(a => tokenLength(a)).sum(); - if (ll > formatterConfig.columnHardLimit) + if (ll > config.columnHardLimit) { this._solved = false; break; } - else if (ll > formatterConfig.columnSoftLimit) - _cost += (ll - formatterConfig.columnSoftLimit) * remainingCharsMultiplier; + else if (ll > config.columnSoftLimit) + _cost += (ll - config.columnSoftLimit) * remainingCharsMultiplier; i = j; - ll = indentLevel * formatterConfig.indentSize; + ll = indentLevel * config.indentSize; breakIndex++; } while (i + 1 < tokens.length); @@ -113,7 +113,7 @@ private: } size_t[] chooseLineBreakTokens(size_t index, const Token[] tokens, immutable short[] depths, - const FormatterConfig* formatterConfig, int currentLineLength, int indentLevel) pure + const Config* config, int currentLineLength, int indentLevel) pure { import std.container.rbtree : RedBlackTree; import std.algorithm : filter, min; @@ -123,7 +123,7 @@ size_t[] chooseLineBreakTokens(size_t index, const Token[] tokens, immutable sho int depth = 0; auto open = new RedBlackTree!State; open.insert(State(cast(size_t[])[], tokens[0 .. tokensEnd], - depths[0 .. tokensEnd], depth, formatterConfig, currentLineLength, indentLevel)); + depths[0 .. tokensEnd], depth, config, currentLineLength, indentLevel)); State lowest; while (!open.empty) { @@ -137,7 +137,7 @@ size_t[] chooseLineBreakTokens(size_t index, const Token[] tokens, immutable sho return current.breaks; } foreach (next; validMoves(tokens[0 .. tokensEnd], depths[0 .. tokensEnd], - current, formatterConfig, currentLineLength, indentLevel, depth)) + current, config, currentLineLength, indentLevel, depth)) { open.insert(next); } @@ -156,7 +156,7 @@ size_t[] chooseLineBreakTokens(size_t index, const Token[] tokens, immutable sho } State[] validMoves(const Token[] tokens, immutable short[] depths, ref const State current, - const FormatterConfig* formatterConfig, int currentLineLength, int indentLevel, + const Config* config, int currentLineLength, int indentLevel, int depth) pure @safe { import std.algorithm : sort, canFind; @@ -171,7 +171,7 @@ State[] validMoves(const Token[] tokens, immutable short[] depths, ref const Sta breaks ~= current.breaks; breaks ~= i; sort(breaks); - states ~= State(breaks, tokens, depths, depth + 1, formatterConfig, + states ~= State(breaks, tokens, depths, depth + 1, config, currentLineLength, indentLevel); } return states;