diff --git a/pom.xml b/pom.xml
index 02f6c447..6e912bed 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,6 +154,23 @@
+
+ pl.project13.maven
+ git-commit-id-plugin
+
+
+
+ revision
+
+
+
+
+ true
+ yyyy-MM-dd'T'HH:mm:ssZ
+ true
+ ${project.build.outputDirectory}/git.properties
+
+
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
index 07e80f80..f54ba15d 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java
@@ -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;
+ }
}
}
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardCommandsAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardCommandsAutoConfiguration.java
index 0885a0a1..d4cdfa8a 100644
--- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardCommandsAutoConfiguration.java
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/StandardCommandsAutoConfiguration.java
@@ -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,
+ ObjectProvider gitProperties, ObjectProvider 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;
+ }
}
diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java
new file mode 100644
index 00000000..b371552b
--- /dev/null
+++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ThemingAutoConfiguration.java
@@ -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 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);
+ }
+
+}
diff --git a/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories
index 4022caba..193d4ae8 100644
--- a/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -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
diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java
index 234f8b28..9ef9577c 100644
--- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java
+++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java
@@ -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();
});
}
diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java
new file mode 100644
index 00000000..19cce0ba
--- /dev/null
+++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ThemingAutoConfigurationTests.java
@@ -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 {
+ }
+}
diff --git a/spring-shell-core/pom.xml b/spring-shell-core/pom.xml
index 1ef9a806..eb7b78b0 100644
--- a/spring-shell-core/pom.xml
+++ b/spring-shell-core/pom.xml
@@ -11,7 +11,7 @@
spring-shell-parent
3.0.0-SNAPSHOT
-
+
Core API and classes for Spring Shell 2
@@ -31,7 +31,10 @@
org.jline
jline-terminal-jna
-
+
+ org.antlr
+ ST4
+
org.springframework.shell
spring-shell-table
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/StringToStyleExpressionRenderer.java b/spring-shell-core/src/main/java/org/springframework/shell/style/StringToStyleExpressionRenderer.java
new file mode 100644
index 00000000..a024d099
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/StringToStyleExpressionRenderer.java
@@ -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 {
+
+ 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);
+ }
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/TemplateExecutor.java b/spring-shell-core/src/main/java/org/springframework/shell/style/TemplateExecutor.java
new file mode 100644
index 00000000..40cd16fb
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/TemplateExecutor.java
@@ -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 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);
+ }
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/Theme.java b/spring-shell-core/src/main/java/org/springframework/shell/style/Theme.java
new file mode 100644
index 00000000..0aaa4412
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/Theme.java
@@ -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;
+ }
+ };
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeRegistry.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeRegistry.java
new file mode 100644
index 00000000..798c43c7
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeRegistry.java
@@ -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 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);
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java
new file mode 100644
index 00000000..577c019e
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeResolver.java
@@ -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);
+ }
+}
diff --git a/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java
new file mode 100644
index 00000000..38a2e753
--- /dev/null
+++ b/spring-shell-core/src/main/java/org/springframework/shell/style/ThemeSettings.java
@@ -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 value1
+ * : list value2
+ */
+ public final static String TAG_LIST_KEY = "list-key";
+
+ /**
+ * Styling for keys or names in a lists:
+ * list key1 :
+ * list key2 :
+ */
+ 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 {
+ }
+}
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/style/TemplateExecutorTests.java b/spring-shell-core/src/test/java/org/springframework/shell/style/TemplateExecutorTests.java
new file mode 100644
index 00000000..8587967d
--- /dev/null
+++ b/spring-shell-core/src/test/java/org/springframework/shell/style/TemplateExecutorTests.java
@@ -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 = "";
+ Map 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 = "";
+ Map 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 = "";
+ Map attributes = new HashMap<>();
+ attributes.put("foo", "bar");
+ AttributedString result = executor.render(template, attributes);
+ AttributedString equalTo = new AttributedStringBuilder().append("bar").toAttributedString();
+ assertThat(result).isEqualTo(equalTo);
+ }
+}
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeRegistryTests.java b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeRegistryTests.java
new file mode 100644
index 00000000..5e73412a
--- /dev/null
+++ b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeRegistryTests.java
@@ -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();
+ }
+}
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeResolverTests.java b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeResolverTests.java
new file mode 100644
index 00000000..0c02f93d
--- /dev/null
+++ b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeResolverTests.java
@@ -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));
+ }
+}
diff --git a/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeSettingsTests.java b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeSettingsTests.java
new file mode 100644
index 00000000..3796d799
--- /dev/null
+++ b/spring-shell-core/src/test/java/org/springframework/shell/style/ThemeSettingsTests.java
@@ -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);
+ }
+
+}
diff --git a/spring-shell-samples/pom.xml b/spring-shell-samples/pom.xml
index 5ee588e5..c61d123c 100644
--- a/spring-shell-samples/pom.xml
+++ b/spring-shell-samples/pom.xml
@@ -19,6 +19,13 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+
+ build-info
+
+
+
diff --git a/spring-shell-samples/src/main/resources/application.yml b/spring-shell-samples/src/main/resources/application.yml
index 41888623..ec0c27d2 100644
--- a/spring-shell-samples/src/main/resources/application.yml
+++ b/spring-shell-samples/src/main/resources/application.yml
@@ -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
diff --git a/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Version.java b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Version.java
new file mode 100644
index 00000000..9e8455a7
--- /dev/null
+++ b/spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/Version.java
@@ -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 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);
+ }
+ }
+}
diff --git a/spring-shell-standard/src/main/resources/META-INF/native-image/resource-config.json b/spring-shell-standard/src/main/resources/META-INF/native-image/resource-config.json
index 9f362800..573f1898 100644
--- a/spring-shell-standard/src/main/resources/META-INF/native-image/resource-config.json
+++ b/spring-shell-standard/src/main/resources/META-INF/native-image/resource-config.json
@@ -3,7 +3,10 @@
"includes": [
{
"pattern": "completion/.*"
+ },
+ {
+ "pattern": "template/.*.st"
}
]
}
-}
\ No newline at end of file
+}
diff --git a/spring-shell-standard/src/main/resources/template/version-default.st b/spring-shell-standard/src/main/resources/template/version-default.st
new file mode 100644
index 00000000..dfab31b3
--- /dev/null
+++ b/spring-shell-standard/src/main/resources/template/version-default.st
@@ -0,0 +1,27 @@
+
+<("Build Version"); format="list-key">:
+
+
+<("Build Group"); format="list-key">:
+
+
+<("Build Artifact"); format="list-key">:
+
+
+<("Build Name"); format="list-key">:
+
+
+<("Build Time"); format="list-key">:
+
+
+<("Git Short Commit Id"); format="list-key">:
+
+
+<("Git Commit Id"); format="list-key">:
+
+
+<("Git Branch"); format="list-key">:
+
+
+<("Git Commit Time"); format="list-key">:
+