When we run this code now, we’ll get output like what's shown in Listing 6.
Listing 6. Output of the describe command
> describe
/.
pom.xml
/src
/main
/java
/com
/infoworld
App.java
...
That looks a little dull, so let's spruce it up, as shown in Listing 7.
Listing 7. The getDirectoryHierarchy() with colors
public static String getDirectoryHierarchy(Path path) {
StringBuilder sb = new StringBuilder();
try (Stream<Path> paths = Files.walk(path)) {
paths.sorted()
.forEach(
p -> {
int depth = path.relativize(p).getNameCount();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
String fileName = p.getFileName().toString();
if (p.toFile().isDirectory()) {
fileName = "\033[32m" + “/” + fileName + "\033[0m";
} else {
fileName = "\033[34m" + fileName + "\033[0m";
}
sb.append(fileName).append("\n");
});
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
Now the directories will be green and files will be blue, as shown in Figure 1.
Figure 1. A directory listing with color
The color is relying on the Jansi library dependency that we added in pom.xml
, which supports escape codes to denote the font color at the terminal. We use the escape codes \033[32m
and \033[34m
to change colors for a moment, then reset to the default with \033[0m
.
The \033
is the “escape character,” which tells the system that a control code follows, which tells the terminal how to format the text. The [32m
and [34m
are green and blue colors, respectively, and [0m
clears the formatting. The Jansi library lets your Java program use all these codes in a cross-platform way, without worrying about the underlying operating system. You can learn more about the history of ANSI escape codes here, and more about the Jansi library here. (Jansi's motto is “Eliminating boring console output.")
Create and add user menus
Now that we have the describe
command working, let’s take a crack at create
. The first thing we need to do is offer a choice to the user of what kind of project to create: Java, JavaScript, or Python. If the user picks Java, we’ll let them specify a couple of features to add: database and REST API.
Listing 8 has a simple version of using JLine to handle the create
command.
Listing 8. Modified REPL with the create command
package com.infoworld;
// ...other imports remain the same
import java.util.stream.Stream;
// ...
} else if (line.equalsIgnoreCase("create")) {
String choice = reader.readLine("What kind of project do you want to create? (java, javascript, python) ");
if (choice.equalsIgnoreCase("java")) {
createJavaProject(reader);
} else if (choice.equalsIgnoreCase("javascript")) {
System.out.println("TBD");
} else if (choice.equalsIgnoreCase("python")) {
System.out.println("TBD");
} else {
System.out.println("Unsupported choice: " + choice);
}
} else {
System.out.println("Unknown command: " + line);
}
}
}
public static void createJavaProject(LineReader reader) {
boolean addDatabase = reader.readLine("Add Database Support? (y/n) ").equalsIgnoreCase("y");
boolean addRest = reader.readLine("Add REST API? (y/n) ").equalsIgnoreCase("y");
//TODO: Create project here
System.out.println("Java project created with Database: " + addDatabase + ", REST: " + addRest);
}
//...
}
Listing 8 is pretty straightforward. We prompt for the type of project to create, and if the user enters java
we send them to the createJavaProject()
method. Notice we share the reader object as an argument—the JLine project encourages reusing the reader (and in fact testing reveals that spawning a new reader while another is running causes a dumb terminal to be created, at least on Debian).
The createJavaProject()
chains together two questions, about adding a database and adding a REST API. We just output the user's selections. This use of the create
command demonstrates just one approach to handling multi-select functionality. The interaction looks like what's shown in Listing 9.
Listing 9. Using the create command to chain together two questions
> create
What kind of project do you want to create? (java, javascript, python) java
Add Database Support? (y/n) y
Add REST API? (y/n) y
Java project created with Database: true, REST: true
> exit
This works, but it’s a bit primitive. With the ConsoleUI project, we can implement some much nicer CLI-style menus. ConsoleUI supports the whole range of UI widgets like select, multiselect, and radio buttons with arrow-key navigation and ANSI icons. We’ll just get a quick flavor of it here by implementing the create
command with ConsoleUI.
First, we add the ConsoleUI dependency, as shown in Listing 10.
Listing 10. Adding the ConsoleUI dependency
<dependency>
<groupId>de.codeshelf.consoleui</groupId>
<artifactId>consoleui</artifactId>
<version>0.0.13</version>
</dependency>
Listing 11 gives a quick example of adding the create menus. ConsoleUI gives you a largely fluent API that you can chain together to form the prompts you need. To access the results of the user’s selections, you have to know how the framework models them (details here).
Listing 11. Fancy menus
package com.infoworld;
//...
// consoleui
import de.codeshelf.consoleui.elements.ConfirmChoice;
import de.codeshelf.consoleui.prompt.ConfirmResult;
import de.codeshelf.consoleui.prompt.ConsolePrompt;
import de.codeshelf.consoleui.prompt.PromtResultItemIF;
import de.codeshelf.consoleui.prompt.builder.PromptBuilder;
import java.io.IOException;
import java.util.HashMap;
import static org.fusesource.jansi.Ansi.ansi;
import org.fusesource.jansi.AnsiConsole;
// ...
public static void createCommand() {
AnsiConsole.systemInstall();
System.out.println(ansi().render("@|red,italic Hello|@ @|green World|@\n@|reset Welcome to the project creator.|@"));
try {
ConsolePrompt prompt = new ConsolePrompt();
PromptBuilder promptBuilder = prompt.getPromptBuilder();
promptBuilder.createListPrompt().name("projectType").message("What kind of project do you want to create? (java, javascript, python) ") .newItem().text("java").add().newItem("javascript").text("javascript").add()
.newItem("python").text("python").add().addPrompt();
HashMap<String, ? extends PromtResultItemIF> result = prompt.prompt(promptBuilder.build());
String projectType = ((de.codeshelf.consoleui.prompt.ListResult)result.get("projectType")).getSelectedId();
System.out.println("result = " + ((de.codeshelf.consoleui.prompt.ListResult)result.get("projectType")).getSelectedId());
System.out.println("Building a new project: " + projectType);
if (projectType== "java") {
prompt = new ConsolePrompt();
promptBuilder = prompt.getPromptBuilder();
promptBuilder.createCheckboxPrompt()
.name("features")
.message("Please select features to add:")
.newSeparator("Databases:")
.add()
.newItem().name("mongo").text("MongoDB").add()
.newItem("mysql").text("MySQL").add()
.newItem("db2").text("DB2").disabledText("Sorry, discontinued.").add()
.newSeparator().text("Rest API").add()
.newItem("spring-rest").text("Spring REST").check().add()
.newItem("micronaut").text("Micronaut").add()
.addPrompt();
result = prompt.prompt(promptBuilder.build());
System.out.println("result = " + result);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
TerminalFactory.get().restore();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//...
}
Now you’ll see a very nice console UI, like the one shown in Figure 2.
Figure 2. The updated console UI.
Conclusion
This was a quick tour demonstrating some of the features of the JLine and ConsoleUI libraries. Together, they make many powerful CLI and REPL features simple to implement in Java.