Supporting command line arguments in Dart programs

Dart programs, really all programs, are either hardcoded against certain targets and configurations or they configurable at startup. Not sure about you but the word "hardcoded" always sounds bad. 

The most common way of providing configuration information is probably:
  • Command-line arguments
  • Environmental variables
  • Properties files or other accessible storage
  • Properties services
The approach you pick depends on the nature ephemeral nature of the program, the environment you run in, and if the configuration can change while the program is running.  Sometimes you use a combination, one configuration method at bootstrap, and then a different one as the program continues running.

In all command-style programs, I've found that I need some way to bootstrap the process.  It almost always has command-line arguments or environmental variables. Environment variables are great but can be a bit opaque depending on how the program is run. My go-to is to include run-time argument support often seen as command-line arguments.

Command-line Arguments?

Command-line arguments are passed as part of the run commands.  The arguments below consist of an argument name and an argument value.


dart run example.dart --address localhost --port 8080 --targetAddress HTTP://pub.dev


Command-line arguments are values made directly available to a program, often as a generic list of strings passed into the main() program.  They extend the utility of your dart programs letting you change their behavior without having to commit new code. A lot of my Dart command line programs are utilities. They have always needed some configuration information.  Initially, we just hard-coded the program to "get it done".  Then we come back and need to make changes for some similar tasks.  So we either fork a copy or do a little work to parameter drive the behavior, remote addresses, file names, logging levels, or other settings. 

Command-line arguments also provide a framework for identifying the configuration parameters that are used by your program and pulling those to one location. It is great that the Dart team provides a simple package for defining arguments, their types, and their defaults. 

Command-line Flags?

Command-line flags are a variant of the parameterized argument where the presence of the flag has meaning without any supplemental value.


dart run example.dart --verbose --ignore-certs --no-cors


Command-line flags are made directly available to the program as a type of argument.  A sample is included below.

Dart Args add polish 

The example program has three command line arguments that do not include the --help option. I implemented --help as a command line option in my programs but didn't want to add another item to this example. The command run here is for a Dart-based proxy server that listens on a port and forwards all traffic to a target address (site).


Here I ran the program and provided an unsupported argument.  The Dart Args package fallback behavior lists all the command line options if an unrecognized one is provided.  It is a lazy programmer's implementation of help. 

PS C:\Users\joe\...\shelf_proxy> dart run .\example.dart --help
Could not find an option named "help".
-p, --port             Port to listen on
                       (defaults to "8080")
-a, --address          Address to listen on
                       (defaults to "localhost")
-t, --targetAddress    Address proxying for
                       (defaults to "https://dart.dev")

This program is a proxy server that accepts HTTP requests on an address and port and forwards those to targetAddress.  The Args implementation below lets the caller override those values while providing the original default values, the values that were previously hard coded.

Shelf Proxy Server example.dart

This is a simple example taken from the Dart team's shelf project.  The enhanced argument handling here has been submitted in a PR and so may not be visible in the official shelf repository.

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:args/args.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_proxy/shelf_proxy.dart';

Future<void> main(List<String> args) async {
// Create our configured parser
  final parser = _getParser();

// Where our arguments end up
// Defaults are provided provided to Args at configuration time
  String address;
  int port;
  String targetAddress;

// Ingest the parameters converting them to the correct type.
// Exit with an error return code if they fail to parse
  try {
    final result = parser.parse(args);
    address = result['address'] as String;
    port = int.parse(result['port'] as String);
    targetAddress = result['targetAddress'] as String;
  } on FormatException catch (e) {
    stderr
      ..writeln(e.message)
      ..writeln(parser.usage);
    exit(64);
  }

// Use the provided or default parameters.
  final server = await shelf_io.serve(
    proxyHandler(targetAddress),
    address,
    port,
  );

  print(
      'Proxying for $targetAddress at http://${server.address.host}:${server.port}');
}

// Configure the ArgParser with our command line arguments.
// Note that all of these have default values and help
ArgParser _getParser() => ArgParser()
  ..addOption('port', abbr: 'p', defaultsTo: '8080', help: 'Port to listen on')
  ..addOption('address',
      abbr: 'a', defaultsTo: 'localhost', help: 'Address to listen on')
  ..addOption('targetAddress',
      abbr: 't', defaultsTo: 'https://dart.dev', help: 'Address proxying for');


Code Reviewers

Consider rejecting all Pull Requests that include command-line programs with hard-coded parameters that cannot be overridden by environment variables or command-line arguments.

Video

Revision History 

Created 2024 02

Comments

Popular posts from this blog

Understanding your WSL2 RAM and swap - Changing the default 50%-25%

Installing the RNDIS driver on Windows 11 to use USB Raspberry Pi as network attached

DNS for Azure Point to Site (P2S) VPN - getting the internal IPs