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