Added mandatory (or required) option (#177)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d09d006..edac237 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## 2.1.0-dev
 
+* Add a `mandatory` argument to require the presence of an option.
 * Add `aliases` named argument to `addFlag`, `addOption`, and `addMultiOption`,
   as well as a public `findByNameOrAlias` method on `ArgParser`. This allows
   you to provide aliases for an argument name, which eases the transition from
diff --git a/README.md b/README.md
index 7d5fe5e..dde52cc 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,13 @@
 If an option isn't provided in the args, its callback is passed the default
 value, or `null` if no default value is set.
 
+If an option is `mandatory` but not provided, the parser throws an
+[`ArgParserException`][ArgParserException].
+
+```dart
+parser.addOption('mode', mandatory: true);
+```
+
 ## Parsing arguments
 
 Once you have an [ArgParser][] set up with some options and flags, you use it by
diff --git a/example/test_runner.dart b/example/test_runner.dart
index f600915..9a231e8 100644
--- a/example/test_runner.dart
+++ b/example/test_runner.dart
@@ -164,6 +164,7 @@
   parser.addOption('dart', help: 'Path to dart executable');
   parser.addOption('drt', help: 'Path to content shell executable');
   parser.addOption('dartium', help: 'Path to Dartium Chrome executable');
+  parser.addOption('mandatory', help: 'A mandatory option', mandatory: true);
 
   print(parser.usage);
 }
diff --git a/lib/src/allow_anything_parser.dart b/lib/src/allow_anything_parser.dart
index becff98..46dfc94 100644
--- a/lib/src/allow_anything_parser.dart
+++ b/lib/src/allow_anything_parser.dart
@@ -52,6 +52,7 @@
       void Function(String?)? callback,
       bool allowMultiple = false,
       bool? splitCommas,
+      bool mandatory = false,
       bool hide = false,
       List<String> aliases = const []}) {
     throw UnsupportedError(
diff --git a/lib/src/arg_parser.dart b/lib/src/arg_parser.dart
index 87f7a02..42af6bf 100644
--- a/lib/src/arg_parser.dart
+++ b/lib/src/arg_parser.dart
@@ -198,11 +198,12 @@
       Map<String, String>? allowedHelp,
       String? defaultsTo,
       void Function(String?)? callback,
+      bool mandatory = false,
       bool hide = false,
       List<String> aliases = const []}) {
     _addOption(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
         callback, OptionType.single,
-        hide: hide, aliases: aliases);
+        mandatory: mandatory, hide: hide, aliases: aliases);
   }
 
   /// Defines an option that takes multiple values.
@@ -283,6 +284,7 @@
       OptionType type,
       {bool negatable = false,
       bool? splitCommas,
+      bool mandatory = false,
       bool hide = false,
       List<String> aliases = const []}) {
     var allNames = [name, ...aliases];
@@ -299,10 +301,17 @@
       }
     }
 
+    // Make sure the option is not mandatory with a default value.
+    if (mandatory && defaultsTo != null) {
+      throw ArgumentError(
+          'The option $name cannot be mandatory and have a default value.');
+    }
+
     var option = newOption(name, abbr, help, valueHelp, allowed, allowedHelp,
         defaultsTo, callback, type,
         negatable: negatable,
         splitCommas: splitCommas,
+        mandatory: mandatory,
         hide: hide,
         aliases: aliases);
     _options[name] = option;
diff --git a/lib/src/option.dart b/lib/src/option.dart
index 7a91a4b..a7c6ab8 100644
--- a/lib/src/option.dart
+++ b/lib/src/option.dart
@@ -18,12 +18,14 @@
     OptionType type,
     {bool? negatable,
     bool? splitCommas,
+    bool mandatory = false,
     bool hide = false,
     List<String> aliases = const []}) {
   return Option._(name, abbr, help, valueHelp, allowed, allowedHelp, defaultsTo,
       callback, type,
       negatable: negatable,
       splitCommas: splitCommas,
+      mandatory: mandatory,
       hide: hide,
       aliases: aliases);
 }
@@ -74,6 +76,9 @@
   /// addition to `--option a --option b`.
   final bool splitCommas;
 
+  /// Whether this option must be provided for correct usage.
+  final bool mandatory;
+
   /// Whether this option should be hidden from usage documentation.
   final bool hide;
 
