JavaでのCUI対話型ツールの実装、JLineによる複数行コマンド受付の対応
JavaでCUIによる対話型のツール(REPL)を作りたいときは、
以下のJLineを使うと簡単に実装する事が出来ます。
# ちなみにREPLというのは、Read-Eval-Print Loopの略で、
# コマンド受付→評価→表示を繰り返す、対話型処理のことです。
JLine | GitHub
https://github.com/jline/jline3
このエントリでは、JLineによる基本的なCUIの作り方と、
複数行コマンド受付の対応方法を示します。
JLineによるCUIの実装
まず、JLineによるCUIを実装します。
以下のbuild.gradle
、CuiSample.java
を作成します。
build.gradle
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '4.0.3'
}
repositories {
jcenter()
}
dependencies {
compile 'org.jline:jline:3.9.0'
}
jar {
manifest {
attributes 'Main-Class': 'CuiSample'
}
}
artifacts {
archives shadowJar
}
src/main/java/CuiSample.java
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
public class CuiSample {
public static void main(String[] args) throws Exception {
// JLine terminal の準備
Terminal terminal = TerminalBuilder.builder()
.system(true)
.build();
// Parser の準備
Parser p = new DefaultParser();
LineReader lineReader = LineReaderBuilder.builder()
.terminal(terminal)
.parser(p)
.build();
// REPL
while (true) {
String line = lineReader.readLine("input > ");
String[] exitCommands = {":exit"};
for (String cmd : exitCommands) {
if (line.contains(cmd)) {
return;
}
}
System.out.println(line);
}
}
}
ビルドします。
$ gradle build
実行すると、以下のようになります。
この実装では、入力されたコマンドをそのまま表示、:exit
でループを抜けるようにしています。
$ java -jar build/libs/jline-sample-all.jar
input > hello
hello
input > :exit
複数行コマンド受付の対応
上記で作成したCUIでは、改行が入力されるとコマンドの評価に入ります。
複数行のコマンドを受け付けたい場合は、以下のように、
「EofOnEscapedNewLine」を有効にすると、
複数行コマンドを入力できるようになります。
src/main/java/CuiSample.java の変更
// Parser の準備
DefaultParser p = new DefaultParser();
p.setEofOnEscapedNewLine(true);
LineReader lineReader = LineReaderBuilder.builder()
.terminal(terminal)
.parser(p)
.build();
実行すると、以下のようになります。
$ gradle build
$ java -jar build/libs/jline-sample-all.jar
input > hello \
> world
hello world
input > :exit
改行をESCAPEするのではなく、
SQLのCUIのように、「;」が入力された時にコマンドを確定させる。
ということをやりたい場合は、
標準のorg.jline.reader.impl.DefaultParser
では無く、
以下のように自前のParserを作って対応します。
src/main/java/MultilineParser.java
import org.jline.reader.EOFError;
import org.jline.reader.ParsedLine;
import org.jline.reader.impl.DefaultParser;
public class MultilineParser extends DefaultParser {
@Override
public ParsedLine parse(final String line, final int cursor, ParseContext context) {
ParsedLine pl = super.parse(line, cursor, context);
if (!line.trim().endsWith(";") && !line.startsWith(":")) {
throw new EOFError(-1, -1, "Without semicolon", "");
}
return pl;
}
}
src/main/java/CuiSample.java の変更
// Parser の準備
MultilineParser p = new MultilineParser();
LineReader lineReader = LineReaderBuilder.builder()
.terminal(terminal)
.parser(p)
.build();
MultilineParserでは、;
で終わる or :
で始まる時に、コマンドを確定させています。
parseメソッドで、EOFError
を投げればコマンドは未確定と扱われます。
実行すると、以下のようになります。
$ gradle build
$ java -jar build/libs/jline-sample-all.jar
input > hello
> world;
hello
world;
input > :exit
以上。