File Coverage

blib/lib/Crypt/HSXKPasswd/Dictionary/Basic.pm
Criterion Covered Total %
statement 101 124 81.4
branch 18 24 75.0
condition 4 4 100.0
subroutine 19 21 90.4
pod 0 6 0.0
total 142 179 79.3


line stmt bran cond sub pod time code
1             package Crypt::HSXKPasswd::Dictionary::Basic;
2              
3 3     3   15 use parent Crypt::HSXKPasswd::Dictionary;
  3         4  
  3         23  
4              
5             # import required modules
6 3     3   136 use strict;
  3         5  
  3         61  
7 3     3   12 use warnings;
  3         4  
  3         102  
8 3     3   16 use Carp; # for nicer 'exception' handling for users of the module
  3         3  
  3         196  
9 3     3   15 use Fatal qw( :void open close binmode ); # make builtins throw exceptions on failure
  3         5  
  3         22  
10 3     3   4598 use English qw(-no_match_vars); # for more readable code
  3         6  
  3         22  
11 3     3   1363 use Readonly; # for truly constant constants
  3         5  
  3         215  
12 3     3   19 use Type::Params qw( compile multisig ); # for parameter validation with Type::Tiny objects
  3         8  
  3         48  
13 3     3   987 use Types::Standard qw( :types ); # for basic type checking (Int Str etc.)
  3         6  
  3         31  
14 3     3   10947 use Crypt::HSXKPasswd::Types qw( :types ); # for custom type checking
  3         6  
  3         31  
15 3     3   6492 use Crypt::HSXKPasswd::Helper; # exports utility functions like _error & _warn
  3         4  
  3         213  
16              
17             # set things up for using UTF-8
18 3     3   69 use 5.016; # min Perl for good UTF-8 support, implies feature 'unicode_strings'
  3         16  
19 3     3   15 use Encode qw(encode decode);
  3         3  
  3         207  
20 3     3   17 use utf8;
  3         4  
  3         21  
21             binmode STDOUT, ':encoding(UTF-8)';
22              
23             # Copyright (c) 2015, Bart Busschots T/A Bartificer Web Solutions All rights
24             # reserved.
25             #
26             # Code released under the FreeBSD license (included in the POD at the bottom of
27             # HSXKPasswd.pm)
28              
29             #
30             # --- Constants ---------------------------------------------------------------
31             #
32              
33             # version info
34 3     3   108 use version; our $VERSION = qv('1.2');
  3         3  
  3         14  
