The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;
use utf8;
use Test::More;
use Data::Dumper;
BEGIN { *CORE::GLOBAL::exit = sub (;$) { die bless {}, 'SystemExit' } };
use Docopt;
use Data::Dumper;
use boolean;
use Docopt::Util qw(repl serialize pyprint);
use Test::Fatal;
use t::Util;

*parse_section = *Docopt::parse_section;

subtest 'test_pattern_flat' => sub {
    test_pattern_flat(
        Required(OneOrMore(Argument('N')),
                        Option('-a'), Argument('M')),
        [Argument('N'), Option('-a'), Argument('M')],
    );
    test_pattern_flat(
        Required(Optional(OptionsShortcut()),
                                Optional(Option('-a', None))),
        [OptionsShortcut()],
        Docopt::OptionsShortcut::
    );
};

subtest 'test_option' => sub {
    test_option(
        '-h',
        ['-h', None]
    );
    test_option(
        '--help',
        [None, '--help'],
    );
    test_option(
        '-h --help',
        ['-h', '--help'],
    );
    test_option(
        '-h, --help',
        ['-h', '--help'],
    );

    test_option(
        '-h TOPIC',
        ['-h', None, 1]
    );
    test_option(
        '--help TOPIC',
        [None, '--help', 1]
    );
    test_option(
        '-h TOPIC --help TOPIC',
        ['-h', '--help', 1]
    );
    test_option(
        '-h TOPIC, --help=TOPIC',
        ['-h', '--help', 1]
    );
    test_option(
        '-h  Description...',
        ['-h', None]
    );
    test_option(
        '-h --help  Description...',
        ['-h', '--help']
    );
    test_option(
        '-h TOPIC  Description...',
        ['-h', None, 1]
    );
    test_option(
        '    -h',
        ['-h', None]
    );


    test_option('-h TOPIC  Descripton... [default: 2]',
               ['-h', None, 1, '2']);
    test_option('-h TOPIC  Descripton... [default: topic-1]',
               ['-h', None, 1, 'topic-1']);
    test_option('--help=TOPIC  ... [default: 3.14]',
               [None, '--help', 1, '3.14']);
    test_option('-h, --help=DIR  ... [default: ./]',
               ['-h', '--help', 1, "./"]);
    test_option('-h TOPIC  Descripton... [dEfAuLt: 2]',
               ['-h', None, 1, '2']);
};

subtest 'test_option_name' => sub {
    is(Option('-h', None)->name, '-h');
    is(Option('-h', '--help')->name, '--help');
    is(Option(None, '--help')->name, '--help');
};

DEBUG:
subtest 'test_commands' => sub {
    is_deeply(docopt(doc => 'Usage: prog add', argv => 'add'), {'add'=> True});
    is_deeply(docopt(doc => 'Usage: prog [add]', argv => ''), {'add'=> undef});
    is_deeply(docopt(doc => 'Usage: prog [add]', argv => 'add'), {'add'=> True});
    is_deeply(docopt(doc => 'Usage: prog (add|rm)', argv => 'add'), {'add' => True, 'rm'=> undef});
    is_deeply(docopt(doc => 'Usage: prog (add|rm)', argv => 'rm'), {'add'=> undef, 'rm'=> True});
    is_deeply(docopt(doc => 'Usage: prog a b', argv => 'a b'), {'a' => True, 'b' => True});
#   with raises(DocoptExit):
    isa_ok(exception { docopt(doc => 'Usage: prog a b', argv => 'b a') }, 'Docopt::Exceptions::DocoptExit');
};

subtest 'test_formal_usage' => sub {
    my $doc = "
    Usage: prog [-hv] ARG
           prog N M

    prog is a program.";
    my ($usage,) = Docopt::parse_section('usage:', $doc);
    is($usage, "Usage: prog [-hv] ARG\n           prog N M");
    is(Docopt::formal_usage($usage), "( [-hv] ARG ) | ( N M )");
};

