How to transform a Sink<T> into a Sink<V>? - dart

I have a method that can emit its output into a given Sink<Node>.
I wanted to pipe that into stdout which is a Sink<List<int>>.
Supposing I have a function convert that converts Node to List<int>, how can I transform stdout into a Sink<Node>, so that it will print my Tree to the console?

I have made this example showing how you can do it with a StreamController:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
class Message {
String text;
Message(this.text);
}
void main() {
final controller = StreamController<Message>();
stdout.addStream(controller.stream
.map((var msg) => msg.text)
.transform(const Utf8Encoder()));
var messageSink = controller.sink;
messageSink.add(Message('Hello World'));
}
The StreamController in this example takes Message objects and converts them into List<int> by first using map to convert the Message to String object and then use a transformer to convert the String into a List of UTF8 bytes.

I've filed: https://github.com/dart-lang/sdk/issues/50607
Here is how I solved this:
class _MappedSink<From, To> implements Sink<From> {
final To Function(From) _transform;
final Sink<To> _sink;
const _MappedSink(this._sink, this._transform);
#override
void add(From data) => _sink.add(_transform(data));
#override
void close() => _sink.close();
}
extension SinkMap<To> on Sink<To> {
Sink<From> map<From>(To Function(From) transform) =>
_MappedSink(this, transform);
}

Related

Override stdin, stdout, stderr in Dart

I'm looking to build the equivalent of the bash 'expect' command which is able to
capture output to stdout as well as inject data into stdin.
Dart provides the IOOverrides class that looks like it allows you to override stdout, stdin and stderr.
The ZoneSpecification that you pass to IOOverrides expects a instance of type StdIn to override stdin.
IOOverrides.runZoned(() => action,
stdin: () => Stdin._(mystream), // this won't work as there is no public ctor
);
As such I was hoping I could instantiate my own copy of StdIn and then inject data into it.
The problem is that StdIn only has a private constructor.
So it would appear that there is no way to actually override Stdin using a ZoneSpecification.
Am I missing something here?
The stdin getter actually has the code to allow it to be overriden:
/// The standard input stream of data read by this program.
Stdin get stdin {
return IOOverrides.current?.stdin ?? _stdin;
}
Are there other ways to achieve this?
Ultimately this is what I'm trying to achieve:
Interact(spawn: () {
final age = ask(
'How old are you',
defaultValue: '5',
customPrompt: (prompt, defaultValue, {hidden = false}) =>
'AAA$prompt:$defaultValue',
);
print('You are $age years old');
})
..expect('AAAHow old ar you:5')
..send('6')
..expect('You are 6 years old');
The Process class exposes a stdin member. See https://api.dart.dev/stable/2.10.5/dart-io/Process-class.html#standard-io-streams.
So this is not even close to a working solution, but based on #jamesdlin
comment the basic implementation looks like this.
Currently all of the puppet wrapper classes just forward calls to the real stdin/out/err. In reality these will need to intercept and process the calls rather than passing them through.
void test() {
Puppet(spawn: () {
final age = ask(
'How old are you',
defaultValue: '5',
customPrompt: (prompt, defaultValue, {hidden = false}) =>
'AAA$prompt:$defaultValue',
);
print('You are $age years old');
})
..expect('AAAHow old ar you:5')
..send('6')
..expect('You are 6 years old');
}
The Puppet class:
class Puppet<T> {
Puppet({required this.spawn});
T Function() spawn;
void _run() {
IOOverrides.runZoned(() => spawn,
stdin: PuppetStdin.new,
stdout: () => PuppetStdout(stdout),
stderr: () => PuppetStdout(stderr));
}
void expect(String expected) {}
void send(String s) {}
}
PuppetStdin
import 'dart:async';
import 'dart:convert';
import 'dart:io';
class PuppetStdin extends Stream<List<int>> implements Stdin {
PuppetStdin(
{this.echoMode = true,
this.echoNewlineMode = true,
this.lineMode = true});
StreamController<List<int>> controller = StreamController();
#override
bool echoMode;
#override
bool echoNewlineMode;
#override
bool lineMode;
#override
bool get hasTerminal => stdin.hasTerminal;
#override
StreamSubscription<List<int>> listen(void Function(List<int> event)? onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
throw UnimplementedError();
}
#override
int readByteSync() => stdin.readByteSync();
#override
String? readLineSync(
{Encoding encoding = systemEncoding, bool retainNewlines = false}) =>
stdin.readLineSync(encoding: encoding, retainNewlines: retainNewlines);
#override
bool get supportsAnsiEscapes => stdin.supportsAnsiEscapes;
}
PuppetStdout which will probably also serve the needs of overloading stderr
import 'dart:convert';
import 'dart:io';
class PuppetStdout implements Stdout {
PuppetStdout(this.stdout);
Stdout stdout;
IOSink? _nonBlocking;
/// Whether there is a terminal attached to stdout.
#override
bool get hasTerminal => stdout.hasTerminal;
/// The number of columns of the terminal.
///
/// If no terminal is attached to stdout, a [StdoutException] is thrown. See
/// [hasTerminal] for more info.
#override
int get terminalColumns => stdout.terminalColumns;
/// The number of lines of the terminal.
///
/// If no terminal is attached to stdout, a [StdoutException] is thrown. See
/// [hasTerminal] for more info.
#override
int get terminalLines => stdout.terminalLines;
/// 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.
#override
bool get supportsAnsiEscapes => stdout.supportsAnsiEscapes;
/// A non-blocking `IOSink` for the same output.
#override
IOSink get nonBlocking => _nonBlocking ??= stdout.nonBlocking;
#override
Encoding get encoding => stdout.encoding;
#override
set encoding(Encoding encoding) {
stdout.encoding = encoding;
}
#override
void add(List<int> data) {
stdout.add(data);
}
#override
void addError(Object error, [StackTrace? stackTrace]) {
stdout.addError(error, stackTrace);
}
#override
// ignore: strict_raw_type
Future addStream(Stream<List<int>> stream) => stdout.addStream(stream);
#override
// ignore: strict_raw_type
Future close() => stdout.close();
#override
// ignore: strict_raw_type
Future get done => stdout.done;
#override
// ignore: strict_raw_type
Future flush() => stdout.flush();
#override
void write(Object? object) {
stdout.write(object);
}
#override
// ignore: strict_raw_type
void writeAll(Iterable objects, [String sep = '']) {
stdout.writeAll(objects, sep);
}
#override
void writeCharCode(int charCode) {
stdout.writeCharCode(charCode);
}
#override
void writeln([Object? object = '']) {
stdout.writeln(object);
}
}

