Add version command

- Add new styling system which works around concept that
  you use tags to request jline styles where tags comes
  from an activated theme.
- There is a default theme with options to add custom
  ones and change it via property.
- Add templating system which uses antlr stringtemplate which
  allows to write output with a template instead of manually
  crafting code.
- Add version command which integrates to Boot's BuildProperties
  and GitProperties. Only version field is visible on default
  and others can be enabled/disable via properties.
- Fixes #352
- Fixes #353
This commit is contained in:
Janne Valkealahti
2022-01-23 09:23:16 +00:00
parent 0fb5b5ee7a
commit 52ff4a2d85
23 changed files with 1208 additions and 6 deletions

17
pom.xml
View File

@@ -154,6 +154,23 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
</configuration>
</plugin>
</plugins>
</build>
<profiles>

View File

@@ -28,6 +28,7 @@ public class SpringShellProperties {
private Script script = new Script();
private Interactive interactive = new Interactive();
private Noninteractive noninteractive = new Noninteractive();
private Theme theme = new Theme();
private Command command = new Command();
public void setScript(Script script) {
@@ -54,6 +55,14 @@ public class SpringShellProperties {
this.noninteractive = noninteractive;
}
public Theme getTheme() {
return theme;
}
public void setTheme(Theme theme) {
this.theme = theme;
}
public Command getCommand() {
return command;
}
@@ -101,6 +110,19 @@ public class SpringShellProperties {
}
}
public static class Theme {
private String name = "default";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class HelpCommand {
private boolean enabled = true;
@@ -210,6 +232,7 @@ public class SpringShellProperties {
private ScriptCommand script = new ScriptCommand();
private HistoryCommand history = new HistoryCommand();
private CompletionCommand completion = new CompletionCommand();
private VersionCommand version = new VersionCommand();
public void setHelp(HelpCommand help) {
this.help = help;
@@ -266,5 +289,116 @@ public class SpringShellProperties {
public void setCompletion(CompletionCommand completion) {
this.completion = completion;
}
public VersionCommand getVersion() {
return version;
}
public void setVersion(VersionCommand version) {
this.version = version;
}
}
public static class VersionCommand {
private boolean enabled = true;
private String template = "classpath:template/version-default.st";
private boolean showBuildGroup = false;
private boolean showBuildArtifact = false;
private boolean showBuildName = false;
private boolean showBuildVersion = true;
private boolean showBuildTime = false;
private boolean showGitBranch = false;
private boolean showGitCommitId = false;
private boolean showGitShortCommitId = false;
private boolean showGitCommitTime = false;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
public boolean isShowBuildGroup() {
return showBuildGroup;
}
public void setShowBuildGroup(boolean showBuildGroup) {
this.showBuildGroup = showBuildGroup;
}
public boolean isShowBuildArtifact() {
return showBuildArtifact;
}
public void setShowBuildArtifact(boolean showBuildArtifact) {
this.showBuildArtifact = showBuildArtifact;
}
public boolean isShowBuildName() {
return showBuildName;
}
public void setShowBuildName(boolean showBuildName) {
this.showBuildName = showBuildName;
}
public boolean isShowBuildVersion() {
return showBuildVersion;
}
public void setShowBuildVersion(boolean showBuildVersion) {
this.showBuildVersion = showBuildVersion;
}
public boolean isShowBuildTime() {
return showBuildTime;
}
public void setShowBuildTime(boolean showBuildTime) {
this.showBuildTime = showBuildTime;
}
public boolean isShowGitBranch() {
return showGitBranch;
}
public void setShowGitBranch(boolean showGitBranch) {
this.showGitBranch = showGitBranch;
}
public boolean isShowGitCommitId() {
return showGitCommitId;
}
public void setShowGitCommitId(boolean showGitCommitId) {
this.showGitCommitId = showGitCommitId;
}
public boolean isShowGitShortCommitId() {
return showGitShortCommitId;
}
public void setShowGitShortCommitId(boolean showGitShortCommitId) {
this.showGitShortCommitId = showGitShortCommitId;
}
public boolean isShowGitCommitTime() {
return showGitCommitTime;
}
public void setShowGitCommitTime(boolean showGitCommitTime) {
this.showGitCommitTime = showGitCommitTime;
}
}
}

View File

@@ -23,9 +23,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.boot.SpringShellProperties.VersionCommand;
import org.springframework.shell.boot.condition.OnCompletionCommandCondition;
import org.springframework.shell.result.ThrowableResultHandler;
import org.springframework.shell.standard.commands.Clear;
@@ -35,6 +38,8 @@ import org.springframework.shell.standard.commands.History;
import org.springframework.shell.standard.commands.Quit;
import org.springframework.shell.standard.commands.Script;
import org.springframework.shell.standard.commands.Stacktrace;
import org.springframework.shell.standard.commands.Version;
import org.springframework.shell.style.TemplateExecutor;
/**
* Creates beans for standard commands.
@@ -94,4 +99,26 @@ public class StandardCommandsAutoConfiguration {
public Completion completion(SpringShellProperties properties) {
return new Completion(properties.getCommand().getCompletion().getRootCommand());
}
@Bean
@ConditionalOnMissingBean(Version.Command.class)
@ConditionalOnProperty(prefix = "spring.shell.command.version", value = "enabled", havingValue = "true", matchIfMissing = true)
public Version version(SpringShellProperties properties, ObjectProvider<BuildProperties> buildProperties,
ObjectProvider<GitProperties> gitProperties, ObjectProvider<TemplateExecutor> templateExecutor) {
Version version = new Version(templateExecutor.getIfAvailable());
version.setBuildProperties(buildProperties.getIfAvailable());
version.setGitProperties(gitProperties.getIfAvailable());
VersionCommand versionProperties = properties.getCommand().getVersion();
version.setTemplate(versionProperties.getTemplate());
version.setShowBuildArtifact(versionProperties.isShowBuildArtifact());
version.setShowBuildGroup(versionProperties.isShowBuildGroup());
version.setShowBuildName(versionProperties.isShowBuildName());
version.setShowBuildTime(versionProperties.isShowBuildTime());
version.setShowBuildVersion(versionProperties.isShowBuildVersion());
version.setShowGitBranch(versionProperties.isShowGitBranch());
version.setShowGitCommitId(versionProperties.isShowGitCommitId());
version.setShowGitShortCommitId(versionProperties.isShowGitShortCommitId());
version.setShowGitCommitTime(versionProperties.isShowGitCommitTime());
return version;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.boot;
import org.springframework.beans.factory.ObjectProvider;
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.ThemeRegistry;
import org.springframework.shell.style.ThemeResolver;
import org.springframework.shell.style.ThemeSettings;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SpringShellProperties.class)
public class ThemingAutoConfiguration {
@Bean
public ThemeRegistry themeRegistry(ObjectProvider<Theme> themes) {
ThemeRegistry registry = new ThemeRegistry();
registry.register(Theme.of("default", ThemeSettings.themeSettings()));
themes.orderedStream().forEachOrdered(theme -> registry.register(theme));
return registry;
}
@Bean
public ThemeResolver themeResolver(ThemeRegistry themeRegistry, SpringShellProperties properties) {
return new ThemeResolver(themeRegistry, properties.getTheme().getName());
}
@Bean
public TemplateExecutor templateExecutor(ThemeResolver themeResolver) {
return new TemplateExecutor(themeResolver);
}
}

View File

@@ -11,4 +11,5 @@ org.springframework.shell.boot.JLineShellAutoConfiguration,\
org.springframework.shell.boot.JCommanderParameterResolverAutoConfiguration,\
org.springframework.shell.boot.ParameterResolverAutoConfiguration,\
org.springframework.shell.boot.StandardAPIAutoConfiguration,\
org.springframework.shell.boot.ThemingAutoConfiguration,\
org.springframework.shell.boot.StandardCommandsAutoConfiguration

View File

@@ -35,6 +35,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.getCommand().getClear().isEnabled()).isTrue();
assertThat(properties.getCommand().getHelp().isEnabled()).isTrue();
assertThat(properties.getCommand().getHistory().isEnabled()).isTrue();
@@ -43,6 +44,17 @@ public class SpringShellPropertiesTests {
assertThat(properties.getCommand().getStacktrace().isEnabled()).isTrue();
assertThat(properties.getCommand().getCompletion().isEnabled()).isTrue();
assertThat(properties.getCommand().getCompletion().getRootCommand()).isNull();
assertThat(properties.getCommand().getVersion().isEnabled()).isTrue();
assertThat(properties.getCommand().getVersion().getTemplate()).isNotNull();
assertThat(properties.getCommand().getVersion().isShowBuildArtifact()).isFalse();
assertThat(properties.getCommand().getVersion().isShowBuildGroup()).isFalse();
assertThat(properties.getCommand().getVersion().isShowBuildName()).isFalse();
assertThat(properties.getCommand().getVersion().isShowBuildTime()).isFalse();
assertThat(properties.getCommand().getVersion().isShowBuildVersion()).isTrue();
assertThat(properties.getCommand().getVersion().isShowGitBranch()).isFalse();
assertThat(properties.getCommand().getVersion().isShowGitCommitId()).isFalse();
assertThat(properties.getCommand().getVersion().isShowGitShortCommitId()).isFalse();
assertThat(properties.getCommand().getVersion().isShowGitCommitTime()).isFalse();
});
}
@@ -52,6 +64,7 @@ public class SpringShellPropertiesTests {
.withPropertyValues("spring.shell.script.enabled=false")
.withPropertyValues("spring.shell.interactive.enabled=false")
.withPropertyValues("spring.shell.noninteractive.enabled=false")
.withPropertyValues("spring.shell.theme.name=fake")
.withPropertyValues("spring.shell.command.clear.enabled=false")
.withPropertyValues("spring.shell.command.help.enabled=false")
.withPropertyValues("spring.shell.command.history.enabled=false")
@@ -60,12 +73,24 @@ public class SpringShellPropertiesTests {
.withPropertyValues("spring.shell.command.stacktrace.enabled=false")
.withPropertyValues("spring.shell.command.completion.enabled=false")
.withPropertyValues("spring.shell.command.completion.root-command=fake")
.withPropertyValues("spring.shell.command.version.enabled=false")
.withPropertyValues("spring.shell.command.version.template=fake")
.withPropertyValues("spring.shell.command.version.show-build-artifact=true")
.withPropertyValues("spring.shell.command.version.show-build-group=true")
.withPropertyValues("spring.shell.command.version.show-build-name=true")
.withPropertyValues("spring.shell.command.version.show-build-time=true")
.withPropertyValues("spring.shell.command.version.show-build-version=false")
.withPropertyValues("spring.shell.command.version.show-git-branch=true")
.withPropertyValues("spring.shell.command.version.show-git-commit-id=true")
.withPropertyValues("spring.shell.command.version.show-git-short-commit-id=true")
.withPropertyValues("spring.shell.command.version.show-git-commit-time=true")
.withUserConfiguration(Config1.class)
.run((context) -> {
SpringShellProperties properties = context.getBean(SpringShellProperties.class);
assertThat(properties.getScript().isEnabled()).isFalse();
assertThat(properties.getInteractive().isEnabled()).isFalse();
assertThat(properties.getNoninteractive().isEnabled()).isFalse();
assertThat(properties.getTheme().getName()).isEqualTo("fake");
assertThat(properties.getCommand().getClear().isEnabled()).isFalse();
assertThat(properties.getCommand().getHelp().isEnabled()).isFalse();
assertThat(properties.getCommand().getHistory().isEnabled()).isFalse();
@@ -74,6 +99,17 @@ public class SpringShellPropertiesTests {
assertThat(properties.getCommand().getStacktrace().isEnabled()).isFalse();
assertThat(properties.getCommand().getCompletion().isEnabled()).isFalse();
assertThat(properties.getCommand().getCompletion().getRootCommand()).isEqualTo("fake");
assertThat(properties.getCommand().getVersion().isEnabled()).isFalse();
assertThat(properties.getCommand().getVersion().getTemplate()).isEqualTo("fake");
assertThat(properties.getCommand().getVersion().isShowBuildArtifact()).isTrue();
assertThat(properties.getCommand().getVersion().isShowBuildGroup()).isTrue();
assertThat(properties.getCommand().getVersion().isShowBuildName()).isTrue();
assertThat(properties.getCommand().getVersion().isShowBuildTime()).isTrue();
assertThat(properties.getCommand().getVersion().isShowBuildVersion()).isFalse();
assertThat(properties.getCommand().getVersion().isShowGitBranch()).isTrue();
assertThat(properties.getCommand().getVersion().isShowGitCommitId()).isTrue();
assertThat(properties.getCommand().getVersion().isShowGitShortCommitId()).isTrue();
assertThat(properties.getCommand().getVersion().isShowGitCommitTime()).isTrue();
});
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.boot;
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.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 static org.assertj.core.api.Assertions.assertThat;
public class ThemingAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ThemingAutoConfiguration.class));
@Test
public void testDefaults() {
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(context).hasSingleBean(ThemeResolver.class);
});
}
@Test
public void testRegistersCustomTheme() {
this.contextRunner
.withUserConfiguration(CustomThemeConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(ThemeRegistry.class);
ThemeRegistry registry = context.getBean(ThemeRegistry.class);
assertThat(registry.get("default")).isNotNull();
assertThat(registry.get("mytheme")).isNotNull();
});
}
@Configuration
static class CustomThemeConfig {
@Bean
public Theme myTheme() {
return new Theme() {
@Override
public String getName() {
return "mytheme";
}
@Override
public ThemeSettings getSettings() {
return new MyThemeSettings();
}
};
}
}
static class MyThemeSettings extends ThemeSettings {
}
}

View File

@@ -11,7 +11,7 @@
<artifactId>spring-shell-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<description>Core API and classes for Spring Shell 2</description>
<dependencies>
@@ -31,7 +31,10 @@
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jna</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-table</artifactId>

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import java.util.Locale;
import org.jline.style.StyleExpression;
import org.stringtemplate.v4.AttributeRenderer;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@code ST} {@link AttributeRenderer} which knows to use format string to
* render strings into {@code jline} {@link StyleExpression} based on theming
* settings.
*
* @author Janne Valkealahti
*/
public class StringToStyleExpressionRenderer implements AttributeRenderer<String> {
private final ThemeResolver themeResolver;
public StringToStyleExpressionRenderer(ThemeResolver themeResolver) {
Assert.notNull(themeResolver, "themeResolver must be set");
this.themeResolver = themeResolver;
}
@Override
public String toString(String value, String formatString, Locale locale) {
if (!StringUtils.hasText(formatString)) {
return value;
}
else {
return String.format("@{%s %s}", themeResolver.resolveTag(formatString), value);
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import java.util.Map;
import org.jline.utils.AttributedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STErrorListener;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.misc.STMessage;
/**
* Template executor which knows to use styling.
*
* @author Janne Valkealahti
*/
public class TemplateExecutor {
private final static STErrorListener ERROR_LISTENER = new LoggingSTErrorListener();
private final ThemeResolver themeResolver;
private StringToStyleExpressionRenderer renderer;
public TemplateExecutor(ThemeResolver themeResolver) {
this.themeResolver = themeResolver;
renderer = new StringToStyleExpressionRenderer(themeResolver);
}
/**
* Render template with a given attributes.
*
* @param template the ST template
* @param attributes the ST template attributes
* @return a rendered template
*/
public AttributedString render(String template, Map<String, Object> attributes) {
STGroup group = new STGroup();
group.setListener(ERROR_LISTENER);
group.registerRenderer(String.class, renderer);
ST st = new ST(group, template);
if (attributes != null) {
attributes.entrySet().stream().forEach(e -> st.add(e.getKey(), e.getValue()));
}
String templateRendered = st.render();
return themeResolver.evaluateExpression(templateRendered);
}
private static class LoggingSTErrorListener implements STErrorListener {
private final static Logger log = LoggerFactory.getLogger(LoggingSTErrorListener.class);
@Override
public void compileTimeError(STMessage msg) {
log.error("compileTimeError [{}]", msg);
}
@Override
public void runTimeError(STMessage msg) {
log.error("runTimeError [{}]", msg);
}
@Override
public void IOError(STMessage msg) {
log.error("IOError [{}]", msg);
}
@Override
public void internalError(STMessage msg) {
log.error("internalError [{}]", msg);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
/**
* Contract representing a theme with its name and settings.
*
* {@link Theme} is a concept where you can request a {@code style} by using a theme {@code tag}.
* At this point an actual style is not known as it's going to get resolved from an
* enable {@code theme}.
*
* @author Janne Valkealahti
*/
public interface Theme {
/**
* Gets a theme name.
*
* @return a theme name.
*/
String getName();
/**
* Gets a theme settings.
*
* @return a theme settings
*/
ThemeSettings getSettings();
/**
* Create a {@link Theme}.
*
* @param name the theme name
* @param themeSettings the theme settings
* @return a theme
*/
public static Theme of(String name, ThemeSettings themeSettings) {
return new Theme() {
@Override
public String getName() {
return name;
}
@Override
public ThemeSettings getSettings() {
return themeSettings;
}
};
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* Registry which stores {@link Theme}'s with its name.
*
* @author Janne Valkealahti
*/
public class ThemeRegistry {
private final Map<String, Theme> themes = new HashMap<>();
/**
* Gets a theme from a registry.
*
* @param name the theme name
* @return a theme
*/
public Theme get(String name) {
return themes.get(name);
}
/**
* Register a theme.
*
* @param theme the theme
*/
public void register(Theme theme) {
Assert.notNull(theme, "theme cannot be null");
themes.put(theme.getName(), theme);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import org.jline.style.MemoryStyleSource;
import org.jline.style.StyleExpression;
import org.jline.style.StyleResolver;
import org.jline.style.StyleSource;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
/**
* Service which helps to do various things with styles.
*
* @author Janne Valkealahti
*/
public class ThemeResolver {
private StyleSource styleSource = new MemoryStyleSource();
private StyleResolver styleResolver = new StyleResolver(styleSource, "default");
private StyleExpression styleExpression = new StyleExpression(styleResolver);
private final Theme theme;
public ThemeResolver(ThemeRegistry themeRegistry, String themeName) {
this.theme = themeRegistry.get(themeName);
}
/**
* Evaluate expression.
*
* @param expression the expression
* @return evaluated attributed string
*/
public AttributedString evaluateExpression(String expression) {
return styleExpression.evaluate(expression);
}
/**
* Resolve style from a tag with activated theme.
*
* @param tag the tag
* @return a style
*/
public String resolveTag(String tag) {
return theme.getSettings().resolveTag(tag);
}
/**
* Resolve {@link AttributedStyle} from a {@code spec}.
*
* @param spec the spec
* @return resolved attributed style
*/
public AttributedStyle resolveStyle(String spec) {
return styleResolver.resolve(spec);
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
/**
* {@link ThemeSettings} is storing {@link Theme} related settings together and
* is base class for further customizations.
*
* Settings in this base class are default styles.
*
* @author Janne Valkealahti
*/
public abstract class ThemeSettings {
/**
* Represents some arbitrary {@code title}.
*/
public final static String TAG_TITLE = "title";
/**
* Represents some arbitrary {@code value}.
*/
public final static String TAG_VALUE = "value";
/**
* Styling for keys or names in a lists:
* <list key1> : list value1
* <list key2> : list value2
*/
public final static String TAG_LIST_KEY = "list-key";
/**
* Styling for keys or names in a lists:
* list key1 : <list value1>
* list key2 : <list value2>
*/
public final static String TAG_LIST_VALUE = "list-value";
public String title() {
return "bold";
}
public String value() {
return "fg:blue";
}
public String listKey() {
return null;
}
public String listValue() {
return "bold,fg:green";
}
/**
* Resolve a theme setting from a given tag.
*
* @param tag the tag
* @return a theme setting
*/
public String resolveTag(String tag) {
switch (tag) {
case TAG_TITLE:
return title();
case TAG_VALUE:
return value();
case TAG_LIST_KEY:
return listKey();
case TAG_LIST_VALUE:
return listValue();
}
throw new IllegalArgumentException(String.format("Unknown tag '%s'", tag));
}
/**
* Creates an instance of a default settings.
*
* @return a default theme settings
*/
public static ThemeSettings themeSettings() {
return new DefaultThemeSettings();
}
private static class DefaultThemeSettings extends ThemeSettings {
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import java.util.HashMap;
import java.util.Map;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.jline.utils.AttributedStyle.BOLD;
public class TemplateExecutorTests {
private ThemeResolver themeResolver;
@BeforeEach
public void setup() {
ThemeRegistry themeRegistry = new ThemeRegistry();
themeRegistry.register(new Theme() {
@Override
public String getName() {
return "default";
}
@Override
public ThemeSettings getSettings() {
return ThemeSettings.themeSettings();
}
});
themeResolver = new ThemeResolver(themeRegistry, "default");
}
@Test
public void testSimple() {
TemplateExecutor executor = new TemplateExecutor(themeResolver);
String template = "<(\"foo\")>";
AttributedString result = executor.render(template, null);
AttributedString equalTo = new AttributedStringBuilder().append("foo").toAttributedString();
assertThat(result).isEqualTo(equalTo);
}
@Test
public void testWithExpression() {
TemplateExecutor executor = new TemplateExecutor(themeResolver);
String template = "<foo>";
Map<String, Object> attributes = new HashMap<>();
attributes.put("foo", "bar");
AttributedString result = executor.render(template, attributes);
AttributedString equalTo = new AttributedStringBuilder().append("bar").toAttributedString();
assertThat(result).isEqualTo(equalTo);
}
@Test
public void testWithTheme() {
TemplateExecutor executor = new TemplateExecutor(themeResolver);
String template = "<foo; format=\"title\">";
Map<String, Object> attributes = new HashMap<>();
attributes.put("foo", "bar");
AttributedString result = executor.render(template, attributes);
AttributedString equalTo = new AttributedStringBuilder().append("bar", BOLD).toAttributedString();
assertThat(result).isEqualTo(equalTo);
}
@Test
public void testNullDontStyle() {
TemplateExecutor executor = new TemplateExecutor(themeResolver);
String template = "<foo; format=\"list-key\">";
Map<String, Object> attributes = new HashMap<>();
attributes.put("foo", "bar");
AttributedString result = executor.render(template, attributes);
AttributedString equalTo = new AttributedStringBuilder().append("bar").toAttributedString();
assertThat(result).isEqualTo(equalTo);
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ThemeRegistryTests {
@Test
public void test() {
ThemeRegistry registry = new ThemeRegistry();
Theme theme = Theme.of("name1", ThemeSettings.themeSettings());
registry.register(theme);
assertThat(registry.get("name1")).isSameAs(theme);
assertThat(registry.get("name2")).isNull();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ThemeResolverTests {
@Test
public void test() {
ThemeRegistry themeRegistry = new ThemeRegistry();
themeRegistry.register(new Theme() {
@Override
public String getName() {
return "default";
}
@Override
public ThemeSettings getSettings() {
return ThemeSettings.themeSettings();
}
});
ThemeResolver themeResolver = new ThemeResolver(themeRegistry, "default");
assertThat(themeResolver.resolveTag(ThemeSettings.TAG_TITLE)).isEqualTo("bold");
assertThat(themeResolver.resolveStyle("bold")).isEqualTo(AttributedStyle.BOLD);
assertThat(themeResolver.evaluateExpression("@{bold foo}"))
.isEqualTo(new AttributedString("foo", AttributedStyle.BOLD));
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.style;
import org.jline.style.MemoryStyleSource;
import org.jline.style.StyleResolver;
import org.jline.style.StyleSource;
import org.jline.utils.AttributedStyle;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ThemeSettingsTests {
@Test
public void test() {
ThemeSettings themeSettings = ThemeSettings.themeSettings();
String resolveTag = themeSettings.resolveTag(ThemeSettings.TAG_TITLE);
assertThat(resolveTag).isEqualTo("bold");
StyleSource styleSource = new MemoryStyleSource();
StyleResolver styleResolver = new StyleResolver(styleSource, "default");
AttributedStyle style = styleResolver.resolve(resolveTag);
assertThat(style).isEqualTo(AttributedStyle.BOLD);
}
}

View File

@@ -19,6 +19,13 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -1,6 +1,3 @@
logging:
level:
root: error
spring:
main:
banner-mode: off
@@ -8,3 +5,15 @@ spring:
command:
completion:
root-command: spring-shell-samples
## disable console logging
logging:
pattern:
console:
## log debug from a cli
# file:
# name: shell.log
# level:
# root: debug
# org:
# springframework:
# shell: debug

View File

@@ -0,0 +1,173 @@
/*
* Copyright 2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.standard.commands;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.jline.utils.AttributedString;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.style.TemplateExecutor;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
/**
* Command to list version and other build related infos.
*
* @author Janne Valkealahti
*/
@ShellComponent
public class Version extends AbstractShellComponent implements ResourceLoaderAware {
/**
* Marker interface used in auto-config.
*/
public interface Command {
}
private BuildProperties buildProperties;
private GitProperties gitProperties;
private ResourceLoader resourceLoader;
private TemplateExecutor templateExecutor;
private String template;
private boolean showBuildGroup;
private boolean showBuildArtifact;
private boolean showBuildName;
private boolean showBuildVersion;
private boolean showBuildTime;
private boolean showGitBranch;
private boolean showGitCommitId;
private boolean showGitShortCommitId;
private boolean showGitCommitTime;
public Version(TemplateExecutor templateExecutor) {
this.templateExecutor = templateExecutor;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@ShellMethod(key = "version", value = "Show version info")
public AttributedString version() {
String templateResource = resourceAsString(resourceLoader.getResource(template));
Map<String, Object> attributes = new HashMap<>();
if (buildProperties != null) {
if (showBuildGroup && StringUtils.hasText(buildProperties.getGroup())) {
attributes.put("buildGroup", buildProperties.getGroup());
}
if (showBuildArtifact && StringUtils.hasText(buildProperties.getArtifact())) {
attributes.put("buildArtifact", buildProperties.getArtifact());
}
if (showBuildName && StringUtils.hasText(buildProperties.getName())) {
attributes.put("buildName", buildProperties.getName());
}
if (showBuildVersion && StringUtils.hasText(buildProperties.getVersion())) {
attributes.put("buildVersion", buildProperties.getVersion());
}
if (showBuildTime && buildProperties.getTime() != null) {
attributes.put("buildTime", buildProperties.getTime().toString());
}
}
if (gitProperties != null) {
if (showGitBranch && StringUtils.hasText(gitProperties.getBranch())) {
attributes.put("gitBranch", gitProperties.getBranch());
}
if (showGitCommitId && StringUtils.hasText(gitProperties.getCommitId())) {
attributes.put("gitCommitId", gitProperties.getCommitId());
}
if (showGitShortCommitId && StringUtils.hasText(gitProperties.getShortCommitId())) {
attributes.put("gitShortCommitId", gitProperties.getShortCommitId());
}
if (showGitCommitTime && gitProperties.getCommitTime() != null) {
attributes.put("gitCommitTime", gitProperties.getCommitTime().toString());
}
}
AttributedString rendered = templateExecutor.render(templateResource, attributes);
return rendered;
}
public void setBuildProperties(BuildProperties buildProperties) {
this.buildProperties = buildProperties;
}
public void setGitProperties(GitProperties gitProperties) {
this.gitProperties = gitProperties;
}
public void setTemplate(String template) {
this.template = template;
}
public void setShowBuildGroup(boolean showBuildGroup) {
this.showBuildGroup = showBuildGroup;
}
public void setShowBuildArtifact(boolean showBuildArtifact) {
this.showBuildArtifact = showBuildArtifact;
}
public void setShowBuildName(boolean showBuildName) {
this.showBuildName = showBuildName;
}
public void setShowBuildVersion(boolean showBuildVersion) {
this.showBuildVersion = showBuildVersion;
}
public void setShowBuildTime(boolean showBuildTime) {
this.showBuildTime = showBuildTime;
}
public void setShowGitBranch(boolean showGitBranch) {
this.showGitBranch = showGitBranch;
}
public void setShowGitCommitId(boolean showGitCommitId) {
this.showGitCommitId = showGitCommitId;
}
public void setShowGitShortCommitId(boolean showGitShortCommitId) {
this.showGitShortCommitId = showGitShortCommitId;
}
public void setShowGitCommitTime(boolean showGitCommitTime) {
this.showGitCommitTime = showGitCommitTime;
}
private static String resourceAsString(Resource resource) {
try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {
return FileCopyUtils.copyToString(reader);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View File

@@ -3,7 +3,10 @@
"includes": [
{
"pattern": "completion/.*"
},
{
"pattern": "template/.*.st"
}
]
}
}
}

View File

@@ -0,0 +1,27 @@
<if(buildVersion)>
<("Build Version"); format="list-key">: <buildVersion; format="list-value">
<endif>
<if(buildGroup)>
<("Build Group"); format="list-key">: <buildGroup; format="list-value">
<endif>
<if(buildArtifact)>
<("Build Artifact"); format="list-key">: <buildArtifact; format="list-value">
<endif>
<if(buildName)>
<("Build Name"); format="list-key">: <buildName; format="list-value">
<endif>
<if(buildTime)>
<("Build Time"); format="list-key">: <buildTime; format="list-value">
<endif>
<if(gitShortCommitId)>
<("Git Short Commit Id"); format="list-key">: <gitShortCommitId; format="list-value">
<endif>
<if(gitCommitId)>
<("Git Commit Id"); format="list-key">: <gitCommitId; format="list-value">
<endif>
<if(gitBranch)>
<("Git Branch"); format="list-key">: <gitBranch; format="list-value">
<endif>
<if(gitCommitTime)>
<("Git Commit Time"); format="list-key">: <gitCommitTime; format="list-value">
<endif>