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