File Coverage

blib/lib/App/PerlPPP.pm
Criterion Covered Total %
statement 46 110 41.8
branch 8 44 18.1
condition 4 13 30.7
subroutine 14 23 60.8
pod 6 6 100.0
total 78 196 39.8


line stmt bran cond sub pod time code
1             package App::PerlPPP;
2              
3 2     2   63001 use strict;
  2         4  
  2         92  
4 2     2   12 use warnings;
  2         4  
  2         208  
5 2     2   11 use Carp;
  2         8  
  2         281  
6 2     2   10975 use Getopt::Long;
  2         35672  
  2         13  
7 2     2   1515 use Crypt::PerfectPaperPasswords;
  2         8  
  2         71  
8 2     2   2160 use Pod::Usage;
  2         127115  
  2         1016  
9              
10             =head1 NAME
11              
12             App::PerlPPP - Command line tool for Perfect Paper Passwords
13              
14             =head1 VERSION
15              
16             This document describes App::PerlPPP version 0.06
17              
18             =cut
19              
20             our $VERSION = '0.06';
21              
22             =head1 SYNOPSIS
23              
24             use App::PerlPPP;
25              
26             my $app = App::PerlPPP->new;
27             $app->parse_args(@ARGV);
28             $app->run;
29              
30             =head1 DESCRIPTION
31              
32             =head1 INTERFACE
33              
34             =head2 C<< new >>
35              
36             =cut
37              
38             {
39             my %ARG_SPEC;
40              
41             BEGIN {
42              
43             sub _range_spec {
44 4     4   10 my ( $name, $default, $min, $max ) = @_;
45             return [
46             $default,
47             sub {
48 2     2   4 my ( $self, $value ) = @_;
49 2 50 33     21 die "$name must be between $min and $max\n"
50             if $value < $min || $value > $max;
51 2         10 return $value;
52             }
53 4         51 ];
54             }
55              
56             sub _array_spec {
57             return [
58             [],
59             sub {
60 1     1   3 my $self = shift;
61 1 50       4 return [ map { 'ARRAY' eq ref $_ ? @$_ : $_ } @_ ];
  1         14  
62             },
63 2     2   14 ];
64             }
65              
66             %ARG_SPEC = (
67             show_man => [0],
68             show_help => [0],
69             args => _array_spec(),
70             columns => _range_spec( 'columns', 7, 1, 100 ),
71             rows => _range_spec( 'rows', 10, 1, 100 ),
72             codelen => [undef],
73             passphrase => [undef],
74             alphabet => [undef],
75             key => [
76             undef,
77             sub {
78 0         0 my ( $self, $key ) = @_;
79 0 0       0 die "Key must be 64 characters long\n"
80             unless length( $key ) == 64;
81 0 0       0 die "Key must be hexadecimal (0-9, A-F)\n"
82             unless $key =~ /^[0-9A-F]{64}$/i;
83 0         0 return $key;
84             },
85 2     2   9 ],
86             title => ['Perfect Paper Passwords'],
87             );
88              
89 2         18 while ( my ( $name, $spec ) = each %ARG_SPEC ) {
90 2     2   34 no strict 'refs';
  2         4  
  2         268  
91 20   100     96 my $validator = $spec->[1] || sub { shift; shift };
92 20         2890 *{ __PACKAGE__ . '::' . $name } = sub {
93 6     6   10 my $self = shift;
94 6 50       27 $self->{$name} = $self->$validator( @_ )
95             if ( @_ );
96 6         10 my $value = $self->{$name};
97 6 50 33     31 return ( wantarray && 'ARRAY' eq ref $value )
98             ? @$value
99             : $value;
100 20         72 };
101             }
102             }
103              
104             sub new {
105 1     1 1 241 my ( $class, %args ) = @_;
106              
107 1         4 my $self = bless {}, $class;
108              
109 1         11 while ( my ( $name, $spec ) = each %ARG_SPEC ) {
110 10 50       29 my $value
111             = exists $args{$name} ? delete $args{$name} : $spec->[0];
112 10 100       45 $self->$name( $value )
113             if defined $value;
114             }
115              
116 1 50       5 croak "Unknown options: ", join( ', ', sort keys %args )
117             if keys %args;
118              
119 1         3 return $self;
120             }
121             }
122              
123             =head2 C<< args >>
124              
125             =head2 C<< alphabet >>
126              
127             =head2 C<< codelen >>
128              
129             =head2 C<< columns >>
130              
131             =head2 C<< rows >>
132              
133             =head2 C<< key >>
134              
135             =head2 C<< passphrase >>
136              
137             =head2 C<< title >>
138              
139             =head2 C<< show_help >>
140              
141             =head2 C<< show_man >>
142              
143             =head2 C<< parse_args >>
144              
145             =cut
146              
147             sub parse_args {
148 0     0 1   my ( $self, @args ) = @_;
149              
150 0           local @ARGV = @args;
151              
152 0           my %options;
153              
154 0 0         GetOptions(
155             'help|?' => \$options{show_help},
156             man => \$options{show_man},
157             'key=s' => \$options{key},
158             'passphrase=s' => \$options{passphrase},
159             'columns=i' => \$options{columns},
160             'rows=i' => \$options{rows},
161             'title=s' => \$options{title},
162             'alphabet=s' => \$options{alphabet},
163             'codelen=i' => \$options{codelen},
164             ) or pod2usage();
165              
166 0           while ( my ( $name, $value ) = each %options ) {
167 0 0         $self->$name( $value ) if defined $value;
168             }
169              
170 0           $self->args( @ARGV );
171             }
172              
173             =head2 C<< run >>
174              
175             =cut
176              
177             sub run {
178 0     0 1   my $self = shift;
179              
180 0 0         if ( $self->show_help ) {
    0          
181 0           $self->do_help;
182             }
183             elsif ( $self->show_man ) {
184 0           pod2usage( -verbose => 2, -exitstatus => 0 );
185             }
186             else {
187 0           my @args = $self->args;
188 0 0         pod2usage() unless @args;
189 0           my $verb = shift @args;
190 0 0         if ( my $code = $self->can( "do_$verb" ) ) {
191 0           $self->$code( @args );
192             }
193             else {
194 0           die "Unknown action: $verb\n";
195             }
196             }
197             }
198              
199             =head1 ACTIONS
200              
201             =head2 C<< do_card >>
202              
203             Output a card
204              
205             =cut
206              
207             sub do_card {
208 0     0 1   my ( $self, @args ) = @_;
209 0 0         my $card_no = @args ? shift @args : 1;
210 0 0         die "Card numbers start at 1\n" if $card_no < 1;
211              
212 0           my $title = $self->title;
213 0           my $rows = $self->rows;
214 0           my $cols = $self->columns;
215 0           my $ppp = $self->_make_ppp;
216             my @passcodes
217 0           = $ppp->passcodes( $card_no, $rows * $cols, $self->_get_key );
218              
219 0           my $colw = length( $passcodes[0] );
220             my $center = sub {
221 0     0     my $str = shift;
222 0           my $pad = $colw - length $str;
223 0 0         return $str if $pad <= 0;
224 0           return ( ' ' x ( $pad / 2 ) ) . $str
225             . ( ' ' x ( ( $pad + 1 ) / 2 ) );
226 0           };
227              
228 0           my $row_fmt = "%4d";
229 0           my @hdr = ( ' ' x ( length sprintf( $row_fmt, 1 ) ) );
230 0           my $col = 'A';
231 0           push @hdr, $center->( $col++ ) for ( 1 .. $cols );
232 0           my $hdr = join( ' ', @hdr );
233 0           my $rule = '=' x length $hdr;
234 0           print "$title [$card_no]\n$rule\n$hdr\n$rule\n";
235 0           for ( 1 .. $rows ) {
236 0           print join( ' ',
237             sprintf( $row_fmt, $_ ),
238             splice( @passcodes, 0, $cols ) ),
239             "\n";
240             }
241 0           print "$rule\n";
242             }
243              
244             =head2 C
245              
246             Create and display a new random key.
247              
248             =cut
249              
250             sub do_newkey {
251 0     0 1   my $self = shift;
252 0 0         if ( my $key = $self->_make_key ) {
253 0           print "Specified key is $key\n";
254             }
255             else {
256 0           print "Generated key is ",
257             $self->key( $self->_make_ppp->random_sequence ), "\n";
258             }
259             }
260              
261             =head2 C
262              
263             Output help page
264              
265             =cut
266              
267             sub do_help {
268 0     0 1   my $self = shift;
269 0           pod2usage( -verbose => 1 );
270             }
271              
272             sub _make_key {
273 0     0     my $self = shift;
274              
275 0 0         if ( defined( my $key = $self->key ) ) {
    0          
276 0           return $key;
277             }
278             elsif ( defined( my $phrase = $self->passphrase ) ) {
279 0           return $self->key( $self->_make_ppp->sequence_from_key( $phrase ) );
280             }
281             else {
282 0           return;
283             }
284             }
285              
286             sub _get_key {
287 0     0     my $self = shift;
288 0   0       return $self->_make_key
289             || die "Must supply --key or --passphrase\n";
290             }
291              
292             sub _make_ppp {
293 0     0     my $self = shift;
294 0           my %args;
295 0           for my $a ( qw( alphabet codelen ) ) {
296 0 0         if ( defined( my $value = $self->$a() ) ) {
297 0           $args{$a} = $value;
298             }
299             }
300              
301 0   0       return $self->{_ppp} ||= Crypt::PerfectPaperPasswords->new( %args );
302             }
303              
304             1;
305             __END__