Support modify option names

- New OptionNameModifier which is just a Function<String,String> to
  modify a name.
- Can be defined per option in CommandRegistration.
- Can be defined as global default as bean.
- Default implementation for common case types is enabled via boot's
  config props under spring.shell.option.naming.case-type
- Support facilities for camel, kebab, snake and pascal conversions.
- Fixes #621
This commit is contained in:
Janne Valkealahti
2023-01-15 10:02:55 +00:00
parent 448c507ce9
commit a04091c08f
14 changed files with 747 additions and 5 deletions

View File

@@ -20,7 +20,9 @@ import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.shell.MethodTargetRegistrar;
@@ -29,6 +31,8 @@ import org.springframework.shell.command.CommandCatalog;
import org.springframework.shell.command.CommandCatalogCustomizer;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.command.CommandRegistration.BuilderSupplier;
import org.springframework.shell.command.CommandRegistration.OptionNameModifier;
import org.springframework.shell.command.support.OptionNameModifierSupport;
import org.springframework.shell.command.CommandResolver;
@AutoConfiguration
@@ -74,6 +78,40 @@ public class CommandCatalogAutoConfiguration {
};
}
@Bean
@ConditionalOnBean(OptionNameModifier.class)
public CommandRegistrationCustomizer customOptionNameModifierCommandRegistrationCustomizer(OptionNameModifier modifier) {
return builder -> {
builder.defaultOptionNameModifier(modifier);
};
}
@Bean
@ConditionalOnMissingBean(OptionNameModifier.class)
@ConditionalOnProperty(prefix = "spring.shell.option.naming", name = "case-type")
public CommandRegistrationCustomizer defaultOptionNameModifierCommandRegistrationCustomizer(SpringShellProperties properties) {
return builder -> {
switch (properties.getOption().getNaming().getCaseType()) {
case NOOP:
break;
case CAMEL:
builder.defaultOptionNameModifier(OptionNameModifierSupport.CAMELCASE);
break;
case SNAKE:
builder.defaultOptionNameModifier(OptionNameModifierSupport.SNAKECASE);
break;
case KEBAB:
builder.defaultOptionNameModifier(OptionNameModifierSupport.KEBABCASE);
break;
case PASCAL:
builder.defaultOptionNameModifier(OptionNameModifierSupport.PASCALCASE);
break;
default:
break;
}
};
}
@Bean
@ConditionalOnMissingBean
public BuilderSupplier commandRegistrationBuilderSupplier(

View File

@@ -33,6 +33,7 @@ public class SpringShellProperties {
private Theme theme = new Theme();
private Command command = new Command();
private Help help = new Help();
private Option option = new Option();
public void setConfig(Config config) {
this.config = config;
@@ -98,6 +99,14 @@ public class SpringShellProperties {
return help;
}
public Option getOption() {
return option;
}
public void setOption(Option option) {
this.option = option;
}
public static class Config {
private String env;
@@ -559,4 +568,38 @@ public class SpringShellProperties {
this.enabled = enabled;
}
}
public static class Option {
private OptionNaming naming = new OptionNaming();
public OptionNaming getNaming() {
return naming;
}
public void setNaming(OptionNaming naming) {
this.naming = naming;
}
}
public static class OptionNaming {
private OptionNamingCase caseType = OptionNamingCase.NOOP;
public OptionNamingCase getCaseType() {
return caseType;
}
public void setCaseType(OptionNamingCase caseType) {
this.caseType = caseType;
}
}
public static enum OptionNamingCase {
NOOP,
CAMEL,
SNAKE,
KEBAB,
PASCAL
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2022 the original author or authors.
* Copyright 2022-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,6 +27,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.shell.command.CommandCatalog;
import org.springframework.shell.command.CommandRegistration;
import org.springframework.shell.command.CommandResolver;
import org.springframework.shell.command.CommandRegistration.Builder;
import org.springframework.shell.command.CommandRegistration.BuilderSupplier;
import org.springframework.shell.command.CommandRegistration.OptionNameModifier;
import static org.assertj.core.api.Assertions.assertThat;
@@ -68,6 +71,82 @@ public class CommandCatalogAutoConfigurationTests {
});
}
@Test
void builderSupplierIsCreated() {
this.contextRunner
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
assertThat(builderSupplier).isNotNull();
});
}
@Test
void defaultOptionNameModifierIsNull() {
this.contextRunner
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
Builder builder = builderSupplier.get();
assertThat(builder).extracting("defaultOptionNameModifier").isNull();
});
}
@Test
void defaultOptionNameModifierIsSet() {
this.contextRunner
.withUserConfiguration(CustomOptionNameModifierConfiguration.class)
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
Builder builder = builderSupplier.get();
assertThat(builder).extracting("defaultOptionNameModifier").isNotNull();
});
}
@Test
void defaultOptionNameModifierIsSetFromProperties() {
this.contextRunner
.withPropertyValues("spring.shell.option.naming.case-type=kebab")
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
Builder builder = builderSupplier.get();
assertThat(builder).extracting("defaultOptionNameModifier").isNotNull();
});
}
@Test
void defaultOptionNameModifierNoopNotSetFromProperties() {
this.contextRunner
.withPropertyValues("spring.shell.option.naming.case-type=noop")
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
Builder builder = builderSupplier.get();
assertThat(builder).extracting("defaultOptionNameModifier").isNull();
// there is customizer but it doesn't do anything
assertThat(context).hasBean("defaultOptionNameModifierCommandRegistrationCustomizer");
});
}
@Test
void noCustomizerIfPropertyIsNotSet() {
this.contextRunner
.run(context -> {
BuilderSupplier builderSupplier = context.getBean(BuilderSupplier.class);
Builder builder = builderSupplier.get();
assertThat(builder).extracting("defaultOptionNameModifier").isNull();
// no customizer added without property
assertThat(context).doesNotHaveBean("defaultOptionNameModifierCommandRegistrationCustomizer");
});
}
// defaultOptionNameModifierCommandRegistrationCustomizer
@Configuration
static class CustomOptionNameModifierConfiguration {
@Bean
OptionNameModifier customOptionNameModifier() {
return name -> name;
}
}
@Configuration
static class CustomCommandResolverConfiguration {

View File

@@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.shell.boot.SpringShellProperties.OptionNamingCase;
import org.springframework.shell.boot.SpringShellProperties.HelpCommand.GroupingMode;
import static org.assertj.core.api.Assertions.assertThat;
@@ -67,6 +68,7 @@ public class SpringShellPropertiesTests {
assertThat(properties.getHelp().getCommand()).isEqualTo("help");
assertThat(properties.getHelp().getLongNames()).containsExactly("help");
assertThat(properties.getHelp().getShortNames()).containsExactly('h');
assertThat(properties.getOption().getNaming().getCaseType()).isEqualTo(OptionNamingCase.NOOP);
});
}
@@ -107,6 +109,7 @@ public class SpringShellPropertiesTests {
.withPropertyValues("spring.shell.help.command=fake")
.withPropertyValues("spring.shell.help.long-names=fake")
.withPropertyValues("spring.shell.help.short-names=f")
.withPropertyValues("spring.shell.option.naming.case-type=camel")
.withUserConfiguration(Config1.class)
.run((context) -> {
SpringShellProperties properties = context.getBean(SpringShellProperties.class);
@@ -144,6 +147,7 @@ public class SpringShellPropertiesTests {
assertThat(properties.getHelp().getCommand()).isEqualTo("fake");
assertThat(properties.getHelp().getLongNames()).containsExactly("fake");
assertThat(properties.getHelp().getShortNames()).containsExactly('f');
assertThat(properties.getOption().getNaming().getCaseType()).isEqualTo(OptionNamingCase.CAMEL);
});
}