Appendix A: Appendix: Techical Introduction

This appendix contains information for developers and others who would like to know more about how Spring Shell works internally and what its design decisions are.spring-doc.cn

A.1. Command Registration

Defining a command registration is a first step to introducing the structure of a command and its options and parameters. This is loosely decoupled from what happens later, such as parsing command-line input and running actual target code. Essentially, it is the definition of a command API that is shown to a user.spring-doc.cn

A.1.1. Commands

A command in a spring-shell structure is defined as an array of commands. This yields a structure similar to the following example:spring-doc.cn

command1 sub1
command2 sub1 subsub1
command2 sub2 subsub1
command2 sub2 subsub2
We do not currently support mapping commands to an explicit parent if sub-commands are defined. For example, command1 sub1 and command1 sub1 subsub1 cannot both be registered.

A.1.2. Interaction Mode

Spring Shell has been designed to work on two modes: interactive (which essentially is a REPL where you have an active shell instance throughout a series of commands) and non-interactive (where commands are executed one by one from a command line).spring-doc.cn

Differentation between these modes is mostly around limitations about what can be done in each mode. For example, it would not be feasible to show what was a previous stacktrace of a command if the shell is no longer active. Generally, whether the shell is still active dictates the available information.spring-doc.cn

Also, being on an active REPL session may provide more information about what the user has been doing within an active session.spring-doc.cn

A.1.3. Options

Options can be defined as long and short, where the prefixing is -- and -, respectively. The following examples show long and short options:spring-doc.cn

CommandRegistration.builder()
	.withOption()
		.longNames("myopt")
		.and()
	.build();
CommandRegistration.builder()
	.withOption()
		.shortNames('s')
		.and()
	.build();

A.1.4. Target

The target defines the execution target of a command. It can be a method in a POJO, a Consumer, or a Function.spring-doc.cn

Method

Using a Method in an existing POJO is one way to define a target. Consider the following class:spring-doc.cn

public static class CommandPojo {

	String command(String arg) {
		return arg;
	}
}

Given the existing class shown in the preceding listing, you can then register its method:spring-doc.cn

CommandPojo pojo = new CommandPojo();
CommandRegistration.builder()
	.command("command")
	.withTarget()
		.method(pojo, "command")
		.and()
	.withOption()
		.longNames("arg")
		.and()
	.build();
Function

Using a Function as a target gives a lot of flexibility to handle what happens in a command execution, because you can handle many things manually by using a CommandContext given to a Function. The return type from a Function is then what gets printed into the shell as a result. Consider the following example:spring-doc.cn

CommandRegistration.builder()
	.command("command")
	.withTarget()
		.function(ctx -> {
			String arg = ctx.getOptionValue("arg");
			return String.format("hi, arg value is '%s'", arg);
		})
		.and()
	.withOption()
		.longNames("arg")
		.and()
	.build();
Consumer

Using a Consumer is basically the same as using a Function, with the difference being that there is no return type. If you need to print something into a shell, you can get a reference to a Terminal from a context and print something through it. Consider the following example:spring-doc.cn

CommandRegistration.builder()
	.command("command")
	.withTarget()
		.consumer(ctx -> {
			String arg = ctx.getOptionValue("arg");
			ctx.getTerminal().writer()
				.println(String.format("hi, arg value is '%s'", arg));
		})
	.and()
	.withOption()
		.longNames("arg")
		.and()
	.build();

A.2. Command Parser

Before a command can be executed, we need to parse the command and whatever options the user may have provided. Parsing comes between command registration and command execution.spring-doc.cn

A.3. Command Execution

When command parsing has done its job and command registration has been resolved, command execution does the hard work of running the code.spring-doc.cn

A.4. Command Context

The CommandContext interface gives access to a currently running context. You can use it to get access to options:spring-doc.cn

String arg = ctx.getOptionValue("arg");

If you need to print something into a shell, you can get a Terminal and use its writer to print something:spring-doc.cn

ctx.getTerminal().writer().println("hi");

A.5. Command Catalog

The CommandCatalog interface defines how command registrations exist in a shell application. It is possible to dynamically register and de-register commands, which gives flexibility for use cases where possible commands come and go, depending on a shell’s state. Consider the following example:spring-doc.cn

CommandRegistration registration = CommandRegistration.builder().build();
catalog.register(registration);

A.5.1. Command Resolver

You can implement the CommandResolver interface and define a bean to dynamically resolve mappings from a command’s name to its CommandRegistration instances. Consider the following example:spring-doc.cn

static class CustomCommandResolver implements CommandResolver {
	List<CommandRegistration> registrations = new ArrayList<>();

	CustomCommandResolver() {
		CommandRegistration resolved = CommandRegistration.builder()
			.command("resolve command")
			.build();
		registrations.add(resolved);
	}

	@Override
	public List<CommandRegistration> resolve() {
		return registrations;
	}
}
A current limitation of a CommandResolver is that it is used every time commands are resolved. Thus, we advise not using it if a command resolution call takes a long time, as it would make the shell feel sluggish.

A.5.2. Command Catalog Customizer

You can use the CommandCatalogCustomizer interface to customize a CommandCatalog. Its main use is to modify a catalog. Also, within spring-shell auto-configuration, this interface is used to register existing CommandRegistration beans into a catalog. Consider the following example:spring-doc.cn

static class CustomCommandCatalogCustomizer implements CommandCatalogCustomizer {

	@Override
	public void customize(CommandCatalog commandCatalog) {
		CommandRegistration registration = CommandRegistration.builder()
			.command("resolve command")
			.build();
		commandCatalog.register(registration);
	}
}

You can create a CommandCatalogCustomizer as a bean, and Spring Shell handles the rest.spring-doc.cn

A.6. Theming

Styling in a theming is provided by a use of a AttributedString from JLine. Unfortunately styling in JLine is mostly undocumented but we try to go through some of its features here.spring-doc.cn

In JLine a style spec is a string having a special format. Spec can be given multiple times if separated by a comma. A spec will either define a color for foreground, background or its mode. Special format <spec>:=<spec> allows to define a default within latter spec if former for some reason is invalid.spring-doc.cn

If spec contains a colon its former part indicates either foreground or background and possible values are foreground, fg, f, background, bg, b, foreground-rgb, fg-rgb, f-rgb, background-rgb, bg-rgb or b-rgb. Without rbg a color value is name from an allowable colors black, red, green, yellow, blue, magenta, cyan or white. Colors have their short formats k, r, g, y, b, m, c and w respectively. If color is prefixed with either ! or bright-, bright mode is automatically applied. Prefixing with ~ will resolve from JLine internal bsd color table.spring-doc.cn

If rgb format is expected and prefixed with either x or # a normal hex format is used.spring-doc.cn

fg-red
fg-r
fg-rgb:red
fg-rgb:xff3333
fg-rgb:#ff3333

If spec contains special names default, bold, faint, italic, underline, blink, inverse, inverse-neg, inverseneg, conceal, crossed-out, crossedout or hidden a style is changed accordingly with an existing color.spring-doc.cn

bold
bold,fg:red

If spec is a number or numbers separated with semicolon, format is a plain part of an ansi ascii codes.spring-doc.cn

31
31;1
JLine special mapping format which would resolve spec starting with dot can’t be used as we don’t yet map those into Spring Shell styling names.