File Coverage

blib/lib/Declare/Opts.pm
Criterion Covered Total %
statement 117 118 99.1
branch 58 66 87.8
condition 25 31 80.6
subroutine 19 19 100.0
pod 6 8 75.0
total 225 242 92.9


line stmt bran cond sub pod time code
1             package Declare::Opts;
2 7     7   1046220 use strict;
  7         14  
  7         161  
3 7     7   35 use warnings;
  7         7  
  7         245  
4              
5             our $VERSION = "0.009";
6              
7 7     7   35 use Carp qw/croak/;
  7         7  
  7         357  
8              
9 7         42 use Exporter::Declare qw{
10             import
11             gen_default_export
12             default_export
13 7     7   35 };
  7         14  
14              
15             gen_default_export 'OPTS_META' => sub {
16             my ( $class, $caller ) = @_;
17             my $meta = $class->new();
18             $meta->{class} = $caller;
19 37     37   12231 return sub { $meta };
20             };
21              
22 20     20   7661573 default_export opt => sub { caller->OPTS_META->opt( @_ ) };
23 12     12   15468 default_export parse_opts => sub { caller->OPTS_META->parse( @_ ) };
24 2     2   33 default_export opt_info => sub { caller->OPTS_META->info };
25              
26 1     1 1 35 sub class { shift->{class} }
27 145     145 0 1366 sub opts { shift->{opts} }
28 9     9 1 45 sub default { shift->{default} }
29              
30             sub new {
31 7     7 1 14 my $class = shift;
32 7         14 my ( %opts ) = @_;
33              
34 7         35 my $self = bless { opts => {}, default => {} } => $class;
35 7         28 $self->opt( $_, $opts{$_} ) for keys %opts;
36              
37 7         14 return $self;
38             }
39              
40             sub valid_opt_params {
41 24     24 0 862 return qr/^(alias|list|bool|default|check|transform|description)$/;
42             }
43              
44             sub opt {
45 20     20 1 100 my $self = shift;
46 20         300 my ( $name, %config ) = @_;
47              
48             croak "opt '$name' already defined"
49 20 50       277 if $self->opts->{$name};
50              
51 20         233 for my $prop ( keys %config ) {
52 24 50       163 next if $prop =~ $self->valid_opt_params;
53 0         0 croak "invalid opt property: '$prop'";
54             }
55              
56 20         214 $config{name} = $name;
57              
58             croak "'check' cannot be used with 'bool'"
59 20 50 66     234 if $config{bool} && $config{check};
60              
61             croak "'transform' cannot be used with 'bool'"
62 20 50 66     161 if $config{bool} && $config{transform};
63              
64             croak "opt properties 'list' and 'bool' are mutually exclusive"
65 20 100 66     320 if $config{list} && $config{bool};
66              
67 19 100       80 if (exists $config{default}) {
68             croak "References cannot be used in default, wrap them in a sub."
69 2 50 33     29 if ref $config{default} && ref $config{default} ne 'CODE';
70 2         15 $self->default->{$name} = $config{default};
71             }
72              
73 19 100       113 if ( exists $config{check} ) {
74 9         50 my $ref = ref $config{check};
75             croak "'$config{check}' is not a valid value for 'check'"
76             if ($ref && $ref !~ m/^(CODE|Regexp)$/)
77 9 100 100     472 || (!$ref && $config{check} !~ m/^(file|dir|number)$/);
      100        
      100        
78             }
79              
80 17 100       87 if ( exists $config{alias} ) {
81             my $aliases = ref $config{alias} ? $config{alias}
82 2 100       21 : [ $config{alias} ];
83              
84 2         13 $config{_alias} = { map { $_ => 1 } @$aliases };
  3         45  
85              
86 2         15 for my $alias ( @$aliases ) {
87             croak "Cannot use alias '$alias', name is already taken by another opt."
88 3 50       10 if $self->opts->{$alias};
89              
90 3         10 $self->opts->{$alias} = \%config;
91             }
92             }
93              
94 17         67 $self->opts->{$name} = \%config;
95             }
96              
97             sub parse {
98 12     12 1 56 my $self = shift;
99 12         74 my @opts = @_;
100              
101 12         32 my $params = [];
102 12         55 my $flags = {};
103 12         140 my $no_flags = 0;
104              
105 12         54 while ( my $opt = shift @opts ) {
106 33 100 100     591 if ( $opt eq '--' ) {
    100          
107 3         19 $no_flags++;
108             }
109             elsif ( $opt =~ m/^-+([^-=]+)(?:=(.+))?$/ && !$no_flags ) {
110 24         256 my ( $key, $value ) = ( $1, $2 );
111              
112 24         89 my $name = $self->_flag_name( $key );
113 22         112 my $values = $self->_flag_value(
114             $name,
115             $value,
116             \@opts
117             );
118              
119 17 100       109 if( $self->opts->{$name}->{list} ) {
120 5         12 push @{$flags->{$name}} => @$values;
  5         59  
121             }
122             else {
123 12         84 $flags->{$name} = $values->[0];
124             }
125             }
126             else {
127 6         29 push @$params => $opt;
128             }
129             }
130              
131             # Add defaults for opts not provided
132 5         16 for my $opt ( keys %{ $self->default } ) {
  5         53  
133 4 100       16 next if exists $flags->{$opt};
134 2         9 my $val = $self->default->{$opt};
135 2 50       13 $flags->{$opt} = ref $val ? $val->() : $val;
136             }
137              
138 5         44 return ( $params, $flags );
139             }
140              
141             sub info {
142 2     2 1 6 my $self = shift;
143             return {
144 4   100     14 map { $self->opts->{$_}->{name} => $self->opts->{$_}->{description} || "No Description" }
145 2         5 keys %{ $self->opts }
  2         11  
146             };
147             }
148              
149             sub _flag_value {
150 22     22   56 my $self = shift;
151 22         51 my ( $flag, $value, $opts ) = @_;
152              
153 22         144 my $spec = $self->opts->{$flag};
154              
155 22 100       94 if ( $spec->{bool} ) {
156 3 100       12 return [$value] if defined $value;
157 2 100       14 return [$spec->{default} ? 0 : 1];
158             }
159              
160 19 100       74 my $val = defined $value ? $value : shift @$opts;
161              
162 19 100       183 my $out = $spec->{list} ? [ split /\s*,\s*/, $val ]
163             : [ $val ];
164              
165 19         118 $self->_validate( $flag, $spec, $out );
166              
167 14 100       72 return $out unless $spec->{transform};
168 2         5 return [ map { $spec->{transform}->($_) } @$out ];
  4         25  
169             }
170              
171             sub _validate {
172 19     19   38 my $self = shift;
173 19         43 my ( $flag, $spec, $value ) = @_;
174              
175 19         41 my $check = $spec->{check};
176 19 100       75 return unless $check;
177 12   100     84 my $ref = ref $check || "";
178              
179 12         25 my @bad;
180              
181 12 100       103 if ( $ref eq 'Regexp' ) {
    100          
    100          
    100          
    50          
182 2         6 @bad = grep { $_ !~ $check } @$value;
  2         28  
183             }
184             elsif ( $ref eq 'CODE' ) {
185 2         7 @bad = grep { !$check->( $_ ) } @$value;
  2         10  
186             }
187             elsif ( $check eq 'file' ) {
188 2         6 @bad = grep { ! -f $_ } @$value;
  2         71  
189             }
190             elsif ( $check eq 'dir' ) {
191 2         6 @bad = grep { ! -d $_ } @$value;
  6         69  
192             }
193             elsif ( $check eq 'number' ) {
194 4         21 @bad = grep { m/\D/ } @$value;
  11         62  
195             }
196              
197 12 100       77 return unless @bad;
198 5   66     30 my $type = $ref || $check;
199 5         84 die "Validation Failed for '$flag=$type': " . join( ", ", @bad ) . "\n";
200             }
201              
202             sub _flag_name {
203 24     24   37 my $self = shift;
204 24         57 my ( $key ) = @_;
205              
206             # Exact match
207             return $self->opts->{$key}->{name}
208 24 100       306 if $self->opts->{$key};
209              
210 4         17 my %matches = map { $self->opts->{$_}->{name} => 1 }
211 22         276 grep { m/^$key/ }
212 4         9 keys %{ $self->opts };
  4         14  
213 4         18 my @matches = keys %matches;
214              
215 4 100       46 die "partial option '$key' is ambiguous, could be: " . join( ", " => @matches ) . "\n"
216             if @matches > 1;
217              
218 3 100       28 die "unknown option '$key'\n"
219             unless @matches;
220              
221 2         18 return $matches[0];
222             }
223              
224             1;
225              
226             __END__