Skip to content

Commit

Permalink
Add progress tracker and message for CLI while cloning a template (sm…
Browse files Browse the repository at this point in the history
…ithy-lang#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.
  • Loading branch information
hpmellema authored and alextwoods committed Sep 15, 2023
1 parent 3cb19cf commit 00253b5
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 6 deletions.
6 changes: 6 additions & 0 deletions config/spotbugs/filter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,10 @@
<Bug pattern="SF_SWITCH_FALLTHROUGH"/>
</Match>

<!-- It is not worth catching if the closing task of a progress bar fails to execute -->
<Match>
<Class name="software.amazon.smithy.cli.commands.ProgressTracker"/>
<Bug pattern="DE_MIGHT_IGNORE"/>
</Match>

</FindBugsFilter>
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,18 @@ private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options op
final String templatePath = getTemplatePath(templateNode, template);
List<String> 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);



Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
};
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 00253b5

Please sign in to comment.