1 module workspaced.helpers; 2 3 import std.ascii; 4 import std.string; 5 6 string determineIndentation(scope const(char)[] code) @safe 7 { 8 const(char)[] indent = null; 9 foreach (line; code.lineSplitter) 10 { 11 if (line.strip.length == 0) 12 continue; 13 indent = line[0 .. $ - line.stripLeft.length]; 14 } 15 return indent.idup; 16 } 17 18 int stripLineEndingLength(scope const(char)[] code) @safe @nogc 19 { 20 switch (code.length) 21 { 22 case 0: 23 return 0; 24 case 1: 25 return code[0] == '\r' || code[0] == '\n' ? 1 : 0; 26 default: 27 if (code[$ - 2 .. $] == "\r\n") 28 return 2; 29 else if (code[$ - 1] == '\r' || code[$ - 1] == '\n') 30 return 1; 31 else 32 return 0; 33 } 34 } 35 36 bool isIdentifierChar(dchar c) @safe @nogc 37 { 38 return c.isAlphaNum || c == '_'; 39 } 40 41 ptrdiff_t indexOfKeyword(scope const(char)[] code, string keyword, ptrdiff_t start = 0) @safe @nogc 42 { 43 ptrdiff_t index = start; 44 while (true) 45 { 46 index = code.indexOf(keyword, index); 47 if (index == -1) 48 break; 49 50 if ((index > 0 && code[index - 1].isIdentifierChar) 51 || (index + keyword.length < code.length && code[index + keyword.length].isIdentifierChar)) 52 { 53 index++; 54 continue; 55 } 56 else 57 break; 58 } 59 return index; 60 } 61 62 bool endsWithKeyword(scope const(char)[] code, string keyword) @safe @nogc 63 { 64 return code == keyword || (code.endsWith(keyword) && code[$ - 1 - keyword.length] 65 .isIdentifierChar); 66 } 67 68 bool isIdentifierSeparatingChar(dchar c) @safe @nogc 69 { 70 return c < 48 || (c > 57 && c < 65) || c == '[' || c == '\\' || c == ']' 71 || c == '`' || (c > 122 && c < 128) || c == '\u2028' || c == '\u2029'; // line separators 72 } 73 74 version (unittest) 75 { 76 import std.json; 77 78 /// Iterates over all files in the given folder, reads them as D files until 79 /// a __EOF__ token is encountered, then parses the following lines in this 80 /// format per file: 81 /// - If the line is empty or starts with `//` ignore it 82 /// - If the line starts with `:` it's a variable assignment in form `:variable=JSON` 83 /// - Otherwise it's a tab separated line like `1 2 3` 84 /// Finally, it's tested that at least one test has been tested. 85 void runTestDataFileTests(string dir, 86 void delegate() onFileStart, 87 void delegate(string code, string variable, JSONValue value) setVariable, 88 void delegate(string code, string[] parts, string line) onTestLine, 89 void delegate(string code) onFileFinished, 90 string __file = __FILE__, 91 size_t __line = __LINE__) 92 { 93 import core.exception; 94 import std.algorithm; 95 import std.array; 96 import std.conv; 97 import std.file; 98 import std.stdio; 99 100 int noTested = 0; 101 foreach (testFile; dirEntries(dir, SpanMode.shallow)) 102 { 103 int lineNo = 0; 104 try 105 { 106 auto testCode = appender!string; 107 bool inCode = true; 108 if (onFileStart) 109 onFileStart(); 110 foreach (line; File(testFile, "r").byLine) 111 { 112 lineNo++; 113 if (line == "__EOF__") 114 { 115 inCode = false; 116 continue; 117 } 118 119 if (inCode) 120 { 121 testCode ~= line; 122 testCode ~= '\n'; // normalize CRLF to LF 123 } 124 else if (!line.length || line.startsWith("//")) 125 { 126 continue; 127 } 128 else if (line[0] == ':') 129 { 130 auto variable = line[1 .. $].idup.findSplit("="); 131 if (setVariable) 132 setVariable(testCode.data, variable[0], parseJSON(variable[2])); 133 } 134 else 135 { 136 if (onTestLine) 137 { 138 string lineDup = line.idup; 139 onTestLine(testCode.data, lineDup.split("\t"), lineDup); 140 } 141 } 142 } 143 144 if (onFileFinished) 145 onFileFinished(testCode.data); 146 noTested++; 147 } 148 catch (AssertError e) 149 { 150 e.file = __file; 151 e.line = __line; 152 e.msg = "in " ~ testFile ~ "(" ~ lineNo.to!string ~ "): " ~ e.msg; 153 throw e; 154 } 155 } 156 157 assert(noTested > 0); 158 } 159 }