001/* 002 * Copyright (C) 2009-2017 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 static org.fusesource.jansi.internal.CLibrary.STDERR_FILENO; 019import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO; 020import static org.fusesource.jansi.internal.CLibrary.isatty; 021 022import java.io.FilterOutputStream; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.io.PrintStream; 026import java.util.Locale; 027 028/** 029 * Provides consistent access to an ANSI aware console PrintStream or an ANSI codes stripping PrintStream 030 * if not on a terminal (see 031 * <a href="http://fusesource.github.io/jansi/documentation/native-api/index.html?org/fusesource/jansi/internal/CLibrary.html">Jansi native isatty(int)</a>). 032 * 033 * @author <a href="http://hiramchirino.com">Hiram Chirino</a> 034 * @since 1.0 035 * @see #systemInstall() 036 * @see #wrapPrintStream(PrintStream, int) wrapPrintStream(PrintStream, int) for more details on ANSI mode selection 037 */ 038public class AnsiConsole { 039 040 public static final PrintStream system_out = System.out; 041 public static final PrintStream out; 042 043 public static final PrintStream system_err = System.err; 044 public static final PrintStream err; 045 046 static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win"); 047 048 static final boolean IS_CYGWIN = IS_WINDOWS 049 && System.getenv("PWD") != null 050 && System.getenv("PWD").startsWith("/") 051 && !"cygwin".equals(System.getenv("TERM")); 052 053 static final boolean IS_MINGW_XTERM = IS_WINDOWS 054 && System.getenv("MSYSTEM") != null 055 && System.getenv("MSYSTEM").startsWith("MINGW") 056 && "xterm".equals(System.getenv("TERM")); 057 058 private static JansiOutputType jansiOutputType; 059 static final JansiOutputType JANSI_STDOUT_TYPE; 060 static final JansiOutputType JANSI_STDERR_TYPE; 061 static { 062 out = wrapSystemOut(system_out); 063 JANSI_STDOUT_TYPE = jansiOutputType; 064 err = wrapSystemErr(system_err); 065 JANSI_STDERR_TYPE = jansiOutputType; 066 } 067 068 private static int installed; 069 070 private AnsiConsole() { 071 } 072 073 @Deprecated 074 public static OutputStream wrapOutputStream(final OutputStream stream) { 075 try { 076 return wrapOutputStream(stream, STDOUT_FILENO); 077 } catch (Throwable ignore) { 078 return wrapOutputStream(stream, 1); 079 } 080 } 081 082 public static PrintStream wrapSystemOut(final PrintStream ps) { 083 try { 084 return wrapPrintStream(ps, STDOUT_FILENO); 085 } catch (Throwable ignore) { 086 return wrapPrintStream(ps, 1); 087 } 088 } 089 090 @Deprecated 091 public static OutputStream wrapErrorOutputStream(final OutputStream stream) { 092 try { 093 return wrapOutputStream(stream, STDERR_FILENO); 094 } catch (Throwable ignore) { 095 return wrapOutputStream(stream, 2); 096 } 097 } 098 099 public static PrintStream wrapSystemErr(final PrintStream ps) { 100 try { 101 return wrapPrintStream(ps, STDERR_FILENO); 102 } catch (Throwable ignore) { 103 return wrapPrintStream(ps, 2); 104 } 105 } 106 107 @Deprecated 108 public static OutputStream wrapOutputStream(final OutputStream stream, int fileno) { 109 110 // If the jansi.passthrough property is set, then don't interpret 111 // any of the ansi sequences. 112 if (Boolean.getBoolean("jansi.passthrough")) { 113 jansiOutputType = JansiOutputType.PASSTHROUGH; 114 return stream; 115 } 116 117 // If the jansi.strip property is set, then we just strip the 118 // the ansi escapes. 119 if (Boolean.getBoolean("jansi.strip")) { 120 jansiOutputType = JansiOutputType.STRIP_ANSI; 121 return new AnsiOutputStream(stream); 122 } 123 124 if (IS_WINDOWS && !IS_CYGWIN && !IS_MINGW_XTERM) { 125 126 // On windows we know the console does not interpret ANSI codes.. 127 try { 128 jansiOutputType = JansiOutputType.WINDOWS; 129 return new WindowsAnsiOutputStream(stream); 130 } catch (Throwable ignore) { 131 // this happens when JNA is not in the path.. or 132 // this happens when the stdout is being redirected to a file. 133 } 134 135 // Use the ANSIOutputStream to strip out the ANSI escape sequences. 136 jansiOutputType = JansiOutputType.STRIP_ANSI; 137 return new AnsiOutputStream(stream); 138 } 139 140 // We must be on some Unix variant, including Cygwin or MSYS(2) on Windows... 141 try { 142 // If the jansi.force property is set, then we force to output 143 // the ansi escapes for piping it into ansi color aware commands (e.g. less -r) 144 boolean forceColored = Boolean.getBoolean("jansi.force"); 145 // If we can detect that stdout is not a tty.. then setup 146 // to strip the ANSI sequences.. 147 if (!forceColored && isatty(fileno) == 0) { 148 jansiOutputType = JansiOutputType.STRIP_ANSI; 149 return new AnsiOutputStream(stream); 150 } 151 } catch (Throwable ignore) { 152 // These errors happen if the JNI lib is not available for your platform. 153 // But since we are on ANSI friendly platform, assume the user is on the console. 154 } 155 156 // By default we assume your Unix tty can handle ANSI codes. 157 // Just wrap it up so that when we get closed, we reset the 158 // attributes. 159 jansiOutputType = JansiOutputType.RESET_ANSI_AT_CLOSE; 160 return new FilterOutputStream(stream) { 161 @Override 162 public void close() throws IOException { 163 write(AnsiOutputStream.RESET_CODE); 164 flush(); 165 super.close(); 166 } 167 }; 168 } 169 170 /** 171 * Wrap PrintStream applying rules in following order:<ul> 172 * <li>if <code>jansi.passthrough</code> is <code>true</code>, don't wrap but just passthrough (console is 173 * expected to natively support ANSI escape codes),</li> 174 * <li>if <code>jansi.strip</code> is <code>true</code>, just strip ANSI escape codes inconditionally,</li> 175 * <li>if OS is Windows and terminal is not Cygwin or Mingw, wrap as WindowsAnsiPrintStream to process ANSI escape codes,</li> 176 * <li>if file descriptor is a terminal (see <code>isatty(int)</code>) or <code>jansi.force</code> is <code>true</code>, 177 * just passthrough,</li> 178 * <li>else strip ANSI escape codes (not a terminal).</li> 179 * </ul> 180 * 181 * @param ps original PrintStream to wrap 182 * @param fileno file descriptor 183 * @return wrapped PrintStream depending on OS and system properties 184 * @since 1.17 185 */ 186 public static PrintStream wrapPrintStream(final PrintStream ps, int fileno) { 187 188 // If the jansi.passthrough property is set, then don't interpret 189 // any of the ansi sequences. 190 if (Boolean.getBoolean("jansi.passthrough")) { 191 jansiOutputType = JansiOutputType.PASSTHROUGH; 192 return ps; 193 } 194 195 // If the jansi.strip property is set, then we just strip the 196 // the ansi escapes. 197 if (Boolean.getBoolean("jansi.strip")) { 198 jansiOutputType = JansiOutputType.STRIP_ANSI; 199 return new AnsiPrintStream(ps); 200 } 201 202 if (IS_WINDOWS && !IS_CYGWIN && !IS_MINGW_XTERM) { 203 204 // On windows we know the console does not interpret ANSI codes.. 205 try { 206 jansiOutputType = JansiOutputType.WINDOWS; 207 return new WindowsAnsiPrintStream(ps); 208 } catch (Throwable ignore) { 209 // this happens when JNA is not in the path.. or 210 // this happens when the stdout is being redirected to a file. 211 } 212 213 // Use the AnsiPrintStream to strip out the ANSI escape sequences. 214 jansiOutputType = JansiOutputType.STRIP_ANSI; 215 return new AnsiPrintStream(ps); 216 } 217 218 // We must be on some Unix variant, including Cygwin or MSYS(2) on Windows... 219 try { 220 // If the jansi.force property is set, then we force to output 221 // the ansi escapes for piping it into ansi color aware commands (e.g. less -r) 222 boolean forceColored = Boolean.getBoolean("jansi.force"); 223 // If we can detect that stdout is not a tty.. then setup 224 // to strip the ANSI sequences.. 225 if (!forceColored && isatty(fileno) == 0) { 226 jansiOutputType = JansiOutputType.STRIP_ANSI; 227 return new AnsiPrintStream(ps); 228 } 229 } catch (Throwable ignore) { 230 // These errors happen if the JNI lib is not available for your platform. 231 // But since we are on ANSI friendly platform, assume the user is on the console. 232 } 233 234 // By default we assume your Unix tty can handle ANSI codes. 235 // Just wrap it up so that when we get closed, we reset the 236 // attributes. 237 jansiOutputType = JansiOutputType.RESET_ANSI_AT_CLOSE; 238 return new FilterPrintStream(ps) { 239 @Override 240 public void close() { 241 ps.print(AnsiPrintStream.RESET_CODE); 242 ps.flush(); 243 super.close(); 244 } 245 }; 246 } 247 248 /** 249 * If the standard out natively supports ANSI escape codes, then this just 250 * returns System.out, otherwise it will provide an ANSI aware PrintStream 251 * which strips out the ANSI escape sequences or which implement the escape 252 * sequences. 253 * 254 * @return a PrintStream which is ANSI aware. 255 * @see #wrapPrintStream(PrintStream, int) 256 */ 257 public static PrintStream out() { 258 return out; 259 } 260 261 /** 262 * If the standard out natively supports ANSI escape codes, then this just 263 * returns System.err, otherwise it will provide an ANSI aware PrintStream 264 * which strips out the ANSI escape sequences or which implement the escape 265 * sequences. 266 * 267 * @return a PrintStream which is ANSI aware. 268 * @see #wrapPrintStream(PrintStream, int) 269 */ 270 public static PrintStream err() { 271 return err; 272 } 273 274 /** 275 * Install <code>AnsiConsole.out</code> to <code>System.out</code> and 276 * <code>AnsiConsole.err</code> to <code>System.err</code>. 277 * @see #systemUninstall() 278 */ 279 synchronized static public void systemInstall() { 280 installed++; 281 if (installed == 1) { 282 System.setOut(out); 283 System.setErr(err); 284 } 285 } 286 287 /** 288 * undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called 289 * multiple times, {@link #systemUninstall()} must be called the same number of times before 290 * it is actually uninstalled. 291 */ 292 synchronized public static void systemUninstall() { 293 installed--; 294 if (installed == 0) { 295 System.setOut(system_out); 296 System.setErr(system_err); 297 } 298 } 299 300 /** 301 * Type of output installed by AnsiConsole. 302 */ 303 enum JansiOutputType { 304 PASSTHROUGH("just pass through, ANSI escape codes are supposed to be supported by terminal"), 305 RESET_ANSI_AT_CLOSE("like pass through but reset ANSI attributes when closing the stream"), 306 STRIP_ANSI("strip ANSI escape codes, for example when output is not a terminal"), 307 WINDOWS("detect ANSI escape codes and transform Jansi-supported ones into a Windows API to get desired effect" + 308 " (since ANSI escape codes are not natively supported by Windows terminals like cmd.exe or PowerShell)"); 309 310 private final String description; 311 312 private JansiOutputType(String description) { 313 this.description = description; 314 } 315 316 String getDescription() { 317 return description; 318 } 319 }; 320}