|
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