|
1 | 1 | package io.cucumber.core.plugin;
|
2 | 2 |
|
3 | 3 | import io.cucumber.messages.types.Envelope;
|
4 |
| -import io.cucumber.messages.types.TestRunFinished; |
5 |
| -import io.cucumber.messages.types.TestStepFinished; |
6 |
| -import io.cucumber.messages.types.TestStepResultStatus; |
7 | 4 | import io.cucumber.plugin.ColorAware;
|
8 | 5 | import io.cucumber.plugin.ConcurrentEventListener;
|
9 | 6 | import io.cucumber.plugin.event.EventPublisher;
|
| 7 | +import io.cucumber.prettyformatter.MessagesToProgressWriter; |
10 | 8 |
|
| 9 | +import java.io.IOException; |
11 | 10 | import java.io.OutputStream;
|
12 |
| -import java.io.OutputStreamWriter; |
13 |
| -import java.io.PrintWriter; |
14 |
| -import java.nio.charset.StandardCharsets; |
15 |
| -import java.util.EnumMap; |
16 |
| -import java.util.Map; |
17 | 11 |
|
18 |
| -import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_CYAN; |
19 |
| -import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_DEFAULT; |
20 |
| -import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_GREEN; |
21 |
| -import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_RED; |
22 |
| -import static io.cucumber.core.plugin.ProgressFormatter.Ansi.Attributes.FOREGROUND_YELLOW; |
23 |
| -import static io.cucumber.messages.types.TestStepResultStatus.AMBIGUOUS; |
24 |
| -import static io.cucumber.messages.types.TestStepResultStatus.FAILED; |
25 |
| -import static io.cucumber.messages.types.TestStepResultStatus.PASSED; |
26 |
| -import static io.cucumber.messages.types.TestStepResultStatus.PENDING; |
27 |
| -import static io.cucumber.messages.types.TestStepResultStatus.SKIPPED; |
28 |
| -import static io.cucumber.messages.types.TestStepResultStatus.UNDEFINED; |
29 |
| -import static java.lang.System.lineSeparator; |
30 |
| -import static java.util.Objects.requireNonNull; |
| 12 | +import static io.cucumber.prettyformatter.Theme.cucumber; |
| 13 | +import static io.cucumber.prettyformatter.Theme.plain; |
31 | 14 |
|
32 | 15 | /**
|
33 | 16 | * Renders a rudimentary progress bar.
|
|
37 | 20 | */
|
38 | 21 | public final class ProgressFormatter implements ConcurrentEventListener, ColorAware {
|
39 | 22 |
|
40 |
| - private static final int MAX_WIDTH = 80; |
41 |
| - private static final Map<TestStepResultStatus, String> SYMBOLS = new EnumMap<>(TestStepResultStatus.class); |
42 |
| - private static final Map<TestStepResultStatus, Ansi> ESCAPES = new EnumMap<>(TestStepResultStatus.class); |
43 |
| - private static final Ansi RESET = Ansi.with(FOREGROUND_DEFAULT); |
44 |
| - static { |
45 |
| - SYMBOLS.put(PASSED, "."); |
46 |
| - SYMBOLS.put(UNDEFINED, "U"); |
47 |
| - SYMBOLS.put(PENDING, "P"); |
48 |
| - SYMBOLS.put(SKIPPED, "-"); |
49 |
| - SYMBOLS.put(FAILED, "F"); |
50 |
| - SYMBOLS.put(AMBIGUOUS, "A"); |
51 |
| - |
52 |
| - ESCAPES.put(PASSED, Ansi.with(FOREGROUND_GREEN)); |
53 |
| - ESCAPES.put(UNDEFINED, Ansi.with(FOREGROUND_YELLOW)); |
54 |
| - ESCAPES.put(PENDING, Ansi.with(FOREGROUND_YELLOW)); |
55 |
| - ESCAPES.put(SKIPPED, Ansi.with(FOREGROUND_CYAN)); |
56 |
| - ESCAPES.put(FAILED, Ansi.with(FOREGROUND_RED)); |
57 |
| - ESCAPES.put(AMBIGUOUS, Ansi.with(FOREGROUND_RED)); |
58 |
| - } |
59 |
| - |
60 |
| - private final PrintWriter writer; |
61 |
| - private boolean monochrome = false; |
62 |
| - private int width = 0; |
| 23 | + private final OutputStream out; |
| 24 | + private MessagesToProgressWriter writer; |
63 | 25 |
|
64 | 26 | public ProgressFormatter(OutputStream out) {
|
65 |
| - this.writer = createPrintWriter(out); |
| 27 | + this.out = out; |
| 28 | + this.writer = createBuilder().build(out); |
66 | 29 | }
|
67 | 30 |
|
68 |
| - private static PrintWriter createPrintWriter(OutputStream out) { |
69 |
| - return new PrintWriter( |
70 |
| - new OutputStreamWriter( |
71 |
| - requireNonNull(out), |
72 |
| - StandardCharsets.UTF_8)); |
| 31 | + private static MessagesToProgressWriter.Builder createBuilder() { |
| 32 | + return MessagesToProgressWriter.builder() |
| 33 | + .theme(cucumber()); |
73 | 34 | }
|
74 | 35 |
|
75 | 36 | @Override
|
76 | 37 | public void setMonochrome(boolean monochrome) {
|
77 |
| - this.monochrome = monochrome; |
| 38 | + if (monochrome) { |
| 39 | + writer = createBuilder().theme(plain()).build(out); |
| 40 | + } |
78 | 41 | }
|
79 | 42 |
|
80 | 43 | @Override
|
81 | 44 | public void setEventPublisher(EventPublisher publisher) {
|
82 |
| - publisher.registerHandlerFor(Envelope.class, event -> { |
83 |
| - event.getTestStepFinished().ifPresent(this::handleTestStepFinished); |
84 |
| - event.getTestRunFinished().ifPresent(this::handleTestRunFinished); |
85 |
| - }); |
| 45 | + publisher.registerHandlerFor(Envelope.class, this::write); |
86 | 46 | }
|
87 | 47 |
|
88 |
| - private void handleTestStepFinished(TestStepFinished event) { |
89 |
| - TestStepResultStatus status = event.getTestStepResult().getStatus(); |
90 |
| - // Prevent tearing in output when multiple threads write to System.out |
91 |
| - StringBuilder buffer = new StringBuilder(); |
92 |
| - if (!monochrome) { |
93 |
| - buffer.append(ESCAPES.get(status)); |
94 |
| - } |
95 |
| - buffer.append(SYMBOLS.get(status)); |
96 |
| - if (!monochrome) { |
97 |
| - buffer.append(RESET); |
98 |
| - } |
99 |
| - // Start a new line if at the end of this one |
100 |
| - if (++width % MAX_WIDTH == 0) { |
101 |
| - width = 0; |
102 |
| - buffer.append(lineSeparator()); |
103 |
| - } |
104 |
| - writer.append(buffer); |
105 |
| - // Flush to provide immediate feedback. |
106 |
| - writer.flush(); |
107 |
| - } |
108 |
| - |
109 |
| - private void handleTestRunFinished(TestRunFinished testRunFinished) { |
110 |
| - writer.println(); |
111 |
| - writer.close(); |
112 |
| - } |
113 |
| - |
114 |
| - /** |
115 |
| - * Represents an |
116 |
| - * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code">ANSI escape |
117 |
| - * code</a> in the format {@code CSI n m}. |
118 |
| - */ |
119 |
| - static final class Ansi { |
120 |
| - |
121 |
| - private static final char FIRST_ESCAPE = 27; |
122 |
| - private static final char SECOND_ESCAPE = '['; |
123 |
| - private static final String END_SEQUENCE = "m"; |
124 |
| - private final String controlSequence; |
125 |
| - |
126 |
| - /** |
127 |
| - * Constructs an ANSI escape code with the given attributes. |
128 |
| - * |
129 |
| - * @param attributes to include. |
130 |
| - * @return an ANSI escape code with the given attributes |
131 |
| - */ |
132 |
| - public static Ansi with(Ansi.Attributes... attributes) { |
133 |
| - return new Ansi(requireNonNull(attributes)); |
| 48 | + private void write(Envelope event) { |
| 49 | + try { |
| 50 | + writer.write(event); |
| 51 | + } catch (IOException e) { |
| 52 | + throw new IllegalStateException(e); |
134 | 53 | }
|
135 | 54 |
|
136 |
| - private Ansi(Ansi.Attributes... attributes) { |
137 |
| - this.controlSequence = createControlSequence(attributes); |
138 |
| - } |
139 |
| - |
140 |
| - private String createControlSequence(Ansi.Attributes... attributes) { |
141 |
| - StringBuilder a = new StringBuilder(attributes.length * 5); |
142 |
| - |
143 |
| - for (Ansi.Attributes attribute : attributes) { |
144 |
| - a.append(FIRST_ESCAPE).append(SECOND_ESCAPE); |
145 |
| - a.append(attribute.value); |
146 |
| - a.append(END_SEQUENCE); |
147 |
| - } |
148 |
| - |
149 |
| - return a.toString(); |
150 |
| - } |
151 |
| - |
152 |
| - @Override |
153 |
| - public String toString() { |
154 |
| - return controlSequence; |
155 |
| - } |
156 |
| - |
157 |
| - /** |
158 |
| - * A select number of attributes from all the available <a |
159 |
| - * href=https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters>Select |
160 |
| - * Graphic Rendition attributes</a>. |
161 |
| - */ |
162 |
| - enum Attributes { |
163 |
| - |
164 |
| - // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors |
165 |
| - FOREGROUND_RED(31), |
166 |
| - FOREGROUND_GREEN(32), |
167 |
| - FOREGROUND_YELLOW(33), |
168 |
| - FOREGROUND_CYAN(36), |
169 |
| - FOREGROUND_DEFAULT(39); |
170 |
| - |
171 |
| - private final int value; |
172 |
| - |
173 |
| - Attributes(int index) { |
174 |
| - this.value = index; |
175 |
| - } |
| 55 | + // TODO: Plugins should implement the closable interface |
| 56 | + // and be closed by Cucumber |
| 57 | + if (event.getTestRunFinished().isPresent()) { |
| 58 | + writer.close(); |
176 | 59 | }
|
177 | 60 | }
|
178 |
| - |
179 | 61 | }
|
0 commit comments