Developing a CLI Application With Spring Shell

agency04-novi-logo
Agency04
  • Date Published
  • Categories Blog, Guide, Tutorial
  • Reading Time 13-Minute Read

Part one of our complete tutorial where I will walk you through the steps of creating a simple command-line interpreter (CLI) application using Spring Shell.

The first application I ever wrote was my personal library management software. I did it in the late ’90s, and it was written as a terminal application in C (back then, for me the most glamorous programming language — Unix was written in it!). Since then, I have written literally tens of thousands of lines of code (in dozen of languages) and today I am mainly involved in the development of enterprise-grade IT solutions using languages and tools from the Java ecosystem (having Spring framework as the foundation) with some kind of single-page-application (SPA) serving as the front-end.

That said, my heart still skips a beat when I see a nice terminal application.

In this series of blog posts, I will walk you through the steps of creating a simple command-line interpreter (CLI) application using one of the less known Spring projects: Spring Shell.


Looking for a digital agency? Post a Project


About Spring Shell

As the authors of the Spring Shell library say on their web site:

“Not all applications need a fancy web user interface! Sometimes, interacting with an application using an interactive terminal is the most appropriate way to get things done.

Spring Shell aims to fill in this gap and provide Spring users with a library designed to support the development of CLI applications using all familiar concepts and design patterns (DI and IoC for example) from the Spring framework.

At the time of writing of this article, Spring Shell is in its second incarnation (version 2.0.1 is the latest release). This version represents a complete rewrite of the previous 1.2.x version, adjusted to the changes introduced in the Spring ecosystem with the release of Spring Boot 2.x.

About This Blog Post Series

This series of blog posts is an extension to the getting started guide, in which we go a step further and cover some of the base building blocks of CLI applications not addressed in the reference documentation. This first blog post will lead you through the process of setting up and configuring a simple demo application and will also cover the following topics:

  • base customization of the CLI application
  • conveying contextual information to the user through the use of colored messages

In the next posts, we will tackle some of the more complex topics such as:

  • Capturing user input (free text, passwords, input from the list of select values, etc)
  • Displaying operation progress information (spinners, counter, progress bars)
  • Displaying data as tables using Spring Shell built-in util classes
  • Integrating Spring Shell and Spring Security

Generate and Configure Spring-Boot/Gradle Project

The simplest way to start a new Spring-based project is to use Spring Initializr. To do so open a browser of your choice and navigate to https://start.spring.io/

Fill in the form by entering the defaults, as displayed below:

  • Select: Gradle Project and Java as the project language
  • Choose: Spring Boot 2.1.3 (or the latest version at the moment)
  • Under dependencies enter “spring shell” and press enter

Finally, press, “Generate Project”, download the zip file, unpack it and import the project into your favorite IDE (IntelliJ Idea, VSS Code, Eclipse, …).

Now we are ready to make some changes in the build file. First of all, lets upgrade Spring Shell library to its latest RELEASE (2.0.1).

In the generated build.gradle file find the following block of code:

dependencies {
implementation ‘org.springframework.shell:spring-shell-starter:2.0.0.RELEASE’
testImplementation ‘org.springframework.boot:spring-boot-starter-test’
}

and change it to look like this:

dependencies {
implementation ‘org.springframework.shell:spring-shell-starter:2.0.1.RELEASE’
testImplementation ‘org.springframework.boot:spring-boot-starter-test’
}

Finally, delete the integration tests found in `src/test/java/` folder generated by Spring Initializr (see this link for a detailed explanation), and execute the following command from your terminal inside the root clidemo/ folder in order to compile and build the application:

./gradlew clean build

If all went well, start the application by running:

java -jar build/libs/clidemo-0.0.1-SNAPSHOT.jar

And soon, you should see the familiar output of a SpringBoot application starting:

CLI application started

The last line says that our application is now ready to accept our input. At this point just type in ”help” to see the list of predefined commands available by default in a Spring Shell application, and you should see an output similar to this one:

shell:>help
AVAILABLE COMMANDS
Built-In Commands
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.
shell:>

Voila, we have a nice CLI application running with set of available (built-in) commands.

Customizing Our Application

Banner and log level can be customized to suit our specific needs same as in any other Spring Boot application (please refer to the official Spring Boot docs for this). Spring Shell CLI applications, however, offer some specific customization options, the most notable one being the ability to adjust the shell prompt. As can be seen from the examples above, the application greets the users with a default prompt message: “shell:>

We will change this message into “cli-demo:>

In order to do so, we need to configure our Spring application with a Bean implementing the PromptProvider interface:

