Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add picocli-shell-JLine3 module #574

Merged
merged 5 commits into from
Dec 25, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ hamcrestCoreVersion = 1.3
ivyVersion = 2.4.0
jansiVersion = 1.15
jlineVersion = 2.14.6
jline3Version = 3.9.0
junitDepVersion = 4.11
junitVersion = 4.12

Expand Down
155 changes: 155 additions & 0 deletions picocli-shell-jline3/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<p align="center"><img src="https://picocli.info/images/logo/horizontal-400x150.png" alt="picocli" height="150px"></p>


# Picocli Shell JLine3 - build interactive shells with ease

Picocli Shell JLine3 contains components and documentation for building
interactive shell command line applications with JLine 3 and picocli.

JLine and picocli complement each other very well and have little or none functional overlap.

JLine provides interactive shell functionality but has no built-in command line parsing functionality.
What it does provide is a tokenizer for splitting a single command line String into an array of command line argument Strings.

Given an array of Strings, picocli can execute a command or subcommand.
Combining these two libraries makes it easy to build powerful interactive shell applications.


## About JLine 3

[JLine 3](https://github.com/jline/jline3) is a well-known library for building interactive shell applications.
From the JLine [web site](https://github.com/jline/jline.github.io/blob/master/index.md):

> JLine is a Java library for handling console input. It is similar in functionality to [BSD editline](http://www.thrysoee.dk/editline/) and [GNU readline](http://www.gnu.org/s/readline/) but with additional features that bring it in par with [ZSH line editor](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html).

## About picocli
Picocli is a Java command line parser with both an annotations API and a programmatic API, featuring usage help with ANSI colors, autocomplete and nested subcommands.

The picocli user manual is [here](https://picocli.info), and the GitHub project is [here](https://github.com/remkop/picocli).

## Command Completer
`PicocliJLineCompleter` is a small component that generates completion candidates to allow users to
get command line TAB auto-completion for a picocli-based application running in a JLine 2 shell.

## Example

```java
package picocli.shell.jline3.example;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
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 picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.ParentCommand;
import picocli.shell.jline3.PicocliJLineCompleter;

/**
* Example that demonstrates how to build an interactive shell with JLine3 and picocli.
* @since 3.7
*/
public class Example {

/**
* Top-level command that just prints help.
*/
@Command(name = "", description = "Example interactive shell with completion",
footer = {"", "Press Ctl-D to exit."},
subcommands = {MyCommand.class, ClearScreen.class})
static class CliCommands implements Runnable {
LineReaderImpl reader;
PrintWriter out;

CliCommands() {}

public void setReader(LineReader reader){
this.reader = (LineReaderImpl)reader;
out = reader.getTerminal().writer();
}

public void run() {
out.println(new CommandLine(this).getUsageMessage());
}
}

/**
* A command with some options to demonstrate completion.
*/
@Command(name = "cmd", mixinStandardHelpOptions = true, version = "1.0",
description = "Command with some options to demonstrate TAB-completion" +
" (note that enum values also get completed)")
static class MyCommand implements Runnable {
@Option(names = {"-v", "--verbose"})
private boolean[] verbosity = {};

@Option(names = {"-d", "--duration"})
private int amount;

@Option(names = {"-u", "--timeUnit"})
private TimeUnit unit;

@ParentCommand CliCommands parent;

public void run() {
if (verbosity.length > 0) {
parent.out.printf("Hi there. You asked for %d %s.%n", amount, unit);
} else {
parent.out.println("hi!");
}
}
}

/**
* Command that clears the screen.
*/
@Command(name = "cls", aliases = "clear", mixinStandardHelpOptions = true,
description = "Clears the screen", version = "1.0")
static class ClearScreen implements Callable<Void> {

@ParentCommand CliCommands parent;

public Void call() throws IOException {
parent.reader.clearScreen();
return null;
}
}

public static void main(String[] args) {
try {
// set up the completion
CliCommands commands = new CliCommands();
CommandLine cmd = new CommandLine(commands);
Terminal terminal = TerminalBuilder.builder().build();
LineReader reader = LineReaderBuilder.builder()
.terminal(terminal)
.completer(new PicocliJLineCompleter(cmd.getCommandSpec()))
.parser(new DefaultParser())
.build();
commands.setReader(reader);
String prompt = "prompt> ";
String rightPrompt = null;

// start the shell and process input until the user quits with Ctl-D
String line;
while (true) {
line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
CommandLine.run(commands, line.split("\\s+"));
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
```
84 changes: 84 additions & 0 deletions picocli-shell-jline3/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
plugins {
id 'java'
id 'distribution'
id 'maven-publish'
}

group 'info.picocli'
description 'Picocli Shell JLine2 - easily build interactive shell applications with JLine 2 and picocli.'
version "$projectVersion"
sourceCompatibility = 1.5

dependencies {
compile rootProject
compile "org.jline:jline:$jline3Version"
testCompile "junit:junit:$junitVersion"
}

jar {
manifest {
attributes 'Specification-Title' : 'Picocli Shell JLine2',
'Specification-Vendor' : 'Remko Popma',
'Specification-Version' : version,
'Implementation-Title' : 'Picocli Shell JLine2',
'Implementation-Vendor' : 'Remko Popma',
'Implementation-Version': version,
'Automatic-Module-Name' : 'info.picocli.shell.jline2'
}
}

ext {
bintrayBaseUrl = 'https://api.bintray.com/maven'
bintrayRepository = 'picocli'
bintrayPackage = 'picocli-shell-jline2'
bintrayUsername = System.getenv('BINTRAY_USER')
bintrayApiKey = System.getenv('BINTRAY_KEY')
}
publishing {
publications {
plugin(MavenPublication) {
from components.java
artifact sourcesJar
artifact testJar
artifact testSourcesJar
artifact javadocJar
pom.withXml {
def root = asNode()
root.appendNode('packaging', 'jar')
root.appendNode('name', 'picocli-shell-jline2')
root.appendNode('description', description)
root.appendNode('url', 'http://picocli.info')
root.appendNode('inceptionYear', '2018')

def license = root.appendNode('licenses').appendNode('license')
license.appendNode('name', 'The Apache Software License, version 2.0')
license.appendNode('url', 'http://www.apache.org/licenses/LICENSE-2.0.txt')
license.appendNode('distribution', 'repo')

def developer = root.appendNode('developers').appendNode('developer')
developer.appendNode('id', 'rpopma')
developer.appendNode('name', 'Remko Popma')
developer.appendNode('email', 'rpopma@apache.org')

def scm = root.appendNode('scm')
scm.appendNode('connection', 'scm:git:https://github.com/remkop/picocli.git')
scm.appendNode('developerConnection', 'scm:git:ssh://github.com:remkop/picocli.git')
scm.appendNode('url', 'https://github.com/remkop/picocli/tree/master')
}
}
}
repositories {
maven {
name 'myLocal'
url "file://$rootDir/../repo/$bintrayUsername"
}
maven {
name 'Bintray'
url "$bintrayBaseUrl/$bintrayUsername/$bintrayRepository/$bintrayPackage"
credentials {
username = bintrayUsername
password = bintrayApiKey
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package picocli.shell.jline3;

import org.jline.reader.LineReader;
import org.jline.reader.Completer;
import org.jline.reader.Candidate;
import org.jline.reader.ParsedLine;
import picocli.AutoComplete;
import picocli.CommandLine.Model.CommandSpec;

import java.util.List;
import java.util.ArrayList;
import java.lang.CharSequence;

/**
* Implementation of the JLine 3 {@link Completer} interface that generates completion
* candidates for the specified command line based on the {@link CommandSpec} that
* this {@code PicocliJLineCompleter} was constructed with.
*
* @since 3.7
*/
public class PicocliJLineCompleter implements Completer {
private final CommandSpec spec;

/**
* Constructs a new {@code PicocliJLineCompleter} for the given command spec.
* @param spec the command specification to generate completions for. Must be non-{@code null}.
*/
public PicocliJLineCompleter(CommandSpec spec) {
if (spec == null) { throw new NullPointerException("spec"); }
this.spec = spec;
}

/**
* Populates <i>candidates</i> with a list of possible completions for the <i>command line</i>.
*
* The list of candidates will be sorted and filtered by the LineReader, so that
* the list of candidates displayed to the user will usually be smaller than
* the list given by the completer. Thus it is not necessary for the completer
* to do any matching based on the current buffer. On the contrary, in order
* for the typo matcher to work, all possible candidates for the word being
* completed should be returned.
*
* @param reader The line reader
* @param line The parsed command line
* @param candidates The {@link List} of candidates to populate
*/
//@Override
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
// let picocli generate completion candidates for the token where the cursor is at
String[] words = new String[line.words().size()];
words = line.words().toArray(words);
List<CharSequence> cs = new ArrayList<CharSequence>();
AutoComplete.complete(spec,
words,
line.wordIndex(),
0,
line.cursor(),
cs);
for(CharSequence c: cs){
candidates.add(new Candidate((String)c));
}
}
}
Loading