diff --git a/config/spotbugs/filter.xml b/config/spotbugs/filter.xml index 746e037d118..24d313006d0 100644 --- a/config/spotbugs/filter.xml +++ b/config/spotbugs/filter.xml @@ -154,6 +154,10 @@ + + + + diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/DiffCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/DiffCommandTest.java new file mode 100644 index 00000000000..15106bf0997 --- /dev/null +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/DiffCommandTest.java @@ -0,0 +1,98 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.cli; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.utils.ListUtils; + +public class DiffCommandTest { + @Test + public void passingDiffEventsExitZero() { + IntegUtils.withTempDir("diff", dir -> { + Path a = dir.resolve("a.smithy"); + writeFile(a, "$version: \"2.0\"\nnamespace example\nstring A\n"); + + RunResult result = IntegUtils.run(dir, ListUtils.of("diff", "--old", a.toString(), "--new", a.toString())); + assertThat(result.getExitCode(), equalTo(0)); + }); + } + + @Test + public void showsLabelForOldModelEvents() { + IntegUtils.withTempDir("diff", dir -> { + Path a = dir.resolve("a.smithy"); + writeFile(a, "$version: \"2.0\"\nnamespace example\n@aaaaaa\nstring A\n"); + + RunResult result = IntegUtils.run(dir, ListUtils.of("diff", "--old", a.toString(), "--new", a.toString())); + assertThat(result.getExitCode(), equalTo(1)); + assertThat(result.getOutput(), containsString("── OLD ERROR ──")); + }); + } + + @Test + public void showsLabelForNewModelEvents() { + IntegUtils.withTempDir("diff", dir -> { + Path a = dir.resolve("a.smithy"); + writeFile(a, "$version: \"2.0\"\nnamespace example\nstring A\n"); + + Path b = dir.resolve("b.smithy"); + writeFile(b, "$version: \"2.0\"\nnamespace example\n@aaaaaa\nstring A\n"); + + RunResult result = IntegUtils.run(dir, ListUtils.of("diff", "--old", a.toString(), "--new", b.toString())); + assertThat(result.getExitCode(), equalTo(1)); + assertThat(result.getOutput(), containsString("── NEW ERROR ──")); + }); + } + + @Test + public void showsLabelForDiffEvents() { + IntegUtils.withTempDir("diff", dir -> { + Path a = dir.resolve("a.smithy"); + writeFile(a, "$version: \"2.0\"\nnamespace example\nstring A\n"); + + Path b = dir.resolve("b.smithy"); + writeFile(b, "$version: \"2.0\"\nnamespace example\nstring A\nstring B\n"); // Added B. + + RunResult result = IntegUtils.run(dir, ListUtils.of( + "diff", + "--old", a.toString(), + "--new", b.toString(), + "--severity", "NOTE")); // Note that this is required since the default severity is WARNING. + assertThat(result.getExitCode(), equalTo(0)); + assertThat(result.getOutput(), containsString("── DIFF NOTE ──")); + }); + } + + private void writeFile(Path path, String contents) { + try { + FileWriter fileWriter = new FileWriter(path.toString()); + PrintWriter printWriter = new PrintWriter(fileWriter); + printWriter.print(contents); + printWriter.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java index c68eb4ead78..b37b73b329e 100644 --- a/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/IntegUtils.java @@ -91,7 +91,7 @@ private static List createSmithyCommand(List args) { throw new RuntimeException("No SMITHY_BINARY location was set. Did you build the Smithy jlink CLI?"); } - private static void withTempDir(String name, Consumer consumer) { + static void withTempDir(String name, Consumer consumer) { try { Path path = Files.createTempDirectory(name.replace("/", "_")); try { diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java index 111086cdd45..99113319a0c 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Cli.java @@ -81,6 +81,8 @@ public int run(String[] args) { Command.Env env = new Command.Env(colorFormatter, stdoutPrinter, stderrPrinter, classLoader); return command.execute(arguments, env); } catch (Exception e) { + stdoutPrinter.flush(); + stderrPrinter.flush(); printException(e, standardOptions.stackTrace()); throw CliError.wrap(e); } finally { @@ -112,13 +114,13 @@ public void stderr(CliPrinter stderrPrinter) { private void printException(Throwable e, boolean stacktrace) { try (ColorBuffer buffer = ColorBuffer.of(colorFormatter, stderrPrinter)) { if (!stacktrace) { - colorFormatter.println(stderrPrinter, e.getMessage(), Style.RED); + colorFormatter.println(stderrPrinter, e.getMessage(), ColorTheme.ERROR); } else { StringWriter writer = new StringWriter(); e.printStackTrace(new PrintWriter(writer)); String result = writer.toString(); int positionOfName = result.indexOf(':'); - buffer.print(result.substring(0, positionOfName), Style.RED, Style.UNDERLINE); + buffer.print(result.substring(0, positionOfName), ColorTheme.ERROR); buffer.println(result.substring(positionOfName)); } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/CliPrinter.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/CliPrinter.java index 35c49c7e8f3..185fcdd4670 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/CliPrinter.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/CliPrinter.java @@ -26,7 +26,6 @@ /** * Handles text output of the CLI. */ -@FunctionalInterface public interface CliPrinter extends Appendable, Flushable { @Override @@ -38,12 +37,7 @@ default CliPrinter append(CharSequence csq) { } @Override - default CliPrinter append(CharSequence csq, int start, int end) { - for (int i = start; i < end; i++) { - append(csq.charAt(i)); - } - return this; - } + CliPrinter append(CharSequence csq, int start, int end); /** * Prints text to the writer and appends a new line. diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorBuffer.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorBuffer.java index 7cd0a9a06f2..587a2adb822 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorBuffer.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorBuffer.java @@ -64,9 +64,7 @@ public static ColorBuffer of(ColorFormatter colors, Appendable sink) { */ public static ColorBuffer of(ColorFormatter colors, CliPrinter sink) { StringBuilder buffer = new StringBuilder(); - return new ColorBuffer(colors, buffer, s -> { - sink.append(s.toString()); - }); + return new ColorBuffer(colors, buffer, s -> sink.append(s.toString())); } @Override diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java new file mode 100644 index 00000000000..0e9e6e33f32 --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java @@ -0,0 +1,51 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.cli; + +/** + * Standardizes on colors across commands. + */ +public final class ColorTheme { + + public static final Style EM_UNDERLINE = Style.of(Style.BRIGHT_WHITE, Style.UNDERLINE); + public static final Style DEPRECATED = Style.of(Style.BG_YELLOW, Style.BLACK); + + public static final Style MUTED = Style.of(Style.BRIGHT_BLACK); + public static final Style EVENT_SHAPE_ID = Style.of(Style.BRIGHT_MAGENTA); + public static final Style LITERAL = Style.of(Style.CYAN); + + public static final Style ERROR_TITLE = Style.of(Style.BG_RED, Style.BLACK); + public static final Style ERROR = Style.of(Style.RED); + + public static final Style DANGER_TITLE = Style.of(Style.BG_MAGENTA, Style.BLACK); + public static final Style DANGER = Style.of(Style.MAGENTA); + + public static final Style WARNING_TITLE = Style.of(Style.BG_YELLOW, Style.BLACK); + public static final Style WARNING = Style.of(Style.YELLOW); + + public static final Style NOTE_TITLE = Style.of(Style.BG_CYAN, Style.BLACK); + public static final Style NOTE = Style.of(Style.CYAN); + + public static final Style SUPPRESSED_TITLE = Style.of(Style.BG_GREEN, Style.BLACK); + public static final Style SUPPRESSED = Style.of(Style.GREEN); + + public static final Style SUCCESS = Style.of(Style.GREEN); + + public static final Style DIFF_TITLE = Style.of(Style.BG_BRIGHT_BLACK, Style.WHITE); + public static final Style DIFF_EVENT_TITLE = Style.of(Style.BG_BRIGHT_BLUE, Style.BLACK); + + private ColorTheme() {} +} diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Command.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Command.java index 81b5352db3c..1bd802ff826 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Command.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Command.java @@ -86,6 +86,11 @@ public ClassLoader classLoader() { return classLoader == null ? getClass().getClassLoader() : classLoader; } + public void flush() { + stderr.flush(); + stdout.flush(); + } + public Env withClassLoader(ClassLoader classLoader) { return classLoader == this.classLoader ? this : new Env(colors, stdout, stderr, classLoader); } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/HelpPrinter.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/HelpPrinter.java index c0a56e0ec17..52d6f188c28 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/HelpPrinter.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/HelpPrinter.java @@ -129,7 +129,7 @@ public void print(ColorFormatter colors, CliPrinter printer) { LineWrapper builder = new LineWrapper(maxWidth); builder.appendWithinLine("Usage: ") - .appendWithinLine(colors.style(name, Style.BRIGHT_WHITE, Style.UNDERLINE)) + .appendWithinLine(colors.style(name, ColorTheme.EM_UNDERLINE)) .space(); // Calculate the column manually to account for possible styles interfering with the current column number. @@ -168,13 +168,13 @@ public void print(ColorFormatter colors, CliPrinter printer) { private void writeArgHelp(ColorFormatter colors, LineWrapper builder, Arg arg) { if (arg.longName != null) { - builder.appendWithinLine(colors.style(arg.longName, Style.YELLOW)); + builder.appendWithinLine(colors.style(arg.longName, ColorTheme.LITERAL)); if (arg.shortName != null) { builder.appendWithinLine(", "); } } if (arg.shortName != null) { - builder.appendWithinLine(colors.style(arg.shortName, Style.YELLOW)); + builder.appendWithinLine(colors.style(arg.shortName, ColorTheme.LITERAL)); } if (arg.exampleValue != null) { builder.space().appendWithinLine(arg.exampleValue); @@ -213,13 +213,13 @@ String toShortArgs(ColorFormatter colors) { StringBuilder builder = new StringBuilder(); builder.append('['); if (longName != null) { - builder.append(colors.style(longName, Style.YELLOW)); + builder.append(colors.style(longName, ColorTheme.LITERAL)); if (shortName != null) { builder.append(" | "); } } if (shortName != null) { - builder.append(colors.style(shortName, Style.YELLOW)); + builder.append(colors.style(shortName, ColorTheme.LITERAL)); } if (exampleValue != null) { builder.append(' ').append(exampleValue); diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/LoggingArgumentsHandler.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/LoggingArgumentsHandler.java index dd9a537f7bb..9ee74077e35 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/LoggingArgumentsHandler.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/LoggingArgumentsHandler.java @@ -189,9 +189,9 @@ public void publish(LogRecord record) { if (isLoggable(record)) { String formatted = getFormatter().format(record); if (record.getLevel().equals(Level.SEVERE)) { - colors.println(printer, formatted, Style.RED); + colors.println(printer, formatted, ColorTheme.ERROR); } else if (record.getLevel().equals(Level.WARNING)) { - colors.println(printer, formatted, Style.YELLOW); + colors.println(printer, formatted, ColorTheme.WARNING); } else { printer.println(formatted); } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/Style.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/Style.java index b956ee7cfd8..321b4766af9 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/Style.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/Style.java @@ -15,6 +15,8 @@ package software.amazon.smithy.cli; +import java.util.StringJoiner; + /** * Colors and styles for use with {@link AnsiColorFormatter} or {@link CliPrinter}. * @@ -67,6 +69,14 @@ public interface Style { String getAnsiColorCode(); + static Style of(Style... styles) { + if (styles.length == 1) { + return styles[0]; + } else { + return new Multi(styles); + } + } + final class Constant implements Style { private final String ansiColorCode; @@ -74,8 +84,30 @@ public Constant(String ansiColorCode) { this.ansiColorCode = ansiColorCode; } + @Override public String getAnsiColorCode() { return ansiColorCode; } } + + final class Multi implements Style { + private final String ansiStyle; + + public Multi(Style... styles) { + if (styles.length == 1) { + ansiStyle = styles[0].getAnsiColorCode(); + } else { + StringJoiner joiner = new StringJoiner(";"); + for (Style style : styles) { + joiner.add(style.getAnsiColorCode()); + } + ansiStyle = joiner.toString(); + } + } + + @Override + public String getAnsiColorCode() { + return ansiStyle; + } + } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java index 38eb38dd402..d61a0f70418 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java @@ -37,6 +37,7 @@ import software.amazon.smithy.cli.CliPrinter; import software.amazon.smithy.cli.ColorBuffer; import software.amazon.smithy.cli.ColorFormatter; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.HelpPrinter; import software.amazon.smithy.cli.StandardOptions; @@ -44,6 +45,7 @@ import software.amazon.smithy.cli.dependencies.DependencyResolver; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.ModelAssembler; +import software.amazon.smithy.model.loader.sourcecontext.SourceContextLoader; import software.amazon.smithy.model.validation.Severity; final class BuildCommand implements Command { @@ -122,6 +124,11 @@ private int runWithClassLoader(SmithyBuildConfig config, Arguments arguments, En .validationPrinter(env.stderr()) .build(); + if (!standardOptions.quiet()) { + env.colors().println(env.stderr(), "Validated model, now starting projections...", ColorTheme.MUTED); + env.stderr().println(""); + } + Supplier modelAssemblerSupplier = () -> { ModelAssembler assembler = Model.assembler(classLoader); if (buildOptions.allowUnknownTraits()) { @@ -151,25 +158,30 @@ private int runWithClassLoader(SmithyBuildConfig config, Arguments arguments, En ResultConsumer resultConsumer = new ResultConsumer(env.colors(), env.stderr(), standardOptions.quiet()); smithyBuild.build(resultConsumer, resultConsumer); + env.flush(); + if (!standardOptions.quiet()) { - Style ansiColor = resultConsumer.failedProjections.isEmpty() - ? Style.BRIGHT_GREEN - : Style.BRIGHT_YELLOW; - env.colors().println(env.stderr(), - String.format("Smithy built %s projection(s), %s plugin(s), and %s artifacts", - resultConsumer.projectionCount, - resultConsumer.pluginCount, - resultConsumer.artifactCount), - Style.BOLD, ansiColor); + try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stderr())) { + buffer.print("Summary", ColorTheme.EM_UNDERLINE); + buffer.println(String.format(": Smithy built %s projection(s), %s plugin(s), and %s artifacts", + resultConsumer.projectionCount, + resultConsumer.pluginCount, + resultConsumer.artifactCount)); + } } // Throw an exception if any errors occurred. if (!resultConsumer.failedProjections.isEmpty()) { resultConsumer.failedProjections.sort(String::compareTo); - throw new CliError(String.format( - "The following %d Smithy build projection(s) failed: %s", - resultConsumer.failedProjections.size(), - resultConsumer.failedProjections)); + StringBuilder error = new StringBuilder(); + try (ColorBuffer buffer = ColorBuffer.of(env.colors(), error)) { + buffer.println(); + buffer.println(String.format( + "The following %d Smithy build projection(s) failed: %s", + resultConsumer.failedProjections.size(), + resultConsumer.failedProjections)); + } + throw new CliError(error.toString()); } return 0; @@ -196,45 +208,74 @@ public void accept(String name, Throwable exception) { StringWriter writer = new StringWriter(); writer.write(String.format("%nProjection %s failed: %s%n", name, exception.toString())); exception.printStackTrace(new PrintWriter(writer)); - colors.println(printer, writer.toString(), Style.RED); + colors.println(printer, writer.toString(), ColorTheme.ERROR); } @Override public void accept(ProjectionResult result) { try (ColorBuffer buffer = ColorBuffer.of(colors, printer)) { + String status; + Style statusStyle; + if (result.isBroken()) { - // Write out validation errors as they occur. failedProjections.add(result.getProjectionName()); - buffer - .println() - .print(result.getProjectionName(), Style.RED) - .println(" has a model that failed validation"); - result.getEvents().forEach(event -> { - if (event.getSeverity() == Severity.DANGER || event.getSeverity() == Severity.ERROR) { - buffer.println(event.toString(), Style.RED); - } - }); + statusStyle = ColorTheme.ERROR; + status = "Failed"; } else { // Only increment the projection count if it succeeded. projectionCount.incrementAndGet(); + statusStyle = ColorTheme.SUCCESS; + status = "Completed"; } pluginCount.addAndGet(result.getPluginManifests().size()); + // Increment the total number of artifacts written. + for (FileManifest manifest : result.getPluginManifests().values()) { + artifactCount.addAndGet(manifest.getFiles().size()); + } + // Get the base directory of the projection. Iterator manifestIterator = result.getPluginManifests().values().iterator(); Path root = manifestIterator.hasNext() ? manifestIterator.next().getBaseDir().getParent() : null; if (!quiet) { - String message = String.format("Completed projection %s (%d shapes): %s", - result.getProjectionName(), result.getModel().toSet().size(), root); - buffer.println(message, Style.GREEN); + int remainingLength = 80 - 6 - result.getProjectionName().length(); + buffer.style(w -> { + w.append("── "); + w.append(result.getProjectionName()); + w.append(" "); + for (int i = 0; i < remainingLength; i++) { + w.append('─'); + } + w.println(); + }, statusStyle); + buffer + .print(status) + .append(" projection ") + .append(result.getProjectionName()) + .append(" (") + .append(String.valueOf(result.getModel().toSet().size())) + .append("): ") + .append(String.valueOf(root)) + .println(); } - // Increment the total number of artifacts written. - for (FileManifest manifest : result.getPluginManifests().values()) { - artifactCount.addAndGet(manifest.getFiles().size()); + if (result.isBroken()) { + SourceContextLoader loader = SourceContextLoader.createModelAwareLoader(result.getModel(), 4); + PrettyAnsiValidationFormatter formatter = PrettyAnsiValidationFormatter.builder() + .sourceContextLoader(loader) + .colors(colors) + .titleLabel(result.getProjectionName(), statusStyle) + .build(); + result.getEvents().forEach(event -> { + if (event.getSeverity() == Severity.DANGER || event.getSeverity() == Severity.ERROR) { + buffer.println(formatter.format(event)); + } + }); } + + buffer.println(); } } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CodeFormatter.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CodeFormatter.java index e64b08f0f3c..d5dcd81ed77 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CodeFormatter.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/CodeFormatter.java @@ -18,7 +18,7 @@ import java.util.Collection; import java.util.Iterator; import software.amazon.smithy.cli.ColorBuffer; -import software.amazon.smithy.cli.Style; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.model.loader.sourcecontext.SourceContextLoader; /** @@ -81,7 +81,7 @@ private void writeColumnAndContent(int numberLength, int lineNumber, CharSequenc } writer.append("| "); } - }, Style.BRIGHT_BLACK); + }, ColorTheme.MUTED); if (content.length() > 0) { writeStringWithMaxWidth(content, numberLength); @@ -96,12 +96,12 @@ private void writePointer(int numberLength, int cursorColumn) { w.append(' '); } w.append("|"); - }, Style.BRIGHT_BLACK); + }, ColorTheme.MUTED); for (int j = 0; j < cursorColumn; j++) { writer.append(' '); } - writer.print("^", Style.RED); + writer.print("^", ColorTheme.ERROR); writer.println(); } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java index 26d341b3fcf..00d15f01ecd 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/DiffCommand.java @@ -19,20 +19,16 @@ import java.util.List; import java.util.function.Consumer; import java.util.logging.Logger; -import java.util.stream.Collectors; import software.amazon.smithy.build.model.SmithyBuildConfig; import software.amazon.smithy.cli.ArgumentReceiver; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.CliError; -import software.amazon.smithy.cli.ColorBuffer; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.HelpPrinter; -import software.amazon.smithy.cli.StandardOptions; -import software.amazon.smithy.cli.Style; import software.amazon.smithy.cli.dependencies.DependencyResolver; import software.amazon.smithy.diff.ModelDiff; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidatedResult; import software.amazon.smithy.model.validation.ValidationEvent; @@ -111,7 +107,6 @@ int runWithClassLoader(SmithyBuildConfig config, Arguments arguments, Env env) { throw new CliError("Unexpected arguments: " + arguments.getPositional()); } - StandardOptions standardOptions = arguments.getReceiver(StandardOptions.class); Options options = arguments.getReceiver(Options.class); ClassLoader classLoader = env.classLoader(); @@ -119,50 +114,30 @@ int runWithClassLoader(SmithyBuildConfig config, Arguments arguments, Env env) { List newModels = options.newModels; LOGGER.fine(() -> String.format("Setting old models to: %s; new models to: %s", oldModels, newModels)); - ModelAssembler assembler = ModelBuilder.createModelAssembler(classLoader); - Model oldModel = loadModel("old", assembler, oldModels); - assembler.reset(); - Model newModel = loadModel("new", assembler, newModels); - + ModelBuilder modelBuilder = new ModelBuilder() + .config(config) + .arguments(arguments) + .env(env) + .validationPrinter(env.stderr()) + .validationMode(Validator.Mode.DISABLE) + .severity(Severity.DANGER); + Model oldModel = modelBuilder + .models(oldModels) + .titleLabel("OLD", ColorTheme.DIFF_EVENT_TITLE) + .build(); + Model newModel = modelBuilder + .models(newModels) + .titleLabel("NEW", ColorTheme.DIFF_EVENT_TITLE) + .build(); + + // Diff the models and report on the events, failing if necessary. List events = ModelDiff.compare(classLoader, oldModel, newModel); - boolean hasError = events.stream().anyMatch(event -> event.getSeverity() == Severity.ERROR); - boolean hasDanger = events.stream().anyMatch(event -> event.getSeverity() == Severity.DANGER); - boolean hasWarning = events.stream().anyMatch(event -> event.getSeverity() == Severity.DANGER); - String result = events.stream().map(ValidationEvent::toString).collect(Collectors.joining("\n")); - - if (hasError) { - throw new CliError(String.format("Model diff detected errors: %n%s", result)); - } - - if (!result.isEmpty()) { - env.stdout().println(result); - } - - // Print the "framing" style output to stderr only if !quiet. - if (!standardOptions.quiet()) { - try (ColorBuffer buffer = ColorBuffer.of(env.colors(), env.stderr())) { - if (hasDanger) { - buffer.println("Smithy diff detected danger", Style.BRIGHT_RED, Style.BOLD); - } else if (hasWarning) { - buffer.println("Smithy diff detected warnings", Style.BRIGHT_YELLOW, Style.BOLD); - } else { - buffer.println("Smithy diff complete", Style.BRIGHT_GREEN, Style.BOLD); - } - } - } + modelBuilder + .titleLabel("DIFF", ColorTheme.DIFF_TITLE) + .validatedResult(new ValidatedResult<>(newModel, events)) + .severity(null) // reset so it takes on standard option settings. + .build(); return 0; } - - private Model loadModel(String descriptor, ModelAssembler assembler, List models) { - models.forEach(assembler::addImport); - ValidatedResult result = assembler.assemble(); - if (result.isBroken()) { - throw new CliError("Error loading " + descriptor + " models: \n" + result.getValidationEvents().stream() - .map(ValidationEvent::toString) - .collect(Collectors.joining("\n"))); - } - - return result.unwrap(); - } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/MigrateCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/MigrateCommand.java index 5cbe13d9115..255be59c178 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/MigrateCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/MigrateCommand.java @@ -40,9 +40,9 @@ import software.amazon.smithy.build.model.SmithyBuildConfig; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.StandardOptions; -import software.amazon.smithy.cli.Style; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.loader.ModelAssembler; @@ -92,7 +92,7 @@ public String getSummary() { public int execute(Arguments arguments, Env env) { if (!arguments.getReceiver(StandardOptions.class).quiet()) { env.colors().style(env.stderr(), "upgrade-1-to-2 is deprecated. Use the migrate command instead." - + System.lineSeparator(), Style.BG_YELLOW, Style.BLACK); + + System.lineSeparator(), ColorTheme.DEPRECATED); env.stderr().flush(); } return command.execute(arguments, env); diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java index fcf62b802d7..95f170b8f52 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ModelBuilder.java @@ -33,6 +33,7 @@ import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.EnvironmentVariable; import software.amazon.smithy.cli.StandardOptions; +import software.amazon.smithy.cli.Style; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.loader.sourcecontext.SourceContextLoader; @@ -57,6 +58,9 @@ final class ModelBuilder { private Command.Env env; private SmithyBuildConfig config; private Severity severity; + private ValidatedResult validatedResult; + private String titleLabel; + private Style[] titleLabelStyles; public ModelBuilder arguments(Arguments arguments) { this.arguments = arguments; @@ -64,6 +68,7 @@ public ModelBuilder arguments(Arguments arguments) { } public ModelBuilder models(List models) { + validatedResult = null; this.models = models; return this; } @@ -93,6 +98,17 @@ public ModelBuilder severity(Severity severity) { return this; } + public ModelBuilder validatedResult(ValidatedResult validatedResult) { + this.validatedResult = validatedResult; + return this; + } + + public ModelBuilder titleLabel(String titleLabel, Style... styles) { + this.titleLabel = titleLabel; + this.titleLabelStyles = styles; + return this; + } + public Model build() { SmithyBuilder.requiredState("arguments", arguments); SmithyBuilder.requiredState("models", models); @@ -104,7 +120,6 @@ public Model build() { DiscoveryOptions discoveryOptions = arguments.getReceiver(DiscoveryOptions.class); Severity minSeverity = resolveMinSeverity(standardOptions); ClassLoader classLoader = env.classLoader(); - ModelAssembler assembler = createModelAssembler(classLoader); ColorFormatter colors = env.colors(); CliPrinter stderr = env.stderr(); @@ -116,31 +131,38 @@ public Model build() { validationMode = Validator.Mode.from(standardOptions); } - if (validationMode == Validator.Mode.DISABLE) { - assembler.disableValidation(); - } - - // Emit status updates. - AtomicInteger issueCount = new AtomicInteger(); - assembler.validationEventListener(createStatusUpdater(standardOptions, colors, stderr, issueCount)); + if (validatedResult == null) { + ModelAssembler assembler = createModelAssembler(classLoader); - handleModelDiscovery(discoveryOptions, assembler, classLoader, config); - handleUnknownTraitsOption(buildOptions, assembler); - config.getSources().forEach(assembler::addImport); - models.forEach(assembler::addImport); - config.getImports().forEach(assembler::addImport); - ValidatedResult result = assembler.assemble(); + if (validationMode == Validator.Mode.DISABLE) { + assembler.disableValidation(); + } - clearStatusUpdateIfPresent(issueCount, stderr); + // Emit status updates. + AtomicInteger issueCount = new AtomicInteger(); + assembler.validationEventListener(createStatusUpdater(standardOptions, colors, stderr, issueCount)); + + handleModelDiscovery(discoveryOptions, assembler, classLoader, config); + handleUnknownTraitsOption(buildOptions, assembler); + config.getSources().forEach(assembler::addImport); + models.forEach(assembler::addImport); + config.getImports().forEach(assembler::addImport); + validatedResult = assembler.assemble(); + clearStatusUpdateIfPresent(issueCount, stderr); + } // Sort events by file so that we can efficiently read files for context sequentially. - List sortedEvents = new ArrayList<>(result.getValidationEvents()); + List sortedEvents = new ArrayList<>(validatedResult.getValidationEvents()); sortedEvents.sort(Comparator.comparing(ValidationEvent::getSourceLocation)); - SourceContextLoader sourceContextLoader = result.getResult() + SourceContextLoader sourceContextLoader = validatedResult.getResult() .map(model -> SourceContextLoader.createModelAwareLoader(model, DEFAULT_CODE_LINES)) .orElseGet(() -> SourceContextLoader.createLineBasedLoader(DEFAULT_CODE_LINES)); - PrettyAnsiValidationFormatter formatter = new PrettyAnsiValidationFormatter(sourceContextLoader, colors); + PrettyAnsiValidationFormatter formatter = PrettyAnsiValidationFormatter.builder() + .sourceContextLoader(sourceContextLoader) + .colors(colors) + .titleLabel(titleLabel, titleLabelStyles) + .build(); for (ValidationEvent event : sortedEvents) { // Only log events that are >= --severity. Note that setting --quiet inherently @@ -150,14 +172,12 @@ public Model build() { } } + env.flush(); // Note: disabling validation will still show a summary of failures if the model can't be loaded. - Validator.validate(validationMode != Validator.Mode.ENABLE, colors, stderr, result); - - // Flush outputs to ensure there is no interleaving with subsequent command output. - env.stderr().flush(); - env.stdout().flush(); + Validator.validate(validationMode != Validator.Mode.ENABLE, colors, stderr, validatedResult); + env.flush(); - return result.getResult().orElseThrow(() -> new RuntimeException("Expected Validator to throw")); + return validatedResult.getResult().orElseThrow(() -> new RuntimeException("Expected Validator to throw")); } static Consumer createStatusUpdater( diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatter.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatter.java index 4a9cfcf1aa4..4241ee94954 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatter.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatter.java @@ -21,11 +21,13 @@ import java.util.regex.Pattern; import software.amazon.smithy.cli.ColorBuffer; import software.amazon.smithy.cli.ColorFormatter; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.Style; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.loader.sourcecontext.SourceContextLoader; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.model.validation.ValidationEventFormatter; +import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.StringUtils; final class PrettyAnsiValidationFormatter implements ValidationEventFormatter { @@ -35,10 +37,18 @@ final class PrettyAnsiValidationFormatter implements ValidationEventFormatter { private final SourceContextLoader sourceContextLoader; private final ColorFormatter colors; private final String rootPath = Paths.get("").normalize().toAbsolutePath().toString(); + private final String titleLabel; + private final Style[] titleLabelStyles; + + PrettyAnsiValidationFormatter(Builder builder) { + this.sourceContextLoader = SmithyBuilder.requiredState("sourceContextLoader", builder.sourceContextLoader); + this.colors = SmithyBuilder.requiredState("colors", builder.colors); + this.titleLabel = SmithyBuilder.requiredState("titleLabel", builder.titleLabel); + this.titleLabelStyles = builder.titleLabelStyles; + } - PrettyAnsiValidationFormatter(SourceContextLoader loader, ColorFormatter colors) { - this.sourceContextLoader = loader; - this.colors = colors; + static Builder builder() { + return new Builder(); } @Override @@ -49,26 +59,26 @@ public String format(ValidationEvent event) { switch (event.getSeverity()) { case WARNING: - printTitle(writer, event, Style.YELLOW, Style.BG_YELLOW, Style.BLACK); + printTitle(writer, event, ColorTheme.WARNING, ColorTheme.WARNING_TITLE); break; case ERROR: - printTitle(writer, event, Style.RED, Style.BG_RED, Style.BLACK); + printTitle(writer, event, ColorTheme.ERROR, ColorTheme.ERROR_TITLE); break; case DANGER: - printTitle(writer, event, Style.MAGENTA, Style.BG_MAGENTA, Style.BLACK); + printTitle(writer, event, ColorTheme.DANGER, ColorTheme.DANGER_TITLE); break; case NOTE: - printTitle(writer, event, Style.CYAN, Style.BG_CYAN, Style.BLACK); + printTitle(writer, event, ColorTheme.NOTE, ColorTheme.NOTE_TITLE); break; case SUPPRESSED: default: - printTitle(writer, event, Style.GREEN, Style.BG_GREEN, Style.BLACK); + printTitle(writer, event, ColorTheme.SUPPRESSED, ColorTheme.SUPPRESSED_TITLE); } // Only write an event ID if there is an associated ID. event.getShapeId().ifPresent(id -> { - colors.style(writer, "Shape: ", Style.BRIGHT_BLACK); - colors.style(writer, id.toString(), Style.BLUE); + colors.style(writer, "Shape: ", ColorTheme.MUTED); + colors.style(writer, id.toString(), ColorTheme.EVENT_SHAPE_ID); writer.append(ls); }); @@ -78,8 +88,8 @@ public String format(ValidationEvent event) { String humanReadableFilename = getHumanReadableFilename(event.getSourceLocation().getFilename()); int line = event.getSourceLocation().getLine(); int column = event.getSourceLocation().getColumn(); - colors.style(writer, "File: ", Style.BRIGHT_BLACK); - colors.style(writer, humanReadableFilename + ':' + line + ':' + column, Style.BLUE); + colors.style(writer, "File: ", ColorTheme.MUTED); + colors.style(writer, humanReadableFilename + ':' + line + ':' + column, ColorTheme.MUTED); writer.append(ls).append(ls); try { @@ -88,7 +98,7 @@ public String format(ValidationEvent event) { new CodeFormatter(writer, LINE_LENGTH).writeCode(line, column, lines); } } catch (UncheckedIOException e) { - colors.style(writer, "Invalid source file", Style.UNDERLINE); + colors.style(writer, "Invalid source file", ColorTheme.EM_UNDERLINE); writer.append(": "); writeMessage(writer, e.getCause().getMessage()); writer.append(ls).append(ls); @@ -102,31 +112,38 @@ public String format(ValidationEvent event) { } } - private void printTitle(ColorBuffer writer, ValidationEvent event, Style borderColor, Style... styles) { - colors.style(writer, "── ", borderColor); - String severity = ' ' + event.getSeverity().toString() + ' '; - colors.style(writer, severity, styles); + private void printTitle(ColorBuffer writer, ValidationEvent event, Style border, Style styles) { + colors.style(writer, "── ", border); + + if (!titleLabel.isEmpty()) { + colors.style(writer, ' ' + titleLabel + ' ', titleLabelStyles); + } + + colors.style(writer, ' ' + event.getSeverity().toString() + ' ', styles); + + // dash+padding, [padding + titleLabel + padding + padding], severity, padding+dash, padding, padding. + int prefixLength = 3 + (titleLabel.isEmpty() ? 0 : (titleLabel.length() + 2)) + + 1 + event.getSeverity().toString().length() + 1 + 3 + 1; writer.style(w -> { w.append(" ──"); - int currentLength = severity.length() + 3 + 3 + 1; // severity, dash+padding, padding+dash, padding. - int remainingLength = LINE_LENGTH - currentLength; + int remainingLength = LINE_LENGTH - prefixLength; int padding = remainingLength - event.getId().length(); for (int i = 0; i < padding; i++) { w.append("─"); } w.append(' '); - }, borderColor); + }, border); writer.append(event.getId()).append(System.lineSeparator()); } // Converts Markdown style ticks to use color highlights if colors are enabled. private void writeMessage(ColorBuffer writer, String message) { - String content = StringUtils.wrap(message, 80, System.lineSeparator(), false); + String content = StringUtils.wrap(message, LINE_LENGTH, System.lineSeparator(), false); if (colors.isColorEnabled()) { - content = TICK_PATTERN.matcher(content).replaceAll(colors.style("$1", Style.CYAN)); + content = TICK_PATTERN.matcher(content).replaceAll(colors.style("$1", ColorTheme.LITERAL)); } writer.append(content); @@ -147,4 +164,31 @@ private String getHumanReadableFilename(String filename) { return filename; } + + static final class Builder { + private SourceContextLoader sourceContextLoader; + private ColorFormatter colors; + private String titleLabel = ""; + private Style[] titleLabelStyles; + + Builder sourceContextLoader(SourceContextLoader sourceContextLoader) { + this.sourceContextLoader = sourceContextLoader; + return this; + } + + Builder colors(ColorFormatter colors) { + this.colors = colors; + return this; + } + + Builder titleLabel(String titleLabel, Style... styles) { + this.titleLabel = titleLabel == null ? "" : titleLabel; + titleLabelStyles = styles; + return this; + } + + PrettyAnsiValidationFormatter build() { + return new PrettyAnsiValidationFormatter(this); + } + } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java index 0e51652c2a3..167615aa1e4 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SmithyCommand.java @@ -22,11 +22,11 @@ import software.amazon.smithy.cli.CliError; import software.amazon.smithy.cli.CliPrinter; import software.amazon.smithy.cli.ColorFormatter; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.Command; import software.amazon.smithy.cli.EnvironmentVariable; import software.amazon.smithy.cli.SmithyCli; import software.amazon.smithy.cli.StandardOptions; -import software.amazon.smithy.cli.Style; import software.amazon.smithy.cli.dependencies.DependencyResolver; public final class SmithyCommand implements Command { @@ -65,7 +65,7 @@ public String getSummary() { private void printHelp(ColorFormatter colors, CliPrinter printer) { printer.println(String.format("Usage: %s [-h | --help] [--version] []", - colors.style("smithy", Style.BRIGHT_WHITE, Style.UNDERLINE))); + colors.style("smithy", ColorTheme.EM_UNDERLINE))); printer.println(""); printer.println("Available commands:"); @@ -81,7 +81,7 @@ private void printHelp(ColorFormatter colors, CliPrinter printer) { for (Command command : commands) { if (!command.isHidden()) { printer.println(String.format(" %-" + longestName + "s %s", - colors.style(command.getName(), Style.YELLOW), + colors.style(command.getName(), ColorTheme.LITERAL), command.getSummary())); } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java index 4fa24d971ea..686c3ba13bc 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/Validator.java @@ -20,8 +20,8 @@ import software.amazon.smithy.cli.CliPrinter; import software.amazon.smithy.cli.ColorBuffer; import software.amazon.smithy.cli.ColorFormatter; +import software.amazon.smithy.cli.ColorTheme; import software.amazon.smithy.cli.StandardOptions; -import software.amazon.smithy.cli.Style; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidatedResult; @@ -52,9 +52,9 @@ static void validate(boolean quiet, ColorFormatter colors, CliPrinter printer, V try (ColorBuffer output = ColorBuffer.of(colors, new StringBuilder())) { if (isFailed) { - output.append(colors.style("FAILURE: ", Style.RED, Style.BOLD)); + output.append(colors.style("FAILURE: ", ColorTheme.ERROR)); } else { - output.append(colors.style("SUCCESS: ", Style.GREEN, Style.BOLD)); + output.append(colors.style("SUCCESS: ", ColorTheme.SUCCESS)); } output.append("Validated " + shapeCount).append(" shapes"); diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/BufferPrinter.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/BufferPrinter.java index 5d72b41553a..d6d38cd37b1 100644 --- a/smithy-cli/src/test/java/software/amazon/smithy/cli/BufferPrinter.java +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/BufferPrinter.java @@ -15,6 +15,12 @@ public BufferPrinter println(String text) { return append(text + "\n"); } + @Override + public CliPrinter append(CharSequence csq, int start, int end) { + builder.append(csq, start, end); + return this; + } + @Override public BufferPrinter append(CharSequence text) { synchronized (this) { diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/CliPrinterTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/CliPrinterTest.java index 0e323d7e5c9..ed973628326 100644 --- a/smithy-cli/src/test/java/software/amazon/smithy/cli/CliPrinterTest.java +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/CliPrinterTest.java @@ -24,9 +24,18 @@ public class CliPrinterTest { @Test public void printsWithNewlineByDefault() { StringBuilder builder = new StringBuilder(); - CliPrinter printer = c -> { - builder.append(c); - return null; + CliPrinter printer = new CliPrinter() { + @Override + public CliPrinter append(char c) { + builder.append(c); + return this; + } + + @Override + public CliPrinter append(CharSequence csq, int start, int end) { + builder.append(csq, start, end); + return this; + } }; printer.println("Hi"); diff --git a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatterTest.java b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatterTest.java index 937dcff6f51..4d3c9a314e9 100644 --- a/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatterTest.java +++ b/smithy-cli/src/test/java/software/amazon/smithy/cli/commands/PrettyAnsiValidationFormatterTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import software.amazon.smithy.cli.AnsiColorFormatter; import software.amazon.smithy.cli.ColorFormatter; +import software.amazon.smithy.cli.Style; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.loader.sourcecontext.SourceContextLoader; @@ -57,8 +58,8 @@ public void formatsEventsWithColors() { assertThat(formatted, equalTo( "\n" + "\u001B[31m── \u001B[0m\u001B[41;30m ERROR \u001B[0m\u001B[31m ───────────────────────────────────────────────────────────────── \u001B[0mFoo\n" - + "\u001B[90mShape: \u001B[0m\u001B[34msmithy.example#Foo\u001B[0m\n" - + "\u001B[90mFile: \u001B[0m\u001B[34mbuild/resources/test/software/amazon/smithy/cli/commands/valid-model.smithy:5:1\u001B[0m\n" + + "\u001B[90mShape: \u001B[0m\u001B[95msmithy.example#Foo\u001B[0m\n" + + "\u001B[90mFile: \u001B[0m\u001B[90mbuild/resources/test/software/amazon/smithy/cli/commands/valid-model.smithy:5:1\u001B[0m\n" + "\n" + "\u001B[90m4| \u001B[0m\n" + "\u001B[90m5| \u001B[0mresource Foo {\n" @@ -111,7 +112,7 @@ public void wrapsLongLines() { private PrettyAnsiValidationFormatter createFormatter(ColorFormatter colors) { SourceContextLoader loader = SourceContextLoader.createLineBasedLoader(2); - return new PrettyAnsiValidationFormatter(loader, colors); + return PrettyAnsiValidationFormatter.builder().sourceContextLoader(loader).colors(colors).build(); } private String formatTestEventWithSeverity(PrettyAnsiValidationFormatter pretty, Severity severity) { @@ -135,7 +136,10 @@ public void toleratesInvalidSourceFiles() { SourceContextLoader loader = s -> { throw new UncheckedIOException(new IOException("Error!!!")); }; - PrettyAnsiValidationFormatter pretty = new PrettyAnsiValidationFormatter(loader, colors); + PrettyAnsiValidationFormatter pretty = PrettyAnsiValidationFormatter.builder() + .sourceContextLoader(loader) + .colors(colors) + .build(); ValidationEvent event = ValidationEvent.builder() .id("Foo") .severity(Severity.NOTE) @@ -154,4 +158,29 @@ public void toleratesInvalidSourceFiles() { + "\n" + "Hello\n")); } + + @Test + public void showsTitleLabelsWhenPresent() { + PrettyAnsiValidationFormatter pretty = PrettyAnsiValidationFormatter.builder() + .sourceContextLoader(SourceContextLoader.createLineBasedLoader(4)) + .colors(AnsiColorFormatter.FORCE_COLOR) + .titleLabel("FOO", Style.BG_BLUE, Style.BLACK) + .build(); + ValidationEvent event = ValidationEvent.builder() + .id("Hello") + .severity(Severity.WARNING) + .shapeId(ShapeId.from("smithy.example#Foo")) + .message("hello") + .build(); + + String formatted = normalizeLinesAndFiles(pretty.format(event)); + + assertThat(formatted, equalTo( + "\n" + + "\u001B[33m── \u001B[0m\u001B[44;30m FOO \u001B[0m\u001B[43;30m " + + "WARNING \u001B[0m\u001B[33m ──────────────────────────────────────────────────────── \u001B[0mHello\n" + + "\u001B[90mShape: \u001B[0m\u001B[95msmithy.example#Foo\u001B[0m\n" + + "\n" + + "hello\n")); + } }