-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
stdio.dart
482 lines (425 loc) · 14.1 KB
/
stdio.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.6
part of dart.io;
const int _stdioHandleTypeTerminal = 0;
const int _stdioHandleTypePipe = 1;
const int _stdioHandleTypeFile = 2;
const int _stdioHandleTypeSocket = 3;
const int _stdioHandleTypeOther = 4;
class _StdStream extends Stream<List<int>> {
final Stream<List<int>> _stream;
_StdStream(this._stream);
StreamSubscription<List<int>> listen(void onData(List<int> event),
{Function onError, void onDone(), bool cancelOnError}) {
return _stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
}
}
/**
* [Stdin] allows both synchronous and asynchronous reads from the standard
* input stream.
*
* Mixing synchronous and asynchronous reads is undefined.
*/
class Stdin extends _StdStream implements Stream<List<int>> {
int _fd;
Stdin._(Stream<List<int>> stream, this._fd) : super(stream);
/**
* Read a line from stdin.
*
* Blocks until a full line is available.
*
* Lines my be terminated by either `<CR><LF>` or `<LF>`. On Windows in cases
* where the [stdioType] of stdin is [StdioType.terminal] the terminator may
* also be a single `<CR>`.
*
* Input bytes are converted to a string by [encoding].
* If [encoding] is omitted, it defaults to [systemEncoding].
*
* If [retainNewlines] is `false`, the returned String will not include the
* final line terminator. If `true`, the returned String will include the line
* terminator. Default is `false`.
*
* If end-of-file is reached after any bytes have been read from stdin,
* that data is returned without a line terminator.
* Returns `null` if no bytes preceded the end of input.
*/
String readLineSync(
{Encoding encoding: systemEncoding, bool retainNewlines: false}) {
const CR = 13;
const LF = 10;
final List<int> line = <int>[];
// On Windows, if lineMode is disabled, only CR is received.
bool crIsNewline = Platform.isWindows &&
(stdioType(stdin) == StdioType.terminal) &&
!lineMode;
if (retainNewlines) {
int byte;
do {
byte = readByteSync();
if (byte < 0) {
break;
}
line.add(byte);
} while (byte != LF && !(byte == CR && crIsNewline));
if (line.isEmpty) {
return null;
}
} else if (crIsNewline) {
// CR and LF are both line terminators, neither is retained.
while (true) {
int byte = readByteSync();
if (byte < 0) {
if (line.isEmpty) return null;
break;
}
if (byte == LF || byte == CR) break;
line.add(byte);
}
} else {
// Case having to handle CR LF as a single unretained line terminator.
outer:
while (true) {
int byte = readByteSync();
if (byte == LF) break;
if (byte == CR) {
do {
byte = readByteSync();
if (byte == LF) break outer;
line.add(CR);
} while (byte == CR);
// Fall through and handle non-CR character.
}
if (byte < 0) {
if (line.isEmpty) return null;
break;
}
line.add(byte);
}
}
return encoding.decode(line);
}
/**
* Check if echo mode is enabled on [stdin].
*/
external bool get echoMode;
/**
* Enable or disable echo mode on [stdin].
*
* If disabled, input from to console will not be echoed.
*
* Default depends on the parent process, but usually enabled.
*
* On Windows this mode can only be enabled if [lineMode] is enabled as well.
*/
external void set echoMode(bool enabled);
/**
* Check if line mode is enabled on [stdin].
*/
external bool get lineMode;
/**
* Enable or disable line mode on [stdin].
*
* If enabled, characters are delayed until a new-line character is entered.
* If disabled, characters will be available as typed.
*
* Default depends on the parent process, but usually enabled.
*
* On Windows this mode can only be disabled if [echoMode] is disabled as well.
*/
external void set lineMode(bool enabled);
/**
* Whether connected to a terminal that supports ANSI escape sequences.
*
* Not all terminals are recognized, and not all recognized terminals can
* report whether they support ANSI escape sequences, so this value is a
* best-effort attempt at detecting the support.
*
* The actual escape sequence support may differ between terminals,
* with some terminals supporting more escape sequences than others,
* and some terminals even differing in behavior for the same escape
* sequence.
*
* The ANSI color selection is generally supported.
*
* Currently, a `TERM` environment variable containing the string `xterm`
* will be taken as evidence that ANSI escape sequences are supported.
* On Windows, only versions of Windows 10 after v.1511
* ("TH2", OS build 10586) will be detected as supporting the output of
* ANSI escape sequences, and only versions after v.1607 ("Anniversary
* Update", OS build 14393) will be detected as supporting the input of
* ANSI escape sequences.
*/
external bool get supportsAnsiEscapes;
/**
* Synchronously read a byte from stdin. This call will block until a byte is
* available.
*
* If at end of file, -1 is returned.
*/
external int readByteSync();
/**
* Returns true if there is a terminal attached to stdin.
*/
bool get hasTerminal {
try {
return stdioType(this) == StdioType.terminal;
} on FileSystemException catch (_) {
// If stdioType throws a FileSystemException, then it is not hooked up to
// a terminal, probably because it is closed, but let other exception
// types bubble up.
return false;
}
}
}
/**
* [Stdout] represents the [IOSink] for either `stdout` or `stderr`.
*
* It provides a *blocking* `IOSink`, so using this to write will block until
* the output is written.
*
* In some situations this blocking behavior is undesirable as it does not
* provide the same non-blocking behavior as dart:io in general exposes.
* Use the property [nonBlocking] to get an `IOSink` which has the non-blocking
* behavior.
*
* This class can also be used to check whether `stdout` or `stderr` is
* connected to a terminal and query some terminal properties.
*
* The [addError] API is inherited from [StreamSink] and calling it will result
* in an unhandled asynchronous error unless there is an error handler on
* [done].
*/
class Stdout extends _StdSink implements IOSink {
final int _fd;
IOSink _nonBlocking;
Stdout._(IOSink sink, this._fd) : super(sink);
/**
* Returns true if there is a terminal attached to stdout.
*/
bool get hasTerminal => _hasTerminal(_fd);
/**
* Get the number of columns of the terminal.
*
* If no terminal is attached to stdout, a [StdoutException] is thrown. See
* [hasTerminal] for more info.
*/
int get terminalColumns => _terminalColumns(_fd);
/**
* Get the number of lines of the terminal.
*
* If no terminal is attached to stdout, a [StdoutException] is thrown. See
* [hasTerminal] for more info.
*/
int get terminalLines => _terminalLines(_fd);
/**
* Whether connected to a terminal that supports ANSI escape sequences.
*
* Not all terminals are recognized, and not all recognized terminals can
* report whether they support ANSI escape sequences, so this value is a
* best-effort attempt at detecting the support.
*
* The actual escape sequence support may differ between terminals,
* with some terminals supporting more escape sequences than others,
* and some terminals even differing in behavior for the same escape
* sequence.
*
* The ANSI color selection is generally supported.
*
* Currently, a `TERM` environment variable containing the string `xterm`
* will be taken as evidence that ANSI escape sequences are supported.
* On Windows, only versions of Windows 10 after v.1511
* ("TH2", OS build 10586) will be detected as supporting the output of
* ANSI escape sequences, and only versions after v.1607 ("Anniversary
* Update", OS build 14393) will be detected as supporting the input of
* ANSI escape sequences.
*/
bool get supportsAnsiEscapes => _supportsAnsiEscapes(_fd);
external bool _hasTerminal(int fd);
external int _terminalColumns(int fd);
external int _terminalLines(int fd);
external static bool _supportsAnsiEscapes(int fd);
/**
* Get a non-blocking `IOSink`.
*/
IOSink get nonBlocking {
_nonBlocking ??= new IOSink(new _FileStreamConsumer.fromStdio(_fd));
return _nonBlocking;
}
}
class StdoutException implements IOException {
final String message;
final OSError osError;
const StdoutException(this.message, [this.osError]);
String toString() {
return "StdoutException: $message${osError == null ? "" : ", $osError"}";
}
}
class StdinException implements IOException {
final String message;
final OSError osError;
const StdinException(this.message, [this.osError]);
String toString() {
return "StdinException: $message${osError == null ? "" : ", $osError"}";
}
}
class _StdConsumer implements StreamConsumer<List<int>> {
final _file;
_StdConsumer(int fd) : _file = _File._openStdioSync(fd);
Future addStream(Stream<List<int>> stream) {
var completer = new Completer();
var sub;
sub = stream.listen((data) {
try {
_file.writeFromSync(data);
} catch (e, s) {
sub.cancel();
completer.completeError(e, s);
}
},
onError: completer.completeError,
onDone: completer.complete,
cancelOnError: true);
return completer.future;
}
Future close() {
_file.closeSync();
return new Future.value();
}
}
class _StdSink implements IOSink {
final IOSink _sink;
_StdSink(this._sink);
Encoding get encoding => _sink.encoding;
void set encoding(Encoding encoding) {
_sink.encoding = encoding;
}
void write(object) {
_sink.write(object);
}
void writeln([object = ""]) {
_sink.writeln(object);
}
void writeAll(objects, [sep = ""]) {
_sink.writeAll(objects, sep);
}
void add(List<int> data) {
_sink.add(data);
}
void addError(error, [StackTrace stackTrace]) {
_sink.addError(error, stackTrace);
}
void writeCharCode(int charCode) {
_sink.writeCharCode(charCode);
}
Future addStream(Stream<List<int>> stream) => _sink.addStream(stream);
Future flush() => _sink.flush();
Future close() => _sink.close();
Future get done => _sink.done;
}
/// The type of object a standard IO stream is attached to.
class StdioType {
static const StdioType terminal = const StdioType._("terminal");
static const StdioType pipe = const StdioType._("pipe");
static const StdioType file = const StdioType._("file");
static const StdioType other = const StdioType._("other");
@Deprecated("Use terminal instead")
static const StdioType TERMINAL = terminal;
@Deprecated("Use pipe instead")
static const StdioType PIPE = pipe;
@Deprecated("Use file instead")
static const StdioType FILE = file;
@Deprecated("Use other instead")
static const StdioType OTHER = other;
final String name;
const StdioType._(this.name);
String toString() => "StdioType: $name";
}
Stdin _stdin;
Stdout _stdout;
Stdout _stderr;
// These may be set to different values by the embedder by calling
// _setStdioFDs when initializing dart:io.
int _stdinFD = 0;
int _stdoutFD = 1;
int _stderrFD = 2;
@pragma('vm:entry-point', 'call')
void _setStdioFDs(int stdin, int stdout, int stderr) {
_stdinFD = stdin;
_stdoutFD = stdout;
_stderrFD = stderr;
}
/// The standard input stream of data read by this program.
Stdin get stdin {
_stdin ??= _StdIOUtils._getStdioInputStream(_stdinFD);
return _stdin;
}
/// The standard output stream of data written by this program.
///
/// The `addError` API is inherited from `StreamSink` and calling it will
/// result in an unhandled asynchronous error unless there is an error handler
/// on `done`.
Stdout get stdout {
_stdout ??= _StdIOUtils._getStdioOutputStream(_stdoutFD);
return _stdout;
}
/// The standard output stream of errors written by this program.
///
/// The `addError` API is inherited from `StreamSink` and calling it will
/// result in an unhandled asynchronous error unless there is an error handler
/// on `done`.
Stdout get stderr {
_stderr ??= _StdIOUtils._getStdioOutputStream(_stderrFD);
return _stderr;
}
/// For a stream, returns whether it is attached to a file, pipe, terminal, or
/// something else.
StdioType stdioType(object) {
if (object is _StdStream) {
object = object._stream;
} else if (object == stdout || object == stderr) {
int stdiofd = object == stdout ? _stdoutFD : _stderrFD;
switch (_StdIOUtils._getStdioHandleType(stdiofd)) {
case _stdioHandleTypeTerminal:
return StdioType.terminal;
case _stdioHandleTypePipe:
return StdioType.pipe;
case _stdioHandleTypeFile:
return StdioType.file;
}
}
if (object is _FileStream) {
return StdioType.file;
}
if (object is Socket) {
int socketType = _StdIOUtils._socketType(object);
if (socketType == null) return StdioType.other;
switch (socketType) {
case _stdioHandleTypeTerminal:
return StdioType.terminal;
case _stdioHandleTypePipe:
return StdioType.pipe;
case _stdioHandleTypeFile:
return StdioType.file;
}
}
if (object is _IOSinkImpl) {
try {
if (object._target is _FileStreamConsumer) {
return StdioType.file;
}
} catch (e) {
// Only the interface implemented, _sink not available.
}
}
return StdioType.other;
}
class _StdIOUtils {
external static _getStdioOutputStream(int fd);
external static Stdin _getStdioInputStream(int fd);
/// Returns the socket type or `null` if [socket] is not a builtin socket.
external static int _socketType(Socket socket);
external static _getStdioHandleType(int fd);
}