CLI apps have bin and lib directories, both of which are public.
The bin directory contains your main() function and is the entry point for your CLI app
The lib directory contains your application’s Dart code which is imported into the bin directory
For the sake of this tutorial, I’ll ignore the lib directory and add all the code in a single file.
Run the App
A simple CLI app looks like this:
You can run it one of two ways. (1) Use dart run at the root of your project:
(2) Navigate into the bin folder and and use dart run with your specific dart file:
Pass in Arguments
You can pass arguments directly to your app by simply appending them to your run command:
And then you can read the arguments just how you would expect:
The issue with this approach is that all arguments are bundled into a single List<String> and its difficult to figure out what each argument means for the app. For insance, is the second argument an option flag (“-n”) or a value (“hello”)?
Enter the args package from the Dart team. This utility package “parses raw command-line arguments into a set of options and values” so you don’t have to do it yourself. Add the dependency to your pubspec.yaml:
Then create an ArgParser object inside your main() function.
Without any other configuration, the parser isn’t too helpful. It will still “parse” the arguments if you ask it to but you’d basically be taking more steps to get the same List<String>:
ArgParser’s true power is in its ability to look for specific types of inputs, or “options”. You can add Options to your ArgParser using the following methods:
addMultiOption: Adds an option that takes multiple values
Defining Options
Each option defines the name of the argument its looking for as well as abbreviations, aliases, help text, and default values, among other things.
Lets take a look at an example. Say we wanted our app to accept an argument called “name”. To do that, we would add an option to our parser…
…and pass a named argument in our command:
We can define an abbreviation and aliases so users don’t have to remember a single syntax:
With this setup all of the following commands will work the same:
The default option added with addOption accepts a string argument. If you’d like for users to be able to input a true or false value, use the addFlag method instead.
Including the flag in your command, using either the full name or abbreviation, will set it to true:
To set it to false, prefix the full name with “no-”:
Finally, if an argument can accept multiple values, use the addMultiOption method:
And then pass in multiple values separated by commas:
Defining Multiple Options
You can use multiple add[Option] methods to add as many options as you’d like to a single ArgParser. For instance:
Now you can specify all arguments in the same call:
If there are additional arguments included in the command that do not match an expected option, you can access those using the rest property on the ArgResults object. For example, lets use the same main() function from above but this time our command looks like this:
We can use the rest property to get the last two arguments which have no corresponding option:
The rest property will only capture unhyphenated arguments. If the user adds an unrecognized property name, like “-r” or “—color”, an error will be thrown.
Add Commands
Tha ArgParser looks for two types of user input: options (above) and commands. While options define what type of arguments a user can provide to our app, commands determine what the app will do with the arguments. Each command has its own ArgParser that can be used to define the arguments it expects.
For example, say we have a CLI app that converts a string to all uppercase or lowercase. We accomplish this using just a flag and an input option:
To refactor this app so it uses commands, we can use the addCommand method of ArgParser:
This code is a little hard to follow. Fortunately, the args package offers a cleaner way to set this up using CommandRunner and the Command class. Instead of defining the full command inline, create a separate class for each command and override the name, description, argParser, and run methods:
Then add this Command class to a CommandRunner in your main() function and call run():
Using the CommandRunner this way allows your users to view help text by appending “-h” or “—help” to their commands (dart run bin/cotr_cli.dart uppercase -h). If you add commands using the addCommand method, there is no way for you to update the command’s description.
Add Help Text
You can add a short help text to each option you add to an ArgParser.
The parser has a usage property that will print each option with its name, abbreviation, and help text. The output of parser.usage for the above code is shown here:
Additional help text can be added to options that accept multiple arguments. For example, we can expand on our languages option above by adding valueHelp, allowed, and allowedHelp properties:
The new help text for the updated argument looks like this:
You can also remove an argument from the help text by setting hide to true.
The openRead() method returns a Stream of bytes which you can convert to a string using the transform method and the utf8 decoder. This is useful for reading large files:
Read all Files in a Directory
To loop through the files in a directory, first obtain a reference to the directory:
You can write to a file using the writeAsString() or writeAsStringSync() method;. First create a reference to the file you want to write to and then call the method:
Create a Directory
You can use the Directory class from dart:io
to create and manipulate directories. First create a reference to the directory you want to create. The path always starts at the current directory. Then use the create() method to create the directory:
Use the Current Directory
You can use the Directory.current property to get a reference to the current directory.
Then you can add files or subdirectories as described above. To create a temporary subdirectory, use the createTemp() method. Directories can be deleted using the delete() method:
Requesting User Input
You can prompt the CLI user for input using a combination of stdout and stdin.