summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog5
-rwxr-xr-xbin/raps222
-rw-r--r--lib/App/Raps2.pm418
-rw-r--r--lib/App/Raps2/Password.pm227
-rw-r--r--lib/App/Raps2/UI.pm208
-rw-r--r--t/20-app-raps2-password.t2
6 files changed, 455 insertions, 427 deletions
diff --git a/Changelog b/Changelog
index 0e99e10..2b97f42 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,8 @@
+git HEAD
+
+ * Rename App::Raps2::Password's crypt method to bcrypt to avoid name
+ confusion with Perl's builtin function
+
App::Raps2 0.3 - Wed May 18 2011
* Terminal input is now read via Term::ReadLine (included in perl core)
diff --git a/bin/raps2 b/bin/raps2
index 71ccbea..27dcdbc 100755
--- a/bin/raps2
+++ b/bin/raps2
@@ -9,21 +9,21 @@ use 5.010;
use App::Raps2;
my $raps2 = App::Raps2->new();
-my ($action, @args) = @ARGV;
+my ( $action, @args ) = @ARGV;
-my $VERSION = '0.3';
+our $VERSION = '0.3';
$raps2->sanity_check();
$raps2->load_config();
given ($action) {
- when ('add') { $raps2->cmd_add(@args) }
- when ('del') { $raps2->cmd_remove(@args) }
- when ('dump') { $raps2->cmd_dump(@args) }
- when ('edit') { $raps2->cmd_edit(@args) }
- when ('get') { $raps2->cmd_get(@args) }
- when ('info') { $raps2->cmd_info(@args) }
- when ('list') { $raps2->cmd_list(@args) }
+ when ('add') { $raps2->cmd_add(@args) }
+ when ('del') { $raps2->cmd_remove(@args) }
+ when ('dump') { $raps2->cmd_dump(@args) }
+ when ('edit') { $raps2->cmd_edit(@args) }
+ when ('get') { $raps2->cmd_get(@args) }
+ when ('info') { $raps2->cmd_info(@args) }
+ when ('list') { $raps2->cmd_list(@args) }
when ('version') { say "raps2 version ${VERSION}" }
}
@@ -92,6 +92,10 @@ List all saved accounts with their respective Logins and URLs
=back
+=head1 OPTIONS
+
+None.
+
=head1 EXIT STATUS
zero on success, non-zero otherwise.
diff --git a/lib/App/Raps2.pm b/lib/App/Raps2.pm
index 0f1fdc4..4a204aa 100644
--- a/lib/App/Raps2.pm
+++ b/lib/App/Raps2.pm
@@ -1,32 +1,5 @@
package App::Raps2;
-=head1 NAME
-
-App::Raps2 - A Password safe
-
-=head1 SYNOPSIS
-
- use App::Raps2;
-
- my $raps2 = App::Raps2->new();
- my ($action, @args) = @ARGV;
-
- $raps2->sanity_check();
- $raps2->load_config();
-
- given ($action) {
- when ('add') { $raps2->cmd_add(@args) }
- when ('dump') { $raps2->cmd_dump(@args) }
- when ('get') { $raps2->cmd_get(@args) }
- when ('info') { $raps2->cmd_info(@args) }
- }
-
-=head1 DESCRIPTION
-
-B<App::Raps2> is the backend for B<raps2>, a simple commandline password safe.
-
-=cut
-
use strict;
use warnings;
use 5.010;
@@ -40,28 +13,8 @@ use File::Slurp qw(read_dir slurp write_file);
our $VERSION = '0.3';
-=head1 METHODS
-
-=over
-
-=item $raps2 = App::Raps2->new(I<%conf>)
-
-Returns a new B<App::Raps2> object.
-
-Accepted configuration parameters are:
-
-=over
-
-=item B<cost> => I<int>
-
-B<cost> of key setup, passed on to App::Raps2::Password(3pm).
-
-=back
-
-=cut
-
sub new {
- my ($obj, %conf) = @_;
+ my ( $obj, %conf ) = @_;
my $ref = {};
$ref->{xdg_conf} = config_home('raps2');
@@ -71,24 +24,17 @@ sub new {
$ref->{default} = \%conf;
- return bless($ref, $obj);
+ return bless( $ref, $obj );
}
-=item $raps2->file_to_hash(I<$file>)
-
-Reads $file (lines with key/value separated by whitespace) and returns a hash
-with its key/value pairs.
-
-=cut
-
sub file_to_hash {
- my ($self, $file) = @_;
+ my ( $self, $file ) = @_;
my %ret;
- for my $line (slurp($file)) {
- my ($key, $value) = split(qr{\s+}, $line);
+ for my $line ( slurp($file) ) {
+ my ( $key, $value ) = split( qr{ \s+ }x, $line );
- if (not ($key and $value)) {
+ if ( not( $key and $value ) ) {
next;
}
@@ -97,63 +43,44 @@ sub file_to_hash {
return %ret;
}
-=item $raps2->sanity_check()
-
-Create working directories (~/.config/raps2 and ~/.local/share/raps2, or the
-respective XDG environment variable contents), if they don't exist yet.
-
-Calls B<create_config> if no raps2 config was found.
-
-=cut
-
sub sanity_check {
my ($self) = @_;
- make_path($self->{xdg_conf});
- make_path($self->{xdg_data});
+ make_path( $self->{xdg_conf} );
+ make_path( $self->{xdg_data} );
- if (not -e $self->{xdg_conf} . '/password') {
+ if ( not -e $self->{xdg_conf} . '/password' ) {
$self->create_config();
}
return;
}
-=item $raps2->get_master_password()
-
-Asks the user for the master passphrase.
-
-=cut
-
sub get_master_password {
my ($self) = @_;
- my $pass = $self->ui->read_pw('Master Password', 0);
+ my $pass = $self->ui->read_pw( 'Master Password', 0 );
$self->{pass} = App::Raps2::Password->new(
- cost => $self->{default}->{cost},
- salt => $self->{master_salt},
+ cost => $self->{default}->{cost},
+ salt => $self->{master_salt},
passphrase => $pass,
);
- $self->{pass}->verify($self->{master_hash});
-}
-
-=item $raps2->create_config()
-
-Creates a default config and asks the user to set a master password.
+ $self->{pass}->verify( $self->{master_hash} );
-=cut
+ return;
+}
sub create_config {
my ($self) = @_;
my $cost = 12;
- my $pass = $self->ui->read_pw('Master Password', 1);
+ my $pass = $self->ui->read_pw( 'Master Password', 1 );
$self->{pass} = App::Raps2::Password->new(
- cost => $cost,
+ cost => $cost,
passphrase => $pass,
);
- my $hash = $self->pw->crypt();
+ my $hash = $self->pw->bcrypt();
my $salt = $self->pw->salt();
write_file(
@@ -162,61 +89,47 @@ sub create_config {
"salt ${salt}\n",
"hash ${hash}\n",
);
-}
-
-=item $raps2->load_config()
-Load config
-
-=cut
+ return;
+}
sub load_config {
my ($self) = @_;
- my %cfg = $self->file_to_hash($self->{xdg_conf} . '/password');
+ my %cfg = $self->file_to_hash( $self->{xdg_conf} . '/password' );
$self->{master_hash} = $cfg{hash};
$self->{master_salt} = $cfg{salt};
$self->{default}->{cost} //= $cfg{cost};
-}
-
-=item $raps2->pw()
-
-Returns the App::Raps2::Password(3pm) object.
-=cut
+ return;
+}
sub pw {
my ($self) = @_;
- if (defined $self->{pass}) {
+ if ( defined $self->{pass} ) {
return $self->{pass};
}
else {
- confess('No App::Raps2::Password object, did you call get_master_password?');
+ confess(
+ 'No App::Raps2::Password object, did you call get_master_password?'
+ );
}
-}
-
-=item $raps2->ui()
-
-Returns the App::Raps2::UI(3pm) object.
-=cut
+ return;
+}
sub ui {
my ($self) = @_;
+
return $self->{ui};
}
-=item $raps2->cmd_add(I<$name>)
-
-Adds a new password file called $name.
-
-=cut
-
sub cmd_add {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
- if (-e $pwfile) {
+ if ( -e $pwfile ) {
confess('Password file already exists');
}
@@ -225,39 +138,34 @@ sub cmd_add {
my $salt = $self->pw->create_salt();
my $url = $self->ui->read_line('URL');
my $login = $self->ui->read_line('Login');
- my $pass = $self->ui->read_pw('Password', 1);
+ my $pass = $self->ui->read_pw( 'Password', 1 );
my $extra = $self->ui->read_multiline('Additional content');
$self->pw->salt($salt);
- my $pass_hash = $self->pw->encrypt($pass);
+ my $pass_hash = $self->pw->encrypt($pass);
my $extra_hash = (
- $extra ?
- $self->pw->encrypt($extra) :
- q{}
+ $extra
+ ? $self->pw->encrypt($extra)
+ : q{}
);
-
write_file(
- $pwfile,
- "url ${url}\n",
+ $pwfile, "url ${url}\n",
"login ${login}\n",
"salt ${salt}\n",
"hash ${pass_hash}\n",
"extra ${extra_hash}\n",
);
-}
-
-=item $raps2->cmd_dump(I<$account>)
-Dumps the content of I<account>
-
-=cut
+ return;
+}
sub cmd_dump {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
- if (not -e $pwfile) {
+ if ( not -e $pwfile ) {
confess('Password file does not exist');
}
@@ -265,45 +173,42 @@ sub cmd_dump {
$self->get_master_password();
- $self->pw->salt($key{salt});
+ $self->pw->salt( $key{salt} );
$self->ui()->output(
- ['URL', $key{url}],
- ['Login', $key{login}],
- ['Password', $self->pw->decrypt($key{hash})],
+ [ 'URL', $key{url} ],
+ [ 'Login', $key{login} ],
+ [ 'Password', $self->pw->decrypt( $key{hash} ) ],
);
- if ($key{extra}) {
- print $self->pw->decrypt($key{extra});
+ if ( $key{extra} ) {
+ print $self->pw->decrypt( $key{extra} );
}
-}
-
-=item $raps2->cmd_edit(I<$acount>)
-Edit I<account>.
-
-=cut
+ return;
+}
sub cmd_edit {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
my $pass_hash;
- if (not -e $pwfile) {
+ if ( not -e $pwfile ) {
confess('Password file does not exist');
}
my %key = $self->file_to_hash($pwfile);
$self->get_master_password();
- $self->pw->salt($key{salt});
+ $self->pw->salt( $key{salt} );
- my $salt = $key{salt};
- my $url = $self->ui->read_line('URL', $key{url});
- my $login = $self->ui->read_line('Login', $key{login});
- my $pass = $self->ui->read_pw('New password (empty to keep old)', 1);
+ my $salt = $key{salt};
+ my $url = $self->ui->read_line( 'URL', $key{url} );
+ my $login = $self->ui->read_line( 'Login', $key{login} );
+ my $pass = $self->ui->read_pw( 'New password (empty to keep old)', 1 );
my $extra = $key{extra} // q{};
- if (length($pass)) {
+ if ( length($pass) ) {
$pass_hash = $self->pw->encrypt($pass);
}
else {
@@ -311,26 +216,22 @@ sub cmd_edit {
}
write_file(
- $pwfile,
- "url ${url}\n",
+ $pwfile, "url ${url}\n",
"login ${login}\n",
"salt ${salt}\n",
"hash ${pass_hash}\n",
"extra ${extra}\n",
);
-}
-
-=item $raps2->cmd_get(I<$name>)
-Puts the password saved in $name into the X clipboard.
-
-=cut
+ return;
+}
sub cmd_get {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
- if (not -e $pwfile) {
+ if ( not -e $pwfile ) {
confess('Password file does not exist');
}
@@ -338,79 +239,190 @@ sub cmd_get {
$self->get_master_password();
- $self->pw->salt($key{salt});
+ $self->pw->salt( $key{salt} );
- $self->ui()->to_clipboard($self->pw->decrypt($key{hash}));
+ $self->ui()->to_clipboard( $self->pw->decrypt( $key{hash} ) );
- if ($key{extra}) {
- print $self->pw->decrypt($key{extra})
+ if ( $key{extra} ) {
+ print $self->pw->decrypt( $key{extra} );
}
-}
-
-=item $raps2->cmd_info(I<$name>)
-Prints unencrypted information about $name.
-
-=cut
+ return;
+}
sub cmd_info {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
- if (not -e $pwfile) {
+ if ( not -e $pwfile ) {
confess('Password file does not exist');
}
my %key = $self->file_to_hash($pwfile);
- $self->ui()->output(
- ['URL', $key{url}],
- ['Login', $key{login}],
- );
-}
+ $self->ui()->output( [ 'URL', $key{url} ], [ 'Login', $key{login} ], );
-=item $raps2->cmd_list()
-
-Lists all saved passwords and their logins and urls
-
-=cut
+ return;
+}
sub cmd_list {
my ($self) = @_;
- my @files = read_dir($self->{xdg_data});
- for my $file (sort @files) {
- my %key = $self->file_to_hash($self->{xdg_data} . "/${file}");
+ my @files = read_dir( $self->{xdg_data} );
+
+ for my $file ( sort @files ) {
+ my %key = $self->file_to_hash( $self->{xdg_data} . "/${file}" );
$self->ui->list(
- ['Account', $file],
- ['Login', $key{login}],
- ['URL', $key{url}],
+ [ 'Account', $file ],
+ [ 'Login', $key{login} ],
+ [ 'URL', $key{url} ],
);
}
-}
-
-=item $raps2->cmd_remove(I<$name>)
-
-Remove (unlink) the account I<name>.
-=cut
+ return;
+}
sub cmd_remove {
- my ($self, $name) = @_;
+ my ( $self, $name ) = @_;
+
my $pwfile = $self->{xdg_data} . "/${name}";
- if (-e $pwfile) {
+ if ( -e $pwfile ) {
unlink($pwfile);
}
else {
say STDERR 'File did not exist, so could not be removed';
}
+
+ return;
}
+1;
+
+__END__
+
+=head1 NAME
+
+App::Raps2 - A Password safe
+
+=head1 SYNOPSIS
+
+ use App::Raps2;
+
+ my $raps2 = App::Raps2->new();
+ my ($action, @args) = @ARGV;
+
+ $raps2->sanity_check();
+ $raps2->load_config();
+
+ given ($action) {
+ when ('add') { $raps2->cmd_add(@args) }
+ when ('dump') { $raps2->cmd_dump(@args) }
+ when ('get') { $raps2->cmd_get(@args) }
+ when ('info') { $raps2->cmd_info(@args) }
+ }
+
+=head1 DESCRIPTION
+
+B<App::Raps2> is the backend for B<raps2>, a simple commandline password safe.
+
+=head1 VERSION
+
+This manual documents App::Raps2 version 0.3
+
+=head1 METHODS
+
+=over
+
+=item $raps2 = App::Raps2->new(I<%conf>)
+
+Returns a new B<App::Raps2> object.
+
+Accepted configuration parameters are:
+
+=over
+
+=item B<cost> => I<int>
+
+B<cost> of key setup, passed on to App::Raps2::Password(3pm).
+
=back
+=item $raps2->file_to_hash(I<$file>)
+
+Reads $file (lines with key/value separated by whitespace) and returns a hash
+with its key/value pairs.
+
+=item $raps2->sanity_check()
+
+Create working directories (~/.config/raps2 and ~/.local/share/raps2, or the
+respective XDG environment variable contents), if they don't exist yet.
+
+Calls B<create_config> if no raps2 config was found.
+
+=item $raps2->get_master_password()
+
+Asks the user for the master passphrase.
+
+=item $raps2->create_config()
+
+Creates a default config and asks the user to set a master password.
+
+=item $raps2->load_config()
+
+Load config
+
+=item $raps2->pw()
+
+Returns the App::Raps2::Password(3pm) object.
+
+=item $raps2->ui()
+
+Returns the App::Raps2::UI(3pm) object.
+
+=item $raps2->cmd_add(I<$name>)
+
+Adds a new password file called $name.
+
+=item $raps2->cmd_dump(I<$account>)
+
+Dumps the content of I<account>
+
+=item $raps2->cmd_edit(I<$acount>)
+
+Edit I<account>.
+
+=item $raps2->cmd_get(I<$name>)
+
+Puts the password saved in $name into the X clipboard.
+
+=item $raps2->cmd_info(I<$name>)
+
+Prints unencrypted information about $name.
+
+=item $raps2->cmd_list()
+
+Lists all saved passwords and their logins and urls
+
+=item $raps2->cmd_remove(I<$name>)
+
+Remove (unlink) the account I<name>.
+
+=back
+
+=head1 DIAGNOSTICS
+
+If anything goes wrong, B<App::Raps2> will die with a backtrace (using
+B<confess> from Carp(3pm)).
+
=head1 DEPENDENCIES
-L<App::Raps2::Password>, L<App::Raps2::UI>, L<File::BaseDir>, L<File::Slurp>.
+App::Raps2::Password(3pm), App::Raps2::UI(3pm), File::BaseDir(3pm),
+File::Slurp(3pm).
+
+=head1 BUGS AND LIMITATIONS
+
+Unknown.
=head1 AUTHOR
diff --git a/lib/App/Raps2/Password.pm b/lib/App/Raps2/Password.pm
index 1e33f58..1a3ab63 100644
--- a/lib/App/Raps2/Password.pm
+++ b/lib/App/Raps2/Password.pm
@@ -11,6 +11,103 @@ use Crypt::Eksblowfish::Bcrypt qw(bcrypt_hash en_base64 de_base64);
our $VERSION = '0.3';
+sub new {
+ my ( $obj, %conf ) = @_;
+
+ $conf{cost} //= 12;
+
+ if ( not defined $conf{salt} ) {
+ $conf{salt} = create_salt();
+ }
+
+ if ( length( $conf{salt} ) != 16 ) {
+ confess('incorrect salt length');
+ }
+
+ if ( not( defined $conf{passphrase} and length $conf{passphrase} ) ) {
+ confess('no passphrase given');
+ }
+
+ my $ref = \%conf;
+
+ return bless( $ref, $obj );
+}
+
+sub create_salt {
+ my ($self) = @_;
+ my $salt = q{};
+
+ for ( 1 .. 16 ) {
+ $salt .= chr( 0x21 + int( rand(90) ) );
+ }
+
+ return $salt;
+}
+
+sub salt {
+ my ( $self, $salt ) = @_;
+
+ if ( defined $salt ) {
+ if ( length($salt) != 16 ) {
+ confess('incorrect salt length');
+ }
+
+ $self->{salt} = $salt;
+ }
+
+ return $self->{salt};
+}
+
+sub encrypt {
+ my ( $self, $in ) = @_;
+
+ my $eksblowfish = Crypt::Eksblowfish->new( $self->{cost}, $self->{salt},
+ $self->{passphrase}, );
+ my $cbc = Crypt::CBC->new( -cipher => $eksblowfish );
+
+ return $cbc->encrypt_hex($in);
+}
+
+sub decrypt {
+ my ( $self, $in ) = @_;
+
+ my $eksblowfish = Crypt::Eksblowfish->new( $self->{cost}, $self->{salt},
+ $self->{passphrase}, );
+ my $cbc = Crypt::CBC->new( -cipher => $eksblowfish );
+
+ return $cbc->decrypt_hex($in);
+}
+
+sub bcrypt {
+ my ($self) = @_;
+
+ return en_base64(
+ bcrypt_hash(
+ {
+ key_nul => 1,
+ cost => $self->{cost},
+ salt => $self->{salt},
+ },
+ $self->{passphrase},
+ )
+ );
+}
+
+sub verify {
+ my ( $self, $testhash ) = @_;
+
+ my $myhash = $self->bcrypt();
+
+ if ( $testhash eq $myhash ) {
+ return 1;
+ }
+ confess('Passwords did not match');
+}
+
+1;
+
+__END__
+
=head1 NAME
App::Raps2::Password - Password class for App::Raps2
@@ -23,7 +120,7 @@ App::Raps2::Password - Password class for App::Raps2
passphrase => 'secret',
);
- my $oneway_hash = $raps2->crypt();
+ my $oneway_hash = $raps2->bcrypt();
$raps2->verify($oneway_hash);
my $twoway_hash = $raps2->encrypt('data');
@@ -34,6 +131,10 @@ App::Raps2::Password - Password class for App::Raps2
This manual documents B<App::Raps2::Password> version 0.3
+=head1 DESCRIPTION
+
+App::Raps2::Pasword is a wrapper around Crypt::Eksblowfish.
+
=head1 METHODS
=over
@@ -60,153 +161,47 @@ generates its own.
=back
-=cut
-
-sub new {
- my ($obj, %conf) = @_;
-
- $conf{cost} //= 12;
-
- if (not defined $conf{salt}) {
- $conf{salt} = create_salt();
- }
-
- if (length($conf{salt}) != 16) {
- confess('incorrect salt length');
- }
-
- if (not (defined $conf{passphrase} and length $conf{passphrase})) {
- confess('no passphrase given');
- }
-
- my $ref = \%conf;
-
- return bless($ref, $obj);
-}
-
=item $pass->create_salt()
Returns a new 16-byte salt. Contains only printable characters.
-=cut
-
-sub create_salt {
- my ($self) = @_;
- my $salt = q{};
-
- for (1 .. 16) {
- $salt .= chr(0x21 + int(rand(90)));
- }
-
- return $salt;
-}
-
=item $pass->salt([I<salt>])
Returns the currently used salt and optionally changes it to I<salt>.
-=cut
-
-sub salt {
- my ($self, $salt) = @_;
-
- if (defined $salt) {
- if (length($salt) != 16) {
- confess('incorrect salt length');
- }
-
- $self->{salt} = $salt;
- }
-
- return $self->{salt};
-}
-
=item $pass->encrypt(I<data>)
Encrypts I<data> with the passphrase saved in the object, returns the
corresponding hexadecimal hash (as string).
-=cut
-
-sub encrypt {
- my ($self, $in) = @_;
-
- my $eksblowfish = Crypt::Eksblowfish->new(
- $self->{cost},
- $self->{salt},
- $self->{passphrase},
- );
- my $cbc = Crypt::CBC->new(-cipher => $eksblowfish);
-
- return $cbc->encrypt_hex($in);
-}
-
=item $pass->decrypt(I<hexstr>)
Decrypts I<hexstr> (as created by B<encrypt>), returns its original content.
-=cut
-
-sub decrypt {
- my ($self, $in) = @_;
-
- my $eksblowfish = Crypt::Eksblowfish->new(
- $self->{cost},
- $self->{salt},
- $self->{passphrase},
- );
- my $cbc = Crypt::CBC->new(-cipher => $eksblowfish);
-
- return $cbc->decrypt_hex($in);
-}
-
-=item $pass->crypt()
+=item $pass->bcrypt()
Return a base64 bcrypt hash of the password, salted with the salt.
-=cut
-
-sub crypt {
- my ($self) = @_;
-
- return en_base64(
- bcrypt_hash({
- key_nul => 1,
- cost => $self->{cost},
- salt => $self->{salt},
- },
- $self->{passphrase},
- ));
-}
-
=item $pass->verify(I<hash>)
Verify a hash as returned by B<crypt>.
Returns true if it matches, dies if it doesn't.
-=cut
-
-sub verify {
- my ($self, $testhash) = @_;
-
- my $myhash = $self->crypt();
+=back
- if ($testhash eq $myhash) {
- return 1;
- }
- confess('Passwords did not match');
-}
+=head1 DIAGNOSTICS
-1;
+When anything goes wrong, App::Raps2::Password will use Carp(3pm)'s B<confess>
+method to die with a backtrace.
-__END__
+=head1 DEPENDENCIES
-=back
+Crypt::CBC(3pm), Crypt::Eksblowfish(3pm).
-=head1 DEPENDENCIES
+=head1 BUGS AND LIMITATIONS
-B<Crypt::CBC>, B<Crypt::Eksblowfish>.
+Unknown.
=head1 SEE ALSO
diff --git a/lib/App/Raps2/UI.pm b/lib/App/Raps2/UI.pm
index 4d7f3d0..b588222 100644
--- a/lib/App/Raps2/UI.pm
+++ b/lib/App/Raps2/UI.pm
@@ -10,113 +10,61 @@ use Term::ReadLine;
our $VERSION = '0.3';
-=head1 NAME
-
-App::Raps2::UI - App::Raps2 User Interface
-
-=head1 SYNOPSIS
-
- my $ui = App::Raps2::UI->new();
-
- my $input = $ui->read_line('Say something');
-
- my $password = $ui->read_pw('New password', 1);
-
- $ui->to_clipboard('stuff!');
-
-=head1 VERSION
-
-This manual documents B<App::Raps2::UI> version 0.3
-
-=head1 METHODS
-
-=over
-
-=item $ui = App::Raps2::UI->new()
-
-Returns a new App::Raps2::UI object.
-
-=cut
-
sub new {
my ($obj) = @_;
- my $ref = {};
- $ref->{term_readline} = Term::ReadLine->new('App::Raps2');
- return bless($ref, $obj);
-}
-=item $ui->list(I<\@item1>, I<\@item2>, I<\@item3>)
+ my $ref = {};
-Print the list items neatly formatted to stdout. Each I<item> looks like B<[>
-I<key>, I<value> B<]>. When B<list> is called for the first time, it will
-print the keys as well as the values.
+ $ref->{term_readline} = Term::ReadLine->new('App::Raps2');
-=cut
+ return bless( $ref, $obj );
+}
sub list {
- my ($self, @list) = @_;
+ my ( $self, @list ) = @_;
+
my $format = "%-20s %-20s %s\n";
- if (not $self->{list}->{header}) {
- printf($format, map { $_->[0] } @list);
+ if ( not $self->{list}->{header} ) {
+ printf( $format, map { $_->[0] } @list );
$self->{list}->{header} = 1;
}
- printf($format, map { $_->[1] // q{} } @list);
-}
-
-=item $ui->read_line(I<$question>, [I<$prefill>])
-
-Print "I<question>: " to stdout and wait for the user to input text followed
-by a newline. I<prefill> sets the default content of the answer field.
-
-Returns the user's reply, excluding the newline.
+ printf( $format, map { $_->[1] // q{} } @list );
-=cut
+ return;
+}
sub read_line {
- my ($self, $str, $pre) = @_;
+ my ( $self, $str, $pre ) = @_;
- my $input = $self->{term_readline}->readline("${str}: ", $pre);
+ my $input = $self->{term_readline}->readline( "${str}: ", $pre );
return $input;
}
-=item $ui->read_multiline(I<$message>)
-
-Like B<read_line>, but repeats I<message> each time the user hits return.
-Input is terminated by EOF (Ctrl+D). Returns a string concatenation of all
-lines (including newlines).
-
-=cut
-
sub read_multiline {
- my ($self, $str) = @_;
+ my ( $self, $str ) = @_;
+
my $in;
say "${str} (^D to quit)";
- while (my $line = $self->read_line('multiline')) {
+ while ( my $line = $self->read_line('multiline') ) {
$in .= "${line}\n";
}
+
return $in;
}
-=item $ui->read_pw(I<$message>, I<$verify>)
-
-Prompt the user for a password. I<message> is displayed, the user's input is
-noch echoed. If I<verify> is set, the user has to enter the same input twice,
-otherwise B<read_pw> dies. Returns the input.
-
-=cut
-
sub read_pw {
- my ($self, $str, $verify) = @_;
- my ($in1, $in2);
+ my ( $self, $str, $verify ) = @_;
+ my ( $in1, $in2 );
my $term = POSIX::Termios->new();
+
$term->getattr(0);
- $term->setlflag($term->getlflag() & ~POSIX::ECHO);
- $term->setattr(0, POSIX::TCSANOW);
+ $term->setlflag( $term->getlflag() & ~POSIX::ECHO );
+ $term->setattr( 0, POSIX::TCSANOW );
print "${str}: ";
$in1 = readline(STDIN);
@@ -128,49 +76,39 @@ sub read_pw {
print "\n";
}
- $term->setlflag($term->getlflag() | POSIX::ECHO);
- $term->setattr(0, POSIX::TCSANOW);
+ $term->setlflag( $term->getlflag() | POSIX::ECHO );
+ $term->setattr( 0, POSIX::TCSANOW );
- if ($verify and $in1 ne $in2) {
+ if ( $verify and $in1 ne $in2 ) {
confess('Input lines did not match');
}
chomp $in1;
+
return $in1;
}
-=item $ui->to_clipboard(I<$string>)
+sub to_clipboard {
+ my ( $self, $str ) = @_;
-Place I<string> in the primary X Clipboard.
+ open( my $clipboard, q{|-}, 'xclip -l 1' )
+ or confess("Failed to execute xclip -l 1: $!");
-=cut
+ print $clipboard $str;
-sub to_clipboard {
- my ($self, $str) = @_;
+ close($clipboard)
+ or confess("Failed to close pipe to xclip: $!");
- open(my $clipboard, '|-', 'xclip -l 1');
- print $clipboard $str;
- close($clipboard);
return;
}
-=item $ui->output(I<\@pair>, I<...>)
-
-I<pair> consinsts of B<[> I<key>, I<value> B<]>. For each I<pair>, prints
-" key : value" to stdout.
-
-=cut
-
sub output {
- my ($self, @out) = @_;
+ my ( $self, @out ) = @_;
for my $pair (@out) {
- printf(
- "%-8s : %s\n",
- $pair->[0],
- $pair->[1] // q{},
- );
+ printf( "%-8s : %s\n", $pair->[0], $pair->[1] // q{}, );
}
+
return;
}
@@ -178,12 +116,86 @@ sub output {
__END__
+=head1 NAME
+
+App::Raps2::UI - App::Raps2 User Interface
+
+=head1 SYNOPSIS
+
+ my $ui = App::Raps2::UI->new();
+
+ my $input = $ui->read_line('Say something');
+
+ my $password = $ui->read_pw('New password', 1);
+
+ $ui->to_clipboard('stuff!');
+
+=head1 VERSION
+
+This manual documents B<App::Raps2::UI> version 0.3
+
+=head1 DESCRIPTION
+
+App::Raps2::UI is used by App::Raps2 to interface with the user, i.e. do input
+and output on the terminal.
+
+=head1 METHODS
+
+=over
+
+=item $ui = App::Raps2::UI->new()
+
+Returns a new App::Raps2::UI object.
+
+=item $ui->list(I<\@item1>, I<\@item2>, I<\@item3>)
+
+Print the list items neatly formatted to stdout. Each I<item> looks like B<[>
+I<key>, I<value> B<]>. When B<list> is called for the first time, it will
+print the keys as well as the values.
+
+=item $ui->read_line(I<$question>, [I<$prefill>])
+
+Print "I<question>: " to stdout and wait for the user to input text followed
+by a newline. I<prefill> sets the default content of the answer field.
+
+Returns the user's reply, excluding the newline.
+
+=item $ui->read_multiline(I<$message>)
+
+Like B<read_line>, but repeats I<message> each time the user hits return.
+Input is terminated by EOF (Ctrl+D). Returns a string concatenation of all
+lines (including newlines).
+
+=item $ui->read_pw(I<$message>, I<$verify>)
+
+Prompt the user for a password. I<message> is displayed, the user's input is
+noch echoed. If I<verify> is set, the user has to enter the same input twice,
+otherwise B<read_pw> dies. Returns the input.
+
+=item $ui->to_clipboard(I<$string>)
+
+Place I<string> in the primary X Clipboard.
+
+=item $ui->output(I<\@pair>, I<...>)
+
+I<pair> consinsts of B<[> I<key>, I<value> B<]>. For each I<pair>, prints
+" key : value" to stdout.
+
=back
+=head1 DIAGNOSTICS
+
+When App::Raps2::UI encounters an error, it uses Carp(3pm)'s B<confess>
+function to die with a backtrace.
+
=head1 DEPENDENCIES
This module requires B<Term::ReadLine> and the B<xclip> executable.
+=head1 BUGS AND LIMITATIONS
+
+Unknown.
+
=head1 SEE ALSO
App::Raps2(3pm).
diff --git a/t/20-app-raps2-password.t b/t/20-app-raps2-password.t
index 5faa7e9..a8f56e8 100644
--- a/t/20-app-raps2-password.t
+++ b/t/20-app-raps2-password.t
@@ -82,7 +82,7 @@ like(
'verify: does not verify invalid hash'
);
-ok($pw->verify($pw->crypt('truth')), 'crypt->verify okay');
+ok($pw->verify($pw->bcrypt('truth')), 'bcrypt->verify okay');
is($pw->salt(), $salt, 'salt() returns current salt');