What is 'Native Type' for 'char*' in dart FFI?

I have a function like this in C language:
char* getString() {
return "SOME_STRING";
}
now I want to invoke it by FFI in dart, and this is my code:
import 'dart:io';
import 'dart:ffi';
void main(List<String> arguments) {
print('${getString()}');
}
final DynamicLibrary nativeAppTokenLib = Platform.isAndroid
? DynamicLibrary.open('lib_native_get_string.so')
: DynamicLibrary.process();
final String Function() getString = nativeAppTokenLib
.lookup<NativeFunction<*** Function()>>('getString')
.asFunction();
I wonder what should I put instead of *** as the native type?
Try:
import 'dart:ffi';
import 'dart:io';
import "package:ffi/ffi.dart";
...
final Pointer<Utf8> Function() _getString = nativeAppTokenLib
.lookup<NativeFunction<Pointer<Utf8> Function()>>('getString')
.asFunction();
String getString() => _getString().toDartString();
This uses package:ffi's Utf8 type to represent characters. The toDartString extension method on Pointer<Utf8> is the intended way to convert those to a string.

Dart2 Router Implementation

I am trying to upgrade Dart1 application to Dart 2.4, I am facing a problem in Router my code is as shown below
import 'dart:async';
import 'dart:convert';
import 'package:angular/src/core/di/decorators.dart';
#Injectable()
class SpRouterImpl implements SpRouter {
final Router _router;
SpRouterImpl(this._router);
#override
void go(String routeName, Map<String, String> parameters,
[bool openInNewWindow = false]) {
if (openInNewWindow) {
var url = _router.generate([routeName, parameters]).component.urlPath;
window.open(url, "_blank");
} else {
_router.navigate([routeName, parameters]);
}
}
}
I am getting error in this line
var url = _router.generate([routeName, parameters]).component.urlPath;
The method generate isn't defined for the class Router
Second error is here
_router.navigate([routeName, parameters]);
The argument type List can't be assigned to the parameter type 'String'
The above function is working fine in Dart 1 but when I upgrade to Dart 2, I am getting the errors, don't know how to solve it.
Can anyone help in this regard
You need a RoutePath instance to define your "route".
final search = RoutePath(path: "search/:term"); // term is the parameter
Then use that path to navigate to that route.
_router.navigate(search.toUrl(parameters: {'term': searchTerm}));
So in your case it might look like this:
import 'dart:async';
import 'dart:convert';
import 'package:angular/src/core/di/decorators.dart';
#Injectable()
class SpRouterImpl implements SpRouter {
final Router _router;
SpRouterImpl(this._router);
#override
void go(String routeName, Map<String, String> parameters,
[bool openInNewWindow = false]) {
final path = RoutePath(routeName);
final url = path.toUrl(parameters: parameters)
if (openInNewWindow) {
window.open(url, "_blank");
} else {
_router.navigate(url);
}
}
}
It might not drop in and work depending on how your routeName is defined but this is the general idea.
There are several other options for RoutePath check them out and see what works best for you!

