Developing CLI application with Spring Shell (part 2)

Domagoj Madunić
Agency04
Published in
8 min readMay 7, 2019

In the previous post we have created the skeleton of the sample CLI application with some helper classes added to support display of contextual messages to the user (similar to the Bootstrap’s contextual messages).

In this blog post we go a step further and cover the topic of interacting with a user in a CLI application. What we will concentrate on will be capturing of the user’s input in the form of: free text, passwords, an option from the list of values, etc.

What we will be building in this part?

For the purposes of this tutorial we will build a command that captures data necessary to create a new user. This command will interact with user and ask him to provide the following data for the new user:

  • Full name (free text)
  • password (hidden text)
  • gender (a choice from list of options)
  • superuser (input from list of available values — with default value)

NOTE: Spring Shell relies on JLine — a powerful Java library — and most of the examples provided in the rest of this post are mainly related to the use of JLine and are not Spring Shell specific.

The model we will use

Package com.ag04.clidemo.model contains CliUser class we will use to store user’s input and pass it on to user service for further processing.

Enumeration Gender is pretty basic:

Simple UserService interface, together with its mock implementation, one we will invoke to process user provided data is available in the package: com.ag04.clidemo.service, and defines the following contract:

With this model at hand we can now proceed, and implement a command that will collect user input and then invoke UserService.create() method.

Create new user

Lets start with creating a new Spring Shell command named UserCommand with just one method designed to create a new user.

After rebuilding and re-running application, invoking help command should provide output similar as bellow (listing also our new command among the list of available commands):

CLI-DEMO:>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
User Command
create-user: Create new user with supplied username

Available mock implementation of UserService.exists() method returns true for “admin” username. Now, invoke create-user command with admin as parameter and you should see the following output.

With all these pieces in place we can now start collecting user input and implementing create-user command.

Interacting with a user

Similar to the ShellHelper class we created in the previous post, in this post we will create a new InputReader class that will contain various helper methods for interacting with a user. The first method we need is the one that prompts the user to enter data in free format.

Listing bellow, contains our first implementation of InputReader class, with three methods designed to capture user data in free entry format:

