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 }