subtest 'parse_argv' => sub {
    test_parse_argv(
        [],
        []
    );

    test_parse_argv(
        '-h',
        [Option('-h', None, 0, True)],
    );
    test_parse_argv(
        '-h --verbose',
        [Option('-h', None, 0, True), Option('-v', '--verbose', 0, True)],
    );
    test_parse_argv('-h --file f.txt',
            [Option('-h', None, 0, True), Option('-f', '--file', 1, 'f.txt')]);
    test_parse_argv('-h --file f.txt arg',
           [Option('-h', None, 0, True),
            Option('-f', '--file', 1, 'f.txt'),
            Argument(None, 'arg')]);
    test_parse_argv('-h --file f.txt arg arg2',
            [Option('-h', None, 0, True),
             Option('-f', '--file', 1, 'f.txt'),
             Argument(None, 'arg'),
             Argument(None, 'arg2')]);
    test_parse_argv('-h arg -- -v',
            [Option('-h', None, 0, True),
             Argument(None, 'arg'),
             Argument(None, '--'),
             Argument(None, '-v')]);
};

subtest 'test_parse_pattern' => sub {
    test_parse_pattern('[ -h ]',
        Required(Optional(Option('-h')))
    );
    test_parse_pattern('[ ARG ... ]',
        Required(Optional(OneOrMore(Argument('ARG')))),
    );
    test_parse_pattern('[ -h | -v ]',
        Required(Optional(Either(Option('-h'),
                                    Option('-v', '--verbose'))))
    );
    test_parse_pattern('[ --file <f> ]',
        Required(Optional(Option('-f', '--file', 1, undef)))
    );
    test_parse_pattern(
        '( -h | -v [ --file <f> ] )',
            Required(Required(
                Either(Option('-h'),
                Required(Option('-v', '--verbose'),
                Optional(Option('-f', '--file', 1, undef))))))
    );
    test_parse_pattern('(-h|-v[--file=<f>]N...)',
        Required(Required(Either(Option('-h'),
            Required(Option('-v', '--verbose'),
            Optional(Option('-f', '--file', 1, undef)),
            OneOrMore(Argument('N')))))),
    );
    test_parse_pattern('(N [M | (K | L)] | O P)',
        Required(Required(Either(
                        Required(Argument('N'),
                                    Optional(Either(Argument('M'),
                                                    Required(Either(Argument('K'),
                                                                    Argument('L')))))),
                        Required(Argument('O'), Argument('P')))))
    );
    test_parse_pattern('[ -h ] [N]',
        Required(Optional(Option('-h')),
                    Optional(Argument('N'))));
    test_parse_pattern(
        '[options]',
        Required(Optional(OptionsShortcut())),
    );
    test_parse_pattern('[options] A',
        Required(Optional(OptionsShortcut()), Argument('A')));
    test_parse_pattern('-v [options]',
        Required(Option('-v', '--verbose'),
                                    Optional(OptionsShortcut())));
    test_parse_pattern('ADD',
        Required(Argument('ADD')));
    test_parse_pattern('<add>',
        Required(Argument('<add>')));
};

subtest test_options_match => sub {
    test_options_match(
        [Option('-a')->match([Option('-a', True)])],
            [True, [], [Option('-a', True)]]
    );
    test_options_match([Option('-a')->match([Option('-x')])], [False, [Option('-x')], []]);
    test_options_match([Option('-a')->match([Argument('N')])], [False, [Argument('N')], []]);
    test_options_match([Option('-a')->match([Option('-x'), Option('-a'), Argument('N')])],
            [True, [Option('-x'), Argument('N')], [Option('-a')]]);
    test_options_match([Option('-a')->match([Option('-a', True), Option('-a')])],
            [True, [Option('-a')], [Option('-a', True)]]);
};

subtest 'test_argument_match' => sub {
    test_argument_match(
        [Argument('N')->match([Argument(None, 9)])],
        [True, [], [Argument('N', 9)]]
    );
    test_argument_match(
        [Argument('N')->match([Option('-x')])],
        [False, [Option('-x')], []]
    );
    test_argument_match(
        [Argument('N')->match([Option('-x'),
                                Option('-a'),
                                Argument(None, 5)])],
        [True, [Option('-x'), Option('-a')], [Argument('N', 5)]]
    );
    test_argument_match(
        [Argument('N')->match([Argument(None, 9), Argument(None, 0)])],
        [True, [Argument(None, 0)], [Argument('N', 9)]]
    );
};

