From 00253b523170d65fae8f97ac714e89010c977700 Mon Sep 17 00:00:00 2001 From: Hunter Mellema <124718352+hpmellema@users.noreply.github.com> Date: Mon, 31 Jul 2023 10:52:53 -0600 Subject: [PATCH] Add progress tracker and message for CLI while cloning a template (#1888) Adds a progress tracker for long running CLI tasks. The progress tracker currently supports a simple repeated dots pattern to indicate execution of a long running task. The progress tracker can be extended in the future to support progress bars and additional loading messages. --- config/spotbugs/filter.xml | 6 ++ .../smithy/cli/commands/InitCommand.java | 17 +++-- .../smithy/cli/commands/ProgressStyle.java | 58 ++++++++++++++++ .../smithy/cli/commands/ProgressTracker.java | 68 +++++++++++++++++++ 4 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressStyle.java create mode 100644 smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressTracker.java diff --git a/config/spotbugs/filter.xml b/config/spotbugs/filter.xml index 3e4ed00a8e0..65595618241 100644 --- a/config/spotbugs/filter.xml +++ b/config/spotbugs/filter.xml @@ -188,4 +188,10 @@ + + + + + + diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java index 8deb92fd084..5b814dba4f4 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java @@ -188,13 +188,18 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options op final String templatePath = getTemplatePath(templateNode, template); List includedFiles = getIncludedFiles(templateNode); - // Specify the subdirectory to download - exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", templatePath), temp); - // add any additional files that should be included - for (String includedFile : includedFiles) { - exec(ListUtils.of("git", "sparse-checkout", "add", "--no-cone", includedFile), temp); + try (ProgressTracker t = new ProgressTracker(env, + ProgressStyle.dots("cloning template", "template cloned"), + standardOptions.quiet() + )) { + // Specify the subdirectory to download + exec(ListUtils.of("git", "sparse-checkout", "set", "--no-cone", templatePath), temp); + // add any additional files that should be included + for (String includedFile : includedFiles) { + exec(ListUtils.of("git", "sparse-checkout", "add", "--no-cone", includedFile), temp); + } + exec(ListUtils.of("git", "checkout"), temp); } - exec(ListUtils.of("git", "checkout"), temp); diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressStyle.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressStyle.java new file mode 100644 index 00000000000..176da8f8f11 --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressStyle.java @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.cli.commands; + +import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.smithy.cli.ColorBuffer; +import software.amazon.smithy.cli.ColorTheme; +import software.amazon.smithy.cli.Command; +import software.amazon.smithy.utils.StringUtils; + + +interface ProgressStyle { + + void updateAction(Command.Env env, AtomicInteger tracker); + + void closeAction(Command.Env env); + + static ColorBuffer getBuffer(Command.Env env) { + return ColorBuffer.of(env.colors(), env.stdout()); + } + + static ProgressStyle dots(String progressMessage, String closeMessage) { + return new ProgressStyle() { + private static final String PROGRESS_CHAR = "."; + private static final int TICKER_LENGTH = 3; + private final long startTimeMillis = System.currentTimeMillis(); + + @Override + public void updateAction(Command.Env env, AtomicInteger tracker) { + int tickCount = tracker.getAndIncrement(); + int tickNumber = tickCount % (TICKER_LENGTH + 1); + String loadStr = StringUtils.repeat(PROGRESS_CHAR, tickNumber) + + StringUtils.repeat(" ", TICKER_LENGTH - tickNumber); + try (ColorBuffer buffer = getBuffer(env)) { + buffer.print("\r") + .print(progressMessage, ColorTheme.NOTE) + .print(loadStr, ColorTheme.NOTE); + } + } + + @Override + public void closeAction(Command.Env env) { + try (ColorBuffer buffer = getBuffer(env)) { + buffer.print("\r") + .print(closeMessage, ColorTheme.SUCCESS) + .print(" [", ColorTheme.MUTED) + .print((System.currentTimeMillis() - startTimeMillis) / 1000.0 + "s", + ColorTheme.NOTE) + .print("]", ColorTheme.MUTED) + .println(); + } + } + }; + } +} diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressTracker.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressTracker.java new file mode 100644 index 00000000000..d6bcb8f1f98 --- /dev/null +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/ProgressTracker.java @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.cli.commands; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import software.amazon.smithy.cli.Command; + +final class ProgressTracker implements AutoCloseable { + private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(1, runnable -> { + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setDaemon(true); + return thread; + }); + private static final long INTERVAL_MILLIS = 400L; + private final ScheduledFuture task; + private final ProgressStyle style; + private final Command.Env env; + private final AtomicInteger tracker; + private final boolean quiet; + + ProgressTracker(Command.Env env, ProgressStyle style, boolean quiet) { + this(env, style, quiet, new AtomicInteger()); + } + + ProgressTracker(Command.Env env, ProgressStyle style, boolean quiet, AtomicInteger tracker) { + this.env = env; + this.style = style; + this.quiet = quiet; + this.tracker = tracker; + + // Do not print a progress bar if the quiet setting is enabled + if (quiet) { + task = null; + } else { + task = EXECUTOR.scheduleAtFixedRate(this::write, 0, INTERVAL_MILLIS, TimeUnit.MILLISECONDS); + } + } + + @Override + public void close() { + if (!quiet) { + task.cancel(false); + try { + EXECUTOR.schedule(this::executeClose, 0, TimeUnit.NANOSECONDS).get(); + } catch (ExecutionException | InterruptedException e) { /* ignored */ } + } + } + + private void write() { + style.updateAction(env, tracker); + // Flush so the output is written immediately + env.stdout().flush(); + } + + private void executeClose() { + style.closeAction(env); + // Flush so the output is written immediately + env.stdout().flush(); + } +}