Ask
view release on metacpan or search on metacpan
lib/Ask/Caroline.pm view on Meta::CPAN
use 5.008008;
use strict;
use warnings;
package Ask::Caroline;
our $AUTHORITY = 'cpan:TOBYINK';
our $VERSION = '0.015';
use Moo;
use Caroline ();
use Path::Tiny 'path';
use Term::ANSIColor 'colored';
use namespace::autoclean;
with 'Ask::API';
has caroline => (
is => 'lazy',
default => sub {
my $self = shift;
Scalar::Util::weaken( my $weak = $self );
'Caroline'->new(
completion_callback => sub {
return unless $weak && $weak->has_completion;
$weak->completion->( @_ );
},
);
},
);
has completion => ( is => 'rw', predicate => 1, clearer => 1 );
sub BUILD {
STDOUT->autoflush( 1 );
}
sub is_usable {
my ( $self ) = @_;
-t STDIN and -t STDOUT;
}
sub quality {
my ( $self ) = ( shift );
( ref( $self ) ? $self : 'Caroline'->new )->is_supported ? 90 : 30;
}
sub entry {
my ( $self, %opts ) = ( shift, @_ );
$opts{prompt} = 'entry> ' unless exists $opts{prompt};
if ( exists $opts{completion} ) {
$self->completion( $opts{completion} );
}
else {
$self->completion(
sub {
return $opts{default};
}
);
}
if ( exists $opts{text} ) {
$self->info(
text => $opts{text},
colour => $opts{colour} || 'bright_cyan',
);
}
my ( $line, $tio );
if (
$opts{hide_text}
and do { require POSIX; $tio = 'POSIX::Termios'->new }
)
{
$tio->getattr( 0 );
$tio->setlflag( $tio->getlflag & ~POSIX::ECHO() );
$tio->setattr( 0 );
print STDOUT $opts{prompt}; # no new line;
STDOUT->flush;
chomp( $line = <STDIN> );
$tio->setlflag( $tio->getlflag | POSIX::ECHO() );
$tio->setattr( 0 );
print STDOUT "\r\n";
STDOUT->flush;
} #/ if ( $opts{hide_text} ...)
else {
chomp( $line = $self->caroline->readline( $opts{prompt} ) );
}
$self->clear_completion;
return $line;
} #/ sub entry
sub question {
my ( $self, %opts ) = ( shift, @_ );
$opts{prompt} = 'y/n> ' unless exists $opts{prompt};
my $response = $self->entry( %opts );
my $lang = $self->_lang_support( $opts{lang} );
$lang->boolean( $response );
}
sub print_findings {
my ( $self, $findings, $fh ) = ( shift, @_ );
if ( my @copy = @$findings ) {
print {$fh} "\r\n";
my $longest = 0;
for ( @copy ) { $longest = length if length > $longest }
my $per_line = int( 80 / ( $longest + 2 ) ) || 1;
my $template = '%-' . ( $longest + 2 ) . 's';
while ( @copy ) {
my @chunk = splice @copy, 0, $per_line;
while ( @chunk < $per_line ) {
push @chunk, '';
}
printf {$fh} $template x $per_line, @chunk;
print {$fh} "\r\n";
}
$fh->flush;
return 1;
} #/ if ( my @copy = @$findings)
return;
} #/ sub print_findings
sub file_selection {
my ( $self, %opts ) = ( shift, @_ );
my $single = !$opts{multiple};
$opts{prompt} = sprintf( '%s> ', $opts{directory} ? 'directory' : 'file' )
unless exists $opts{prompt};
unless ( $opts{text} ) {
$opts{text} =
$single
? (
$opts{directory} ? 'Please choose a directory.' : 'Please choose a file.' )
: (
$opts{directory}
? 'Please choose some directories.'
: 'Please choose some files.'
);
} #/ unless ( $opts{text} )
$self->info(
text => $opts{text},
colour => $opts{colour} || 'bright_cyan',
);
$self->info(
text =>
'Please enter one choice per line; leave a blank line to stop choosing.',
colour => $opts{colour} || 'bright_cyan',
) unless $single;
$self->completion(
sub {
my $raw = shift;
$raw = '.' unless length $raw;
my $got = path( $raw );
my @kids;
if ( $got->is_dir ) {
@kids = $opts{directory} ? grep( $_->is_dir, $got->children ) : $got->children;
}
else {
my $dir = $got->parent;
my $stem = quotemeta( $got->basename );
@kids = $opts{directory} ? grep( $_->is_dir, $dir->children ) : $dir->children;
@kids = grep $_->basename =~ /^$stem/, @kids;
}
my @printable = sort map $_->basename . ( $_->is_dir ? '/' : '' ), @kids;
unshift @printable, '.' if $got->is_dir;
$self->print_findings( \@printable, \*STDOUT );
return map "$_", ( $got->is_dir ? $got : () ), @kids;
}
);
my @chosen;
CHOICE: while ( 1 ) {
chomp( my $line = $self->caroline->readline( $opts{prompt} ) );
if ( $line eq '' ) {
$single ? next( CHOICE ) : last( CHOICE );
}
if ( $opts{existing} and not path( $line )->exists ) {
$self->error(
text => sprintf( 'Does not exist: %s. Please try again.', $line ),
);
}
elsif ( $opts{directory} and not path( $line )->is_dir ) {
$self->error(
text => sprintf( 'Not a directory: %s. Please try again.', $line ),
);
}
else {
push @chosen, $line;
last CHOICE if $single;
}
} #/ CHOICE: while ( 1 )
return $chosen[0] if $single;
$self->clear_completion;
return @chosen;
} #/ sub file_selection
sub single_choice {
shift->multiple_choice( @_, _single => 1 );
}
sub multiple_choice {
my ( $self, %opts ) = ( shift, @_ );
my $single = $opts{_single};
$opts{prompt} = 'choice> ' unless exists $opts{prompt};
if ( exists $opts{text} ) {
$self->info(
text => $opts{text},
colour => $opts{colour} || 'bright_cyan',
);
}
my %allowed;
my @choices_list = map {
$allowed{ $_->[0] } = 1;
defined $_->[1] ? sprintf( '"%s" (%s)', @$_ ) : "\"$_->[0]\""
} @{ $opts{choices} };
$self->info(
text => sprintf( 'Choices: %s.', join( q[, ], @choices_list ) ),
colour => $opts{colour} || 'bright_cyan',
) unless $opts{hide_choices};
$self->info(
text =>
'Please enter one choice per line; leave a blank line to stop choosing.',
colour => $opts{colour} || 'bright_cyan',
) unless $single;
$self->completion(
sub {
my $got = quotemeta( shift );
my @found = grep /^$got/, map $_->[0], @{ $opts{choices} };
$self->print_findings( \@found, \*STDOUT );
return @found;
}
);
my @chosen;
CHOICE: while ( 1 ) {
chomp( my $line = $self->caroline->readline( $opts{prompt} ) );
if ( $line eq '' ) {
$single ? next( CHOICE ) : last( CHOICE );
}
if ( $allowed{$line} ) {
push @chosen, $line;
last CHOICE if $single;
}
else {
$self->error(
text => sprintf( 'Not valid: %s. Please try again.', $line ),
);
$self->info(
text => sprintf( 'Choices: %s.', join( q[, ], @choices_list ) ),
colour => $opts{colour} || 'bright_cyan',
) unless $opts{hide_choices};
}
} #/ CHOICE: while ( 1 )
return $chosen[0] if $single;
$self->clear_completion;
return @chosen;
} #/ sub multiple_choice
sub info {
my ( $self, %opts ) = ( shift, @_ );
chomp( my $text = $opts{text} );
if ( $opts{colour} ) {
$text = colored( [ $opts{colour} ], $text );
}
print STDOUT $text, "\n";
STDOUT->flush;
}
sub warning {
my ( $self, %opts ) = ( shift, @_ );
$opts{colour} ||= 'bright_yellow';
$self->info( %opts );
}
sub error {
my ( $self, %opts ) = ( shift, @_ );
$opts{colour} ||= 'bright_red';
$self->info( %opts );
}
1;
__END__
=head1 NAME
Ask::Caroline - read lines never seemed so good
=head1 SYNOPSIS
my $ask = Ask::Caroline->new;
( run in 1.109 second using v1.01-cache-2.11-cpan-0b5f733616e )