subtest 'test_command_match' => sub {
    test_command_match(
        [Command('c')->match([Argument(None, 'c')])],
        [True, [], [Command('c', True)]]
    );
    test_command_match(
        [Command('c')->match([Option('-x')])],
        [False, [Option('-x')], []],
    );
    test_command_match(
        [Command('c')->match([Option('-x'),
                                Option('-a'),
                                Argument(None, 'c')])],
        [True, [Option('-x'), Option('-a')], [Command('c', True)]]
    );
    test_command_match(
        [Either(Command('add', False), Command('rm', False))->match(
            [Argument(None, 'rm')])],
        [True, [], [Command('rm', True)]]
    );
};

subtest 'test_optional_match' => sub {
    test_optional_match(
        [Optional(Option('-a'))->match([Option('-a')])],
        [True, [], [Option('-a')]]
    );
    test_optional_match(
        [Optional(Option('-a'))->match([])],
        [True, [], []],
    );
    test_optional_match(
        [Optional(Option('-a'))->match([Option('-x')])],
        [True, [Option('-x')], []]
    );
    test_optional_match(
        [Optional(Option('-a'), Option('-b'))->match([Option('-a')])],
        [True, [], [Option('-a')]],
    );
    test_optional_match(
        [Optional(Option('-a'), Option('-b'))->match([Option('-b')])],
        [True, [], [Option('-b')]],
    );
    test_optional_match(
        [Optional(Option('-a'), Option('-b'))->match([Option('-x')])],
        [True, [Option('-x')], []]
    );
    test_optional_match(
        [Optional(Argument('N'))->match([Argument(None, 9)])],
        [True, [], [Argument('N', 9)]],
    );
    test_optional_match(
        [Optional(Option('-a'), Option('-b'))->match(
                [Option('-b'), Option('-x'), Option('-a')])],
        [True, [Option('-x')], [Option('-a'), Option('-b')]],
    );
};

subtest 'test_required_match' => sub {
    test_required_match(
        [Required(Option('-a'))->match([Option('-a')])],
        [(True, [], [Option('-a')])],
    );
    test_required_match(
        [Required(Option('-a'))->match([])],
        [(False, [], [])],
    );
    test_required_match(
        [Required(Option('-a'))->match([Option('-x')])],
        [(False, [Option('-x')], [])],
    );
    test_required_match(
        [Required(Option('-a'), Option('-b'))->match([Option('-a')])],
        [(False, [Option('-a')], [])]
    );
};

subtest 'test_either_match()' => sub {
    test_either_match(
        [Either(Option('-a'), Option('-b'))->match(
                [Option('-a')])],
        [(True, [], [Option('-a')])]
    );
    test_either_match(
        [Either(Option('-a'), Option('-b'))->match(
            [Option('-a'), Option('-b')])],
        [(True, [Option('-b')], [Option('-a')])],
    );
    test_either_match(
        [Either(Option('-a'), Option('-b'))->match(
                [Option('-x')])],
        [(False, [Option('-x')], [])]
    );
    test_either_match(
        [Either(Option('-a'), Option('-b'), Option('-c'))->match(
            [Option('-x'), Option('-b')])],
        [(True, [Option('-x')], [Option('-b')])]
    );
    test_either_match(
       [Either(Argument('M'),
                  Required(Argument('N'), Argument('M')))->match(
                                   [Argument(None, 1), Argument(None, 2)])],
        [(True, [], [Argument('N', 1), Argument('M', 2)])]
    );
};

