File Coverage

blib/lib/Ask/Question.pm
Criterion Covered Total %
statement 89 156 57.0
branch 38 106 35.8
condition 15 68 22.0
subroutine 10 13 76.9
pod 1 3 33.3
total 153 346 44.2


line stmt bran cond sub pod time code
1 1     1   26 use 5.008008;
  1         4  
2 1     1   8 use strict;
  1         2  
  1         25  
3 1     1   4 use warnings;
  1         2  
  1         71  
4              
5             package Ask::Question;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.015';
9              
10 1     1   7 use Moo;
  1         2  
  1         8  
11 1     1   392 use Scalar::Util 'blessed';
  1         2  
  1         153  
12              
13             use overload (
14 2     2   2442 '&{}' => sub { shift->coderef },
15 1         11 fallback => 1,
16 1     1   8 );
  1         2  
17              
18 0     0 1 0 sub ask { shift->coderef->() }
19              
20             has backend => ( is => 'lazy' );
21             has type => ( is => 'rwp', predicate => 1 );
22             has spec => ( is => 'rwp', predicate => 1 );
23             has title => ( is => 'rwp', predicate => 1 );
24             has text => ( is => 'rwp', predicate => 1 );
25             has multiple => ( is => 'rwp' );
26             has choices => ( is => 'rwp' );
27             has coderef => ( is => 'lazy', init_arg => undef );
28             has default => ( is => 'rwp', predicate => 1 );
29             has method => ( is => 'rwp' );
30              
31             sub _build_backend {
32 0     0   0 require Ask;
33 0         0 'Ask'->instance;
34             }
35              
36             sub BUILDARGS {
37 1     1 0 1837 my ( $class, @args ) = ( shift, @_ );
38 1 50 33     7 @args == 1 and ref $args[0] and return $args[0];
39 1 50       7 unshift @args, 'text' if @args % 2;
40 1         23 +{@args};
41             }
42              
43             sub isa { # trick Moose
44 0 0   0 0 0 return 1 if $_[1] eq __PACKAGE__;
45 0 0       0 return 1 if $_[1] eq 'Class::MOP::Method';
46 0 0       0 return 1 if $_[1] eq 'Moo::Object';
47 0 0       0 return 1 if $_[1] eq 'UNIVERSAL';
48 0         0 return 0;
49             }
50              
51             sub _build_coderef {
52 1     1   12 my ( $self ) = ( shift );
53            
54             # Avoid closing over $self
55 1         20 my $ask = $self->backend;
56 1         14 my $type = $self->type;
57 1         5 my $choices = $self->choices;
58 1         8 my $multiple = $self->multiple;
59 1   50     24 my $spec = $self->spec || {};
60 1 50       8 my $default = $self->has_default ? $self->default : $spec->{default};
61 1 50       6 my $text = $self->has_text ? $self->text : $spec->{documentation};
62 1         5 my $method = $self->method;
63 1         5 my $title = $self->title;
64            
65 1 50 33     9 undef $default if ( blessed $default and $default == $self );
66            
67             return sub {
68 1     1   5623 my @args = @_;
69 1         4 my ( $instance ) = ( @args );
70            
71 1         4 my $local_text = $text;
72 1 50       5 if ( ref $local_text ) {
73 0         0 $local_text = $local_text->( @args );
74             }
75 1 50       5 if ( not defined $local_text ) {
76 0         0 $local_text = '?';
77             }
78            
79 1         3 my $local_default = $default;
80 1 50       3 if ( ref $local_default ) {
81 0         0 $local_default = $local_default->( @args );
82             }
83            
84 1 50 33     14 unless ( blessed($ask) and $ask->DOES('Ask::API') ) {
85 0         0 $ask = $ask->();
86             }
87            
88 1         48 my $local_type = $type;
89 1 50       4 if ( not ref $local_type ) {
90            
91 0   0     0 $local_type ||= $spec->{type};
92            
93 0 0 0     0 if ( defined $local_type and not ref $local_type ) {
    0 0        
    0 0        
94 0         0 require Type::Utils;
95 0         0 $local_type = Type::Utils::dwim_type(
96             $local_type,
97             for => ref( $instance ),
98             );
99             }
100            
101             elsif ( defined $spec->{'isa'} and not ref $local_type ) {
102             $local_type =
103             ref( $spec->{'isa'} )
104             ? $spec->{'isa'}
105 0 0       0 : do {
106 0         0 require Type::Utils;
107             Type::Utils::dwim_type(
108 0         0 $spec->{'isa'},
109             for => ref( $instance ),
110             fallback => ['make_class_type'],
111             );
112             };
113             } #/ elsif ( defined $spec->{'isa'...})
114            
115             elsif ( defined $spec->{'does'} and not ref $local_type ) {
116             $local_type =
117             ref( $spec->{'does'} )
118             ? $spec->{'does'}
119 0 0       0 : do {
120 0         0 require Type::Utils;
121             Type::Utils::dwim_type(
122 0         0 $spec->{'does'},
123             for => ref( $instance ),
124             fallback => ['make_role_type'],
125             );
126             };
127             } #/ elsif ( defined $spec->{'does'...})
128             } #/ if ( not ref $local_type)
129            
130 1         2 my $local_multiple = $multiple;
131 1 50 33     9 if ( blessed $local_type and not defined $local_multiple ) {
132 1         8 require Types::Standard;
133 1         5 $local_multiple = ( $local_type <= Types::Standard::ArrayRef() );
134             }
135            
136 1         460 my $local_choices = $choices;
137 1 50 33     12 if ( defined $local_type
      33        
138             and blessed $local_type
139             and not defined $local_choices )
140             {
141 1         8 my $map = sub { [ map [ $_ x 2 ], @{ +shift } ] };
  0         0  
  0         0  
142 1         6 require Types::Standard;
143 1 50 33     4 if ( $local_type->isa( 'Type::Tiny::Enum' ) ) {
    50          
    50          
144 0         0 $local_choices = $map->( $local_type->unique_values );
145             }
146             elsif ( $local_type->isa( 'Moose::Meta::TypeConstraint::Enum' ) ) {
147 0         0 $local_choices = $map->( $local_type->values );
148             }
149             elsif ( $local_type <= Types::Standard::ArrayRef()
150             and $local_type->is_parameterized )
151             {
152 1         364 my $tp = $local_type->type_parameter;
153 1 50       11 if ( $tp->isa( 'Type::Tiny::Enum' ) ) {
    50          
154 0         0 $local_choices = $map->( $tp->unique_values );
155             }
156             elsif ( $tp->isa( 'Moose::Meta::TypeConstraint::Enum' ) ) {
157 0         0 $local_choices = $map->( $tp->values );
158             }
159             } #/ elsif ( $local_type <= Types::Standard::ArrayRef...)
160             } #/ if ( defined $local_type...)
161            
162 1         26 my $is_bool;
163 1 50 33     24 if ( defined $local_type and blessed $local_type ) {
164 1         8 require Types::Standard;
165 1         7 $is_bool = !!( $local_type <= Types::Standard::Bool() );
166             }
167            
168 1         1003 my ( $is_path, $is_dir, $is_abs );
169 1 50 33     9 if ( defined $local_type and blessed $local_type ) {
170            
171 1 50       2 if ( eval { require Types::Path::Tiny } ) {
  1         257  
172 0         0 $is_path = !!( $local_type <= Types::Path::Tiny::Path() );
173 0 0       0 my $path_type = $is_path ? $local_type : undef;
174            
175 0         0 require Types::Standard;
176 0 0 0     0 if ( !$is_path
      0        
177             and $local_type <= Types::Standard::ArrayRef()
178             and $local_type->is_parameterized )
179             {
180 0         0 my $tp = $local_type->type_parameter;
181 0 0       0 if ( $tp <= Types::Path::Tiny::Path() ) {
182 0         0 $is_path = 1;
183 0         0 $local_multiple = 1;
184 0         0 $path_type = $tp;
185             }
186             } #/ if ( !$is_path and $local_type...)
187            
188 0 0       0 if ( $is_path ) {
189 0   0     0 $is_dir = ( $path_type <= Types::Path::Tiny::Dir() )
190             || ( $path_type <= Types::Path::Tiny::AbsDir() );
191 0   0     0 $is_abs =
192             ( $path_type <= Types::Path::Tiny::AbsPath() )
193             || ( $path_type <= Types::Path::Tiny::AbsFile() )
194             || ( $path_type <= Types::Path::Tiny::AbsDir() );
195             }
196             } #/ if ( eval { require Types::Path::Tiny...})
197             } #/ if ( defined $local_type...)
198            
199 1 50       21 my @common = (
    50          
200             text => $local_text,
201             defined( $title ) ? ( title => $title ) : (),
202             defined( $local_default ) ? ( default => $local_default ) : (),
203             );
204            
205             my $get_answer = sub {
206            
207 2 50 33     33 if ( $method ) {
    50 33        
    50          
    50          
    50          
    50          
    50          
208 0         0 my $str = $ask->$method(
209             choices => $choices,
210             multiple => $multiple,
211             @common,
212             );
213 0         0 chomp $str;
214 0         0 return $str;
215             }
216            
217             elsif ( $local_multiple and $local_choices ) {
218 0         0 my @values = $ask->multiple_choice( @common, choices => $local_choices );
219 0         0 return \@values;
220             }
221            
222             elsif ( $local_choices ) {
223 0         0 return $ask->single_choice( @common, choices => $local_choices );
224             }
225            
226             elsif ( $local_multiple and $is_path ) {
227 0         0 require Path::Tiny;
228 0         0 my @paths = map( 'Path::Tiny'->new( $_ ),
229             $ask->file_selection( @common, directory => $is_dir, multiple => 1 ),
230             );
231 0 0       0 if ( $is_abs ) {
232 0         0 @paths = map $_->absolute, @paths;
233             }
234 0         0 return \@paths;
235             } #/ elsif ( $local_multiple and...)
236            
237             elsif ( $is_path ) {
238 0         0 require Path::Tiny;
239 0         0 my $path = 'Path::Tiny'->new(
240             $ask->file_selection( @common, directory => $is_dir, multiple => 0 ),
241             );
242 0 0       0 return $is_abs ? $path->absolute : $path;
243             }
244            
245             elsif ( $is_bool ) {
246 0         0 return $ask->question( @common, ok_label => 'TRUE', cancel_label => 'FALSE' );
247             }
248            
249             elsif ( $local_multiple ) {
250 2         5 my @strings;
251 2         3 STRING: while ( 1 ) {
252 6         22 chomp( my $str = $ask->entry( @common ) );
253 6 100       51 if ( length $str ) {
254 4         28 push @strings, $str;
255             }
256             else {
257 2         7 last STRING;
258             }
259 4 50       14 return if @strings >= 100;
260             } #/ STRING: while ( 1 )
261 2         6 return \@strings;
262             } #/ elsif ( $local_multiple )
263            
264             else {
265 0         0 chomp( my $str = $ask->entry( @common ) );
266 0         0 return $str;
267             }
268            
269 1         18 }; # sub $get_answer
270            
271 1         3 my $answer;
272 1         2 my $tries = 0;
273 1         5 TRY: while ( !defined $answer ) {
274            
275 2         8 $answer = $get_answer->();
276 2         5 ++$tries;
277            
278 2 50       12 if ( blessed $local_type ) {
    0          
279 2         11 my $okay = $local_type->check( $answer );
280            
281 2 50 33     71 if ( !$okay
      33        
282             and $local_type->can( 'has_coercion' )
283             and $local_type->has_coercion )
284             {
285 2         119 $answer = $local_type->coerce( $answer );
286 2         1492 $okay = $local_type->check( $answer );
287             }
288            
289 2 100       43 if ( not $okay ) {
290 1         5 $ask->error( text => $local_type->get_message( $answer ) );
291 1         11 $answer = undef;
292             }
293             } #/ if ( blessed $local_type)
294            
295             elsif ( ref $local_type ) {
296 0         0 local $@;
297 0         0 my $okay = eval { $local_type->( $answer ); 1 };
  0         0  
  0         0  
298 0 0       0 if ( not $okay ) {
299 0         0 $ask->error( text => $@ );
300 0         0 $answer = undef;
301             }
302             }
303            
304 2 50 33     15 if ( $tries >= 3 and not defined $answer ) {
305 0         0 $ask->error( text => 'Too many retries!' );
306 0         0 last TRY;
307             }
308             } #/ TRY: while ( !defined $answer )
309            
310 1 50       52 return $answer if defined $answer;
311 0           return $local_default;
312 1         15 }; # built sub
313             } #/ sub _build_coderef
314              
315             1;
316              
317             __END__