@@ -101,6 +106,7 @@
       OptionType type,
       {this.negatable,
       bool? splitCommas,
+      this.mandatory = false,
       this.hide = false,
       this.aliases = const []})
       : allowed = allowed == null ? null : List.unmodifiable(allowed),
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 9cfaa60..25497be 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -92,10 +92,19 @@
       rest.add(args.removeFirst());
     }
 
-    // Invoke the callbacks.
+    // Check if mandatory and invoke existing callbacks.
     grammar.options.forEach((name, option) {
+      var parsedOption = results[name];
+
+      // Check if an option was mandatory and exist
+      // if not throw an exception
+      if (option.mandatory && parsedOption == null) {
+        throw ArgParserException('Option $name is mandatory.', [name]);
+      }
+
       var callback = option.callback;
-      if (callback != null) callback(option.valueOrDefault(results[name]));
+      if (callback == null) return;
+      callback(option.valueOrDefault(parsedOption));
     });
 
     // Add in the leftover arguments we didn't parse to the innermost command.
diff --git a/lib/src/usage.dart b/lib/src/usage.dart
index 6e44244..8b788c5 100644
--- a/lib/src/usage.dart
+++ b/lib/src/usage.dart
@@ -85,7 +85,7 @@
 
   void _writeOption(Option option) {
     _write(0, _abbreviation(option));
-    _write(1, _longOption(option));
+    _write(1, '${_longOption(option)}${_mandatoryOption(option)}');
 
     if (option.help != null) _write(2, option.help!);
 
@@ -131,6 +131,10 @@
     return result;
   }
 
+  String _mandatoryOption(Option option) {
+    return option.mandatory ? ' (mandatory)' : '';
+  }
+
   String _allowedTitle(Option option, String allowed) {
     var isDefault = option.defaultsTo is List
         ? option.defaultsTo.contains(allowed)
@@ -149,7 +153,7 @@
       abbr = math.max(abbr, _abbreviation(option).length);
 
       // Make room for the option.
-      title = math.max(title, _longOption(option).length);
+      title = math.max(title, _longOption(option).length + _mandatoryOption(option).length);
 
       // Make room for the allowed help.
       if (option.allowedHelp != null) {
diff --git a/test/parse_test.dart b/test/parse_test.dart
index c90219e..4714d16 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.dart
@@ -509,6 +509,28 @@
         var results = parser.parse(['--b=1']);
         expect(results['a'], '1');
       });
+
+      group('mandatory', () {
+        test('throw if no args', () {
+          var parser = ArgParser();
+          parser.addOption('username', mandatory: true);
+          throwsFormat(parser, []);
+        });
+
+        test('throw if no mandatory args', () {
+          var parser = ArgParser();
+          parser.addOption('test');
+          parser.addOption('username', mandatory: true);
+          throwsFormat(parser, ['--test', 'test']);
+        });
+
+        test('parse successfully', () {
+          var parser = ArgParser();
+          parser.addOption('test', mandatory: true);
+          var results = parser.parse(['--test', 'test']);
+          expect(results['test'], equals('test'));
+        });
+      });
     });
 
     group('remaining args', () {
diff --git a/test/usage_test.dart b/test/usage_test.dart
index 0ba33f0..dbcef78 100644
--- a/test/usage_test.dart
+++ b/test/usage_test.dart
@@ -5,6 +5,8 @@
 import 'package:args/args.dart';
 import 'package:test/test.dart';
 
+import 'test_utils.dart';
+
 void main() {
   group('ArgParser.usage', () {
     test('negatable flags show "no-" in title', () {
@@ -404,6 +406,22 @@
           ''');
     });
 
+    test('display "mandatory" after a mandatory option', () {
+      var parser = ArgParser();
+      parser.addOption('test', mandatory: true);
+      validateUsage(parser, '''
+        --test (mandatory)    
+        ''');
+    });
+
+    test('throw argument error if option is mandatory with a default value', () {
+      var parser = ArgParser();
+      expect(
+        () => parser.addOption('test', mandatory: true, defaultsTo: 'test'),
+        throwsArgumentError
+      );
+    });
+
     group('separators', () {
       test("separates options where it's placed", () {
         var parser = ArgParser();