Skip to content

Commit

Permalink
Add progress reporters (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Matyrobbrt authored Jan 3, 2024
1 parent 9ae778c commit 21f2d34
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/.gradle/
/.idea/
/build/
build/
/repo/
/.settings/
/bin/
Expand Down
16 changes: 12 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ allprojects {
apply plugin: 'maven-publish'
apply plugin: 'java-library'
apply plugin: 'net.neoforged.gradleutils'
apply plugin: 'com.github.johnrengelman.shadow'
if (project.name != 'cli-utils') {
apply plugin: 'com.github.johnrengelman.shadow'
}

group = 'net.neoforged.installertools'
java {
Expand All @@ -34,10 +36,15 @@ allprojects {

tasks.named('jar', Jar).configure {
manifest.attributes('Implementation-Version': project.version)
manifest.attributes('Main-Class': application.mainClass.get())
if (application.mainClass.getOrNull()) {
manifest.attributes('Main-Class': application.mainClass.get())
}
}
tasks.named('shadowJar', ShadowJar).configure {
archiveClassifier = 'fatjar'

if (project.name != 'cli-utils') {
tasks.named('shadowJar', ShadowJar).configure {
archiveClassifier = 'fatjar'
}
}
}

Expand All @@ -61,6 +68,7 @@ dependencies {
implementation 'de.siegmar:fastcsv:2.0.0'
implementation 'net.neoforged:srgutils:1.0.0'
implementation 'org.ow2.asm:asm-commons:9.3'
implementation project(':cli-utils')
}

publishing {
Expand Down
18 changes: 18 additions & 0 deletions cli-utils/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
testImplementation('org.junit.jupiter:junit-jupiter-engine:5.8.2')
}

test {
useJUnitPlatform()
}

publishing {
publications.register('mavenJava', MavenPublication) {
from components.java
pom {
name = 'CLI Utils'
description = "CLI Utilities for NeoForge's projects"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.neoforged.cliutils.progress;

import java.util.Arrays;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;

public enum ProgressActionType {
STEP('s', ProgressManager::setStep),
PROGRESS('p', (progressManager, value) -> {
if (value.endsWith("%")) {
progressManager.setPercentageProgress(Double.parseDouble(value.substring(0, value.length() - 1)));
} else {
progressManager.setProgress(Integer.parseInt(value));
}
}),
MAX_PROGRESS('m', (progressManager, value) -> progressManager.setMaxProgress(Integer.parseInt(value))),
INDETERMINATE('i', (progressManager, value) -> progressManager.setIndeterminate(Boolean.parseBoolean(value)));

public static final Map<Character, ProgressActionType> TYPES = Arrays.stream(values())
.collect(Collectors.toMap(val -> val.identifier, Function.identity()));

public final char identifier;
public final BiConsumer<ProgressManager, String> acceptor;

ProgressActionType(char identifier, BiConsumer<ProgressManager, String> acceptor) {
this.identifier = identifier;
this.acceptor = acceptor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package net.neoforged.cliutils.progress;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
* Intercepts {@link ProgressActionType} progress manager actions and redirects them to the {@code manager}.
*/
public class ProgressInterceptor extends FilterOutputStream {
public static final String FULL_MODIFIER = "\033[" + ProgressReporter.MODIFIER_KEY + ";";

protected final ProgressManager manager;
public ProgressInterceptor(OutputStream out, ProgressManager manager) {
super(out);
this.manager = manager;
}

private StringBuffer current;

@Override
public void write(int b) throws IOException {
if (current != null) {
if (b == '\n') {
processCurrent();
} else if (b != '\r') // Ignore CR because Windows is bad
current.append((char)b);
} else {
if (b == '\033') {
current = new StringBuffer()
.append((char)b);
} else {
out.write(b);
}
}
}

private void processCurrent() throws IOException {
final String cur = current.toString();
if (cur.startsWith(FULL_MODIFIER)) {
final String[] values = cur.substring(FULL_MODIFIER.length()).split(" ", 2);
// If not 2, the value's empty, which isn't supported, so ignore
if (values.length == 2) {
final String first = values[0];
if (first.length() != 1) {
System.err.println("Invalid progress modifier: " + values[0]);
return;
}

final ProgressActionType action = ProgressActionType.TYPES.get(first.charAt(0));
if (action == null) {
System.err.println("Unknown progress modifier: " + first.charAt(0));
return;
}

action.acceptor.accept(manager, values[1]);
}
} else {
out.write(cur.getBytes(StandardCharsets.UTF_8));
out.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8));
}

current = null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.neoforged.cliutils.progress;

/**
* A manager that changes the progress of a progress bar, to indicate the current progress to users.
*/
public interface ProgressManager {
/**
* Sets the max progress of the manager.
*/
void setMaxProgress(int maxProgress);

/**
* Sets the current progress of the manager.
*/
void setProgress(int progress);

/**
* Sets progress of the manager, based on a fractional value. <br>
* Note: this also sets the {@link #setMaxProgress(int)} to {@literal 100}.
*/
void setPercentageProgress(double progress);

/**
* Sets the current step to be shown to the user.
*/
void setStep(String name);

/**
* Sets whether the max progress is known or not. If not, the loading bar will load 'forever'.
*/
void setIndeterminate(boolean indeterminate);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package net.neoforged.cliutils.progress;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URLConnection;
import java.text.DecimalFormat;

/**
* A {@link ProgressManager} that forwards all changes to a {@link ProgressReporter#output print stream}
* that writes the changes with an ANSI modifier {@value #MODIFIER_KEY}. <br>
* The ANSI modifier takes the form "\033[{@literal progressmanager};action value". <p>
* The {@link #getDefault() default reporter} will only be enabled if the {@value #ENABLED_PROPERTY} system property is set to {@code true},
* and will output to {@link System#err}.
* @see ProgressActionType for the actions
*/
public class ProgressReporter implements ProgressManager {
public static final String MODIFIER_KEY = "progressmanager";
public static final String ENABLED_PROPERTY = "net.neoforged.progressmanager.enabled";
private static final DecimalFormat TWO_DECIMALS = new DecimalFormat("#.00");

protected final boolean enabled;
protected final PrintStream output;

public ProgressReporter(boolean enabled, PrintStream output) {
this.enabled = enabled;
this.output = output;
}

public static ProgressReporter getDefault() {
return new ProgressReporter(Boolean.getBoolean(ENABLED_PROPERTY), System.err);
}

@Override
public void setMaxProgress(int maxProgress) {
write(ProgressActionType.MAX_PROGRESS, String.valueOf(maxProgress));
}

@Override
public void setProgress(int progress) {
write(ProgressActionType.PROGRESS, String.valueOf(progress));
}

@Override
public void setPercentageProgress(double progress) {
write(ProgressActionType.PROGRESS, TWO_DECIMALS.format(progress) + "%");
}

@Override
public void setStep(String name) {
write(ProgressActionType.STEP, name);
}

@Override
public void setIndeterminate(boolean indeterminate) {
write(ProgressActionType.INDETERMINATE, String.valueOf(indeterminate));
}

protected void write(ProgressActionType type, String value) {
if (!enabled) return;

try {
//noinspection RedundantStringFormatCall
output.println(String.format("\033[%s;%s %s", MODIFIER_KEY, type.identifier, value));
} catch (Exception exception) {
exception.printStackTrace(System.err);
System.err.println("Failed to write progress: " + exception);
}
}

public InputStream wrapDownload(URLConnection connection) throws IOException {
connection.connect();
setMaxProgress(connection.getContentLength());
return wrapDownload(connection.getInputStream());
}

public InputStream wrapDownload(InputStream in) {
return new FilterInputStream(in) {
private int nread = 0;

@Override
public int read() throws IOException {
int c = in.read();
if (c >= 0) setProgress(++nread);
return c;
}


@Override
public int read(byte[] b) throws IOException {
int nr = in.read(b);
if (nr > 0) setProgress(nread += nr);
return nr;
}


@Override
public int read(byte[] b, int off, int len) throws IOException {
int nr = in.read(b, off, len);
if (nr > 0) setProgress(nread += nr);
return nr;
}


@Override
public long skip(long n) throws IOException {
long nr = in.skip(n);
if (nr > 0) setProgress(nread += nr);
return nr;
}
};
}
}
Loading

0 comments on commit 21f2d34

Please sign in to comment.