subtest 'test_one_or_more_match' => sub {
    test_one_or_more_match(
        [OneOrMore(Argument('N'))->match([Argument(None, 9)])],
        [(True, [], [Argument('N', 9)])],
    );
    test_one_or_more_match(
        [OneOrMore(Argument('N'))->match([])],
        [(False, [], [])]
    );
    test_one_or_more_match(
        [OneOrMore(Argument('N'))->match([Option('-x')])],
        [(False, [Option('-x')], [])],
    );
    test_one_or_more_match(
        [OneOrMore(Argument('N'))->match(
            [Argument(None, 9), Argument(None, 8)])],
        [True, [], [Argument('N', 9), Argument('N', 8)]],
    );
    test_one_or_more_match(
        [OneOrMore(Argument('N'))->match(
            [Argument(None, 9), Option('-x'), Argument(None, 8)])],
        [True, [Option('-x')], [Argument('N', 9), Argument('N', 8)]],
    );
    test_one_or_more_match(
        [OneOrMore(Option('-a'))->match(
            [Option('-a'), Argument(None, 8), Option('-a')])],
        [True, [Argument(None, 8)], [Option('-a'), Option('-a')]],
    );
    test_one_or_more_match(
        [OneOrMore(Option('-a'))->match([Argument(None, 8),
                                          Option('-x')])],
        [False, [Argument(None, 8), Option('-x')], []],
    );
    test_one_or_more_match(
        [OneOrMore(Required(Option('-a'), Argument('N')))->match(
                [Option('-a'), Argument(None, 1), Option('-x'),
                Option('-a'), Argument(None, 2)])],
        [True, [Option('-x')],
                [Option('-a'), Argument('N', 1), Option('-a'), Argument('N', 2)]],
    );
    test_one_or_more_match(
        [OneOrMore(Optional(Argument('N')))->match([Argument(None, 9)])],
        [(True, [], [Argument('N', 9)])],
    );
};

subtest 'test_list_argument_match()' => sub {
        test_list_argument_match(
            [Required(Argument('N'), Argument('N'))->fix()->match(
                    [Argument(None, '1'), Argument(None, '2')])],
            [(True, [], [Argument('N', ['1', '2'])])],
        );
        test_list_argument_match(
            [OneOrMore(Argument('N'))->fix()->match(
                [Argument(None, '1'), Argument(None, '2'), Argument(None, '3')])],
                        [(True, [], [Argument('N', ['1', '2', '3'])])],
        );
        test_list_argument_match(
        [Required(Argument('N'), OneOrMore(Argument('N')))->fix()->match(
            [Argument(None, '1'), Argument(None, '2'), Argument(None, '3')])],
                        [(True, [], [Argument('N', ['1', '2', '3'])])],
        );
        test_list_argument_match(
        [Required(Argument('N'), Required(Argument('N')))->fix()->match(
                [Argument(None, '1'), Argument(None, '2')])],
                        [(True, [], [Argument('N', ['1', '2'])])],
        );
};

subtest 'test_basic_pattern_matching' => sub {
    # ( -a N [ -x Z ] )
    my $pattern = Required(Option('-a'), Argument('N'),
                       Optional(Option('-x'), Argument('Z')));
    # -a N
    test_basic_pattern_matching(
        [$pattern->match([Option('-a'), Argument(None, 9)])],
        [(True, [], [Option('-a'), Argument('N', 9)])],
    );
    # -a -x N Z
    test_basic_pattern_matching(
        [$pattern->match([Option('-a'), Option('-x'),
                          Argument(None, 9), Argument(None, 5)])],
        [(True, [], [Option('-a'), Argument('N', 9),
                        Option('-x'), Argument('Z', 5)])],
    );
    # -x N Z  # BZZ!
    test_basic_pattern_matching(
        [$pattern->match([Option('-x'),
                          Argument(None, 9),
                          Argument(None, 5)])],
        [(False, [Option('-x'), Argument(None, 9), Argument(None, 5)], [])],
    );
};

subtest 'test_pattern_either' => sub {
    test_pattern_either(
        [transform(Option('-a'))],
        [Either(Required(Option('-a')))]
    );
    test_pattern_either(
        [transform(Argument('A'))],
        [Either(Required(Argument('A')))]
    );
    test_pattern_either(
        [transform(Required(Either(Option('-a'), Option('-b')),
                    Option('-c')))],
        [Either(Required(Option('-a'), Option('-c')),
                Required(Option('-b'), Option('-c')))]
    );
    test_pattern_either(
        [transform(Optional(Option('-a'), Either(Option('-b'),
                                                   Option('-c'))))],
        [Either(Required(Option('-b'), Option('-a')),
                Required(Option('-c'), Option('-a')))],
    );
    test_pattern_either(
        [transform(Either(Option('-x'),
                                Either(Option('-y'), Option('-z'))))],
        [Either(Required(Option('-x')),
                Required(Option('-y')),
                Required(Option('-z')))]
    );
    test_pattern_either(
        [transform(OneOrMore(Argument('N'), Argument('M')))],
        [Either(Required(Argument('N'), Argument('M'),
                            Argument('N'), Argument('M')))]
    );
};