Thank you note: This class is very much inspired by functionality of the ConsoleUserInput.class from the Spring Cloud Skipper Project (https://github.com/spring-cloud/spring-cloud-skipper/tree/master/spring-cloud-skipper-shell-commands).

These three methods provide us with the capability to ask a user for an input with the display of customized prompt message at the beginning of the line. Additionally we have an option to supply the default return value in case a user just pressed enter, and to mask user’s entered input (in case we need to capture sensitive data, ie password).

In order to be able to use this class we also need to add the following configuration lines to an existing SpringShellConfig class:

@Bean
public InputReader inputReader(@Lazy LineReader lineReader) {
return new InputReader(lineReader);
}

Now we can start implementing UserCommand.

Prompt user for full name and password

Now, lets modify UserCommand to resemble the code snippet bellow:

Here, we have added two blocks of code that capture user’s data. One that loops until user’s full name, containing at least one non space character, was entered, and the other one that prompts user to enter password, which masks user input (replacing his entry with default mask: *) and returns “secret” as default password value if user just presses enter.

Finally, the last block of code we added, prints out user’s entered data and invokes UserService.create() method. In the real life application user’s password would not be printed, but we have done it here for the purposes of debugging.

Now, try it out, (press enter when prompted for password) and the output should be similar to this:

Create user initial version

As can be seen, Gender property of CliUser is set to null and superuser property is set to false, which is fine since these are default values of CliUser. Now, lets improve InputReader and add support to allow user to chose one of the options from the list, and then expand UserCommand with the block of code that uses this functionality to capture user’s Gender data.

Prompt user to select one value from the list of available options

Now we are going to attempt something more ambitious, that is, to prompt a user to select one value from the list of values. Once implemented, we are going to use this capability to ask user to provide his gender in create-user command. As you could have noticed Gender enumeration defines three values. We will print each value, associate with each of them one character key, and ask user to enter one key as his choice.

What we will build, in the end will look as follows:

First, add ShellHelper as class property and change constructor of the InputReader util class as follows:

ShellHelper shellHelper;public InputReader(LineReader lineReader, ShellHelper shellHelper) {
this(lineReader, shellHelper, null);
}
public InputReader(LineReader lineReader, ShellHelper shellHelper, Character mask) {
this.lineReader = lineReader;
this.shellHelper = shellHelper;
this.mask = mask != null ? mask : DEFAULT_MASK;
}

Then, at the end of the InputReader class add the following methods:

Note: above implementation of selectFromList() method will also print default option value with different color style(info).

For this additions to work we also need to change the method that creates InputReader bean in SpringShellConfig class as follows:

@Bean
public InputReader inputReader(@Lazy LineReader lineReader, ShellHelper shellHelper) {
return new InputReader(lineReader, shellHelper);
}

Finally, modify the UserCommand by adding the following lines before print of user’s input:

// 3. read user's Gender ----------------------------------------------
Map<String, String> options = new HashMap<>();
options.put("M", Gender.MALE.name());
options.put("F", Gender.FEMALE.name() );
options.put("D", Gender.DIVERSE.name());
String genderValue = inputReader.selectFromList("Gender", "Please enter one of the [] values", options, true, null);
Gender gender = Gender.valueOf(options.get(genderValue.toUpperCase()));
user.setGender(gender);
// Print user's input ----------------------------------------------

Gender property has no default value so we supplied null as fourth argument to the selectFromList() method.

Rebuild, run clidemo and invoke create-user command to test newly implemented features!

Method selectFromList() is convenient for cases when number of available options is greater than 2 or 3, and we would like to print each on single line. Yet, in cases when there are only two options for user to choose from (for example: Y/N) it would be more suited to have a method in InputReader that will present a user with a single prompt line, consisting of all available options.

To do so we will now modify InputReader class and add the two following methods:

These methods provides previously described functionality, and similar to the previously created selectFromList() method, if default value is provided, option of that value will be printed with the info color style.

To see it in action, we will use this method to ask user, should newly created user be marked as superuser. In the UserCommand class after the block of code that asks user to provide Gender add the following lines of code:

// 4. Prompt for superuser attribute ------------------------------
String superuserValue = inputReader.promptWithOptions("New user is superuser", "N", Arrays.asList("Y", "N"));
if ("Y".equals(superuserValue)) {
user.setSuperuser(true);
} else {
user.setSuperuser(false);
}
// Print user's input ----------------------------------------------
...

As can be seen default value in this case is set to “N”, resulting in return of “N” by InputReader.promptWithOptions() method in case the user just pressed ENTER as its input.

Finally, running clidemo again should result in the following output:

Create new user method of our UserCommand is now finished. We collect all the necessary data from a user, map it to the CliUser object and pass it to UserService.create() method in order to create a new user.

Additionally, we could also add one more action to this method, prompting a user to confirm that provided data is correct, using InputReader.promptWithOptions() as described above, after entered data is printed out and before invocation of UserService.create() method.

Final fine tuning

Yet, one small issue remains to be addressed. As can be seen in the pictures above, user input values are displayed with red color. This is the result of using SpringShell auto-configured LineReader, which is configured to display all user’s input in red color unless a input matches on of the shell commands.

To avoid this behavior we need to configure our own LineReader and pass it to InputReader’s constructor.

Modify SpringShellConfig class method inputReader() to match the code snippet bellow:

Now all user’s input will be displayed with default color and bold style, as visible in the image bellow:

Final output of the implemented UserCommand

This concludes the second part of this series of posts. In the next part we will show you how to build progress bar component.

Rest of the series:

Part 1: Conveying contextual messages to the users in the CLI application

Part 2: Capturing user’s input in the CLI application

Part 3: Displaying the progress of CLI command execution with the use of counters, spinners and progress bars

Part 4: Displaying the data with the use of tables in a Spring Shell based CLI application

Part 5: Securing CLI application with Spring Security

Sample Code:

Entire source code for this tutorial is available at GitHub repository:

Additional resources:

Spring Shell project site:

https://projects.spring.io/spring-shell/

Spring Shell official documentation:

https://docs.spring.io/spring-shell/docs/current-SNAPSHOT/reference/htmlsingle/

https://docs.spring.io/spring-shell/docs/current/api/

Jline project site:

https://jline.github.io/

Keywords:

Java, Spring, Spring Shell, Spring boot, CLI, Terminal, JLine

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

--

--

Domagoj Madunić
Agency04

CEO and founder of Agency04 — Software development company based in Zagreb. Passionate about software development and 17th century Mediterranean history.