#!perl

# BEGIN DATAPACK CODE
{
    my $toc;
    my $data_linepos = 1;
    unshift @INC, sub {
        $toc ||= do {

            # calculate the line number of data section
            my $data_pos = tell(DATA);
            seek DATA, 0, 0;
            my $pos = 0;
            while (1) {
                my $line = <DATA>;
                $pos += length($line);
                $data_linepos++;
                last if $pos >= $data_pos;
            }
            seek DATA, $data_pos, 0;

            my $fh = \*DATA;

        my $header_line;
        my $header_found;
        while (1) {
            my $header_line = <$fh>;
            defined($header_line)
                or die "Unexpected end of data section while reading header line";
            chomp($header_line);
            if ($header_line eq 'Data::Section::Seekable v1') {
                $header_found++;
                last;
            }
        }
        die "Can't find header 'Data::Section::Seekable v1'"
            unless $header_found;

        my %toc;
        my $i = 0;
        while (1) {
            $i++;
            my $toc_line = <$fh>;
            defined($toc_line)
                or die "Unexpected end of data section while reading TOC line #$i";
            chomp($toc_line);
            $toc_line =~ /\S/ or last;
            $toc_line =~ /^([^,]+),(\d+),(\d+)(?:,(.*))?$/
                or die "Invalid TOC line #$i in data section: $toc_line";
            $toc{$1} = [$2, $3, $4];
        }
        my $pos = tell $fh;
        $toc{$_}[0] += $pos for keys %toc;


            \%toc;
        };
        if ($toc->{$_[1]}) {
            seek DATA, $toc->{$_[1]}[0], 0;
            read DATA, my($content), $toc->{$_[1]}[1];
            my ($order, $lineoffset) = split(';', $toc->{$_[1]}[2]);
            $content =~ s/^ //gm;
            $content = "# line ".($data_linepos + 1 + keys(%$toc) + 1 + $order+1 + $lineoffset)." \"".__FILE__."\"\n" . $content;
            open my $fh, '<', \$content
                or die "DataPacker error loading $_[1]: $!";
            return $fh;
        }
        return;
    };
}
# END DATAPACK CODE

# Note: This completer script is generated by App::GenPericmdCompleterScript version 0.05
# on Mon Nov 23 21:48:07 2015. You probably should not manually edit this file.

# NO_PERINCI_CMDLINE_SCRIPT
# PERINCI_CMDLINE_COMPLETER_SCRIPT: {load_module=>undef,program_name=>"pick-random-lines",read_config=>0,read_env=>0,skip_format=>undef,subcommands=>undef,url=>"/App/PickRandomLines/pick_random_lines"}
# FRAGMENT id=shcompgen-hint completer=1 for=pick-random-lines
our $DATE = '2015-11-23'; # DATE
our $VERSION = '0.01'; # VERSION
# PODNAME: _pick-random-lines
# ABSTRACT: Completer script for pick-random-lines

use 5.010;
use strict;
use warnings;

die "Please run this script under shell completion\n" unless $ENV{COMP_LINE} || $ENV{COMMAND_LINE};

my $args = {load_module=>undef,program_name=>"pick-random-lines",read_config=>0,read_env=>0,skip_format=>undef,subcommands=>undef,url=>"/App/PickRandomLines/pick_random_lines"};

my $meta = {args=>{algorithm=>{default=>"scan",description=>"\n`scan` is the algorithm described in the `perlfaq` manual (`perldoc -q \"random\nline\"). This algorithm scans the whole input once and pick one or more lines\nrandomly from it.\n\n`seek` is the algorithm employed by the Perl module `File::RandomLine`. It works\nby seeking a file randomly and finding the next line (repeated `n` number of\ntimes). This algorithm is faster when the input is very large as it avoids\nhaving to scan the whole input. But it requires that the input is seekable (a\nsingle file, stdin is not supported and currently multiple files are not\nsupported as well). *Might produce duplicate lines*.\n\n",schema=>["str",{in=>["scan","seek"],req=>1},{}]},files=>{description=>"\nIf none is specified, will get input from stdin.\n\n",greedy=>1,pos=>0,schema=>["array",{of=>"str*",req=>1},{}],"x.name.is_plural"=>1,"x.schema.element_entity"=>"filename"},num_lines=>{cmdline_aliases=>{n=>{}},default=>1,description=>"\nIf input contains less lines than the requested number of lines, then will only\nreturn as many lines as the input contains.\n\n",schema=>["int",{min=>1,req=>1},{}]}},description=>"\nTODO:\n* option to allow or disallow duplicates\n\n",entity_date=>undef,entity_v=>undef,summary=>"Pick one or more random lines from input",v=>1.1};

my $sc_metas = {};

my $copts = {format=>{default=>undef,getopt=>"format=s",handler=>sub {    package Perinci::CmdLine::Base;    use warnings;    use strict;    no feature;    use feature ':5.10';    my($go, $val, $r) = @_;    $r->{'format'} = $val;},is_settable_via_config=>1,schema=>["str*","in",["text","text-simple","text-pretty","json","json-pretty","csv"]],summary=>"Choose output format, e.g. json, text",tags=>["category:output"]},help=>{getopt=>"help|h|?",handler=>sub {    package Perinci::CmdLine::Base;    use warnings;    use strict;    no feature;    use feature ':5.10';    my($go, $val, $r) = @_;    $r->{'action'} = 'help';    $r->{'skip_parse_subcommand_argv'} = 1;},order=>0,summary=>"Display help message and exit",usage=>"--help (or -h, -?)"},json=>{getopt=>"json",handler=>sub {    package Perinci::CmdLine::Base;    use warnings;    use strict;    no feature;    use feature ':5.10';    my($go, $val, $r) = @_;    $r->{'format'} = -t STDOUT ? 'json-pretty' : 'json';},summary=>"Set output format to json",tags=>["category:output"]},naked_res=>{default=>0,description=>"\nBy default, when outputing as JSON, the full enveloped result is returned, e.g.:\n\n    [200,\"OK\",[1,2,3],{\"func.extra\"=>4}]\n\nThe reason is so you can get the status (1st element), status message (2nd\nelement) as well as result metadata/extra result (4th element) instead of just\nthe result (3rd element). However, sometimes you want just the result, e.g. when\nyou want to pipe the result for more post-processing. In this case you can use\n`--naked-res` so you just get:\n\n    [1,2,3]\n\n",getopt=>"naked-res!",handler=>sub {    package Perinci::CmdLine::Base;    use warnings;    use strict;    no feature;    use feature ':5.10';    my($go, $val, $r) = @_;    $r->{'naked_res'} = $val ? 1 : 0;},is_settable_via_config=>1,summary=>"When outputing as JSON, strip result envelope","summary.alt.bool.not"=>"When outputing as JSON, add result envelope",tags=>["category:output"]},version=>{getopt=>"version|v",handler=>sub {    package Perinci::CmdLine::Base;    use warnings;    use strict;    no feature;    use feature ':5.10';    my($go, $val, $r) = @_;    $r->{'action'} = 'version';    $r->{'skip_parse_subcommand_argv'} = 1;},summary=>"Display program's version and exit",usage=>"--version (or -v)"}};

my $r = {};

# get words
my $shell;
my ($words, $cword);
if ($ENV{COMP_LINE}) { $shell = "bash"; require Complete::Bash; ($words,$cword) = @{ Complete::Bash::parse_cmdline() }; }
elsif ($ENV{COMMAND_LINE}) { $shell = "tcsh"; require Complete::Tcsh; ($words,$cword) = @{ Complete::Tcsh::parse_cmdline() }; }
@ARGV = @$words;

# strip program name
shift @$words; $cword--;

# parse common_opts which potentially sets subcommand
{
    require Getopt::Long;
    my $old_go_conf = Getopt::Long::Configure('pass_through', 'no_ignore_case', 'bundling', 'no_auto_abbrev');
    my @go_spec;
    for my $k (keys %$copts) { push @go_spec, $copts->{$k}{getopt} => sub { my ($go, $val) = @_; $copts->{$k}{handler}->($go, $val, $r); } }
    Getopt::Long::GetOptions(@go_spec);
    Getopt::Long::Configure($old_go_conf);
}

# select subcommand
my $scn = $r->{subcommand_name};
my $scn_from = $r->{subcommand_name_from};
if (!defined($scn) && defined($args->{default_subcommand})) {
    # get from default_subcommand
    if ($args->{get_subcommand_from_arg} == 1) {
        $scn = $args->{default_subcommand};
        $scn_from = "default_subcommand";
    } elsif ($args->{get_subcommand_from_arg} == 2 && !@ARGV) {
        $scn = $args->{default_subcommand};
        $scn_from = "default_subcommand";
    }
}
if (!defined($scn) && $args->{subcommands} && @ARGV) {
    # get from first command-line arg
    $scn = shift @ARGV;
    $scn_from = "arg";
}

if (defined($scn) && !$sc_metas->{$scn}) { undef $scn } # unknown subcommand name
# XXX read_env