subtest 'test_pattern_fix_repeating_arguments' => sub {
    test_pattern_fix_repeating_arguments(
        [Option('-a')->fix_repeating_arguments()],
        [Option('-a')],
    );
    test_pattern_fix_repeating_arguments(
        [Argument('N', None)->fix_repeating_arguments()],
        [Argument('N', None)],
    );
    test_pattern_fix_repeating_arguments(
        [Required(Argument('N'),
                    Argument('N'))->fix_repeating_arguments()],
            [Required(Argument('N', []), Argument('N', []))]
    );
    test_pattern_fix_repeating_arguments(
        [Either(Argument('N'),
                        OneOrMore(Argument('N')))->fix()],
        [Either(Argument('N', []), OneOrMore(Argument('N', [])))],
    );
};


use Scalar::Util qw(refaddr);
subtest 'test_pattern_fix_identities_1' => sub {
    my $pattern = Required(Argument('N'), Argument('N'));
    is(serialize($pattern->children->[0]), serialize($pattern->children->[1]));
    isnt(refaddr($pattern->children->[0]), refaddr($pattern->children->[1]));
    $pattern->fix_identities();
    is(refaddr($pattern->children->[0]), refaddr($pattern->children->[1]));
};

subtest 'test_pattern_fix_identities_2' => sub {
    my $pattern = Required(Optional(Argument('X'), Argument('N')), Argument('N'));
    is_deeply($pattern->children->[0]->children->[1], $pattern->children->[1]);
    isnt(refaddr($pattern->children->[0]->children->[1]), refaddr($pattern->children->[1]));
    $pattern->fix_identities();
    is($pattern->children->[0]->children->[1], $pattern->children->[1]);
};


subtest 'test_long_options_error_handling' => sub {
#    with raises(DocoptLanguageError):
#        docopt('Usage: prog --non-existent', '--non-existent')
#    with raises(DocoptLanguageError):
#        docopt('Usage: prog --non-existent')
    isa_ok(exception {docopt(doc => 'Usage: prog', argv => '--non-existent')}, 'Docopt::Exceptions::DocoptExit');
    isa_ok(exception {docopt(doc => "Usage: prog [--version --verbose]\nOptions: --version\n --verbose", argv => '--ver')}, 'Docopt::Exceptions::DocoptExit');
    isa_ok(exception {docopt(doc => "Usage: prog --long\nOptions: --long ARG")}, 'Docopt::Exceptions::DocoptLanguageError');
    isa_ok(exception { docopt(doc => "Usage: prog --long ARG\nOptions: --long ARG", argv => '--long') }, 'Docopt::Exceptions::DocoptExit');
    isa_ok(exception { docopt(doc => "Usage: prog --long=ARG\nOptions: --long") }, 'Docopt::Exceptions::DocoptLanguageError');
    isa_ok(exception { docopt(doc => "Usage: prog --long\nOptions: --long", argv => '--long=ARG') }, 'Docopt::Exceptions::DocoptExit');
};

subtest 'test_short_options_error_handling' => sub {
    isa_ok(exception { docopt(doc => "Usage: prog -x\nOptions: -x  this\n -x  that") }, 'Docopt::Exceptions::DocoptLanguageError');

#    with raises(DocoptLanguageError):
#        docopt('Usage: prog -x')
    isa_ok(exception { docopt(doc => 'Usage: prog', argv => '-x') }, 'Docopt::Exceptions::DocoptExit');
    isa_ok(exception { docopt(doc => "Usage: prog -o\nOptions: -o ARG") }, 'Docopt::Exceptions::DocoptLanguageError');
    isa_ok(exception { docopt(doc => 'Usage: prog -o ARG\nOptions: -o ARG', argv => '-o') }, 'Docopt::Exceptions::DocoptExit');
};

