OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of dart.io; |
| 6 |
| 7 const int _STDIO_HANDLE_TYPE_TERMINAL = 0; |
| 8 const int _STDIO_HANDLE_TYPE_PIPE = 1; |
| 9 const int _STDIO_HANDLE_TYPE_FILE = 2; |
| 10 const int _STDIO_HANDLE_TYPE_SOCKET = 3; |
| 11 const int _STDIO_HANDLE_TYPE_OTHER = 4; |
| 12 |
| 13 |
| 14 class _StdStream extends Stream<List<int>> { |
| 15 final Stream<List<int>> _stream; |
| 16 |
| 17 _StdStream(this._stream); |
| 18 |
| 19 StreamSubscription<List<int>> listen(void onData(List<int> event), |
| 20 {Function onError, |
| 21 void onDone(), |
| 22 bool cancelOnError}) { |
| 23 return _stream.listen( |
| 24 onData, |
| 25 onError: onError, |
| 26 onDone: onDone, |
| 27 cancelOnError: cancelOnError); |
| 28 } |
| 29 } |
| 30 |
| 31 |
| 32 /** |
| 33 * [Stdin] allows both synchronous and asynchronous reads from the standard |
| 34 * input stream. |
| 35 * |
| 36 * Mixing synchronous and asynchronous reads is undefined. |
| 37 */ |
| 38 class Stdin extends _StdStream implements Stream<List<int>> { |
| 39 Stdin._(Stream<List<int>> stream) : super(stream); |
| 40 |
| 41 /** |
| 42 * Synchronously read a line from stdin. This call will block until a full |
| 43 * line is available. |
| 44 * |
| 45 * The argument [encoding] can be used to changed how the input should be |
| 46 * decoded. Default is [SYSTEM_ENCODING]. |
| 47 * |
| 48 * If [retainNewlines] is `false`, the returned String will not contain the |
| 49 * final newline. If `true`, the returned String will contain the line |
| 50 * terminator. Default is `false`. |
| 51 * |
| 52 * If end-of-file is reached after any bytes have been read from stdin, |
| 53 * that data is returned. |
| 54 * Returns `null` if no bytes preceeded the end of input. |
| 55 */ |
| 56 String readLineSync({Encoding encoding: SYSTEM_ENCODING, |
| 57 bool retainNewlines: false}) { |
| 58 const CR = 13; |
| 59 const LF = 10; |
| 60 final List line = []; |
| 61 // On Windows, if lineMode is disabled, only CR is received. |
| 62 bool crIsNewline = Platform.isWindows && |
| 63 (stdioType(stdin) == StdioType.TERMINAL) && |
| 64 !lineMode; |
| 65 if (retainNewlines) { |
| 66 int byte; |
| 67 do { |
| 68 byte = readByteSync(); |
| 69 if (byte < 0) { |
| 70 break; |
| 71 } |
| 72 line.add(byte); |
| 73 } while (byte != LF && !(byte == CR && crIsNewline)); |
| 74 if (line.isEmpty) { |
| 75 return null; |
| 76 } |
| 77 } else if (crIsNewline) { |
| 78 // CR and LF are both line terminators, neither is retained. |
| 79 while (true) { |
| 80 int byte = readByteSync(); |
| 81 if (byte < 0) { |
| 82 if (line.isEmpty) return null; |
| 83 break; |
| 84 } |
| 85 if (byte == LF || byte == CR) break; |
| 86 line.add(byte); |
| 87 } |
| 88 } else { |
| 89 // Case having to hande CR LF as a single unretained line terminator. |
| 90 outer: while (true) { |
| 91 int byte = readByteSync(); |
| 92 if (byte == LF) break; |
| 93 if (byte == CR) { |
| 94 do { |
| 95 byte = readByteSync(); |
| 96 if (byte == LF) break outer; |
| 97 |
| 98 line.add(CR); |
| 99 } while (byte == CR); |
| 100 // Fall through and handle non-CR character. |
| 101 } |
| 102 if (byte < 0) { |
| 103 if (line.isEmpty) return null; |
| 104 break; |
| 105 } |
| 106 line.add(byte); |
| 107 } |
| 108 } |
| 109 return encoding.decode(line); |
| 110 } |
| 111 |
| 112 /** |
| 113 * Check if echo mode is enabled on [stdin]. |
| 114 */ |
| 115 external bool get echoMode; |
| 116 |
| 117 /** |
| 118 * Enable or disable echo mode on [stdin]. |
| 119 * |
| 120 * If disabled, input from to console will not be echoed. |
| 121 * |
| 122 * Default depends on the parent process, but usually enabled. |
| 123 */ |
| 124 external void set echoMode(bool enabled); |
| 125 |
| 126 /** |
| 127 * Check if line mode is enabled on [stdin]. |
| 128 */ |
| 129 external bool get lineMode; |
| 130 |
| 131 /** |
| 132 * Enable or disable line mode on [stdin]. |
| 133 * |
| 134 * If enabled, characters are delayed until a new-line character is entered. |
| 135 * If disabled, characters will be available as typed. |
| 136 * |
| 137 * Default depends on the parent process, but usually enabled. |
| 138 */ |
| 139 external void set lineMode(bool enabled); |
| 140 |
| 141 /** |
| 142 * Synchronously read a byte from stdin. This call will block until a byte is |
| 143 * available. |
| 144 * |
| 145 * If at end of file, -1 is returned. |
| 146 */ |
| 147 external int readByteSync(); |
| 148 } |
| 149 |
| 150 |
| 151 /** |
| 152 * [Stdout] represents the [IOSink] for either `stdout` or `stderr`. |
| 153 * |
| 154 * It provides a *blocking* `IOSink`, so using this to write will block until |
| 155 * the output is written. |
| 156 * |
| 157 * In some situations this blocking behavior is undesirable as it does not |
| 158 * provide the same non-blocking behavior as dart:io in general exposes. |
| 159 * Use the property [nonBlocking] to get an `IOSink` which has the non-blocking |
| 160 * behavior. |
| 161 * |
| 162 * This class can also be used to check whether `stdout` or `stderr` is |
| 163 * connected to a terminal and query some terminal properties. |
| 164 */ |
| 165 class Stdout extends _StdSink implements IOSink { |
| 166 final int _fd; |
| 167 IOSink _nonBlocking; |
| 168 |
| 169 Stdout._(IOSink sink, this._fd) : super(sink); |
| 170 |
| 171 /** |
| 172 * Returns true if there is a terminal attached to stdout. |
| 173 */ |
| 174 bool get hasTerminal => _hasTerminal(_fd); |
| 175 |
| 176 /** |
| 177 * Get the number of columns of the terminal. |
| 178 * |
| 179 * If no terminal is attached to stdout, a [StdoutException] is thrown. See |
| 180 * [hasTerminal] for more info. |
| 181 */ |
| 182 int get terminalColumns => _terminalColumns(_fd); |
| 183 |
| 184 /** |
| 185 * Get the number of lines of the terminal. |
| 186 * |
| 187 * If no terminal is attached to stdout, a [StdoutException] is thrown. See |
| 188 * [hasTerminal] for more info. |
| 189 */ |
| 190 int get terminalLines => _terminalLines(_fd); |
| 191 |
| 192 external bool _hasTerminal(int fd); |
| 193 external int _terminalColumns(int fd); |
| 194 external int _terminalLines(int fd); |
| 195 |
| 196 /** |
| 197 * Get a non-blocking `IOSink`. |
| 198 */ |
| 199 IOSink get nonBlocking { |
| 200 if (_nonBlocking == null) { |
| 201 _nonBlocking = new IOSink(new _FileStreamConsumer.fromStdio(_fd)); |
| 202 } |
| 203 return _nonBlocking; |
| 204 } |
| 205 } |
| 206 |
| 207 |
| 208 class StdoutException implements IOException { |
| 209 final String message; |
| 210 final OSError osError; |
| 211 |
| 212 const StdoutException(this.message, [this.osError]); |
| 213 |
| 214 String toString() { |
| 215 return "StdoutException: $message${osError == null ? "" : ", $osError"}"; |
| 216 } |
| 217 } |
| 218 |
| 219 class _StdConsumer implements StreamConsumer<List<int>> { |
| 220 final _file; |
| 221 |
| 222 _StdConsumer(int fd) : _file = _File._openStdioSync(fd); |
| 223 |
| 224 Future addStream(Stream<List<int>> stream) { |
| 225 var completer = new Completer(); |
| 226 var sub; |
| 227 sub = stream.listen( |
| 228 (data) { |
| 229 try { |
| 230 _file.writeFromSync(data); |
| 231 } catch (e, s) { |
| 232 sub.cancel(); |
| 233 completer.completeError(e, s); |
| 234 } |
| 235 }, |
| 236 onError: completer.completeError, |
| 237 onDone: completer.complete, |
| 238 cancelOnError: true); |
| 239 return completer.future; |
| 240 } |
| 241 |
| 242 Future close() { |
| 243 _file.closeSync(); |
| 244 return new Future.value(); |
| 245 } |
| 246 } |
| 247 |
| 248 class _StdSink implements IOSink { |
| 249 final IOSink _sink; |
| 250 |
| 251 _StdSink(this._sink); |
| 252 |
| 253 Encoding get encoding => _sink.encoding; |
| 254 void set encoding(Encoding encoding) { |
| 255 _sink.encoding = encoding; |
| 256 } |
| 257 void write(object) { _sink.write(object); } |
| 258 void writeln([object = "" ]) { _sink.writeln(object); } |
| 259 void writeAll(objects, [sep = ""]) { _sink.writeAll(objects, sep); } |
| 260 void add(List<int> data) { _sink.add(data); } |
| 261 void addError(error, [StackTrace stackTrace]) { |
| 262 _sink.addError(error, stackTrace); |
| 263 } |
| 264 void writeCharCode(int charCode) { _sink.writeCharCode(charCode); } |
| 265 Future addStream(Stream<List<int>> stream) => _sink.addStream(stream); |
| 266 Future flush() => _sink.flush(); |
| 267 Future close() => _sink.close(); |
| 268 Future get done => _sink.done; |
| 269 } |
| 270 |
| 271 /// The type of object a standard IO stream is attached to. |
| 272 class StdioType { |
| 273 static const StdioType TERMINAL = const StdioType._("terminal"); |
| 274 static const StdioType PIPE = const StdioType._("pipe"); |
| 275 static const StdioType FILE = const StdioType._("file"); |
| 276 static const StdioType OTHER = const StdioType._("other"); |
| 277 final String name; |
| 278 const StdioType._(this.name); |
| 279 String toString() => "StdioType: $name"; |
| 280 } |
| 281 |
| 282 |
| 283 Stdin _stdin; |
| 284 Stdout _stdout; |
| 285 Stdout _stderr; |
| 286 |
| 287 |
| 288 /// The standard input stream of data read by this program. |
| 289 Stdin get stdin { |
| 290 if (_stdin == null) { |
| 291 _stdin = _StdIOUtils._getStdioInputStream(); |
| 292 } |
| 293 return _stdin; |
| 294 } |
| 295 |
| 296 |
| 297 /// The standard output stream of data written by this program. |
| 298 Stdout get stdout { |
| 299 if (_stdout == null) { |
| 300 _stdout = _StdIOUtils._getStdioOutputStream(1); |
| 301 } |
| 302 return _stdout; |
| 303 } |
| 304 |
| 305 |
| 306 /// The standard output stream of errors written by this program. |
| 307 Stdout get stderr { |
| 308 if (_stderr == null) { |
| 309 _stderr = _StdIOUtils._getStdioOutputStream(2); |
| 310 } |
| 311 return _stderr; |
| 312 } |
| 313 |
| 314 |
| 315 /// For a stream, returns whether it is attached to a file, pipe, terminal, or |
| 316 /// something else. |
| 317 StdioType stdioType(object) { |
| 318 if (object is _StdStream) { |
| 319 object = object._stream; |
| 320 } else if (object == stdout || object == stderr) { |
| 321 switch (_StdIOUtils._getStdioHandleType(object == stdout ? 1 : 2)) { |
| 322 case _STDIO_HANDLE_TYPE_TERMINAL: return StdioType.TERMINAL; |
| 323 case _STDIO_HANDLE_TYPE_PIPE: return StdioType.PIPE; |
| 324 case _STDIO_HANDLE_TYPE_FILE: return StdioType.FILE; |
| 325 } |
| 326 } |
| 327 if (object is _FileStream) { |
| 328 return StdioType.FILE; |
| 329 } |
| 330 if (object is Socket) { |
| 331 int socketType = _StdIOUtils._socketType(object); |
| 332 if (socketType == null) return StdioType.OTHER; |
| 333 switch (socketType) { |
| 334 case _STDIO_HANDLE_TYPE_TERMINAL: |
| 335 return StdioType.TERMINAL; |
| 336 case _STDIO_HANDLE_TYPE_PIPE: |
| 337 return StdioType.PIPE; |
| 338 case _STDIO_HANDLE_TYPE_FILE: |
| 339 return StdioType.FILE; |
| 340 } |
| 341 } |
| 342 if (object is _IOSinkImpl) { |
| 343 try { |
| 344 if (object._target is _FileStreamConsumer) { |
| 345 return StdioType.FILE; |
| 346 } |
| 347 } catch (e) { |
| 348 // Only the interface implemented, _sink not available. |
| 349 } |
| 350 } |
| 351 return StdioType.OTHER; |
| 352 } |
| 353 |
| 354 |
| 355 class _StdIOUtils { |
| 356 external static _getStdioOutputStream(int fd); |
| 357 external static Stdin _getStdioInputStream(); |
| 358 /// Returns the socket type or `null` if [socket] is not a builtin socket. |
| 359 external static int _socketType(Socket socket); |
| 360 external static _getStdioHandleType(int fd); |
| 361 } |
OLD | NEW |