File Coverage

blib/lib/Ask/Caroline.pm
Criterion Covered Total %
statement 25 157 15.9
branch 2 84 2.3
condition 0 29 0.0
subroutine 9 23 39.1
pod 0 12 0.0
total 36 305 11.8


line stmt bran cond sub pod time code
1 3     3   1562 use 5.008008;
  3         12  
2 3     3   17 use strict;
  3         11  
  3         66  
3 3     3   18 use warnings;
  3         6  
  3         191  
4              
5             package Ask::Caroline;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.014';
9              
10 3     3   18 use Moo;
  3         8  
  3         26  
11 3     3   2623 use Caroline ();
  3         162506  
  3         90  
12 3     3   27 use Path::Tiny 'path';
  3         7  
  3         154  
13 3     3   2232 use Term::ANSIColor 'colored';
  3         24299  
  3         2394  
14 3     3   40 use namespace::autoclean;
  3         7  
  3         30  
15              
16             with 'Ask::API';
17              
18             has caroline => (
19             is => 'lazy',
20             default => sub {
21             my $self = shift;
22             Scalar::Util::weaken( my $weak = $self );
23             'Caroline'->new(
24             completion_callback => sub {
25             return unless $weak && $weak->has_completion;
26             $weak->completion->( @_ );
27             },
28             );
29             },
30             );
31              
32             has completion => ( is => 'rw', predicate => 1, clearer => 1 );
33              
34             sub BUILD {
35 0     0 0 0 STDOUT->autoflush( 1 );
36             }
37              
38             sub is_usable {
39 0     0 0 0 my ( $self ) = @_;
40 0 0       0 -t STDIN and -t STDOUT;
41             }
42              
43             sub quality {
44 9     9 0 22 my ( $self ) = ( shift );
45            
46 9 50       55 ( ref( $self ) ? $self : 'Caroline'->new )->is_supported ? 90 : 30;
    50          
47             }
48              
49             sub entry {
50 0     0 0   my ( $self, %opts ) = ( shift, @_ );
51 0 0         $opts{prompt} = 'entry> ' unless exists $opts{prompt};
52            
53 0 0         if ( exists $opts{completion} ) {
54 0           $self->completion( $opts{completion} );
55             }
56             else {
57             $self->completion(
58             sub {
59 0     0     return $opts{default};
60             }
61 0           );
62             }
63            
64 0 0         if ( exists $opts{text} ) {
65             $self->info(
66             text => $opts{text},
67 0   0       colour => $opts{colour} || 'bright_cyan',
68             );
69             }
70            
71 0           my ( $line, $tio );
72            
73 0 0 0       if (
74             $opts{hide_text}
75 0           and do { require POSIX; $tio = 'POSIX::Termios'->new }
  0            
76             )
77             {
78 0           $tio->getattr( 0 );
79 0           $tio->setlflag( $tio->getlflag & ~POSIX::ECHO() );
80 0           $tio->setattr( 0 );
81 0           print STDOUT $opts{prompt}; # no new line;
82 0           STDOUT->flush;
83 0           chomp( $line = );
84 0           $tio->setlflag( $tio->getlflag | POSIX::ECHO() );
85 0           $tio->setattr( 0 );
86 0           print STDOUT "\r\n";
87 0           STDOUT->flush;
88             } #/ if ( $opts{hide_text} ...)
89             else {
90 0           chomp( $line = $self->caroline->readline( $opts{prompt} ) );
91             }
92            
93 0           $self->clear_completion;
94            
95 0           return $line;
96             } #/ sub entry
97              
98             sub question {
99 0     0 0   my ( $self, %opts ) = ( shift, @_ );
100 0 0         $opts{prompt} = 'y/n> ' unless exists $opts{prompt};
101            
102 0           my $response = $self->entry( %opts );
103 0           my $lang = $self->_lang_support( $opts{lang} );
104 0           $lang->boolean( $response );
105             }
106              
107             sub print_findings {
108 0     0 0   my ( $self, $findings, $fh ) = ( shift, @_ );
109            
110 0 0         if ( my @copy = @$findings ) {
111 0           print {$fh} "\r\n";
  0            
112 0           my $longest = 0;
113 0 0         for ( @copy ) { $longest = length if length > $longest }
  0            
114 0   0       my $per_line = int( 80 / ( $longest + 2 ) ) || 1;
115 0           my $template = '%-' . ( $longest + 2 ) . 's';
116 0           while ( @copy ) {
117 0           my @chunk = splice @copy, 0, $per_line;
118 0           while ( @chunk < $per_line ) {
119 0           push @chunk, '';
120             }
121 0           printf {$fh} $template x $per_line, @chunk;
  0            
122 0           print {$fh} "\r\n";
  0            
123             }
124 0           $fh->flush;
125 0           return 1;
126             } #/ if ( my @copy = @$findings)
127            
128 0           return;
129             } #/ sub print_findings
130              
131             sub file_selection {
132 0     0 0   my ( $self, %opts ) = ( shift, @_ );
133            
134 0           my $single = !$opts{multiple};
135            
136             $opts{prompt} = sprintf( '%s> ', $opts{directory} ? 'directory' : 'file' )
137 0 0         unless exists $opts{prompt};
    0          
138            
139 0 0         unless ( $opts{text} ) {
140             $opts{text} =
141             $single
142             ? (
143             $opts{directory} ? 'Please choose a directory.' : 'Please choose a file.' )
144             : (
145             $opts{directory}
146 0 0         ? 'Please choose some directories.'
    0          
    0          
147             : 'Please choose some files.'
148             );
149             } #/ unless ( $opts{text} )
150            
151             $self->info(
152             text => $opts{text},
153 0   0       colour => $opts{colour} || 'bright_cyan',
154             );
155            
156             $self->info(
157             text =>
158             'Please enter one choice per line; leave a blank line to stop choosing.',
159 0 0 0       colour => $opts{colour} || 'bright_cyan',
160             ) unless $single;
161            
162             $self->completion(
163             sub {
164 0     0     my $raw = shift;
165 0 0         $raw = '.' unless length $raw;
166 0           my $got = path( $raw );
167            
168 0           my @kids;
169            
170 0 0         if ( $got->is_dir ) {
171 0 0         @kids = $opts{directory} ? grep( $_->is_dir, $got->children ) : $got->children;
172             }
173             else {
174 0           my $dir = $got->parent;
175 0           my $stem = quotemeta( $got->basename );
176            
177 0 0         @kids = $opts{directory} ? grep( $_->is_dir, $dir->children ) : $dir->children;
178 0           @kids = grep $_->basename =~ /^$stem/, @kids;
179             }
180            
181 0 0         my @printable = sort map $_->basename . ( $_->is_dir ? '/' : '' ), @kids;
182 0 0         unshift @printable, '.' if $got->is_dir;
183            
184 0           $self->print_findings( \@printable, \*STDOUT );
185 0 0         return map "$_", ( $got->is_dir ? $got : () ), @kids;
186             }
187 0           );
188            
189 0           my @chosen;
190            
191 0           CHOICE: while ( 1 ) {
192            
193 0           chomp( my $line = $self->caroline->readline( $opts{prompt} ) );
194            
195 0 0         if ( $line eq '' ) {
196 0 0         $single ? next( CHOICE ) : last( CHOICE );
197             }
198            
199 0 0 0       if ( $opts{existing} and not path( $line )->exists ) {
    0 0        
200 0           $self->error(
201             text => sprintf( 'Does not exist: %s. Please try again.', $line ),
202             );
203             }
204             elsif ( $opts{directory} and not path( $line )->is_dir ) {
205 0           $self->error(
206             text => sprintf( 'Not a directory: %s. Please try again.', $line ),
207             );
208             }
209             else {
210 0           push @chosen, $line;
211 0 0         last CHOICE if $single;
212             }
213             } #/ CHOICE: while ( 1 )
214            
215 0 0         return $chosen[0] if $single;
216            
217 0           $self->clear_completion;
218            
219 0           return @chosen;
220             } #/ sub file_selection
221              
222             sub single_choice {
223 0     0 0   shift->multiple_choice( @_, _single => 1 );
224             }
225              
226             sub multiple_choice {
227 0     0 0   my ( $self, %opts ) = ( shift, @_ );
228            
229 0           my $single = $opts{_single};
230            
231 0 0         $opts{prompt} = 'choice> ' unless exists $opts{prompt};
232            
233 0 0         if ( exists $opts{text} ) {
234             $self->info(
235             text => $opts{text},
236 0   0       colour => $opts{colour} || 'bright_cyan',
237             );
238             }
239            
240 0           my %allowed;
241             my @choices_list = map {
242 0           $allowed{ $_->[0] } = 1;
243 0 0         defined $_->[1] ? sprintf( '"%s" (%s)', @$_ ) : "\"$_->[0]\""
244 0           } @{ $opts{choices} };
  0            
245            
246             $self->info(
247             text => sprintf( 'Choices: %s.', join( q[, ], @choices_list ) ),
248             colour => $opts{colour} || 'bright_cyan',
249 0 0 0       ) unless $opts{hide_choices};
250            
251             $self->info(
252             text =>
253             'Please enter one choice per line; leave a blank line to stop choosing.',
254 0 0 0       colour => $opts{colour} || 'bright_cyan',
255             ) unless $single;
256            
257             $self->completion(
258             sub {
259 0     0     my $got = quotemeta( shift );
260 0           my @found = grep /^$got/, map $_->[0], @{ $opts{choices} };
  0            
261 0           $self->print_findings( \@found, \*STDOUT );
262 0           return @found;
263             }
264 0           );
265            
266 0           my @chosen;
267            
268 0           CHOICE: while ( 1 ) {
269            
270 0           chomp( my $line = $self->caroline->readline( $opts{prompt} ) );
271            
272 0 0         if ( $line eq '' ) {
273 0 0         $single ? next( CHOICE ) : last( CHOICE );
274             }
275            
276 0 0         if ( $allowed{$line} ) {
277 0           push @chosen, $line;
278 0 0         last CHOICE if $single;
279             }
280             else {
281 0           $self->error(
282             text => sprintf( 'Not valid: %s. Please try again.', $line ),
283             );
284             $self->info(
285             text => sprintf( 'Choices: %s.', join( q[, ], @choices_list ) ),
286             colour => $opts{colour} || 'bright_cyan',
287 0 0 0       ) unless $opts{hide_choices};
288             }
289             } #/ CHOICE: while ( 1 )
290            
291 0 0         return $chosen[0] if $single;
292            
293 0           $self->clear_completion;
294            
295 0           return @chosen;
296             } #/ sub multiple_choice
297              
298             sub info {
299 0     0 0   my ( $self, %opts ) = ( shift, @_ );
300 0           chomp( my $text = $opts{text} );
301 0 0         if ( $opts{colour} ) {
302 0           $text = colored( [ $opts{colour} ], $text );
303             }
304 0           print STDOUT $text, "\n";
305 0           STDOUT->flush;
306             }
307              
308             sub warning {
309 0     0 0   my ( $self, %opts ) = ( shift, @_ );
310 0   0       $opts{colour} ||= 'bright_yellow';
311 0           $self->info( %opts );
312             }
313              
314             sub error {
315 0     0 0   my ( $self, %opts ) = ( shift, @_ );
316 0   0       $opts{colour} ||= 'bright_red';
317 0           $self->info( %opts );
318             }
319              
320             1;
321              
322             __END__