blob: e1dd5e5ec32cea81643c6cd9cbc60447a4fa046c [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.lib.util.io;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.EnumSet;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* Allows to print "colored" strings by parsing predefined string keywords,
* which, depending on the useColor value are either replaced with ANSI terminal
* coloring sequences (as defined by the {@link AnsiTerminal} class) or stripped.
*
* Supported keywords are defined by the enum {@link AnsiTerminalPrinter.Mode}.
* Following keywords are supported:
* INFO - switches color to green.
* ERROR - switches color to bold red.
* WARNING - switches color to magenta.
* NORMAL - resets terminal to the default state.
*
* Each keyword is starts with prefix "{#" followed by the enum constant name
* and suffix "#}". Keywords should not be inserted manually - provided enum
* constants should be used instead.
*/
@ThreadCompatible
public class AnsiTerminalPrinter {
private static final String MODE_PREFIX = "{#";
private static final String MODE_SUFFIX = "#}";
// Mode pattern must match MODE_PREFIX and do lookahead for the rest of the
// mode string.
private static final String MODE_PATTERN = "\\{\\#(?=[A-Z]+\\#\\})";
/**
* List of supported coloring modes for the {@link AnsiTerminalPrinter}.
*/
public static enum Mode {
INFO, // green
ERROR, // bold red
WARNING, // magenta
DEFAULT; // default color
@Override
public String toString() {
return MODE_PREFIX + name() + MODE_SUFFIX;
}
}
private static final Logger logger = Logger.getLogger(AnsiTerminalPrinter.class.getName());
private static final EnumSet<Mode> MODES = EnumSet.allOf(Mode.class);
private static final Pattern PATTERN = Pattern.compile(MODE_PATTERN);
private final OutputStream stream;
private final PrintWriter writer;
private final AnsiTerminal terminal;
private boolean useColor;
private Mode lastMode = Mode.DEFAULT;
/**
* Creates new instance using provided OutputStream and sets coloring logic
* for that instance.
*/
public AnsiTerminalPrinter(OutputStream out, boolean useColor) {
this.useColor = useColor;
terminal = new AnsiTerminal(out);
writer = new PrintWriter(out, true);
stream = out;
}
/**
* Writes the specified string to the output stream while injecting coloring
* sequences when appropriate mode keyword is found and flushes.
*
* List of supported mode keywords is defined by the enum {@link Mode}.
*
* See class documentation for details.
*/
public void print(String str) {
for (String part : PATTERN.split(str)) {
int index = part.indexOf(MODE_SUFFIX);
// Mode name will contain at least one character, so suffix index
// must be at least 1. If it isn't then there is no match.
if (index > 1) {
for (Mode mode : MODES) {
if (index == mode.name().length() && part.startsWith(mode.name())) {
setupTerminal(mode);
part = part.substring(index + MODE_SUFFIX.length());
break;
}
}
}
writer.print(part);
writer.flush();
}
}
public void printLn(String str) {
print(str + "\n");
}
/**
* Returns the underlying OutputStream.
*/
public OutputStream getOutputStream() {
return stream;
}
/**
* Injects coloring escape sequences if output should be colored and mode
* has been changed.
*/
private void setupTerminal(Mode mode) {
if (!useColor) {
return;
}
try {
if (lastMode != mode) {
terminal.resetTerminal();
lastMode = mode;
if (mode == Mode.DEFAULT) {
return; // Terminal is already reset - nothing else to do.
} else if (mode == Mode.INFO) {
terminal.textGreen();
} else if (mode == Mode.ERROR) {
terminal.textRed();
terminal.textBold();
} else if (mode == Mode.WARNING) {
terminal.textMagenta();
}
}
} catch (IOException e) {
// AnsiTerminal state is now considered to be inconsistent - coloring
// should be disabled to prevent future use of AnsiTerminal instance.
logger.warning("Disabling coloring due to " + e.getMessage());
useColor = false;
}
}
}