Bump JLine to 3.13.2

This commit is contained in:
mattirn
2019-11-30 19:00:20 +01:00
committed by Remko Popma
parent 257c199a80
commit c7b64708f7
5 changed files with 196 additions and 15 deletions

View File

@@ -7,7 +7,7 @@ ivyVersion = 2.4.0
jacocoVersion = 0.8.2
jansiVersion = 1.15
jlineVersion = 2.14.6
jline3Version = 3.9.0
jline3Version = 3.13.2
junitDepVersion = 4.11
junitVersion = 4.12
springBootVersion = 2.1.6.RELEASE

View File

@@ -8,6 +8,8 @@ plugins {
group 'info.picocli'
description 'Picocli Shell JLine3 - easily build interactive shell applications with JLine 3 and picocli.'
version "$projectVersion"
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile rootProject

View File

@@ -0,0 +1,109 @@
package picocli.shell.jline3;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jline.builtins.Completers.OptionCompleter;
import org.jline.builtins.Completers.SystemCompleter;
import org.jline.builtins.Options.HelpException;
import org.jline.builtins.Widgets.ArgDesc;
import org.jline.builtins.Widgets.CmdDesc;
import org.jline.reader.impl.completer.ArgumentCompleter;
import org.jline.reader.impl.completer.NullCompleter;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.utils.AttributedString;
import picocli.CommandLine;
import picocli.CommandLine.Help;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
public class PicocliCommands {
private final Supplier<Path> workDir;
private final CommandLine cmd;
private final List<String> commands;
public PicocliCommands(Path workDir, CommandLine cmd) {
this(() -> workDir, cmd);
}
public PicocliCommands(Supplier<Path> workDir, CommandLine cmd) {
this.workDir = workDir;
this.cmd = cmd;
commands = new ArrayList<>(Arrays.asList(cmd.getCommandSpec().aliases()));
commands.addAll(cmd.getCommandSpec().subcommands().keySet());
}
public boolean hasCommand(String command) {
return commands.contains(command);
}
public SystemCompleter compileCompleters() {
SystemCompleter out = new SystemCompleter();
// with original completer...
// out.add(commands, new PicocliJLineCompleter(cmd.getCommandSpec()));
// return out;
for (String s: commands) {
CommandSpec spec = cmd.getSubcommands().get(s).getCommandSpec();
List<String> options = new ArrayList<>();
Map<String,List<String>> optionValues = new HashMap<>();
for (OptionSpec o: spec.options()) {
List<String> values = new ArrayList<>();
if (o.completionCandidates() != null) {
o.completionCandidates().forEach(v -> values.add(v));
}
if (o.arity().max() == 0) {
options.addAll(Arrays.asList(o.names()));
} else {
for (String n: o.names()) {
optionValues.put(n, values);
}
}
}
// TODO positional parameter completion
// JLine OptionCompleter need to be improved with option descriptions and option value completion,
// now it completes only strings.
if (options.isEmpty() && optionValues.isEmpty()) {
out.add(s, new ArgumentCompleter(new StringsCompleter(s), NullCompleter.INSTANCE));
} else {
out.add(s, new ArgumentCompleter(new StringsCompleter(s)
, new OptionCompleter(NullCompleter.INSTANCE, optionValues, options, 1)));
}
}
return out;
}
public CmdDesc commandDescription(String command) {
CommandSpec spec = cmd.getSubcommands().get(command).getCommandSpec();
Help cmdhelp= new picocli.CommandLine.Help(spec);
List<AttributedString> main = new ArrayList<>();
Map<String, List<AttributedString>> options = new HashMap<>();
String synopsis = AttributedString.stripAnsi(spec.usageMessage().sectionMap().get("synopsis").render(cmdhelp).toString());
main.add(HelpException.highlightSyntax(synopsis.trim(), HelpException.defaultStyle()));
// using JLine help highlight because the statement below does not work well...
// main.add(new AttributedString(spec.usageMessage().sectionMap().get("synopsis").render(cmdhelp).toString()));
for (OptionSpec o: spec.options()) {
String key = Arrays.stream(o.names()).collect(Collectors.joining(" "));
List<AttributedString> val = new ArrayList<>();
for (String d: o.description()) {
val.add(new AttributedString(d));
}
if (val.isEmpty()) {
val.add(new AttributedString("")); // in order to avoid IndexOutOfBoundsException
// need to be fixed in JLine
}
if (o.arity().max() > 0 && key.matches(".*[a-zA-Z]{2,}$")) {
key += "=" + o.paramLabel();
}
options.put(key, val);
}
return new CmdDesc(main, ArgDesc.doArgNames(Arrays.asList("")), options);
}
}

View File

@@ -2,30 +2,44 @@ package picocli.shell.jline3.example;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import org.jline.reader.Completer;
import org.jline.builtins.Builtins;
import org.jline.builtins.Completers.SystemCompleter;
import org.jline.builtins.Options.HelpException;
import org.jline.builtins.Widgets.CmdDesc;
import org.jline.builtins.Widgets.CmdLine;
import org.jline.builtins.Widgets.TailTipWidgets;
import org.jline.builtins.Widgets.TailTipWidgets.TipType;
import org.jline.reader.EndOfFileException;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.EndOfFileException;
import org.jline.reader.UserInterruptException;
import org.jline.reader.MaskingCallback;
import org.jline.reader.ParsedLine;
import org.jline.reader.Parser;
import org.jline.reader.UserInterruptException;
import org.jline.reader.impl.DefaultParser;
import org.jline.reader.impl.LineReaderImpl;
import org.jline.terminal.TerminalBuilder;
import org.jline.terminal.Terminal;
import org.jline.reader.MaskingCallback;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParentCommand;
import picocli.shell.jline3.PicocliJLineCompleter;
import picocli.shell.jline3.PicocliCommands;
/**
* Example that demonstrates how to build an interactive shell with JLine3 and picocli.
* @since 3.9
* @since 4.1.2
*/
public class Example {
@@ -40,8 +54,8 @@ public class Example {
PrintWriter out;
CliCommands() {}
public void setReader(LineReader reader){
public void setReader(LineReader reader){
this.reader = (LineReaderImpl)reader;
out = reader.getTerminal().writer();
}
@@ -93,18 +107,59 @@ public class Example {
}
}
private static class DescriptionGenerator {
Builtins builtins;
PicocliCommands picocli;
public DescriptionGenerator(Builtins builtins, PicocliCommands picocli) {
this.builtins = builtins;
this.picocli = picocli;
}
CmdDesc commandDescription(CmdLine line) {
CmdDesc out = null;
switch (line.getDescriptionType()) {
case COMMAND:
String cmd = Parser.getCommand(line.getArgs().get(0));
if (builtins.hasCommand(cmd)) {
out = builtins.commandDescription(cmd);
} else if (picocli.hasCommand(cmd)) {
out = picocli.commandDescription(cmd);
}
break;
default:
break;
}
return out;
}
}
public static void main(String[] args) {
try {
// set up the completion
// set up jline built-in commands
Path workDir = Paths.get("");
Builtins builtins = new Builtins(workDir, null, null);
builtins.rename(org.jline.builtins.Builtins.Command.TTOP, "top");
builtins.alias("zle", "widget");
builtins.alias("bindkey", "keymap");
SystemCompleter systemCompleter = builtins.compileCompleters();
// set up picocli commands
CliCommands commands = new CliCommands();
CommandLine cmd = new CommandLine(commands);
PicocliCommands picocliCommands = new PicocliCommands(workDir, cmd);
systemCompleter.add(picocliCommands.compileCompleters());
systemCompleter.compile();
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.completer(new PicocliJLineCompleter(cmd.getCommandSpec()))
.completer(systemCompleter)
.parser(new DefaultParser())
.variable(LineReader.LIST_MAX, 50) // max tab completion candidates
.build();
builtins.setLineReader(reader);
commands.setReader(reader);
DescriptionGenerator descriptionGenerator = new DescriptionGenerator(builtins, picocliCommands);
new TailTipWidgets(reader, descriptionGenerator::commandDescription, 5, TipType.COMPLETER);
String prompt = "prompt> ";
String rightPrompt = null;
@@ -113,14 +168,29 @@ public class Example {
while (true) {
try {
line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
if (line.matches("^\\s*#.*")) {
continue;
}
ParsedLine pl = reader.getParser().parse(line, 0);
String[] arguments = pl.words().toArray(new String[0]);
new CommandLine(commands).execute(arguments);
String command = Parser.getCommand(pl.word());
if (builtins.hasCommand(command)) {
builtins.execute(command, Arrays.copyOfRange(arguments, 1, arguments.length)
, System.in, System.out, System.err);
} else {
new CommandLine(commands).execute(arguments);
}
} catch (HelpException e) {
HelpException.highlight(e.getMessage(), HelpException.defaultStyle()).print(terminal);
} catch (UserInterruptException e) {
// Ignore
} catch (EndOfFileException e) {
return;
}
} catch (Exception e) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(e.getMessage(), AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
asb.toAttributedString().println(terminal);
}
}
} catch (Throwable t) {
t.printStackTrace();

View File

@@ -2,12 +2,12 @@ rootProject.name = 'picocli'
include 'picocli-groovy'
include 'picocli-examples'
include 'picocli-shell-jline2'
include 'picocli-shell-jline3'
include 'picocli-codegen'
if (org.gradle.api.JavaVersion.current().isJava8Compatible()) {
include 'picocli-annotation-processing-tests'
include 'picocli-spring-boot-starter'
include 'picocli-shell-jline3'
} else {
println("Excluding module picocli-annotation-processing-tests from the build: they require Java 8 but we have Java version ${org.gradle.api.JavaVersion.current()}")
}