diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java b/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java index 2a1aebac..1ed49779 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/MultiItemSelector.java @@ -59,6 +59,7 @@ public class MultiItemSelector> extends LinkedHashMap implements ComponentContext { + private Integer terminalWidth; + @Override public Object get(Object key) { Object o = super.get(key); @@ -60,12 +62,28 @@ public class BaseComponentContext> extends LinkedH return entrySet().stream(); } + @Override + public Integer getTerminalWidth() { + return terminalWidth; + } + + @Override + public void setTerminalWidth(Integer terminalWidth) { + this.terminalWidth = terminalWidth; + } + @Override public Map toTemplateModel() { Map attributes = new HashMap<>(); // hardcoding enclosed map values into 'rawValues' // as it may contain anything attributes.put("rawValues", this); + attributes.put("terminalWidth", terminalWidth); return attributes; } + + @Override + public String toString() { + return "BaseComponentContext [terminalWidth=" + terminalWidth + "]"; + } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/component/context/ComponentContext.java b/spring-shell-core/src/main/java/org/springframework/shell/component/context/ComponentContext.java index d8278026..082e20e5 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/component/context/ComponentContext.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/component/context/ComponentContext.java @@ -80,6 +80,20 @@ public interface ComponentContext> { */ Stream> stream(); + /** + * Get terminal width. + * + * @return a terminal width + */ + Integer getTerminalWidth(); + + /** + * Set terminal width. + * + * @param terminalWidth the width + */ + void setTerminalWidth(Integer terminalWidth); + /** * Gets context values as a map. Every context implementation can * do their own model as essentially what matter is a one coming 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 index dffdc2c1..50e71bc9 100644 --- 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 @@ -16,6 +16,7 @@ package org.springframework.shell.style; import java.util.Locale; +import java.util.stream.Stream; import org.jline.style.StyleExpression; import org.stringtemplate.v4.AttributeRenderer; @@ -33,6 +34,7 @@ import org.springframework.util.StringUtils; public class StringToStyleExpressionRenderer implements AttributeRenderer { private final ThemeResolver themeResolver; + private final static String TRUNCATE = "truncate-"; public StringToStyleExpressionRenderer(ThemeResolver themeResolver) { Assert.notNull(themeResolver, "themeResolver must be set"); @@ -44,8 +46,51 @@ public class StringToStyleExpressionRenderer implements AttributeRenderer config.width) { + return String.format(locale, "%1." + (config.width - config.prefix - 2) + "s.." , value); + } + else { + return value; + } + } + else { + return String.format(locale, formatString, value); + } + } + + private static class TruncateValues { + Integer width; + Integer prefix; + + public void setWidth(Integer width) { + this.width = width; + } + public void setPrefix(Integer prefix) { + this.prefix = prefix; + } + } + + private static TruncateValues mapValues(String expression) { + TruncateValues values = new TruncateValues(); + Stream.of(expression.split("-")) + .map(String::trim) + .forEach(v -> { + String[] split = v.split(":", 2); + if (split.length == 2) { + if ("width".equals(split[0])) { + values.setWidth(Integer.parseInt(split[1])); + } + else if ("prefix".equals(split[0])) { + values.setPrefix(Integer.parseInt(split[1])); + } + } + }); + return values; } } diff --git a/spring-shell-core/src/main/resources/org/springframework/shell/component/multi-item-selector-default.stg b/spring-shell-core/src/main/resources/org/springframework/shell/component/multi-item-selector-default.stg index fd21c226..535642e8 100644 --- a/spring-shell-core/src/main/resources/org/springframework/shell/component/multi-item-selector-default.stg +++ b/spring-shell-core/src/main/resources/org/springframework/shell/component/multi-item-selector-default.stg @@ -1,10 +1,15 @@ +// selector rows +truncate(name,model) ::= <% +-prefix:5}> +%> + // used to select style if item is selected/unselected selected_style(flag) ::= <% style-item-selectedstyle-item-unselected %> // selector rows -select_item(item) ::= <% +select_item(item,model) ::= <% <({ }); format="style-item-selector"> @@ -13,16 +18,16 @@ select_item(item) ::= <% - <({ }); format=selected_style(item.selected)> + <({ }); format=selected_style(item.selected)> - <({ }); format=selected_style(item.selected)> + <({ }); format=selected_style(item.selected)> - <({ }); format="style-item-disabled"> + <({ }); format="style-item-disabled"> <({}); format="style-item-disabled"> - <({ }); format="style-item-disabled"> - + <({ }); format="style-item-disabled"> <({}); format="style-item-disabled"> + %> @@ -58,7 +63,7 @@ result(model) ::= << // component is running running(model) ::= << -}; separator="\n"> +}; separator="\n"> >> // main - hardcoded name diff --git a/spring-shell-core/src/main/resources/org/springframework/shell/component/single-item-selector-default.stg b/spring-shell-core/src/main/resources/org/springframework/shell/component/single-item-selector-default.stg index 20c42942..bac12635 100644 --- a/spring-shell-core/src/main/resources/org/springframework/shell/component/single-item-selector-default.stg +++ b/spring-shell-core/src/main/resources/org/springframework/shell/component/single-item-selector-default.stg @@ -1,9 +1,13 @@ // selector rows -select_item(item) ::= <% +truncate(name,model) ::= <% +-prefix:2}> +%> + +select_item(item,model) ::= <% -<({ }); format="style-item-selector"> +<({ }); format="style-item-selector"><({}); format="style-item-selector"> -<(" ")> +<(" ")> %> @@ -34,7 +38,7 @@ result(model) ::= << // component is running running(model) ::= << -}; separator="\n"> +}; separator="\n"> >> // main - hardcoded name diff --git a/spring-shell-core/src/test/java/org/springframework/shell/component/AbstractShellTests.java b/spring-shell-core/src/test/java/org/springframework/shell/component/AbstractShellTests.java index 8300d44e..b13095cd 100644 --- a/spring-shell-core/src/test/java/org/springframework/shell/component/AbstractShellTests.java +++ b/spring-shell-core/src/test/java/org/springframework/shell/component/AbstractShellTests.java @@ -80,7 +80,7 @@ public abstract class AbstractShellTests { pipedInputStream.connect(pipedOutputStream); terminal = new DumbTerminal("terminal", "ansi", pipedInputStream, consoleOut, StandardCharsets.UTF_8); - terminal.setSize(new Size(1, 1)); + terminal.setSize(new Size(80, 24)); executorService.execute(() -> { try { diff --git a/spring-shell-core/src/test/java/org/springframework/shell/style/StringToStyleExpressionRendererTests.java b/spring-shell-core/src/test/java/org/springframework/shell/style/StringToStyleExpressionRendererTests.java new file mode 100644 index 00000000..9a6a0858 --- /dev/null +++ b/spring-shell-core/src/test/java/org/springframework/shell/style/StringToStyleExpressionRendererTests.java @@ -0,0 +1,71 @@ +/* + * 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 java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +class StringToStyleExpressionRendererTests { + + private static Locale LOCALE = Locale.getDefault(); + private static StringToStyleExpressionRenderer renderer; + + @BeforeAll + static void setup() { + ThemeRegistry themeRegistry = new ThemeRegistry(); + themeRegistry.register(new Theme() { + @Override + public String getName() { + return "default"; + } + + @Override + public ThemeSettings getSettings() { + return ThemeSettings.defaults(); + } + }); + ThemeResolver themeResolver = new ThemeResolver(themeRegistry, "default"); + renderer = new StringToStyleExpressionRenderer(themeResolver); + } + + @Test + void emptyFormatReturnsValue() { + String value = renderer.toString("fake", null, LOCALE); + assertThat(value).isEqualTo("fake"); + } + + static Stream truncate() { + return Stream.of( + Arguments.of("0123456789", "truncate-width:6-prefix:2", "01.."), + Arguments.of("0123456789", "truncate-width:6-prefix:0", "0123.."), + Arguments.of("0123456789", "truncate-width:11-prefix:0", "0123456789") + ); + } + + @ParameterizedTest + @MethodSource + void truncate(String value, String expression, String expected) { + assertThat(renderer.toString(value, expression, LOCALE)).isEqualTo(expected); + } +} diff --git a/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-render.adoc b/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-render.adoc index 561f977a..5b7e2390 100644 --- a/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-render.adoc +++ b/spring-shell-docs/src/main/asciidoc/using-shell-components-ui-render.adoc @@ -89,3 +89,14 @@ from a parent component types. The following tables show those context variables |The current cursor row in a selector. |=== + + +[[componentcontext-template-variables]] +.ComponentContext Template Variables +|=== +|Key |Description + +|`terminalWidth` +|The width of terminal, type is _Integer_ and defaults to _NULL_ if not set. + +|=== diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java index 11cc74fd..5136450d 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentCommands.java @@ -39,6 +39,7 @@ import org.springframework.shell.component.support.SelectorItem; import org.springframework.shell.standard.AbstractShellComponent; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; +import org.springframework.shell.standard.ShellOption; import org.springframework.util.StringUtils; @ShellComponent @@ -75,10 +76,16 @@ public class ComponentCommands extends AbstractShellComponent { } @ShellMethod(key = "component single", value = "Single selector", group = "Components") - public String singleSelector() { + public String singleSelector( + @ShellOption(defaultValue = ShellOption.NULL) Boolean longKeys + ) { List> items = new ArrayList<>(); items.add(SelectorItem.of("key1", "value1")); items.add(SelectorItem.of("key2", "value2")); + if (longKeys != null && longKeys == true) { + items.add(SelectorItem.of("key3 long long long long long", "value3")); + items.add(SelectorItem.of("key4 long long long long long long long long long long", "value4")); + } SingleItemSelector> component = new SingleItemSelector<>(getTerminal(), items, "testSimple", null); component.setResourceLoader(getResourceLoader()); @@ -90,11 +97,17 @@ public class ComponentCommands extends AbstractShellComponent { } @ShellMethod(key = "component multi", value = "Multi selector", group = "Components") - public String multiSelector() { + public String multiSelector( + @ShellOption(defaultValue = ShellOption.NULL) Boolean longKeys + ) { List> items = new ArrayList<>(); items.add(SelectorItem.of("key1", "value1")); items.add(SelectorItem.of("key2", "value2", false, true)); items.add(SelectorItem.of("key3", "value3")); + if (longKeys != null && longKeys == true) { + items.add(SelectorItem.of("key4 long long long long long", "value4", false, true)); + items.add(SelectorItem.of("key5 long long long long long long long long long long", "value5")); + } MultiItemSelector> component = new MultiItemSelector<>(getTerminal(), items, "testSimple", null); component.setResourceLoader(getResourceLoader());