Rework themes support

- Add theme settings for figures.
- Settings for styles are now in it own class and
  ThemeSettings are wrapping those.
- Prefixed formatting string to have `style-` prefix.
- Templates can now use added `figures` dictionary which
  is resolved per activated theme.
- Create `dump` theme.
- New `ThemeActive` interface where default impl uses dump
  theme if `CI` or `NO_COLOR` env is set.
- Rework existing templates.
- Fixes #442
This commit is contained in:
Janne Valkealahti
2022-06-10 09:11:30 +01:00
parent fa65a2308e
commit 073cd8562c
27 changed files with 807 additions and 256 deletions

View File

@@ -174,7 +174,7 @@ public class SpringShellProperties {
public static class Theme {
private String name = "default";
private String name;
public String getName() {
return name;

View File

@@ -16,35 +16,57 @@
package org.springframework.shell.boot;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.style.TemplateExecutor;
import org.springframework.shell.style.Theme;
import org.springframework.shell.style.ThemeActive;
import org.springframework.shell.style.ThemeRegistry;
import org.springframework.shell.style.ThemeResolver;
import org.springframework.shell.style.ThemeSettings;
import org.springframework.util.StringUtils;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SpringShellProperties.class)
public class ThemingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public ThemeActive themeActive() {
return () -> {
if (System.getenv("CI") != null || System.getenv("NO_COLOR") != null) {
return "dump";
}
return "default";
};
}
@Bean
public ThemeRegistry themeRegistry(ObjectProvider<Theme> themes) {
ThemeRegistry registry = new ThemeRegistry();
registry.register(Theme.of("default", ThemeSettings.themeSettings()));
registry.register(Theme.of("default", ThemeSettings.defaults()));
registry.register(Theme.of("dump", ThemeSettings.dump()));
themes.orderedStream().forEachOrdered(registry::register);
return registry;
}
@Bean
public ThemeResolver shellThemeResolver(ThemeRegistry themeRegistry, SpringShellProperties properties) {
return new ThemeResolver(themeRegistry, properties.getTheme().getName());
public ThemeResolver shellThemeResolver(ThemeRegistry themeRegistry, SpringShellProperties properties,
ThemeActive themeActive) {
String themeName = properties.getTheme().getName();
if (!StringUtils.hasText(themeName)) {
themeName = themeActive.get();
}
if (!StringUtils.hasText(themeName)) {
themeName = "default";
}
return new ThemeResolver(themeRegistry, themeName);
}
@Bean
public TemplateExecutor templateExecutor(ThemeResolver themeResolver) {
return new TemplateExecutor(themeResolver);
}
}

View File

@@ -40,7 +40,7 @@ public class SpringShellPropertiesTests {
assertThat(properties.getScript().isEnabled()).isTrue();
assertThat(properties.getInteractive().isEnabled()).isTrue();
assertThat(properties.getNoninteractive().isEnabled()).isTrue();
assertThat(properties.getTheme().getName()).isEqualTo("default");
assertThat(properties.getTheme().getName()).isNull();
assertThat(properties.getCommand().getClear().isEnabled()).isTrue();
assertThat(properties.getCommand().getHelp().isEnabled()).isTrue();
assertThat(properties.getCommand().getHelp().getGroupingMode()).isEqualTo(GroupingMode.GROUP);

View File

@@ -15,17 +15,24 @@
*/
package org.springframework.shell.boot;
import java.util.stream.Stream;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.InstanceOfAssertFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.style.FigureSettings;
import org.springframework.shell.style.StyleSettings;
import org.springframework.shell.style.TemplateExecutor;
import org.springframework.shell.style.Theme;
import org.springframework.shell.style.ThemeRegistry;
import org.springframework.shell.style.ThemeResolver;
import org.springframework.shell.style.ThemeSettings;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,19 +42,22 @@ public class ThemingAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(ThemingAutoConfiguration.class));
@Test
public void testDefaults() {
void createsDefaultBeans() {
this.contextRunner
.run(context -> {
assertThat(context).hasSingleBean(TemplateExecutor.class);
assertThat(context).hasSingleBean(ThemeRegistry.class);
ThemeRegistry registry = context.getBean(ThemeRegistry.class);
assertThat(registry.get("default")).isNotNull();
assertThat(registry.get("dump")).isNotNull();
assertThat(context).hasSingleBean(ThemeResolver.class);
ThemeResolver resolver = context.getBean(ThemeResolver.class);
assertThat(resolver).extracting("theme").asInstanceOf(THEME).hasName("default", "dump");
});
}
@Test
public void testRegistersCustomTheme() {
public void canRegisterCustomTheme() {
this.contextRunner
.withUserConfiguration(CustomThemeConfig.class)
.run(context -> {
@@ -79,5 +89,36 @@ public class ThemingAutoConfigurationTests {
}
static class MyThemeSettings extends ThemeSettings {
MyThemeSettings() {
super(StyleSettings.defaults(), FigureSettings.defaults());
}
}
InstanceOfAssertFactory<Theme, ThemeAssert> THEME = new InstanceOfAssertFactory<>(Theme.class,
ThemeAssertions::assertThat);
static class ThemeAssertions {
public static ThemeAssert assertThat(Theme actual) {
return new ThemeAssert(actual);
}
}
static class ThemeAssert extends AbstractAssert<ThemeAssert, Theme> {
public ThemeAssert(Theme actual) {
super(actual, ThemeAssert.class);
}
public ThemeAssert hasName(String... names) {
isNotNull();
boolean match = Stream.of(names).filter(n -> actual.getName().equals(n)).findFirst().isPresent();
if (!match) {
failWithMessage("Expected theme to have names %s but was %s",
StringUtils.arrayToCommaDelimitedString(names), actual.getName());
}
return this;
}
}
}