
Building a CLI Util in Dart
A Short Look at Building a Simple Util to Format Brackets & Parentheses
August 30, 2019
Problem Description
Every so often programs will output string representations of internal data
structures. The outputs are often not valid syntactic equivalents to the
language they were generated in, and so trying to apply the language’s formatter
becomes difficult if not impossible. I suppose this is why JSON objects and
arrays are pleasant to work with. Piping them into jq
or another util (online
and offline) will print a prettified structure.
Seeing the structure of things remains difficult task if there is not existing pretty-print equivalent in your language without having to write some custom serializer. I’ve had this issue in enough languages, and different context, and one thing remains constant. The output’s all have a nesting of parentheses, square brackets, and curly braces.
We have now recognized our input. We not have to identify the transformation that would yield the most readable format with minimal code. The simplest idea is to take the input, push the indentation based on every open bracket, and reduce the indent on every close brace.
([{1}])
would simply become:
(
[
{1
}
] )
This sound simple enough, and we can probably do it within a couple of functions.
Defining the Dart Program
We want to make this a command line application, so we’ll be reading from STDIN
.
The input will then need to be reshaped and inject line breaks after every key
input character: ()[]{}
.
Reading from STDIN
The entry to our program will look something like below:
import "dart:io";
main() {List<String> collector = [];
int nextByte = stdin.readByteSync();
while (nextByte != -1; ) {
String char = new String.fromCharCode(nextByte);
.add(char);
collector= stdin.readByteSync()
nextByte
}
// Output
print(format(collector)); }
We want to collect all the input from STDIN and pass it to a format
function which
will return our formatted string that we can then print back out to the
command line.
Reading from stdin is consuming one byte at a time, and converting each to a its corresponding ASCII value. This method breaks down with Unicode characters like emojis, so note that if your input contains any Unicode characters they may not be parsed correctly with this method.
Defining the format method
Next we want to define our format method. This method copies the input characters into an output list injecting indentation and line breaks after every opening bracket and before every closing bracket.
To reduce some of the duplication, we wrote a helper method writeIndent
that
writes the newline and appropriate indent into our output list.
String format(List<String> input) {
List<String> output = [];
int indent = 0;
for (String char in input) {
switch (char) {
case '(':
case '{':
case '[':
.add(char);
output, ++indent);
writeIndent(outputbreak;
case ')':
case '}':
case ']':
, --indent);
writeIndent(output.add(char);
outputbreak;
default:
.add(char);
output
}
}
return output.join("");
}
void writeIndent(List<String> output, int indentLevel, [int indentSize = 2]) {
.add("\n");
output.addAll(List.filled(indentLevel * indentSize, ' '));
output }
The method iterates through the input looking at every character, and doing
our special injection only for our target characters. If we wanted to also insert
indentation after every ,
, for example. We could augment the above method with:
case ',':
.add(char);
output, indent);
writeIndent(outputbreak;
This would make every adjacent element in a function display on a new line with the same indentation level.
Bringing it all together
At this point we have our program entry point, and our function that completes our desired task. All we need to do is run our program.
# Assuming that format.dart contains the program we've developed above
echo "([{1}])" | dart format.dart
# Output:
# (
# [
# {
# 1
# }
# ]
# )
We could go a step further and compile our dart program to native machine code
with dart2aot
to speed up the performance of our program. I’ll leave that
as an exercise for the reader. How much faster is the program with AOT compilation?
Conclusion
This has been a post on how to build a command line utility using the Dart programming language. This utility is something to help me as a developer look at output from other programs which does not deserialize back to an easy to compare format.