More doodling

This commit is contained in:
Stephan Schroevers
2023-04-20 22:51:52 +02:00
parent 15622acfc2
commit 943a409ec1
2 changed files with 204 additions and 1 deletions

View File

@@ -0,0 +1,200 @@
package tech.picnic.errorprone.openai;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSetMultimap;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.fusesource.jansi.AnsiConsole;
import org.jline.builtins.ConfigurationPath;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.Builtins;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.keymap.KeyMap;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.MaskingCallback;
import org.jline.reader.Parser;
import org.jline.reader.Reference;
import org.jline.reader.UserInterruptException;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.widget.TailTipWidgets;
import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.HelpCommand;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.Model.PositionalParamSpec;
import picocli.CommandLine.Model.UsageMessageSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParentCommand;
import picocli.CommandLine.ParseResult;
import picocli.shell.jline3.PicocliCommands;
import picocli.shell.jline3.PicocliCommands.PicocliCommandsFactory;
import tech.picnic.errorprone.openai.IssueExtractor.Issue;
// XXX: Review whether to enable a *subset* of JLine's built-ins. See
// https://github.com/remkop/picocli/tree/main/picocli-shell-jline3#jline-316-and-picocli-44-example.
public final class Cli {
public static void main(String... args) {
IssueResolutionController issueResolutionController =
new IssueResolutionController(ImmutableSetMultimap.of());
AnsiConsole.systemInstall();
try {
CommandSpec issues = command("issues", 'i', "List issues.");
CommandSpec submit =
command("submit", 's', "Submit issues to OpenAI.")
.addPositional(
PositionalParamSpec.builder()
.paramLabel("ISSUES")
.type(List.class)
.auxiliaryTypes(Integer.class)
.description("The subset of issues to submit (default: all)")
.build());
CommandSpec apply = command("apply", 'a', "Apply the changes suggested by OpenAI");
CommandSpec next = command("next", 'n', "Move to the next issue.");
CommandSpec previous = command("previous", 'p', "Move to the previous issue.");
PicocliCommandsFactory factory = new PicocliCommandsFactory();
// Or, if you have your own factory, you can chain them like this:
// MyCustomFactory customFactory = createCustomFactory(); // your application custom factory
// PicocliCommandsFactory factory = new PicocliCommandsFactory(customFactory); // chain the
// factories
CommandLine cmd =
new CommandLine(
CommandSpec.create()
.name("")
.addSubcommand(null, issues)
.addSubcommand(null, submit)
.addSubcommand(null, apply)
.addSubcommand(null, next)
.addSubcommand(null, previous),
factory);
// XXX: Rename to `commands` if we don't enable the built-ins.
PicocliCommands commands = new PicocliCommands(cmd);
Parser parser = new DefaultParser();
// XXX: Check `TerminalBuilder.builder().build()` options.
try (Terminal terminal = TerminalBuilder.terminal()) {
SystemRegistry systemRegistry =
new SystemRegistryImpl(parser, terminal, () -> Path.of("").toAbsolutePath(), null);
systemRegistry.setCommandRegistries(commands);
systemRegistry.register("help", commands);
LineReader reader =
LineReaderBuilder.builder()
.terminal(terminal)
.completer(systemRegistry.completer())
.parser(parser)
.build();
cmd.setExecutionStrategy(
pr -> run(pr, reader.getTerminal().writer(), issueResolutionController));
factory.setTerminal(terminal);
new TailTipWidgets(
reader, systemRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER)
.enable();
reader.getKeyMaps().get("main").bind(new Reference("tailtip-toggle"), KeyMap.ctrl('t'));
while (true) {
try {
systemRegistry.cleanUp();
systemRegistry.execute(reader.readLine("prompt> ", null, (MaskingCallback) null, null));
} catch (UserInterruptException e) {
// XXX: Review whether indeed to ignore this.
} catch (EndOfFileException e) {
return;
} catch (Exception e) {
systemRegistry.trace(e);
}
}
}
} catch (Throwable t) {
// XXX: Review!
t.printStackTrace();
} finally {
AnsiConsole.systemUninstall();
}
}
static final class IssueResolutionController {
private final ImmutableList<ImmutableList<Issue<Path>>> issuesGroupedByPath;
private int currentIndex = 0;
// XXX: Maybe shouldn't already group by path at call site?
IssueResolutionController(ImmutableSetMultimap<Path, Issue<Path>> issues) {
this.issuesGroupedByPath =
issues.asMap().values().stream().map(ImmutableList::copyOf).collect(toImmutableList());
}
void list(PrintWriter out) {
if (currentIndex >= issuesGroupedByPath.size()) {
out.println("No more issues.");
return;
}
ImmutableList<Issue<Path>> issues = issuesGroupedByPath.get(currentIndex);
out.printf("Issues for %s:%n", issues.get(0).file());
for (int i = 0; i < issues.size(); i++) {
out.printf(Locale.ROOT, "%02d. %s%n", i, issues.get(i).description());
}
}
void next(PrintWriter out) {
currentIndex++;
list(out);
}
void previous(PrintWriter out) {
currentIndex--;
list(out);
}
}
static int run(ParseResult pr, PrintWriter out, IssueResolutionController controller) {
Integer helpExitCode = CommandLine.executeHelpRequest(pr);
if (helpExitCode != null) {
return helpExitCode;
}
ParseResult subcommand = Objects.requireNonNull(pr.subcommand(), "subcommand");
switch (subcommand.commandSpec().name()) {
case "issues" -> controller.list(out);
case "submit" -> out.println("submit");
case "apply" -> out.println("apply");
case "next" -> controller.next(out);
case "previous" -> controller.previous(out);
default -> throw new IllegalStateException(
"Unknown command: " + subcommand.commandSpec().name());
}
return 0;
}
private static CommandSpec command(String name, char alias, String description) {
return CommandSpec.create()
.name(name)
.aliases(Character.toString(alias))
.usageMessage(new UsageMessageSpec().description(description))
.addOption(helpOption());
}
private static OptionSpec helpOption() {
return OptionSpec.builder("-h", "--help")
.usageHelp(true)
.description("Show this help message and exit.")
.build();
}
}

View File

@@ -32,12 +32,15 @@ warning messages extracted from Maven build output.
* Add a `--help` flag that prints a help message.
* Add a `--run-to-fix <command>` (name TBD) flag that repeatedly runs the
given command in a sub-process and processes the output until either no
further issues are reported, or no further fixes are found.
further issues are reported, or no further fixes are found. (For this we
could use `ProcessBuilder`.)
* Add a `--git-safe` (name TBD) flag that only processes files that are
tracked by Git and that have not been modified.
* Add a `--format <format>` flag that allows the user to specify the
format of the input. E.g. `--format maven` (default), `--format
errorprone`, `--format sarif`, etc.
* Add a `--patch-context <lines>` flag that allows the user to specify the
number of lines of context to include in presented unified patches.
* Create a binary image using [GraalVM](https://www.graalvm.org/).
* Add support for sending a suitable subset of the code to OpenAI, so as (a) to
better deal with the token limit and (b) potentially reduce cost. This might