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 |