Commit 69c44faf authored by Phillip Webb's avatar Phillip Webb

Merge pull request #2704 from sailorgeoffrey/master

* pr/2704:
  Add support for ANSI colored resource banners
parents 9f143ad9 d6200389
......@@ -72,6 +72,10 @@ display (surrounded with brackets and prefixed with `v`). For example `(v1.0)`.
|`${spring-boot.formatted-version}`
|The Spring Boot version that you are using formatted for display (surrounded with
brackets and prefixed with `v`). For example `(v{spring-boot-version})`.
|`${Ansi.NAME}`,
|Where `NAME` is the name of an ANSI escape code. See
{sc-spring-boot}/ansi/AnsiPropertySource.{sc-ext}[`AnsiPropertySource`] for details.
|===
TIP: The `SpringBootApplication.setBanner(...)` method can be used if you want to generate
......
......@@ -25,6 +25,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.ansi.AnsiPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
......@@ -77,6 +78,7 @@ public class ResourceBanner implements Banner {
List<PropertyResolver> resolvers = new ArrayList<PropertyResolver>();
resolvers.add(environment);
resolvers.add(getVersionResolver(sourceClass));
resolvers.add(getAnsiResolver());
return resolvers;
}
......@@ -114,4 +116,10 @@ public class ResourceBanner implements Banner {
return (format ? " (v" + version + ")" : version);
}
private PropertyResolver getAnsiResolver() {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new AnsiPropertySource("ansi", true));
return new PropertySourcesPropertyResolver(sources);
}
}
......@@ -21,9 +21,9 @@ import java.io.PrintStream;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.core.env.Environment;
import static org.springframework.boot.ansi.AnsiElement.DEFAULT;
import static org.springframework.boot.ansi.AnsiElement.FAINT;
import static org.springframework.boot.ansi.AnsiElement.GREEN;
import static org.springframework.boot.ansi.AnsiColor.DEFAULT;
import static org.springframework.boot.ansi.AnsiColor.GREEN;
import static org.springframework.boot.ansi.AnsiStyle.FAINT;
/**
* Default Banner implementation which writes the 'Spring' banner.
......
/*
* Copyright 2012-2015 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
*
* http://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.boot.ansi;
/**
* {@link AnsiElement Ansi} background colors.
*
* @author Phillip Webb
* @author Geoffrey Chandler
* @since 1.3.0
*/
public enum AnsiBackground implements AnsiElement {
DEFAULT("49"),
BLACK("40"),
RED("41"),
GREEN("42"),
YELLOW("43"),
BLUE("44"),
MAGENTA("45"),
CYAN("46"),
WHITE("47"),
BRIGHT_BLACK("100"),
BRIGHT_RED("101"),
BRIGHT_GREEN("102"),
BRIGHT_YELLOW("103"),
BRIGHT_BLUE("104"),
BRIGHT_MAGENTA("105"),
BRIGHT_CYAN("106"),
BRIGHT_WHITE("107");
private String code;
private AnsiBackground(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}
/*
* Copyright 2012-2015 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
*
* http://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.boot.ansi;
/**
* {@link AnsiElement Ansi} colors.
*
* @author Phillip Webb
* @author Geoffrey Chandler
* @since 1.3.0
*/
public enum AnsiColor implements AnsiElement {
DEFAULT("39"),
BLACK("30"),
RED("31"),
GREEN("32"),
YELLOW("33"),
BLUE("34"),
MAGENTA("35"),
CYAN("36"),
WHITE("37"),
BRIGHT_BLACK("90"),
BRIGHT_RED("91"),
BRIGHT_GREEN("92"),
BRIGHT_YELLOW("93"),
BRIGHT_BLUE("94"),
BRIGHT_MAGENTA("95"),
BRIGHT_CYAN("96"),
BRIGHT_WHITE("97");
private final String code;
private AnsiColor(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}
......@@ -23,32 +23,88 @@ package org.springframework.boot.ansi;
*/
public interface AnsiElement {
/**
* @deprecated in 1.3.0 in favor of {@link AnsiStyle#NORMAL}
*/
@Deprecated
public static final AnsiElement NORMAL = new DefaultAnsiElement("0");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiStyle#BOLD}
*/
@Deprecated
public static final AnsiElement BOLD = new DefaultAnsiElement("1");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiStyle#FAINT}
*/
@Deprecated
public static final AnsiElement FAINT = new DefaultAnsiElement("2");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiStyle#ITALIC}
*/
@Deprecated
public static final AnsiElement ITALIC = new DefaultAnsiElement("3");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiStyle#UNDERLINE}
*/
@Deprecated
public static final AnsiElement UNDERLINE = new DefaultAnsiElement("4");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#BLACK}
*/
@Deprecated
public static final AnsiElement BLACK = new DefaultAnsiElement("30");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#RED}
*/
@Deprecated
public static final AnsiElement RED = new DefaultAnsiElement("31");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#GREEN}
*/
@Deprecated
public static final AnsiElement GREEN = new DefaultAnsiElement("32");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#YELLOW}
*/
@Deprecated
public static final AnsiElement YELLOW = new DefaultAnsiElement("33");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#BLUE}
*/
@Deprecated
public static final AnsiElement BLUE = new DefaultAnsiElement("34");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#MAGENTA}
*/
@Deprecated
public static final AnsiElement MAGENTA = new DefaultAnsiElement("35");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#CYAN}
*/
@Deprecated
public static final AnsiElement CYAN = new DefaultAnsiElement("36");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#WHITE}
*/
@Deprecated
public static final AnsiElement WHITE = new DefaultAnsiElement("37");
/**
* @deprecated in 1.3.0 in favor of {@link AnsiColor#DEFAULT}
*/
@Deprecated
public static final AnsiElement DEFAULT = new DefaultAnsiElement("39");
/**
......
......@@ -39,7 +39,7 @@ public abstract class AnsiOutput {
private static final String ENCODE_END = "m";
private static final String RESET = "0;" + AnsiElement.DEFAULT;
private static final String RESET = "0;" + AnsiColor.DEFAULT;
/**
* Sets if ANSI output is enabled.
......@@ -63,6 +63,18 @@ public abstract class AnsiOutput {
return AnsiOutput.enabled;
}
/**
* Encode a single {@link AnsiElement} if output is enabled.
* @param element the element to encode
* @return the encoded element or an empty string
*/
public static String encode(AnsiElement element) {
if (isEnabled()) {
return ENCODE_START + element + ENCODE_END;
}
return "";
}
/**
* Create a new ANSI string from the specified elements. Any {@link AnsiElement}s will
* be encoded as required.
......
/*
* Copyright 2012-2015 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
*
* http://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.boot.ansi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
/**
* {@link PropertyResolver} for {@link AnsiStyle}, {@link AnsiColor} and
* {@link AnsiBackground} elements. Supports properties of the form {@code AnsiStyle.BOLD}
* , {@code AnsiColor.RED} or {@code AnsiBackground.GREEN}. Also supports a prefix of
* {@code Ansi.} which is an aggregation of everything (with background colors prefixed
* {@code BG_}).
*
* @author Phillip Webb
* @since 1.3.0
*/
public class AnsiPropertySource extends PropertySource<AnsiElement> {
private static final Iterable<MappedEnum<?>> MAPPED_ENUMS;
static {
List<MappedEnum<?>> enums = new ArrayList<MappedEnum<?>>();
enums.add(new MappedEnum<AnsiStyle>("AnsiStyle.", AnsiStyle.class));
enums.add(new MappedEnum<AnsiColor>("AnsiColor.", AnsiColor.class));
enums.add(new MappedEnum<AnsiBackground>("AnsiBackground.", AnsiBackground.class));
enums.add(new MappedEnum<AnsiStyle>("Ansi.", AnsiStyle.class));
enums.add(new MappedEnum<AnsiColor>("Ansi.", AnsiColor.class));
enums.add(new MappedEnum<AnsiBackground>("Ansi.BG_", AnsiBackground.class));
MAPPED_ENUMS = Collections.unmodifiableList(enums);
}
private final boolean encode;
/**
* Create a new {@link AnsiPropertySource} instance.
* @param name the name of the property source
* @param encode if the output should be encoded
*/
public AnsiPropertySource(String name, boolean encode) {
super(name);
this.encode = encode;
}
@Override
public Object getProperty(String name) {
if (StringUtils.hasLength(name)) {
for (MappedEnum<?> mappedEnum : MAPPED_ENUMS) {
if (name.startsWith(mappedEnum.getPrefix())) {
String enumName = name.substring(mappedEnum.getPrefix().length());
for (Enum<?> ansiEnum : mappedEnum.getEnums()) {
if (ansiEnum.name().equals(enumName)) {
if (this.encode) {
return AnsiOutput.encode((AnsiElement) ansiEnum);
}
return ansiEnum;
}
}
}
}
}
return null;
}
/**
* Mapping between an enum and the pseudo property source.
*/
private static class MappedEnum<E extends Enum<E>> {
private final String prefix;
private final Set<E> enums;
public MappedEnum(String prefix, Class<E> enumType) {
this.prefix = prefix;
this.enums = EnumSet.allOf(enumType);
}
public String getPrefix() {
return this.prefix;
}
public Set<E> getEnums() {
return this.enums;
}
}
}
/*
* Copyright 2012-2015 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
*
* http://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.boot.ansi;
/**
* {@link AnsiElement Ansi} styles.
*
* @author Phillip Webb
* @since 1.3.0
*/
public enum AnsiStyle implements AnsiElement {
NORMAL("0"),
BOLD("1"),
FAINT("2"),
ITALIC("3"),
UNDERLINE("4");
private final String code;
private AnsiStyle(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}
......@@ -20,8 +20,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.ansi.AnsiColor;
import org.springframework.boot.ansi.AnsiElement;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiStyle;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
......@@ -39,21 +41,21 @@ public class ColorConverter extends CompositeConverter<ILoggingEvent> {
private static final Map<String, AnsiElement> ELEMENTS;
static {
Map<String, AnsiElement> elements = new HashMap<String, AnsiElement>();
elements.put("faint", AnsiElement.FAINT);
elements.put("red", AnsiElement.RED);
elements.put("green", AnsiElement.GREEN);
elements.put("yellow", AnsiElement.YELLOW);
elements.put("blue", AnsiElement.BLUE);
elements.put("magenta", AnsiElement.MAGENTA);
elements.put("cyan", AnsiElement.CYAN);
elements.put("faint", AnsiStyle.FAINT);
elements.put("red", AnsiColor.RED);
elements.put("green", AnsiColor.GREEN);
elements.put("yellow", AnsiColor.YELLOW);
elements.put("blue", AnsiColor.BLUE);
elements.put("magenta", AnsiColor.MAGENTA);
elements.put("cyan", AnsiColor.CYAN);
ELEMENTS = Collections.unmodifiableMap(elements);
}
private static final Map<Integer, AnsiElement> LEVELS;
static {
Map<Integer, AnsiElement> levels = new HashMap<Integer, AnsiElement>();
levels.put(Level.ERROR_INTEGER, AnsiElement.RED);
levels.put(Level.WARN_INTEGER, AnsiElement.YELLOW);
levels.put(Level.ERROR_INTEGER, AnsiColor.RED);
levels.put(Level.WARN_INTEGER, AnsiColor.YELLOW);
LEVELS = Collections.unmodifiableMap(levels);
}
......@@ -63,7 +65,7 @@ public class ColorConverter extends CompositeConverter<ILoggingEvent> {
if (element == null) {
// Assume highlighting
element = LEVELS.get(event.getLevel().toInteger());
element = (element == null ? AnsiElement.GREEN : element);
element = (element == null ? AnsiColor.GREEN : element);
}
return toAnsiString(in, element);
}
......
......@@ -21,7 +21,10 @@ import java.io.PrintStream;
import java.util.Collections;
import java.util.Map;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.ByteArrayResource;
......@@ -38,6 +41,11 @@ import static org.junit.Assert.assertThat;
*/
public class ResourceBannerTests {
@After
public void reset() {
AnsiOutput.setEnabled(Enabled.DETECT);
}
@Test
public void renderVersions() throws Exception {
Resource resource = new ByteArrayResource(
......@@ -72,6 +80,24 @@ public class ResourceBannerTests {
assertThat(banner, startsWith("banner 1"));
}
@Test
public void renderWithColors() throws Exception {
Resource resource = new ByteArrayResource(
"${Ansi.RED}This is red.${Ansi.NORMAL}".getBytes());
AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS);
String banner = printBanner(resource, null, null);
assertThat(banner, startsWith("\u001B[31mThis is red.\u001B[0m"));
}
@Test
public void renderWithColorsButDisabled() throws Exception {
Resource resource = new ByteArrayResource(
"${Ansi.RED}This is red.${Ansi.NORMAL}".getBytes());
AnsiOutput.setEnabled(AnsiOutput.Enabled.NEVER);
String banner = printBanner(resource, null, null);
assertThat(banner, startsWith("This is red."));
}
private String printBanner(Resource resource, String bootVersion,
String applicationVersion) {
ResourceBanner banner = new MockResourceBanner(resource, bootVersion,
......
/*
* Copyright 2012-2015 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
*
* http://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.boot.ansi;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link AnsiPropertySource}.
*
* @author Phillip Webb
*/
public class AnsiPropertySourceTests {
private AnsiPropertySource source = new AnsiPropertySource("ansi", false);
@After
public void reset() {
AnsiOutput.setEnabled(Enabled.DETECT);
}
@Test
public void getAnsiStyle() throws Exception {
assertThat(this.source.getProperty("AnsiStyle.BOLD"),
equalTo((Object) AnsiStyle.BOLD));
}
@Test
public void getAnsiColor() throws Exception {
assertThat(this.source.getProperty("AnsiColor.RED"),
equalTo((Object) AnsiColor.RED));
}
@Test
public void getAnsiBackground() throws Exception {
assertThat(this.source.getProperty("AnsiBackground.GREEN"),
equalTo((Object) AnsiBackground.GREEN));
}
@Test
public void getAnsi() throws Exception {
assertThat(this.source.getProperty("Ansi.BOLD"), equalTo((Object) AnsiStyle.BOLD));
assertThat(this.source.getProperty("Ansi.RED"), equalTo((Object) AnsiColor.RED));
assertThat(this.source.getProperty("Ansi.BG_RED"),
equalTo((Object) AnsiBackground.RED));
}
@Test
public void getMissing() throws Exception {
assertThat(this.source.getProperty("AnsiStyle.NOPE"), nullValue());
}
@Test
public void encodeEnabled() throws Exception {
AnsiOutput.setEnabled(Enabled.ALWAYS);
AnsiPropertySource source = new AnsiPropertySource("ansi", true);
assertThat(source.getProperty("Ansi.RED"), equalTo((Object) "\033[31m"));
}
@Test
public void encodeDisabled() throws Exception {
AnsiOutput.setEnabled(Enabled.NEVER);
AnsiPropertySource source = new AnsiPropertySource("ansi", true);
assertThat(source.getProperty("Ansi.RED"), equalTo((Object) ""));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment