|
4 | 4 |
|
5 | 5 | import java.io.BufferedReader;
|
6 | 6 | import java.io.BufferedWriter;
|
| 7 | +import java.io.ByteArrayInputStream; |
7 | 8 | import java.io.ByteArrayOutputStream;
|
8 | 9 | import java.io.IOException;
|
9 | 10 | import java.io.InputStream;
|
|
12 | 13 | import java.io.OutputStreamWriter;
|
13 | 14 | import java.io.Writer;
|
14 | 15 | import java.nio.charset.StandardCharsets;
|
| 16 | +import java.util.function.Supplier; |
15 | 17 |
|
16 | 18 | import static java.nio.charset.StandardCharsets.UTF_8;
|
17 | 19 | import static java.util.Objects.requireNonNull;
|
|
20 | 22 | * Writes the message output of a test run as single page html report.
|
21 | 23 | */
|
22 | 24 | public final class MessagesToHtmlWriter implements AutoCloseable {
|
23 |
| - private final String template; |
24 |
| - private final Writer writer; |
| 25 | + private final OutputStreamWriter writer; |
25 | 26 | private final JsonInHtmlWriter jsonInHtmlWriter;
|
26 | 27 | private final Serializer serializer;
|
| 28 | + |
| 29 | + private final String template; |
| 30 | + private final Supplier<InputStream> title; |
| 31 | + private final Supplier<InputStream> icon; |
| 32 | + private final Supplier<InputStream> css; |
| 33 | + private final Supplier<InputStream> customCss; |
| 34 | + private final Supplier<InputStream> script; |
| 35 | + private final Supplier<InputStream> customScript; |
| 36 | + |
27 | 37 | private boolean preMessageWritten = false;
|
28 | 38 | private boolean postMessageWritten = false;
|
29 | 39 | private boolean firstMessageWritten = false;
|
30 | 40 | private boolean streamClosed = false;
|
31 | 41 |
|
| 42 | + @Deprecated |
32 | 43 | public MessagesToHtmlWriter(OutputStream outputStream, Serializer serializer) throws IOException {
|
33 | 44 | this(
|
34 |
| - new OutputStreamWriter( |
35 |
| - requireNonNull(outputStream), |
36 |
| - StandardCharsets.UTF_8), |
37 |
| - requireNonNull(serializer) |
| 45 | + createWriter(outputStream), |
| 46 | + requireNonNull(serializer), |
| 47 | + () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)), |
| 48 | + () -> getResource("icon.url"), |
| 49 | + () -> getResource("main.css"), |
| 50 | + MessagesToHtmlWriter::getEmptyResource, |
| 51 | + () -> getResource("main.js"), |
| 52 | + MessagesToHtmlWriter::getEmptyResource |
38 | 53 | );
|
39 | 54 | }
|
40 | 55 |
|
41 |
| - |
42 |
| - private MessagesToHtmlWriter(Writer writer, Serializer serializer) throws IOException { |
| 56 | + private MessagesToHtmlWriter( |
| 57 | + OutputStreamWriter writer, |
| 58 | + Serializer serializer, |
| 59 | + Supplier<InputStream> title, |
| 60 | + Supplier<InputStream> icon, |
| 61 | + Supplier<InputStream> css, |
| 62 | + Supplier<InputStream> customCss, |
| 63 | + Supplier<InputStream> script, |
| 64 | + Supplier<InputStream> customScript |
| 65 | + ) { |
43 | 66 | this.writer = writer;
|
44 | 67 | this.jsonInHtmlWriter = new JsonInHtmlWriter(writer);
|
45 | 68 | this.serializer = serializer;
|
46 |
| - this.template = readResource("index.mustache.html"); |
| 69 | + this.template = readTemplate(); |
| 70 | + this.title = title; |
| 71 | + this.icon = icon; |
| 72 | + this.css = css; |
| 73 | + this.customCss = customCss; |
| 74 | + this.customScript = customScript; |
| 75 | + this.script = script; |
| 76 | + } |
| 77 | + |
| 78 | + private static String readTemplate() { |
| 79 | + try { |
| 80 | + return readResource("index.mustache.html"); |
| 81 | + } catch (IOException e) { |
| 82 | + throw new RuntimeException("Could not read resource index.mustache.html", e); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + private static OutputStreamWriter createWriter(OutputStream outputStream) { |
| 87 | + return new OutputStreamWriter( |
| 88 | + requireNonNull(outputStream), |
| 89 | + StandardCharsets.UTF_8); |
| 90 | + } |
| 91 | + |
| 92 | + /** |
| 93 | + * Creates a builder to construct this writer. |
| 94 | + * |
| 95 | + * @param serializer used to convert messages into json. |
| 96 | + * @return a new builder |
| 97 | + */ |
| 98 | + public static Builder builder(Serializer serializer) { |
| 99 | + return new Builder(serializer); |
| 100 | + } |
| 101 | + |
| 102 | + private static ByteArrayInputStream getEmptyResource() { |
| 103 | + return new ByteArrayInputStream(new byte[0]); |
| 104 | + } |
| 105 | + |
| 106 | + private static void writeTemplateBetween(Writer writer, String template, String begin, String end) |
| 107 | + throws IOException { |
| 108 | + int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); |
| 109 | + int endIndex = end == null ? template.length() : template.indexOf(end); |
| 110 | + writer.write(template.substring(beginIndex, endIndex)); |
| 111 | + } |
| 112 | + |
| 113 | + private static void writeResource(Writer writer, Supplier<InputStream> resource) throws IOException { |
| 114 | + writeResource(writer, resource.get()); |
| 115 | + } |
| 116 | + |
| 117 | + private static void writeResource(Writer writer, InputStream resource) throws IOException { |
| 118 | + BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); |
| 119 | + char[] buffer = new char[1024]; |
| 120 | + for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { |
| 121 | + writer.write(buffer, 0, read); |
| 122 | + } |
| 123 | + } |
| 124 | + |
| 125 | + private static InputStream getResource(String name) { |
| 126 | + InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); |
| 127 | + requireNonNull(resource, name + " could not be loaded"); |
| 128 | + return resource; |
| 129 | + } |
| 130 | + |
| 131 | + private static String readResource(String name) throws IOException { |
| 132 | + ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| 133 | + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { |
| 134 | + InputStream resource = getResource(name); |
| 135 | + writeResource(writer, resource); |
| 136 | + } |
| 137 | + return new String(baos.toByteArray(), UTF_8); |
47 | 138 | }
|
48 | 139 |
|
49 | 140 | private void writePreMessage() throws IOException {
|
50 |
| - writeTemplateBetween(writer, template, null, "{{css}}"); |
51 |
| - writeResource(writer, "main.css"); |
52 |
| - writeTemplateBetween(writer, template, "{{css}}", "{{messages}}"); |
| 141 | + writeTemplateBetween(writer, template, null, "{{title}}"); |
| 142 | + writeResource(writer, title); |
| 143 | + writeTemplateBetween(writer, template, "{{title}}", "{{icon}}"); |
| 144 | + writeResource(writer, icon); |
| 145 | + writeTemplateBetween(writer, template, "{{icon}}", "{{css}}"); |
| 146 | + writeResource(writer, css); |
| 147 | + writeTemplateBetween(writer, template, "{{css}}", "{{customCss}}"); |
| 148 | + writeResource(writer, customCss); |
| 149 | + writeTemplateBetween(writer, template, "{{customCss}}", "{{messages}}"); |
53 | 150 | }
|
54 | 151 |
|
55 | 152 | private void writePostMessage() throws IOException {
|
56 | 153 | writeTemplateBetween(writer, template, "{{messages}}", "{{script}}");
|
57 |
| - writeResource(writer, "main.js"); |
58 |
| - writeTemplateBetween(writer, template, "{{script}}", null); |
| 154 | + writeResource(writer, script); |
| 155 | + writeTemplateBetween(writer, template, "{{script}}", "{{customScript}}"); |
| 156 | + writeResource(writer, customScript); |
| 157 | + writeTemplateBetween(writer, template, "{{customScript}}", null); |
59 | 158 | }
|
60 | 159 |
|
61 | 160 | /**
|
@@ -113,31 +212,6 @@ public void close() throws IOException {
|
113 | 212 | }
|
114 | 213 | }
|
115 | 214 |
|
116 |
| - private static void writeTemplateBetween(Writer writer, String template, String begin, String end) |
117 |
| - throws IOException { |
118 |
| - int beginIndex = begin == null ? 0 : template.indexOf(begin) + begin.length(); |
119 |
| - int endIndex = end == null ? template.length() : template.indexOf(end); |
120 |
| - writer.write(template.substring(beginIndex, endIndex)); |
121 |
| - } |
122 |
| - |
123 |
| - private static void writeResource(Writer writer, String name) throws IOException { |
124 |
| - InputStream resource = MessagesToHtmlWriter.class.getResourceAsStream(name); |
125 |
| - requireNonNull(resource, name + " could not be loaded"); |
126 |
| - BufferedReader reader = new BufferedReader(new InputStreamReader(resource, UTF_8)); |
127 |
| - char[] buffer = new char[1024]; |
128 |
| - for (int read = reader.read(buffer); read != -1; read = reader.read(buffer)) { |
129 |
| - writer.write(buffer, 0, read); |
130 |
| - } |
131 |
| - } |
132 |
| - |
133 |
| - private static String readResource(String name) throws IOException { |
134 |
| - ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
135 |
| - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos, UTF_8))) { |
136 |
| - writeResource(writer, name); |
137 |
| - } |
138 |
| - return new String(baos.toByteArray(), UTF_8); |
139 |
| - } |
140 |
| - |
141 | 215 | /**
|
142 | 216 | * Serializes a message to JSON.
|
143 | 217 | */
|
@@ -165,4 +239,106 @@ public interface Serializer {
|
165 | 239 |
|
166 | 240 | }
|
167 | 241 |
|
| 242 | + public static final class Builder { |
| 243 | + private final Serializer serializer; |
| 244 | + private Supplier<InputStream> title = () -> new ByteArrayInputStream("Cucumber".getBytes(UTF_8)); |
| 245 | + private Supplier<InputStream> icon = () -> getResource("icon.url"); |
| 246 | + private Supplier<InputStream> css = () -> getResource("main.css"); |
| 247 | + private Supplier<InputStream> customCss = MessagesToHtmlWriter::getEmptyResource; |
| 248 | + private Supplier<InputStream> script = () -> getResource("main.js"); |
| 249 | + private Supplier<InputStream> customScript = MessagesToHtmlWriter::getEmptyResource; |
| 250 | + |
| 251 | + private Builder(Serializer serializer) { |
| 252 | + this.serializer = requireNonNull(serializer); |
| 253 | + } |
| 254 | + |
| 255 | + /** |
| 256 | + * Sets a custom title for the report, default value "Cucumber". |
| 257 | + * |
| 258 | + * @param title the custom title. |
| 259 | + * @return this builder |
| 260 | + */ |
| 261 | + public Builder title(String title) { |
| 262 | + requireNonNull(title); |
| 263 | + this.title = () -> new ByteArrayInputStream(title.getBytes(UTF_8)); |
| 264 | + return this; |
| 265 | + } |
| 266 | + |
| 267 | + /** |
| 268 | + * Sets a custom icon for the report, default value the cucumber logo. |
| 269 | + * <p> |
| 270 | + * The {@code icon} is any valid {@code href} value. |
| 271 | + * |
| 272 | + * @param icon the custom icon. |
| 273 | + * @return this builder |
| 274 | + */ |
| 275 | + public Builder icon(Supplier<InputStream> icon) { |
| 276 | + this.icon = requireNonNull(icon); |
| 277 | + return this; |
| 278 | + } |
| 279 | + |
| 280 | + /** |
| 281 | + * Sets default css for the report. |
| 282 | + * <p> |
| 283 | + * The default script styles the cucumber report. |
| 284 | + * |
| 285 | + * @param css the custom css. |
| 286 | + * @return this builder |
| 287 | + */ |
| 288 | + public Builder css(Supplier<InputStream> css) { |
| 289 | + this.css = requireNonNull(css); |
| 290 | + return this; |
| 291 | + } |
| 292 | + |
| 293 | + /** |
| 294 | + * Sets custom css for the report. |
| 295 | + * <p> |
| 296 | + * The custom css is applied after the default css. |
| 297 | + * |
| 298 | + * @param customCss the custom css. |
| 299 | + * @return this builder |
| 300 | + */ |
| 301 | + public Builder customCss(Supplier<InputStream> customCss) { |
| 302 | + this.customCss = requireNonNull(customCss); |
| 303 | + return this; |
| 304 | + } |
| 305 | + |
| 306 | + /** |
| 307 | + * Replaces default script for the report. |
| 308 | + * <p> |
| 309 | + * The default script renders the cucumber messages into a report. |
| 310 | + * |
| 311 | + * @param script the custom script. |
| 312 | + * @return this builder |
| 313 | + */ |
| 314 | + public Builder script(Supplier<InputStream> script) { |
| 315 | + this.script = requireNonNull(script); |
| 316 | + return this; |
| 317 | + } |
| 318 | + |
| 319 | + /** |
| 320 | + * Sets custom script for the report. |
| 321 | + * <p> |
| 322 | + * The custom script is applied after the default script. |
| 323 | + * |
| 324 | + * @param customScript the custom script. |
| 325 | + * @return this builder |
| 326 | + */ |
| 327 | + public Builder customScript(Supplier<InputStream> customScript) { |
| 328 | + this.customScript = requireNonNull(customScript); |
| 329 | + return this; |
| 330 | + } |
| 331 | + |
| 332 | + |
| 333 | + /** |
| 334 | + * Create an instance of the messages to html writer. |
| 335 | + * |
| 336 | + * @param out the output stream to write to |
| 337 | + * @return a new instance of the messages to html writer. |
| 338 | + */ |
| 339 | + public MessagesToHtmlWriter build(OutputStream out) { |
| 340 | + return new MessagesToHtmlWriter(createWriter(out), serializer, title, icon, css, customCss, script, customScript); |
| 341 | + } |
| 342 | + } |
| 343 | + |
168 | 344 | }
|
0 commit comments