Skip to content

Commit e7d5951

Browse files
authored
Avoid triggering dart-lang/sdk#34775 (#10)
1 parent b1fdc20 commit e7d5951

File tree

8 files changed

+113
-55
lines changed

8 files changed

+113
-55
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.2.2
4+
5+
- Avoid triggering https://github.com/dart-lang/sdk/issue/34775.
6+
7+
* Explicitly declare `Repl.exit()`'s type as `FutureOr<void>`.
8+
39
## 0.2.1
410

511
- Migrate to null-safety

example/example.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ main(args) async {
88
var repl = new Repl(prompt: '>>> ', continuation: '... ', validator: v);
99
await for (var x in repl.runAsync()) {
1010
if (x.trim().isEmpty) continue;
11+
if (x == 'throw;') throw "oh no!";
1112
print(x);
1213
}
1314
}

lib/cli_repl.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
library cli_repl;
22

3+
import 'dart:async';
4+
35
import 'src/repl_adapter.dart';
46

57
class Repl {
@@ -34,7 +36,7 @@ class Repl {
3436
Stream<String> runAsync() => _adapter.runAsync();
3537

3638
/// Kills and cleans up the REPL.
37-
Future exit() => _adapter.exit();
39+
FutureOr<void> exit() => _adapter.exit();
3840

3941
/// History is by line, not by statement.
4042
///

lib/src/repl_adapter/interface.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ class ReplAdapter {
1111

1212
Stream<String> runAsync() async* {}
1313

14-
exit() {}
14+
FutureOr<void> exit() {}
1515
}

lib/src/repl_adapter/node.dart

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,52 @@ class ReplAdapter {
1616

1717
ReadlineInterface? rl;
1818

19-
Stream<String> runAsync() async* {
19+
Stream<String> runAsync() {
2020
var output = stdinIsTTY ? stdout : null;
21-
rl = readline.createInterface(
21+
var rl = this.rl = readline.createInterface(
2222
new ReadlineOptions(input: stdin, output: output, prompt: repl.prompt));
23-
String statement = "";
24-
String prompt = repl.prompt;
25-
var controller = new StreamController<String>();
26-
var queue = new StreamQueue<String>(controller.stream);
27-
rl!.on('line', allowInterop((value) {
28-
controller.add(value);
29-
}));
30-
while (true) {
31-
if (stdinIsTTY) stdout.write(prompt);
32-
var line = await queue.next;
33-
if (!stdinIsTTY) print('$prompt$line');
34-
statement += line;
35-
if (repl.validator(statement)) {
36-
yield statement;
37-
statement = "";
38-
prompt = repl.prompt;
39-
rl!.setPrompt(repl.prompt);
40-
} else {
41-
statement += '\n';
42-
prompt = repl.continuation;
43-
rl!.setPrompt(repl.continuation);
44-
}
45-
}
23+
var statement = "";
24+
var prompt = repl.prompt;
25+
26+
late StreamController<String> runController;
27+
runController = StreamController<String>(
28+
onListen: () async {
29+
try {
30+
var lineController = StreamController<String>();
31+
var lineQueue = StreamQueue<String>(lineController.stream);
32+
rl.on('line',
33+
allowInterop((value) => lineController.add(value as String)));
34+
35+
while (true) {
36+
if (stdinIsTTY) stdout.write(prompt);
37+
var line = await lineQueue.next;
38+
if (!stdinIsTTY) print('$prompt$line');
39+
statement += line;
40+
if (repl.validator(statement)) {
41+
runController.add(statement);
42+
statement = "";
43+
prompt = repl.prompt;
44+
rl.setPrompt(repl.prompt);
45+
} else {
46+
statement += '\n';
47+
prompt = repl.continuation;
48+
rl.setPrompt(repl.continuation);
49+
}
50+
}
51+
} catch (error, stackTrace) {
52+
runController.addError(error, stackTrace);
53+
await exit();
54+
runController.close();
55+
}
56+
},
57+
onCancel: exit);
58+
59+
return runController.stream;
4660
}
4761

48-
exit() {
62+
FutureOr<void> exit() {
4963
rl?.close();
64+
rl = null;
5065
}
5166
}
5267

lib/src/repl_adapter/vm.dart

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,39 +48,56 @@ class ReplAdapter {
4848
}
4949
}
5050

51-
Stream<String> runAsync() async* {
51+
Stream<String> runAsync() {
5252
bool interactive = true;
5353
try {
5454
stdin.echoMode = false;
5555
stdin.lineMode = false;
5656
} on StdinException {
5757
interactive = false;
5858
}
59-
charQueue = new StreamQueue<int>(stdin.expand((data) => data));
60-
while (true) {
61-
try {
62-
if (!interactive && !(await charQueue!.hasNext)) {
63-
break;
64-
}
65-
var result = await readStatementAsync();
66-
if (result == null) {
67-
print("");
68-
break;
69-
}
70-
yield result;
71-
} on Exception catch (e) {
72-
print(e);
73-
}
74-
}
75-
await exit();
59+
60+
late StreamController<String> controller;
61+
controller = StreamController(
62+
onListen: () async {
63+
try {
64+
var charQueue =
65+
this.charQueue = StreamQueue<int>(stdin.expand((data) => data));
66+
while (true) {
67+
if (!interactive && !(await charQueue.hasNext)) {
68+
this.charQueue = null;
69+
controller.close();
70+
return;
71+
}
72+
73+
var result = await _readStatementAsync(charQueue);
74+
if (result == null) {
75+
print("");
76+
break;
77+
}
78+
controller.add(result);
79+
}
80+
} catch (error, stackTrace) {
81+
controller.addError(error, stackTrace);
82+
await exit();
83+
controller.close();
84+
}
85+
},
86+
onCancel: exit,
87+
sync: true);
88+
89+
return controller.stream;
7690
}
7791

78-
exit() {
92+
FutureOr<void> exit() {
7993
try {
8094
stdin.lineMode = true;
8195
stdin.echoMode = true;
8296
} on StdinException {}
83-
return charQueue?.cancel();
97+
98+
var future = charQueue?.cancel(immediate: true);
99+
charQueue = null;
100+
return future;
84101
}
85102

86103
Iterable<String> linesToStatements(Iterable<String> lines) sync* {
@@ -150,15 +167,15 @@ class ReplAdapter {
150167
}
151168
}
152169

153-
Future<String?> readStatementAsync() async {
170+
Future<String?> _readStatementAsync(StreamQueue<int> charQueue) async {
154171
startReadStatement();
155172
while (true) {
156-
int char = await charQueue!.next;
173+
int char = await charQueue.next;
157174
if (char == eof && buffer.isEmpty) return null;
158175
if (char == escape) {
159-
char = await charQueue!.next;
176+
char = await charQueue.next;
160177
if (char == c('[') || char == c('O')) {
161-
var ansi = await charQueue!.next;
178+
var ansi = await charQueue.next;
162179
if (!handleAnsi(ansi)) {
163180
write('^[');
164181
input(char);

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: cli_repl
22
description: A simple library for creating CLI REPLs
3-
version: 0.2.1
3+
version: 0.2.2
44
homepage: https://github.com/jathak/cli_repl
55
author: Jen Thakar <[email protected]>
66

test/repl_test.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import 'package:test/test.dart';
44
import 'package:test_process/test_process.dart';
55

66
main() {
7-
testWith(Platform.executable, 'example/example.dart');
8-
testWith('node', 'build/example/example.js');
7+
group("VM", () => testWith(Platform.executable, 'example/example.dart'));
8+
group("Node.js", () => testWith('node', 'build/example/example.js'));
99
}
1010

1111
testWith(String executable, String script) {
@@ -36,4 +36,21 @@ testWith(String executable, String script) {
3636
expect(process.stdout, emitsDone);
3737
await process.shouldExit(0);
3838
});
39+
40+
// This is a regression test to ensure that the reply code doesn't fall prey
41+
// to dart-lang/sdk#34775.
42+
test('example repl throws an error', () async {
43+
if (!await new File(script).exists()) {
44+
fail("$script does not exist");
45+
}
46+
47+
var process = await TestProcess.start(executable, [script]);
48+
process.stdin.writeln('throw;');
49+
expect(process.stdout, emits(">>> throw;"));
50+
expect(process.stdout, emitsDone);
51+
52+
expect(process.stderr, emitsThrough(contains("oh no!")));
53+
54+
await process.shouldExit(greaterThan(0));
55+
});
3956
}

0 commit comments

Comments
 (0)