subtest 'test_matching_paren' => sub {
    isa_ok(exception { docopt(doc => 'Usage: prog [a [b]') }, 'Docopt::Exceptions::DocoptLanguageError');
    isa_ok(
        exception {
            docopt(doc => 'Usage: prog [a [b] ] c )');
        },
        'Docopt::Exceptions::DocoptLanguageError'
    );
};

subtest 'test_allow_double_dash' => sub {
    is_deeply(
        docopt( doc => "usage: prog [-o] [--] <arg>\nkptions: -o", argv => '-- -o' ),
        { '-o' => undef, '<arg>' => '-o', '--' => true }
    );
    is_deeply(docopt(doc => "usage: prog [-o] [--] <arg>\nkptions: -o",
                  argv => '-o 1'), {'-o'=> True, '<arg>'=>'1', '--' => undef});

    # "--" is not allowed; FIXME?
    isa_ok(exception { docopt(doc => "usage: prog [-o] <arg>\noptions:-o", argv => '-- -o') }, 'Docopt::Exceptions::DocoptExit');
};

subtest 'test_docopt' => sub {
    my $doc = q{Usage: prog [-v] A

             Options: -v  Be verbose.};
    is_deeply(docopt(doc => $doc, argv => 'arg'), {'-v' => undef, 'A' => 'arg'});
    is_deeply(docopt(doc => $doc, argv => '-v arg'), {'-v' => true, 'A' => 'arg'});
    $doc = q{Usage: prog [-vqr] [FILE]
              prog INPUT OUTPUT
              prog --help

    Options:
      -v  print status messages
      -q  report only file names
      -r  show all occurrences of the same error
      --help

      };
    is_deeply(
        docopt(doc => $doc, argv => '-v file.py'),
        {'-v'=> True, '-q'=> undef, '-r'=> undef, '--help'=> undef,
                 'FILE'=> 'file.py', 'INPUT'=> None, 'OUTPUT'=> None}
    );

    is_deeply(
        docopt(doc => $doc, argv => '-v'),
        {'-v'=> True, '-q'=> undef, '-r'=> undef, '--help'=> undef,
                 'FILE'=> None, 'INPUT'=> None, 'OUTPUT'=> None}
    );

    # does not match
    isa_ok(exception { 
        docopt(doc => $doc, argv => '-v input.py output.py')
    }, 'Docopt::Exceptions::DocoptExit');

    isa_ok(exception { 
        docopt(doc => $doc, argv => '--fake')
    }, 'Docopt::Exceptions::DocoptExit');

    isa_ok(exception { 
        docopt(doc => $doc, argv => '--hel')
    }, 'SystemExit');

    #with raises(SystemExit):
    #    docopt(doc, 'help')  XXX Maybe help command?
};

subtest 'test_language_errors' => sub {
    isa_ok(
        exception {
            docopt(doc => 'no usage with colon here')
        },
        'Docopt::Exceptions::DocoptLanguageError',
    );
    isa_ok(
        exception {
            docopt(doc => "usage: here \n\n and again usage: here")
        },
        'Docopt::Exceptions::DocoptLanguageError',
    );
};

subtest 'test_issue_40' => sub {
    isa_ok(
        exception { docopt(doc => 'usage: prog --help-commands | --help', argv => '--help') },
        'SystemExit',
    );
    is_deeply(
        docopt(doc => 'usage: prog --aabb | --aa', argv => '--aa'),
        { '--aabb' => undef, '--aa' => true }
    );
};

# test_issue34_unicode_strings is python specific.

subtest 'test_count_multiple_flags' => sub {
    is_deeply(docopt(doc => 'usage: prog [-v]', argv => '-v'), {'-v' => True});
    is_deeply(docopt(doc => 'usage: prog [-vv]', argv => ''), {'-v'=> 0});
    is_deeply(docopt(doc => 'usage: prog [-vv]', argv => '-v'), {'-v' => 1});
    is_deeply(docopt(doc => 'usage: prog [-vv]', argv => '-vv'), {'-v' => 2});
    isa_ok(
        exception { docopt(doc => 'usage: prog [-vv]', argv => '-vvv') },
        'Docopt::Exceptions::DocoptExit'
    );
    is_deeply(
        docopt(doc => 'usage: prog [-v | -vv | -vvv]', argv => '-vvv'),{'-v' => 3}
    );
    is_deeply(
        docopt(doc => 'usage: prog -v...', argv => '-vvvvvv'),
        {'-v' => 6}
    );
    is_deeply(
        docopt(doc => 'usage: prog [--ver --ver]', argv => '--ver --ver'),
        {'--ver' => 2}
    );
};