# complete with periscomp
my $compres;
{
    require Perinci::Sub::Complete;
    $compres = Perinci::Sub::Complete::complete_cli_arg(
        meta => defined($scn) ? $sc_metas->{$scn} : $meta,
        words => $words,
        cword => $cword,
        common_opts => $copts,
        riap_server_url => undef,
        riap_uri => undef,
        extras => {r=>$r, cmdline=>undef},
        func_arg_starts_at => (($scn_from//"") eq "arg" ? 1:0),
        completion => sub {
            my %args = @_;
            my $type = $args{type};

            # user specifies custom completion routine, so use that first
            if ($args->{completion}) {
                my $res = $args->{completion}->(%args);
                return $res if $res;
            }
            # if subcommand name has not been supplied and we're at arg#0,
            # complete subcommand name
            if ($args->{subcommands} &&
                $scn_from ne "--cmd" &&
                     $type eq "arg" && $args{argpos}==0) {
                require Complete::Util;
                return Complete::Util::complete_array_elem(
                    array => [keys %{ $args->{subcommands} }],
                    word  => $words->[$cword]);
            }

            # otherwise let periscomp do its thing
            return undef;
        },
    );
}

# display result
if    ($shell eq "bash") { print Complete::Bash::format_completion($compres, {word=>$words->[$cword]}) }
elsif ($shell eq "tcsh") { print Complete::Tcsh::format_completion($compres) }

=pod

=encoding UTF-8

=head1 NAME

_pick-random-lines - Completer script for pick-random-lines

=head1 VERSION

This document describes version 0.01 of Perinci::CmdLine::Base (from Perl distribution App-PickRandomLines), released on 2015-11-23.

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-PickRandomLines>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-PickRandomLines>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-PickRandomLines>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by perlancar@cpan.org.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

__DATA__
Data::Section::Seekable v1
Clone/PP.pm,20,1884,0;0
Complete.pm,1924,93,1;71
Complete/Bash.pm,2042,16756,2;80
Complete/Env.pm,18822,1693,3;579
Complete/File.pm,20540,7030,4;645
Complete/Getopt/Long.pm,27602,18426,5;879
Complete/Path.pm,46053,9057,6;1406
Complete/Setting.pm,55138,490,7;1694
Complete/Tcsh.pm,55653,2757,8;1713
Complete/Util.pm,58435,9957,9;1818
Data/Sah/Normalize.pm,68422,6073,10;2189
Function/Fallback/CoreOrPP.pm,74533,1609,11;2370
Getopt/Long/Negate/EN.pm,76175,811,12;2454
Getopt/Long/Util.pm,77014,7487,13;2484
Lingua/EN/PluralToSingular.pm,84539,4407,14;2758
Log/Any.pm,88965,2421,15;3041
Log/Any/Adapter.pm,91413,367,16;3147
Log/Any/Adapter/Base.pm,91812,772,17;3176
Log/Any/Adapter/File.pm,92616,1791,18;3213
Log/Any/Adapter/Null.pm,94439,350,19;3277
Log/Any/Adapter/Stderr.pm,94823,1197,20;3299
Log/Any/Adapter/Stdout.pm,96054,1197,21;3348
Log/Any/Adapter/Test.pm,97283,4292,22;3397
Log/Any/Adapter/Util.pm,101607,3025,23;3571
Log/Any/IfLOG.pm,104657,1357,24;3719
Log/Any/Manager.pm,106041,5072,25;3784
Log/Any/Proxy.pm,111138,2492,26;3973
Log/Any/Proxy/Test.pm,113660,475,27;4062
Log/Any/Test.pm,114159,275,28;4092
Module/Path/More.pm,114462,4810,29;4108
Perinci/Sub/Complete.pm,119304,36097,30;4301
Perinci/Sub/GetArgs/Argv.pm,155437,34808,31;5241
Perinci/Sub/GetArgs/Array.pm,190282,3345,32;6185
Perinci/Sub/Normalize.pm,193660,4905,33;6307
Perinci/Sub/Util.pm,198593,9870,34;6460
Perinci/Sub/Util/ResObj.pm,208498,243,35;6812
Perinci/Sub/Util/Sort.pm,208774,463,36;6827
Rinci.pm,209254,64,37;6856
Sah/Schema/Rinci.pm,209346,4218,38;6864
String/Wildcard/Bash.pm,213596,2159,39;7045

### Clone/PP.pm ###
 package Clone::PP;
 
 use 5.006;
 use strict;
 use warnings;
 use vars qw($VERSION @EXPORT_OK);
 use Exporter;
 
 $VERSION = 1.06;
 
 @EXPORT_OK = qw( clone );
 sub import { goto &Exporter::import } 
 
 use vars qw( $CloneSelfMethod $CloneInitMethod );
 $CloneSelfMethod ||= 'clone_self';
 $CloneInitMethod ||= 'clone_init';
 
 use vars qw( %CloneCache );
 
 sub clone {
   my $source = shift;
 
   return undef if not defined($source);
   
   my $depth = shift;
   return $source if ( defined $depth and $depth -- < 1 );
   
   local %CloneCache = ( undef => undef ) unless ( exists $CloneCache{undef} );
   
   return $CloneCache{ $source } if ( defined $CloneCache{ $source } );
   
   my $ref_type = ref $source or return $source;
   
   my $class_name;
   if ( "$source" =~ /^\Q$ref_type\E\=([A-Z]+)\(0x[0-9a-f]+\)$/ ) {
     $class_name = $ref_type;
     $ref_type = $1;
     return $CloneCache{ $source } = $source->$CloneSelfMethod() 
 				  if $source->can($CloneSelfMethod);
   }
   
   
   my $copy;
   if ($ref_type eq 'HASH') {
     $CloneCache{ $source } = $copy = {};
     if ( my $tied = tied( %$source ) ) { tie %$copy, ref $tied }
     %$copy = map { ! ref($_) ? $_ : clone($_, $depth) } %$source;
   } elsif ($ref_type eq 'ARRAY') {
     $CloneCache{ $source } = $copy = [];
     if ( my $tied = tied( @$source ) ) { tie @$copy, ref $tied }
     @$copy = map { ! ref($_) ? $_ : clone($_, $depth) } @$source;
   } elsif ($ref_type eq 'REF' or $ref_type eq 'SCALAR') {
     $CloneCache{ $source } = $copy = \( my $var = "" );
     if ( my $tied = tied( $$source ) ) { tie $$copy, ref $tied }
     $$copy = clone($$source, $depth);
   } else {
     $CloneCache{ $source } = $copy = $source;
   }
   
   if ( $class_name ) {
     bless $copy, $class_name;
     $copy->$CloneInitMethod() if $copy->can($CloneInitMethod);
   }
   
   return $copy;
 }
 
 1;
 
 __END__
 
### Complete.pm ###
 package Complete;
 
 our $DATE = '2015-09-16'; 
 our $VERSION = '0.16'; 
 
 1;
 
 __END__
 
### Complete/Bash.pm ###
 package Complete::Bash;
 
 our $DATE = '2015-09-09'; 
 our $VERSION = '0.21'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        parse_cmdline
                        parse_options
                        format_completion
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Completion module for bash shell',
     links => [
         {url => 'pm:Complete'},
     ],
 };
 
 sub _expand_tilde {
     my ($user, $slash) = @_;
     my @ent;
     if (length $user) {
         @ent = getpwnam($user);
     } else {
         @ent = getpwuid($>);
         $user = $ent[0];
     }
     return $ent[7] . $slash if @ent;
     "~$user$slash"; 
 }
 
 sub _add_unquoted {
     no warnings 'uninitialized';
 
     my ($word, $is_cur_word, $after_ws) = @_;
 
 
     $word =~ s!^(~)(\w*)(/|\z) |  # 1) tilde  2) username  3) optional slash
                \\(.)           |  # 4) escaped char
                \$(\w+)            # 5) variable name
               !
                   $1 ? (not($after_ws) || $is_cur_word ? "$1$2$3" : _expand_tilde($2, $3)) :
                       $4 ? $4 :
                           ($is_cur_word ? "\$$5" : $ENV{$5})
                               !egx;
     $word;
 }
 
 sub _add_double_quoted {
     no warnings 'uninitialized';
 
     my ($word, $is_cur_word) = @_;
 
     $word =~ s!\\(.)           |  # 1) escaped char
                \$(\w+)            # 2) variable name
               !
                   $1 ? $1 :
                       ($is_cur_word ? "\$$2" : $ENV{$2})
                           !egx;
     $word;
 }
 
 sub _add_single_quoted {
     my $word = shift;
     $word =~ s/\\(.)/$1/g;
     $word;
 }
 
 $SPEC{parse_cmdline} = {
     v => 1.1,
     summary => 'Parse shell command-line for processing by completion routines',
     description => <<'_',
 
 This function basically converts COMP_LINE (str) and COMP_POINT (int) into
 something like (but not exactly the same as) COMP_WORDS (array) and COMP_CWORD
 (int) that bash supplies to shell functions.
 
 The differences with bash are (these differences are mostly for parsing
 convenience for programs that use this routine):
 
 1) quotes and backslashes are stripped (bash's COMP_WORDS contains all the
 quotes and backslashes);
 
 2) variables are substituted with their values from environment variables except
 for the current word (COMP_WORDS[COMP_CWORD]) (bash does not perform variable
 substitution for COMP_WORDS). However, note that special shell variables that
 are not environment variables like `$0`, `$_`, `$IFS` will not be replaced
 correctly because bash does not export those variables for us.
 
 3) tildes (~) are expanded with user's home directory except for the current
 word (bash does not perform tilde expansion for COMP_WORDS);
 
 4) no word-breaking characters aside from whitespaces and `=` are currently used
 (bash uses COMP_WORDBREAKS which by default also include `:`, `;`, and so on).
 This is done for convenience of parsing of Getopt::Long-based applications. More
 word-breaking characters might be used in the future, e.g. when we want to
 handle complex bash statements like pipes, redirection, etc.
 
 Caveats:
 
 * Due to the way bash parses the command line, the two below are equivalent:
 
     % cmd --foo=bar
     % cmd --foo = bar
 
 Because they both expand to `['--foo', '=', 'bar']`. But obviously
 `Getopt::Long` does not regard the two as equivalent.
 
 _
     args_as => 'array',
     args => {
         cmdline => {
             summary => 'Command-line, defaults to COMP_LINE environment',
             schema => 'str*',
             pos => 0,
         },
         point => {
             summary => 'Point/position to complete in command-line, '.
                 'defaults to COMP_POINT',
             schema => 'int*',
             pos => 1,
         },
     },
     result => {
         schema => ['array*', len=>2],
         description => <<'_',
 
 Return a 2-element array: `[$words, $cword]`. `$words` is array of str,
 equivalent to `COMP_WORDS` provided by bash to shell functions. `$cword` is an
 integer, equivalent to `COMP_CWORD` provided by bash to shell functions. The
 word to be completed is at `$words->[$cword]`.
 
 Note that COMP_LINE includes the command name. If you want the command-line
 arguments only (like in `@ARGV`), you need to strip the first element from
 `$words` and reduce `$cword` by 1.
 
 
 _
     },
     result_naked => 1,
     links => [
     ],
 };
 sub parse_cmdline {
     no warnings 'uninitialized';
     my ($line, $point) = @_;
 
     $line  //= $ENV{COMP_LINE};
     $point //= $ENV{COMP_POINT} // 0;
 
     die "$0: COMP_LINE not set, make sure this script is run under ".
         "bash completion (e.g. through complete -C)\n" unless defined $line;
 
     my @words;
     my $cword;
     my $pos = 0;
     my $pos_min_ws = 0;
     my $after_ws = 1;
     my $chunk;
     my $add_blank;
     my $is_cur_word;
     $line =~ s!(                                                 # 1) everything
                   (")((?: \\\\|\\"|[^"])*)(?:"|\z)(\s*)       |  # 2) open "  3) content  4) space after
                   (')((?: \\\\|\\'|[^'])*)(?:'|\z)(\s*)       |  # 5) open '  6) content  7) space after
                   ((?: \\\\|\\"|\\'|\\=|\\\s|[^"'=\s])+)(\s*) |  # 8) unquoted word  9) space after
                   = |
                   \s+
               )!
                   $pos += length($1);
                   #say "D:<$1> pos=$pos, point=$point, cword=$cword, after_ws=$after_ws";
 
                   if ($2 || $5 || defined($8)) {
                       # double-quoted/single-quoted/unquoted chunk
 
                       if (not(defined $cword)) {
                           $pos_min_ws = $pos - length($2 ? $4 : $5 ? $7 : $9);
                           #say "D:pos_min_ws=$pos_min_ws";
                           if ($point <= $pos_min_ws) {
                               $cword = @words - ($after_ws ? 0 : 1);
                           } elsif ($point < $pos) {
                               $cword = @words + 1 - ($after_ws ? 0 : 1);
                               $add_blank = 1;
                           }
                       }
 
                       if ($after_ws) {
                           $is_cur_word = defined($cword) && $cword==@words;
                       } else {
                           $is_cur_word = defined($cword) && $cword==@words-1;
                       }
                       $chunk =
                           $2 ? _add_double_quoted($3, $is_cur_word) :
                               $5 ? _add_single_quoted($6) :
                                   _add_unquoted($8, $is_cur_word, $after_ws);
                       if ($after_ws) {
                           push @words, $chunk;
                       } else {
                           $words[-1] .= $chunk;
                       }
                       if ($add_blank) {
                           push @words, '';
                           $add_blank = 0;
                       }
                       $after_ws = ($2 ? $4 : $5 ? $7 : $9) ? 1:0;
 
                   } elsif ($1 eq '=') {
                       # equal sign as word-breaking character
                       push @words, '=';
                       $after_ws = 1;
                   } else {
                       # whitespace
                       $after_ws = 1;
                   }
     !egx;
 
     $cword //= @words;
     $words[$cword] //= '';
 
     [\@words, $cword];
 }
 
 $SPEC{parse_options} = {
     v => 1.1,
     summary => 'Parse command-line for options and arguments, '.
         'more or less like Getopt::Long',
     description => <<'_',
 
 Parse command-line into words using `parse_cmdline()` then separate options and
 arguments. Since this routine does not accept `Getopt::Long` (this routine is
 meant to be a generic option parsing of command-lines), it uses a few simple
 rules to server the common cases:
 
 * After `--`, the rest of the words are arguments (just like Getopt::Long).
 
 * If we get something like `-abc` (a single dash followed by several letters) it
   is assumed to be a bundle of short options.
 
 * If we get something like `-MData::Dump` (a single dash, followed by a letter,
   followed by some letters *and* non-letters/numbers) it is assumed to be an
   option (`-M`) followed by a value.
 
 * If we get something like `--foo` it is a long option. If the next word is an
   option (starts with a `-`) then it is assumed that this option does not have
   argument. Otherwise, the next word is assumed to be this option's value.
 
 * Otherwise, it is an argument (that is, permute is assumed).
 
 _
 
     args => {
         cmdline => {
             summary => 'Command-line, defaults to COMP_LINE environment',
             schema => 'str*',
         },
         point => {
             summary => 'Point/position to complete in command-line, '.
                 'defaults to COMP_POINT',
             schema => 'int*',
         },
         words => {
             summary => 'Alternative to passing `cmdline` and `point`',
             schema => ['array*', of=>'str*'],
             description => <<'_',
 
 If you already did a `parse_cmdline()`, you can pass the words result (the first
 element) here to avoid calling `parse_cmdline()` twice.
 
 _
         },
         cword => {
             summary => 'Alternative to passing `cmdline` and `point`',
             schema => ['array*', of=>'str*'],
             description => <<'_',
 
 If you already did a `parse_cmdline()`, you can pass the cword result (the
 second element) here to avoid calling `parse_cmdline()` twice.
 
 _
         },
     },
     result => {
         schema => 'hash*',
     },
 };
 sub parse_options {
     my %args = @_;
 
     my ($words, $cword) = @_;
     if ($args{words}) {
         ($words, $cword) = ($args{words}, $args{cword});
     } else {
         ($words, $cword) = @{parse_cmdline($args{cmdline}, $args{point}, '=')};
     }
 
     my @types;
     my %opts;
     my @argv;
     my $type;
     $types[0] = 'command';
     my $i = 1;
     while ($i < @$words) {
         my $word = $words->[$i];
         if ($word eq '--') {
             if ($i == $cword) {
                 $types[$i] = 'opt_name';
                 $i++; next;
             }
             $types[$i] = 'separator';
             for ($i+1 .. @$words-1) {
                 $types[$_] = 'arg,' . @argv;
                 push @argv, $words->[$_];
             }
             last;
         } elsif ($word =~ /\A-(\w*)\z/) {
             $types[$i] = 'opt_name';
             for (split '', $1) {
                 push @{ $opts{$_} }, undef;
             }
             $i++; next;
         } elsif ($word =~ /\A-([\w?])(.*)/) {
             $types[$i] = 'opt_name';
             push @{ $opts{$1} }, $2;
             $i++; next;
         } elsif ($word =~ /\A--(\w[\w-]*)\z/) {
             $types[$i] = 'opt_name';
             my $opt = $1;
             $i++;
             if ($i < @$words) {
                 if ($words->[$i] eq '=') {
                     $types[$i] = 'separator';
                     $i++;
                 }
                 if ($words->[$i] =~ /\A-/) {
                     push @{ $opts{$opt} }, undef;
                     next;
                 }
                 $types[$i] = 'opt_val';
                 push @{ $opts{$opt} }, $words->[$i];
                 $i++; next;
             }
         } else {
             $types[$i] = 'arg,' . @argv;
             push @argv, $word;
             $i++; next;
         }
     }
 
     return {
         opts      => \%opts,
         argv      => \@argv,
         cword     => $cword,
         words     => $words,
         word_type => $types[$cword],
     };
 }
 
 $SPEC{format_completion} = {
     v => 1.1,
     summary => 'Format completion for output (for shell)',
     description => <<'_',
 
 Bash accepts completion reply in the form of one entry per line to STDOUT. Some
 characters will need to be escaped. This function helps you do the formatting,
 with some options.
 
 This function accepts completion answer structure as described in the `Complete`
 POD. Aside from `words`, this function also recognizes these keys:
 
 * `as` (str): Either `string` (the default) or `array` (to return array of lines
   instead of the lines joined together). Returning array is useful if you are
   doing completion inside `Term::ReadLine`, for example, where the library
   expects an array.
 
 * `esc_mode` (str): Escaping mode for entries. Either `default` (most
   nonalphanumeric characters will be escaped), `shellvar` (like `default`, but
   dollar sign `$` will not be escaped, convenient when completing environment
   variables for example), `filename` (currently equals to `default`), `option`
   (currently equals to `default`), or `none` (no escaping will be done).
 
 * `path_sep` (str): If set, will enable "path mode", useful for
   completing/drilling-down path. Below is the description of "path mode".
 
   In shell, when completing filename (e.g. `foo`) and there is only a single
   possible completion (e.g. `foo` or `foo.txt`), the shell will display the
   completion in the buffer and automatically add a space so the user can move to
   the next argument. This is also true when completing other values like
   variables or program names.
 
   However, when completing directory (e.g. `/et` or `Downloads`) and there is
   solely a single completion possible and it is a directory (e.g. `/etc` or
   `Downloads`), the shell automatically adds the path separator character
   instead (`/etc/` or `Downloads/`). The user can press Tab again to complete
   for files/directories inside that directory, and so on. This is obviously more
   convenient compared to when shell adds a space instead.
 
   The `path_sep` option, when set, will employ a trick to mimic this behaviour.
   The trick is, if you have a completion array of `['foo/']`, it will be changed
   to `['foo/', 'foo/ ']` (the second element is the first element with added
   space at the end) to prevent bash from adding a space automatically.
 
   Path mode is not restricted to completing filesystem paths. Anything path-like
   can use it. For example when you are completing Java or Perl module name (e.g.
   `com.company.product.whatever` or `File::Spec::Unix`) you can use this mode
   (with `path_sep` appropriately set to, e.g. `.` or `::`).
 
 _
     args_as => 'array',
     args => {
         completion => {
             summary => 'Completion answer structure',
             description => <<'_',
 
 Either an array or hash. See function description for more details.
 
 _
             schema=>['any*' => of => ['hash*', 'array*']],
             req=>1,
             pos=>0,
         },
         opts => {
             schema=>'hash*',
             pos=>1,
         },
     },
     result => {
         summary => 'Formatted string (or array, if `as` is set to `array`)',
         schema => ['any*' => of => ['str*', 'array*']],
     },
     result_naked => 1,
 };
 sub format_completion {
     my ($hcomp, $opts) = @_;
 
     $opts //= {};
 
     $hcomp = {words=>$hcomp} unless ref($hcomp) eq 'HASH';
     my $comp     = $hcomp->{words};
     my $as       = $hcomp->{as} // 'string';
     my $esc_mode = $hcomp->{esc_mode} // $hcomp->{escmode} // 'default';
     my $path_sep = $hcomp->{path_sep};
 
     if (defined($path_sep) && @$comp == 1) {
         my $re = qr/\Q$path_sep\E\z/;
         my $word;
         if (ref($comp->[0]) eq 'HASH') {
             $comp = [$comp->[0], {word=>"$comp->[0] "}] if
                 $comp->[0]{word} =~ $re;
         } else {
             $comp = [$comp->[0], "$comp->[0] "]
                 if $comp->[0] =~ $re;
         }
     }
 
     if (defined($opts->{word})) {
         if ($opts->{word} =~ s/(.+:)//) {
             my $prefix = $1;
             for (@$comp) {
                 if (ref($_) eq 'HASH') {
                     $_->{word} =~ s/\A\Q$prefix\E//i;
                 } else {
                     s/\A\Q$prefix\E//i;
                 }
             }
         }
     }
 
     my @res;
     for my $entry (@$comp) {
         my $word = ref($entry) eq 'HASH' ? $entry->{word} : $entry;
         if ($esc_mode eq 'shellvar') {
             $word =~ s!([^A-Za-z0-9,+._/\$~-])!\\$1!g;
         } elsif ($esc_mode eq 'none') {
         } else {
             $word =~ s!([^A-Za-z0-9,+._/:~-])!\\$1!g;
         }
         push @res, $word;
     }
 
     if ($as eq 'array') {
         return \@res;
     } else {
         return join("", map {($_, "\n")} @res);
     }
 }
 
 1;
 
 __END__
 
### Complete/Env.pm ###
 package Complete::Env;
 
 our $DATE = '2015-09-17'; 
 our $VERSION = '0.37'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Complete::Setting;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        complete_env
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Completion routines related to environment variables',
 };
 
 $SPEC{complete_env} = {
     v => 1.1,
     summary => 'Complete from environment variables',
     description => <<'_',
 
 On Windows, environment variable names are all converted to uppercase. You can
 use case-insensitive option (`ci`) to match against original casing.
 
 _
     args => {
         word     => { schema=>[str=>{default=>''}], pos=>0, req=>1 },
         ci       => { schema=>['bool'] },
         fuzzy    => { schema=>['int*', min=>0] },
         map_case => { schema=>['bool'] },
     },
     result_naked => 1,
     result => {
         schema => 'array',
     },
 };
 sub complete_env {
     require Complete::Util;
 
     my %args  = @_;
     my $word     = $args{word} // "";
     my $ci       = $args{ci} // $Complete::Setting::OPT_CI;
     my $fuzzy    = $args{fuzzy} // $Complete::Setting::OPT_FUZZY;
     my $map_case = $args{map_case} // $Complete::Setting::OPT_MAP_CASE;
     if ($word =~ /^\$/) {
         Complete::Util::complete_array_elem(
             word=>$word, array=>[map {"\$$_"} keys %ENV],
             ci=>$ci, fuzzy=>$fuzzy, map_case=>$map_case);
     } else {
         Complete::Util::complete_array_elem(
             word=>$word, array=>[keys %ENV],
             ci=>$ci, fuzzy=>$fuzzy, map_case=>$map_case);
     }
 }
 1;
 
 __END__
 
### Complete/File.pm ###
 package Complete::File;
 
 our $DATE = '2015-11-06'; 
 our $VERSION = '0.38'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Complete::Setting;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        complete_file
                        complete_dir
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Completion routines related to files',
 };
 
 $SPEC{complete_file} = {
     v => 1.1,
     summary => 'Complete file and directory from local filesystem',
     args_rels => {
         choose_one => [qw/filter file_regex_filter/],
     },
     args => {
         word => {
             schema  => [str=>{default=>''}],
             req     => 1,
             pos     => 0,
         },
         ci => {
             summary => 'Case-insensitive matching',
             schema  => 'bool',
         },
         fuzzy => {
             summary => 'Fuzzy matching',
             schema  => ['int*', min=>0],
         },
         map_case => {
             schema  => 'bool',
         },
         exp_im_path => {
             schema  => 'bool',
         },
         dig_leaf => {
             schema  => 'bool',
         },
         filter => {
             summary => 'Only return items matching this filter',
             description => <<'_',
 
 Filter can either be a string or a code.
 
 For string filter, you can specify a pipe-separated groups of sequences of these
 characters: f, d, r, w, x. Dash can appear anywhere in the sequence to mean
 not/negate. An example: `f` means to only show regular files, `-f` means only
 show non-regular files, `drwx` means to show only directories which are
 readable, writable, and executable (cd-able). `wf|wd` means writable regular
 files or writable directories.
 
 For code filter, you supply a coderef. The coderef will be called for each item
 with these arguments: `$name`. It should return true if it wants the item to be
 included.
 
 _
             schema  => ['any*' => {of => ['str*', 'code*']}],
         },
         file_regex_filter => {
             summary => 'Filter shortcut for file regex',
             description => <<'_',
 
 This is a shortcut for constructing a filter. So instead of using `filter`, you
 use this option. This will construct a filter of including only directories or
 regular files, and the file must match a regex pattern. This use-case is common.
 
 _
             schema => 're*',
         },
         starting_path => {
             schema  => 'str*',
             default => '.',
         },
         handle_tilde => {
             schema  => 'bool',
             default => 1,
         },
         allow_dot => {
             summary => 'If turned off, will not allow "." or ".." in path',
             description => <<'_',
 
 This is most useful when combined with `starting_path` option to prevent user
 going up/outside the starting path.
 
 _
             schema  => 'bool',
             default => 1,
         },
     },
     result_naked => 1,
     result => {
         schema => 'array',
     },
 };
 sub complete_file {
     require Complete::Path;
     require File::Glob;
 
     my %args   = @_;
     my $word   = $args{word} // "";
     my $ci          = $args{ci} // $Complete::Setting::OPT_CI;
     my $fuzzy       = $args{fuzzy} // $Complete::Setting::OPT_FUZZY;
     my $map_case    = $args{map_case} // $Complete::Setting::OPT_MAP_CASE;
     my $exp_im_path = $args{exp_im_path} // $Complete::Setting::OPT_EXP_IM_PATH;
     my $dig_leaf    = $args{dig_leaf} // $Complete::Setting::OPT_DIG_LEAF;
     my $handle_tilde = $args{handle_tilde} // 1;
     my $allow_dot   = $args{allow_dot} // 1;
     my $filter = $args{filter};
 
     my $result_prefix;
     my $starting_path = $args{starting_path} // '.';
     if ($handle_tilde && $word =~ s!\A(~[^/]*)/!!) {
         $result_prefix = "$1/";
         my @dir = File::Glob::glob($1); 
         return [] unless @dir;
         $starting_path = $dir[0];
     } elsif ($allow_dot && $word =~ s!\A((?:\.\.?/+)+|/+)!!) {
         $starting_path = $1;
         $result_prefix = $1;
         $starting_path =~ s#/+\z## unless $starting_path =~ m!\A/!;
     }
 
     return [] if !$allow_dot &&
         $word =~ m!(?:\A|/)\.\.?(?:\z|/)!;
 
     my $list = sub {
         my ($path, $intdir, $isint) = @_;
         opendir my($dh), $path or return undef;
         my @res;
         for (sort readdir $dh) {
             next if ($_ eq '.' || $_ eq '..') && $intdir eq '';
             next if $isint && !(-d "$path/$_");
             push @res, $_;
         }
         \@res;
     };
 
     if ($filter && !ref($filter)) {
         my @seqs = split /\s*\|\s*/, $filter;
         $filter = sub {
             my $name = shift;
             my @st = stat($name) or return 0;
             my $mode = $st[2];
             my $pass;
           SEQ:
             for my $seq (@seqs) {
                 my $neg = sub { $_[0] };
                 for my $c (split //, $seq) {
                     if    ($c eq '-') { $neg = sub { $_[0] ? 0 : 1 } }
                     elsif ($c eq 'r') { next SEQ unless $neg->($mode & 0400) }
                     elsif ($c eq 'w') { next SEQ unless $neg->($mode & 0200) }
                     elsif ($c eq 'x') { next SEQ unless $neg->($mode & 0100) }
                     elsif ($c eq 'f') { next SEQ unless $neg->($mode & 0100000)}
                     elsif ($c eq 'd') { next SEQ unless $neg->($mode & 0040000)}
                     else {
                         die "Unknown character in filter: $c (in $seq)";
                     }
                 }
                 $pass = 1; last SEQ;
             }
             $pass;
         };
     } elsif (!$filter && $args{file_regex_filter}) {
         $filter = sub {
             my $name = shift;
             return 1 if -d $name;
             return 0 unless -f _;
             return 1 if $name =~ $args{file_regex_filter};
             0;
         };
     }
 
     if ($args{_dir}) {
         my $orig_filter = $filter;
         $filter = sub {
             my $name = shift;
             return 0 if $orig_filter && !$orig_filter->($name);
             return 0 unless (-d $name);
             1;
         };
     }
 
     Complete::Path::complete_path(
         word => $word,
 
         ci => $ci,
         fuzzy => $fuzzy,
         map_case => $map_case,
         exp_im_path => $exp_im_path,
         dig_leaf => $dig_leaf,
 
         list_func => $list,
         is_dir_func => sub { -d $_[0] },
         filter_func => $filter,
         starting_path => $starting_path,
         result_prefix => $result_prefix,
     );
 }
 
 $SPEC{complete_dir} = do {
     my $spec = {%{ $SPEC{complete_file} }}; 
 
     $spec->{summary} = 'Complete directory from local filesystem '.
         '(wrapper for complete_dir() that only picks directories)';
     delete $spec->{args}{file_regex_filter};
 
     $spec;
 };
 sub complete_dir {
     my %args = @_;
 
     complete_file(%args, _dir=>1);
 }
 
 1;
 
 __END__
 
### Complete/Getopt/Long.pm ###
 package Complete::Getopt::Long;
 
 our $DATE = '2015-09-22'; 
 our $VERSION = '0.37'; 
 
 use 5.010001;
 use strict;
 use warnings;
 use Log::Any::IfLOG '$log';
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        complete_cli_arg
                );
 
 our %SPEC;
 
 sub _default_completion {
     require Complete::Env;
     require Complete::File;
     require Complete::Util;
 
     my %args = @_;
     my $word = $args{word} // '';
 
     my $fres;
     $log->tracef('[comp][compgl] entering default completion routine');
 
     if ($word =~ /\A\$/) {
         $log->tracef('[comp][compgl] completing shell variable');
         {
             my $compres = Complete::Env::complete_env(
                 word=>$word);
             last unless @$compres;
             $fres = {words=>$compres, esc_mode=>'shellvar'};
             goto RETURN_RES;
         }
     }
 
     if ($word =~ m!\A~([^/]*)\z!) {
         $log->tracef("[comp][compgl] completing userdir, user=%s", $1);
         {
             eval { require Unix::Passwd::File };
             last if $@;
             my $res = Unix::Passwd::File::list_users(detail=>1);
             last unless $res->[0] == 200;
             my $compres = Complete::Util::complete_array_elem(
                 array=>[map {"~" . $_->{user} . ((-d $_->{home}) ? "/":"")}
                             @{ $res->[2] }],
                 word=>$word,
             );
             last unless @$compres;
             $fres = {words=>$compres, path_sep=>'/'};
             goto RETURN_RES;
         }
     }
 
     if ($word =~ m!\A(~[^/]*)/!) {
         $log->tracef("[comp][compgl] completing file, path=<%s>", $word);
         $fres = {words=>Complete::File::complete_file(word=>$word),
                  path_sep=>'/'};
         goto RETURN_RES;
     }
 
     require String::Wildcard::Bash;
     if (String::Wildcard::Bash::contains_wildcard($word)) {
         $log->tracef("[comp][compgl] completing with wildcard glob, glob=<%s>", "$word*");
         {
             my $compres = [glob("$word*")];
             last unless @$compres;
             for (@$compres) {
                 $_ .= "/" if (-d $_);
             }
             $fres = {words=>$compres, path_sep=>'/'};
             goto RETURN_RES;
         }
     }
     $log->tracef("[comp][compgl] completing with file, file=<%s>", $word);
     $fres = {words=>Complete::File::complete_file(word=>$word),
              path_sep=>'/'};
   RETURN_RES:
     $log->tracef("[comp][compgl] leaving default completion routine, result=%s", $fres);
     $fres;
 }
 
 sub _expand1 {
     my ($opt, $opts) = @_;
     my @candidates;
     my $is_hash = ref($opts) eq 'HASH';
     for ($is_hash ? (sort {length($a)<=>length($b)} keys %$opts) : @$opts) {
         next unless index($_, $opt) == 0;
         push @candidates, $is_hash ? $opts->{$_} : $_;
         last if $opt eq $_;
     }
     return @candidates == 1 ? $candidates[0] : undef;
 }
 
 sub _mark_seen {
     my ($seen_opts, $opt, $opts) = @_;
     my $opthash = $opts->{$opt};
     return unless $opthash;
     my $ospec = $opthash->{ospec};
     for (keys %$opts) {
         my $v = $opts->{$_};
         $seen_opts->{$_}++ if $v->{ospec} eq $ospec;
     }
 }
 
 $SPEC{complete_cli_arg} = {
     v => 1.1,
     summary => 'Complete command-line argument using '.
         'Getopt::Long specification',
     description => <<'_',
 
 This routine can complete option names, where the option names are retrieved
 from `Getopt::Long` specification. If you provide completion routine in
 `completion`, you can also complete _option values_ and _arguments_.
 
 Note that this routine does not use `Getopt::Long` (it does its own parsing) and
 currently is not affected by Getopt::Long's configuration. Its behavior mimics
 Getopt::Long under these configuration: `no_ignore_case`, `bundling` (or
 `no_bundling` if the `bundling` option is turned off). Which I think is the
 sensible default. This routine also does not currently support `auto_help` and
 `auto_version`, so you'll need to add those options specifically if you want to
 recognize `--help/-?` and `--version`, respectively.
 
 _
     args => {
         getopt_spec => {
             summary => 'Getopt::Long specification',
             schema  => 'hash*',
             req     => 1,
         },
         completion => {
             summary     =>
                 'Completion routine to complete option value/argument',
             schema      => 'code*',
             description => <<'_',
 
 Completion code will receive a hash of arguments (`%args`) containing these
 keys:
 
 * `type` (str, what is being completed, either `optval`, or `arg`)
 * `word` (str, word to be completed)
 * `cword` (int, position of words in the words array, starts from 0)
 * `opt` (str, option name, e.g. `--str`; undef if we're completing argument)
 * `ospec` (str, Getopt::Long option spec, e.g. `str|S=s`; undef when completing
   argument)
 * `argpos` (int, argument position, zero-based; undef if type='optval')
 * `nth` (int, the number of times this option has seen before, starts from 0
   that means this is the first time this option has been seen; undef when
   type='arg')
 * `seen_opts` (hash, all the options seen in `words`)
 * `parsed_opts` (hash, options parsed the standard/raw way)
 
 as well as all keys from `extras` (but these won't override the above keys).
 
 and is expected to return a completion answer structure as described in
 `Complete` which is either a hash or an array. The simplest form of answer is
 just to return an array of strings. The various `complete_*` function like those
 in `Complete::Util` or the other `Complete::*` modules are suitable to use here.
 
 Completion routine can also return undef to express declination, in which case
 the default completion routine will then be consulted. The default routine
 completes from shell environment variables (`$FOO`), Unix usernames (`~foo`),
 and files/directories.
 
 Example:
 
     use Complete::Unix qw(complete_user);
     use Complete::Util qw(complete_array_elem);
     complete_cli_arg(
         getopt_spec => {
             'help|h'   => sub{...},
             'format=s' => \$format,
             'user=s'   => \$user,
         },
         completion  => sub {
             my %args  = @_;
             my $word  = $args{word};
             my $ospec = $args{ospec};
             if ($ospec && $ospec eq 'format=s') {
                 complete_array_elem(array=>[qw/json text xml yaml/], word=>$word);
             } else {
                 complete_user(word=>$word);
             }
         },
     );
 
 _
         },
         words => {
             summary     => 'Command line arguments, like @ARGV',
             description => <<'_',
 
 See function `parse_cmdline` in `Complete::Bash` on how to produce this (if
 you're using bash).
 
 _
             schema      => 'array*',
             req         => 1,
         },
         cword => {
             summary     =>
                 "Index in words of the word we're trying to complete",
             description => <<'_',
 
 See function `parse_cmdline` in `Complete::Bash` on how to produce this (if
 you're using bash).
 
 _
             schema      => 'int*',
             req         => 1,
         },
         extras => {
             summary => 'Add extra arguments to completion routine',
             schema  => 'hash',
             description => <<'_',
 
 The keys from this `extras` hash will be merged into the final `%args` passed to
 completion routines. Note that standard keys like `type`, `word`, and so on as
 described in the function description will not be overwritten by this.
 
 _
         },
         bundling => {
             schema  => 'bool*',
             default => 1,
             'summary.alt.bool.not' => 'Turn off bundling',
             description => <<'_',
 
 If you turn off bundling, completion of short-letter options won't support
 bundling (e.g. `-b<tab>` won't add more single-letter options), but single-dash
 multiletter options can be recognized. Currently only those specified with a
 single dash will be completed. For example if you have `-foo=s` in your option
 specification, `-f<tab>` can complete it.
 
 This can be used to complete old-style programs, e.g. emacs which has options
 like `-nw`, `-nbc` etc (but also have double-dash options like
 `--no-window-system` or `--no-blinking-cursor`).
 
 _
         },
     },
     result_naked => 1,
     result => {
         schema => ['any*' => of => ['hash*', 'array*']],
         description => <<'_',
 
 You can use `format_completion` function in `Complete::Bash` module to format
 the result of this function for bash.
 
 _
     },
 };
 sub complete_cli_arg {
     require Complete::Util;
     require Getopt::Long::Util;
 
     my %args = @_;
 
     my $fname = __PACKAGE__ . "::complete_cli_arg"; 
     my $fres;
 
     $args{words} or die "Please specify words";
     my @words = @{ $args{words} };
     defined(my $cword = $args{cword}) or die "Please specify cword";
     my $gospec = $args{getopt_spec} or die "Please specify getopt_spec";
     my $comp = $args{completion};
     my $extras = $args{extras} // {};
     my $bundling = $args{bundling} // 1;
     my %parsed_opts;
 
     $log->tracef('[comp][compgl] entering %s(), words=%s, cword=%d, word=<%s>',
                  $fname, \@words, $cword, $words[$cword]);
 
     my %opts;
     for my $ospec (keys %$gospec) {
         my $res = Getopt::Long::Util::parse_getopt_long_opt_spec($ospec)
             or die "Can't parse option spec '$ospec'";
         $res->{min_vals} //= $res->{type} ? 1 : 0;
         $res->{max_vals} //= $res->{type} || $res->{opttype} ? 1:0;
         for my $o0 (@{ $res->{opts} }) {
             my @o = $res->{is_neg} && length($o0) > 1 ?
                 ($o0, "no$o0", "no-$o0") : ($o0);
             for my $o (@o) {
                 my $k = length($o)==1 ||
                     (!$bundling && $res->{dash_prefix} eq '-') ?
                         "-$o" : "--$o";
                 $opts{$k} = {
                     name => $k,
                     ospec => $ospec, 
                     parsed => $res,
                 };
             }
         }
     }
     my @optnames = sort keys %opts;
 
     my %seen_opts;
 
     my @expects;
 
     my $i = -1;
     my $argpos = 0;
 
   WORD:
     while (1) {
         last WORD if ++$i >= @words;
         my $word = $words[$i];
 
         if ($word eq '--' && $i != $cword) {
             $expects[$i] = {separator=>1};
             while (1) {
                 $i++;
                 last WORD if $i >= @words;
                 $expects[$i] = {arg=>1, argpos=>$argpos++};
             }
         }
 
         if ($word =~ /\A-/) {
 
           SPLIT_BUNDLED:
             {
                 last unless $bundling;
                 my $shorts = $word;
                 if ($shorts =~ s/\A-([^-])(.*)/$2/) {
                     my $opt = "-$1";
                     my $opthash = $opts{$opt};
                     if (!$opthash || $opthash->{parsed}{max_vals}) {
                         last SPLIT_BUNDLED;
                     }
                     $words[$i] = $word = "-$1";
                     $expects[$i]{prefix} = $word;
                     $expects[$i]{word} = '';
                     $expects[$i]{short_only} = 1;
                     my $len_before_split = @words;
                     my $j = $i+1;
                   SHORTOPT:
                     while ($shorts =~ s/(.)//) {
                         $opt = "-$1";
                         $opthash = $opts{$opt};
                         if (!$opthash || $opthash->{parsed}{max_vals}) {
                             $expects[$i]{do_complete_optname} = 0;
                             if (length $shorts) {
                                 splice @words, $j, 0, $opt, '=', $shorts;
                                 $j += 3;
                             } else {
                                 splice @words, $j, 0, $opt;
                                 $j++;
                             }
                             last SHORTOPT;
                         } else {
                             splice @words, $j, 0, $opt;
                             $j++;
                         }
                     }
                     $cword += @words-$len_before_split if $cword > $i;
                 }
             }
 
           SPLIT_EQUAL:
             {
                 if ($word =~ /\A(--?[^=]+)(=)(.*)/) {
                     splice @words, $i, 1, $1, $2, $3;
                     $word = $1;
                     $cword += 2 if $cword >= $i;
                 }
             }
 
             my $opt = $word;
             my $opthash = _expand1($opt, \%opts);
 
             if ($opthash) {
                 $opt = $opthash->{name};
                 $expects[$i]{optname} = $opt;
                 my $nth = $seen_opts{$opt} // 0;
                 $expects[$i]{nth} = $nth;
                 _mark_seen(\%seen_opts, $opt, \%opts);
 
                 my $min_vals = $opthash->{parsed}{min_vals};
                 my $max_vals = $opthash->{parsed}{max_vals};
 
                 if ($i+1 < @words && $words[$i+1] eq '=') {
                     $i++;
                     $expects[$i] = {separator=>1, optval=>$opt, word=>'', nth=>$nth};
                     if (!$max_vals) { $min_vals = $max_vals = 1 }
                 }
 
                 push @{ $parsed_opts{$opt} }, $words[$i+1];
                 for (1 .. $min_vals) {
                     $i++;
                     last WORD if $i >= @words;
                     $expects[$i]{optval} = $opt;
                     $expects[$i]{nth} = $nth;
                 }
                 for (1 .. $max_vals-$min_vals) {
                     last if $i+$_ >= @words;
                     last if $words[$i+$_] =~ /\A-/; 
                     $expects[$i+$_]{optval} = $opt; 
                     $expects[$i]{nth} = $nth;
                 }
             } else {
                 $opt = undef;
                 $expects[$i]{optname} = $opt;
 
                 if ($i+1 < @words && $words[$i+1] eq '=') {
                     $i++;
                     $expects[$i] = {separator=>1, optval=>undef, word=>''};
                     if ($i+1 < @words) {
                         $i++;
                         $expects[$i]{optval} = $opt;
                     }
                 }
             }
         } else {
             $expects[$i]{optname} = '';
             $expects[$i]{arg} = 1;
             $expects[$i]{argpos} = $argpos++;
         }
     }
 
 
     my $exp = $expects[$cword];
     my $word = $exp->{word} // $words[$cword];
 
     my @answers;
 
     {
         last unless exists $exp->{optname};
         last if defined($exp->{do_complete_optname}) &&
             !$exp->{do_complete_optname};
         my $opt = $exp->{optname};
         my @o;
         for (@optnames) {
             my $repeatable = 0;
             next if $exp->{short_only} && /\A--/;
             if ($seen_opts{$_}) {
                 my $opthash = $opts{$_};
                 my $ospecval = $gospec->{$opthash->{ospec}};
                 my $parsed = $opthash->{parsed};
                 if (ref($ospecval) eq 'ARRAY') {
                     $repeatable = 1;
                 } elsif ($parsed->{desttype} || $parsed->{is_inc}) {
                     $repeatable = 1;
                 }
             }
             next if $seen_opts{$_} && !$repeatable && (
                 (!$opt || $opt ne $_) ||
                     (defined($exp->{prefix}) &&
                          index($exp->{prefix}, substr($opt, 1, 1)) >= 0));
             if (defined $exp->{prefix}) {
                 my $o = $_; $o =~ s/\A-//;
                 push @o, "$exp->{prefix}$o";
             } else {
                 push @o, $_;
             }
         }
         my $compres = Complete::Util::complete_array_elem(
             array => \@o, word => $word);
         $log->tracef('[comp][compgl] adding result from option names, '.
                          'matching options=%s', $compres);
         push @answers, $compres;
         if (!exists($exp->{optval}) && !exists($exp->{arg})) {
             $fres = {words=>$compres, esc_mode=>'option'};
             goto RETURN_RES;
         }
     }
 
     {
         last unless exists($exp->{optval});
         my $opt = $exp->{optval};
         my $opthash = $opts{$opt} if $opt;
         my %compargs = (
             %$extras,
             type=>'optval', words=>\@words, cword=>$args{cword},
             word=>$word, opt=>$opt, ospec=>$opthash->{ospec},
             argpos=>undef, nth=>$exp->{nth}, seen_opts=>\%seen_opts,
             parsed_opts=>\%parsed_opts,
         );
         my $compres;
         if ($comp) {
             $log->tracef("[comp][compgl] invoking routine supplied from 'completion' argument to complete option value, option=<%s>", $opt);
             $compres = $comp->(%compargs);
             $log->tracef('[comp][compgl] adding result from routine: %s', $compres);
         }
         if (!$compres || !$comp) {
             $compres = _default_completion(%compargs);
             $log->tracef('[comp][compgl] adding result from default '.
                              'completion routine');
         }
         push @answers, $compres;
     }
 
     {
         last unless exists($exp->{arg});
         my %compargs = (
             %$extras,
             type=>'arg', words=>\@words, cword=>$args{cword},
             word=>$word, opt=>undef, ospec=>undef,
             argpos=>$exp->{argpos}, seen_opts=>\%seen_opts,
             parsed_opts=>\%parsed_opts,
         );
         $log->tracef('[comp][compgl] invoking \'completion\' routine '.
                          'to complete argument');
         my $compres = $comp->(%compargs);
         if (!defined $compres) {
             $compres = _default_completion(%compargs);
             $log->tracef('[comp][compgl] adding result from default '.
                              'completion routine: %s', $compres);
         }
         push @answers, $compres;
     }
 
     $log->tracef("[comp][compgl] combining result from %d source(s)", ~~@answers);
     $fres = Complete::Util::combine_answers(@answers) // [];
 
   RETURN_RES:
     $log->tracef("[comp][compgl] leaving %s(), result=%s", $fname, $fres);
     $fres;
 }
 
 1;
 
 __END__
 
### Complete/Path.pm ###
 package Complete::Path;
 
 our $DATE = '2015-09-22'; 
 our $VERSION = '0.19'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Complete::Setting;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        complete_path
                );
 
 sub _dig_leaf {
     my ($p, $list_func, $is_dir_func, $path_sep) = @_;
     my $num_dirs;
     my $listres = $list_func->($p, '', 0);
     return $p unless ref($listres) eq 'ARRAY' && @$listres == 1;
     my $e = $listres->[0];
     my $p2 = $p =~ m!\Q$path_sep\E\z! ? "$p$e" : "$p$path_sep$e";
     my $is_dir;
     if ($e =~ m!\Q$path_sep\E\z!) {
         $is_dir++;
     } else {
         $is_dir = $is_dir_func && $is_dir_func->($p2);
     }
     return _dig_leaf($p2, $list_func, $is_dir_func, $path_sep) if $is_dir;
     $p2;
 }
 
 our %SPEC;
 
 $SPEC{complete_path} = {
     v => 1.1,
     summary => 'Complete path',
     description => <<'_',
 
 Complete path, for anything path-like. Meant to be used as backend for other
 functions like `Complete::File::complete_file` or
 `Complete::Module::complete_module`. Provides features like case-insensitive
 matching, expanding intermediate paths, and case mapping.
 
 Algorithm is to split path into path elements, then list items (using the
 supplied `list_func`) and perform filtering (using the supplied `filter_func`)
 at every level.
 
 _
     args => {
         word => {
             schema  => [str=>{default=>''}],
             pos     => 0,
         },
         list_func => {
             summary => 'Function to list the content of intermediate "dirs"',
             schema => 'code*',
             req => 1,
             description => <<'_',
 
 Code will be called with arguments: ($path, $cur_path_elem, $is_intermediate).
 Code should return an arrayref containing list of elements. "Directories" can be
 marked by ending the name with the path separator (see `path_sep`). Or, you can
 also provide an `is_dir_func` function that will be consulted after filtering.
 If an item is a "directory" then its name will be suffixed with a path
 separator by `complete_path()`.
 
 _
         },
         is_dir_func => {
             summary => 'Function to check whether a path is a "dir"',
             schema  => 'code*',
             description => <<'_',
 
 Optional. You can provide this function to determine if an item is a "directory"
 (so its name can be suffixed with path separator). You do not need to do this if
 you already suffix names of "directories" with path separator in `list_func`.
 
 One reason you might want to provide this and not mark "directories" in
 `list_func` is when you want to do extra filtering with `filter_func`. Sometimes
 you do not want to suffix the names first (example: see `complete_file` in
 `Complete::File`).
 
 _
         },
         starting_path => {
             schema => 'str*',
             req => 1,
             default => '',
         },
         filter_func => {
             schema  => 'code*',
             description => <<'_',
 
 Provide extra filtering. Code will be given path and should return 1 if the item
 should be included in the final result or 0 if the item should be excluded.
 
 _
         },
 
         path_sep => {
             schema  => 'str*',
             default => '/',
         },
         ci => {
             summary => 'Case-insensitive matching',
             schema  => 'bool',
         },
         fuzzy => {
             summary => 'Fuzzy matching',
             schema  => ['int*', min=>0],
         },
         map_case => {
             summary => 'Treat _ (underscore) and - (dash) as the same',
             schema  => 'bool',
             description => <<'_',
 
 This is another convenience option like `ci`, where you can type `-` (without
 pressing Shift, at least in US keyboard) and can still complete `_` (underscore,
 which is typed by pressing Shift, at least in US keyboard).
 
 This option mimics similar option in bash/readline: `completion-map-case`.
 
 _
         },
         exp_im_path => {
             summary => 'Expand intermediate paths',
             schema  => 'bool',
             description => <<'_',
 
 This option mimics feature in zsh where when you type something like `cd
 /h/u/b/myscript` and get `cd /home/ujang/bin/myscript` as a completion answer.
 
 _
         },
         dig_leaf => {
             summary => 'Dig leafs',
             schema => 'bool',
             description => <<'_',
 
 This feature mimics what's seen on GitHub. If a directory entry only contains a
 single entry, it will directly show the subentry (and subsubentry and so on) to
 save a number of tab presses.
 
 _
         },
     },
     result_naked => 1,
     result => {
         schema => 'array',
     },
 };
 sub complete_path {
     require Complete::Util;
 
     my %args   = @_;
     my $word   = $args{word} // "";
     my $path_sep = $args{path_sep} // '/';
     my $list_func   = $args{list_func};
     my $is_dir_func = $args{is_dir_func};
     my $filter_func = $args{filter_func};
     my $ci          = $args{ci} // $Complete::Setting::OPT_CI;
     my $fuzzy       = $args{fuzzy} // $Complete::Setting::OPT_FUZZY;
     my $map_case    = $args{map_case} // $Complete::Setting::OPT_MAP_CASE;
     my $exp_im_path = $args{exp_im_path} // $Complete::Setting::OPT_EXP_IM_PATH;
     my $dig_leaf    = $args{dig_leaf} // $Complete::Setting::OPT_DIG_LEAF;
     my $result_prefix = $args{result_prefix};
     my $starting_path = $args{starting_path} // '';
 
     my $re_ends_with_path_sep = qr!\A\z|\Q$path_sep\E\z!;
 
     my @intermediate_dirs;
     {
         @intermediate_dirs = split qr/\Q$path_sep/, $word;
         @intermediate_dirs = ('') if !@intermediate_dirs;
         push @intermediate_dirs, '' if $word =~ $re_ends_with_path_sep;
     }
 
     my $leaf = pop @intermediate_dirs;
     @intermediate_dirs = ('') if !@intermediate_dirs;
 
 
     my @candidate_paths;
 
     for my $i (0..$#intermediate_dirs) {
         my $intdir = $intermediate_dirs[$i];
         my $intdir_with_path_sep = "$intdir$path_sep";
         my @dirs;
         if ($i == 0) {
             @dirs = ($starting_path);
         } else {
             @dirs = @candidate_paths;
         }
 
         if ($i == $#intermediate_dirs && $intdir eq '') {
             @candidate_paths = @dirs;
             last;
         }
 
         my @new_candidate_paths;
         for my $dir (@dirs) {
             my $listres = $list_func->($dir, $intdir, 1);
             next unless $listres && @$listres;
             my $matches = Complete::Util::complete_array_elem(
                 word => $intdir, array => $listres,
                 ci=>$ci, fuzzy=>$fuzzy, map_case=>$map_case,
             );
             my $exact_matches = [grep {
                 length($intdir)+length($path_sep) eq length($_)
             } @$matches];
 
             if ($exp_im_path && @$exact_matches == 1) {
                 $matches = $exact_matches;
             }
 
             for (@$matches) {
                 my $p = $dir =~ $re_ends_with_path_sep ?
                     "$dir$_" : "$dir$path_sep$_";
                 push @new_candidate_paths, $p;
             }
 
         }
         return [] unless @new_candidate_paths;
         @candidate_paths = @new_candidate_paths;
     }
 
     my $cut_chars = 0;
     if (length($starting_path)) {
         $cut_chars += length($starting_path);
         unless ($starting_path =~ /\Q$path_sep\E\z/) {
             $cut_chars += length($path_sep);
         }
     }
 
     my @res;
     for my $dir (@candidate_paths) {
         my $listres = $list_func->($dir, $leaf, 0);
         next unless $listres && @$listres;
         my $matches = Complete::Util::complete_array_elem(
             word => $leaf, array => $listres,
             ci=>$ci, fuzzy=>$fuzzy, map_case=>$map_case,
         );
 
       L1:
         for my $e (@$matches) {
             my $p = $dir =~ $re_ends_with_path_sep ?
                 "$dir$e" : "$dir$path_sep$e";
             {
                 local $_ = $p; 
                 next L1 if $filter_func && !$filter_func->($p);
             }
 
             my $is_dir;
             if ($e =~ $re_ends_with_path_sep) {
                 $is_dir = 1;
             } else {
                 local $_ = $p; 
                 $is_dir = $is_dir_func->($p);
             }
 
             if ($is_dir && $dig_leaf) {
                 $p = _dig_leaf($p, $list_func, $is_dir_func, $path_sep);
                 if ($p =~ $re_ends_with_path_sep) {
                     $is_dir = 1;
                 } else {
                     local $_ = $p; 
                     $is_dir = $is_dir_func->($p);
                 }
             }
 
             my $p0 = $p;
             substr($p, 0, $cut_chars) = '' if $cut_chars;
             $p = "$result_prefix$p" if length($result_prefix);
             unless ($p =~ /\Q$path_sep\E\z/) {
                 $p .= $path_sep if $is_dir;
             }
             push @res, $p;
         }
     }
 
     \@res;
 }
 1;
 
 __END__
 
### Complete/Setting.pm ###
 package Complete::Setting;
 
 our $DATE = '2015-09-16'; 
 our $VERSION = '0.16'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 our $OPT_CI          = ($ENV{COMPLETE_OPT_CI}          // 1) ? 1:0;
 our $OPT_FUZZY       = ($ENV{COMPLETE_OPT_FUZZY}       // 1)+0;
 our $OPT_MAP_CASE    = ($ENV{COMPLETE_OPT_MAP_CASE}    // 1) ? 1:0;
 our $OPT_EXP_IM_PATH = ($ENV{COMPLETE_OPT_EXP_IM_PATH} // 1) ? 1:0;
 our $OPT_DIG_LEAF    = ($ENV{COMPLETE_OPT_DIG_LEAF}    // 1) ? 1:0;
 
 1;
 
 __END__
 
### Complete/Tcsh.pm ###
 package Complete::Tcsh;
 
 our $DATE = '2015-09-09'; 
 our $VERSION = '0.02'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        parse_cmdline
                        format_completion
                );
 
 require Complete::Bash;
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Completion module for tcsh shell',
 };
 
 $SPEC{parse_cmdline} = {
     v => 1.1,
     summary => 'Parse shell command-line for processing by completion routines',
     description => <<'_',
 
 This function converts COMMAND_LINE (str) given by tcsh to become something like
 COMP_WORDS (array) and COMP_CWORD (int), like what bash supplies to shell
 functions. Currently implemented using `Complete::Bash`'s `parse_cmdline`.
 
 _
     args_as => 'array',
     args => {
         cmdline => {
             summary => 'Command-line, defaults to COMMAND_LINE environment',
             schema => 'str*',
             pos => 0,
         },
     },
     result => {
         schema => ['array*', len=>2],
         description => <<'_',
 
 Return a 2-element array: `[$words, $cword]`. `$words` is array of str,
 equivalent to `COMP_WORDS` provided by bash to shell functions. `$cword` is an
 integer, equivalent to `COMP_CWORD` provided by bash to shell functions. The
 word to be completed is at `$words->[$cword]`.
 
 Note that COMP_LINE includes the command name. If you want the command-line
 arguments only (like in `@ARGV`), you need to strip the first element from
 `$words` and reduce `$cword` by 1.
 
 _
     },
     result_naked => 1,
 };
 sub parse_cmdline {
     my ($line) = @_;
 
     $line //= $ENV{COMMAND_LINE};
     Complete::Bash::parse_cmdline($line, length($line));
 }
 
 $SPEC{format_completion} = {
     v => 1.1,
     summary => 'Format completion for output (for shell)',
     description => <<'_',
 
 tcsh accepts completion reply in the form of one entry per line to STDOUT.
 Currently the formatting is done using `Complete::Bash`'s `format_completion`
 because escaping rule and so on are not yet well defined in tcsh.
 
 _
     args_as => 'array',
     args => {
         completion => {
             summary => 'Completion answer structure',
             description => <<'_',
 
 Either an array or hash, as described in `Complete`.
 
 _
             schema=>['any*' => of => ['hash*', 'array*']],
             req=>1,
             pos=>0,
         },
     },
     result => {
         summary => 'Formatted string (or array, if `as` is set to `array`)',
         schema => ['any*' => of => ['str*', 'array*']],
     },
     result_naked => 1,
 };
 sub format_completion {
     Complete::Bash::format_completion(@_);
 }
 
 1;
 
 __END__
 
### Complete/Util.pm ###
 package Complete::Util;
 
 our $DATE = '2015-09-18'; 
 our $VERSION = '0.38'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Complete::Setting;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        hashify_answer
                        arrayify_answer
                        combine_answers
                        complete_array_elem
                        complete_hash_key
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'General completion routine',
 };
 
 $SPEC{hashify_answer} = {
     v => 1.1,
     summary => 'Make sure we return completion answer in hash form',
     description => <<'_',
 
 This function accepts a hash or an array. If it receives an array, will convert
 the array into `{words=>$ary}' first to make sure the completion answer is in
 hash form.
 
 Then will add keys from `meta` to the hash.
 
 _
     args => {
         arg => {
             summary => '',
             schema  => ['any*' => of => ['array*','hash*']],
             req => 1,
             pos => 0,
         },
         meta => {
             summary => 'Metadata (extra keys) for the hash',
             schema  => 'hash*',
             pos => 1,
         },
     },
     result_naked => 1,
     result => {
         schema => 'hash*',
     },
 };
 sub hashify_answer {
     my $ans = shift;
     if (ref($ans) ne 'HASH') {
         $ans = {words=>$ans};
     }
     if (@_) {
         my $meta = shift;
         for (keys %$meta) {
             $ans->{$_} = $meta->{$_};
         }
     }
     $ans;
 }
 
 $SPEC{arrayify_answer} = {
     v => 1.1,
     summary => 'Make sure we return completion answer in array form',
     description => <<'_',
 
 This is the reverse of `hashify_answer`. It accepts a hash or an array. If it
 receives a hash, will return its `words` key.
 
 _
     args => {
         arg => {
             summary => '',
             schema  => ['any*' => of => ['array*','hash*']],
             req => 1,
             pos => 0,
         },
     },
     result_naked => 1,
     result => {
         schema => 'array*',
     },
 };
 sub arrayify_answer {
     my $ans = shift;
     if (ref($ans) eq 'HASH') {
         $ans = $ans->{words};
     }
     $ans;
 }
 
 sub __min(@) {
     my $m = $_[0];
     for (@_) {
         $m = $_ if $_ < $m;
     }
     $m;
 }
 
 sub __editdist {
     my @a = split //, shift;
     my @b = split //, shift;
 
     my @d;
     $d[$_][0] = $_ for 0 .. @a;
     $d[0][$_] = $_ for 0 .. @b;
 
     for my $i (1 .. @a) {
         for my $j (1 .. @b) {
             $d[$i][$j] = (
                 $a[$i-1] eq $b[$j-1]
                     ? $d[$i-1][$j-1]
                     : 1 + __min(
                         $d[$i-1][$j],
                         $d[$i][$j-1],
                         $d[$i-1][$j-1]
                     )
                 );
         }
     }
 
     $d[@a][@b];
 }
 
 $SPEC{complete_array_elem} = {
     v => 1.1,
     summary => 'Complete from array',
     description => <<'_',
 
 Will sort the resulting completion list, so you don't have to presort the array.
 
 _
     args => {
         word     => { schema=>[str=>{default=>''}], pos=>0, req=>1 },
         array    => { schema=>['array*'=>{of=>'str*'}], req=>1 },
         ci       => { schema=>['bool'] },
         exclude  => { schema=>['array*'] },
         fuzzy    => { schema=>['int*', min=>0] },
         map_case => {
             summary => 'Treat _ (underscore) and - (dash) as the same',
             schema  => ['bool'],
         },
     },
     result_naked => 1,
     result => {
         schema => 'array',
     },
 };
 sub complete_array_elem {
     state $code_editdist = do {
         if (eval { require Text::Levenshtein::XS; 1 }) {
             \&Text::Levenshtein::XS::distance;
         } else {
             \&__editdist;
         }
     };
 
     my %args  = @_;
 
     my $array    = $args{array} or die "Please specify array";
     my $word     = $args{word} // "";
     my $ci       = $args{ci} // $Complete::Setting::OPT_CI;
     my $fuzzy    = $args{fuzzy} // $Complete::Setting::OPT_FUZZY;
     my $map_case = $args{map_case} // $Complete::Setting::OPT_MAP_CASE;
 
     return [] unless @$array;
 
     my $wordn = $ci ? uc($word) : $word; $wordn =~ s/_/-/g if $map_case;
 
     my @words;
     for my $el (@$array) {
         my $eln = $ci ? uc($el) : $el; $eln =~ s/_/-/g if $map_case;
         next unless 0==index($eln, $wordn);
         push @words, $el;
     }
 
     if ($fuzzy && !@words) {
         my $factor = 1.3;
         my $x = -1;
         my $y = 1;
 
         my %editdists;
       ELEM:
         for my $el (@$array) {
             my $eln = $ci ? uc($el) : $el; $eln =~ s/_/-/g if $map_case;
             for my $l (length($wordn)-$y .. length($wordn)+$y) {
                 next if $l <= 0;
                 my $chopped = substr($eln, 0, $l);
                 my $d;
                 unless (defined $editdists{$chopped}) {
                     $d = $code_editdist->($wordn, $chopped);
                     $editdists{$chopped} = $d;
                 } else {
                     $d = $editdists{$chopped};
                 }
                 my $maxd = __min(
                     __min(length($chopped), length($word))/$factor,
                     $fuzzy,
                 );
                 next unless $d <= $maxd;
                 push @words, $el;
                 next ELEM;
             }
         }
     }
 
     if ($args{exclude}) {
         my $exclude = $ci ? [map {uc} @{ $args{exclude} }] : $args{exclude};
         @words = grep {
             my $w = $_;
             !(grep {($ci ? uc($w) : $w) eq $_} @$exclude);
         } @words;
     }
 
     return $ci ? [sort {lc($a) cmp lc($b)} @words] : [sort @words];
 }
 
 $SPEC{complete_hash_key} = {
     v => 1.1,
     summary => 'Complete from hash keys',
     args => {
         word     => { schema=>[str=>{default=>''}], pos=>0, req=>1 },
         hash     => { schema=>['hash*'=>{}], req=>1 },
         ci       => { schema=>['bool'] },
         fuzzy    => { schema=>['int*', min=>0] },
         map_case => { schema=>['bool'] },
     },
     result_naked => 1,
     result => {
         schema => 'array',
     },
 };
 sub complete_hash_key {
     my %args  = @_;
     my $hash     = $args{hash} or die "Please specify hash";
     my $word     = $args{word} // "";
     my $ci       = $args{ci} // $Complete::Setting::OPT_CI;
     my $fuzzy    = $args{fuzzy} // $Complete::Setting::OPT_FUZZY;
     my $map_case = $args{map_case} // $Complete::Setting::OPT_MAP_CASE;
 
     my @array = $ci ?
         (sort {uc($a) cmp uc($b)} keys %$hash) : (sort keys %$hash);
     complete_array_elem(
         word=>$word, array=>\@array,
         ci=>$ci, fuzzy=>$fuzzy, map_case=>$map_case,
     );
 }
 
 $SPEC{combine_answers} = {
     v => 1.1,
     summary => 'Given two or more answers, combine them into one',
     description => <<'_',
 
 This function is useful if you want to provide a completion answer that is
 gathered from multiple sources. For example, say you are providing completion
 for the Perl tool `cpanm`, which accepts a filename (a tarball like `*.tar.gz`),
 a directory, or a module name. You can do something like this:
 
     combine_answers(
         complete_file(word=>$word, ci=>1),
         complete_module(word=>$word, ci=>1),
     );
 
 If a completion answer has a metadata `final` set to true, then that answer is
 used as the final answer without any combining with the other answers.
 
 _
     args => {
         answers => {
             schema => [
                 'array*' => {
                     of => ['any*', of=>['hash*','array*']], 
                     min_len => 1,
                 },
             ],
             req => 1,
             pos => 0,
             greedy => 1,
         },
     },
     args_as => 'array',
     result_naked => 1,
     result => {
         schema => 'hash*',
         description => <<'_',
 
 Return a combined completion answer. Words from each input answer will be
 combined, order preserved and duplicates removed. The other keys from each
 answer will be merged.
 
 _
     },
 };
 sub combine_answers {
     require List::Util;
 
     return undef unless @_;
     return $_[0] if @_ < 2;
 
     my $final = {words=>[]};
     my $encounter_hash;
     my $add_words = sub {
         my $words = shift;
         for my $entry (@$words) {
             push @{ $final->{words} }, $entry
                 unless List::Util::first(
                     sub {
                         (ref($entry) ? $entry->{word} : $entry)
                             eq
                                 (ref($_) ? $_->{word} : $_)
                             }, @{ $final->{words} }
                         );
         }
     };
 
   ANSWER:
     for my $ans (@_) {
         if (ref($ans) eq 'ARRAY') {
             $add_words->($ans);
         } elsif (ref($ans) eq 'HASH') {
             $encounter_hash++;
 
             if ($ans->{final}) {
                 $final = $ans;
                 last ANSWER;
             }
 
             $add_words->($ans->{words} // []);
             for (keys %$ans) {
                 if ($_ eq 'words') {
                     next;
                 } elsif ($_ eq 'static') {
                     if (exists $final->{$_}) {
                         $final->{$_} &&= $ans->{$_};
                     } else {
                         $final->{$_} = $ans->{$_};
                     }
                 } else {
                     $final->{$_} = $ans->{$_};
                 }
             }
         }
     }
 
     if ($final->{words}) {
         $final->{words} = [
             sort {
                 (ref($a) ? $a->{word} : $a) cmp
                     (ref($b) ? $b->{word} : $b);
             }
                 @{ $final->{words} }];
     }
 
     $encounter_hash ? $final : $final->{words};
 }
 
 1;
 
 __END__
 
### Data/Sah/Normalize.pm ###
 package Data::Sah::Normalize;
 
 use 5.010001;
 use strict;
 use warnings;
 
 our $DATE = '2015-09-06'; 
 our $VERSION = '0.04'; 
 
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT_OK = qw(
                        normalize_clset
                        normalize_schema
 
                        $type_re
                        $clause_name_re
                        $clause_re
                        $attr_re
                        $funcset_re
                        $compiler_re
                );
 
 our $type_re        = qr/\A(?:[A-Za-z_]\w*::)*[A-Za-z_]\w*\z/;
 our $clause_name_re = qr/\A[A-Za-z_]\w*\z/;
 our $clause_re      = qr/\A[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\z/;
 our $attr_re        = $clause_re;
 our $funcset_re     = qr/\A(?:[A-Za-z_]\w*::)*[A-Za-z_]\w*\z/;
 our $compiler_re    = qr/\A[A-Za-z_]\w*\z/;
 our $clause_attr_on_empty_clause_re = qr/\A(?:\.[A-Za-z_]\w*)+\z/;
 
 sub normalize_clset($;$) {
     my ($clset0, $opts) = @_;
     $opts //= {};
 
     my $clset = {};
     for my $c (sort keys %$clset0) {
         my $c0 = $c;
 
         my $v = $clset0->{$c};
 
         my $expr;
         if ($c =~ s/=\z//) {
             $expr++;
             die "Conflict between '$c=' and '$c'" if exists $clset0->{$c};
             $clset->{"$c.is_expr"} = 1;
             }
 
         my $sc = "";
         my $cn;
         {
             my $errp = "Invalid clause name syntax '$c0'"; 
             if (!$expr && $c =~ s/\A!(?=.)//) {
                 die "$errp, syntax should be !CLAUSE"
                     unless $c =~ $clause_name_re;
                 $sc = "!";
             } elsif (!$expr && $c =~ s/(?<=.)\|\z//) {
                 die "$errp, syntax should be CLAUSE|"
                     unless $c =~ $clause_name_re;
                 $sc = "|";
             } elsif (!$expr && $c =~ s/(?<=.)\&\z//) {
                 die "$errp, syntax should be CLAUSE&"
                     unless $c =~ $clause_name_re;
                 $sc = "&";
             } elsif (!$expr && $c =~ /\A([^.]+)(?:\.(.+))?\((\w+)\)\z/) {
                 my ($c2, $a, $lang) = ($1, $2, $3);
                 die "$errp, syntax should be CLAUSE(LANG) or C.ATTR(LANG)"
                     unless $c2 =~ $clause_name_re &&
                         (!defined($a) || $a =~ $attr_re);
                 $sc = "(LANG)";
                 $cn = $c2 . (defined($a) ? ".$a" : "") . ".alt.lang.$lang";
             } elsif ($c !~ $clause_re &&
                          $c !~ $clause_attr_on_empty_clause_re) {
                 die "$errp, please use letter/digit/underscore only";
             }
         }
 
         if ($sc eq '!') {
             die "Conflict between clause shortcuts '!$c' and '$c'"
                 if exists $clset0->{$c};
             die "Conflict between clause shortcuts '!$c' and '$c|'"
                 if exists $clset0->{"$c|"};
             die "Conflict between clause shortcuts '!$c' and '$c&'"
                 if exists $clset0->{"$c&"};
             $clset->{$c} = $v;
             $clset->{"$c.op"} = "not";
         } elsif ($sc eq '&') {
             die "Conflict between clause shortcuts '$c&' and '$c'"
                 if exists $clset0->{$c};
             die "Conflict between clause shortcuts '$c&' and '$c|'"
                 if exists $clset0->{"$c|"};
             die "Clause 'c&' value must be an array"
                 unless ref($v) eq 'ARRAY';
             $clset->{$c} = $v;
             $clset->{"$c.op"} = "and";
         } elsif ($sc eq '|') {
             die "Conflict between clause shortcuts '$c|' and '$c'"
                 if exists $clset0->{$c};
             die "Clause 'c|' value must be an array"
                 unless ref($v) eq 'ARRAY';
             $clset->{$c} = $v;
             $clset->{"$c.op"} = "or";
         } elsif ($sc eq '(LANG)') {
             die "Conflict between clause '$c' and '$cn'"
                 if exists $clset0->{$cn};
             $clset->{$cn} = $v;
         } else {
             $clset->{$c} = $v;
         }
 
     }
     $clset->{req} = 1 if $opts->{has_req};
 
 
     $clset;
 }
 
 sub normalize_schema($) {
     my $s = shift;
 
     my $ref = ref($s);
     if (!defined($s)) {
 
         die "Schema is missing";
 
     } elsif (!$ref) {
 
         my $has_req = $s =~ s/\*\z//;
         $s =~ $type_re or die "Invalid type syntax $s, please use ".
             "letter/digit/underscore only";
         return [$s, $has_req ? {req=>1} : {}, {}];
 
     } elsif ($ref eq 'ARRAY') {
 
         my $t = $s->[0];
         my $has_req = $t && $t =~ s/\*\z//;
         if (!defined($t)) {
             die "For array form, at least 1 element is needed for type";
         } elsif (ref $t) {
             die "For array form, first element must be a string";
         }
         $t =~ $type_re or die "Invalid type syntax $s, please use ".
             "letter/digit/underscore only";
 
         my $clset0;
         my $extras;
         if (defined($s->[1])) {
             if (ref($s->[1]) eq 'HASH') {
                 $clset0 = $s->[1];
                 $extras = $s->[2];
                 die "For array form, there should not be more than 3 elements"
                     if @$s > 3;
             } else {
                 die "For array in the form of [t, c1=>1, ...], there must be ".
                     "3 elements (or 5, 7, ...)"
                         unless @$s % 2;
                 $clset0 = { @{$s}[1..@$s-1] };
             }
         } else {
             $clset0 = {};
         }
 
         my $clset = normalize_clset($clset0, {has_req=>$has_req});
         if (defined $extras) {
             die "For array form with 3 elements, extras must be hash"
                 unless ref($extras) eq 'HASH';
             die "'def' in extras must be a hash"
                 if exists $extras->{def} && ref($extras->{def}) ne 'HASH';
             return [$t, $clset, { %{$extras} }];
         } else {
             return [$t, $clset, {}];
         }
     }
 
     die "Schema must be a string or arrayref (not $ref)";
 }
 
 1;
 
 __END__
 
### Function/Fallback/CoreOrPP.pm ###
 package Function::Fallback::CoreOrPP;
 
 use 5.010001;
 use strict;
 use warnings;
 
 our $VERSION = '0.06'; 
 
 our $USE_NONCORE_XS_FIRST = 1;
 
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT_OK = qw(
                        clone
                        unbless
                        uniq
                );
 
 sub clone {
     my $data = shift;
     goto FALLBACK unless $USE_NONCORE_XS_FIRST;
     goto FALLBACK unless eval { require Data::Clone; 1 };
 
   STANDARD:
     return Data::Clone::clone($data);
 
   FALLBACK:
     require Clone::PP;
     return Clone::PP::clone($data);
 }
 
 sub _unbless_fallback {
     my $ref = shift;
 
     my $r = ref($ref);
     return $ref unless $r;
 
     my ($r2, $r3) = "$ref" =~ /(.+)=(.+?)\(/
         or return $ref;
 
     if ($r3 eq 'HASH') {
         return { %$ref };
     } elsif ($r3 eq 'ARRAY') {
         return [ @$ref ];
     } elsif ($r3 eq 'SCALAR') {
         return \( my $copy = ${$ref} );
     } else {
         die "Can't handle $ref";
     }
 }
 
 sub unbless {
     my $ref = shift;
 
     goto FALLBACK unless $USE_NONCORE_XS_FIRST;
     goto FALLBACK unless eval { require Acme::Damn; 1 };
 
   STANDARD:
     return Acme::Damn::damn($ref);
 
   FALLBACK:
     return _unbless_fallback($ref);
 }
 
 sub uniq {
     goto FALLBACK unless $USE_NONCORE_XS_FIRST;
     goto FALLBACK unless eval { require List::MoreUtils; 1 };
 
   STANDARD:
     return List::MoreUtils::uniq(@_);
 
   FALLBACK:
     my %h;
     my @res;
     for (@_) {
         push @res, $_ unless $h{$_}++;
     }
     return @res;
 }
 
 1;
 
 __END__
 
### Getopt/Long/Negate/EN.pm ###
 package Getopt::Long::Negate::EN;
 
 our $DATE = '2015-03-19'; 
 our $VERSION = '0.01'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Exporter qw(import);
 our @EXPORT_OK = qw(negations_for_option);
 
 sub negations_for_option {
     my $word = shift;
     if    ($word =~ /\Awith([_-].+)/   ) { return ("without$1") }
     elsif ($word =~ /\Awithout([_-].+)/) { return ("with$1")    }
     elsif ($word =~ /\Ais([_-].+)/     ) { return ("isnt$1")    }
     elsif ($word =~ /\Aisnt([_-].+)/   ) { return ("is$1")      }
     elsif ($word =~ /\Aare([_-].+)/    ) { return ("arent$1")   }
     elsif ($word =~ /\Aarent([_-].+)/  ) { return ("are$1")     }
     elsif ($word =~ /\Ano[_-](.+)/     ) { return ($1)          }
     else {
         return ("no-$word", "no$word");
     }
 }
 
 1;
 
 __END__
 
### Getopt/Long/Util.pm ###
 package Getopt::Long::Util;
 
 our $DATE = '2015-06-11'; 
 our $VERSION = '0.83'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT_OK = qw(
                        parse_getopt_long_opt_spec
                        humanize_getopt_long_opt_spec
                        detect_getopt_long_script
                );
 
 our %SPEC;
 
 $SPEC{parse_getopt_long_opt_spec} = {
     v => 1.1,
     summary => 'Parse a single Getopt::Long option specification',
     description => <<'_',
 
 Will produce a hash with some keys: `opts` (array of option names, in the order
 specified in the opt spec), `type` (string, type name), `desttype` (either '',
 or '@' or '%'), `is_neg` (true for `--opt!`), `is_inc` (true for `--opt+`),
 `min_vals` (int, usually 0 or 1), `max_vals` (int, usually 0 or 1 except for
 option that requires multiple values),
 
 Will return undef if it can't parse the string.
 
 _
     args => {
         optspec => {
             schema => 'str*',
             req => 1,
             pos => 0,
         },
     },
     args_as => 'array',
     result_naked => 1,
     result => {
         schema => 'hash*',
     },
     examples => [
         {
             args => {optspec => 'help|h|?'},
             result => {dash_prefix=>'', opts=>['help', 'h', '?']},
         },
         {
             args => {optspec=>'--foo=s'},
             result => {dash_prefix=>'--', opts=>['foo'], type=>'s', desttype=>''},
         },
     ],
 };
 sub parse_getopt_long_opt_spec {
     my $optspec = shift;
     $optspec =~ qr/\A
                (?P<dash_prefix>-{0,2})
                (?P<name>[A-Za-z0-9_][A-Za-z0-9_-]*)
                (?P<aliases> (?: \| (?:[^:|!+=:-][^:|!+=:]*) )*)?
                (?:
                    (?P<is_neg>!) |
                    (?P<is_inc>\+) |
                    (?:
                        =
                        (?P<type>[siof])
                        (?P<desttype>|[%@])?
                        (?:
                            \{
                            (?: (?P<min_vals>\d+), )?
                            (?P<max_vals>\d+)
                            \}
                        )?
                    ) |
                    (?:
                        :
                        (?P<opttype>[siof])
                        (?P<desttype>|[%@])
                    ) |
                    (?:
                        :
                        (?P<optnum>\d+)
                        (?P<desttype>|[%@])
                    )
                    (?:
                        :
                        (?P<optplus>\+)
                        (?P<desttype>|[%@])
                    )
                )?
                \z/x
                    or return undef;
     my %res = %+;
 
     if ($res{aliases}) {
         my @als;
         for my $al (split /\|/, $res{aliases}) {
             next unless length $al;
             next if $al eq $res{name};
             next if grep {$_ eq $al} @als;
             push @als, $al;
         }
         $res{opts} = [$res{name}, @als];
     } else {
         $res{opts} = [$res{name}];
     }
     delete $res{name};
     delete $res{aliases};
 
     $res{is_neg} = 1 if $res{is_neg};
     $res{is_inc} = 1 if $res{is_inc};
 
     \%res;
 }
 
 $SPEC{humanize_getopt_long_opt_spec} = {
     v => 1.1,
     description => <<'_',
 
 Convert `Getopt::Long` option specification like `help|h|?` or `--foo=s` or
 `debug!` into, respectively, `--help, -h, -?` or `--foo=s` or `--(no)debug`.
 Will die if can't parse the string. The output is suitable for including in
 help/usage text.
 
 _
     args => {
         optspec => {
             schema => 'str*',
             req => 1,
             pos => 0,
         },
     },
     args_as => 'array',
     result_naked => 1,
     result => {
         schema => 'str*',
     },
 };
 sub humanize_getopt_long_opt_spec {
     my $optspec = shift;
 
     my $parse = parse_getopt_long_opt_spec($optspec)
         or die "Can't parse opt spec $optspec";
 
     my $res = '';
     my $i = 0;
     for (@{ $parse->{opts} }) {
         $i++;
         $res .= ", " if length($res);
         if ($parse->{is_neg} && length($_) > 1) {
             $res .= "--(no)$_";
         } else {
             if (length($_) > 1) {
                 $res .= "--$_";
             } else {
                 $res .= "-$_";
             }
             $res .= "=$parse->{type}" if $i==1 && $parse->{type};
         }
     }
     $res;
 }
 
 $SPEC{detect_getopt_long_script} = {
     v => 1.1,
     summary => 'Detect whether a file is a Getopt::Long-based CLI script',
     description => <<'_',
 
 The criteria are:
 
 * the file must exist and readable;
 
 * (optional, if `include_noexec` is false) file must have its executable mode
   bit set;
 
 * content must start with a shebang C<#!>;
 
 * either: must be perl script (shebang line contains 'perl') and must contain
   something like `use Getopt::Long`;
 
 _
     args => {
         filename => {
             summary => 'Path to file to be checked',
             schema => 'str*',
             description => <<'_',
 
 Either `filename` or `string` must be specified.
 
 _
         },
         string => {
             summary => 'Path to file to be checked',
             schema => 'buf*',
             description => <<'_',
 
 Either `file` or `string` must be specified.
 
 _
         },
         include_noexec => {
             summary => 'Include scripts that do not have +x mode bit set',
             schema  => 'bool*',
             default => 1,
         },
     },
 };
 sub detect_getopt_long_script {
     my %args = @_;
 
     (defined($args{filename}) xor defined($args{string}))
         or return [400, "Please specify either filename or string"];
     my $include_noexec  = $args{include_noexec}  // 1;
 
     my $yesno = 0;
     my $reason = "";
 
     my $str = $args{string};
   DETECT:
     {
         if (defined $args{filename}) {
             my $fn = $args{filename};
             unless (-f $fn) {
                 $reason = "'$fn' is not a file";
                 last;
             };
             if (!$include_noexec && !(-x _)) {
                 $reason = "'$fn' is not an executable";
                 last;
             }
             my $fh;
             unless (open $fh, "<", $fn) {
                 $reason = "Can't be read";
                 last;
             }
             read $fh, $str, 2;
             unless ($str eq '#!') {
                 $reason = "Does not start with a shebang (#!) sequence";
                 last;
             }
             my $shebang = <$fh>;
             unless ($shebang =~ /perl/) {
                 $reason = "Does not have 'perl' in the shebang line";
                 last;
             }
             seek $fh, 0, 0;
             {
                 local $/;
                 $str = <$fh>;
             }
         }
         unless ($str =~ /\A#!/) {
             $reason = "Does not start with a shebang (#!) sequence";
             last;
         }
         unless ($str =~ /\A#!.*perl/) {
             $reason = "Does not have 'perl' in the shebang line";
             last;
         }
         if ($str =~ /^\s*(use|require)\s+Getopt::Long(\s|;)/m) {
             $yesno = 1;
             last DETECT;
         }
         $reason = "Can't find any statement requiring Getopt::Long module";
     } 
 
     [200, "OK", $yesno, {"func.reason"=>$reason}];
 }
 
 
 __END__
 
### Lingua/EN/PluralToSingular.pm ###
 package Lingua::EN::PluralToSingular;
 require Exporter;
 @ISA = qw(Exporter);
 @EXPORT_OK = qw/to_singular is_plural/;
 use warnings;
 use strict;
 our $VERSION = '0.14';
 
 
 
 
 my %irregular = (qw/
     analyses analysis
     children child
     corpora corpus
     craftsmen craftsman
     crises crisis
     criteria criterion
     curricula curriculum
     feet foot
     fungi fungus
     geese goose
     genera genus
     indices index
     lice louse
     matrices matrix
     memoranda memorandum
     men man
     mice mouse
     monies money
     neuroses neurosis
     nuclei nucleus
     oases oasis
     pence penny
     people person
     phenomena phenomenon
     quanta quantum
     strata stratum
     teeth tooth
     testes testis
     these this
     theses thesis
     those that
     women woman
 /);
 
 
 
 my %ves = (qw/
     calves calf
     dwarves dwarf
     elves elf
     halves half
     knives knife
     leaves leaf
     lives life
     loaves loaf
     scarves scarf
     sheaves sheaf
     shelves shelf
     wharves wharf 
     wives wife
     wolves wolf
 /);
 
 
 my %plural = (
     'menus' => 'menu',
     'buses' => 'bus',
     %ves,
     %irregular,
 );
 
 
 my @no_change = qw/
                       clothes
                       deer
                       ides
                       fish
                       means
                       offspring
                       series
                       sheep
                       species
                   /;
 
 @plural{@no_change} = @no_change;
 
 
 
 
 my @not_plural = (qw/
     Charles
     Texas
 Hades 
 Hercules 
 Hermes 
 Gonzales 
 Holmes 
 Hughes 
 Ives 
 Jacques 
 James 
 Keyes 
 Mercedes 
 Naples 
 Oates 
 Raines 
 
     dias
     iris
     molasses
     this
     yes
     chaos
     lens
     corps
     mews
     news
 
     athletics
     mathematics
     physics
     metaphysics
 
 
     bogus
     bus
     cactus
     citrus
     corpus
     hippopotamus
     homunculus
     minus
     narcissus
     octopus
     papyrus
     platypus
     plus
     pus
     stylus
     various
     previous
     devious
     metropolis
     miscellaneous
     perhaps
     thus
     famous
     mrs
 sometimes
 
 ourselves
 themselves
 cannabis
 /);
 
 my %not_plural;
 
 @not_plural{@not_plural} = (1) x @not_plural;
 
 
 
 my @oes = (qw/
 		 foes
 		 shoes
                  hoes
 		 throes
                  toes
 		 oboes
              /);
 
 my %oes;
 
 @oes{@oes} = (1) x @oes;
 
 
 
 my @ies = (qw/
 calories
 genies
 lies
 movies
 neckties
 pies
 ties
 /);
 
 my %ies;
 
 @ies{@ies} = (1) x @ies;
 
 
 my @ses = (qw/
 horses
 tenses
 /);
 
 my %ses;
 @ses{@ses} = (1) x @ses;
 
 
 my $es_re = qr/([^aeiou]s|ch|sh)es$/;
 
 
 sub to_singular
 {
     my ($word) = @_;
     my $singular = $word;
     if (! $not_plural{$word}) {
         if ($plural{$word}) {
             $singular = $plural{$word};
         }
         elsif ($word =~ /s$/) {
 	    if ($word =~ /'s$/) {
 		;
 	    }
 	    elsif (length ($word) <= 2) {
 		;
 	    }
 	    elsif ($word =~ /ss$/) {
 		;
 	    }
 	    elsif ($word =~ /sis$/) {
 		;
 	    }
             elsif ($word =~ /ies$/) {
                 if ($ies{$word}) {
                     $singular =~ s/ies$/ie/;
                 }
                 else {
                     $singular =~ s/ies$/y/;
                 }
             }
             elsif ($word =~ /oes$/) {
                 if ($oes{$word}) {
                     $singular =~ s/oes$/oe/;
                 }
                 else {
                     $singular =~ s/oes$/o/;
                 }
             }
             elsif ($word =~ /xes$/) {
 		$singular =~ s/xes$/x/;
             }
 	    elsif ($word =~ /ses$/) {
 		if ($ses{$word}) {
 		    $singular =~ s/ses$/se/;
 		}
 		else {
 		    $singular =~ s/ses$/s/;
 		}
 	    }
             elsif ($word =~ $es_re) {
                 $singular =~ s/$es_re/$1/;
             }
             else {
                 $singular =~ s/s$//;
             }
         }
     }            
     return $singular;
 }
 
 sub is_plural
 {
     my ($word) = @_;
     my $singular = to_singular ($word);
     my $is_plural;
     if ($singular ne $word) {
 	$is_plural = 1;
     }
     elsif ($plural{$singular} && $plural{$singular} eq $singular) {
 	$is_plural = 1;
     }
     else {
 	$is_plural = 0;
     }
     return $is_plural;
 }
 
 1;
 
### Log/Any.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any;
 
 our $VERSION = '1.032';
 
 use Log::Any::Manager;
 use Log::Any::Adapter::Util qw(
   require_dynamic
   detection_aliases
   detection_methods
   log_level_aliases
   logging_aliases
   logging_and_detection_methods
   logging_methods
 );
 
 our $OverrideDefaultAdapterClass;
 our $OverrideDefaultProxyClass;
 
 {
     my $manager = Log::Any::Manager->new();
     sub _manager { return $manager }
 }
 
 sub import {
     my $class  = shift;
     my $caller = caller();
 
     my @export_params = ( $caller, @_ );
     $class->_export_to_caller(@export_params);
 }
 
 sub _export_to_caller {
     my $class  = shift;
     my $caller = shift;
 
     my $saw_log_param;
     my @params;
     while ( my $param = shift @_ ) {
         if ( $param eq '$log' ) {
             $saw_log_param = 1;    
             next;                  
         }
         else {
             push @params, $param, shift @_;    
         }
     }
 
     unless ( @params % 2 == 0 ) {
         require Carp;
         Carp::croak("Argument list not balanced: @params");
     }
 
     if ($saw_log_param) {
         no strict 'refs';
         my $proxy = $class->get_logger( category => $caller, @params );
         my $varname = "$caller\::log";
         *$varname = \$proxy;
     }
 }
 
 sub get_logger {
     my ( $class, %params ) = @_;
     no warnings 'once';
 
     my $proxy_class = $class->_get_proxy_class( delete $params{proxy_class} );
     my $category =
       defined $params{category} ? delete $params{'category'} : caller;
 
     if ( my $default = delete $params{'default_adapter'} ) {
         $class->_manager->set_default( $category, $default );
     }
 
     my $adapter = $class->_manager->get_adapter( $category );
 
     require_dynamic($proxy_class);
     return $proxy_class->new(
         %params, adapter => $adapter, category => $category,
     );
 }
 
 sub _get_proxy_class {
     my ( $self, $proxy_name ) = @_;
     return $Log::Any::OverrideDefaultProxyClass
       if $Log::Any::OverrideDefaultProxyClass;
     return "Log::Any::Proxy" unless $proxy_name;
     my $proxy_class = (
           substr( $proxy_name, 0, 1 ) eq '+'
         ? substr( $proxy_name, 1 )
         : "Log::Any::Proxy::$proxy_name"
     );
     return $proxy_class;
 }
 
 sub set_adapter {
     my $class = shift;
     Log::Any->_manager->set(@_);
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter;
 
 our $VERSION = '1.032';
 
 use Log::Any;
 
 sub import {
     my $pkg = shift;
     Log::Any->_manager->set(@_) if (@_);
 }
 
 sub set {
     my $pkg = shift;
     Log::Any->_manager->set(@_)
 }
 
 sub remove {
     my $pkg = shift;
     Log::Any->_manager->remove(@_)
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter/Base.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Base;
 
 our $VERSION = '1.032';
 
 use Log::Any::Adapter::Util qw/make_method dump_one_line/;
 
 sub new {
     my $class = shift;
     my $self  = {@_};
     bless $self, $class;
     $self->init(@_);
     return $self;
 }
 
 sub init { }
 
 for my $method ( Log::Any::Adapter::Util::logging_and_detection_methods() ) {
     no strict 'refs';
     *$method = sub {
         my $class = ref( $_[0] ) || $_[0];
         die "$class does not implement $method";
     };
 }
 
 sub delegate_method_to_slot {
     my ( $class, $slot, $method, $adapter_method ) = @_;
 
     make_method( $method,
         sub { my $self = shift; return $self->{$slot}->$adapter_method(@_) },
         $class );
 }
 
 1;
### Log/Any/Adapter/File.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::File;
 
 our $VERSION = '1.032';
 
 use Config;
 use Fcntl qw/:flock/;
 use IO::File;
 use Log::Any::Adapter::Util ();
 
 use base qw/Log::Any::Adapter::Base/;
 
 my $HAS_FLOCK = $Config{d_flock} || $Config{d_fcntl_can_lock} || $Config{d_lockf};
 
 my $trace_level = Log::Any::Adapter::Util::numeric_level('trace');
 sub new {
     my ( $class, $file, @args ) = @_;
     return $class->SUPER::new( file => $file, log_level => $trace_level, @args );
 }
 
 sub init {
     my $self = shift;
     if ( exists $self->{log_level} ) {
         $self->{log_level} = Log::Any::Adapter::Util::numeric_level( $self->{log_level} )
             unless $self->{log_level} =~ /^\d+$/;
     }
     else {
         $self->{log_level} = $trace_level;
     }
     my $file = $self->{file};
     open( $self->{fh}, ">>", $file )
       or die "cannot open '$file' for append: $!";
     $self->{fh}->autoflush(1);
 }
 
 foreach my $method ( Log::Any::Adapter::Util::logging_methods() ) {
     no strict 'refs';
     my $method_level = Log::Any::Adapter::Util::numeric_level( $method );
     *{$method} = sub {
         my ( $self, $text ) = @_;
         return if $method_level > $self->{log_level};
         my $msg = sprintf( "[%s] %s\n", scalar(localtime), $text );
         flock($self->{fh}, LOCK_EX) if $HAS_FLOCK;
         $self->{fh}->print($msg);
         flock($self->{fh}, LOCK_UN) if $HAS_FLOCK;
       }
 }
 
 foreach my $method ( Log::Any::Adapter::Util::detection_methods() ) {
     no strict 'refs';
     my $base = substr($method,3);
     my $method_level = Log::Any::Adapter::Util::numeric_level( $base );
     *{$method} = sub {
         return !!(  $method_level <= $_[0]->{log_level} );
     };
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter/Null.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Null;
 
 our $VERSION = '1.032';
 
 use base qw/Log::Any::Adapter::Base/;
 
 use Log::Any::Adapter::Util ();
 
 
 foreach my $method (Log::Any::Adapter::Util::logging_and_detection_methods()) {
     no strict 'refs';
     *{$method} = sub { return '' }; 
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter/Stderr.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Stderr;
 
 our $VERSION = '1.032';
 
 use Log::Any::Adapter::Util ();
 
 use base qw/Log::Any::Adapter::Base/;
 
 my $trace_level = Log::Any::Adapter::Util::numeric_level('trace');
 
 sub init {
     my ($self) = @_;
     if ( exists $self->{log_level} ) {
         $self->{log_level} =
           Log::Any::Adapter::Util::numeric_level( $self->{log_level} )
           unless $self->{log_level} =~ /^\d+$/;
     }
     else {
         $self->{log_level} = $trace_level;
     }
 }
 
 foreach my $method ( Log::Any::Adapter::Util::logging_methods() ) {
     no strict 'refs';
     my $method_level = Log::Any::Adapter::Util::numeric_level($method);
     *{$method} = sub {
         my ( $self, $text ) = @_;
         return if $method_level > $self->{log_level};
         print STDERR "$text\n";
     };
 }
 
 foreach my $method ( Log::Any::Adapter::Util::detection_methods() ) {
     no strict 'refs';
     my $base = substr( $method, 3 );
     my $method_level = Log::Any::Adapter::Util::numeric_level($base);
     *{$method} = sub {
         return !!( $method_level <= $_[0]->{log_level} );
     };
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter/Stdout.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Stdout;
 
 our $VERSION = '1.032';
 
 use Log::Any::Adapter::Util ();
 
 use base qw/Log::Any::Adapter::Base/;
 
 my $trace_level = Log::Any::Adapter::Util::numeric_level('trace');
 
 sub init {
     my ($self) = @_;
     if ( exists $self->{log_level} ) {
         $self->{log_level} =
           Log::Any::Adapter::Util::numeric_level( $self->{log_level} )
           unless $self->{log_level} =~ /^\d+$/;
     }
     else {
         $self->{log_level} = $trace_level;
     }
 }
 
 foreach my $method ( Log::Any::Adapter::Util::logging_methods() ) {
     no strict 'refs';
     my $method_level = Log::Any::Adapter::Util::numeric_level($method);
     *{$method} = sub {
         my ( $self, $text ) = @_;
         return if $method_level > $self->{log_level};
         print STDOUT "$text\n";
     };
 }
 
 foreach my $method ( Log::Any::Adapter::Util::detection_methods() ) {
     no strict 'refs';
     my $base = substr( $method, 3 );
     my $method_level = Log::Any::Adapter::Util::numeric_level($base);
     *{$method} = sub {
         return !!( $method_level <= $_[0]->{log_level} );
     };
 }
 
 1;
 
 __END__
 
### Log/Any/Adapter/Test.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Test;
 
 our $VERSION = '1.032';
 
 use Log::Any::Adapter::Util qw/dump_one_line/;
 use Test::Builder;
 
 use base qw/Log::Any::Adapter::Base/;
 
 my $tb = Test::Builder->new();
 my @msgs;
 
 
 sub new {
     my $class = shift;
     if ( defined $Log::Any::OverrideDefaultAdapterClass
         && $Log::Any::OverrideDefaultAdapterClass eq __PACKAGE__ )
     {
         my $category = pop @_;
         return $class->SUPER::new( category => $category );
     }
     else {
         return $class->SUPER::new(@_);
     }
 }
 
 foreach my $method ( Log::Any::Adapter::Util::detection_methods() ) {
     no strict 'refs';
     *{$method} = sub { 1 };
 }
 
 foreach my $method ( Log::Any::Adapter::Util::logging_methods() ) {
     no strict 'refs';
     *{$method} = sub {
         my ( $self, $msg ) = @_;
         push(
             @msgs,
             {
                 message  => $msg,
                 level    => $method,
                 category => $self->{category}
             }
         );
     };
 }
 
 
 sub msgs {
     my $self = shift;
 
     return \@msgs;
 }
 
 sub clear {
     my ($self) = @_;
 
     @msgs = ();
 }
 
 sub contains_ok {
     my ( $self, $regex, $test_name ) = @_;
 
     $test_name ||= "log contains '$regex'";
     my $found =
       _first_index( sub { $_->{message} =~ /$regex/ }, @{ $self->msgs } );
     if ( $found != -1 ) {
         splice( @{ $self->msgs }, $found, 1 );
         $tb->ok( 1, $test_name );
     }
     else {
         $tb->ok( 0, $test_name );
         $tb->diag( "could not find message matching $regex; log contains: "
               . $self->dump_one_line( $self->msgs ) );
     }
 }
 
 sub category_contains_ok {
     my ( $self, $category, $regex, $test_name ) = @_;
 
     $test_name ||= "log for $category contains '$regex'";
     my $found =
       _first_index(
         sub { $_->{category} eq $category && $_->{message} =~ /$regex/ },
         @{ $self->msgs } );
     if ( $found != -1 ) {
         splice( @{ $self->msgs }, $found, 1 );
         $tb->ok( 1, $test_name );
     }
     else {
         $tb->ok( 0, $test_name );
         $tb->diag(
             "could not find $category message matching $regex; log contains: "
               . $self->dump_one_line( $self->msgs ) );
     }
 }
 
 sub does_not_contain_ok {
     my ( $self, $regex, $test_name ) = @_;
 
     $test_name ||= "log does not contain '$regex'";
     my $found =
       _first_index( sub { $_->{message} =~ /$regex/ }, @{ $self->msgs } );
     if ( $found != -1 ) {
         $tb->ok( 0, $test_name );
         $tb->diag( "found message matching $regex: " . $self->msgs->[$found] );
     }
     else {
         $tb->ok( 1, $test_name );
     }
 }
 
 sub category_does_not_contain_ok {
     my ( $self, $category, $regex, $test_name ) = @_;
 
     $test_name ||= "log for $category contains '$regex'";
     my $found =
       _first_index(
         sub { $_->{category} eq $category && $_->{message} =~ /$regex/ },
         @{ $self->msgs } );
     if ( $found != -1 ) {
         $tb->ok( 0, $test_name );
         $tb->diag( "found $category message matching $regex: "
               . $self->msgs->[$found] );
     }
     else {
         $tb->ok( 1, $test_name );
     }
 }
 
 sub empty_ok {
     my ( $self, $test_name ) = @_;
 
     $test_name ||= "log is empty";
     if ( !@{ $self->msgs } ) {
         $tb->ok( 1, $test_name );
     }
     else {
         $tb->ok( 0, $test_name );
         $tb->diag( "log is not empty; contains "
               . $self->dump_one_line( $self->msgs ) );
         $self->clear();
     }
 }
 
 sub contains_only_ok {
     my ( $self, $regex, $test_name ) = @_;
 
     $test_name ||= "log contains only '$regex'";
     my $count = scalar( @{ $self->msgs } );
     if ( $count == 1 ) {
         local $Test::Builder::Level = $Test::Builder::Level + 1;
         $self->contains_ok( $regex, $test_name );
     }
     else {
         $tb->ok( 0, $test_name );
         $tb->diag( "log contains $count messages: "
               . $self->dump_one_line( $self->msgs ) );
     }
 }
 
 sub _first_index {
     my $f = shift;
     for my $i ( 0 .. $#_ ) {
         local *_ = \$_[$i];
         return $i if $f->();
     }
     return -1;
 }
 
 1;
### Log/Any/Adapter/Util.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Adapter::Util;
 
 our $VERSION = '1.032';
 
 use Data::Dumper;
 use base qw(Exporter);
 
 my %LOG_LEVELS;
 BEGIN {
     %LOG_LEVELS = (
         EMERGENCY => 0,
         ALERT     => 1,
         CRITICAL  => 2,
         ERROR     => 3,
         WARNING   => 4,
         NOTICE    => 5,
         INFO      => 6,
         DEBUG     => 7,
         TRACE     => 8,
     );
 }
 
 use constant \%LOG_LEVELS;
 
 our @EXPORT_OK = qw(
   cmp_deeply
   detection_aliases
   detection_methods
   dump_one_line
   log_level_aliases
   logging_aliases
   logging_and_detection_methods
   logging_methods
   make_method
   numeric_level
   read_file
   require_dynamic
 );
 
 push @EXPORT_OK, keys %LOG_LEVELS;
 
 our %EXPORT_TAGS = ( 'levels' => [ keys %LOG_LEVELS ] );
 
 my ( %LOG_LEVEL_ALIASES, @logging_methods, @logging_aliases, @detection_methods,
     @detection_aliases, @logging_and_detection_methods );
 
 BEGIN {
     %LOG_LEVEL_ALIASES = (
         inform => 'info',
         warn   => 'warning',
         err    => 'error',
         crit   => 'critical',
         fatal  => 'critical'
     );
     @logging_methods =
       qw(trace debug info notice warning error critical alert emergency);
     @logging_aliases               = keys(%LOG_LEVEL_ALIASES);
     @detection_methods             = map { "is_$_" } @logging_methods;
     @detection_aliases             = map { "is_$_" } @logging_aliases;
     @logging_and_detection_methods = ( @logging_methods, @detection_methods );
 }
 
 
 sub logging_methods               { @logging_methods }
 
 
 sub detection_methods             { @detection_methods }
 
 
 sub logging_and_detection_methods { @logging_and_detection_methods }
 
 
 sub log_level_aliases             { %LOG_LEVEL_ALIASES }
 
 
 sub logging_aliases               { @logging_aliases }
 
 
 sub detection_aliases             { @detection_aliases }
 
 
 sub numeric_level {
     my ($level) = @_;
     my $canonical =
       exists $LOG_LEVEL_ALIASES{$level} ? $LOG_LEVEL_ALIASES{$level} : $level;
     return $LOG_LEVELS{ uc($canonical) };
 }
 
 
 sub dump_one_line {
     my ($value) = @_;
 
     return Data::Dumper->new( [$value] )->Indent(0)->Sortkeys(1)->Quotekeys(0)
       ->Terse(1)->Dump();
 }
 
 
 sub make_method {
     my ( $method, $code, $pkg ) = @_;
 
     $pkg ||= caller();
     no strict 'refs';
     *{ $pkg . "::$method" } = $code;
 }
 
 
 sub require_dynamic {
     my ($class) = @_;
 
     return 1 if $class->can('new'); 
 
     unless ( defined( eval "require $class; 1" ) )
     {    
         die $@;
     }
 }
 
 
 sub read_file {
     my ($file) = @_;
 
     local $/ = undef;
     open( my $fh, '<', $file )
       or die "cannot open '$file': $!";
     my $contents = <$fh>;
     return $contents;
 }
 
 
 sub cmp_deeply {
     my ( $ref1, $ref2, $name ) = @_;
 
     my $tb = Test::Builder->new();
     $tb->is_eq( dump_one_line($ref1), dump_one_line($ref2), $name );
 }
 
 require Log::Any;
 
 1;
 
 
 
 __END__
 
### Log/Any/IfLOG.pm ###
 package Log::Any::IfLOG;
 
 our $DATE = '2015-08-17'; 
 our $VERSION = '0.07'; 
 
 our $DEBUG;
 our $ENABLE_LOG;
 
 my $log_singleton;
 sub __log_singleton {
     if (!$log_singleton) { $log_singleton = Object::Dumb->new }
     $log_singleton;
 }
 
 sub __log_enabled {
     if (defined $ENABLE_LOG) {
         return $ENABLE_LOG;
     } elsif ($INC{'Log/Any.pm'}) {
         return 1;
     } else {
         return
             $ENV{LOG} || $ENV{TRACE} || $ENV{DEBUG} ||
             $ENV{VERBOSE} || $ENV{QUIET} || $ENV{LOG_LEVEL};
     }
 }
 
 sub import {
     my $self = shift;
 
     my $caller = caller();
     if (__log_enabled()) {
         require Log::Any;
         Log::Any->_export_to_caller($caller, @_);
     } else {
         my $saw_log_param = grep { $_ eq '$log' } @_;
         if ($saw_log_param) {
             __log_singleton(); 
             *{"$caller\::log"} = \$log_singleton;
         }
     }
 }
 
 sub get_logger {
     if (__log_enabled()) {
         require Log::Any;
         my $class = shift;
         if ($class eq 'Log::Any::IfLOG') {
             Log::Any->get_logger(@_);
         } else {
             Log::Any::get_logger($class, @_);
         }
     } else {
         return __log_singleton();
     }
 }
 
 package
     Object::Dumb;
 sub new { my $o = ""; bless \$o, shift }
 sub AUTOLOAD { 0 }
 
 1;
 
 __END__
 
### Log/Any/Manager.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Manager;
 
 our $VERSION = '1.032';
 
 sub new {
     my $class = shift;
     my $self  = {
         entries         => [],
         category_cache  => {},
         default_adapter => {},
     };
     bless $self, $class;
 
     return $self;
 }
 
 sub get_adapter {
     my ( $self, $category ) = @_;
 
     my $category_cache = $self->{category_cache};
     if ( !defined( $category_cache->{$category} ) ) {
         my $entry = $self->_choose_entry_for_category($category);
         my $adapter = $self->_new_adapter_for_entry( $entry, $category );
         $category_cache->{$category} = { entry => $entry, adapter => $adapter };
     }
     return $category_cache->{$category}->{adapter};
 }
 
 {
     no warnings 'once';
     *get_logger = \&get_adapter;    
 }
 
 sub _choose_entry_for_category {
     my ( $self, $category ) = @_;
 
     foreach my $entry ( @{ $self->{entries} } ) {
         if ( $category =~ $entry->{pattern} ) {
             return $entry;
         }
     }
     my $default = $self->{default_adapter}{$category}
         || [ $self->_get_adapter_class("Null"), [] ];
     my ($adapter_class, $adapter_params) = @$default;
     _require_dynamic($adapter_class);
     return {
         adapter_class  => $adapter_class,
         adapter_params => $adapter_params,
     };
 }
 
 sub _new_adapter_for_entry {
     my ( $self, $entry, $category ) = @_;
 
     return $entry->{adapter_class}
       ->new( @{ $entry->{adapter_params} }, category => $category );
 }
 
 sub set_default {
     my ( $self, $category, $adapter_name, @adapter_params ) = @_;
     my $adapter_class = $self->_get_adapter_class($adapter_name);
     $self->{default_adapter}{$category} = [$adapter_class, \@adapter_params];
 }
 
 sub set {
     my $self = shift;
     my $options;
     if ( ref( $_[0] ) eq 'HASH' ) {
         $options = shift(@_);
     }
     my ( $adapter_name, @adapter_params ) = @_;
 
     unless ( defined($adapter_name) && $adapter_name =~ /\S/ ) {
         require Carp;
         Carp::croak("expected adapter name");
     }
 
     my $pattern = $options->{category};
     if ( !defined($pattern) ) {
         $pattern = qr/.*/;
     }
     elsif ( !ref($pattern) ) {
         $pattern = qr/^\Q$pattern\E$/;
     }
 
     my $adapter_class = $self->_get_adapter_class($adapter_name);
     _require_dynamic($adapter_class);
 
     my $entry = $self->_new_entry( $pattern, $adapter_class, \@adapter_params );
     unshift( @{ $self->{entries} }, $entry );
 
     $self->_reselect_matching_adapters($pattern);
 
     if ( my $lex_ref = $options->{lexically} ) {
         $$lex_ref = Log::Any::Manager::_Guard->new(
             sub { $self->remove($entry) unless _in_global_destruction() } );
     }
 
     return $entry;
 }
 
 sub remove {
     my ( $self, $entry ) = @_;
 
     my $pattern = $entry->{pattern};
     $self->{entries} = [ grep { $_ ne $entry } @{ $self->{entries} } ];
     $self->_reselect_matching_adapters($pattern);
 }
 
 sub _new_entry {
     my ( $self, $pattern, $adapter_class, $adapter_params ) = @_;
 
     return {
         pattern        => $pattern,
         adapter_class  => $adapter_class,
         adapter_params => $adapter_params,
     };
 }
 
 sub _reselect_matching_adapters {
     my ( $self, $pattern ) = @_;
 
     return if _in_global_destruction();
 
     while ( my ( $category, $category_info ) =
         each( %{ $self->{category_cache} } ) )
     {
         my $new_entry = $self->_choose_entry_for_category($category);
         if ( $new_entry ne $category_info->{entry} ) {
             my $new_adapter =
               $self->_new_adapter_for_entry( $new_entry, $category );
             %{ $category_info->{adapter} } = %$new_adapter;
             bless( $category_info->{adapter}, ref($new_adapter) );
             $category_info->{entry} = $new_entry;
         }
     }
 }
 
 sub _get_adapter_class {
     my ( $self, $adapter_name ) = @_;
     return $Log::Any::OverrideDefaultAdapterClass if $Log::Any::OverrideDefaultAdapterClass;
     $adapter_name =~ s/^Log:://;    
     my $adapter_class = (
           substr( $adapter_name, 0, 1 ) eq '+'
         ? substr( $adapter_name, 1 )
         : "Log::Any::Adapter::$adapter_name"
     );
     return $adapter_class;
 }
 
 if ( defined ${^GLOBAL_PHASE} ) {
     eval 'sub _in_global_destruction () { ${^GLOBAL_PHASE} eq q[DESTRUCT] }; 1' 
       or die $@;
 }
 else {
     require B;
     my $started = !B::main_start()->isa(q[B::NULL]);
     unless ($started) {
         eval '0 && $started; CHECK { $started = 1 }; 1' 
           or die $@;
     }
     eval 
       '0 && $started; sub _in_global_destruction () { $started && B::main_start()->isa(q[B::NULL]) }; 1'
       or die $@;
 }
 
 sub _require_dynamic {
     my ($class) = @_;
 
     return 1 if $class->can('new'); 
 
     unless ( defined( eval "require $class; 1" ) )
     {    
         die $@;
     }
 }
 
 package    
   Log::Any::Manager::_Guard;
 
 sub new { bless $_[1], $_[0] }
 
 sub DESTROY { $_[0]->() }
 
 1;
### Log/Any/Proxy.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Proxy;
 
 our $VERSION = '1.032';
 
 use Log::Any::Adapter::Util ();
 
 sub _default_formatter {
     my ( $cat, $lvl, $format, @params ) = @_;
     my @new_params =
       map { !defined($_) ? '<undef>' : ref($_) ? _dump_one_line($_) : $_ }
       @params;
     return sprintf( $format, @new_params );
 }
 
 sub _dump_one_line {
     my ($value) = @_;
 
     return Data::Dumper->new( [$value] )->Indent(0)->Sortkeys(1)->Quotekeys(0)
       ->Terse(1)->Useqq(1)->Dump();
 }
 
 sub new {
     my $class = shift;
     my $self = { formatter => \&_default_formatter, @_ };
     unless ( $self->{adapter} ) {
         require Carp;
         Carp::croak("$class requires an 'adapter' parameter");
     }
     unless ( $self->{category} ) {
         require Carp;
         Carp::croak("$class requires an 'category' parameter")
     }
     bless $self, $class;
     $self->init(@_);
     return $self;
 }
 
 sub init { }
 
 for my $attr (qw/adapter filter formatter prefix/) {
     no strict 'refs';
     *{$attr} = sub { return $_[0]->{$attr} };
 }
 
 my %aliases = Log::Any::Adapter::Util::log_level_aliases();
 
 foreach my $name ( Log::Any::Adapter::Util::logging_methods(), keys(%aliases) )
 {
     my $realname    = $aliases{$name} || $name;
     my $namef       = $name . "f";
     my $is_name     = "is_$name";
     my $is_realname = "is_$realname";
     my $numeric     = Log::Any::Adapter::Util::numeric_level($realname);
     no strict 'refs';
     *{$is_name} = sub {
         my ($self) = @_;
         return $self->{adapter}->$is_realname;
     };
     *{$name} = sub {
         my ( $self, @parts ) = @_;
         my $message = join(" ", grep { defined($_) && length($_) } @parts );
         return unless length $message;
         $message = $self->{filter}->( $self->{category}, $numeric, $message )
           if defined $self->{filter};
         return unless defined $message and length $message;
         $message = "$self->{prefix}$message"
           if defined $self->{prefix} && length $self->{prefix};
         return $self->{adapter}->$realname($message);
     };
     *{$namef} = sub {
         my ( $self, @args ) = @_;
         return unless $self->{adapter}->$is_realname;
         my $message =
           $self->{formatter}->( $self->{category}, $numeric, @args );
         return unless defined $message and length $message;
         return $self->$name($message);
     };
 }
 
 1;
 
 
 
 __END__
 
### Log/Any/Proxy/Test.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Proxy::Test;
 
 our $VERSION = '1.032';
 
 use base qw/Log::Any::Proxy/;
 
 my @test_methods = qw(
   msgs
   clear
   contains_ok
   category_contains_ok
   does_not_contain_ok
   category_does_not_contain_ok
   empty_ok
   contains_only_ok
 );
 
 foreach my $name (@test_methods) {
     no strict 'refs';
     *{$name} = sub {
         my $self = shift;
         $self->{adapter}->$name(@_);
     };
 }
 
 1;
### Log/Any/Test.pm ###
 use 5.008001;
 use strict;
 use warnings;
 
 package Log::Any::Test;
 
 our $VERSION = '1.032';
 
 no warnings 'once';
 $Log::Any::OverrideDefaultAdapterClass = 'Log::Any::Adapter::Test';
 $Log::Any::OverrideDefaultProxyClass   = 'Log::Any::Proxy::Test';
 
 1;
 
 __END__
 
### Module/Path/More.pm ###
 package Module::Path::More;
 
 our $DATE = '2015-09-03'; 
 our $VERSION = '0.29'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT_OK = qw(module_path pod_path);
 
 my $SEPARATOR;
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Get path to locally installed Perl module',
 };
 
 BEGIN {
     if ($^O =~ /^(dos|os2)/i) {
         $SEPARATOR = '\\';
     } elsif ($^O =~ /^MacOS/i) {
         $SEPARATOR = ':';
     } else {
         $SEPARATOR = '/';
     }
 }
 
 $SPEC{module_path} = {
     v => 1.1,
     summary => 'Get path to locally installed Perl module',
     description => <<'_',
 
 Search `@INC` (reference entries are skipped) and return path(s) to Perl module
 files with the requested name.
 
 This function is like the one from `Module::Path`, except with a different
 interface and more options (finding all matches instead of the first, the option
 of not absolutizing paths, finding `.pmc` & `.pod` files, finding module
 prefixes).
 
 _
     args => {
         module => {
             summary => 'Module name to search',
             schema  => 'str*',
             req     => 1,
             pos     => 0,
         },
         find_pm => {
             summary => 'Whether to find .pm files',
             schema  => 'bool',
             default => 1,
         },
         find_pmc => {
             summary => 'Whether to find .pmc files',
             schema  => 'bool',
             default => 1,
         },
         find_pod => {
             summary => 'Whether to find .pod files',
             schema  => 'bool',
             default => 0,
         },
         find_prefix => {
             summary => 'Whether to find module prefixes',
             schema  => 'bool',
             default => 0,
         },
         all => {
             summary => 'Return all results instead of just the first',
             schema  => 'bool',
             default => 0,
         },
         abs => {
             summary => 'Whether to return absolute paths',
             schema  => 'bool',
             default => 0,
         },
     },
     result => {
         schema => ['any' => of => ['str*', ['array*' => of => 'str*']]],
     },
     result_naked => 1,
 };
 sub module_path {
     my %args = @_;
 
     my $module = $args{module} or die "Please specify module";
 
     $args{abs}         //= 0;
     $args{all}         //= 0;
     $args{find_pm}     //= 1;
     $args{find_pmc}    //= 1;
     $args{find_pod}    //= 0;
     $args{find_prefix} //= 0;
 
     require Cwd if $args{abs};
 
     my @res;
     my $add = sub { push @res, $args{abs} ? Cwd::abs_path($_[0]) : $_[0] };
 
     my $relpath;
 
     ($relpath = $module) =~ s/::/$SEPARATOR/g;
     $relpath =~ s/\.(pm|pmc|pod)\z//i;
 
     foreach my $dir (@INC) {
         next if not defined($dir);
         next if ref($dir);
 
         my $prefix = $dir . $SEPARATOR . $relpath;
         if ($args{find_pmc}) {
             my $file = $prefix . ".pmc";
             if (-f $file) {
                 $add->($file);
                 last unless $args{all};
             }
         }
         if ($args{find_pm}) {
             my $file = $prefix . ".pm";
             if (-f $file) {
                 $add->($file);
                 last unless $args{all};
             }
         }
         if ($args{find_pod}) {
             my $file = $prefix . ".pod";
             if (-f $file) {
                 $add->($file);
                 last unless $args{all};
             }
         }
         if ($args{find_prefix}) {
             if (-d $prefix) {
                 $add->($prefix);
                 last unless $args{all};
             }
         }
     }
 
     if ($args{all}) {
         return \@res;
     } else {
         return @res ? $res[0] : undef;
     }
 }
 
 $SPEC{pod_path} = {
     v => 1.1,
     summary => 'Get path to locally installed POD',
     description => <<'_',
 
 This is a shortcut for:
 
     module_path(%args, find_pm=>0, find_pmc=>0, find_pod=>1, find_prefix=>0)
 
 _
     args => {
         module => {
             summary => 'Module name to search',
             schema  => 'str*',
             req     => 1,
             pos     => 0,
         },
         all => {
             summary => 'Return all results instead of just the first',
             schema  => 'bool',
             default => 0,
         },
         abs => {
             summary => 'Whether to return absolute paths',
             schema  => 'bool',
             default => 0,
         },
     },
     result => {
         schema => ['any' => of => ['str*', ['array*' => of => 'str*']]],
     },
     result_naked => 1,
 };
 sub pod_path {
     module_path(@_, find_pm=>0, find_pmc=>0, find_pod=>1, find_prefix=>0);
 }
 
 1;
 
 __END__
 
### Perinci/Sub/Complete.pm ###
 package Perinci::Sub::Complete;
 
 our $DATE = '2015-09-09'; 
 our $VERSION = '0.82'; 
 
 use 5.010001;
 use strict;
 use warnings;
 use Log::Any::IfLOG '$log';
 
 use Complete::Util qw(hashify_answer complete_array_elem combine_answers);
 use Complete::Setting;
 use Perinci::Sub::Util qw(gen_modified_sub);
 
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT_OK = qw(
                        complete_from_schema
                        complete_arg_val
                        complete_arg_elem
                        complete_cli_arg
                );
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Complete command-line argument using Rinci metadata',
 };
 
 my %common_args_riap = (
     riap_client => {
         summary => 'Optional, to perform complete_arg_val to the server',
         schema  => 'obj*',
         description => <<'_',
 
 When the argument spec in the Rinci metadata contains `completion` key, this
 means there is custom completion code for that argument. However, if retrieved
 from a remote server, sometimes the `completion` key no longer contains the code
 (it has been cleansed into a string). Moreover, the completion code needs to run
 on the server.
 
 If supplied this argument and te `riap_server_url` argument, the function will
 try to request to the server (via Riap request `complete_arg_val`). Otherwise,
 the function will just give up/decline completing.
 
 _
         },
     riap_server_url => {
         summary => 'Optional, to perform complete_arg_val to the server',
         schema  => 'str*',
         description => <<'_',
 
 See the `riap_client` argument.
 
 _
     },
     riap_uri => {
         summary => 'Optional, to perform complete_arg_val to the server',
         schema  => 'str*',
         description => <<'_',
 
 See the `riap_client` argument.
 
 _
     },
 );
 
 $SPEC{complete_from_schema} = {
     v => 1.1,
     summary => 'Complete a value from schema',
     description => <<'_',
 
 Employ some heuristics to complete a value from Sah schema. For example, if
 schema is `[str => in => [qw/new open resolved rejected/]]`, then we can
 complete from the `in` clause. Or for something like `[int => between => [1,
 20]]` we can complete using values from 1 to 20.
 
 _
     args => {
         schema => {
             summary => 'Must be normalized',
             req => 1,
         },
         word => {
             schema => [str => default => ''],
             req => 1,
         },
         ci => {
             schema => 'bool',
         },
     },
 };
 sub complete_from_schema {
     my %args = @_;
     my $sch  = $args{schema}; 
     my $word = $args{word} // "";
     my $ci   = $args{ci} // $Complete::Setting::OPT_CI;
 
     my $fres;
     $log->tracef("[comp][periscomp] entering complete_from_schema, word=<%s>, schema=%s", $word, $sch);
 
     my ($type, $cs) = @{$sch};
 
     my $static;
     my $words;
     eval {
         if ($cs->{is} && !ref($cs->{is})) {
             $log->tracef("[comp][periscomp] adding completion from 'is' clause");
             push @$words, $cs->{is};
             $static++;
             return; 
         }
         if ($cs->{in}) {
             $log->tracef("[comp][periscomp] adding completion from 'in' clause");
             push @$words, grep {!ref($_)} @{ $cs->{in} };
             $static++;
             return; 
         }
         if ($type eq 'any') {
             require Data::Sah::Normalize;
             if ($cs->{of} && @{ $cs->{of} }) {
                 $fres = combine_answers(
                     grep { defined } map {
                         complete_from_schema(
                             schema=>Data::Sah::Normalize::normalize_schema($_),
                             word => $word,
                             ci => $ci,
                         )
                     } @{ $cs->{of} }
                 );
                 goto RETURN_RES; 
             }
         }
         if ($type eq 'bool') {
             $log->tracef("[comp][periscomp] adding completion from possible values of bool");
             push @$words, 0, 1;
             $static++;
             return; 
         }
         if ($type eq 'int') {
             my $limit = 100;
             if ($cs->{between} &&
                     $cs->{between}[0] - $cs->{between}[0] <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'between' clause");
                 push @$words, $cs->{between}[0] .. $cs->{between}[1];
                 $static++;
             } elsif ($cs->{xbetween} &&
                          $cs->{xbetween}[0] - $cs->{xbetween}[0] <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'xbetween' clause");
                 push @$words, $cs->{xbetween}[0]+1 .. $cs->{xbetween}[1]-1;
                 $static++;
             } elsif (defined($cs->{min}) && defined($cs->{max}) &&
                          $cs->{max}-$cs->{min} <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'min' & 'max' clauses");
                 push @$words, $cs->{min} .. $cs->{max};
                 $static++;
             } elsif (defined($cs->{min}) && defined($cs->{xmax}) &&
                          $cs->{xmax}-$cs->{min} <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'min' & 'xmax' clauses");
                 push @$words, $cs->{min} .. $cs->{xmax}-1;
                 $static++;
             } elsif (defined($cs->{xmin}) && defined($cs->{max}) &&
                          $cs->{max}-$cs->{xmin} <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'xmin' & 'max' clauses");
                 push @$words, $cs->{xmin}+1 .. $cs->{max};
                 $static++;
             } elsif (defined($cs->{xmin}) && defined($cs->{xmax}) &&
                          $cs->{xmax}-$cs->{xmin} <= $limit) {
                 $log->tracef("[comp][periscomp] adding completion from 'xmin' & 'xmax' clauses");
                 push @$words, $cs->{xmin}+1 .. $cs->{xmax}-1;
                 $static++;
             } elsif (length($word) && $word !~ /\A-?\d*\z/) {
                 $log->tracef("[comp][periscomp] word not an int");
                 $words = [];
             } else {
                 $words = [];
                 for my $sign ("", "-") {
                     for ("", 0..9) {
                         my $i = $sign . $word . $_;
                         next unless length $i;
                         next unless $i =~ /\A-?\d+\z/;
                         next if $i eq '-0';
                         next if $i =~ /\A-?0\d/;
                         next if $cs->{between} &&
                             ($i < $cs->{between}[0] ||
                                  $i > $cs->{between}[1]);
                         next if $cs->{xbetween} &&
                             ($i <= $cs->{xbetween}[0] ||
                                  $i >= $cs->{xbetween}[1]);
                         next if defined($cs->{min} ) && $i <  $cs->{min};
                         next if defined($cs->{xmin}) && $i <= $cs->{xmin};
                         next if defined($cs->{max} ) && $i >  $cs->{max};
                         next if defined($cs->{xmin}) && $i >= $cs->{xmax};
                         push @$words, $i;
                     }
                 }
                 $words = [sort @$words];
             }
             return; 
         }
         if ($type eq 'float') {
             if (length($word) && $word !~ /\A-?\d*(\.\d*)?\z/) {
                 $log->tracef("[comp][periscomp] word not a float");
                 $words = [];
             } else {
                 $words = [];
                 for my $sig ("", "-") {
                     for ("", 0..9,
                          ".0",".1",".2",".3",".4",".5",".6",".7",".8",".9") {
                         my $f = $sig . $word . $_;
                         next unless length $f;
                         next unless $f =~ /\A-?\d+(\.\d+)?\z/;
                         next if $f eq '-0';
                         next if $f =~ /\A-?0\d\z/;
                         next if $cs->{between} &&
                             ($f < $cs->{between}[0] ||
                                  $f > $cs->{between}[1]);
                         next if $cs->{xbetween} &&
                             ($f <= $cs->{xbetween}[0] ||
                                  $f >= $cs->{xbetween}[1]);
                         next if defined($cs->{min} ) && $f <  $cs->{min};
                         next if defined($cs->{xmin}) && $f <= $cs->{xmin};
                         next if defined($cs->{max} ) && $f >  $cs->{max};
                         next if defined($cs->{xmin}) && $f >= $cs->{xmax};
                         push @$words, $f;
                     }
                 }
             }
             return; 
         }
     }; 
 
     $log->tracef("[periscomp] complete_from_schema died: %s", $@) if $@;
 
     goto RETURN_RES unless $words;
     $fres = hashify_answer(
         complete_array_elem(array=>$words, word=>$word, ci=>$ci),
         {static=>$static && $word eq '' ? 1:0},
     );
 
   RETURN_RES:
     $log->tracef("[comp][periscomp] leaving complete_from_schema, result=%s", $fres);
     $fres;
 }
 
 $SPEC{complete_arg_val} = {
     v => 1.1,
     summary => 'Given argument name and function metadata, complete value',
     description => <<'_',
 
 Will attempt to complete using the completion routine specified in the argument
 specification (the `completion` property, or in the case of `complete_arg_elem`
 function, the `element_completion` property), or if that is not specified, from
 argument's schema using `complete_from_schema`.
 
 Completion routine will get `%args`, with the following keys:
 
 * `word` (str, the word to be completed)
 * `ci` (bool, whether string matching should be case-insensitive)
 * `arg` (str, the argument name which value is currently being completed)
 * `index (int, only for the `complete_arg_elem` function, the index in the
    argument array that is currently being completed, starts from 0)
 * `args` (hash, the argument hash to the function, so far)
 
 as well as extra keys from `extras` (but these won't overwrite the above
 standard keys).
 
 Completion routine should return a completion answer structure (described in
 `Complete`) which is either a hash or an array. The simplest form of answer is
 just to return an array of strings. Completion routine can also return undef to
 express declination.
 
 _
     args => {
         meta => {
             summary => 'Rinci function metadata, must be normalized',
             schema => 'hash*',
             req => 1,
         },
         arg => {
             summary => 'Argument name',
             schema => 'str*',
             req => 1,
         },
         word => {
             summary => 'Word to be completed',
             schema => ['str*', default => ''],
         },
         ci => {
             summary => 'Whether to be case-insensitive',
             schema => ['bool*'],
         },
         args => {
             summary => 'Collected arguments so far, '.
                 'will be passed to completion routines',
             schema  => 'hash',
         },
         extras => {
             summary => 'Add extra arguments to completion routine',
             schema  => 'hash',
             description => <<'_',
 
 The keys from this `extras` hash will be merged into the final `%args` passed to
 completion routines. Note that standard keys like `word`, `cword`, `ci`, and so
 on as described in the function description will not be overwritten by this.
 
 _
         },
 
         %common_args_riap,
     },
     result_naked => 1,
     result => {
         schema => 'array', 
     },
 };
 sub complete_arg_val {
     my %args = @_;
 
     $log->tracef("[comp][periscomp] entering complete_arg_val, arg=<%s>", $args{arg});
     my $fres;
 
     my $extras = $args{extras} // {};
 
     my $meta = $args{meta} or do {
         $log->tracef("[comp][periscomp] meta is not supplied, declining");
         goto RETURN_RES;
     };
     my $arg  = $args{arg} or do {
         $log->tracef("[comp][periscomp] arg is not supplied, declining");
         goto RETURN_RES;
     };
     my $ci   = $args{ci} // $Complete::Setting::OPT_CI;
     my $word = $args{word} // '';
 
 
     my $args_prop = $meta->{args} // {};
     my $arg_spec = $args_prop->{$arg} or do {
         $log->tracef("[comp][periscomp] arg '$arg' is not specified in meta, declining");
         goto RETURN_RES;
     };
 
     my $static;
     eval { 
 
         my $comp;
       GET_COMP_ROUTINE:
         {
             $comp = $arg_spec->{completion};
             if ($comp) {
                 $log->tracef("[comp][periscomp] using arg completion routine from 'completion' property");
                 last GET_COMP_ROUTINE;
             }
             my $xcomp = $arg_spec->{'x.completion'};
             if ($xcomp) {
                 require Module::Path::More;
                 my $mod = "Perinci::Sub::XCompletion::$xcomp->[0]";
                 if (Module::Path::More::module_path(module=>$mod)) {
                     $log->tracef("[comp][periscomp] loading module %s ...", $mod);
                     my $mod_pm = $mod; $mod_pm =~ s!::!/!g; $mod_pm .= ".pm";
                     require $mod_pm;
                     my $fref = \&{"$mod\::gen_completion"};
                     $comp = $fref->(%{ $xcomp->[1] });
                 }
                 if ($comp) {
                     $log->tracef("[comp][periscomp] using arg completion routine from 'x.completion' attribute");
                     last GET_COMP_ROUTINE;
                 }
             }
             my $ent = $arg_spec->{'x.schema.entity'};
             if ($ent) {
                 require Module::Path::More;
                 my $mod = "Perinci::Sub::ArgEntity::$ent";
                 if (Module::Path::More::module_path(module=>$mod)) {
                     $log->tracef("[comp][periscomp] loading module %s ...", $mod);
                     my $mod_pm = $mod; $mod_pm =~ s!::!/!g; $mod_pm .= ".pm";
                     require $mod_pm;
                     if (defined &{"$mod\::complete_arg_val"}) {
                         $log->tracef("[comp][periscomp] using arg completion routine from complete_arg_val() from %s", $mod);
                         $comp = \&{"$mod\::complete_arg_val"};
                         last GET_COMP_ROUTINE;
                     }
                 }
             }
         } 
 
         if ($comp) {
             if (ref($comp) eq 'CODE') {
                 $log->tracef("[comp][periscomp] invoking arg completion routine");
                 $fres = $comp->(
                     %$extras,
                     word=>$word, ci=>$ci, arg=>$arg, args=>$args{args});
                 return; 
             } elsif (ref($comp) eq 'ARRAY') {
                 $log->tracef("[comp][periscomp] using array specified in arg completion routine: %s", $comp);
                 $fres = complete_array_elem(
                     array=>$comp, word=>$word, ci=>$ci);
                 $static++;
                 return; 
             }
 
             $log->tracef("[comp][periscomp] arg spec's 'completion' property is not a coderef or arrayref");
             if ($args{riap_client} && $args{riap_server_url}) {
                 $log->tracef("[comp][periscomp] trying to perform complete_arg_val request to Riap server");
                 my $res = $args{riap_client}->request(
                     complete_arg_val => $args{riap_server_url},
                     {(uri=>$args{riap_uri}) x !!defined($args{riap_uri}),
                      arg=>$arg, word=>$word, ci=>$ci},
                 );
                 if ($res->[0] != 200) {
                     $log->tracef("[comp][periscomp] Riap request failed (%s), declining", $res);
                     return; 
                 }
                 $fres = $res->[2];
                 return; 
             }
 
             $log->tracef("[comp][periscomp] declining");
             return; 
         }
 
         my $sch = $arg_spec->{schema};
         unless ($sch) {
             $log->tracef("[comp][periscomp] arg spec does not specify schema, declining");
             return; 
         };
 
 
         $fres = complete_from_schema(schema=>$sch, word=>$word, ci=>$ci);
     };
     $log->debug("[comp][periscomp] completion died: $@") if $@;
     unless ($fres) {
         $log->tracef("[comp][periscomp] no completion from metadata possible, declining");
         goto RETURN_RES;
     }
 
     $fres = hashify_answer($fres);
     $fres->{static} //= $static && $word eq '' ? 1:0;
   RETURN_RES:
     $log->tracef("[comp][periscomp] leaving complete_arg_val, result=%s", $fres);
     $fres;
 }
 
 gen_modified_sub(
     output_name  => 'complete_arg_elem',
     install_sub  => 0,
     base_name    => 'complete_arg_val',
     summary      => 'Given argument name and function metadata, '.
         'complete array element',
     add_args     => {
         index => {
             summary => 'Index of element to complete',
             schema  => [int => min => 0],
         },
     },
 );
 sub complete_arg_elem {
     require Data::Sah::Normalize;
 
     my %args = @_;
 
     my $fres;
 
     $log->tracef("[comp][periscomp] entering complete_arg_elem, arg=<%s>, index=<%d>",
                  $args{arg}, $args{index});
 
     my $extras = $args{extras} // {};
 
     my $ourextras = {arg=>$args{arg}, args=>$args{args}};
 
     my $meta = $args{meta} or do {
         $log->tracef("[comp][periscomp] meta is not supplied, declining");
         goto RETURN_RES;
     };
     my $arg  = $args{arg} or do {
         $log->tracef("[comp][periscomp] arg is not supplied, declining");
         goto RETURN_RES;
     };
     defined(my $index = $args{index}) or do {
         $log->tracef("[comp][periscomp] index is not supplied, declining");
         goto RETURN_RES;
     };
     my $ci   = $args{ci} // $Complete::Setting::OPT_CI;
     my $word = $args{word} // '';
 
 
     my $args_prop = $meta->{args} // {};
     my $arg_spec = $args_prop->{$arg} or do {
         $log->tracef("[comp][periscomp] arg '$arg' is not specified in meta, declining");
         goto RETURN_RES;
     };
 
     my $static;
     eval { 
 
         my $elcomp;
       GET_ELCOMP_ROUTINE:
         {
             $elcomp = $arg_spec->{element_completion};
             if ($elcomp) {
                 $log->tracef("[comp][periscomp] using arg element completion routine from 'element_completion' property");
                 last GET_ELCOMP_ROUTINE;
             }
             my $xelcomp = $arg_spec->{'x.element_completion'};
             if ($xelcomp) {
                require Module::Path::More;
                 my $mod = "Perinci::Sub::XCompletion::$xelcomp->[0]";
                 if (Module::Path::More::module_path(module=>$mod)) {
                     $log->tracef("[comp][periscomp] loading module %s ...", $mod);
                     my $mod_pm = $mod; $mod_pm =~ s!::!/!g; $mod_pm .= ".pm";
                     require $mod_pm;
                     my $fref = \&{"$mod\::gen_completion"};
                     $elcomp = $fref->(%{ $xelcomp->[1] });
                 }
                 if ($elcomp) {
                     $log->tracef("[comp][periscomp] using arg element completion routine from 'x.element_completion' attribute");
                     last GET_ELCOMP_ROUTINE;
                 }
             }
             my $ent = $arg_spec->{'x.schema.element_entity'};
             if ($ent) {
                 require Module::Path::More;
                 my $mod = "Perinci::Sub::ArgEntity::$ent";
                 if (Module::Path::More::module_path(module=>$mod)) {
                     $log->tracef("[comp][periscomp] loading module %s ...", $mod);
                     my $mod_pm = $mod; $mod_pm =~ s!::!/!g; $mod_pm .= ".pm";
                     require $mod_pm;
                     if (defined &{"$mod\::complete_arg_val"}) {
                         $log->tracef("[comp][periscomp] using arg element completion routine from complete_arg_val() from %s", $mod);
                         $elcomp = \&{"$mod\::complete_arg_val"};
                         last GET_ELCOMP_ROUTINE;
                     }
                 }
             }
         } 
 
         $ourextras->{index} = $index;
         if ($elcomp) {
             if (ref($elcomp) eq 'CODE') {
                 $log->tracef("[comp][periscomp] invoking arg element completion routine");
                 $fres = $elcomp->(
                     %$extras,
                     %$ourextras,
                     word=>$word, ci=>$ci);
                 return; 
             } elsif (ref($elcomp) eq 'ARRAY') {
                 $log->tracef("[comp][periscomp] using array specified in arg element completion routine: %s", $elcomp);
                 $fres = complete_array_elem(
                     array=>$elcomp, word=>$word, ci=>$ci);
                 $static = $word eq '';
             }
 
             $log->tracef("[comp][periscomp] arg spec's 'element_completion' property is not a coderef or ".
                              "arrayref");
             if ($args{riap_client} && $args{riap_server_url}) {
                 $log->tracef("[comp][periscomp] trying to perform complete_arg_elem request to Riap server");
                 my $res = $args{riap_client}->request(
                     complete_arg_elem => $args{riap_server_url},
                     {(uri=>$args{riap_uri}) x !!defined($args{riap_uri}),
                      arg=>$arg, args=>$args{args}, word=>$word, ci=>$ci,
                      index=>$index},
                 );
                 if ($res->[0] != 200) {
                     $log->tracef("[comp][periscomp] Riap request failed (%s), declining", $res);
                     return; 
                 }
                 $fres = $res->[2];
                 return; 
             }
 
             $log->tracef("[comp][periscomp] declining");
             return; 
         }
 
         my $sch = $arg_spec->{schema};
         unless ($sch) {
             $log->tracef("[comp][periscomp] arg spec does not specify schema, declining");
             return; 
         };
 
 
         my ($type, $cs) = @{ $sch };
         if ($type ne 'array') {
             $log->tracef("[comp][periscomp] can't complete element for non-array");
             return; 
         }
 
         unless ($cs->{of}) {
             $log->tracef("[comp][periscomp] schema does not specify 'of' clause, declining");
             return; 
         }
 
         my $elsch = Data::Sah::Normalize::normalize_schema($cs->{of});
 
         $fres = complete_from_schema(schema=>$elsch, word=>$word, ci=>$ci);
     };
     $log->debug("[comp][periscomp] completion died: $@") if $@;
     unless ($fres) {
         $log->tracef("[comp][periscomp] no completion from metadata possible, declining");
         goto RETURN_RES;
     }
 
     $fres = hashify_answer($fres);
     $fres->{static} //= $static && $word eq '' ? 1:0;
   RETURN_RES:
     $log->tracef("[comp][periscomp] leaving complete_arg_elem, result=%s", $fres);
     $fres;
 }
 
 $SPEC{complete_cli_arg} = {
     v => 1.1,
     summary => 'Complete command-line argument using Rinci function metadata',
     description => <<'_',
 
 This routine uses `Perinci::Sub::GetArgs::Argv` to generate `Getopt::Long`
 specification from arguments list in Rinci function metadata and common options.
 Then, it will use `Complete::Getopt::Long` to complete option names, option
 values, as well as arguments.
 
 _
     args => {
         meta => {
             summary => 'Rinci function metadata',
             schema => 'hash*',
             req => 1,
         },
         words => {
             summary => 'Command-line arguments',
             schema => ['array*' => {of=>'str*'}],
             req => 1,
         },
         cword => {
             summary => 'On which argument cursor is located (zero-based)',
             schema => 'int*',
             req => 1,
         },
         completion => {
             summary => 'Supply custom completion routine',
             description => <<'_',
 
 If supplied, instead of the default completion routine, this code will be called
 instead. Will receive all arguments that `Complete::Getopt::Long` will pass, and
 additionally:
 
 * `arg` (str, the name of function argument)
 * `args` (hash, the function arguments formed so far)
 * `index` (int, if completing argument element value)
 
 _
             schema => 'code*',
         },
         per_arg_json => {
             summary => 'Will be passed to Perinci::Sub::GetArgs::Argv',
             schema  => 'bool',
         },
         per_arg_yaml => {
             summary => 'Will be passed to Perinci::Sub::GetArgs::Argv',
             schema  => 'bool',
         },
         common_opts => {
             summary => 'Common options',
             description => <<'_',
 
 A hash where the values are hashes containing these keys: `getopt` (Getopt::Long
 option specification), `handler` (Getopt::Long handler). Will be passed to
 `get_args_from_argv()`. Example:
 
     {
         help => {
             getopt  => 'help|h|?',
             handler => sub { ... },
             summary => 'Display help and exit',
         },
         version => {
             getopt  => 'version|v',
             handler => sub { ... },
             summary => 'Display version and exit',
         },
     }
 
 _
             schema => ['hash*'],
         },
         extras => {
             summary => 'Add extra arguments to completion routine',
             schema  => 'hash',
             description => <<'_',
 
 The keys from this `extras` hash will be merged into the final `%args` passed to
 completion routines. Note that standard keys like `word`, `cword`, `ci`, and so
 on as described in the function description will not be overwritten by this.
 
 _
         },
         func_arg_starts_at => {
             schema  => 'int*',
             default => 0,
             description => <<'_',
 
 This is a (temporary?) workaround for Perinci::CmdLine. In an application with
 subcommands (e.g. `cmd --verbose subcmd arg0 arg1 ...`), then `words` will still
 contain the subcommand name. Positional function arguments then start at 1 not
 0. This option allows offsetting function arguments.
 
 _
         },
         %common_args_riap,
     },
     result_naked => 1,
     result => {
         schema => 'hash*',
         description => <<'_',
 
 You can use `format_completion` function in `Complete::Bash` module to format
 the result of this function for bash.
 
 _
     },
 };
 sub complete_cli_arg {
     require Complete::Getopt::Long;
     require Perinci::Sub::GetArgs::Argv;
 
     my %args   = @_;
     my $meta   = $args{meta} or die "Please specify meta";
     my $words  = $args{words} or die "Please specify words";
     my $cword  = $args{cword}; defined($cword) or die "Please specify cword";
     my $copts  = $args{common_opts} // {};
     my $comp   = $args{completion};
     my $extras = {
         %{ $args{extras} // {} },
         words => $args{words},
         cword => $args{cword},
     };
 
     my $fname = __PACKAGE__ . "::complete_cli_arg"; 
     my $fres;
 
     my $word   = $words->[$cword];
     my $args_prop = $meta->{args} // {};
 
     $log->tracef('[comp][periscomp] entering %s(), words=%s, cword=%d, word=<%s>',
                  $fname, $words, $cword, $word);
 
     my $genres = Perinci::Sub::GetArgs::Argv::gen_getopt_long_spec_from_meta(
         meta         => $meta,
         common_opts  => $copts,
         per_arg_json => $args{per_arg_json},
         per_arg_yaml => $args{per_arg_yaml},
         ignore_converted_code => 1,
     );
     die "Can't generate getopt spec from meta: $genres->[0] - $genres->[1]"
         unless $genres->[0] == 200;
     my $gospec = $genres->[2];
     my $specmeta = $genres->[3]{'func.specmeta'};
 
     my $gares = Perinci::Sub::GetArgs::Argv::get_args_from_argv(
         argv   => [@$words],
         meta   => $meta,
         strict => 0,
     );
 
     my $copts_by_ospec = {};
     for (keys %$copts) { $copts_by_ospec->{$copts->{$_}{getopt}}=$copts->{$_} }
 
     my $compgl_comp = sub {
         $log->tracef("[comp][periscomp] entering completion routine (that we supply to Complete::Getopt::Long)");
         my %cargs = @_;
         my $type  = $cargs{type};
         my $ospec = $cargs{ospec} // '';
         my $word  = $cargs{word};
         my $ci    = $cargs{ci} // $Complete::Setting::OPT_CI;
 
         my $fres;
 
         my %rargs = (
             riap_server_url => $args{riap_server_url},
             riap_uri        => $args{riap_uri},
             riap_client     => $args{riap_client},
         );
 
         if (my $sm = $specmeta->{$ospec}) {
             $cargs{type} = 'optval';
             if ($sm->{arg}) {
                 $log->tracef("[comp][periscomp] completing option value for a known function argument, arg=<%s>, ospec=<%s>", $sm->{arg}, $ospec);
                 $cargs{arg} = $sm->{arg};
                 my $arg_spec = $args_prop->{$sm->{arg}} or goto RETURN_RES;
                 if ($comp) {
                     $log->tracef("[comp][periscomp] invoking routine supplied from 'completion' argument");
                     my $compres;
                     eval { $compres = $comp->(%cargs) };
                     $log->debug("[comp][periscomp] completion died: $@") if $@;
                     $log->tracef("[comp][periscomp] result from 'completion' routine: %s", $compres);
                     if ($compres) {
                         $fres = $compres;
                         goto RETURN_RES;
                     }
                 }
                 if ($ospec =~ /\@$/) {
                     $fres = complete_arg_elem(
                         meta=>$meta, arg=>$sm->{arg}, args=>$gares->[2],
                         word=>$word, index=>$cargs{nth}, 
                         extras=>$extras, %rargs);
                     goto RETURN_RES;
                 } else {
                     $fres = complete_arg_val(
                         meta=>$meta, arg=>$sm->{arg}, args=>$gares->[2],
                         word=>$word, extras=>$extras, %rargs);
                     goto RETURN_RES;
                 }
             } else {
                 $log->tracef("[comp][periscomp] completing option value for a common option, ospec=<%s>", $ospec);
                 $cargs{arg}  = undef;
                 my $codata = $copts_by_ospec->{$ospec};
                 if ($comp) {
                     $log->tracef("[comp][periscomp] invoking routine supplied from 'completion' argument");
                     my $res;
                     eval { $res = $comp->(%cargs) };
                     $log->debug("[comp][periscomp] completion died: $@") if $@;
                     if ($res) {
                         $fres = $res;
                         goto RETURN_RES;
                     }
                 }
                 if ($codata->{completion}) {
                     $cargs{arg}  = undef;
                     $log->tracef("[comp][periscomp] completing with common option's 'completion' property");
                     my $res;
                     eval { $res = $codata->{completion}->(%cargs) };
                     $log->debug("[comp][periscomp] completion died: $@") if $@;
                     if ($res) {
                         $fres = $res;
                         goto RETURN_RES;
                     }
                 }
                 if ($codata->{schema}) {
                     require Data::Sah::Normalize;
                     my $nsch = Data::Sah::Normalize::normalize_schema(
                         $codata->{schema});
                     $log->tracef("[comp][periscomp] completing with common option's schema");
                     $fres = complete_from_schema(
                         schema => $nsch, word=>$word, ci=>$ci);
                     goto RETURN_RES;
                 }
                 goto RETURN_RES;
             }
         } elsif ($type eq 'arg') {
             $log->tracef("[comp][periscomp] completing argument #%d", $cargs{argpos});
             $cargs{type} = 'arg';
 
             my $pos = $cargs{argpos};
             my $fasa = $args{func_arg_starts_at} // 0;
 
             for my $an (keys %$args_prop) {
                 my $arg_spec = $args_prop->{$an};
                 next unless !$arg_spec->{greedy} &&
                     defined($arg_spec->{pos}) && $arg_spec->{pos} == $pos - $fasa;
                 $log->tracef("[comp][periscomp] this argument position is for non-greedy function argument <%s>", $an);
                 $cargs{arg} = $an;
                 if ($comp) {
                     $log->tracef("[comp][periscomp] invoking routine supplied from 'completion' argument");
                     my $res;
                     eval { $res = $comp->(%cargs) };
                     $log->debug("[comp][periscomp] completion died: $@") if $@;
                     if ($res) {
                         $fres = $res;
                         goto RETURN_RES;
                     }
                 }
                 $fres = complete_arg_val(
                     meta=>$meta, arg=>$an, args=>$gares->[2],
                     word=>$word, extras=>$extras, %rargs);
                 goto RETURN_RES;
             }
 
             for my $an (sort {
                 ($args_prop->{$b}{pos} // 9999) <=> ($args_prop->{$a}{pos} // 9999)
             } keys %$args_prop) {
                 my $arg_spec = $args_prop->{$an};
                 next unless $arg_spec->{greedy} &&
                     defined($arg_spec->{pos}) && $arg_spec->{pos} <= $pos - $fasa;
                 my $index = $pos - $fasa - $arg_spec->{pos};
                 $cargs{arg} = $an;
                 $cargs{index} = $index;
                 $log->tracef("[comp][periscomp] this position is for greedy function argument <%s>'s element[%d]", $an, $index);
                 if ($comp) {
                     $log->tracef("[comp][periscomp] invoking routine supplied from 'completion' argument");
                     my $res;
                     eval { $res = $comp->(%cargs) };
                     $log->debug("[comp][periscomp] completion died: $@") if $@;
                     if ($res) {
                         $fres = $res;
                         goto RETURN_RES;
                     }
                 }
                 $fres = complete_arg_elem(
                     meta=>$meta, arg=>$an, args=>$gares->[2],
                     word=>$word, index=>$index, extras=>$extras, %rargs);
                 goto RETURN_RES;
             }
 
             $log->tracef("[comp][periscomp] there is no matching function argument at this position");
             if ($comp) {
                 $log->tracef("[comp][periscomp] invoking routine supplied from 'completion' argument");
                 my $res;
                 eval { $res = $comp->(%cargs) };
                 $log->debug("[comp][periscomp] completion died: $@") if $@;
                 if ($res) {
                     $fres = $res;
                     goto RETURN_RES;
                 }
             }
             goto RETURN_RES;
         } else {
             $log->tracef("[comp][periscomp] completing option value for an unknown/ambiguous option, declining ...");
             goto RETURN_RES;
         }
       RETURN_RES:
         $log->tracef("[comp][periscomp] leaving completion routine (that we supply to Complete::Getopt::Long)");
         $fres;
     }; 
 
     $fres = Complete::Getopt::Long::complete_cli_arg(
         getopt_spec => $gospec,
         words       => $words,
         cword       => $cword,
         completion  => $compgl_comp,
         extras      => $extras,
     );
 
   RETURN_RES:
     $log->tracef('[comp][periscomp] leaving %s(), result=%s',
                  $fname, $fres);
     $fres;
 }
 
 1;
 
 __END__
 
### Perinci/Sub/GetArgs/Argv.pm ###
 package Perinci::Sub::GetArgs::Argv;
 
 our $DATE = '2015-08-19'; 
 our $VERSION = '0.69'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Data::Sah::Normalize qw(normalize_schema);
 use Getopt::Long::Negate::EN qw(negations_for_option);
 use Getopt::Long::Util qw(parse_getopt_long_opt_spec);
 use List::Util qw(first);
 use Perinci::Sub::GetArgs::Array qw(get_args_from_array);
 use Perinci::Sub::Util qw(err);
 
 use Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        gen_getopt_long_spec_from_meta
                        get_args_from_argv
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Get subroutine arguments from command line arguments (@ARGV)',
 };
 
 my $re_simple_scalar = qr/^(str|num|int|float|bool|buf|re|date|duration)$/;
 
 sub _parse_json {
     my $str = shift;
 
     state $json = do {
         require JSON::PP;
         JSON::PP->new->allow_nonref;
     };
 
     state $cleanser = do {
         if (eval { require Data::Clean::FromJSON; 1 }) {
             Data::Clean::FromJSON->get_cleanser;
         } else {
             undef;
         }
     };
 
     my $res;
     eval { $res = $json->decode($str); $cleanser->clean_in_place($res) if $cleanser };
     my $e = $@;
     return (!$e, $e, $res);
 }
 
 sub _parse_yaml {
     no warnings 'once';
 
     state $yaml_xs_available = do {
         if (eval { require YAML::XS; 1 }) {
             1;
         } else {
             require YAML::Old;
             0;
         }
     };
 
     my $str = shift;
 
     my $res;
     eval {
         if ($yaml_xs_available) {
             $res = YAML::XS::Load($str);
         } else {
             $str = "--- $str" unless $str =~ /\A--- /;
             $str .= "\n" unless $str =~ /\n\z/;
             $res = YAML::Old::Load($str);
         }
     };
     my $e = $@;
     return (!$e, $e, $res);
 }
 
 sub _arg2opt {
     my $opt = shift;
     $opt =~ s/[^A-Za-z0-9-]+/-/g; 
     $opt;
 }
 
 sub _opt2ospec {
     my ($opt, $schema, $arg_spec) = @_;
     my $type = $schema->[0];
     my $cs   = $schema->[1];
     my $is_array_of_simple_scalar = $type eq 'array' &&
         $cs->{of} && $cs->{of}[0] =~ $re_simple_scalar;
     if ($is_array_of_simple_scalar && $arg_spec && $arg_spec->{'x.name.is_plural'}) {
         if ($arg_spec->{'x.name.singular'}) {
             $opt = $arg_spec->{'x.name.singular'};
         } else {
             require Lingua::EN::PluralToSingular;
             $opt = Lingua::EN::PluralToSingular::to_singular($opt);
         }
     }
     if ($type eq 'bool') {
         if (length($opt) == 1 || $cs->{is}) {
             return ($opt, {opts=>[$opt]});
         } else {
             my @res;
             my @negs = negations_for_option($opt);
             push @res, $opt, {opts=>[$opt]}, {is_neg=>0, neg_opts=>\@negs};
             for (@negs) {
                 push @res, $_, {opts=>[$_]}, {is_neg=>1, pos_opts=>[$opt]};
             }
             return @res;
         }
     } elsif ($type eq 'buf') {
         return (
             "$opt=s", {opts=>[$opt], desttype=>"", type=>"s"}, undef,
             "$opt-base64=s", {opts=>["$opt-base64"], desttype=>"", type=>"s"}, {is_base64=>1},
         );
     } else {
         my $t = ($type eq 'int' ? 'i' : $type eq 'float' ? 'f' :
                      $is_array_of_simple_scalar ? 's@' : 's');
         return ("$opt=$t", {opts=>[$opt], desttype=>"", type=>$t});
     }
 }
 
 sub _args2opts {
     my %args = @_;
 
     my $argprefix        = $args{argprefix};
     my $parent_args      = $args{parent_args};
     my $meta             = $args{meta};
     my $seen_opts        = $args{seen_opts};
     my $seen_common_opts = $args{seen_common_opts};
     my $seen_func_opts   = $args{seen_func_opts};
     my $rargs            = $args{rargs};
     my $go_spec          = $args{go_spec};
     my $specmeta         = $args{specmeta};
 
     my $args_prop = $meta->{args} // {};
 
     for my $arg (keys %$args_prop) {
         my $fqarg    = "$argprefix$arg";
         my $arg_spec = $args_prop->{$arg};
         my $sch      = $arg_spec->{schema} // ['any', {}];
         my $type     = $sch->[0] // '';
         my $cs       = $sch->[1] // {};
 
         if ($type eq 'array' && $cs->{of}) {
             $cs->{of} = normalize_schema($cs->{of});
         }
         my $opt = _arg2opt($fqarg);
         if ($seen_opts->{$opt}) {
             my $i = 1;
             my $opt2;
             while (1) {
                 $opt2 = "$opt-arg" . ($i > 1 ? $i : '');
                 last unless $seen_opts->{$opt2};
                 $i++;
             }
             $opt = $opt2;
         }
 
         my $is_simple_scalar = $type =~ $re_simple_scalar;
         my $is_array_of_simple_scalar = $type eq 'array' &&
             $cs->{of} && $cs->{of}[0] =~ $re_simple_scalar;
 
         my $stash = {};
 
 
         my $handler = sub {
             my ($val, $val_set);
 
             my $num_called = ++$stash->{called}{$arg};
 
             my $rargs = do {
                 if (ref($rargs) eq 'ARRAY') {
                     $rargs->[$num_called-1] //= {};
                     $rargs->[$num_called-1];
                 } else {
                     $rargs;
                 }
             };
 
             if ($is_array_of_simple_scalar) {
                 $rargs->{$arg} //= [];
                 $val_set = 1; $val = $_[1];
                 push @{ $rargs->{$arg} }, $val;
             } elsif ($is_simple_scalar) {
                 $val_set = 1; $val = $_[1];
                 $rargs->{$arg} = $val;
             } else {
                 {
                     my ($success, $e, $decoded);
                     ($success, $e, $decoded) = _parse_json($_[1]);
                     if ($success) {
                         $val_set = 1; $val = $decoded;
                         $rargs->{$arg} = $val;
                         last;
                     }
                     ($success, $e, $decoded) = _parse_yaml($_[1]);
                     if ($success) {
                         $val_set = 1; $val = $decoded;
                         $rargs->{$arg} = $val;
                         last;
                     }
                     die "Invalid YAML/JSON in arg '$fqarg'";
                 }
             }
             if ($val_set && $arg_spec->{cmdline_on_getopt}) {
                 $arg_spec->{cmdline_on_getopt}->(
                     arg=>$arg, fqarg=>$fqarg, value=>$val, args=>$rargs,
                     opt=>$opt,
                 );
             }
         }; 
 
         my @triplets = _opt2ospec($opt, $sch, $arg_spec);
         my $aliases_processed;
         while (my ($ospec, $parsed, $extra) = splice @triplets, 0, 3) {
             $extra //= {};
             if ($extra->{is_neg}) {
                 $go_spec->{$ospec} = sub { $handler->($_[0], 0) };
             } elsif (defined $extra->{is_neg}) {
                 $go_spec->{$ospec} = sub { $handler->($_[0], 1) };
             } elsif ($extra->{is_base64}) {
                 $go_spec->{$ospec} = sub {
                     require MIME::Base64;
                     my $decoded = MIME::Base64::decode($_[1]);
                     $handler->($_[0], $decoded);
                 };
             } else {
                 $go_spec->{$ospec} = $handler;
             }
 
             $specmeta->{$ospec} = {arg=>$arg, fqarg=>$fqarg, parsed=>$parsed, %$extra};
             for (@{ $parsed->{opts} }) {
                 $seen_opts->{$_}++; $seen_func_opts->{$_} = $fqarg;
             }
 
             if ($parent_args->{per_arg_json} && $type !~ $re_simple_scalar) {
                 my $jopt = "$opt-json";
                 if ($seen_opts->{$jopt}) {
                     warn "Clash of option: $jopt, not added";
                 } else {
                     my $jospec = "$jopt=s";
                     my $parsed = {type=>"s", opts=>[$jopt]};
                     $go_spec->{$jospec} = sub {
                         my ($success, $e, $decoded);
                         ($success, $e, $decoded) = _parse_json($_[1]);
                         if ($success) {
                             $rargs->{$arg} = $decoded;
                         } else {
                             die "Invalid JSON in option --$jopt: $_[1]: $e";
                         }
                     };
                     $specmeta->{$jospec} = {arg=>$arg, fqarg=>$fqarg, is_json=>1, parsed=>$parsed, %$extra};
                     $seen_opts->{$jopt}++; $seen_func_opts->{$jopt} = $fqarg;
                 }
             }
             if ($parent_args->{per_arg_yaml} && $type !~ $re_simple_scalar) {
                 my $yopt = "$opt-yaml";
                 if ($seen_opts->{$yopt}) {
                     warn "Clash of option: $yopt, not added";
                 } else {
                     my $yospec = "$yopt=s";
                     my $parsed = {type=>"s", opts=>[$yopt]};
                     $go_spec->{$yospec} = sub {
                         my ($success, $e, $decoded);
                         ($success, $e, $decoded) = _parse_yaml($_[1]);
                         if ($success) {
                             $rargs->{$arg} = $decoded;
                         } else {
                             die "Invalid YAML in option --$yopt: $_[1]: $e";
                         }
                     };
                     $specmeta->{$yospec} = {arg=>$arg, fqarg=>$fqarg, is_yaml=>1, parsed=>$parsed, %$extra};
                     $seen_opts->{$yopt}++; $seen_func_opts->{$yopt} = $fqarg;
                 }
             }
 
             if ($arg_spec->{cmdline_aliases} && !$aliases_processed++) {
                 for my $al (keys %{$arg_spec->{cmdline_aliases}}) {
                     my $alspec = $arg_spec->{cmdline_aliases}{$al};
                     my $alsch = $alspec->{schema} //
                         $alspec->{is_flag} ? [bool=>{req=>1,is=>1}] : $sch;
                     my $altype = $alsch->[0];
                     my $alopt = _arg2opt("$argprefix$al");
                     if ($seen_opts->{$alopt}) {
                         warn "Clash of cmdline_alias option $al";
                         next;
                     }
                     my $alcode = $alspec->{code};
                     my $alospec;
                     my $parsed;
                     if ($alcode && $alsch->[0] eq 'bool') {
                         $alospec = $alopt; 
                         $parsed = {opts=>[$alopt]};
                     } else {
                         ($alospec, $parsed) = _opt2ospec($alopt, $alsch);
                     }
 
                     if ($alcode) {
                         if ($alcode eq 'CODE') {
                             if ($parent_args->{ignore_converted_code}) {
                                 $alcode = sub {};
                             } else {
                                 return [
                                     501,
                                     join("",
                                          "Code in cmdline_aliases for arg $fqarg ",
                                          "got converted into string, probably ",
                                          "because of JSON/YAML transport"),
                                 ];
                             }
                         }
                         $go_spec->{$alospec} = sub {
 
                             my $num_called = ++$stash->{called}{$arg};
                             my $rargs = do {
                                 if (ref($rargs) eq 'ARRAY') {
                                     $rargs->[$num_called-1] //= {};
                                     $rargs->[$num_called-1];
                                 } else {
                                     $rargs;
                                 }
                             };
 
                             $alcode->($rargs, $_[1]);
                         };
                     } else {
                         $go_spec->{$alospec} = $handler;
                     }
                     $specmeta->{$alospec} = {
                         alias     => $al,
                         is_alias  => 1,
                         alias_for => $ospec,
                         arg       => $arg,
                         fqarg     => $fqarg,
                         is_code   => $alcode ? 1:0,
                         parsed    => $parsed,
                         %$extra,
                     };
                     push @{$specmeta->{$ospec}{($alcode ? '':'non').'code_aliases'}},
                         $alospec;
                     $seen_opts->{$alopt}++; $seen_func_opts->{$alopt} = $fqarg;
                 }
             } 
 
             if ($arg_spec->{meta}) {
                 $rargs->{$arg} = {};
                 my $res = _args2opts(
                     %args,
                     argprefix => "$argprefix$arg\::",
                     meta      => $arg_spec->{meta},
                     rargs     => $rargs->{$arg},
                 );
                 return $res if $res;
             }
 
             if ($arg_spec->{element_meta}) {
                 $rargs->{$arg} = [];
                 my $res = _args2opts(
                     %args,
                     argprefix => "$argprefix$arg\::",
                     meta      => $arg_spec->{element_meta},
                     rargs     => $rargs->{$arg},
                 );
                 return $res if $res;
             }
         } 
 
     } 
 
     undef;
 }
 
 $SPEC{gen_getopt_long_spec_from_meta} = {
     v           => 1.1,
     summary     => 'Generate Getopt::Long spec from Rinci function metadata',
     description => <<'_',
 
 This routine will produce a `Getopt::Long` specification from Rinci function
 metadata, as well as some more data structure in the result metadata to help
 producing a command-line help/usage message.
 
 Function arguments will be mapped to command-line options with the same name,
 with non-alphanumeric characters changed to `-` (`-` is preferred over `_`
 because it lets user avoid pressing Shift on popular keyboards). For example:
 `file_size` becomes `file-size`, `file_size.max` becomes `file-size-max`. If
 function argument option name clashes with command-line option or another
 existing option, it will be renamed to `NAME-arg` (or `NAME-arg2` and so on).
 For example: `help` will become `help-arg` (if `common_opts` contains `help`,
 that is).
 
 Each command-line alias (`cmdline_aliases` property) in the argument
 specification will also be added as command-line option, except if it clashes
 with an existing option, in which case this function will warn and skip adding
 the alias. For more information about `cmdline_aliases`, see `Rinci::function`.
 
 For arguments with type of `bool`, Getopt::Long will by default also
 automatically recognize `--noNAME` or `--no-NAME` in addition to `--name`. So
 this function will also check those names for clashes.
 
 For arguments with type array of simple scalar, `--NAME` can be specified more
 than once to append to the array.
 
 If `per_arg_json` setting is active, and argument's schema is not a "required
 simple scalar" (e.g. an array, or a nullable string), then `--NAME-json` will
 also be added to let users input undef (through `--NAME-json null`) or a
 non-scalar value (e.g. `--NAME-json '[1,2,3]'`). If this name conflicts with
 another existing option, a warning will be displayed and the option will not be
 added.
 
 If `per_arg_yaml` setting is active, and argument's schema is not a "required
 simple scalar" (e.g. an array, or a nullable string), then `--NAME-yaml` will
 also be added to let users input undef (through `--NAME-yaml '~'`) or a
 non-scalar value (e.g. `--NAME-yaml '[foo, bar]'`). If this name conflicts with
 another existing option, a warning will be displayed and the option will not be
 added. YAML can express a larger set of values, e.g. binary data, circular
 references, etc.
 
 Will produce a hash (Getopt::Long spec), with `func.specmeta`, `func.opts`,
 `func.common_opts`, `func.func_opts` that contain extra information
 (`func.specmeta` is a hash of getopt spec name and a hash of extra information
 while `func.*opts` lists all used option names).
 
 _
     args => {
         meta => {
             summary => 'Rinci function metadata',
             schema  => 'hash*',
             req     => 1,
         },
         meta_is_normalized => {
             schema => 'bool*',
         },
         args => {
             summary => 'Reference to hash which will store the result',
             schema  => 'hash*',
         },
         common_opts => {
             summary => 'Common options',
             description => <<'_',
 
 A hash where the values are hashes containing these keys: `getopt` (Getopt::Long
 option specification), `handler` (Getopt::Long handler). Will be passed to
 `get_args_from_argv()`. Example:
 
     {
         help => {
             getopt  => 'help|h|?',
             handler => sub { ... },
             summary => 'Display help and exit',
         },
         version => {
             getopt  => 'version|v',
             handler => sub { ... },
             summary => 'Display version and exit',
         },
     }
 
 _
             schema => ['hash*'],
         },
         per_arg_json => {
             summary => 'Whether to add --NAME-json for non-simple arguments',
             schema  => 'bool',
             default => 0,
             description => <<'_',
 
 Will also interpret command-line arguments as JSON if assigned to function
 arguments, if arguments' schema is not simple scalar.
 
 _
         },
         per_arg_yaml => {
             summary => 'Whether to add --NAME-yaml for non-simple arguments',
             schema  => 'bool',
             default => 0,
             description => <<'_',
 
 Will also interpret command-line arguments as YAML if assigned to function
 arguments, if arguments' schema is not simple scalar.
 
 _
         },
         ignore_converted_code => {
             summary => 'Whether to ignore coderefs converted to string',
             schema => 'bool',
             default => 0,
             description => <<'_',
 
 Across network through JSON encoding, coderef in metadata (e.g. in
 `cmdline_aliases` property) usually gets converted to string `CODE`. In some
 cases, like for tab completion, this is pretty harmless so you can turn this
 option on. For example, in the case of `cmdline_aliases`, the effect is just
 that command-line aliases code are not getting executed, but this is usually
 okay.
 
 _
         },
     },
 };
 sub gen_getopt_long_spec_from_meta {
     my %fargs = @_;
 
     my $meta       = $fargs{meta} or return [400, "Please specify meta"];
     unless ($fargs{meta_is_normalized}) {
         require Perinci::Sub::Normalize;
         $meta = Perinci::Sub::Normalize::normalize_function_metadata($meta);
     }
     my $co           = $fargs{common_opts} // {};
     my $per_arg_yaml = $fargs{per_arg_yaml} // 0;
     my $per_arg_json = $fargs{per_arg_json} // 0;
     my $ignore_converted_code = $fargs{ignore_converted_code};
     my $rargs        = $fargs{args} // {};
 
     my %go_spec;
     my %specmeta; 
     my %seen_opts;
     my %seen_common_opts;
     my %seen_func_opts;
 
     for my $k (keys %$co) {
         my $v = $co->{$k};
         my $ospec   = $v->{getopt};
         my $handler = $v->{handler};
         my $res = parse_getopt_long_opt_spec($ospec)
             or return [400, "Can't parse common opt spec '$ospec'"];
         $go_spec{$ospec} = $handler;
         $specmeta{$ospec} = {common_opt=>$k, arg=>undef, parsed=>$res};
         for (@{ $res->{opts} }) {
             return [412, "Clash of common opt '$_'"] if $seen_opts{$_};
             $seen_opts{$_}++; $seen_common_opts{$_} = $ospec;
             if ($res->{is_neg}) {
                 $seen_opts{"no$_"}++ ; $seen_common_opts{"no$_"}  = $ospec;
                 $seen_opts{"no-$_"}++; $seen_common_opts{"no-$_"} = $ospec;
             }
         }
     }
 
     my $res = _args2opts(
         argprefix        => "",
         parent_args      => \%fargs,
         meta             => $meta,
         seen_opts        => \%seen_opts,
         seen_common_opts => \%seen_common_opts,
         seen_func_opts   => \%seen_func_opts,
         rargs            => $rargs,
         go_spec          => \%go_spec,
         specmeta         => \%specmeta,
     );
     return $res if $res;
 
     my $opts        = [sort(map {length($_)>1 ? "--$_":"-$_"} keys %seen_opts)];
     my $common_opts = [sort(map {length($_)>1 ? "--$_":"-$_"} keys %seen_common_opts)];
     my $func_opts   = [sort(map {length($_)>1 ? "--$_":"-$_"} keys %seen_func_opts)];
     my $opts_by_common = {};
     for my $k (keys %$co) {
         my $v = $co->{$k};
         my $ospec = $v->{getopt};
         my @opts;
         for (keys %seen_common_opts) {
             next unless $seen_common_opts{$_} eq $ospec;
             push @opts, (length($_)>1 ? "--$_":"-$_");
         }
         $opts_by_common->{$ospec} = [sort @opts];
     }
 
     my $opts_by_arg = {};
     for (keys %seen_func_opts) {
         my $fqarg = $seen_func_opts{$_};
         push @{ $opts_by_arg->{$fqarg} }, length($_)>1 ? "--$_":"-$_";
     }
     for (keys %$opts_by_arg) {
         $opts_by_arg->{$_} = [sort @{ $opts_by_arg->{$_} }];
     }
 
     [200, "OK", \%go_spec,
      {
          "func.specmeta"       => \%specmeta,
          "func.opts"           => $opts,
          "func.common_opts"    => $common_opts,
          "func.func_opts"      => $func_opts,
          "func.opts_by_arg"    => $opts_by_arg,
          "func.opts_by_common" => $opts_by_common,
      }];
 }
 
 $SPEC{get_args_from_argv} = {
     v => 1.1,
     summary => 'Get subroutine arguments (%args) from command-line arguments '.
         '(@ARGV)',
     description => <<'_',
 
 Using information in Rinci function metadata's `args` property, parse command
 line arguments `@argv` into hash `%args`, suitable for passing into subroutines.
 
 Currently uses Getopt::Long's GetOptions to do the parsing.
 
 As with GetOptions, this function modifies its `argv` argument, so you might
 want to copy the original `argv` first (or pass a copy instead) if you want to
 preserve the original.
 
 See also: gen_getopt_long_spec_from_meta() which is the routine that generates
 the specification.
 
 _
     args => {
         argv => {
             schema => ['array*' => {
                 of => 'str*',
             }],
             description => 'If not specified, defaults to @ARGV',
         },
         args => {
             summary => 'Specify input args, with some arguments preset',
             schema  => ['hash'],
         },
         meta => {
             schema => ['hash*' => {}],
             req => 1,
         },
         meta_is_normalized => {
             summary => 'Can be set to 1 if your metadata is normalized, '.
                 'to avoid duplicate effort',
             schema => 'bool',
             default => 0,
         },
         strict => {
             schema => ['bool' => {default=>1}],
             summary => 'Strict mode',
             description => <<'_',
 
 If set to 0, will still return parsed argv even if there are parsing errors
 (reported by Getopt::Long). If set to 1 (the default), will die upon error.
 
 Normally you would want to use strict mode, for more error checking. Setting off
 strict is used by, for example, Perinci::Sub::Complete during completion where
 the command-line might still be incomplete.
 
 Should probably be named `ignore_errors`. :-)
 
 _
         },
         per_arg_yaml => {
             schema => ['bool' => {default=>0}],
             summary => 'Whether to recognize --ARGNAME-yaml',
             description => <<'_',
 
 This is useful for example if you want to specify a value which is not
 expressible from the command-line, like 'undef'.
 
     % script.pl --name-yaml '~'
 
 See also: per_arg_json. You should enable just one instead of turning on both.
 
 _
         },
         per_arg_json => {
             schema => ['bool' => {default=>0}],
             summary => 'Whether to recognize --ARGNAME-json',
             description => <<'_',
 
 This is useful for example if you want to specify a value which is not
 expressible from the command-line, like 'undef'.
 
     % script.pl --name-json 'null'
 
 But every other string will need to be quoted:
 
     % script.pl --name-json '"foo"'
 
 See also: per_arg_yaml. You should enable just one instead of turning on both.
 
 _
         },
         common_opts => {
             summary => 'Common options',
             description => <<'_',
 
 A hash where the values are hashes containing these keys: `getopt` (Getopt::Long
 option specification), `handler` (Getopt::Long handler). Will be passed to
 `get_args_from_argv()`. Example:
 
     {
         help => {
             getopt  => 'help|h|?',
             handler => sub { ... },
             summary => 'Display help and exit',
         },
         version => {
             getopt  => 'version|v',
             handler => sub { ... },
             summary => 'Display version and exit',
         },
     }
 
 _
             schema => ['hash*'],
         },
         allow_extra_elems => {
             schema => ['bool' => {default=>0}],
             summary => 'Allow extra/unassigned elements in argv',
             description => <<'_',
 
 If set to 1, then if there are array elements unassigned to one of the
 arguments, instead of generating an error, this function will just ignore them.
 
 This option will be passed to Perinci::Sub::GetArgs::Array's allow_extra_elems.
 
 _
         },
         on_missing_required_args => {
             schema => 'code',
             summary => 'Execute code when there is missing required args',
             description => <<'_',
 
 This can be used to give a chance to supply argument value from other sources if
 not specified by command-line options. Perinci::CmdLine, for example, uses this
 hook to supply value from STDIN or file contents (if argument has `cmdline_src`
 specification key set).
 
 This hook will be called for each missing argument. It will be supplied hash
 arguments: (arg => $the_missing_argument_name, args =>
 $the_resulting_args_so_far, spec => $the_arg_spec).
 
 The hook can return true if it succeeds in making the missing situation
 resolved. In this case, this function will not report the argument as missing.
 
 _
         },
         ignore_converted_code => {
             summary => 'Whether to ignore coderefs converted to string',
             schema => 'bool',
             default => 0,
             description => <<'_',
 
 Across network through JSON encoding, coderef in metadata (e.g. in
 `cmdline_aliases` property) usually gets converted to string `CODE`. In some
 cases, like for tab completion, this is harmless so you can turn this option on.
 
 _
         },
     },
     result => {
         description => <<'_',
 
 Error codes:
 
 * 400 - Error in Getopt::Long option specification, e.g. in common_opts.
 
 * 500 - failure in GetOptions, meaning argv is not valid according to metadata
   specification (only if 'strict' mode is enabled).
 
 * 501 - coderef in cmdline_aliases got converted into a string, probably because
   the metadata was transported (e.g. through Riap::HTTP/Riap::Simple).
 
 _
     },
 };
 sub get_args_from_argv {
     require Getopt::Long;
 
     my %fargs = @_;
     my $argv       = $fargs{argv} // \@ARGV;
     my $meta       = $fargs{meta} or return [400, "Please specify meta"];
     unless ($fargs{meta_is_normalized}) {
         require Perinci::Sub::Normalize;
         $meta = Perinci::Sub::Normalize::normalize_function_metadata($meta);
     }
     my $strict            = $fargs{strict} // 1;
     my $common_opts       = $fargs{common_opts} // {};
     my $per_arg_yaml      = $fargs{per_arg_yaml} // 0;
     my $per_arg_json      = $fargs{per_arg_json} // 0;
     my $allow_extra_elems = $fargs{allow_extra_elems} // 0;
     my $on_missing        = $fargs{on_missing_required_args};
     my $ignore_converted_code = $fargs{ignore_converted_code};
 
     my $rargs = $fargs{args} // {};
 
     my $genres = gen_getopt_long_spec_from_meta(
         meta => $meta, meta_is_normalized => 1,
         args => $rargs,
         common_opts  => $common_opts,
         per_arg_json => $per_arg_json,
         per_arg_yaml => $per_arg_yaml,
         ignore_converted_code => $ignore_converted_code,
     );
     return err($genres->[0], "Can't generate Getopt::Long spec", $genres)
         if $genres->[0] != 200;
     my $go_spec = $genres->[2];
 
     {
         local $SIG{__WARN__} = sub{} if !$strict;
         my $old_go_conf = Getopt::Long::Configure(
             $strict ? "no_pass_through" : "pass_through",
             "no_ignore_case", "permute", "bundling", "no_getopt_compat");
         my $res = Getopt::Long::GetOptionsFromArray($argv, %$go_spec);
         Getopt::Long::Configure($old_go_conf);
         unless ($res) {
             return [500, "GetOptions failed"] if $strict;
         }
     }
 
 
     my $args_prop = $meta->{args};
 
     if (@$argv) {
         my $res = get_args_from_array(
             array=>$argv, meta => $meta,
             meta_is_normalized => 1,
             allow_extra_elems => $allow_extra_elems,
         );
         if ($res->[0] != 200 && $strict) {
             return err(500, "Get args from array failed", $res);
         } elsif ($strict && $res->[0] != 200) {
             return err("Can't get args from argv", $res);
         } elsif ($res->[0] == 200) {
             my $pos_args = $res->[2];
             for my $name (keys %$pos_args) {
                 my $arg_spec = $args_prop->{$name};
                 my $val      = $pos_args->{$name};
                 if (exists $rargs->{$name}) {
                     return [400, "You specified option --$name but also ".
                                 "argument #".$arg_spec->{pos}] if $strict;
                 }
                 my $type = $arg_spec->{schema}[0];
                 my $cs   = $arg_spec->{schema}[1];
                 my $is_simple_scalar = $type =~ $re_simple_scalar;
                 my $is_array_of_simple_scalar = $type eq 'array' &&
                     $cs->{of} && $cs->{of}[0] =~ $re_simple_scalar;
 
                 if ($arg_spec->{greedy} && ref($val) eq 'ARRAY' &&
                         !$is_array_of_simple_scalar) {
                     my $i = 0;
                     for (@$val) {
                       TRY_PARSING_AS_JSON_YAML:
                         {
                             my ($success, $e, $decoded);
                             if ($per_arg_json) {
                                 ($success, $e, $decoded) = _parse_json($_);
                                 if ($success) {
                                     $_ = $decoded;
                                     last TRY_PARSING_AS_JSON_YAML;
                                 } else {
                                     warn "Failed trying to parse argv #$i as JSON: $e";
                                 }
                             }
                             if ($per_arg_yaml) {
                                 ($success, $e, $decoded) = _parse_yaml($_);
                                 if ($success) {
                                     $_ = $decoded;
                                     last TRY_PARSING_AS_JSON_YAML;
                                 } else {
                                     warn "Failed trying to parse argv #$i as YAML: $e";
                                 }
                             }
                         }
                         $i++;
                     }
                 }
                 if (!$arg_spec->{greedy} && !$is_simple_scalar) {
                   TRY_PARSING_AS_JSON_YAML:
                     {
                         my ($success, $e, $decoded);
                         if ($per_arg_json) {
                             ($success, $e, $decoded) = _parse_json($val);
                             if ($success) {
                                 $val = $decoded;
                                 last TRY_PARSING_AS_JSON_YAML;
                             } else {
                                 warn "Failed trying to parse argv #$arg_spec->{pos} as JSON: $e";
                             }
                         }
                         if ($per_arg_yaml) {
                             ($success, $e, $decoded) = _parse_yaml($val);
                             if ($success) {
                                 $val = $decoded;
                                 last TRY_PARSING_AS_JSON_YAML;
                             } else {
                                 warn "Failed trying to parse argv #$arg_spec->{pos} as YAML: $e";
                             }
                         }
                     }
                 }
                 $rargs->{$name} = $val;
                 if ($arg_spec->{cmdline_on_getopt}) {
                     if ($arg_spec->{greedy}) {
                         $arg_spec->{cmdline_on_getopt}->(
                             arg=>$name, fqarg=>$name, value=>$_, args=>$rargs,
                             opt=>undef, 
                         ) for @$val;
                     } else {
                         $arg_spec->{cmdline_on_getopt}->(
                             arg=>$name, fqarg=>$name, value=>$val, args=>$rargs,
                             opt=>undef, 
                         );
                     }
                 }
             }
         }
     }
 
 
     my %missing_args;
     for my $arg (keys %$args_prop) {
         my $arg_spec = $args_prop->{$arg};
         if (!exists($rargs->{$arg})) {
             next unless $arg_spec->{req};
             if ($on_missing) {
                 next if $on_missing->(arg=>$arg, args=>$rargs, spec=>$arg_spec);
             }
             next if exists $rargs->{$arg};
             $missing_args{$arg} = 1;
         }
     }
 
     {
         last unless $strict;
 
         for my $arg (keys %$args_prop) {
             my $arg_spec = $args_prop->{$arg};
             next unless exists $rargs->{$arg};
             next unless $arg_spec->{deps};
             my $dep_arg = $arg_spec->{deps}{arg};
             next unless $dep_arg;
             return [400, "You specify '$arg', but don't specify '$dep_arg' ".
                         "(upon which '$arg' depends)"]
                 unless exists $rargs->{$dep_arg};
         }
     }
 
     [200, "OK", $rargs, {
         "func.missing_args" => [sort keys %missing_args],
         "func.gen_getopt_long_spec_result" => $genres,
     }];
 }
 
 1;
 
 __END__
 
### Perinci/Sub/GetArgs/Array.pm ###
 package Perinci::Sub::GetArgs::Array;
 
 our $DATE = '2015-09-04'; 
 our $VERSION = '0.15'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 use Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(get_args_from_array);
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
 };
 
 $SPEC{get_args_from_array} = {
     v => 1.1,
     summary => 'Get subroutine arguments (%args) from array',
     description => <<'_',
 
 Using information in metadata's `args` property (particularly the `pos` and
 `greedy` arg type clauses), extract arguments from an array into a hash
 `\%args`, suitable for passing into subs.
 
 Example:
 
     my $meta = {
         v => 1.1,
         summary => 'Multiply 2 numbers (a & b)',
         args => {
             a => {schema=>'num*', pos=>0},
             b => {schema=>'num*', pos=>1},
         }
     }
 
 then `get_args_from_array(array=>[2, 3], meta=>$meta)` will produce:
 
     [200, "OK", {a=>2, b=>3}]
 
 _
     args => {
         array => {
             schema => ['array*' => {}],
             req => 1,
             description => <<'_',
 
 NOTE: array will be modified/emptied (elements will be taken from the array as
 they are put into the resulting args). Copy your array first if you want to
 preserve its content.
 
 _
         },
         meta => {
             schema => ['hash*' => {}],
             req => 1,
         },
         meta_is_normalized => {
             summary => 'Can be set to 1 if your metadata is normalized, '.
                 'to avoid duplicate effort',
             schema => 'bool',
             default => 0,
         },
         allow_extra_elems => {
             schema => ['bool' => {default=>0}],
             summary => 'Allow extra/unassigned elements in array',
             description => <<'_',
 
 If set to 1, then if there are array elements unassigned to one of the arguments
 (due to missing `pos`, for example), instead of generating an error, the
 function will just ignore them.
 
 _
         },
     },
 };
 sub get_args_from_array {
     my %fargs = @_;
     my $ary  = $fargs{array} or return [400, "Please specify array"];
     my $meta = $fargs{meta} or return [400, "Please specify meta"];
     unless ($fargs{meta_is_normalized}) {
         require Perinci::Sub::Normalize;
         $meta = Perinci::Sub::Normalize::normalize_function_metadata(
             $meta);
     }
     my $allow_extra_elems = $fargs{allow_extra_elems} // 0;
 
     my $rargs = {};
 
     my $args_p = $meta->{args} // {};
     for my $i (reverse 0..@$ary-1) {
         while (my ($a, $as) = each %$args_p) {
             my $o = $as->{pos};
             if (defined($o) && $o == $i) {
                 if ($as->{greedy}) {
                     my $type = $as->{schema}[0];
                     my @elems = splice(@$ary, $i);
                     if ($type eq 'array') {
                         $rargs->{$a} = \@elems;
                     } else {
                         $rargs->{$a} = join " ", @elems;
                     }
                 } else {
                     $rargs->{$a} = splice(@$ary, $i, 1);
                 }
             }
         }
     }
 
     return [400, "There are extra, unassigned elements in array: [".
                 join(", ", @$ary)."]"] if @$ary && !$allow_extra_elems;
 
     [200, "OK", $rargs];
 }
 
 1;
 
 __END__
 
### Perinci/Sub/Normalize.pm ###
 package Perinci::Sub::Normalize;
 
 our $DATE = '2015-09-30'; 
 our $VERSION = '0.13'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        normalize_function_metadata
                );
 
 use Sah::Schema::Rinci;
 my $sch = $Sah::Schema::Rinci::SCHEMAS{rinci_function}
     or die "BUG: Rinci schema structure changed (1)";
 my $sch_proplist = $sch->[1]{_prop}
     or die "BUG: Rinci schema structure changed (2)";
 
 sub _normalize{
     my ($meta, $ver, $opts, $proplist, $nmeta, $prefix, $modprefix) = @_;
 
     my $opt_aup = $opts->{allow_unknown_properties};
     my $opt_nss = $opts->{normalize_sah_schemas};
     my $opt_rip = $opts->{remove_internal_properties};
 
     if (defined $ver) {
         defined($meta->{v}) && $meta->{v} eq $ver
             or die "$prefix: Metadata version must be $ver";
     }
 
   KEY:
     for my $k (keys %$meta) {
         die "Invalid prop/attr syntax '$k', must be word/dotted-word only"
             unless $k =~ /\A(\w+)(?:\.(\w+(?:\.\w+)*))?(?:\((\w+)\))?\z/;
 
         my ($prop, $attr);
         if (defined $3) {
             $prop = $1;
             $attr = defined($2) ? "$2.alt.lang.$3" : "alt.lang.$3";
         } else {
             $prop = $1;
             $attr = $2;
         }
 
         my $nk = "$prop" . (defined($attr) ? ".$attr" : "");
 
         if ($prop =~ /\A_/ || defined($attr) && $attr =~ /\A_|\._/) {
             unless ($opt_rip) {
                 $nmeta->{$nk} = $meta->{$k};
             }
             next KEY;
         }
 
         my $prop_proplist = $proplist->{$prop};
 
         if (!$opt_aup && !$prop_proplist) {
             $modprefix //= $prefix;
             my $mod = "Perinci/Sub/Property$modprefix/$prop.pm";
             eval { require $mod };
             if ($@) {
                 die "Unknown property '$prefix/$prop' (and couldn't ".
                     "load property module '$mod'): $@" if $@;
             }
             $prop_proplist = $proplist->{$prop};
         }
         die "Unknown property '$prefix/$prop'"
             unless $opt_aup || $prop_proplist;
 
         if ($prop_proplist && $prop_proplist->{_prop}) {
             die "Property '$prefix/$prop' must be a hash"
                 unless ref($meta->{$k}) eq 'HASH';
             $nmeta->{$nk} = {};
             _normalize(
                 $meta->{$k},
                 $prop_proplist->{_ver},
                 $opts,
                 $prop_proplist->{_prop},
                 $nmeta->{$nk},
                 "$prefix/$prop",
             );
         } elsif ($prop_proplist && $prop_proplist->{_elem_prop}) {
             die "Property '$prefix/$prop' must be an array"
                 unless ref($meta->{$k}) eq 'ARRAY';
             $nmeta->{$nk} = [];
             my $i = 0;
             for (@{ $meta->{$k} }) {
                 my $href = {};
                 if (ref($_) eq 'HASH') {
                     _normalize(
                         $_,
                         $prop_proplist->{_ver},
                         $opts,
                         $prop_proplist->{_elem_prop},
                         $href,
                         "$prefix/$prop/$i",
                     );
                     push @{ $nmeta->{$nk} }, $href;
                 } else {
                     push @{ $nmeta->{$nk} }, $_;
                 }
                 $i++;
             }
         } elsif ($prop_proplist && $prop_proplist->{_value_prop}) {
             die "Property '$prefix/$prop' must be a hash"
                 unless ref($meta->{$k}) eq 'HASH';
             $nmeta->{$nk} = {};
             for (keys %{ $meta->{$k} }) {
                 $nmeta->{$nk}{$_} = {};
                 die "Property '$prefix/$prop/$_' must be a hash"
                     unless ref($meta->{$k}{$_}) eq 'HASH';
                 _normalize(
                     $meta->{$k}{$_},
                     $prop_proplist->{_ver},
                     $opts,
                     $prop_proplist->{_value_prop},
                     $nmeta->{$nk}{$_},
                     "$prefix/$prop/$_",
                     ($prop eq 'args' ? "$prefix/arg" : undef),
                 );
             }
         } else {
             if ($k eq 'schema' && $opt_nss) { 
                 require Data::Sah::Normalize;
                 $nmeta->{$nk} = Data::Sah::Normalize::normalize_schema(
                     $meta->{$k});
             } else {
                 $nmeta->{$nk} = $meta->{$k};
             }
         }
     }
 
     $nmeta;
 }
 
 sub normalize_function_metadata($;$) {
     my ($meta, $opts) = @_;
 
     $opts //= {};
 
     $opts->{allow_unknown_properties}    //= 0;
     $opts->{normalize_sah_schemas}       //= 1;
     $opts->{remove_internal_properties}  //= 0;
 
     _normalize($meta, 1.1, $opts, $sch_proplist, {}, '');
 }
 
 1;
 
 __END__
 
### Perinci/Sub/Util.pm ###
 package Perinci::Sub::Util;
 
 our $DATE = '2015-09-04'; 
 our $VERSION = '0.42'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        err
                        caller
                        gen_modified_sub
                        warn_err
                        die_err
                );
 
 our %SPEC;
 
 $SPEC{':package'} = {
     v => 1.1,
     summary => 'Helper when writing functions',
 };
 
 our $STACK_TRACE;
 our @_c; 
 our $_i; 
 sub err {
     require Scalar::Util;
 
     my @caller = CORE::caller(1);
     if (!@caller) {
         @caller = ("main", "-e", 1, "program");
     }
 
     my ($status, $msg, $meta, $prev);
 
     for (@_) {
         my $ref = ref($_);
         if ($ref eq 'ARRAY') { $prev = $_ }
         elsif ($ref eq 'HASH') { $meta = $_ }
         elsif (!$ref) {
             if (Scalar::Util::looks_like_number($_)) {
                 $status = $_;
             } else {
                 $msg = $_;
             }
         }
     }
 
     $status //= 500;
     $msg  //= "$caller[3] failed";
     $meta //= {};
     $meta->{prev} //= $prev if $prev;
 
     if (!$meta->{logs}) {
 
         my $stack_trace;
         {
             no warnings;
             last unless $STACK_TRACE // $INC{"Carp/Always.pm"};
             last if $prev && ref($prev->[3]) eq 'HASH' &&
                 ref($prev->[3]{logs}) eq 'ARRAY' &&
                     ref($prev->[3]{logs}[0]) eq 'HASH' &&
                         $prev->[3]{logs}[0]{stack_trace};
             $stack_trace = [];
             $_i = 1;
             while (1) {
                 {
                     package DB;
                     @_c = CORE::caller($_i);
                     if (@_c) {
                         $_c[4] = [@DB::args];
                     }
                 }
                 last unless @_c;
                 push @$stack_trace, [@_c];
                 $_i++;
             }
         }
         push @{ $meta->{logs} }, {
             type    => 'create',
             time    => time(),
             package => $caller[0],
             file    => $caller[1],
             line    => $caller[2],
             func    => $caller[3],
             ( stack_trace => $stack_trace ) x !!$stack_trace,
         };
     }
 
     [$status, $msg, undef, $meta];
 }
 
 sub caller {
     my $n0 = shift;
     my $n  = $n0 // 0;
 
     my $pkg = $Perinci::Sub::Wrapper::default_wrapped_package //
         'Perinci::Sub::Wrapped';
 
     my @r;
     my $i =  0;
     my $j = -1;
     while ($i <= $n+1) { 
         $j++;
         @r = CORE::caller($j);
         last unless @r;
         if ($r[0] eq $pkg && $r[1] =~ /^\(eval /) {
             next;
         }
         $i++;
     }
 
     return unless @r;
     return defined($n0) ? @r : $r[0];
 }
 
 $SPEC{gen_modified_sub} = {
     v => 1.1,
     summary => 'Generate modified metadata (and subroutine) based on another',
     description => <<'_',
 
 Often you'll want to create another sub (and its metadata) based on another, but
 with some modifications, e.g. add/remove/rename some arguments, change summary,
 add/remove some properties, and so on.
 
 Instead of cloning the Rinci metadata and modify it manually yourself, this
 routine provides some shortcuts.
 
 You can specify base sub/metadata using `base_name` (string, subroutine name,
 either qualified or not) or `base_code` (coderef) + `base_meta` (hash).
 
 _
     args => {
         base_name => {
             summary => 'Subroutine name (either qualified or not)',
             schema => 'str*',
             description => <<'_',
 
 If not qualified with package name, will be searched in the caller's package.
 Rinci metadata will be searched in `%SPEC` package variable.
 
 Alternatively, you can also specify `base_code` and `base_meta`.
 
 _
         },
         base_code => {
             summary => 'Base subroutine code',
             schema  => 'code*',
             description => <<'_',
 
 If you specify this, you'll also need to specify `base_meta`.
 
 Alternatively, you can specify `base_name` instead, to let this routine search
 the base subroutine from existing Perl package.
 
 _
         },
         base_meta => {
             summary => 'Base Rinci metadata',
             schema  => 'hash*', 
         },
         output_name => {
             summary => 'Where to install the modified sub',
             schema  => 'str*',
             description => <<'_',
 
 Subroutine will be put in the specified name. If the name is not qualified with
 package name, will use caller's package. If no `output_code` is specified, the
 base subroutine reference will be assigned here.
 
 Note that this argument is optional.
 
 _
         },
         output_code => {
             summary => 'Code for the modified sub',
             schema  => 'code*',
             description => <<'_',
 
 If not specified will use `base_code` (which will then be required).
 
 _
         },
         summary => {
             summary => 'Summary for the mod subroutine',
             schema  => 'str*',
         },
         description => {
             summary => 'Description for the mod subroutine',
             schema  => 'str*',
         },
         remove_args => {
             summary => 'List of arguments to remove',
             schema  => 'array*',
         },
         add_args => {
             summary => 'Arguments to add',
             schema  => 'hash*',
         },
         replace_args => {
             summary => 'Arguments to add',
             schema  => 'hash*',
         },
         rename_args => {
             summary => 'Arguments to rename',
             schema  => 'hash*',
         },
         modify_args => {
             summary => 'Arguments to modify',
             description => <<'_',
 
 For each argument you can specify a coderef. The coderef will receive the
 argument ($arg_spec) and is expected to modify the argument specification.
 
 _
             schema  => 'hash*',
         },
         modify_meta => {
             summary => 'Specify code to modify metadata',
             schema  => 'code*',
             description => <<'_',
 
 Code will be called with arguments ($meta) where $meta is the cloned Rinci
 metadata.
 
 _
         },
         install_sub => {
             schema  => 'bool',
             default => 1,
         },
     },
     result => {
         schema => ['hash*' => {
             keys => {
                 code => ['code*'],
                 meta => ['hash*'], 
             },
         }],
     },
 };
 sub gen_modified_sub {
     require Function::Fallback::CoreOrPP;
 
     my %args = @_;
 
     my ($base_code, $base_meta);
     if ($args{base_name}) {
         my ($pkg, $leaf);
         if ($args{base_name} =~ /(.+)::(.+)/) {
             ($pkg, $leaf) = ($1, $2);
         } else {
             $pkg  = CORE::caller();
             $leaf = $args{base_name};
         }
         no strict 'refs';
         $base_code = \&{"$pkg\::$leaf"};
         $base_meta = ${"$pkg\::SPEC"}{$leaf};
         die "Can't find Rinci metadata for $pkg\::$leaf" unless $base_meta;
     } elsif ($args{base_meta}) {
         $base_meta = $args{base_meta};
         $base_code = $args{base_code}
             or die "Please specify base_code";
     } else {
         die "Please specify base_name or base_code+base_meta";
     }
 
     my $output_meta = Function::Fallback::CoreOrPP::clone($base_meta);
     my $output_code = $args{output_code} // $base_code;
 
     for (qw/summary description/) {
         $output_meta->{$_} = $args{$_} if $args{$_};
     }
     if ($args{remove_args}) {
         delete $output_meta->{args}{$_} for @{ $args{remove_args} };
     }
     if ($args{add_args}) {
         for my $k (keys %{ $args{add_args} }) {
             my $v = $args{add_args}{$k};
             die "Can't add arg '$k' in mod sub: already exists"
                 if $output_meta->{args}{$k};
             $output_meta->{args}{$k} = $v;
         }
     }
     if ($args{replace_args}) {
         for my $k (keys %{ $args{replace_args} }) {
             my $v = $args{replace_args}{$k};
             die "Can't replace arg '$k' in mod sub: doesn't exist"
                 unless $output_meta->{args}{$k};
             $output_meta->{args}{$k} = $v;
         }
     }
     if ($args{rename_args}) {
         for my $old (keys %{ $args{rename_args} }) {
             my $new = $args{rename_args}{$old};
             my $as = $output_meta->{args}{$old};
             die "Can't rename arg '$old' in mod sub: doesn't exist" unless $as;
             die "Can't rename arg '$old'->'$new' in mod sub: ".
                 "new name already exist" if $output_meta->{args}{$new};
             $output_meta->{args}{$new} = $as;
             delete $output_meta->{args}{$old};
         }
     }
     if ($args{modify_args}) {
         for (keys %{ $args{modify_args} }) {
             $args{modify_args}{$_}->($output_meta->{args}{$_});
         }
     }
     if ($args{modify_meta}) {
         $args{modify_meta}->($output_meta);
     }
 
     if ($args{output_name}) {
         my ($pkg, $leaf);
         if ($args{output_name} =~ /(.+)::(.+)/) {
             ($pkg, $leaf) = ($1, $2);
         } else {
             $pkg  = CORE::caller();
             $leaf = $args{output_name};
         }
         no strict 'refs';
         no warnings 'redefine';
         *{"$pkg\::$leaf"}       = $output_code if $args{install_sub} // 1;
         ${"$pkg\::SPEC"}{$leaf} = $output_meta;
     }
 
     [200, "OK", {code=>$output_code, meta=>$output_meta}];
 }
 
 
 sub warn_err {
     require Carp;
 
     my $res = err(@_);
     Carp::carp("ERROR $res->[0]: $res->[1]");
 }
 
 sub die_err {
     require Carp;
 
     my $res = err(@_);
     Carp::croak("ERROR $res->[0]: $res->[1]");
 }
 
 1;
 
 __END__
 
### Perinci/Sub/Util/ResObj.pm ###
 package Perinci::Sub::Util::ResObj;
 
 our $DATE = '2015-09-04'; 
 our $VERSION = '0.42'; 
 
 use Carp;
 use overload
     q("") => sub {
         my $res = shift; "ERROR $err->[0]: $err->[1]\n" . Carp::longmess();
     };
 
 1;
 
 __END__
 
### Perinci/Sub/Util/Sort.pm ###
 package Perinci::Sub::Util::Sort;
 
 our $DATE = '2015-09-04'; 
 our $VERSION = '0.42'; 
 
 use 5.010;
 use strict;
 use warnings;
 
 require Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        sort_args
                );
 
 our %SPEC;
 
 sub sort_args {
     my $args = shift;
     sort {
         (($args->{$a}{pos} // 9999) <=> ($args->{$b}{pos} // 9999)) ||
             $a cmp $b
         } keys %$args;
 }
 
 1;
 
 __END__
 
### Rinci.pm ###
 package Rinci;
 
 our $VERSION = '1.1.78'; 
 
 1;
 
 __END__
 
### Sah/Schema/Rinci.pm ###
 package Sah::Schema::Rinci;
 
 our $DATE = '2015-09-03'; 
 our $VERSION = '1.1.78'; 
 
 use 5.010001;
 use strict;
 use warnings;
 
 our %SCHEMAS;
 
 my %dh_props = (
     v => {},
     defhash_v => {},
     name => {},
     caption => {},
     summary => {},
     description => {},
     tags => {},
     default_lang => {},
     x => {},
 );
 
 $SCHEMAS{rinci} = [hash => {
     _ver => 1.1, 
     _prop => {
         %dh_props,
 
         entity_v => {},
         entity_date => {},
         links => {
             _elem_prop => {
                 %dh_props,
 
                 url => {},
             },
         },
     },
 }];
 
 $SCHEMAS{rinci_function} = [hash => {
     _ver => 1.1,
     _prop => {
         %dh_props,
 
         entity_v => {},
         entity_date => {},
         links => {},
 
         is_func => {},
         is_meth => {},
         is_class_meth => {},
         args => {
             _value_prop => {
                 %dh_props,
 
                 links => {},
 
                 schema => {},
                 filters => {},
                 default => {},
                 req => {},
                 pos => {},
                 greedy => {},
                 partial => {},
                 stream => {},
                 is_password => {},
                 cmdline_aliases => {
                     _value_prop => {
                         summary => {},
                         description => {},
                         schema => {},
                         code => {},
                         is_flag => {},
                     },
                 },
                 cmdline_on_getopt => {},
                 cmdline_prompt => {},
                 completion => {},
                 element_completion => {},
                 cmdline_src => {},
                 meta => 'fix',
                 element_meta => 'fix',
                 deps => {
                     _keys => {
                         arg => {},
                         all => {},
                         any => {},
                         none => {},
                     },
                 },
             },
         },
         args_as => {},
         args_rels => {},
         result => {
             _prop => {
                 %dh_props,
 
                 schema => {},
                 statuses => {
                     _value_prop => {
                         summary => {},
                         description => {},
                         schema => {},
                     },
                 },
                 partial => {},
                 stream => {},
             },
         },
         result_naked => {},
         examples => {
             _elem_prop => {
                 %dh_props,
 
                 args => {},
                 argv => {},
                 src => {},
                 src_plang => {},
                 status => {},
                 result => {},
                 test => {},
             },
         },
         features => {
             _keys => {
                 reverse => {},
                 tx => {},
                 dry_run => {},
                 pure => {},
                 immutable => {},
                 idempotent => {},
                 check_arg => {},
             },
         },
         deps => {
             _keys => {
                 all => {},
                 any => {},
                 none => {},
                 env => {},
                 prog => {},
                 pkg => {},
                 func => {},
                 code => {},
                 tmp_dir => {},
                 trash_dir => {},
             },
         },
     },
 }];
 $SCHEMAS{rinci_function}[1]{_prop}{args}{_value_prop}{meta} =
     $SCHEMAS{rinci_function}[1];
 $SCHEMAS{rinci_function}[1]{_prop}{args}{_value_prop}{element_meta} =
     $SCHEMAS{rinci_function}[1];
 
 
 $SCHEMAS{rinci_resmeta} = [hash => {
     _ver => 1.1,
     _prop => {
         %dh_props,
 
         perm_err => {},
         func => {}, 
         cmdline => {}, 
         logs => {},
         prev => {},
         results => {},
         part_start => {},
         part_len => {},
         len => {},
         stream => {},
     },
 }];
 
 
 1;
 
 __END__
 
### String/Wildcard/Bash.pm ###
 package String::Wildcard::Bash;
 
 use 5.010001;
 use strict;
 use warnings;
 
 our $VERSION = '0.03'; 
 
 use Exporter;
 our @ISA = qw(Exporter);
 our @EXPORT_OK = qw(
                        $RE_WILDCARD_BASH
                        contains_wildcard
                        convert_wildcard_to_sql
                );
 
 our $RE_WILDCARD_BASH =
     qr(
           # non-escaped brace expression, with at least one comma
           (?P<brace>
               (?<!\\)(?:\\\\)*\{
               (?:           \\\\ | \\\{ | \\\} | [^\\\{\}] )*
               (?:, (?:  \\\\ | \\\{ | \\\} | [^\\\{\}] )* )+
               (?<!\\)(?:\\\\)*\}
           )
       |
           # non-escaped brace expression, to catch * or ? or [...] inside so
           # they don't go to below pattern, because bash doesn't consider them
           # wildcards, e.g. '/{et?,us*}' expands to '/etc /usr', but '/{et?}'
           # doesn't expand at all to /etc.
           (?P<braceno>
               (?<!\\)(?:\\\\)*\{
               (?:           \\\\ | \\\{ | \\\} | [^\\\{\}] )*
               (?<!\\)(?:\\\\)*\}
           )
       |
           (?P<class>
               # non-empty, non-escaped character class
               (?<!\\)(?:\\\\)*\[
               (?:  \\\\ | \\\[ | \\\] | [^\\\[\]] )+
               (?<!\\)(?:\\\\)*\]
           )
       |
           (?P<joker>
               # non-escaped * and ?
               (?<!\\)(?:\\\\)*[*?]
           )
       |
           (?P<sql_wc>
               # non-escaped % and ?
               (?<!\\)(?:\\\\)*[%_]
           )
       )ox;
 
 sub contains_wildcard {
     my $str = shift;
 
     while ($str =~ /$RE_WILDCARD_BASH/go) {
         my %m = %+;
         return 1 if $m{brace} || $m{class} || $m{joker};
     }
     0;
 }
 
 sub convert_wildcard_to_sql {
     my $str = shift;
 
     $str =~ s/$RE_WILDCARD_BASH/
         if ($+{joker}) {
             if ($+{joker} eq '*') {
                 "%";
             } else {
                 "_";
             }
         } elsif ($+{sql_wc}) {
             "\\$+{sql_wc}";
         } else {
             $&;
         }
     /eg;
 
     $str;
 }
 
 1;
 
 __END__
 