35              
36             # utility variables
37             Readonly my $_CLASS => __PACKAGE__;
38              
39             #
40             # --- Constructor -------------------------------------------------------------
41             #
42              
43             #####-SUB-#####################################################################
44             # Type : CONSTRUCTOR (CLASS)
45             # Returns : An object of type Crypt::HSXKPasswd::Dictionary::Basic
46             # Arguments : 1) a string representing a file path to a dictionary file
47             # -- OR --
48             # an array ref containing a list of words
49             # 2) OPTIONAL - the encoding to import the file with. The default
50             # is UTF-8 (ignored if the first argument is not a file path).
51             # Throws : Croaks on invalid invocation and invalid args.
52             # Notes :
53             # See Also :
54             sub new{
55 3     3 0 6 my @args = @_;
56 3         5 my $class = shift @args;
57 3         9 _force_class($class);
58            
59             # validate args
60 3         8 state $args_check = multisig(
61             [NonEmptyString, Optional[Maybe[NonEmptyString]]],
62             [ArrayRef[Str]],
63             );
64 3         2421 my ($dict_source, $encoding) = $args_check->(@args);
65            
66             # set defaults
67 3 100       115 $encoding = 'UTF-8' unless $encoding;
68            
69             # start with a blank object
70 3         15 my $instance = {
71             words => [],
72             sources => {
73             files => [],
74             num_arrays => 0,
75             },
76             };
77 3         10 bless $instance, $class;
78            
79             # try instantiate the word list as appropriate
80 3         12 $instance->add_words($dict_source, $encoding);
81            
82             # return the object
83 3         20 return $instance;
84             }
85              
86             #
87             # --- Public Instance functions -----------------------------------------------
88             #
89              
90             #####-SUB-######################################################################
91             # Type : INSTANCE
92             # Purpose : Override clone() from the parent class and return a clone of
93             # self.
94             # Returns : An object of type Crypt::HSXKPasswd::Dictionary::Basic
95             # Arguments : NONE
96             # Throws : Croaks on invalid invocation
97             # Notes :
98             # See Also :
99             sub clone{
100 0     0 0 0 my $self = shift;
101 0         0 _force_instance($self);
102            
103             # create an empty object
104 0         0 my $clone = {
105             words => [],
106             sources => {
107             files => [],
108             num_arrays => 0,
109             },
110             };
111            
112             # fill in the values
113 0         0 foreach my $word (@{$self->{words}}){
  0         0  
114 0         0 push @{$clone->{words}}, $word;
  0         0  
115             }
116 0         0 foreach my $file (@{$self->{sources}->{files}}){
  0         0  
117 0         0 push @{$clone->{sources}->{files}}, $file;
  0         0  
118             }
119 0         0 $clone->{sources}->{num_arrays} = $self->{sources}->{num_arrays};
120            
121             # bless the clone
122 0         0 bless $clone, $_CLASS;
123            
124             # return the clone
125 0         0 return $clone;
126             }
127              
128             #####-SUB-#####################################################################
129             # Type : INSTANCE
130             # Purpose : Override word_list() from the parent class and return the word
131             # list.
132             # Returns : An array reference.
133             # Arguments : NONE
134             # Throws : NOTHING
135             # Notes :
136             # See Also :
137             sub word_list{
138 3     3 0 5 my $self = shift;
139 3         9 _force_instance($self);
140            
141             # return a reference to the word list
142 3         12 return $self->{words};
143             }
144              
145             #####-SUB-#####################################################################
146             # Type : INSTANCE
147             # Purpose : Override source() from the parent class and return information
148             # about the word sources.
149             # Returns : An array reference.
150             # Arguments : NONE
151             # Throws : NOTHING
152             # Notes :
153             # See Also :
154             sub source{
155 3     3 0 5 my $self = shift;
156 3         8 _force_instance($self);
157            
158 3         20 my $source = $self->SUPER::source();
159 3 50 100     15 if($self->{sources}->{num_arrays} || scalar @{$self->{sources}->{files}}){
  2         12  
160 3         6 $source .= ' (loaded from: ';
161 3 100       8 if($self->{sources}->{num_arrays}){
162 1         3 $source .= $self->{sources}->{num_arrays}.' array refs';
163             }
164 3 50 100     8 if($self->{sources}->{num_arrays} && scalar @{$self->{sources}->{files}}){
  1         4  
165 0         0 $source .= ' and ';
166             }
167 3 100       6 if(scalar @{$self->{sources}->{files}}){
  3         9  
168 2         3 $source .= 'the file(s) '.(join q{, }, @{$self->{sources}->{files}});
  2         7  
169             }
170 3         5 $source .= ')';
171             }
172            
173 3         9 return $source;
174             }
175              
176             #####-SUB-#####################################################################
177             # Type : INSTANCE
178             # Purpose : Blank the loaded word list.
179             # Returns : A reference to self to facilitate function chaining
180             # Arguments : NONE
181             # Throws : Croaks on invalid invocation
182             # Notes :
183             # See Also :
184             sub empty{
185 0     0 0 0 my $self = shift;
186 0         0 _force_instance($self);
187            
188             # blank the word list and sources
189 0         0 $self->{words} = [];
190 0         0 $self->{sources}->{files} = [];
191 0         0 $self->{sources}->{num_arrays} = 0;
192            
193             # return a reference to self
194 0         0 return $self;
195             }
196              
197             #####-SUB-#####################################################################
198             # Type : INSTANCE
199             # Purpose : Load words from a file or array ref, appending them to the word
200             # list.
201             # Returns : a reference to self to facilitate function chaining
202             # Arguments : 1) the path to the file to load words from
203             # --OR--
204             # a reference to an array of words
205             # 2) OPTIONAL - the encoding to import the file with. The default
206             # is UTF-8 (ignored if the first argument is not a file path).
207             # Throws : Croaks on invalid invocation or invalid args. Carps on invalid
208             # invalid word.
209             # Notes :
210             # See Also :
211             sub add_words{
212 3     3 0 6 my @args = @_;
213 3         6 my $self = shift @args;
214 3         9 _force_instance($self);
215            
216             # validate args
217 3         7 state $args_check = multisig(
218             [NonEmptyString, Optional[Maybe[NonEmptyString]]],
219             [ArrayRef[Str]], Optional[Item],
220             );
221 3         2833 my ($dict_source, $encoding) = $args_check->(@args);
222            
223             # set defaults
224 3 100       120 $encoding = 'UTF-8' unless $encoding;
225            
226             # try load the words from the relevant source
227 3         5 my @new_words = ();
228 3 100       10 if(ref $dict_source eq 'ARRAY'){
229             # load valid words from the referenced array
230 1         2 @new_words = @{$dict_source};
  1         2  
231            
232             # increase the array source count
233 1         9 $self->{sources}->{num_arrays}++;
234             }else{
235             # load the words from a file path
236            
237             # make sure the file path is valid
238 2 50       47 unless(-f $dict_source){
239 0         0 _error("file $dict_source not found");
240             }
241            
242             # try load and parse the contents of the file
243 2 50       69 open my $WORD_FILE_FH, "<:encoding($encoding)", $dict_source or _error("Failed to open $dict_source with error: $OS_ERROR");
244 2         484 my $word_file_contents = do{local $/ = undef; <$WORD_FILE_FH>};
  2         9  
  2         70  
245 2         591 close $WORD_FILE_FH;
246             LINE:
247 2         1171 foreach my $line (split /\n/sx, $word_file_contents){
248             # skip empty lines
249 2900 50       4947 next LINE if $line =~ m/^\s*$/sx;
250            
251             # skip comment lines
252 2900 100       3750 next LINE if $line =~ m/^[#]/sx;
253            
254             # if we got here, store the word
255 2850         3644 push @new_words, $line;
256             }
257            
258             # make sure we got at least one word!
259 2 50       323 unless(scalar @new_words){
260 0         0 _error("file $dict_source contained no valid words");
261             }
262            
263             # add the file to the list of loaded files
264 2         6 push @{$self->{sources}->{files}}, $dict_source;
  2         25  
265             }
266            
267             # merge with existing words and save into the instance
268 3         6 my @updated_words = (@{$self->{words}}, @new_words);
  3         724  
269 3         751 $self->{words} = [@updated_words];
270            
271             # return a reference to self
272 3         445 return $self;
273             }
274              
275             1; # because Perl is just a little bit odd :)