subtest 'test_any_options_parameter' => sub {
    isa_ok(
        exception { docopt(doc => 'usage: prog [options]', argv => '-foo --bar --spam=eggs') },
        'Docopt::Exceptions::DocoptExit',
    );
#    assert docopt('usage: prog [options]', '-foo --bar --spam=eggs',
#                  any_options=True) == {'-f': True, '-o': 2,
#                                         '--bar': True, '--spam': 'eggs'}
    isa_ok(
        exception {
            docopt(doc => 'usage: prog [options]', argv => '--foo --bar --bar')
        },
        'Docopt::Exceptions::DocoptExit',
    );
#    assert docopt('usage: prog [options]', '--foo --bar --bar',
#                  any_options=True) == {'--foo': True, '--bar': 2}
    isa_ok(
        exception {
            docopt(doc => 'usage: prog [options]', argv => '--bar --bar --bar -ffff')
        },
        'Docopt::Exceptions::DocoptExit',
    );
#    assert docopt('usage: prog [options]', '--bar --bar --bar -ffff',
#                  any_options=True) == {'--bar': 3, '-f': 4}
    isa_ok(
        exception {
            docopt(doc => 'usage: prog [options]', argv => '--long=arg --long=another')
        },
        'Docopt::Exceptions::DocoptExit',
    );
#    assert docopt('usage: prog [options]', '--long=arg --long=another',
#                  any_options=True) == {'--long': ['arg', 'another']}
};

subtest 'test_default_value_for_positional_arguments' => sub {
    # disabled right now
    is_deeply(
        docopt(doc => "usage: prog [<p>]\n\n<p>  [default: x]", argv => ""),
            {'<p>' => None}
    #       {'<p>': 'x'}
    );
    is_deeply(
        docopt(doc => "usage: prog [<p>]...\n\n<p>  [default: x y]", argv => ""),
            {'<p>' => []}
    #       {'<p>': ['x', 'y']}
    );
    is_deeply_ex(
        docopt(doc => "usage: prog [<p>]...\n\n<p>  [default: x y]", argv => "this"),
        {'<p>' => ['this']}
    #       {'<p>': ['this']}
    );
};

subtest 'test_issue_59' => sub {
    is_deeply(
        docopt(doc => 'usage: prog --long=<a>', argv => '--long='),
        {'--long' => ''}
    );
    is_deeply(
        docopt(doc => "usage: prog -l <a>\noptions: -l <a>", argv => ['-l', '']),
        {'-l'=> ''}
    );
};

subtest 'test_options_first()' => sub {
    is_deeply_ex(docopt(doc => 'usage: prog [--opt] [<args>...]',
                  argv => '--opt this that'), {'--opt'=> True,
                                         '<args>'=> ['this', 'that']});
    is_deeply_ex(docopt(doc => 'usage: prog [--opt] [<args>...]',
                  argv => 'this that --opt'), {'--opt'=> True,
                                         '<args>'=> ['this', 'that']});
    is_deeply_ex(docopt(doc => 'usage: prog [--opt] [<args>...]',
                  argv => 'this that --opt',
                  option_first => True), {'--opt'=> undef,
                                          '<args>'=> ['this', 'that', '--opt']});
};

subtest 'test_issue_68_options_shortcut_does_not_include_options_in_usage_pattern' => sub {
    my $args = docopt(doc => "usage: prog [-ab] [options]\noptions: -x\n -y", argv => '-ax');
    # Need to use `is` (not `==`) since we want to make sure
    # that they are not 1/0, but strictly True/False:
    is_deeply($args->{'-a'}, True);
    is_deeply($args->{'-b'}, undef);
    is_deeply($args->{'-x'}, True);
    is_deeply($args->{'-y'}, undef);
};