public interface PromptProvider {
AttributedString getPrompt();
}

In the package com.ag04.clidemo.shell simply add a Java class named ClidemoPromptProvider with the following content:

package com.ag04.clidemo.shell;

import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.springframework.shell.jline.PromptProvider;
import org.springframework.stereotype.Component;

@Component
public class ClidemoPromptProvider implements PromptProvider {

@Override
public AttributedString getPrompt() {
return new AttributedString(“CLI-DEMO:>”,
AttributedStyle.DEFAULT.foreground(AttributedStyle.BLUE)
);
}
}

In this simple implementation, we have changed the prompt from shell:>to CLI-DEMO:> and have also changed its color to blue. This class is annotated with the @Component annotation so that Spring can discover and register this bean during start up. No further changes are needed, simply, build and rerun the application and you should be greeted with a new custom prompt.

Real-life PromptProvider implementations can be far more complex, than this simple example. They can contain complex application logic that changes the prompt to reflect, for example, user status (signed in or not), application state, current folder path, etc.

Now its time to add some functionality to our CLI application.

Hello World: Implement Your First Command

As always it‘s good to start with a simple Hello World example. In our case, it will be an ”echo” command that prints a greeting message to a person whose name was supplied as a parameter to the command.

Commands are the core of the Spring Shell library, they are equivalent to the Controller object in the classical Web or Rest API application. In short, Commands are the higher-level components responsible for processing user input.

Start by creating the following class named EchoCommand.java in the package: com.ag04.clidemo.command

package com.ag04.clidemo.command;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class EchoCommand {

@ShellMethod(“Displays greeting message to the user whose name is supplied”)
public String echo(@ShellOption({“-N”, “–name”}) String name) {
return String.format(“Hello %s! You are running spring shell cli-demo.”, name);
}

}

Try it out! Build and rerun your app and type: “echo Domagoj”, you should see the following output.

shell:>echo Domagoj
Hello Domagoj! You are running spring shell cli-demo.
shell:>

HINT: Execute the help command again and you should see our new echocommand listed, with the appropriate command description.

shell:>help
AVAILABLE COMMANDS
Built-In Commands
clear: Clear the shell screen.
exit, quit: Exit the shell.
help: Display help about available commands.
history: Display or save the history of previously run commands
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.Echo Command
echo: Displays greeting message to the user whose name is supplied

This is a result of the @ShellMethod and @ShellOption annotations added to the EchoCommand, which provide the necessary information to Spring Shell. For more information concerning the options on customizing your commands please consult the official Spring Shell documentation.

Now that we have our first command, its time to make our application output a little bit more attractive/engaging by adding some colors to it.

Shellhelper: Adding Some Color to the Output

At the moment our application uses the same (standard) terminal color for displaying the entire echo message. What if we would like to display the first part of the message ”Hello %s!” in a different color, say green? One of the great novelties made by Bootstrap was the introduction of the practice to convey meaning to the users by using colors, with a handful of color utility CSS classes available to the developers. Thus came into existence the use of success, info, danger and other familiar Bootstrap CSS styles. Lets attempt to emulate this practice in our CLI application.

To do so we will create a ShellHelper util class that we will use to color the output of our commands. First, in the package com.ag04.clidemo.shellcreate a new Java enumeration of the name PromptColor:

package com.ag04.clidemo.shell;

public enum PromptColor {
BLACK(0),
RED(1),
GREEN(2),
YELLOW(3),
BLUE(4),
MAGENTA(5),
CYAN(6),
WHITE(7),
BRIGHT(8);

private final int value;

PromptColor(int value) {
this.value = value;
}

public int toJlineAttributedStyle() {
return this.value;
}
}

With this enumeration in place we can proceed and define which colors will be used for which “style” of message in our application. In the application.properties file (empty at the moment) found in the src/resources/ folder, add the following lines:

# — contextual colors — — — — — — — — — — — — — — — — — — — —
shell.out.info=CYAN
shell.out.success=GREEN
shell.out.warning=YELLOW
shell.out.error=RED

Now, in the same package crate a Java class named ShellHelper, with the following code:

package com.ag04.clidemo.shell;

import org.jline.terminal.Terminal;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.annotation.Value;