What is the difference between Stream<List<int>> and Stream<int> in Dart

I am trying to wrap my head around Dart Streams. In particular this example of the command line utility cat has the following lines of code:
Stream<List<int>> stream = new File(path).openRead();
// Transform the stream using a `StreamTransformer`. The transformers
// used here convert the data to UTF8 and split string values into
// individual lines.
return stream
.transform(UTF8.decoder)
.transform(const LineSplitter())
.listen((line) {
if (showLineNumbers) {
stdout.write('${lineNumber++} ');
}
stdout.writeln(line);
}).asFuture().catchError((_) => _handleError(path));
The declaration of the Stream<T> as Stream<List<int>> has me a bit confused. Why is it not declared as a Stream<int>. How does the List<> type make this different. Are the subscriber events buffered in some way if it is a List?
What Type (as in <T>) is passed to the first transform? Is it an int or a List<int>?
What type is passed to each of the next transforms and what determines their type.
Does this example read the entire file before passing the results of the transform to the next transform? If so, is there an example somewhere of how to Stream very large files similar to this Node question Parsing huge logfiles in Node.js - read in line-by-line
Good question.
UTF8 is a Utf8Codec that extends Codec<String, List<int>>. So UTF8.decoder is a Converter<List<int>, String> that takes List<int> as parameter.
LineSplitter is a Converter<String, List<String>>. So it takes String as parameter. The resulting stream of .transform(const LineSplitter()) is a Stream<String> where each line is sent.
File.openRead doesn't read the entire file before writing the first bytes to the stream. So there's no problem to deal with large files.
Alexandre Ardhuin has the first three questions right. The 4th question however is not. After taking this apart and stubbing out my own version of the code I determined the following:
Even on a 37Mb file, the the transforms only get called once.
Here is the code I used to figure it out.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
void main(List<String> arguments) {
Stream<List<int>> stream = new File('Data.txt').openRead();
stream
.transform(const Utf8InterceptDecoder())
.transform(const LineSplitterIntercept())
.listen((line) {
// stdout.writeln(line);
}).asFuture().catchError((_) => print(_));
}
int lineSplitCount = 0;
class LineSplitterIntercept extends LineSplitter {
const LineSplitterIntercept() : super();
// Never gets called
List<String> convert(String data) {
stdout.writeln("LineSplitterIntercept.convert : Data:" + data);
return super.convert(data);
}
StringConversionSink startChunkedConversion(ChunkedConversionSink<String> sink) {
stdout.writeln("LineSplitterIntercept.startChunkedConversion Count:"+lineSplitCount.toString()+ " Sink: " + sink.toString());
lineSplitCount++;
return super.startChunkedConversion(sink);
}
}
int utfCount = 0;
class Utf8InterceptDecoder extends Utf8Decoder {
const Utf8InterceptDecoder() : super();
//never gets called
String convert(List<int> codeUnits) {
stdout.writeln("Utf8InterceptDecoder.convert : codeUnits.length:" + codeUnits.length.toString());
return super.convert(codeUnits);
}
ByteConversionSink startChunkedConversion(ChunkedConversionSink<String> sink) {
stdout.writeln("Utf8InterceptDecoder.startChunkedConversion Count:"+ utfCount.toString() + " Sink: "+ sink.toString());
utfCount++;
return super.startChunkedConversion(sink);
}
}

How do I read console input / stdin in Dart?

