001/* 002 * Copyright (C) 2009-2017 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 */ 016package org.fusesource.jansi; 017 018import java.io.IOException; 019import java.util.Locale; 020 021import org.fusesource.jansi.Ansi.Attribute; 022import org.fusesource.jansi.Ansi.Color; 023 024/** 025 * Renders ANSI color escape-codes in strings by parsing out some special syntax to pick up the correct fluff to use. 026 * 027 * The syntax for embedded ANSI codes is: 028 * 029 * <pre> 030 * <tt>@|</tt><em>code</em>(<tt>,</tt><em>code</em>)* <em>text</em><tt>|@</tt> 031 * </pre> 032 * 033 * Examples: 034 * 035 * <pre> 036 * <tt>@|bold Hello|@</tt> 037 * </pre> 038 * 039 * <pre> 040 * <tt>@|bold,red Warning!|@</tt> 041 * </pre> 042 * 043 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a> 044 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 045 * @since 1.1 046 */ 047public class AnsiRenderer { 048 049 public static final String BEGIN_TOKEN = "@|"; 050 051 public static final String END_TOKEN = "|@"; 052 053 public static final String CODE_TEXT_SEPARATOR = " "; 054 055 public static final String CODE_LIST_SEPARATOR = ","; 056 057 private static final int BEGIN_TOKEN_LEN = 2; 058 059 private static final int END_TOKEN_LEN = 2; 060 061 public static String render(final String input) throws IllegalArgumentException { 062 try { 063 return render(input, new StringBuilder()).toString(); 064 } catch (IOException e) { 065 // Cannot happen because StringBuilder does not throw IOException 066 throw new IllegalArgumentException(e); 067 } 068 } 069 070 /** 071 * Renders the given input to the target Appendable. 072 * 073 * @param input 074 * source to render 075 * @param target 076 * render onto this target Appendable. 077 * @return the given Appendable 078 * @throws IOException 079 * If an I/O error occurs 080 */ 081 public static Appendable render(final String input, Appendable target) throws IOException { 082 083 int i = 0; 084 int j, k; 085 086 while (true) { 087 j = input.indexOf(BEGIN_TOKEN, i); 088 if (j == -1) { 089 if (i == 0) { 090 target.append(input); 091 return target; 092 } 093 target.append(input.substring(i, input.length())); 094 return target; 095 } 096 target.append(input.substring(i, j)); 097 k = input.indexOf(END_TOKEN, j); 098 099 if (k == -1) { 100 target.append(input); 101 return target; 102 } 103 j += BEGIN_TOKEN_LEN; 104 String spec = input.substring(j, k); 105 106 String[] items = spec.split(CODE_TEXT_SEPARATOR, 2); 107 if (items.length == 1) { 108 target.append(input); 109 return target; 110 } 111 String replacement = render(items[1], items[0].split(CODE_LIST_SEPARATOR)); 112 113 target.append(replacement); 114 115 i = k + END_TOKEN_LEN; 116 } 117 } 118 119 public static String render(final String text, final String... codes) { 120 return render(Ansi.ansi(), codes) 121 .a(text).reset().toString(); 122 } 123 124 /** 125 * Renders {@link Code} names as an ANSI escape string. 126 * @param codes The code names to render 127 * @return an ANSI escape string. 128 */ 129 public static String renderCodes(final String... codes) { 130 return render(Ansi.ansi(), codes).toString(); 131 } 132 133 /** 134 * Renders {@link Code} names as an ANSI escape string. 135 * @param codes A space separated list of code names to render 136 * @return an ANSI escape string. 137 */ 138 public static String renderCodes(final String codes) { 139 return renderCodes(codes.split("\\s")); 140 } 141 142 private static Ansi render(Ansi ansi, String... names) { 143 for (String name : names) { 144 render(ansi, name); 145 } 146 return ansi; 147 } 148 149 private static Ansi render(Ansi ansi, String name) { 150 Code code = Code.valueOf(name.toUpperCase(Locale.ENGLISH)); 151 if (code.isColor()) { 152 if (code.isBackground()) { 153 ansi.bg(code.getColor()); 154 } else { 155 ansi.fg(code.getColor()); 156 } 157 } else if (code.isAttribute()) { 158 ansi.a(code.getAttribute()); 159 } 160 return ansi; 161 } 162 163 public static boolean test(final String text) { 164 return text != null && text.contains(BEGIN_TOKEN); 165 } 166 167 public enum Code { 168 // 169 // TODO: Find a better way to keep Code in sync with Color/Attribute/Erase 170 // 171 172 // Colors 173 BLACK(Color.BLACK), 174 RED(Color.RED), 175 GREEN(Color.GREEN), 176 YELLOW(Color.YELLOW), 177 BLUE(Color.BLUE), 178 MAGENTA(Color.MAGENTA), 179 CYAN(Color.CYAN), 180 WHITE(Color.WHITE), 181 182 // Foreground Colors 183 FG_BLACK(Color.BLACK, false), 184 FG_RED(Color.RED, false), 185 FG_GREEN(Color.GREEN, false), 186 FG_YELLOW(Color.YELLOW, false), 187 FG_BLUE(Color.BLUE, false), 188 FG_MAGENTA(Color.MAGENTA, false), 189 FG_CYAN(Color.CYAN, false), 190 FG_WHITE(Color.WHITE, false), 191 192 // Background Colors 193 BG_BLACK(Color.BLACK, true), 194 BG_RED(Color.RED, true), 195 BG_GREEN(Color.GREEN, true), 196 BG_YELLOW(Color.YELLOW, true), 197 BG_BLUE(Color.BLUE, true), 198 BG_MAGENTA(Color.MAGENTA, true), 199 BG_CYAN(Color.CYAN, true), 200 BG_WHITE(Color.WHITE, true), 201 202 // Attributes 203 RESET(Attribute.RESET), 204 INTENSITY_BOLD(Attribute.INTENSITY_BOLD), 205 INTENSITY_FAINT(Attribute.INTENSITY_FAINT), 206 ITALIC(Attribute.ITALIC), 207 UNDERLINE(Attribute.UNDERLINE), 208 BLINK_SLOW(Attribute.BLINK_SLOW), 209 BLINK_FAST(Attribute.BLINK_FAST), 210 BLINK_OFF(Attribute.BLINK_OFF), 211 NEGATIVE_ON(Attribute.NEGATIVE_ON), 212 NEGATIVE_OFF(Attribute.NEGATIVE_OFF), 213 CONCEAL_ON(Attribute.CONCEAL_ON), 214 CONCEAL_OFF(Attribute.CONCEAL_OFF), 215 UNDERLINE_DOUBLE(Attribute.UNDERLINE_DOUBLE), 216 UNDERLINE_OFF(Attribute.UNDERLINE_OFF), 217 218 // Aliases 219 BOLD(Attribute.INTENSITY_BOLD), 220 FAINT(Attribute.INTENSITY_FAINT),; 221 222 @SuppressWarnings("unchecked") 223 private final Enum n; 224 225 private final boolean background; 226 227 @SuppressWarnings("unchecked") 228 Code(final Enum n, boolean background) { 229 this.n = n; 230 this.background = background; 231 } 232 233 @SuppressWarnings("unchecked") 234 Code(final Enum n) { 235 this(n, false); 236 } 237 238 public boolean isColor() { 239 return n instanceof Ansi.Color; 240 } 241 242 public Ansi.Color getColor() { 243 return (Ansi.Color) n; 244 } 245 246 public boolean isAttribute() { 247 return n instanceof Attribute; 248 } 249 250 public Attribute getAttribute() { 251 return (Attribute) n; 252 } 253 254 public boolean isBackground() { 255 return background; 256 } 257 } 258 259 private AnsiRenderer() { 260 } 261 262}