public class ShellHelper {

@Value(“${shell.out.info}”)
public String infoColor;

@Value(“${shell.out.success}”)
public String successColor;

@Value(“${shell.out.warning}”)
public String warningColor;

@Value(“${shell.out.error}”)
public String errorColor;

private Terminal terminal;

public ShellHelper(Terminal terminal) {
this.terminal = terminal;
}

public String getColored(String message, PromptColor color) {
return (new AttributedStringBuilder()).append(message, AttributedStyle.DEFAULT.foreground(color.toJlineAttributedStyle())).toAnsi();
}

public String getInfoMessage(String message) {
return getColored(message, PromptColor.valueOf(infoColor));
}

public String getSuccessMessage(String message) {
return getColored(message, PromptColor.valueOf(successColor));
}

public String getWarningMessage(String message) {
return getColored(message, PromptColor.valueOf(warningColor));
}

public String getErrorMessage(String message) {
return getColored(message, PromptColor.valueOf(errorColor));
}
}

Now we can use this class in our commands to display entire, or just part of the, message in particular style. Before we do this, we need to make this class available to our application. Add Spring configuration class SpringShellConfig in the package com.ag04.clidemo.config:

package com.ag04.clidemo.config;

import com.ag04.clidemo.shell.ShellHelper;
import org.jline.terminal.Terminal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

@Configuration
public class SpringShellConfig {

@Bean
public ShellHelper shellHelper(@Lazy Terminal terminal) {
return new ShellHelper(terminal);
}

}

Finally, change EchoCommand so that it uses this util class to construct the returning String:

package com.ag04.clidemo.command;

import com.ag04.clidemo.shell.ShellHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellOption;

@ShellComponent
public class EchoCommand {

@Autowired
ShellHelper shellHelper;

@ShellMethod(“Displays greeting message to the user whose name is supplied”)
public String echo(@ShellOption({“-N”, “–name”}) String name) {
String output = shellHelper.getSuccessMessage(String.format(“Hello %s!”, name));
return output.concat(” You are running spring shell cli-demo.”);
}
}

To see all the applied changes in action, rebuild and execute clidemo again. The output of the echo command should now consist of the message displayed in two colors, as visible in the image below.

Echo Command with Colored Output

In the current implementation, our echo command returns a String (as a result of command execution) which is then also displayed in the terminal, yet there are cases when we would also like to print some informational messages to the user during the execution of our commands.

To support this, lets expand ShellHelper class and add several print methods as shown in the code snippet below.

/**
* Print message to the console in the default color.
*
* @param message message to print
*/
public void print(String message) {
print(message, null);
}

/**
* Print message to the console in the success color.
*
* @param message message to print
*/
public void printSuccess(String message) {
print(message, PromptColor.valueOf(successColor));
}

/**
* Print message to the console in the info color.
*
* @param message message to print
*/
public void printInfo(String message) {
print(message, PromptColor.valueOf(infoColor));
}

/**
* Print message to the console in the warning color.
*
* @param message message to print
*/
public void printWarning(String message) {
print(message, PromptColor.valueOf(warningColor));
}

/**
* Print message to the console in the error color.
*
* @param message message to print
*/
public void printError(String message) {
print(message, PromptColor.valueOf(errorColor));
}

/**
* Generic Print to the console method.
*
* @param message message to print
* @param color (optional) prompt color
*/
public void print(String message, PromptColor color) {
String toPrint = message;
if (color != null) {
toPrint = getColored(message, color);
}
terminal.writer().println(toPrint);
terminal.flush();
}

Shellhelper Print Methods

With these tools at hand, we can now modify our echo command and demonstrate the capability to print contextual messages, we just added. Thus change the body of the echo method to look as in the code snippet below.

@ShellMethod(“Displays greeting message to the user whose name is supplied”)
public String echo(@ShellOption({“-N”, “–name”}) String name) {
String message = String.format(“Hello %s!”, name);
shellHelper.print(message.concat(” (Default style message)”));
shellHelper.printError(message.concat(” (Error style message)”));
shellHelper.printWarning(message.concat(” (Warning style message)”));
shellHelper.printInfo(message.concat(” (Info style message)”));
shellHelper.printSuccess(message.concat(” (Success style message)”));

String output = shellHelper.getSuccessMessage(message);
return output.concat(” You are running spring shell cli-demo.”);
}

Final echo command, with demonstration of all contextual message styles.

Finally, rebuild and execute clidemo once more and you should see the following output:

 

With this, we conclude the first part. In the next part, we will expand this sample skeleton application with examples of how to capture user input.

HINT: Both PromptColor and ShellHelper classes are written in such a way that they can be easily taken out of this project and put in a separate shell-utils library (jar) for re-use in various different CLI projects.

Sample Code:

The entire source code for this tutorial is available at the following GitHub repository:

dmadunic/clidemo

Additional resources:

Spring Shell project site:

Spring Shell official documentation:

Thank you for reading! I do hope you enjoyed it and please share if you did.