Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions enigma-cli/src/main/java/org/quiltmc/enigma/command/Argument.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -88,7 +90,7 @@ static Argument<String> ofLenientEnum(String name, Class<? extends Enum<?>> type
* Assumes enum values have conventional {@code SCREAMING_SNAKE_CASE} names.
*/
static <E extends Enum<E>> Argument<E> ofEnum(String name, Class<E> type, String explanation) {
return new Argument<>(name, alternativesOf(type), string -> Enum.valueOf(type, string.toUpperCase()), explanation);
return new Argument<>(name, alternativesOf(type), string -> parseCaseInsensitiveEnum(type, string), explanation);
}

static <E extends Enum<E>> Argument<E> ofStrictEnum(String name, Class<E> type, String explanation) {
Expand All @@ -111,10 +113,15 @@ static Argument<Pattern> ofPattern(String name, String explanation) {
return new Argument<>(name, PATTERN_TYPE, Argument::parsePattern, explanation);
}

private static String alternativesOf(Class<? extends Enum<?>> type) {
return Arrays.stream(type.getEnumConstants())
.map(Object::toString)
.collect(Collectors.joining(ALTERNATIVES_DELIM));
static <T, C extends Collection<T>> Argument<C> ofCollection(
String name, String typeDescription, String explanation,
Function<String, T> elementParser, Collector<T, ?, C> collector
) {
return new Argument<>(
name, typeDescription,
string -> parseCollection(string, ",", elementParser, collector),
explanation
);
}

private final String name;
Expand Down Expand Up @@ -143,6 +150,21 @@ static Path parseFile(String path) {
return verifyFile(parsePath(path)).orElse(null);
}

static <T, C extends Collection<T>> C parseCollection(
String input, String delimRegex, Function<String, T> elementParser, Collector<T, ?, C> collector
) {
return Arrays.stream(input.split(delimRegex))
.map(string -> {
final T element = elementParser.apply(string);
if (element == null) {
throw new IllegalArgumentException("Invalid element: " + string);
} else {
return element;
}
})
.collect(collector);
}

static Path parseFolder(String path) {
return verifyFolder(parsePath(path)).orElse(null);
}
Expand Down Expand Up @@ -205,6 +227,10 @@ static String parseString(String string) {
return string.isEmpty() ? null : string;
}

static <E extends Enum<E>> E parseCaseInsensitiveEnum(Class<E> type, String string) {
return Enum.valueOf(type, string.toUpperCase());
}

static Pattern parsePattern(String regex) {
if (regex.isEmpty()) {
return null;
Expand Down Expand Up @@ -256,6 +282,16 @@ static <T> Optional<T> peek(Optional<T> optional, Consumer<T> action) {
return optional;
}

static String alternativesOf(Class<? extends Enum<?>> type) {
return alternativesOf(type, ALTERNATIVES_DELIM);
}

static String alternativesOf(Class<? extends Enum<?>> type, String delim) {
return Arrays.stream(type.getEnumConstants())
.map(Object::toString)
.collect(Collectors.joining(delim));
}

public String getName() {
return this.name;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,61 @@
import org.quiltmc.enigma.api.stats.StatType;
import org.quiltmc.enigma.api.stats.StatsGenerator;
import org.quiltmc.enigma.command.PrintStatsCommand.Required;
import org.quiltmc.enigma.command.PrintStatsCommand.Optional;
import org.quiltmc.enigma.util.I18n;
import org.tinylog.Logger;

import javax.annotation.Nullable;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;

import static org.quiltmc.enigma.command.CommonArguments.ENIGMA_PROFILE;
import static org.quiltmc.enigma.command.CommonArguments.INPUT_JAR;
import static org.quiltmc.enigma.command.CommonArguments.INPUT_MAPPINGS;

public final class PrintStatsCommand extends Command<Required, Path> {
public final class PrintStatsCommand extends Command<Required, Optional> {
private static final Argument<Set<StatType>> INCLUDED_TYPES = Argument.ofCollection(
"included-types", Argument.alternativesOf(StatType.class, "&|"),
"""
The stat types to include.""",
string -> Argument.parseCaseInsensitiveEnum(StatType.class, string),
Collectors.toUnmodifiableSet()
);

private static final Argument<Boolean> INCLUDE_SYNTHETIC = Argument.ofBool("include-synthetic",
"""
Whether to include synthetic entries."""
);

private static final Argument<Boolean> COUNT_FALLBACK = Argument.ofBool("count-fallback",
"""
Whether to count fallback-proposed entries as mapped."""
);

public static final PrintStatsCommand INSTANCE = new PrintStatsCommand();

private PrintStatsCommand() {
super(
ArgsParser.of(INPUT_JAR, INPUT_MAPPINGS, Required::new),
ArgsParser.of(ENIGMA_PROFILE)
ArgsParser.of(ENIGMA_PROFILE, INCLUDED_TYPES, INCLUDE_SYNTHETIC, COUNT_FALLBACK, Optional::new)
);
}

@Override
void runImpl(Required required, Path enigmaProfile) throws Exception {
run(required.inputJar, required.inputMappings, enigmaProfile, null);
void runImpl(Required required, Optional optional) throws Exception {
final Set<StatType> includedTypes = optional.includedTypes == null || optional.includedTypes.isEmpty()
? EnumSet.allOf(StatType.class)
: optional.includedTypes;

final GenerationParameters params = new GenerationParameters(
includedTypes,
Boolean.TRUE.equals(optional.includeSynthetic),
Boolean.TRUE.equals(optional.countFallback)
);

run(required.inputJar, required.inputMappings, optional.enigmaProfile, params, null);
}

@Override
Expand All @@ -43,24 +75,44 @@ public String getDescription() {
return "Generates and prints out the statistics of how well the provided mappings cover the provided JAR file.";
}

public static void run(Path inJar, Path mappings, @Nullable Path profilePath, @Nullable Iterable<EnigmaPlugin> plugins) throws Exception {
public static void run(
Path inJar, Path mappings, @Nullable Path profilePath,
@Nullable GenerationParameters params, @Nullable Iterable<EnigmaPlugin> plugins
) throws Exception {
EnigmaProfile profile = EnigmaProfile.read(profilePath);
Enigma enigma = createEnigma(profile, plugins);

run(inJar, mappings, enigma);
run(inJar, mappings, enigma, params);
}

public static void run(Path inJar, Path mappings, Enigma enigma) throws Exception {
public static void run(Path inJar, Path mappings, Enigma enigma, @Nullable GenerationParameters params) throws Exception {
StatsGenerator generator = new StatsGenerator(openProject(inJar, mappings, enigma));
ProjectStatsResult result = generator.generate(new ConsoleProgressListener(), new GenerationParameters(Set.of(StatType.values())));

Logger.info(String.format("Overall mapped: %.2f%% (%s / %s)", result.getPercentage(), result.getMapped(), result.getMappable()));
Logger.info(String.format("Classes: %.2f%% (%s / %s)", result.getPercentage(StatType.CLASSES), result.getMapped(StatType.CLASSES), result.getMappable(StatType.CLASSES)));
Logger.info(String.format("Fields: %.2f%% (%s / %s)", result.getPercentage(StatType.FIELDS), result.getMapped(StatType.FIELDS), result.getMappable(StatType.FIELDS)));
Logger.info(String.format("Methods: %.2f%% (%s / %s)", result.getPercentage(StatType.METHODS), result.getMapped(StatType.METHODS), result.getMappable(StatType.METHODS)));
Logger.info(String.format("Parameters: %.2f%% (%s / %s)", result.getPercentage(StatType.PARAMETERS), result.getMapped(StatType.PARAMETERS), result.getMappable(StatType.PARAMETERS)));
if (params == null) {
params = new GenerationParameters();
}

ProjectStatsResult result = generator.generate(new ConsoleProgressListener(), params);

final Set<StatType> includedTypes = params.includedTypes();

if (includedTypes.size() > 1) {
final String overall = I18n.translate("menu.file.stats.overall");
logResult(overall, result.getPercentage(), result.getMapped(), result.getMappable());
}

for (final StatType type : StatType.values()) {
if (includedTypes.contains(type)) {
logResult(type.getName(), result.getPercentage(type), result.getMapped(type), result.getMappable(type));
}
}
}

private static void logResult(String label, double percentage, int mapped, int mappable) {
Logger.info("%s: %.2f%% (%s / %s)".formatted(label, percentage, mapped, mappable));
}

record Required(Path inputJar, Path inputMappings) { }
record Optional(Path enigmaProfile, Set<StatType> includedTypes, Boolean includeSynthetic, Boolean countFallback) { }
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import org.junit.jupiter.api.Test;
import org.quiltmc.enigma.TestUtil;
import org.quiltmc.enigma.api.stats.GenerationParameters;
import org.quiltmc.enigma.api.stats.StatType;

import java.nio.file.Path;
import java.util.Set;

import static org.quiltmc.enigma.TestUtil.getResource;

Expand All @@ -14,6 +17,8 @@ public class PrintStatsCommandTest {
@Test
public void test() throws Exception {
// just here to manually verify output
PrintStatsCommand.run(JAR, MAPPINGS, null, null);
PrintStatsCommand.run(JAR, MAPPINGS, null, null, null);
final var customParams = new GenerationParameters(Set.of(StatType.CLASSES), true, true);
PrintStatsCommand.run(JAR, MAPPINGS, null, customParams, null);
}
}