1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.site;
21
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.Path;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.regex.Pattern;
30 import java.util.stream.Collectors;
31
32 import org.apache.maven.doxia.macro.AbstractMacro;
33 import org.apache.maven.doxia.macro.Macro;
34 import org.apache.maven.doxia.macro.MacroExecutionException;
35 import org.apache.maven.doxia.macro.MacroRequest;
36 import org.apache.maven.doxia.sink.Sink;
37 import org.codehaus.plexus.component.annotations.Component;
38
39
40
41
42 @Component(role = Macro.class, hint = "example")
43 public class ExampleMacro extends AbstractMacro {
44
45
46 private static final String XML_CONFIG_START = "/*xml";
47
48
49 private static final String XML_CONFIG_END = "*/";
50
51
52 private static final String CODE_SNIPPET_START = "// xdoc section -- start";
53
54
55 private static final String CODE_SNIPPET_END = "// xdoc section -- end";
56
57
58 private static final String NEWLINE = System.lineSeparator();
59
60
61 private static final String INDENTATION = " ";
62
63
64 private static final Pattern XML_PATTERN = Pattern.compile(
65 "^\\s*(<!DOCTYPE\\s+.*?>|<\\?xml\\s+.*?>|<module\\s+.*?>)\\s*",
66 Pattern.DOTALL
67 );
68
69
70 private String lastPath = "";
71
72
73 private List<String> lastLines = new ArrayList<>();
74
75 @Override
76 public void execute(Sink sink, MacroRequest request) throws MacroExecutionException {
77 final String path = (String) request.getParameter("path");
78 final String type = (String) request.getParameter("type");
79
80 List<String> lines = lastLines;
81 if (!path.equals(lastPath)) {
82 lines = readFile("src/xdocs-examples/" + path);
83 lastPath = path;
84 lastLines = lines;
85 }
86
87 if ("config".equals(type)) {
88 final String config = getConfigSnippet(lines);
89
90 if (config.isBlank()) {
91 final String message = String.format(Locale.ROOT,
92 "Empty config snippet from %s, check"
93 + " for xml config snippet delimiters in input file.", path
94 );
95 throw new MacroExecutionException(message);
96 }
97
98 writeSnippet(sink, config);
99 }
100 else if ("code".equals(type)) {
101 String code = getCodeSnippet(lines);
102
103 if (path.contains("filetabcharacter")) {
104 code = code.replace("\t", " ");
105 }
106
107 if (code.isBlank()) {
108 final String message = String.format(Locale.ROOT,
109 "Empty code snippet from %s, check"
110 + " for code snippet delimiters in input file.", path
111 );
112 throw new MacroExecutionException(message);
113 }
114
115 writeSnippet(sink, code);
116 }
117 else if ("raw".equals(type)) {
118 final String content = String.join(NEWLINE, lines);
119 writeSnippet(sink, content);
120 }
121 else {
122 final String message = String.format(Locale.ROOT, "Unknown example type: %s", type);
123 throw new MacroExecutionException(message);
124 }
125 }
126
127
128
129
130
131
132
133
134 private static List<String> readFile(String path) throws MacroExecutionException {
135 try {
136 final Path exampleFilePath = Path.of(path);
137 return Files.readAllLines(exampleFilePath);
138 }
139 catch (IOException ioException) {
140 final String message = String.format(Locale.ROOT, "Failed to read %s", path);
141 throw new MacroExecutionException(message, ioException);
142 }
143 }
144
145
146
147
148
149
150
151
152
153 private static String getConfigSnippet(Collection<String> lines) {
154 return lines.stream()
155 .dropWhile(line -> !XML_CONFIG_START.equals(line))
156 .skip(1)
157 .takeWhile(line -> !XML_CONFIG_END.equals(line))
158 .collect(Collectors.joining(NEWLINE));
159 }
160
161
162
163
164
165
166
167
168 private static String getCodeSnippet(Collection<String> lines) {
169 return lines.stream()
170 .dropWhile(line -> !line.contains(CODE_SNIPPET_START))
171 .skip(1)
172 .takeWhile(line -> !line.contains(CODE_SNIPPET_END))
173 .collect(Collectors.joining(NEWLINE));
174 }
175
176
177
178
179
180
181
182 private static void writeSnippet(Sink sink, String snippet) {
183 sink.rawText("<div class=\"wrapper\">");
184 final boolean isXml = isXml(snippet);
185
186 final String languageClass;
187 if (isXml) {
188 languageClass = "language-xml";
189 }
190 else {
191 languageClass = "language-java";
192 }
193 sink.rawText("<pre class=\"prettyprint\"><code class=\"" + languageClass + "\">" + NEWLINE);
194 sink.rawText(escapeHtml(snippet).trim() + NEWLINE);
195 sink.rawText("</code></pre>");
196 sink.rawText("</div>");
197 }
198
199
200
201
202
203
204
205 private static String escapeHtml(String snippet) {
206 return snippet.replace("&", "&")
207 .replace("<", "<")
208 .replace(">", ">");
209 }
210
211
212
213
214
215
216
217 private static boolean isXml(String snippet) {
218 return XML_PATTERN.matcher(snippet.trim()).matches();
219 }
220 }