How do I read console input from stdin in Dart?
Is there a scanf in Dart?
The readLineSync() method of stdin allows to capture a String from the console:
import 'dart:convert';
import 'dart:io';
void main() {
print('1 + 1 = ...');
var line = stdin.readLineSync(encoding: utf8);
print(line?.trim() == '2' ? 'Yup!' : 'Nope :(');
}
Old version:
import 'dart:io';
main() {
print('1 + 1 = ...');
var line = stdin.readLineSync(encoding: Encoding.getByName('utf-8'));
print(line.trim() == '2' ? 'Yup!' : 'Nope :(');
}
The following should be the most up to date dart code to read input from stdin.
import 'dart:async';
import 'dart:io';
import 'dart:convert';
void main() {
readLine().listen(processLine);
}
Stream<String> readLine() => stdin
.transform(utf8.decoder)
.transform(const LineSplitter());
void processLine(String line) {
print(line);
}
import 'dart:io';
void main(){
stdout.write("Enter your name : ");
var name = stdin.readLineSync();
stdout.write(name);
}
Output
Enter your name : Jay
Jay
By default readLineSync() takes input as string. But If you want integer input then you have to use parse() or tryparse().
With M3 dart classes like StringInputStream are replaced with Stream, try this:
import 'dart:io';
import 'dart:async';
void main() {
print("Please, enter a line \n");
Stream cmdLine = stdin
.transform(new StringDecoder())
.transform(new LineTransformer());
StreamSubscription cmdSubscription = cmdLine.listen(
(line) => print('Entered line: $line '),
onDone: () => print(' finished'),
onError: (e) => /* Error on input. */);
}
As of Dart 2.12, null-safety is enabled, and stdin.readLineSync() now returns a String? instead of a String.
This apparently has been confusing a lot of people. I highly recommend reading https://dart.dev/null-safety/understanding-null-safety to understand what null-safety means.
For stdin.readLineSync() specifically, you can resolve this by checking for null first, which for local variables will automatically promote a String? to a String. Here are some examples:
// Read a line and try to parse it as an integer.
String? line = stdin.readLineSync();
if (line != null) {
int? num = int.tryParse(line); // No more error about `String?`.
if (num != null) {
// Do something with `num`...
}
}
// Read lines from `stdin` until EOF is reached, storing them in a `List<String>`.
var lines = <String>[];
while (true) {
var line = stdin.readLineSync();
if (line == null) {
break;
}
lines.add(line); // No more error about `String?`.
}
// Read a line. If EOF is reached, treat it as an empty string.
String line = stdin.readLineSync() ?? '';
Note that you should not blindly do stdin.readLineSync()!. readLineSync returns a String? for a reason: it returns null when there is no more input. Using the null assertion operator (!) is asking for a runtime exception.
Note that while calling stdin.readLineSync() your isolate/thread will be blocked, no other Future will be completed.
If you want to read a stdin String line asynchronously, avoiding isolate/thread block, this is the way:
import 'dart:async';
import 'dart:convert';
import 'dart:io';
/// [stdin] as a broadcast [Stream] of lines.
Stream<String> _stdinLineStreamBroadcaster = stdin
.transform(utf8.decoder)
.transform(const LineSplitter()).asBroadcastStream() ;
/// Reads a single line from [stdin] asynchronously.
Future<String> _readStdinLine() async {
var lineCompleter = Completer<String>();
var listener = _stdinLineStreamBroadcaster.listen((line) {
if (!lineCompleter.isCompleted) {
lineCompleter.complete(line);
}
});
return lineCompleter.future.then((line) {
listener.cancel();
return line ;
});
}
All these async API readLine*() based solutions miss the syntactic sugar which gives you the ability to do everything without synchronous blocking, but written like synchronous code. This is even more intuitive coming from other languages where you write code to execute synchronously:
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
var lines = stdin.transform(utf8.decoder).transform(const LineSplitter());
await for (final l in lines) {
print(l);
}
print("done");
}
The key takeaway here is to make use of async and await:
async on your method is required, as you're using await to interface with asynchronous API calls
await for is the syntax for doing "synchronous-like" code on a Stream (the corresponding syntax for a Future is just await).
Think of await like "unwrapping" a Stream/Future for you by making the following code execute once something is available to be handled. Now you're no longer blocking your main thread (Isolate) to do the work.
For more information, see the Dart codelab on async/await.
(Sidenote: The correct way to declare any return value for an async function is to wrap it in a Future, hence Future<void> here.)
You can use the following line to read a string from the user:
String str = stdin.readLineSync();
OR the following line to read a number
int n = int.parse(stdin.readLineSync());
Consider the following example:
import 'dart:io'; // we need this to use stdin
void main()
{
// reading the user name
print("Enter your name, please: ");
String name = stdin.readLineSync();
// reading the user age
print("Enter your age, please: ");
int age = int.parse(stdin.readLineSync());
// Printing the data
print("Hello, $name!, Your age is: $age");
/* OR print in this way
* stdout.write("Hello, $name!, Your age is: $age");
* */
}
You could of course just use the dcli package and it's ask function
Import 'package: dcli/dcli.dart':
Var answer = ask('enter your name');
print (name);
Use the named validator argument to restrict input to integers.
To read from the console or terminal in Dart, you need to:
import 'dart:io' library
store the entered value using stdin.readLineSync()!
parse the input into an int using int.parse(input) if necessary
Code:
import 'dart:io';
void main() {
String? string;
var number;
stdout.writeln("Enter a String: ");
string = stdin.readLineSync()!;
stdout.writeln("Enter a number: ");
number = int.parse(stdin.readLineSync()!);
}

Resources