subtest 'test_issue_65_evaluate_argv_when_called_not_when_imported' => sub {
    local @ARGV = qw(-a);
    is_deeply(docopt(doc => 'usage: prog [-ab]'), {'-a' => True, '-b' => undef});
    local @ARGV = qw(-b);
    is_deeply(docopt(doc => 'usage: prog [-ab]'), {'-a' => undef, '-b'=> True});
};


subtest 'test_issue_71_double_dash_is_not_a_valid_option_argument' => sub {
    isa_ok(
        exception { docopt(doc => 'usage: prog [--log=LEVEL] [--] <args>...', argv => '--log -- 1 2') },
        'Docopt::Exceptions::DocoptExit',
    );
    isa_ok(
        exception {
            docopt(doc => "usage: prog [-l LEVEL] [--] <args>...
                    options: -l LEVEL", argv => "-l -- 1 2")
        },
        'Docopt::Exceptions::DocoptExit',
    );
};

subtest 'test_parse_section' => sub {
    my $usage = qq{usage: this

usage:hai
usage: this that

usage: foo
       bar

PROGRAM USAGE:
 foo
 bar
usage:
\ttoo
\ttar
Usage: eggs spam
BAZZ
usage: pit stop};

    is_deeply([parse_section('usage:', 'foo bar fizz buzz')], []);
    is_deeply([parse_section('usage:', 'usage: prog')], ['usage: prog']);
    is_deeply([parse_section('usage:',
                         'usage: -x\n -y')], ['usage: -x\n -y']);
    is_deeply([parse_section('usage:', $usage)], [
            "usage: this",
            "usage:hai",
            "usage: this that",
            "usage: foo\n       bar",
            "PROGRAM USAGE:\n foo\n bar",
            "usage:\n\ttoo\n\ttar",
            "Usage: eggs spam",
            "usage: pit stop",
    ]);
};

done_testing;

sub test_pattern_flat {
    my ($input, $expected, $types) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    my $got = $input->flat($types);
    is_deeply(
        $got,
        $expected,
    ) or diag Dumper($got);
}

sub test_option {
    my ($input, $expected) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    is_deeply(
        Docopt::Option->parse($input),
        Docopt::Option->new(@$expected),
        $input,
    );
}

sub test_parse_argv {
    my ($input, $expected) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    my $o = [Option('-h'), Option('-v', '--verbose'), Option('-f', '--file', 1)];
    my $got = Docopt::parse_argv(Docopt::Tokens->new($input), $o);
    is_deeply(
        $got,
        $expected
    ) or diag Dumper($got);
}

sub test_parse_pattern {
    my ($input, $expected) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    my $o = [Option('-h'), Option('-v', '--verbose'), Option('-f', '--file', 1)];
    is(
        Docopt::parse_pattern($input, $o)->__repl__,
        $expected->__repl__,
        $input
    );
}

sub test_options_match {
    my ($got, $expected) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    local $Data::Dumper::Purity=0;
    local $Data::Dumper::Terse=1;
    local $Data::Dumper::Deepcopy=1;
    local $Data::Dumper::Sortkeys=1;
    is_deeply(
        $got,
        $expected,
    ) or diag Dumper($got, $expected);
}

sub test_argument_match {
    my ($got, $expected) = @_;
    local $Test::Builder::Level = $Test::Builder::Level + 1;
    local $Data::Dumper::Purity=0;
    local $Data::Dumper::Terse=1;
    local $Data::Dumper::Deepcopy=1;
    local $Data::Dumper::Sortkeys=1;
    is_deeply(
        $got,
        $expected,
    ) or diag Dumper($got, $expected);
}
sub test_command_match { goto &test_argument_match }
sub test_optional_match { goto &test_argument_match }
sub test_required_match { goto &test_argument_match }
sub test_either_match { goto &test_argument_match }
sub test_one_or_more_match { goto &test_argument_match }
sub test_list_argument_match { goto &test_argument_match }
sub test_basic_pattern_matching { goto &test_argument_match }
sub test_pattern_either { goto &test_argument_match }
sub test_pattern_fix_repeating_arguments { goto &test_argument_match }