Add a CommandRunner example (#220)

* Add a command runner example

* Update readme

* Review comments
diff --git a/example/arg_parser/README.md b/example/arg_parser/README.md
new file mode 100644
index 0000000..c137bcd
--- /dev/null
+++ b/example/arg_parser/README.md
@@ -0,0 +1,3 @@
+# Example of using `ArgParser`
+
+`dart run example.dart`
diff --git a/example/example.dart b/example/arg_parser/example.dart
similarity index 100%
rename from example/example.dart
rename to example/arg_parser/example.dart
diff --git a/example/arg_parser/pubspec.yaml b/example/arg_parser/pubspec.yaml
new file mode 100644
index 0000000..52f193a
--- /dev/null
+++ b/example/arg_parser/pubspec.yaml
@@ -0,0 +1,13 @@
+# Copyright (c) 2022, 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.
+
+name: arg_parser_example
+version: 1.0.0
+description: An example of using ArgParser
+publish_to: 'none'
+environment:
+  sdk: '>=2.14.0 <3.0.0'
+dependencies:
+  args:
+    path: ../..
diff --git a/example/command_runner/README.md b/example/command_runner/README.md
new file mode 100644
index 0000000..48bf60e
--- /dev/null
+++ b/example/command_runner/README.md
@@ -0,0 +1,5 @@
+# Example of using `CommandRunner`
+
+This example uses `CommandRunner` to create a tool for drawing ascii art shapes.
+
+`dart run draw.dart circle --radius=10`
diff --git a/example/command_runner/draw.dart b/example/command_runner/draw.dart
new file mode 100644
index 0000000..d14ed4a
--- /dev/null
+++ b/example/command_runner/draw.dart
@@ -0,0 +1,142 @@
+// Copyright (c) 2022, 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:async';
+import 'dart:math';
+
+import 'package:args/command_runner.dart';
+
+void main(List<String> args) async {
+  final runner = CommandRunner<String>('draw', 'Draws shapes')
+    ..addCommand(SquareCommand())
+    ..addCommand(CircleCommand())
+    ..addCommand(TriangleCommand());
+  runner.argParser.addOption('char', help: 'The character to use for drawing');
+  final output = await runner.run(args);
+  print(output);
+}
+
+class SquareCommand extends Command<String> {
+  SquareCommand() {
+    argParser.addOption('size', help: 'Size of the square');
+  }
+
+  @override
+  String get name => 'square';
+
+  @override
+  String get description => 'Draws a square';
+
+  @override
+  List<String> get aliases => ['s'];
+
+  @override
+  FutureOr<String>? run() {
+    final size = int.parse(argResults?['size'] ?? '20');
+    final char = globalResults?['char']?[0] ?? '#';
+    return draw(size, size, char, (x, y) => true);
+  }
+}
+
+class CircleCommand extends Command<String> {
+  CircleCommand() {
+    argParser.addOption('radius', help: 'Radius of the circle');
+  }
+
+  @override
+  String get name => 'circle';
+
+  @override
+  String get description => 'Draws a circle';
+
+  @override
+  List<String> get aliases => ['c'];
+
+  @override
+  FutureOr<String>? run() {
+    final size = 2 * int.parse(argResults?['radius'] ?? '10');
+    final char = globalResults?['char']?[0] ?? '#';
+    return draw(size, size, char, (x, y) => x * x + y * y < 1);
+  }
+}
+
+class TriangleCommand extends Command<String> {
+  TriangleCommand() {
+    addSubcommand(EquilateralTriangleCommand());
+    addSubcommand(IsoscelesTriangleCommand());
+  }
+
+  @override
+  String get name => 'triangle';
+
+  @override
+  String get description => 'Draws a triangle';
+
+  @override
+  List<String> get aliases => ['t'];
+}
+
+class EquilateralTriangleCommand extends Command<String> {
+  EquilateralTriangleCommand() {
+    argParser.addOption('size', help: 'Size of the triangle');
+  }
+
+  @override
+  String get name => 'equilateral';
+
+  @override
+  String get description => 'Draws an equilateral triangle';
+
+  @override
+  List<String> get aliases => ['e'];
+
+  @override
+  FutureOr<String>? run() {
+    final size = int.parse(argResults?['size'] ?? '20');
+    final char = globalResults?['char']?[0] ?? '#';
+    return drawTriangle(size, size * sqrt(3) ~/ 2, char);
+  }
+}
+
+class IsoscelesTriangleCommand extends Command<String> {
+  IsoscelesTriangleCommand() {
+    argParser.addOption('width', help: 'Width of the triangle');
+    argParser.addOption('height', help: 'Height of the triangle');
+  }
+
+  @override
+  String get name => 'isosceles';
+
+  @override
+  String get description => 'Draws an isosceles triangle';
+
+  @override
+  List<String> get aliases => ['i'];
+
+  @override
+  FutureOr<String>? run() {
+    final width = int.parse(argResults?['width'] ?? '50');
+    final height = int.parse(argResults?['height'] ?? '10');
+    final char = globalResults?['char']?[0] ?? '#';
+    return drawTriangle(width, height, char);
+  }
+}
+
+String draw(
+    int width, int height, String char, bool Function(double, double) pixel) {
+  final out = StringBuffer();
+  for (int y = 0; y <= height; ++y) {
+    final ty = 2 * y / height - 1;
+    for (int x = 0; x <= width; ++x) {
+      final tx = 2 * x / width - 1;
+      out.write(pixel(tx, ty) ? char : ' ');
+    }
+    out.write('\n');
+  }
+  return out.toString();
+}
+
+String drawTriangle(int width, int height, String char) {
+  return draw(width, height, char, (x, y) => x.abs() <= (1 + y) / 2);
+}
diff --git a/example/command_runner/pubspec.yaml b/example/command_runner/pubspec.yaml
new file mode 100644
index 0000000..0745be6
--- /dev/null
+++ b/example/command_runner/pubspec.yaml
@@ -0,0 +1,13 @@
+# Copyright (c) 2022, 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.
+
+name: command_runner_example
+version: 1.0.0
+description: An example of using CommandRunner
+publish_to: 'none'
+environment:
+  sdk: '>=2.14.0 <3.0.0'
+dependencies:
+  args:
+    path: ../..