blob: e7e9053e4ccca734f5ebd8231c9d1c07a09a692b [file] [log] [blame]
/*
* Copyright 2003-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.tools.shell.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import jline.ANSIBuffer.ANSICodes;
import jline.Terminal;
/**
* Provides support for using ANSI color escape codes.
*
* @version $Id$
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
*/
public class ANSI
{
//
// Detection/Enabled Muck
//
/**
* Tries to detect if the current system supports ANSI.
*/
private static boolean detect() {
boolean enabled = Terminal.getTerminal().isANSISupported();
if (!enabled) {
String force = System.getProperty(ANSI.class.getName() + ".force", "false");
enabled = Boolean.valueOf(force).booleanValue();
}
return enabled;
}
public static boolean isDetected() {
return detect();
}
private static Boolean enabled;
public static void setEnabled(final boolean flag) {
enabled = Boolean.valueOf(flag);
}
public static boolean isEnabled() {
if (enabled == null) {
enabled = Boolean.valueOf(isDetected());
}
return enabled.booleanValue();
}
//
// Code
//
public static class Code
{
//
// NOTE: Some fields duplicated from jline.ANSIBuffer.ANSICodes to change access modifiers
//
public static final int OFF = 0;
public static final int BOLD = 1;
public static final int UNDERSCORE = 4;
public static final int BLINK = 5;
public static final int REVERSE = 7;
public static final int CONCEALED = 8;
public static final int FG_BLACK = 30;
public static final int FG_RED = 31;
public static final int FG_GREEN = 32;
public static final int FG_YELLOW = 33;
public static final int FG_BLUE = 34;
public static final int FG_MAGENTA = 35;
public static final int FG_CYAN = 36;
public static final int FG_WHITE = 37;
public static final int BLACK = FG_BLACK;
public static final int RED = FG_RED;
public static final int GREEN = FG_GREEN;
public static final int YELLOW = FG_YELLOW;
public static final int BLUE = FG_BLUE;
public static final int MAGENTA = FG_MAGENTA;
public static final int CYAN = FG_CYAN;
public static final int WHITE = FG_WHITE;
public static final int BG_BLACK = 40;
public static final int BG_RED = 41;
public static final int BG_GREEN = 42;
public static final int BG_YELLOW = 43;
public static final int BG_BLUE = 44;
public static final int BG_MAGENTA = 45;
public static final int BG_CYAN = 46;
public static final int BG_WHITE = 47;
/** A map of code names to values. */
private static final Map NAMES_TO_CODES;
/** A map of codes to name. */
private static final Map CODES_TO_NAMES;
static {
Field[] fields = Code.class.getDeclaredFields();
Map names = new HashMap(fields.length);
Map codes = new HashMap(fields.length);
try {
for (int i=0; i<fields.length; i++) {
// Skip anything non-public, all public fields are codes
int mods = fields[i].getModifiers();
if (!Modifier.isPublic(mods)) {
continue;
}
String name = fields[i].getName();
Number code = (Number) fields[i].get(Code.class);
names.put(name, code);
codes.put(code, name);
}
}
catch (IllegalAccessException e) {
// This should never happen
throw new Error(e);
}
NAMES_TO_CODES = names;
CODES_TO_NAMES = codes;
}
/**
* Returns the ANSI code for the given symbolic name. Supported symbolic names are all defined as
* fields in {@link org.codehaus.groovy.tools.shell.util.ANSI.Code} where the case is not significant.
*/
public static int forName(final String name) throws IllegalArgumentException {
assert name != null;
// All names in the map are upper-case
String tmp = name.toUpperCase();
Number code = (Number) NAMES_TO_CODES.get(tmp);
if (code == null) {
throw new IllegalArgumentException("Invalid ANSI code name: " + name);
}
return code.intValue();
}
/**
* Returns the symbolic name for the given ANSI code.
*/
public static String name(final int code) throws IllegalArgumentException {
String name = (String) CODES_TO_NAMES.get(new Integer(code));
if (name == null) {
throw new IllegalArgumentException("Invalid ANSI code: " + code);
}
return name;
}
}
//
// Buffer
//
public static class Buffer
{
private final StringBuffer buff = new StringBuffer();
public final boolean autoClear = true;
public String toString() {
try {
return buff.toString();
}
finally {
if (autoClear) clear();
}
}
public void clear() {
buff.setLength(0);
}
public int size() {
return buff.length();
}
public Buffer append(final String text) {
buff.append(text);
return this;
}
public Buffer append(final Object obj) {
return append(String.valueOf(obj));
}
public Buffer attrib(final int code) {
if (isEnabled()) {
buff.append(ANSICodes.attrib(code));
}
return this;
}
public Buffer attrib(final String text, final int code) {
assert text != null;
if (isEnabled()) {
buff.append(ANSICodes.attrib(code)).append(text).append(ANSICodes.attrib(Code.OFF));
}
else {
buff.append(text);
}
return this;
}
public Buffer attrib(final String text, final String codeName) {
return attrib(text, Code.forName(codeName));
}
}
//
// Renderer
//
public static class Renderer
{
public static final String BEGIN_TOKEN = "@|";
private static final int BEGIN_TOKEN_SIZE = BEGIN_TOKEN.length();
public static final String END_TOKEN = "|";
private static final int END_TOKEN_SIZE = END_TOKEN.length();
public static final String CODE_TEXT_SEPARATOR = " ";
public static final String CODE_LIST_SEPARATOR = ",";
private final Buffer buff = new Buffer();
public String render(final String input) throws RenderException {
assert input != null;
// current, prefix and suffix positions
int c = 0, p, s;
while (c < input.length()) {
p = input.indexOf(BEGIN_TOKEN, c);
if (p < 0) { break; }
s = input.indexOf(END_TOKEN, p + BEGIN_TOKEN_SIZE);
if (s < 0) {
throw new RenderException("Missing '" + END_TOKEN + "': " + input);
}
String expr = input.substring(p + BEGIN_TOKEN_SIZE, s);
buff.append(input.substring(c, p));
evaluate(expr);
c = s + END_TOKEN_SIZE;
}
buff.append(input.substring(c));
return buff.toString();
}
private void evaluate(final String input) throws RenderException {
assert input != null;
int i = input.indexOf(CODE_TEXT_SEPARATOR);
if (i < 0) {
throw new RenderException("Missing ANSI code/text separator '" + CODE_TEXT_SEPARATOR + "': " + input);
}
String tmp = input.substring(0, i);
String[] codes = tmp.split(CODE_LIST_SEPARATOR);
String text = input.substring(i + 1, input.length());
for (int j=0; j<codes.length; j++) {
int code = Code.forName(codes[j]);
buff.attrib(code);
}
buff.append(text);
buff.attrib(Code.OFF);
}
//
// RenderException
//
public static class RenderException
extends RuntimeException
{
public RenderException(final String msg) {
super(msg);
}
}
//
// Helpers
//
public static boolean test(final String text) {
return text != null && text.indexOf(BEGIN_TOKEN) >= 0;
}
public static String encode(final String text, final int code) {
return new StringBuffer(BEGIN_TOKEN).
append(Code.name(code)).
append(CODE_TEXT_SEPARATOR).
append(text).
append(END_TOKEN).
toString();
}
}
//
// RenderWriter
//
public static class RenderWriter
extends PrintWriter
{
private final Renderer renderer = new Renderer();
public RenderWriter(final OutputStream out) {
super(out);
}
public RenderWriter(final OutputStream out, final boolean autoFlush) {
super(out, autoFlush);
}
public RenderWriter(final Writer out) {
super(out);
}
public RenderWriter(final Writer out, final boolean autoFlush) {
super(out, autoFlush);
}
public void write(final String s) {
if (Renderer.test(s)) {
super.write(renderer.render(s));
}
else {
super.write(s);
}
}
}
//
// RenderMessageSource
//
public static class RenderMessageSource
extends MessageSource
{
private final Renderer renderer = new Renderer();
public RenderMessageSource(final String[] names) {
super(names);
}
public RenderMessageSource(final String name) {
super(name);
}
public RenderMessageSource(final Class[] types) {
super(types);
}
public RenderMessageSource(final Class type) {
super(type);
}
public String getMessage(final String code) {
final String msg = super.getMessage(code);
if (Renderer.test(msg)) {
return renderer.render(msg);
}
return msg;
}
}
}