|
8 | 8 |
|
9 | 9 | package org.elasticsearch.gradle.internal.doc;
|
10 | 10 |
|
11 |
| -import org.gradle.api.InvalidUserDataException; |
12 |
| - |
13 |
| -import java.io.File; |
14 |
| -import java.io.IOException; |
15 |
| -import java.nio.charset.StandardCharsets; |
16 |
| -import java.nio.file.Files; |
17 |
| -import java.nio.file.Path; |
18 |
| -import java.util.ArrayList; |
19 |
| -import java.util.Collection; |
20 | 11 | import java.util.List;
|
21 | 12 | import java.util.Map;
|
22 |
| -import java.util.function.BiConsumer; |
23 | 13 | import java.util.regex.Matcher;
|
24 | 14 | import java.util.regex.Pattern;
|
25 |
| -import java.util.stream.Collectors; |
26 |
| -import java.util.stream.Stream; |
27 | 15 |
|
28 |
| -public class AsciidocSnippetParser implements SnippetParser { |
| 16 | +public class AsciidocSnippetParser extends SnippetParser { |
29 | 17 | public static final Pattern SNIPPET_PATTERN = Pattern.compile("-{4,}\\s*");
|
| 18 | + public static final Pattern TEST_RESPONSE_PATTERN = Pattern.compile("\\/\\/\s*TESTRESPONSE(\\[(.+)\\])?\s*"); |
| 19 | + public static final Pattern SOURCE_PATTERN = Pattern.compile( |
| 20 | + "\\[\"?source\"?(?:\\.[^,]+)?,\\s*\"?([-\\w]+)\"?(,((?!id=).)*(id=\"?([-\\w]+)\"?)?(.*))?].*" |
| 21 | + ); |
30 | 22 |
|
31 |
| - private static final String CATCH = "catch:\\s*((?:\\/[^\\/]+\\/)|[^ \\]]+)"; |
32 |
| - private static final String SKIP_REGEX = "skip:([^\\]]+)"; |
33 |
| - private static final String SETUP = "setup:([^ \\]]+)"; |
34 |
| - private static final String TEARDOWN = "teardown:([^ \\]]+)"; |
35 |
| - private static final String WARNING = "warning:(.+)"; |
36 |
| - private static final String NON_JSON = "(non_json)"; |
37 |
| - private static final String SCHAR = "(?:\\\\\\/|[^\\/])"; |
38 |
| - private static final String SUBSTITUTION = "s\\/(" + SCHAR + "+)\\/(" + SCHAR + "*)\\/"; |
39 |
| - private static final String TEST_SYNTAX = "(?:" |
40 |
| - + CATCH |
41 |
| - + "|" |
42 |
| - + SUBSTITUTION |
43 |
| - + "|" |
44 |
| - + SKIP_REGEX |
45 |
| - + "|(continued)|" |
46 |
| - + SETUP |
47 |
| - + "|" |
48 |
| - + TEARDOWN |
49 |
| - + "|" |
50 |
| - + WARNING |
51 |
| - + "|(skip_shard_failures)) ?"; |
52 |
| - |
53 |
| - private final Map<String, String> defaultSubstitutions; |
| 23 | + public static final String CONSOLE_REGEX = "\\/\\/\s*CONSOLE\s*"; |
| 24 | + public static final String NOTCONSOLE_REGEX = "\\/\\/\s*NOTCONSOLE\s*"; |
| 25 | + public static final String TESTSETUP_REGEX = "\\/\\/\s*TESTSETUP\s*"; |
| 26 | + public static final String TEARDOWN_REGEX = "\\/\\/\s*TEARDOWN\s*"; |
54 | 27 |
|
55 | 28 | public AsciidocSnippetParser(Map<String, String> defaultSubstitutions) {
|
56 |
| - this.defaultSubstitutions = defaultSubstitutions; |
| 29 | + super(defaultSubstitutions); |
57 | 30 | }
|
58 | 31 |
|
59 | 32 | @Override
|
60 |
| - public List<Snippet> parseDoc(File rootDir, File docFile, List<Map.Entry<String, String>> substitutions) { |
61 |
| - String lastLanguage = null; |
62 |
| - Snippet snippet = null; |
63 |
| - String name = null; |
64 |
| - int lastLanguageLine = 0; |
65 |
| - StringBuilder contents = null; |
66 |
| - List<Snippet> snippets = new ArrayList<>(); |
| 33 | + protected Pattern testResponsePattern() { |
| 34 | + return TEST_RESPONSE_PATTERN; |
| 35 | + } |
67 | 36 |
|
68 |
| - try (Stream<String> lines = Files.lines(docFile.toPath(), StandardCharsets.UTF_8)) { |
69 |
| - List<String> linesList = lines.collect(Collectors.toList()); |
70 |
| - for (int lineNumber = 0; lineNumber < linesList.size(); lineNumber++) { |
71 |
| - String line = linesList.get(lineNumber); |
72 |
| - if (SNIPPET_PATTERN.matcher(line).matches()) { |
73 |
| - if (snippet == null) { |
74 |
| - Path path = rootDir.toPath().relativize(docFile.toPath()); |
75 |
| - snippet = new Snippet(path, lineNumber + 1, name); |
76 |
| - snippets.add(snippet); |
77 |
| - if (lastLanguageLine == lineNumber - 1) { |
78 |
| - snippet.language = lastLanguage; |
79 |
| - } |
80 |
| - name = null; |
81 |
| - } else { |
82 |
| - snippet.end = lineNumber + 1; |
83 |
| - } |
84 |
| - continue; |
85 |
| - } |
| 37 | + protected Pattern testPattern() { |
| 38 | + return Pattern.compile("\\/\\/\s*TEST(\\[(.+)\\])?\s*"); |
| 39 | + } |
86 | 40 |
|
87 |
| - Source source = matchSource(line); |
88 |
| - if (source.matches) { |
89 |
| - lastLanguage = source.language; |
90 |
| - lastLanguageLine = lineNumber; |
91 |
| - name = source.name; |
92 |
| - continue; |
93 |
| - } |
94 |
| - if (consoleHandled(docFile.getName(), lineNumber, line, snippet)) { |
95 |
| - continue; |
96 |
| - } |
97 |
| - if (testHandled(docFile.getName(), lineNumber, line, snippet, substitutions)) { |
98 |
| - continue; |
99 |
| - } |
100 |
| - if (testResponseHandled(docFile.getName(), lineNumber, line, snippet, substitutions)) { |
101 |
| - continue; |
| 41 | + private int lastLanguageLine = 0; |
| 42 | + private String currentName = null; |
| 43 | + private String lastLanguage = null; |
| 44 | + |
| 45 | + protected void parseLine(List<Snippet> snippets, int lineNumber, String line) { |
| 46 | + if (SNIPPET_PATTERN.matcher(line).matches()) { |
| 47 | + if (snippetBuilder == null) { |
| 48 | + snippetBuilder = newSnippetBuilder().withLineNumber(lineNumber + 1) |
| 49 | + .withName(currentName) |
| 50 | + .withSubstitutions(defaultSubstitutions); |
| 51 | + if (lastLanguageLine == lineNumber - 1) { |
| 52 | + snippetBuilder.withLanguage(lastLanguage); |
102 | 53 | }
|
103 |
| - if (line.matches("\\/\\/\s*TESTSETUP\s*")) { |
104 |
| - snippet.testSetup = true; |
105 |
| - continue; |
106 |
| - } |
107 |
| - if (line.matches("\\/\\/\s*TEARDOWN\s*")) { |
108 |
| - snippet.testTearDown = true; |
109 |
| - continue; |
110 |
| - } |
111 |
| - if (snippet == null) { |
112 |
| - // Outside |
113 |
| - continue; |
114 |
| - } |
115 |
| - if (snippet.end == Snippet.NOT_FINISHED) { |
116 |
| - // Inside |
117 |
| - if (contents == null) { |
118 |
| - contents = new StringBuilder(); |
119 |
| - } |
120 |
| - // We don't need the annotations |
121 |
| - line = line.replaceAll("<\\d+>", ""); |
122 |
| - // Nor any trailing spaces |
123 |
| - line = line.replaceAll("\s+$", ""); |
124 |
| - contents.append(line).append("\n"); |
125 |
| - continue; |
126 |
| - } |
127 |
| - // Allow line continuations for console snippets within lists |
128 |
| - if (snippet != null && line.trim().equals("+")) { |
129 |
| - continue; |
130 |
| - } |
131 |
| - finalizeSnippet(snippet, contents.toString(), defaultSubstitutions, substitutions); |
132 |
| - substitutions = new ArrayList<>(); |
133 |
| - ; |
134 |
| - snippet = null; |
135 |
| - contents = null; |
136 |
| - } |
137 |
| - if (snippet != null) { |
138 |
| - finalizeSnippet(snippet, contents.toString(), defaultSubstitutions, substitutions); |
139 |
| - contents = null; |
140 |
| - snippet = null; |
141 |
| - substitutions = new ArrayList<>(); |
| 54 | + currentName = null; |
| 55 | + } else { |
| 56 | + snippetBuilder.withEnd(lineNumber + 1); |
142 | 57 | }
|
143 |
| - } catch (IOException e) { |
144 |
| - e.printStackTrace(); |
| 58 | + return; |
145 | 59 | }
|
146 |
| - return snippets; |
147 |
| - } |
148 | 60 |
|
149 |
| - static Snippet finalizeSnippet( |
150 |
| - final Snippet snippet, |
151 |
| - String contents, |
152 |
| - Map<String, String> defaultSubstitutions, |
153 |
| - Collection<Map.Entry<String, String>> substitutions |
154 |
| - ) { |
155 |
| - snippet.contents = contents.toString(); |
156 |
| - snippet.validate(); |
157 |
| - escapeSubstitutions(snippet, defaultSubstitutions, substitutions); |
158 |
| - return snippet; |
| 61 | + Source source = matchSource(line); |
| 62 | + if (source.matches) { |
| 63 | + lastLanguage = source.language; |
| 64 | + lastLanguageLine = lineNumber; |
| 65 | + currentName = source.name; |
| 66 | + return; |
| 67 | + } |
| 68 | + handleCommons(snippets, line); |
159 | 69 | }
|
160 | 70 |
|
161 |
| - private static void escapeSubstitutions( |
162 |
| - Snippet snippet, |
163 |
| - Map<String, String> defaultSubstitutions, |
164 |
| - Collection<Map.Entry<String, String>> substitutions |
165 |
| - ) { |
166 |
| - BiConsumer<String, String> doSubstitution = (pattern, subst) -> { |
167 |
| - /* |
168 |
| - * $body is really common but it looks like a |
169 |
| - * backreference so we just escape it here to make the |
170 |
| - * tests cleaner. |
171 |
| - */ |
172 |
| - subst = subst.replace("$body", "\\$body"); |
173 |
| - subst = subst.replace("$_path", "\\$_path"); |
174 |
| - subst = subst.replace("\\n", "\n"); |
175 |
| - snippet.contents = snippet.contents.replaceAll(pattern, subst); |
176 |
| - }; |
177 |
| - defaultSubstitutions.forEach(doSubstitution); |
178 |
| - |
179 |
| - if (substitutions != null) { |
180 |
| - substitutions.forEach(e -> doSubstitution.accept(e.getKey(), e.getValue())); |
181 |
| - } |
| 71 | + protected String getTestSetupRegex() { |
| 72 | + return TESTSETUP_REGEX; |
182 | 73 | }
|
183 | 74 |
|
184 |
| - private boolean testResponseHandled( |
185 |
| - String name, |
186 |
| - int lineNumber, |
187 |
| - String line, |
188 |
| - Snippet snippet, |
189 |
| - final List<Map.Entry<String, String>> substitutions |
190 |
| - ) { |
191 |
| - Matcher matcher = Pattern.compile("\\/\\/\s*TESTRESPONSE(\\[(.+)\\])?\s*").matcher(line); |
192 |
| - if (matcher.matches()) { |
193 |
| - if (snippet == null) { |
194 |
| - throw new InvalidUserDataException(name + ":" + lineNumber + ": TESTRESPONSE not paired with a snippet at "); |
195 |
| - } |
196 |
| - snippet.testResponse = true; |
197 |
| - if (matcher.group(2) != null) { |
198 |
| - String loc = name + ":" + lineNumber; |
199 |
| - ParsingUtils.parse( |
200 |
| - loc, |
201 |
| - matcher.group(2), |
202 |
| - "(?:" + SUBSTITUTION + "|" + NON_JSON + "|" + SKIP_REGEX + ") ?", |
203 |
| - (Matcher m, Boolean last) -> { |
204 |
| - if (m.group(1) != null) { |
205 |
| - // TESTRESPONSE[s/adsf/jkl/] |
206 |
| - substitutions.add(Map.entry(m.group(1), m.group(2))); |
207 |
| - } else if (m.group(3) != null) { |
208 |
| - // TESTRESPONSE[non_json] |
209 |
| - substitutions.add(Map.entry("^", "/")); |
210 |
| - substitutions.add(Map.entry("\n$", "\\\\s*/")); |
211 |
| - substitutions.add(Map.entry("( +)", "$1\\\\s+")); |
212 |
| - substitutions.add(Map.entry("\n", "\\\\s*\n ")); |
213 |
| - } else if (m.group(4) != null) { |
214 |
| - // TESTRESPONSE[skip:reason] |
215 |
| - snippet.skip = m.group(4); |
216 |
| - } |
217 |
| - } |
218 |
| - ); |
219 |
| - } |
220 |
| - return true; |
221 |
| - } |
222 |
| - return false; |
| 75 | + protected String getTeardownRegex() { |
| 76 | + return TEARDOWN_REGEX; |
223 | 77 | }
|
224 | 78 |
|
225 |
| - private boolean testHandled(String name, int lineNumber, String line, Snippet snippet, List<Map.Entry<String, String>> substitutions) { |
226 |
| - Matcher matcher = Pattern.compile("\\/\\/\s*TEST(\\[(.+)\\])?\s*").matcher(line); |
227 |
| - if (matcher.matches()) { |
228 |
| - if (snippet == null) { |
229 |
| - throw new InvalidUserDataException(name + ":" + lineNumber + ": TEST not paired with a snippet at "); |
230 |
| - } |
231 |
| - snippet.test = true; |
232 |
| - if (matcher.group(2) != null) { |
233 |
| - String loc = name + ":" + lineNumber; |
234 |
| - ParsingUtils.parse(loc, matcher.group(2), TEST_SYNTAX, (Matcher m, Boolean last) -> { |
235 |
| - if (m.group(1) != null) { |
236 |
| - snippet.catchPart = m.group(1); |
237 |
| - return; |
238 |
| - } |
239 |
| - if (m.group(2) != null) { |
240 |
| - substitutions.add(Map.entry(m.group(2), m.group(3))); |
241 |
| - return; |
242 |
| - } |
243 |
| - if (m.group(4) != null) { |
244 |
| - snippet.skip = m.group(4); |
245 |
| - return; |
246 |
| - } |
247 |
| - if (m.group(5) != null) { |
248 |
| - snippet.continued = true; |
249 |
| - return; |
250 |
| - } |
251 |
| - if (m.group(6) != null) { |
252 |
| - snippet.setup = m.group(6); |
253 |
| - return; |
254 |
| - } |
255 |
| - if (m.group(7) != null) { |
256 |
| - snippet.teardown = m.group(7); |
257 |
| - return; |
258 |
| - } |
259 |
| - if (m.group(8) != null) { |
260 |
| - snippet.warnings.add(m.group(8)); |
261 |
| - return; |
262 |
| - } |
263 |
| - if (m.group(9) != null) { |
264 |
| - snippet.skipShardsFailures = true; |
265 |
| - return; |
266 |
| - } |
267 |
| - throw new InvalidUserDataException("Invalid test marker: " + line); |
268 |
| - }); |
269 |
| - } |
270 |
| - return true; |
271 |
| - } |
272 |
| - return false; |
| 79 | + protected String getNotconsoleRegex() { |
| 80 | + return NOTCONSOLE_REGEX; |
273 | 81 | }
|
274 | 82 |
|
275 |
| - private boolean consoleHandled(String fileName, int lineNumber, String line, Snippet snippet) { |
276 |
| - if (line.matches("\\/\\/\s*CONSOLE\s*")) { |
277 |
| - if (snippet == null) { |
278 |
| - throw new InvalidUserDataException(fileName + ":" + lineNumber + ": CONSOLE not paired with a snippet"); |
279 |
| - } |
280 |
| - if (snippet.console != null) { |
281 |
| - throw new InvalidUserDataException(fileName + ":" + lineNumber + ": Can't be both CONSOLE and NOTCONSOLE"); |
282 |
| - } |
283 |
| - snippet.console = true; |
284 |
| - return true; |
285 |
| - } else if (line.matches("\\/\\/\s*NOTCONSOLE\s*")) { |
286 |
| - if (snippet == null) { |
287 |
| - throw new InvalidUserDataException(fileName + ":" + lineNumber + ": NOTCONSOLE not paired with a snippet"); |
288 |
| - } |
289 |
| - if (snippet.console != null) { |
290 |
| - throw new InvalidUserDataException(fileName + ":" + lineNumber + ": Can't be both CONSOLE and NOTCONSOLE"); |
291 |
| - } |
292 |
| - snippet.console = false; |
293 |
| - return true; |
294 |
| - } |
295 |
| - return false; |
| 83 | + protected String getConsoleRegex() { |
| 84 | + return CONSOLE_REGEX; |
296 | 85 | }
|
297 | 86 |
|
298 | 87 | static Source matchSource(String line) {
|
299 |
| - Pattern pattern = Pattern.compile("\\[\"?source\"?(?:\\.[^,]+)?,\\s*\"?([-\\w]+)\"?(,((?!id=).)*(id=\"?([-\\w]+)\"?)?(.*))?].*"); |
300 |
| - Matcher matcher = pattern.matcher(line); |
| 88 | + Matcher matcher = SOURCE_PATTERN.matcher(line); |
301 | 89 | if (matcher.matches()) {
|
302 | 90 | return new Source(true, matcher.group(1), matcher.group(5));
|
303 | 91 | }
|
|
0 commit comments