Appendix A: 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.

A.7. Search Algorithms

SearchMatch is an interface to match text with a pattern. Match results are in a returned value SearchMatchResult. Match result contains info about match positions and overall score of a match.spring-doc.cn

A.7.1. Implementations

FuzzyMatchV2Searchspring-doc.cn

Port of fzf FuzzyMatchV2Search algorithm. Does a fast fuzzy search and is good quickly finding paths.spring-doc.cn

ExactMatchNaivespring-doc.cn

Port of fzf ExactMatchNaive algorithm. Simple exact match works more accurately if you know what to search.spring-doc.cn

A.7.2. SearchMatch

Algorithms and default syntax are hidden inside package protected classes as we don’t want to fully open these until we know API’s are good to go for longer support. You need to construct SearchMatch via its build-in builder.spring-doc.cn

SearchMatch searchMatch = SearchMatch.builder()
	.caseSensitive(false)
	.normalize(false)
	.forward(true)
	.build();

It’s possible to configure case sensitivity, on what direction search happens or if text should be normilized before search happens. Normalization is handy when different languages have sligh variation for same type of characters.spring-doc.cn

Search algorithm is selected based on a search syntax shown in below table.spring-doc.cn

Table 16. Search syntax
Token Match type Description

hellspring-doc.cn

fuzzy-matchspring-doc.cn

Items that match hellospring-doc.cn

'stuffspring-doc.cn

exact-matchspring-doc.cn

Items that include stuffspring-doc.cn

A.7.3. Examples

SearchMatch searchMatch = SearchMatch.builder()
	.caseSensitive(false)
	.normalize(false)
	.forward(true)
	.build();

SearchMatchResult result = searchMatch.match("foo bar baz", "fbb");

result.getStart();
// 0 - start position inclusive
result.getEnd();
// 9 - end position exclusive
result.getPositions();
// 0,4,8 - positions, inclusive
result.getScore();
// 112 - score
result.getAlgorithm();
// FuzzyMatchV2SearchMatchAlgorithm - resolved algo