| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| library webdriver.support.stdio_stepper; |
| |
| import 'dart:async' show Future, Stream, StreamController; |
| import 'dart:convert' show Encoding, json; |
| import 'dart:io' show exit, Stdin, stdin, systemEncoding; |
| |
| import 'package:webdriver/src/async/stepper.dart'; |
| |
| LineReader _stdinLineReader; |
| |
| /// A [LineReader] instance connected to 'dart:io' [stdin]. |
| LineReader get stdinLineReader { |
| _stdinLineReader ??= LineReader(stdin); |
| return _stdinLineReader; |
| } |
| |
| /// Provides a command line interface for stepping through or skipping |
| /// WebDriver commands. |
| class StdioStepper implements Stepper { |
| bool enabled = true; |
| |
| final LineReader _reader; |
| |
| StdioStepper({LineReader reader}) : _reader = reader ?? stdinLineReader; |
| |
| @override |
| Future<bool> step(String method, String command, params) async { |
| if (!enabled) return true; |
| print('$method $command(${json.encode(params)}):'); |
| await for (String command in _reader.onLine) { |
| switch (command) { |
| case 'continue': |
| case 'c': |
| return true; |
| case 'skip': |
| case 's': |
| return false; |
| case 'break': |
| case 'b': |
| throw Exception('process ended by user.'); |
| case 'help': |
| case 'h': |
| _printUsage(); |
| break; |
| case 'disable': |
| case 'd': |
| enabled = false; |
| return true; |
| case 'quit': |
| case 'q': |
| exit(-1); |
| return false; |
| default: |
| print('invalid command: `$command` enter `h` or `help` for help.'); |
| } |
| } |
| throw Exception('stdin has been closed'); |
| } |
| |
| void _printUsage() { |
| print('`c` or `continue`: execute command'); |
| print('`s` or `skip`: skip command'); |
| print('`d` or `disable`: disable stepper'); |
| print('`b` or `break`: throw exception'); |
| print('`q` or `quit`: terminate vm'); |
| print('`h` or `help`: display this message'); |
| } |
| } |
| |
| /// Converts a Stream<List<int> | int> to Stream<String> that fires an event |
| /// for every line of data in the original Stream. |
| class LineReader { |
| static const CR = 13; |
| static const LF = 10; |
| |
| bool _crPrevious = false; |
| final _bytes = <int>[]; |
| final _controller = StreamController<String>.broadcast(); |
| |
| final Encoding encoding; |
| |
| /// Only encodings that are a superset of ASCII are supported |
| /// TODO(DrMarcII): Support arbitrary encodings |
| LineReader(Stream /* <List<int> | int> */ stream, |
| {this.encoding = systemEncoding}) { |
| if (stream is Stdin) { |
| stdin.lineMode = false; |
| } |
| stream.listen(_listen, |
| onDone: _controller.close, onError: _controller.addError); |
| } |
| |
| void _listen(/* List<int> | int */ data) { |
| if (data is List<int>) { |
| data.forEach(_addByte); |
| } else { |
| _addByte(data); |
| } |
| } |
| |
| void _addByte(int byte) { |
| if (_crPrevious && byte == LF) { |
| _crPrevious = false; |
| return; |
| } |
| _crPrevious = byte == CR; |
| if (byte == CR || byte == LF) { |
| _controller.add(encoding.decode(_bytes)); |
| _bytes.clear(); |
| } else { |
| _bytes.add(byte); |
| } |
| } |
| |
| /// A Stream that fires for each line of data. |
| Stream<String> get onLine => _controller.stream; |
| } |