mirror of
https://github.com/jlengrand/picocli.git
synced 2026-03-10 00:31:17 +00:00
Bump JLine to 3.13.2
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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()}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user