1 /++ 2 Utility functions for string processing 3 Modified from https://github.com/dlang-community/dmarkdown/blob/master/source/dmarkdown/string.d 4 +/ 5 module md2d.util; 6 7 import std.range, 8 std.string; 9 import std.algorithm : canFind; 10 public import std.conv : to; 11 public import tame.text.uri : asSlug; 12 13 @safe pure: 14 /++ 15 Checks if all characters in 'str' are contained in 'chars'. 16 +/ 17 bool allOf(S)(S str, S chars) { 18 foreach (ch; str) 19 if (!chars.canFind(ch)) 20 return false; 21 return true; 22 } 23 24 /++ 25 Checks if any character in 'str' is contained in 'chars'. 26 +/ 27 bool anyOf(S)(S str, S chars) { 28 foreach (ch; str) 29 if (chars.canFind(ch)) 30 return true; 31 return false; 32 } 33 34 /++ 35 Finds the closing bracket (works with any of '[', '$(LPAREN)', '<', '{'). 36 37 Params: 38 str = input string 39 nested = whether to skip nested brackets 40 Returns: 41 The index of the closing bracket or -1 for unbalanced strings 42 and strings that don't start with a bracket. 43 +/ 44 ptrdiff_t matchBracket(string str, bool nested = true) nothrow { 45 if (str.length < 2) 46 return -1; 47 48 // dfmt off 49 char open = str[0], close = void; 50 switch (str[0]) { 51 case '[': close = ']'; break; 52 case '(': close = ')'; break; 53 case '<': close = '>'; break; 54 case '{': close = '}'; break; 55 default: return -1; 56 } 57 // dfmt on 58 59 size_t level = 1; 60 foreach (i, c; str[1 .. $]) { 61 if (nested && c == open) 62 ++level; 63 else if (c == close) 64 --level; 65 if (level == 0) 66 return i + 1; 67 } 68 return -1; 69 } 70 71 bool isLineBlank(in char[] ln) => allOf(ln, " \t"); 72 73 pure @safe: 74 @nogc { 75 bool isLineIndented(in char[] ln) => ln.startsWith('\t') || ln.startsWith(" "); 76 77 auto unindentLine(in char[] ln) { 78 if (ln.startsWith('\t')) 79 return ln[1 .. $]; 80 if (ln.startsWith(" ")) 81 return ln[4 .. $]; 82 return ln; 83 } 84 85 bool isAtxHeaderLine(const(char)[] ln) { 86 ln = ln.stripLeft; 87 size_t i = 0; 88 while (i < ln.length && ln[i] == '#') 89 i++; 90 if (i < 1 || i > 6 || i >= ln.length) 91 return false; 92 return ln[i] == ' '; 93 } 94 } 95 96 bool isSetextHeaderLine(const(char)[] ln, char subHeaderChar) { 97 ln = ln.stripLeft; 98 if (ln.length < 1) 99 return false; 100 if (ln[0] == '=') { 101 while (!ln.empty && ln.front == '=') 102 ln.popFront(); 103 return allOf(ln, " \t"); 104 } 105 if (ln[0] == subHeaderChar) { 106 while (!ln.empty && ln.front == subHeaderChar) 107 ln.popFront(); 108 return allOf(ln, " \t"); 109 } 110 return false; 111 } 112 113 bool isHlineLine(in char[] ln) => 114 (allOf(ln, " -") && count(ln, '-') >= 3) || 115 (allOf(ln, " *") && count(ln, '*') >= 3) || 116 (allOf(ln, " _") && count(ln, '_') >= 3); 117 118 bool isQuoteLine(in char[] ln) => ln.stripLeft().startsWith(">"); 119 120 size_t getQuoteLevel(const(char)[] ln) { 121 size_t level; 122 ln = stripLeft(ln); 123 while (ln.length > 0 && ln[0] == '>') { 124 level++; 125 ln = stripLeft(ln[1 .. $]); 126 } 127 return level; 128 } 129 130 bool isUListLine(const(char)[] ln) { 131 ln = ln.stripLeft; 132 if (ln.length < 2) 133 return false; 134 if (!"*+-".canFind(ln[0])) 135 return false; 136 if (ln[1] != ' ' && ln[1] != '\t') 137 return false; 138 return true; 139 } 140 141 bool isOListLine(const(char)[] ln) { 142 ln = ln.stripLeft; 143 if (!ln.length || ln[0] < '0' || ln[0] > '9') 144 return false; 145 ln = ln[1 .. $]; 146 while (ln.length && ln[0] >= '0' && ln[0] <= '9') 147 ln = ln[1 .. $]; 148 if (ln.length < 2) 149 return false; 150 if (ln[0] != '.') 151 return false; 152 if (ln[1] != ' ' && ln[1] != '\t') 153 return false; 154 return true; 155 } 156 157 bool isTableRowLine(bool proper = false)(in char[] ln) { 158 static if (proper) 159 return ln.indexOf(" | ") >= 0 160 && !ln.isOListLine 161 && !ln.isUListLine 162 && !ln.isAtxHeaderLine; 163 else 164 return ln.indexOf(" | ") >= 0; 165 } 166 167 bool isCodeBlockDelimiter(in char[] ln) => ln.startsWith("```");