// Copyright (c) 2012, 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 'package:args/args.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; void main() { group('ArgParser.parse()', () { test('does not destructively modify the argument list', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = ['--verbose']; var results = parser.parse(args); expect(args, equals(['--verbose'])); expect(results['verbose'], isTrue); }); group('flags', () { test('are true if present', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = parser.parse(['--verbose']); expect(args['verbose'], isTrue); }); test('default if missing', () { var parser = ArgParser(); parser.addFlag('a', defaultsTo: true); parser.addFlag('b', defaultsTo: false); var args = parser.parse([]); expect(args['a'], isTrue); expect(args['b'], isFalse); }); test('are false if missing with no default', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = parser.parse([]); expect(args['verbose'], isFalse); }); test('throws if given a value', () { var parser = ArgParser(); parser.addFlag('verbose'); throwsFormat(parser, ['--verbose=true']); }); test('are case-sensitive', () { var parser = ArgParser(); parser.addFlag('verbose'); parser.addFlag('Verbose'); var results = parser.parse(['--verbose']); expect(results['verbose'], isTrue); expect(results['Verbose'], isFalse); }); test('match letters, numbers, hyphens and underscores', () { var parser = ArgParser(); var allCharacters = 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'; parser.addFlag(allCharacters); var results = parser.parse(['--$allCharacters']); expect(results[allCharacters], isTrue); }); test('can match by alias', () { var parser = ArgParser()..addFlag('a', aliases: ['b']); var results = parser.parse(['--b']); expect(results['a'], isTrue); }); test('can be negated by alias', () { var parser = ArgParser() ..addFlag('a', aliases: ['b'], defaultsTo: true, negatable: true); var results = parser.parse(['--no-b']); expect(results['a'], isFalse); }); test('throws if requested as a multi-option', () { var parser = ArgParser(); parser.addFlag('a', defaultsTo: true); var results = parser.parse(['--a']); throwsIllegalArg(() => results.multiOption('a')); }); }); group('flag()', () { test('returns true if present', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = parser.parse(['--verbose']); expect(args.flag('verbose'), isTrue); }); test('returns default if missing', () { var parser = ArgParser(); parser.addFlag('a', defaultsTo: true); parser.addFlag('b', defaultsTo: false); var args = parser.parse([]); expect(args.flag('a'), isTrue); expect(args.flag('b'), isFalse); }); test('are false if missing with no default', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = parser.parse([]); expect(args.flag('verbose'), isFalse); }); test('are case-sensitive', () { var parser = ArgParser(); parser.addFlag('verbose'); parser.addFlag('Verbose'); var results = parser.parse(['--verbose']); expect(results.flag('verbose'), isTrue); expect(results.flag('Verbose'), isFalse); }); test('match letters, numbers, hyphens and underscores', () { var parser = ArgParser(); var allCharacters = 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'; parser.addFlag(allCharacters); var results = parser.parse(['--$allCharacters']); expect(results.flag(allCharacters), isTrue); }); test('can match by alias', () { var parser = ArgParser()..addFlag('a', aliases: ['b']); var results = parser.parse(['--b']); expect(results.flag('a'), isTrue); }); test('can be negated by alias', () { var parser = ArgParser() ..addFlag('a', aliases: ['b'], defaultsTo: true, negatable: true); var results = parser.parse(['--no-b']); expect(results.flag('a'), isFalse); }); test('throws if requested as a multi-option', () { var parser = ArgParser(); parser.addFlag('a', defaultsTo: true); var results = parser.parse(['--a']); throwsIllegalArg(() => results.multiOption('a')); }); }); group('flags negated with "no-"', () { test('set the flag to false', () { var parser = ArgParser(); parser.addFlag('verbose'); var args = parser.parse(['--no-verbose']); expect(args['verbose'], isFalse); }); test('set the flag to true if the flag actually starts with "no-"', () { var parser = ArgParser(); parser.addFlag('no-body'); var args = parser.parse(['--no-body']); expect(args['no-body'], isTrue); }); test('are not preferred over a colliding one without', () { var parser = ArgParser(); parser.addFlag('no-strum'); parser.addFlag('strum'); var args = parser.parse(['--no-strum']); expect(args['no-strum'], isTrue); expect(args['strum'], isFalse); }); test('fail for non-negatable flags', () { var parser = ArgParser(); parser.addFlag('strum', negatable: false); throwsFormat(parser, ['--no-strum']); }); }); group('callbacks', () { test('for present flags are invoked with the value', () { bool? a; var parser = ArgParser(); parser.addFlag('a', callback: (value) => a = value); parser.parse(['--a']); expect(a, isTrue); }); test('for absent flags are invoked with the default value', () { bool? a; var parser = ArgParser(); parser.addFlag('a', defaultsTo: false, callback: (value) => a = value); parser.parse([]); expect(a, isFalse); }); test('are invoked even if the flag is not present', () { var a = true; var parser = ArgParser(); parser.addFlag('a', callback: (value) => a = value); parser.parse([]); expect(a, isFalse); }); test('for present options are invoked with the value', () { String? a; var parser = ArgParser(); parser.addOption('a', callback: (value) => a = value); parser.parse(['--a=v']); expect(a, equals('v')); }); test('for absent options are invoked with the default value', () { var parser = ArgParser(); parser.addOption('a', defaultsTo: 'v', callback: expectAsync1((value) => expect(value, 'v'))); parser.parse([]); }); test('for absent options are invoked with null if there is no default', () { var parser = ArgParser(); parser.addOption('a', callback: expectAsync1((value) => expect(value, isNull))); parser.parse([]); }); group('with addMultiOption', () { test('for multiple present, options are invoked with value as a list', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', callback: (value) => a = value); parser.parse(['--a=v', '--a=x']); expect(a, equals(['v', 'x'])); // This reified type is important in strong mode so that people can // safely write "as List". expect(a, isA>()); }); test( 'for single present, options are invoked with value as a single ' 'element list', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', callback: (value) => a = value); parser.parse(['--a=v']); expect(a, equals(['v'])); }); test('for absent, options are invoked with default value', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', defaultsTo: ['v', 'w'], callback: (value) => a = value); parser.parse([]); expect(a, equals(['v', 'w'])); }); test('for absent, options are invoked with value as an empty list', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', callback: (value) => a = value); parser.parse([]); expect(a, isEmpty); }); test('parses comma-separated strings', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', callback: (value) => a = value); parser.parse(['--a=v,w', '--a=x']); expect(a, equals(['v', 'w', 'x'])); }); test("doesn't parse comma-separated strings with splitCommas: false", () { List? a; var parser = ArgParser(); parser.addMultiOption('a', splitCommas: false, callback: (value) => a = value); parser.parse(['--a=v,w', '--a=x']); expect(a, equals(['v,w', 'x'])); }); test('parses empty strings', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', callback: (value) => a = value); parser.parse(['--a=,v', '--a=w,', '--a=,', '--a=x,,y', '--a', '']); expect(a, equals(['', 'v', 'w', '', '', '', 'x', '', 'y', ''])); }); test('with allowed parses comma-separated strings', () { List? a; var parser = ArgParser(); parser.addMultiOption('a', allowed: ['v', 'w', 'x'], callback: (value) => a = value); parser.parse(['--a=v,w', '--a=x']); expect(a, equals(['v', 'w', 'x'])); }); test('can mix and match alias and regular name', () { var parser = ArgParser()..addMultiOption('a', aliases: ['b']); var results = parser.parse(['--a=1', '--b=2']); expect(results['a'], ['1', '2']); }); }); }); group('abbreviations', () { test('are parsed with a preceding "-"', () { var parser = ArgParser(); parser.addFlag('arg', abbr: 'a'); var args = parser.parse(['-a']); expect(args['arg'], isTrue); }); test('can use multiple after a single "-"', () { var parser = ArgParser(); parser.addFlag('first', abbr: 'f'); parser.addFlag('second', abbr: 's'); parser.addFlag('third', abbr: 't'); var args = parser.parse(['-tf']); expect(args['first'], isTrue); expect(args['second'], isFalse); expect(args['third'], isTrue); }); test('can have multiple "-" args', () { var parser = ArgParser(); parser.addFlag('first', abbr: 'f'); parser.addFlag('second', abbr: 's'); parser.addFlag('third', abbr: 't'); var args = parser.parse(['-s', '-tf']); expect(args['first'], isTrue); expect(args['second'], isTrue); expect(args['third'], isTrue); }); test('can take arguments without a space separating', () { var parser = ArgParser(); parser.addOption('file', abbr: 'f'); var args = parser.parse(['-flip']); expect(args['file'], equals('lip')); }); test('can take arguments with a space separating', () { var parser = ArgParser(); parser.addOption('file', abbr: 'f'); var args = parser.parse(['-f', 'name']); expect(args['file'], equals('name')); }); test('allow non-option characters in the value', () { var parser = ArgParser(); parser.addOption('apple', abbr: 'a'); var args = parser.parse(['-ab?!c']); expect(args['apple'], equals('b?!c')); }); test('throw if unknown', () { var parser = ArgParser(); throwsFormat(parser, ['-f']); }); test('throw if the value is missing', () { var parser = ArgParser(); parser.addOption('file', abbr: 'f'); throwsFormat(parser, ['-f']); }); test('does not throw if the value looks like an option', () { var parser = ArgParser(); parser.addOption('file', abbr: 'f'); parser.addOption('other'); expect(parser.parse(['-f', '--other'])['file'], equals('--other')); expect(parser.parse(['-f', '--unknown'])['file'], equals('--unknown')); expect(parser.parse(['-f', '-abbr'])['file'], equals('-abbr')); expect(parser.parse(['-f', '--'])['file'], equals('--')); }); test('throw if the value is not allowed', () { var parser = ArgParser(); parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']); throwsFormat(parser, ['-mprofile']); }); group('throw if a comma-separated value is not allowed', () { test('with addMultiOption', () { var parser = ArgParser(); parser .addMultiOption('mode', abbr: 'm', allowed: ['debug', 'release']); throwsFormat(parser, ['-mdebug,profile']); }); }); test('throw if any but the first is not a flag', () { var parser = ArgParser(); parser.addFlag('apple', abbr: 'a'); parser.addOption('banana', abbr: 'b'); // Takes an argument. parser.addFlag('cherry', abbr: 'c'); throwsFormat(parser, ['-abc']); }); test('throw if it has a value but the option is a flag', () { var parser = ArgParser(); parser.addFlag('apple', abbr: 'a'); parser.addFlag('banana', abbr: 'b'); // The '?!' means this can only be understood as '--apple b?!c'. throwsFormat(parser, ['-ab?!c']); }); test('are case-sensitive', () { var parser = ArgParser(); parser.addFlag('file', abbr: 'f'); parser.addFlag('force', abbr: 'F'); var results = parser.parse(['-f']); expect(results['file'], isTrue); expect(results['force'], isFalse); }); }); group('options', () { test('are parsed if present', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse(['--mode=release']); expect(args['mode'], equals('release')); }); test('are null if not present', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse([]); expect(args['mode'], isNull); }); test('default if missing', () { var parser = ArgParser(); parser.addOption('mode', defaultsTo: 'debug'); var args = parser.parse([]); expect(args['mode'], equals('debug')); }); test('allow the value to be separated by whitespace', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse(['--mode', 'release']); expect(args['mode'], equals('release')); }); test('throw if unknown', () { var parser = ArgParser(); throwsFormat(parser, ['--unknown']); throwsFormat(parser, ['--nobody']); // Starts with "no". }); test('throw if the arg does not include a value', () { var parser = ArgParser(); parser.addOption('mode'); throwsFormat(parser, ['--mode']); }); test('do not throw if the value looks like an option', () { var parser = ArgParser(); parser.addOption('mode'); parser.addOption('other'); expect(parser.parse(['--mode', '--other'])['mode'], equals('--other')); expect( parser.parse(['--mode', '--unknown'])['mode'], equals('--unknown')); expect(parser.parse(['--mode', '-abbr'])['mode'], equals('-abbr')); expect(parser.parse(['--mode', '--'])['mode'], equals('--')); }); test('do not throw if the value is in the allowed set', () { var parser = ArgParser(); parser.addOption('mode', allowed: ['debug', 'release']); var args = parser.parse(['--mode=debug']); expect(args['mode'], equals('debug')); }); test('do not throw if there is no allowed set with allowedHelp', () { var parser = ArgParser(); parser.addOption('mode', allowedHelp: { 'debug': 'During development.', 'release': 'For customers.' }); var args = parser.parse(['--mode=profile']); expect(args['mode'], equals('profile')); }); test('throw if the value is not in the allowed set', () { var parser = ArgParser(); parser.addOption('mode', allowed: ['debug', 'release']); throwsFormat(parser, ['--mode=profile']); }); test('returns last provided value', () { var parser = ArgParser(); parser.addOption('define'); var args = parser.parse(['--define=1', '--define=2']); expect(args['define'], equals('2')); }); test('throw if requested as a multi-option', () { var parser = ArgParser(); parser.addOption('a', defaultsTo: 'b'); var results = parser.parse(['--a=c']); throwsIllegalArg(() => results.multiOption('a')); }); group('returns a List', () { test('with addMultiOption', () { var parser = ArgParser(); parser.addMultiOption('define'); var args = parser.parse(['--define=1']); expect(args['define'], equals(['1'])); args = parser.parse(['--define=1', '--define=2']); expect(args['define'], equals(['1', '2'])); }); }); group('returns the default value if not explicitly set', () { test('with addMultiOption', () { var parser = ArgParser(); parser.addMultiOption('define', defaultsTo: ['0']); var args = parser.parse(['']); expect(args['define'], equals(['0'])); }); }); test('are case-sensitive', () { var parser = ArgParser(); parser.addOption('verbose', defaultsTo: 'no'); parser.addOption('Verbose', defaultsTo: 'no'); var results = parser.parse(['--verbose', 'chatty']); expect(results['verbose'], equals('chatty')); expect(results['Verbose'], equals('no')); }); test('can be set by alias', () { var parser = ArgParser()..addOption('a', aliases: ['b']); 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); var results = parser.parse([]); expect(() => results['username'], throwsA(isA())); }); test('throw if no mandatory args', () { var parser = ArgParser(); parser.addOption('test'); parser.addOption('username', mandatory: true); var results = parser.parse(['--test', 'test']); expect(results['test'], equals('test')); expect(() => results['username'], throwsA(isA())); }); test('parse successfully', () { var parser = ArgParser(); parser.addOption('test', mandatory: true); var results = parser.parse(['--test', 'test']); expect(results['test'], equals('test')); }); test('throws when value retrieved', () { var parser = ArgParser(); parser.addFlag('help', abbr: 'h', negatable: false); parser.addOption('test', mandatory: true); var results = parser.parse(['-h']); expect(results['help'], true); expect(() => results['test'], throwsA(isA())); expect(() => results.option('test'), throwsA(isA())); }); }); }); group('option()', () { test('are parsed if present', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse(['--mode=release']); expect(args.option('mode'), equals('release')); }); test('are null if not present', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse([]); expect(args.option('mode'), isNull); }); test('default if missing', () { var parser = ArgParser(); parser.addOption('mode', defaultsTo: 'debug'); var args = parser.parse([]); expect(args.option('mode'), equals('debug')); }); test('allow the value to be separated by whitespace', () { var parser = ArgParser(); parser.addOption('mode'); var args = parser.parse(['--mode', 'release']); expect(args.option('mode'), equals('release')); }); test('do not throw if the value is in the allowed set', () { var parser = ArgParser(); parser.addOption('mode', allowed: ['debug', 'release']); var args = parser.parse(['--mode=debug']); expect(args.option('mode'), equals('debug')); }); test('do not throw if there is no allowed set with allowedHelp', () { var parser = ArgParser(); parser.addOption('mode', allowedHelp: { 'debug': 'During development.', 'release': 'For customers.' }); var args = parser.parse(['--mode=profile']); expect(args.option('mode'), equals('profile')); }); test('returns last provided value', () { var parser = ArgParser(); parser.addOption('define'); var args = parser.parse(['--define=1', '--define=2']); expect(args.option('define'), equals('2')); }); test('throw if requested as a multi-option', () { var parser = ArgParser(); parser.addOption('a', defaultsTo: 'b'); var results = parser.parse(['--a=c']); throwsIllegalArg(() => results.multiOption('a')); }); group('returns a List', () { test('with addMultiOption', () { var parser = ArgParser(); parser.addMultiOption('define'); var args = parser.parse(['--define=1']); expect(args.multiOption('define'), equals(['1'])); args = parser.parse(['--define=1', '--define=2']); expect(args.multiOption('define'), equals(['1', '2'])); }); }); group('returns the default value if not explicitly set', () { test('with addMultiOption', () { var parser = ArgParser(); parser.addMultiOption('define', defaultsTo: ['0']); var args = parser.parse(['']); expect(args.multiOption('define'), equals(['0'])); }); }); test('are case-sensitive', () { var parser = ArgParser(); parser.addOption('verbose', defaultsTo: 'no'); parser.addOption('Verbose', defaultsTo: 'no'); var results = parser.parse(['--verbose', 'chatty']); expect(results.option('verbose'), equals('chatty')); expect(results.option('Verbose'), equals('no')); }); test('can be set by alias', () { var parser = ArgParser()..addOption('a', aliases: ['b']); var results = parser.parse(['--b=1']); expect(results.option('a'), '1'); }); group('mandatory', () { test('parse successfully', () { var parser = ArgParser(); parser.addOption('test', mandatory: true); var results = parser.parse(['--test', 'test']); expect(results.option('test'), equals('test')); }); }); }); group('remaining args', () { test('stops parsing args when a non-option-like arg is encountered', () { var parser = ArgParser(); parser.addFlag('woof'); parser.addOption('meow'); parser.addOption('tweet', defaultsTo: 'bird'); var results = parser.parse(['--woof', '--meow', 'v', 'not', 'option']); expect(results['woof'], isTrue); expect(results['meow'], equals('v')); expect(results['tweet'], equals('bird')); expect(results.rest, equals(['not', 'option'])); }); test('consumes "--" and stops', () { var parser = ArgParser(); parser.addFlag('woof', defaultsTo: false); parser.addOption('meow', defaultsTo: 'kitty'); var results = parser.parse(['--woof', '--', '--meow']); expect(results['woof'], isTrue); expect(results['meow'], equals('kitty')); expect(results.rest, equals(['--meow'])); }); test( 'with allowTrailingOptions: false, leaves "--" if not the first ' 'non-option', () { var parser = ArgParser(allowTrailingOptions: false); parser.addFlag('woof'); var results = parser.parse(['--woof', 'stop', '--', 'arg']); expect(results['woof'], isTrue); expect(results.rest, equals(['stop', '--', 'arg'])); }); }); group('ArgParser Exception Tests', () { test('throws exception for unknown option', () { var parser = ArgParser(); throwsArgParserException(parser, ['--verbose'], 'Could not find an option named "--verbose".', [], '--verbose'); throwsArgParserException( parser, ['-v'], 'Could not find an option or flag "-v".', [], '-v'); }); test('throws exception for flag with value', () { var parser = ArgParser(); parser.addFlag('flag', abbr: 'f'); throwsArgParserException(parser, ['--flag=1'], 'Flag option "--flag" should not be given a value.', [], '--flag'); throwsArgParserException(parser, ['-f=1'], 'Option "-f" is a flag and cannot handle value "=1".', [], '-f'); }); test('throws exception after parsing multiple options', () { var parser = ArgParser(); parser.addOption('first'); parser.addOption('second'); throwsArgParserException( parser, ['--first', '1', '--second', '2', '--verbose', '3'], 'Could not find an option named "--verbose".', [], '--verbose'); }); test('throws exception for option with invalid value', () { var parser = ArgParser(); parser.addOption('first', allowed: ['a', 'b']); throwsArgParserException(parser, ['--first', 'c'], '"c" is not an allowed value for option "--first".', [], '--first'); }); test('throws exception after parsing command', () { var parser = ArgParser(); parser.addCommand('command', ArgParser()); throwsArgParserException( parser, ['command', '--verbose'], 'Could not find an option named "--verbose".', ['command'], '--verbose'); }); }); }); }