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    }