From cdd4c9dbf8631cfa3377f673be29bb4395678b3e Mon Sep 17 00:00:00 2001 From: temeddix Date: Mon, 16 Sep 2024 17:41:07 +0900 Subject: [PATCH 01/19] Use `args` package to parse CLI arguments --- flutter_package/bin/rinf.dart | 129 ++++++++++++++++++--------- flutter_package/bin/src/helpers.dart | 4 +- flutter_package/pubspec.yaml | 1 + 3 files changed, 88 insertions(+), 46 deletions(-) diff --git a/flutter_package/bin/rinf.dart b/flutter_package/bin/rinf.dart index 920b1a21..402ec8ca 100644 --- a/flutter_package/bin/rinf.dart +++ b/flutter_package/bin/rinf.dart @@ -1,55 +1,96 @@ +import 'package:args/command_runner.dart'; + import 'src/config.dart'; import 'src/helpers.dart'; import 'src/message.dart'; import 'src/internet.dart'; Future main(List args) async { - if (args.isEmpty) { - print('No operation is provided.'); - print('Use `rinf --help` to see all available operations.'); - return; + await checkConnectivity(); + + final runner = CommandRunner( + 'rinf', + 'Helper commands for building apps with Rust in Flutter.', + usageLineLength: 80, + ) + ..addCommand(ConfigCommand()) + ..addCommand(TemplateCommand()) + ..addCommand(MessageCommand()) + ..addCommand(WasmCommand()); + + await runner.run(args); +} + +class ConfigCommand extends Command { + final name = 'config'; + final description = 'Shows current Rinf configuration' + + ' resolved from `pubspec.yaml` with defaults applied.'; + + ConfigCommand() {} + + Future run() async { + final rinfConfig = await loadVerifiedRinfConfig('pubspec.yaml'); + print(rinfConfig); } +} - final rinfConfig = await loadVerifiedRinfConfig('pubspec.yaml'); - await checkConnectivity(); +class TemplateCommand extends Command { + final name = 'template'; + final description = 'Applies Rust template to the current Flutter project.'; + + TemplateCommand() {} + + Future run() async { + final rinfConfig = await loadVerifiedRinfConfig('pubspec.yaml'); + await applyRustTemplate(messageConfig: rinfConfig.message); + } +} + +class MessageCommand extends Command { + final name = 'message'; + final description = 'Generates message code from `.proto` files.'; + + MessageCommand() { + argParser.addFlag( + 'watch', + abbr: 'w', + help: 'Continuously watches `.proto` files.', + ); + } + + Future run() async { + final results = argResults; + if (results == null) { + return; + } + final watch = results.flag('watch'); + final rinfConfig = await loadVerifiedRinfConfig('pubspec.yaml'); + if (watch) { + await watchAndGenerateMessageCode(messageConfig: rinfConfig.message); + } else { + await generateMessageCode(messageConfig: rinfConfig.message); + } + } +} + +class WasmCommand extends Command { + final name = 'wasm'; + final description = 'Builds the webassembly module for the web.'; + + WasmCommand() { + argParser.addFlag( + 'release', + abbr: 'r', + help: 'Builds in release mode.', + ); + } - switch (args[0]) { - case 'config': - print(rinfConfig); - break; - case 'template': - await applyRustTemplate(messageConfig: rinfConfig.message); - break; - case 'message': - if (args.contains('--watch') || args.contains('-w')) { - await watchAndGenerateMessageCode(messageConfig: rinfConfig.message); - } else { - await generateMessageCode(messageConfig: rinfConfig.message); - } - break; - case 'wasm': - if (args.contains('--release') || args.contains('-r')) { - await buildWebassembly(isReleaseMode: true); - } else { - await buildWebassembly(); - } - break; - case '--help': - case '-h': - print('Usage: rinf [arguments]'); - print('Arguments:'); - print(' -h, --help Shows this usage information.'); - print(' config Shows current Rinf configuration' - '\n resolved from `pubspec.yaml`' - '\n with defaults applied.'); - print(' template Applies Rust template' - '\n to current Flutter project.'); - print(' message Generates message code from `.proto` files.'); - print(' -w, --watch Continuously watches `.proto` files.'); - print(' wasm Builds webassembly module.'); - print(' -r, --release Builds in release mode.'); - default: - print('No such operation is available.'); - print('Use `rinf --help` to see all available operations.'); + Future run() async { + final results = argResults; + if (results == null) { + return; + } + final release = results.flag('release'); + await buildWebassembly(release); } } diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index 25943482..1856ac35 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -201,7 +201,7 @@ Future copyDirectory(Uri source, Uri destination) async { } } -Future buildWebassembly({bool isReleaseMode = false}) async { +Future buildWebassembly(bool isReleaseMode) async { // Ensure Rust toolchain. if (isInternetConnected) { print('Ensuring Rust toolchain for the web.' + @@ -293,7 +293,7 @@ Future buildWebassembly({bool isReleaseMode = false}) async { print('🎉 Webassembly module is now ready! 🎉'); } -Future getCommandLineDivider({bool isReleaseMode = false}) async { +Future getCommandLineDivider() async { if (Platform.isWindows) { // Windows environment, check further for PowerShell or CMD if (Platform.environment['SHELL'] == null) { diff --git a/flutter_package/pubspec.yaml b/flutter_package/pubspec.yaml index ff7b50be..cf378c69 100644 --- a/flutter_package/pubspec.yaml +++ b/flutter_package/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: ffi: ^2.1.0 yaml: ^3.1.2 internet_connection_checker: ^1.0.0 + args: ^2.5.0 dev_dependencies: lints: ">=3.0.0 <5.0.0" From a53784abed0cd760427fdd7685cab165f8adedeb Mon Sep 17 00:00:00 2001 From: temeddix Date: Mon, 16 Sep 2024 18:08:54 +0900 Subject: [PATCH 02/19] Use `chalkdart` Dart package --- flutter_package/bin/src/config.dart | 1 + flutter_package/bin/src/helpers.dart | 9 ++++++--- flutter_package/bin/src/message.dart | 2 ++ flutter_package/pubspec.yaml | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/flutter_package/bin/src/config.dart b/flutter_package/bin/src/config.dart index 5aa67660..23a3ae8c 100644 --- a/flutter_package/bin/src/config.dart +++ b/flutter_package/bin/src/config.dart @@ -1,4 +1,5 @@ import 'dart:io'; + import 'package:yaml/yaml.dart'; class RinfConfigMessage { diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index 1856ac35..16615f66 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -1,6 +1,9 @@ import 'dart:io'; + import 'package:package_config/package_config.dart'; import 'package:yaml/yaml.dart'; +import 'package:chalkdart/chalkstrings.dart'; + import 'config.dart'; import 'message.dart'; import 'common.dart'; @@ -280,6 +283,8 @@ Future buildWebassembly(bool isReleaseMode) async { } print('Saved `.wasm` and `.js` files to `$subPath`.'); + print('🎉 Webassembly module is now ready! 🎉'); + // Guide the developer how to run Flutter web server with web headers. print('To run the Flutter web server, use:'); final commandLineDivider = await getCommandLineDivider(); @@ -288,9 +293,7 @@ Future buildWebassembly(bool isReleaseMode) async { '--web-header=Cross-Origin-Opener-Policy=same-origin', '--web-header=Cross-Origin-Embedder-Policy=require-corp' ]; - print(commandLines.join(' ${commandLineDivider}\n')); - - print('🎉 Webassembly module is now ready! 🎉'); + print(commandLines.join(' ${commandLineDivider}\n').onGray); } Future getCommandLineDivider() async { diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 475df2dc..77e3d1f0 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -1,6 +1,8 @@ import 'dart:io'; + import 'package:path/path.dart'; import 'package:watcher/watcher.dart'; + import 'config.dart'; import 'common.dart'; import 'internet.dart'; diff --git a/flutter_package/pubspec.yaml b/flutter_package/pubspec.yaml index cf378c69..3a32446c 100644 --- a/flutter_package/pubspec.yaml +++ b/flutter_package/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: yaml: ^3.1.2 internet_connection_checker: ^1.0.0 args: ^2.5.0 + chalkdart: ^2.2.1 dev_dependencies: lints: ">=3.0.0 <5.0.0" From 2336d1c2fd9c37f87a1ebe0121c73f76a27a0dc0 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 00:24:19 +0900 Subject: [PATCH 03/19] Shorten special comments --- documentation/docs/detailed-techniques.md | 3 +-- .../docs/frequently-asked-questions.md | 4 ++-- documentation/docs/messaging.md | 20 +++++++++---------- documentation/docs/tutorial.md | 12 +++++------ flutter_package/bin/src/message.dart | 14 ++++++------- .../example/messages/counter_number.proto | 4 ++-- .../example/messages/fractal_art.proto | 2 +- .../messages/sample_folder/sample_file.proto | 10 ++++------ flutter_package/template/messages/basic.proto | 12 ++++------- 9 files changed, 37 insertions(+), 44 deletions(-) diff --git a/documentation/docs/detailed-techniques.md b/documentation/docs/detailed-techniques.md index a868d337..e22ef0a4 100644 --- a/documentation/docs/detailed-techniques.md +++ b/documentation/docs/detailed-techniques.md @@ -11,7 +11,6 @@ We've covered how to pass signals[^1] between Dart and Rust in the previous tuto It's important to note that creating a Protobuf `message` larger than a few megabytes is not recommended. For large data, split them into multiple signals, or use the `binary` field.[^2] [^1]: Rinf relies solely on native FFI for communication, avoiding the use of web protocols or hidden threads. The goal is to minimize performance overhead as much as possible. - [^2]: Sending a serialized message or binary data is a zero-copy operation from Rust to Dart, while it involves a copy operation from Dart to Rust in memory. Keep in mind that Protobuf's serialization and deserialization does involve memory copy. ## 🗃️ Generation Path @@ -46,7 +45,7 @@ message SomeData { ... } This applies same to marked Protobuf messages. ```proto title="Protobuf" -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] // This is an audio data sample of... // contains... // responsible for... diff --git a/documentation/docs/frequently-asked-questions.md b/documentation/docs/frequently-asked-questions.md index d0fc281b..a74cf63b 100644 --- a/documentation/docs/frequently-asked-questions.md +++ b/documentation/docs/frequently-asked-questions.md @@ -188,13 +188,13 @@ However, if you really need to store some state in a Flutter widget, you can ach syntax = "proto3"; package tutorial_resource; -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message MyUniqueInput { int32 interaction_id = 1; int32 before_number = 2; } -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message MyUniqueOutput { int32 interaction_id = 1; int32 after_number = 2; diff --git a/documentation/docs/messaging.md b/documentation/docs/messaging.md index 7673383f..2ad8f3a1 100644 --- a/documentation/docs/messaging.md +++ b/documentation/docs/messaging.md @@ -4,10 +4,10 @@ There are special comments that you can mark messages with. ## 📢 Channels -`[RINF:RUST-SIGNAL]` generates a message channel from Rust to Dart. +`[RUST-SIGNAL]` generates a message channel from Rust to Dart. ```proto title="Protobuf" -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message MyDataOutput { ... } ``` @@ -29,10 +29,10 @@ StreamBuilder( ) ``` -Use `[RINF:RUST-SIGNAL-BINARY]` to include binary data without the overhead of serialization. +Use `[RUST-SIGNAL-BINARY]` to include binary data without the overhead of serialization. ```proto title="Protobuf" -// [RINF:RUST-SIGNAL-BINARY] +// [RUST-SIGNAL-BINARY] message MyDataOutput { ... } ``` @@ -56,10 +56,10 @@ StreamBuilder( ) ``` -`[RINF:DART-SIGNAL]` generates a message channel from Dart to Rust. +`[DART-SIGNAL]` generates a message channel from Dart to Rust. ```proto title="Protobuf" -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message MyDataInput { ... } ``` @@ -75,10 +75,10 @@ while let Some(dart_signal) = receiver.recv().await { } ``` -Use `[RINF:DART-SIGNAL-BINARY]` to include binary data without the overhead of serialization. +Use `[DART-SIGNAL-BINARY]` to include binary data without the overhead of serialization. ```proto title="Protobuf" -// [RINF:DART-SIGNAL-BINARY] +// [DART-SIGNAL-BINARY] message MyDataInput { ... } ``` @@ -98,9 +98,9 @@ while let Some(dart_signal) = receiver.recv().await { ## 🔖 Attributes -`[RINF:RUST-ATTRIBUTE(...)]` writes an attribute above the generated message struct in Rust. This is useful when you want to automatically implement a trait for the message struct in Rust. +`[RUST-ATTRIBUTE(...)]` writes an attribute above the generated message struct in Rust. This is useful when you want to automatically implement a trait for the message struct in Rust. ```proto title="Protobuf" -// [RINF:RUST-ATTRIBUTE(#[derive(Hash)])] +// [RUST-ATTRIBUTE(#[derive(Hash)])] message MyDataInput { ... } ``` diff --git a/documentation/docs/tutorial.md b/documentation/docs/tutorial.md index ecb899df..88f35d63 100644 --- a/documentation/docs/tutorial.md +++ b/documentation/docs/tutorial.md @@ -15,13 +15,13 @@ child: Column( Let's say that you want to create a new button in Dart that sends an array of numbers and a string to Rust. We need a signal to notify Rust that a user event has occurred. -Write a new `.proto` file in the `./messages` directory with a new message. Note that the message should have the comment `[RINF:DART-SIGNAL]` above it. +Write a new `.proto` file in the `./messages` directory with a new message. Note that the message should have the comment `[DART-SIGNAL]` above it. ```proto title="messages/tutorial_messages.proto" syntax = "proto3"; package tutorial_messages; -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message MyPreciousData { repeated int32 input_numbers = 1; string input_string = 2; @@ -99,13 +99,13 @@ flutter: ZERO-COST ABSTRACTION Let's say that you want to send increasing numbers every second from Rust to Dart. -Define the message. Note that the message should have the comment `[RINF:RUST-SIGNAL]` above it. +Define the message. Note that the message should have the comment `[RUST-SIGNAL]` above it. ```proto title="messages/tutorial_messages.proto" syntax = "proto3"; package tutorial_messages; -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message MyAmazingNumber { int32 current_number = 1; } ``` @@ -172,10 +172,10 @@ You can easily show the updated state on the screen by combining those two ways syntax = "proto3"; package tutorial_messages; -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message MyTreasureInput {} -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message MyTreasureOutput { int32 current_value = 1; } ``` diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 77e3d1f0..566b24a3 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -689,14 +689,14 @@ Future>>> analyzeMarkedMessages( ); final content = await protoFile.readAsString(); final regExp = RegExp(r'{[^}]*}'); - final attrExp = RegExp(r'(?<=\[RINF:RUST-ATTRIBUTE\().*(?=\)\])'); + final attrExp = RegExp(r'(?<=\[RUST-ATTRIBUTE\().*(?=\)\])'); // Remove all { ... } blocks from the string final contentWithoutBlocks = content.replaceAll(regExp, ';'); final statements = contentWithoutBlocks.split(';'); for (final statementRaw in statements) { final statement = statementRaw.trim(); - // To find "}\n\n// [RINF:RUST-SIGNAL]", + // To find "}\n\n// [RUST-SIGNAL]", // `contains` is used instead of `startsWith` String? messageName = null; final lines = statement.split('\n'); @@ -711,17 +711,17 @@ Future>>> analyzeMarkedMessages( continue; } MarkType? markType = null; - if (statement.contains('[RINF:DART-SIGNAL]')) { + if (statement.contains('[DART-SIGNAL]')) { markType = MarkType.dartSignal; - } else if (statement.contains('[RINF:DART-SIGNAL-BINARY]')) { + } else if (statement.contains('[DART-SIGNAL-BINARY]')) { markType = MarkType.dartSignalBinary; - } else if (statement.contains('[RINF:RUST-SIGNAL]')) { + } else if (statement.contains('[RUST-SIGNAL]')) { markType = MarkType.rustSignal; - } else if (statement.contains('[RINF:RUST-SIGNAL-BINARY]')) { + } else if (statement.contains('[RUST-SIGNAL-BINARY]')) { markType = MarkType.rustSignalBinary; } - // find [RINF:RUST-ATTRIBUTE(...)] + // find [RUST-ATTRIBUTE(...)] var attr = attrExp.stringMatch(statement); if (attr != null) { markedMessages[subPath]![filename]!.add(MarkedMessage( diff --git a/flutter_package/example/messages/counter_number.proto b/flutter_package/example/messages/counter_number.proto index 6484fa1c..dacd0193 100644 --- a/flutter_package/example/messages/counter_number.proto +++ b/flutter_package/example/messages/counter_number.proto @@ -1,7 +1,7 @@ syntax = "proto3"; package counter_number; -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message SampleNumberInput { string letter = 1; uint32 dummy_one = 2; @@ -9,7 +9,7 @@ message SampleNumberInput { repeated int32 dummy_three = 4; } -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message SampleNumberOutput { int32 current_number = 1; uint32 dummy_one = 2; diff --git a/flutter_package/example/messages/fractal_art.proto b/flutter_package/example/messages/fractal_art.proto index 556e907e..aa8c9d4d 100644 --- a/flutter_package/example/messages/fractal_art.proto +++ b/flutter_package/example/messages/fractal_art.proto @@ -3,7 +3,7 @@ package fractal_art; import "counter_number.proto"; -// [RINF:RUST-SIGNAL-BINARY] +// [RUST-SIGNAL-BINARY] // You can add your custom comments like this. // Protobuf's import statement also works well. message SampleFractal { diff --git a/flutter_package/example/messages/sample_folder/sample_file.proto b/flutter_package/example/messages/sample_folder/sample_file.proto index 1c31d4ec..7181e13c 100644 --- a/flutter_package/example/messages/sample_folder/sample_file.proto +++ b/flutter_package/example/messages/sample_folder/sample_file.proto @@ -6,7 +6,7 @@ enum Kind { two = 1; } -// [RINF:DART-SIGNAL] +// [DART-SIGNAL] message SampleInput { Kind kind = 1; oneof oneof_input { @@ -15,7 +15,7 @@ message SampleInput { } } -// [RINF:RUST-SIGNAL] +// [RUST-SIGNAL] message SampleOutput { Kind kind = 1; oneof oneof_input { @@ -24,7 +24,5 @@ message SampleOutput { } } -// [RINF:RUST-ATTRIBUTE(#[derive(Hash)])] -message WithRustAttribute { - bool dummy = 1; -} +// [RUST-ATTRIBUTE(#[derive(Hash)])] +message WithRustAttribute { bool dummy = 1; } diff --git a/flutter_package/template/messages/basic.proto b/flutter_package/template/messages/basic.proto index 39e7d723..fc46b9c8 100644 --- a/flutter_package/template/messages/basic.proto +++ b/flutter_package/template/messages/basic.proto @@ -1,12 +1,8 @@ syntax = "proto3"; package basic; -// [RINF:DART-SIGNAL] -message SmallText { - string text = 1; -} +// [DART-SIGNAL] +message SmallText { string text = 1; } -// [RINF:RUST-SIGNAL] -message SmallNumber { - int32 number = 1; -} +// [RUST-SIGNAL] +message SmallNumber { int32 number = 1; } From 26186356d3fa7d9a31074f719a3628f1ae79c51f Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:23:20 +0900 Subject: [PATCH 04/19] Use `ProgressBar` in `rinf wasm` --- flutter_package/bin/src/helpers.dart | 27 +++-- flutter_package/bin/src/progress.dart | 145 ++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 flutter_package/bin/src/progress.dart diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index 16615f66..3c6e6efd 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -8,6 +8,7 @@ import 'config.dart'; import 'message.dart'; import 'common.dart'; import 'internet.dart'; +import 'progress.dart'; /// Creates new folders and files to an existing Flutter project folder. Future applyRustTemplate({ @@ -206,9 +207,12 @@ Future copyDirectory(Uri source, Uri destination) async { Future buildWebassembly(bool isReleaseMode) async { // Ensure Rust toolchain. + final fillingBar = ProgressBar( + total: 3, + width: 12, + ); if (isInternetConnected) { - print('Ensuring Rust toolchain for the web.' + - '\nThis is done by installing it globally on the system.'); + fillingBar.desc = 'Installing Rust toolchain for the web'; final processResults = []; processResults.add(await Process.run('rustup', [ 'toolchain', @@ -242,12 +246,11 @@ Future buildWebassembly(bool isReleaseMode) async { ])); processResults.forEach((processResult) { if (processResult.exitCode != 0) { - print(processResult.stderr.toString().trim()); - throw Exception('Cannot globally install Rust toolchain for the web.'); + throw Exception(processResult.stderr); } }); } else { - print('Skipping ensurement of Rust toolchain for the web.'); + fillingBar.desc = 'Skipping ensurement of Rust toolchain for the web'; } // Prepare the webassembly output path. @@ -256,7 +259,8 @@ Future buildWebassembly(bool isReleaseMode) async { final outputPath = flutterProjectPath.uri.join(subPath); // Build the webassembly module. - print('Compiling Rust with `wasm-pack` to `web` target...'); + fillingBar.desc = 'Compiling Rust with `wasm-pack` to `web` target'; + fillingBar.increment(); final compileCommand = await Process.run( 'wasm-pack', [ @@ -278,12 +282,13 @@ Future buildWebassembly(bool isReleaseMode) async { }, ); if (compileCommand.exitCode != 0) { - print(compileCommand.stderr.toString().trim()); - throw Exception('Unable to compile Rust into webassembly'); + throw Exception(compileCommand.stderr); } - print('Saved `.wasm` and `.js` files to `$subPath`.'); + fillingBar.desc = 'Saved `.wasm` and `.js` files to `$subPath`'; + fillingBar.increment(); - print('🎉 Webassembly module is now ready! 🎉'); + fillingBar.desc = '🎉 Webassembly module is now ready! 🎉'; + fillingBar.increment(); // Guide the developer how to run Flutter web server with web headers. print('To run the Flutter web server, use:'); @@ -293,7 +298,7 @@ Future buildWebassembly(bool isReleaseMode) async { '--web-header=Cross-Origin-Opener-Policy=same-origin', '--web-header=Cross-Origin-Embedder-Policy=require-corp' ]; - print(commandLines.join(' ${commandLineDivider}\n').onGray); + print(commandLines.join(' ${commandLineDivider}\n').dim); } Future getCommandLineDivider() async { diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart new file mode 100644 index 00000000..dcf5ea56 --- /dev/null +++ b/flutter_package/bin/src/progress.dart @@ -0,0 +1,145 @@ +// CLI progress bar copied from +// https://github.com/RohitEdathil/ConsoleBars + +import 'dart:async'; +import 'dart:io'; + +class ProgressBar { + /// Total number of steps + int _total; + + int _current = 0; + int _progress = 0; + late int max; + + // Time + final _clock = Stopwatch(); + + /// Whether a timer should be present + bool time; + + /// Percentage should be displayed or not + bool percentage; + + // Decorations + + /// The description of the bar + String _desc; + + /// The chararcter to used as space + String space; + + /// The character to used as fill + String fill; + + /// Scale of the bar relative to the terminal width + double scale; + + /// Width of the bar + int? width; + + int get total => _total; + String get desc => _desc; + + set desc(String desc) { + _desc = desc; + _render(); + } + + set total(int total) { + _total = total; + _render(); + } + + /// Arguments: + /// - total : Total number of steps + /// - desc : Simple text shown before the bar (optional) + /// - space : Character denoting empty space (default : '.') + /// - fill : Character denoting filled space (default : '█') + /// - time : Toggle timing mode (default : false) + /// - percentage : Toggle percentage display (default : false) + /// - scale : Scale of the bar relative to width (between: 0 and 1, default: 0.5, Irrelavant if width is specified) + /// - width : Width of the bar drawn in the CLI + ProgressBar( + {required int total, + String desc = '', + this.space = '.', + this.fill = '█', + this.time = false, + this.percentage = false, + this.scale = 0.5, + this.width = 40}) + : _desc = desc, + _total = total { + // Handles width of the bar, throws an error if it's not specified and the terminal width is not available + try { + max = width ?? ((stdout.terminalColumns - _desc.length) * scale).toInt(); + } on StdoutException { + throw StdoutException( + 'Could not get terminal width, try specifying a width manually'); + } + if (time) { + _clock.start(); + scheduleMicrotask(autoRender); + } + _render(); + } + + /// Updates the _current value to n + void update(int n) { + _current = n; + _render(); + } + + /// Increments the _current value + void increment({String? desc}) { + if (desc != null) { + this._desc = desc; + } + _current++; + _render(); + } + + /// Automatically updates the frame asynchronously + void autoRender() async { + while (_clock.isRunning) { + await Future.delayed(Duration(seconds: 1)); + _render(); + } + } + + /// Renders a frame of the bar + void _render() { + _progress = ((_current / _total) * max).toInt(); + if (_progress >= max) { + _progress = max; + if (_clock.isRunning) { + _clock.stop(); + } + } + String timeStr = ''; + if (time) { + final rate = _clock.elapsedMicroseconds / (_current == 0 ? 1 : _current); + final eta = Duration(microseconds: ((_total - _current) * rate).toInt()); + timeStr = '[ ' + + _clock.elapsed.toString().substring(0, 10) + + ' / ' + + eta.toString().substring(0, 10) + + ' ]'; + } + String perc = ''; + if (percentage) { + perc = '${(_current * 100 / _total).toStringAsFixed(1)}%'; + } + final bar = '${fill * _progress}${space * (max - _progress)}'; + final frameParts = [bar, '$_current/$_total', perc, timeStr, ':', _desc]; + final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); + final frame = filteredParts.join(' '); + stdout.write('\r'); + stdout.write(frame); + stdout.write(' ' * 100); + if (max == _progress) { + stdout.write('\r\n'); + } + } +} From 48df31d00557e6dbf3886bd8bde048ec20cb5829 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:37:43 +0900 Subject: [PATCH 05/19] Use `ProgressBar` in `rinf message` --- flutter_package/bin/src/helpers.dart | 2 +- flutter_package/bin/src/message.dart | 56 +++++++++++++-------------- flutter_package/bin/src/progress.dart | 30 ++++++++------ 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index 3c6e6efd..fdedd82c 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -206,11 +206,11 @@ Future copyDirectory(Uri source, Uri destination) async { } Future buildWebassembly(bool isReleaseMode) async { - // Ensure Rust toolchain. final fillingBar = ProgressBar( total: 3, width: 12, ); + // Ensure Rust toolchain. if (isInternetConnected) { fillingBar.desc = 'Installing Rust toolchain for the web'; final processResults = []; diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 566b24a3..d6917feb 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -6,6 +6,7 @@ import 'package:watcher/watcher.dart'; import 'config.dart'; import 'common.dart'; import 'internet.dart'; +import 'progress.dart'; enum MarkType { dartSignal, @@ -30,6 +31,11 @@ Future generateMessageCode({ bool silent = false, required RinfConfigMessage messageConfig, }) async { + final fillingBar = ProgressBar( + total: 8, + width: 16, + silent: silent, + ); // Prepare paths. final flutterProjectPath = Directory.current; final protoPath = flutterProjectPath.uri.join(messageConfig.inputDir); @@ -43,23 +49,25 @@ Future generateMessageCode({ await emptyDirectory(dartOutputPath); // Get the list of `.proto` files. + // Also, analyze marked messages in `.proto` files. + fillingBar.desc = 'Collecting Protobuf files'; final resourcesInFolders = >{}; await collectProtoFiles( Directory.fromUri(protoPath), Directory.fromUri(protoPath), resourcesInFolders, ); - - // Analyze marked messages in `.proto` files. final markedMessagesAll = await analyzeMarkedMessages( protoPath, resourcesInFolders, ); + fillingBar.increment(); // Include `package` statement in `.proto` files. // Package name should be the same as the filename // because Rust filenames are written with package name // and Dart filenames are written with the `.proto` filename. + fillingBar.desc = 'Normalizing Protobuf files'; for (final entry in resourcesInFolders.entries) { final subPath = entry.key; final resourceNames = entry.value; @@ -84,25 +92,18 @@ Future generateMessageCode({ await protoFile.writeAsString(outputLines.join('\n') + '\n'); } } + fillingBar.increment(); // Generate Rust message files. + fillingBar.desc = 'Generating Rust message files'; if (isInternetConnected) { - if (!silent) { - print('Ensuring `protoc-gen-prost` for Rust.' + - '\nThis is done by installing it globally on the system.'); - } final cargoInstallCommand = await Process.run('cargo', [ 'install', 'protoc-gen-prost', ...(messageConfig.rustSerde ? ['protoc-gen-prost-serde'] : []) ]); if (cargoInstallCommand.exitCode != 0) { - print(cargoInstallCommand.stderr.toString().trim()); - throw Exception('Cannot globally install `protoc-gen-prost` Rust crate'); - } - } else { - if (!silent) { - print('Skipping ensurement of `protoc-gen-prost` for Rust.'); + throw Exception(cargoInstallCommand.stderr); } } for (final entry in resourcesInFolders.entries) { @@ -132,12 +133,13 @@ Future generateMessageCode({ }) ]); if (protocRustResult.exitCode != 0) { - print(protocRustResult.stderr.toString().trim()); - throw Exception('Could not compile `.proto` files into Rust'); + throw Exception(protocRustResult.stderr); } } + fillingBar.increment(); // Generate `mod.rs` for `messages` module in Rust. + fillingBar.desc = 'Writing `mod.rs` files'; for (final entry in resourcesInFolders.entries) { final subPath = entry.key; final resourceNames = entry.value; @@ -188,13 +190,11 @@ Future generateMessageCode({ await File.fromUri(rustOutputPath.join(subPath).join('mod.rs')) .writeAsString(modRsContent); } + fillingBar.increment(); // Generate Dart message files. + fillingBar.desc = 'Generating Dart message files'; if (isInternetConnected) { - if (!silent) { - print('Ensuring `protoc_plugin` for Dart.' + - '\nThis is done by installing it globally on the system.'); - } final pubGlobalActivateCommand = await Process.run('dart', [ 'pub', 'global', @@ -202,12 +202,7 @@ Future generateMessageCode({ 'protoc_plugin', ]); if (pubGlobalActivateCommand.exitCode != 0) { - print(pubGlobalActivateCommand.stderr.toString().trim()); - throw Exception('Cannot globally install `protoc_plugin` Dart package'); - } - } else { - if (!silent) { - print('Skipping ensurement of `protoc_plugin` for Dart.'); + throw Exception(pubGlobalActivateCommand.stderr); } } for (final entry in resourcesInFolders.entries) { @@ -233,12 +228,13 @@ Future generateMessageCode({ ], ); if (protocDartResult.exitCode != 0) { - print(protocDartResult.stderr.toString().trim()); - throw Exception('Could not compile `.proto` files into Dart'); + throw Exception(protocDartResult.stderr); } } + fillingBar.increment(); // Generate `exports.dart` for `messages` module in Dart. + fillingBar.desc = 'Writing `exports.dart` file'; final exportsDartLines = []; exportsDartLines.add("export './generated.dart';"); for (final entry in resourcesInFolders.entries) { @@ -254,8 +250,10 @@ Future generateMessageCode({ final exportsDartContent = exportsDartLines.join('\n'); await File.fromUri(dartOutputPath.join('exports.dart')) .writeAsString(exportsDartContent); + fillingBar.increment(); // Prepare communication channels between Dart and Rust. + fillingBar.desc = 'Writing communication channels and streams'; for (final entry in markedMessagesAll.entries) { final subPath = entry.key; final filesAndMarks = entry.value; @@ -540,11 +538,11 @@ void assignRustSignal(int messageId, Uint8List messageBytes, Uint8List binary) { '''; await File.fromUri(dartOutputPath.join('generated.dart')) .writeAsString(dartReceiveScript); + fillingBar.increment(); // Notify that it's done - if (!silent) { - print('🎉 Message code in Dart and Rust is now ready! 🎉'); - } + fillingBar.desc = '🎉 Message code in Dart and Rust is now ready! 🎉'; + fillingBar.increment(); } Future watchAndGenerateMessageCode( diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index dcf5ea56..f8528f5c 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -38,6 +38,9 @@ class ProgressBar { /// Width of the bar int? width; + /// Whether the instance should print nothing + bool silent; + int get total => _total; String get desc => _desc; @@ -60,16 +63,17 @@ class ProgressBar { /// - percentage : Toggle percentage display (default : false) /// - scale : Scale of the bar relative to width (between: 0 and 1, default: 0.5, Irrelavant if width is specified) /// - width : Width of the bar drawn in the CLI - ProgressBar( - {required int total, - String desc = '', - this.space = '.', - this.fill = '█', - this.time = false, - this.percentage = false, - this.scale = 0.5, - this.width = 40}) - : _desc = desc, + ProgressBar({ + required int total, + String desc = '', + this.space = '.', + this.fill = '█', + this.time = false, + this.percentage = false, + this.scale = 0.5, + this.width = 40, + this.silent = false, + }) : _desc = desc, _total = total { // Handles width of the bar, throws an error if it's not specified and the terminal width is not available try { @@ -110,6 +114,9 @@ class ProgressBar { /// Renders a frame of the bar void _render() { + if (silent) { + return; + } _progress = ((_current / _total) * max).toInt(); if (_progress >= max) { _progress = max; @@ -136,8 +143,9 @@ class ProgressBar { final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); final frame = filteredParts.join(' '); stdout.write('\r'); + stdout.write(' ' * 200); + stdout.write('\r'); stdout.write(frame); - stdout.write(' ' * 100); if (max == _progress) { stdout.write('\r\n'); } From a0e930d31ed6d3c3aa7c1416d4523fe2f9fa40ea Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:39:20 +0900 Subject: [PATCH 06/19] Dim `rinf config` output text --- flutter_package/bin/rinf.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flutter_package/bin/rinf.dart b/flutter_package/bin/rinf.dart index 402ec8ca..b75551da 100644 --- a/flutter_package/bin/rinf.dart +++ b/flutter_package/bin/rinf.dart @@ -1,4 +1,5 @@ import 'package:args/command_runner.dart'; +import 'package:chalkdart/chalkstrings.dart'; import 'src/config.dart'; import 'src/helpers.dart'; @@ -30,7 +31,7 @@ class ConfigCommand extends Command { Future run() async { final rinfConfig = await loadVerifiedRinfConfig('pubspec.yaml'); - print(rinfConfig); + print(rinfConfig.toString().dim); } } From 182568ce545322425ba93042b81432e77bb5cbc5 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:48:55 +0900 Subject: [PATCH 07/19] Remove CLI lines on command start --- flutter_package/bin/rinf.dart | 10 ++++++++++ flutter_package/bin/src/common.dart | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/flutter_package/bin/rinf.dart b/flutter_package/bin/rinf.dart index b75551da..700572ee 100644 --- a/flutter_package/bin/rinf.dart +++ b/flutter_package/bin/rinf.dart @@ -5,10 +5,20 @@ import 'src/config.dart'; import 'src/helpers.dart'; import 'src/message.dart'; import 'src/internet.dart'; +import 'src/common.dart'; Future main(List args) async { + // After running `dart run rinf`, + // Unnecessary two lines of + //`Building package executable...\nBuilt rinf:rinf.` appear. + // Remove those before proceeding. + removeCliLine(); + removeCliLine(); + + // Check the internet connection status and rembember it. await checkConnectivity(); + // Parse CLI arguments and run the corresponding function. final runner = CommandRunner( 'rinf', 'Helper commands for building apps with Rust in Flutter.', diff --git a/flutter_package/bin/src/common.dart b/flutter_package/bin/src/common.dart index 3bbe5171..f94a9d7c 100644 --- a/flutter_package/bin/src/common.dart +++ b/flutter_package/bin/src/common.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + extension UriJoin on Uri { Uri join(String path) { if (path.isEmpty || path == '/') { @@ -12,3 +14,9 @@ extension UriJoin on Uri { } } } + +/// Removes an existing line from the CLI. +void removeCliLine() { + stdout.write('\x1B[1A'); // Move the cursor up one line + stdout.write('\x1B[2K'); // Clear the line +} From 0dfb4d90b3d5f216b004a32e13f1321c849776f9 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:50:07 +0900 Subject: [PATCH 08/19] Organize code --- flutter_package/bin/src/progress.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index f8528f5c..d9f77f7b 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -142,8 +142,7 @@ class ProgressBar { final frameParts = [bar, '$_current/$_total', perc, timeStr, ':', _desc]; final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); final frame = filteredParts.join(' '); - stdout.write('\r'); - stdout.write(' ' * 200); + stdout.write('\x1B[2K'); // Clear the line stdout.write('\r'); stdout.write(frame); if (max == _progress) { From 4f0741de12aca28374503230f0d7b884aa5d6eea Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:51:37 +0900 Subject: [PATCH 09/19] Organize CLI manipulation code --- flutter_package/bin/src/common.dart | 6 +++++- flutter_package/bin/src/progress.dart | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/flutter_package/bin/src/common.dart b/flutter_package/bin/src/common.dart index f94a9d7c..db54d10a 100644 --- a/flutter_package/bin/src/common.dart +++ b/flutter_package/bin/src/common.dart @@ -15,8 +15,12 @@ extension UriJoin on Uri { } } +void clearCliLine() { + stdout.write('\x1B[2K'); // Clear the line +} + /// Removes an existing line from the CLI. void removeCliLine() { stdout.write('\x1B[1A'); // Move the cursor up one line - stdout.write('\x1B[2K'); // Clear the line + clearCliLine(); } diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index d9f77f7b..691c21c4 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:io'; +import 'common.dart'; class ProgressBar { /// Total number of steps @@ -142,7 +143,7 @@ class ProgressBar { final frameParts = [bar, '$_current/$_total', perc, timeStr, ':', _desc]; final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); final frame = filteredParts.join(' '); - stdout.write('\x1B[2K'); // Clear the line + clearCliLine(); stdout.write('\r'); stdout.write(frame); if (max == _progress) { From 7587a5bf526e6b946fb5cf5927880343d7e58f03 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 01:57:06 +0900 Subject: [PATCH 10/19] Organize code --- flutter_package/bin/rinf.dart | 3 +-- flutter_package/bin/src/common.dart | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/flutter_package/bin/rinf.dart b/flutter_package/bin/rinf.dart index 700572ee..d25566a8 100644 --- a/flutter_package/bin/rinf.dart +++ b/flutter_package/bin/rinf.dart @@ -12,8 +12,7 @@ Future main(List args) async { // Unnecessary two lines of //`Building package executable...\nBuilt rinf:rinf.` appear. // Remove those before proceeding. - removeCliLine(); - removeCliLine(); + removeCliLines(2); // Check the internet connection status and rembember it. await checkConnectivity(); diff --git a/flutter_package/bin/src/common.dart b/flutter_package/bin/src/common.dart index db54d10a..e6065222 100644 --- a/flutter_package/bin/src/common.dart +++ b/flutter_package/bin/src/common.dart @@ -20,7 +20,9 @@ void clearCliLine() { } /// Removes an existing line from the CLI. -void removeCliLine() { - stdout.write('\x1B[1A'); // Move the cursor up one line - clearCliLine(); +void removeCliLines(int lines) { + for (var i = 0; i < lines; i++) { + stdout.write('\x1B[1A'); // Move the cursor up one line + clearCliLine(); + } } From 5fe86faaaef11b012498722afabad2955d0f1f57 Mon Sep 17 00:00:00 2001 From: temeddix Date: Tue, 17 Sep 2024 02:01:12 +0900 Subject: [PATCH 11/19] Remove unneeded standard output code --- flutter_package/bin/src/common.dart | 1 + flutter_package/bin/src/progress.dart | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter_package/bin/src/common.dart b/flutter_package/bin/src/common.dart index e6065222..2847c4e3 100644 --- a/flutter_package/bin/src/common.dart +++ b/flutter_package/bin/src/common.dart @@ -17,6 +17,7 @@ extension UriJoin on Uri { void clearCliLine() { stdout.write('\x1B[2K'); // Clear the line + stdout.write('\r'); // Return the cursor } /// Removes an existing line from the CLI. diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index 691c21c4..0a583c90 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -144,10 +144,9 @@ class ProgressBar { final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); final frame = filteredParts.join(' '); clearCliLine(); - stdout.write('\r'); stdout.write(frame); if (max == _progress) { - stdout.write('\r\n'); + stdout.write('\n'); } } } From 610eb9618f4b8746a25ab5bbcecef906c1a506b3 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:10:38 +0900 Subject: [PATCH 12/19] Improve message watch output --- flutter_package/bin/src/helpers.dart | 5 ----- flutter_package/bin/src/message.dart | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index fdedd82c..7d202935 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -30,11 +30,6 @@ Future applyRustTemplate({ // Check if current folder is a Flutter app project. final specFile = File.fromUri(flutterProjectPath.join('pubspec.yaml')); - final isFlutterProject = await specFile.exists(); - if (!isFlutterProject) { - print("This folder doesn't look like a Flutter project."); - return; - } final pubspec = loadYaml(await specFile.readAsString()); final String? publishTo = pubspec['publish_to']; if (publishTo != 'none') { diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index d6917feb..00f8ed58 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:path/path.dart'; import 'package:watcher/watcher.dart'; +import 'package:chalkdart/chalkstrings.dart'; import 'config.dart'; import 'common.dart'; @@ -569,12 +570,21 @@ Future watchAndGenerateMessageCode( var generated = true; print('Watching `.proto` files...'); print('Press `q` to stop watching.'); + print('Nothing changed yet'.dim); watcher.events.listen((event) { if (event.path.endsWith('.proto') && generated) { var eventType = event.type.toString(); eventType = eventType[0].toUpperCase() + eventType.substring(1); final fileRelativePath = relative(event.path, from: messagesPath); - print('$eventType: $fileRelativePath'); + removeCliLines(1); + final now = DateTime.now(); + final formattedTime = + "${now.year}-${now.month.toString().padLeft(2, '0')}-" + "${now.day.toString().padLeft(2, '0')} " + "${now.hour.toString().padLeft(2, '0')}:" + "${now.minute.toString().padLeft(2, '0')}:" + "${now.second.toString().padLeft(2, '0')}"; + print('$eventType: $fileRelativePath ($formattedTime)'.dim); generated = false; } }); @@ -586,9 +596,8 @@ Future watchAndGenerateMessageCode( if (!generated) { try { await generateMessageCode(silent: true, messageConfig: messageConfig); - print('Message code generated.'); } catch (error) { - // When message code generation has failed + // Do nothing when message code generation has failed } generated = true; } From efcb392f4654a97c763db2f3fbd840ea6ad43771 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:12:36 +0900 Subject: [PATCH 13/19] Improve message watching CLI output --- flutter_package/bin/src/message.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 00f8ed58..2ba54a26 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -568,8 +568,7 @@ Future watchAndGenerateMessageCode( // Watch `.proto` files. final watcher = PollingDirectoryWatcher(messagesDirectory.path); var generated = true; - print('Watching `.proto` files...'); - print('Press `q` to stop watching.'); + print('Watching `.proto` files, press `q` to stop watching'); print('Nothing changed yet'.dim); watcher.events.listen((event) { if (event.path.endsWith('.proto') && generated) { From bef8b0293baff93d6cf1fe0666b3ab9f17884e8b Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:19:36 +0900 Subject: [PATCH 14/19] Organize CLI manipulation code --- flutter_package/bin/src/common.dart | 8 ++------ flutter_package/bin/src/progress.dart | 8 +++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/flutter_package/bin/src/common.dart b/flutter_package/bin/src/common.dart index 2847c4e3..c3c96775 100644 --- a/flutter_package/bin/src/common.dart +++ b/flutter_package/bin/src/common.dart @@ -15,15 +15,11 @@ extension UriJoin on Uri { } } -void clearCliLine() { - stdout.write('\x1B[2K'); // Clear the line - stdout.write('\r'); // Return the cursor -} - /// Removes an existing line from the CLI. void removeCliLines(int lines) { for (var i = 0; i < lines; i++) { stdout.write('\x1B[1A'); // Move the cursor up one line - clearCliLine(); + stdout.write('\x1B[2K'); // Clear the line + stdout.write('\r'); // Return the cursor to the front } } diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index 0a583c90..fd0171e8 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -87,6 +87,7 @@ class ProgressBar { _clock.start(); scheduleMicrotask(autoRender); } + print(''); _render(); } @@ -143,10 +144,7 @@ class ProgressBar { final frameParts = [bar, '$_current/$_total', perc, timeStr, ':', _desc]; final filteredParts = frameParts.where((v) => v.isNotEmpty).toList(); final frame = filteredParts.join(' '); - clearCliLine(); - stdout.write(frame); - if (max == _progress) { - stdout.write('\n'); - } + removeCliLines(1); + print(frame); } } From 640df008d200f222a14c2afd458ca03a0b7e39be Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:22:45 +0900 Subject: [PATCH 15/19] Organize CLI output text --- flutter_package/bin/src/helpers.dart | 4 ++-- flutter_package/bin/src/message.dart | 2 +- flutter_package/bin/src/progress.dart | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/flutter_package/bin/src/helpers.dart b/flutter_package/bin/src/helpers.dart index 7d202935..8a014b18 100644 --- a/flutter_package/bin/src/helpers.dart +++ b/flutter_package/bin/src/helpers.dart @@ -178,7 +178,7 @@ please refer to Rinf's [documentation](https://rinf.cunarist.com). await generateMessageCode(silent: true, messageConfig: messageConfig); - print('🎉 Rust template is now ready! 🎉'); + print('Rust template is now ready 🎉'); } Future copyDirectory(Uri source, Uri destination) async { @@ -282,7 +282,7 @@ Future buildWebassembly(bool isReleaseMode) async { fillingBar.desc = 'Saved `.wasm` and `.js` files to `$subPath`'; fillingBar.increment(); - fillingBar.desc = '🎉 Webassembly module is now ready! 🎉'; + fillingBar.desc = 'Webassembly module is now ready 🎉'; fillingBar.increment(); // Guide the developer how to run Flutter web server with web headers. diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index 2ba54a26..fdb5a9ed 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -542,7 +542,7 @@ void assignRustSignal(int messageId, Uint8List messageBytes, Uint8List binary) { fillingBar.increment(); // Notify that it's done - fillingBar.desc = '🎉 Message code in Dart and Rust is now ready! 🎉'; + fillingBar.desc = 'Message code in Dart and Rust is now ready 🎉'; fillingBar.increment(); } diff --git a/flutter_package/bin/src/progress.dart b/flutter_package/bin/src/progress.dart index fd0171e8..0c8432cf 100644 --- a/flutter_package/bin/src/progress.dart +++ b/flutter_package/bin/src/progress.dart @@ -87,7 +87,9 @@ class ProgressBar { _clock.start(); scheduleMicrotask(autoRender); } - print(''); + if (!silent) { + print(''); + } _render(); } From 0294d918096b036ee4df4f3946ca4d05d3bd45fb Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:34:35 +0900 Subject: [PATCH 16/19] Reformat a markdown file --- documentation/docs/detailed-techniques.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/detailed-techniques.md b/documentation/docs/detailed-techniques.md index e22ef0a4..08abd19c 100644 --- a/documentation/docs/detailed-techniques.md +++ b/documentation/docs/detailed-techniques.md @@ -11,6 +11,7 @@ We've covered how to pass signals[^1] between Dart and Rust in the previous tuto It's important to note that creating a Protobuf `message` larger than a few megabytes is not recommended. For large data, split them into multiple signals, or use the `binary` field.[^2] [^1]: Rinf relies solely on native FFI for communication, avoiding the use of web protocols or hidden threads. The goal is to minimize performance overhead as much as possible. + [^2]: Sending a serialized message or binary data is a zero-copy operation from Rust to Dart, while it involves a copy operation from Dart to Rust in memory. Keep in mind that Protobuf's serialization and deserialization does involve memory copy. ## 🗃️ Generation Path From af97db7af26d6f631ddfcb95105c4e9785a46916 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 00:49:11 +0900 Subject: [PATCH 17/19] Organize message mark code --- flutter_package/bin/src/message.dart | 69 ++++++++++++++++++---------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index fdb5a9ed..abb1539c 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -17,11 +17,11 @@ enum MarkType { rustAttribute, } -class MarkedMessage { +class MessageMark { MarkType markType; String name; int id; - MarkedMessage( + MessageMark( this.markType, this.name, this.id, @@ -672,19 +672,19 @@ Future insertTextToFile( await file.writeAsString(fileContent); } -Future>>> analyzeMarkedMessages( +Future>>> analyzeMarkedMessages( Uri protoPath, Map> resourcesInFolders, ) async { - final markedMessages = >>{}; + final messageMarks = >>{}; for (final entry in resourcesInFolders.entries) { final subpath = entry.key; final filenames = entry.value; - final markedMessagesInFiles = >{}; + final markedMessagesInFiles = >{}; for (final filename in filenames) { markedMessagesInFiles[filename] = []; } - markedMessages[subpath] = markedMessagesInFiles; + messageMarks[subpath] = markedMessagesInFiles; } int messageId = 0; for (final entry in resourcesInFolders.entries) { @@ -716,21 +716,51 @@ Future>>> analyzeMarkedMessages( // When the statement is not a message continue; } - MarkType? markType = null; + + // Find [DART-SIGNAL] if (statement.contains('[DART-SIGNAL]')) { - markType = MarkType.dartSignal; + if (statement.contains('DART-SIGNAL-BINARY')) { + throw Exception( + '`DART-SIGNAL` and `DART-SIGNAL-BINARY` cannot be used together', + ); + } + messageMarks[subPath]![filename]!.add(MessageMark( + MarkType.dartSignal, + messageName, + messageId, + )); } else if (statement.contains('[DART-SIGNAL-BINARY]')) { - markType = MarkType.dartSignalBinary; - } else if (statement.contains('[RUST-SIGNAL]')) { - markType = MarkType.rustSignal; + messageMarks[subPath]![filename]!.add(MessageMark( + MarkType.dartSignalBinary, + messageName, + messageId, + )); + } + + // Find [RUST-SIGNAL] + if (statement.contains('[RUST-SIGNAL]')) { + if (statement.contains('RUST-SIGNAL-BINARY')) { + throw Exception( + '`RUST-SIGNAL` and `RUST-SIGNAL-BINARY` cannot be used together', + ); + } + messageMarks[subPath]![filename]!.add(MessageMark( + MarkType.rustSignal, + messageName, + messageId, + )); } else if (statement.contains('[RUST-SIGNAL-BINARY]')) { - markType = MarkType.rustSignalBinary; + messageMarks[subPath]![filename]!.add(MessageMark( + MarkType.rustSignalBinary, + messageName, + messageId, + )); } - // find [RUST-ATTRIBUTE(...)] + // Find [RUST-ATTRIBUTE(...)] var attr = attrExp.stringMatch(statement); if (attr != null) { - markedMessages[subPath]![filename]!.add(MarkedMessage( + messageMarks[subPath]![filename]!.add(MessageMark( MarkType.rustAttribute, "--prost_opt=type_attribute=$filename.$messageName=${attr.replaceAll(",", "\\,")}", -1, @@ -738,20 +768,11 @@ Future>>> analyzeMarkedMessages( continue; } - if (markType == null) { - // If there's no mark in the message, just ignore it - continue; - } - markedMessages[subPath]![filename]!.add(MarkedMessage( - markType, - messageName, - messageId, - )); messageId += 1; } } } - return markedMessages; + return messageMarks; } String pascalToCamel(String input) { From 2772606f6bd52b0854bc50b7a836720fa0bc8b45 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 01:05:38 +0900 Subject: [PATCH 18/19] Print the error gracefully --- flutter_package/bin/rinf.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/flutter_package/bin/rinf.dart b/flutter_package/bin/rinf.dart index d25566a8..5381fc36 100644 --- a/flutter_package/bin/rinf.dart +++ b/flutter_package/bin/rinf.dart @@ -28,7 +28,12 @@ Future main(List args) async { ..addCommand(MessageCommand()) ..addCommand(WasmCommand()); - await runner.run(args); + try { + await runner.run(args); + } catch (error) { + // Print the error gracefully without backtrace. + print(error.toString().trim().red); + } } class ConfigCommand extends Command { From 50cbb5b300a3d6816b1e805287fd913e07cf1821 Mon Sep 17 00:00:00 2001 From: Donghyun Kim Date: Wed, 18 Sep 2024 01:13:50 +0900 Subject: [PATCH 19/19] Print errors from message watching --- flutter_package/bin/src/message.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flutter_package/bin/src/message.dart b/flutter_package/bin/src/message.dart index abb1539c..f6cd57fa 100644 --- a/flutter_package/bin/src/message.dart +++ b/flutter_package/bin/src/message.dart @@ -575,7 +575,6 @@ Future watchAndGenerateMessageCode( var eventType = event.type.toString(); eventType = eventType[0].toUpperCase() + eventType.substring(1); final fileRelativePath = relative(event.path, from: messagesPath); - removeCliLines(1); final now = DateTime.now(); final formattedTime = "${now.year}-${now.month.toString().padLeft(2, '0')}-" @@ -583,6 +582,7 @@ Future watchAndGenerateMessageCode( "${now.hour.toString().padLeft(2, '0')}:" "${now.minute.toString().padLeft(2, '0')}:" "${now.second.toString().padLeft(2, '0')}"; + removeCliLines(1); print('$eventType: $fileRelativePath ($formattedTime)'.dim); generated = false; } @@ -596,7 +596,8 @@ Future watchAndGenerateMessageCode( try { await generateMessageCode(silent: true, messageConfig: messageConfig); } catch (error) { - // Do nothing when message code generation has failed + removeCliLines(1); + print(error.toString().trim().red); } generated = true; }