001 /*
002 * Copyright (C) 2009 the original author(s).
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.fusesource.jansi;
018
019 import org.fusesource.jansi.Ansi.Attribute;
020 import org.fusesource.jansi.Ansi.Color;
021
022 /**
023 * Renders ANSI color escape-codes in strings by parsing out some special syntax to pick up the correct fluff to use.
024 *
025 * <p/>
026 * The syntax for embedded ANSI codes is:
027 *
028 * <pre>
029 * <tt>@|</tt><em>code</em>(<tt>,</tt><em>code</em>)* <em>text</em><tt>|@</tt>
030 * </pre>
031 *
032 * Examples:
033 *
034 * <pre>
035 * <tt>@|bold Hello|@</tt>
036 * </pre>
037 *
038 * <pre>
039 * <tt>@|bold,red Warning!|@</tt>
040 * </pre>
041 *
042 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
043 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
044 * @since 1.1
045 */
046 public class AnsiRenderer
047 {
048 public static final String BEGIN_TOKEN = "@|";
049
050 private static final int BEGIN_TOKEN_LEN = 2;
051
052 public static final String END_TOKEN = "|@";
053
054 private static final int END_TOKEN_LEN = 2;
055
056 public static final String CODE_TEXT_SEPARATOR = " ";
057
058 public static final String CODE_LIST_SEPARATOR = ",";
059
060 static public String render(final String input) throws IllegalArgumentException {
061 StringBuffer buff = new StringBuffer();
062
063 int i = 0;
064 int j, k;
065
066 while (true) {
067 j = input.indexOf(BEGIN_TOKEN, i);
068 if (j == -1) {
069 if (i == 0) {
070 return input;
071 }
072 else {
073 buff.append(input.substring(i, input.length()));
074 return buff.toString();
075 }
076 }
077 else {
078 buff.append(input.substring(i, j));
079 k = input.indexOf(END_TOKEN, j);
080
081 if (k == -1) {
082 return input;
083 }
084 else {
085 j += BEGIN_TOKEN_LEN;
086 String spec = input.substring(j, k);
087
088 String[] items = spec.split(CODE_TEXT_SEPARATOR, 2);
089 if (items.length == 1) {
090 return input;
091 }
092 String replacement = render(items[1], items[0].split(CODE_LIST_SEPARATOR));
093
094 buff.append(replacement);
095
096 i = k + END_TOKEN_LEN;
097 }
098 }
099 }
100 }
101
102 static private String render(final String text, final String... codes) {
103 Ansi ansi = Ansi.ansi();
104 for (String name : codes) {
105 Code code = Code.valueOf(name.toUpperCase());
106
107 if (code.isColor()) {
108 if (code.isBackground()) {
109 ansi = ansi.bg(code.getColor());
110 }
111 else {
112 ansi = ansi.fg(code.getColor());
113 }
114 }
115 else if (code.isAttribute()) {
116 ansi = ansi.a(code.getAttribute());
117 }
118 }
119
120 return ansi.a(text).reset().toString();
121 }
122
123 public static boolean test(final String text) {
124 return text != null && text.contains(BEGIN_TOKEN);
125 }
126
127 public static enum Code
128 {
129 //
130 // TODO: Find a better way to keep Code in sync with Color/Attribute/Erase
131 //
132
133 // Colors
134 BLACK(Color.BLACK),
135 RED(Color.RED),
136 GREEN(Color.GREEN),
137 YELLOW(Color.YELLOW),
138 BLUE(Color.BLUE),
139 MAGENTA(Color.MAGENTA),
140 CYAN(Color.CYAN),
141 WHITE(Color.WHITE),
142
143 // Foreground Colors
144 FG_BLACK(Color.BLACK, false),
145 FG_RED(Color.RED, false),
146 FG_GREEN(Color.GREEN, false),
147 FG_YELLOW(Color.YELLOW, false),
148 FG_BLUE(Color.BLUE, false),
149 FG_MAGENTA(Color.MAGENTA, false),
150 FG_CYAN(Color.CYAN, false),
151 FG_WHITE(Color.WHITE, false),
152
153 // Background Colors
154 BG_BLACK(Color.BLACK, true),
155 BG_RED(Color.RED, true),
156 BG_GREEN(Color.GREEN, true),
157 BG_YELLOW(Color.YELLOW, true),
158 BG_BLUE(Color.BLUE, true),
159 BG_MAGENTA(Color.MAGENTA, true),
160 BG_CYAN(Color.CYAN, true),
161 BG_WHITE(Color.WHITE, true),
162
163 // Attributes
164 RESET(Attribute.RESET),
165 INTENSITY_BOLD(Attribute.INTENSITY_BOLD),
166 INTENSITY_FAINT(Attribute.INTENSITY_FAINT),
167 ITALIC(Attribute.ITALIC),
168 UNDERLINE(Attribute.UNDERLINE),
169 BLINK_SLOW(Attribute.BLINK_SLOW),
170 BLINK_FAST(Attribute.BLINK_FAST),
171 BLINK_OFF(Attribute.BLINK_OFF),
172 NEGATIVE_ON(Attribute.NEGATIVE_ON),
173 NEGATIVE_OFF(Attribute.NEGATIVE_OFF),
174 CONCEAL_ON(Attribute.CONCEAL_ON),
175 CONCEAL_OFF(Attribute.CONCEAL_OFF),
176 UNDERLINE_DOUBLE(Attribute.UNDERLINE_DOUBLE),
177 UNDERLINE_OFF(Attribute.UNDERLINE_OFF),
178
179 // Aliases
180 BOLD(Attribute.INTENSITY_BOLD),
181 FAINT(Attribute.INTENSITY_FAINT),;
182
183 @SuppressWarnings("unchecked")
184 private final Enum n;
185
186 private final boolean background;
187
188 @SuppressWarnings("unchecked")
189 private Code(final Enum n, boolean background) {
190 this.n = n;
191 this.background = background;
192 }
193
194 @SuppressWarnings("unchecked")
195 private Code(final Enum n) {
196 this(n, false);
197 }
198
199 public boolean isColor() {
200 return n instanceof Ansi.Color;
201 }
202
203 public Ansi.Color getColor() {
204 return (Ansi.Color) n;
205 }
206
207 public boolean isAttribute() {
208 return n instanceof Attribute;
209 }
210
211 public Attribute getAttribute() {
212 return (Attribute) n;
213 }
214
215 public boolean isBackground() {
216 return background;
217 }
218 }
219 }