File Coverage

blib/lib/Crypt/HSXKPasswd/Helper.pm
Criterion Covered Total %
statement 103 151 68.2
branch 24 58 41.3
condition 5 21 23.8
subroutine 22 23 95.6
pod n/a
total 154 253 60.8


line stmt bran cond sub pod time code
1             package Crypt::HSXKPasswd::Helper;
2              
3             # import required modules
4 3     3   13 use strict;
  3         5  
  3         99  
5 3     3   14 use warnings;
  3         6  
  3         118  
6 3     3   12 use English qw( -no_match_vars );
  3         5  
  3         26  
7 3     3   1125 use Carp; # for nicer 'exceptions' for users of the module
  3         5  
  3         200  
8 3     3   16 use Fatal qw( :void open close binmode ); # make builtins throw exceptions
  3         5  
  3         23  
9 3     3   4772 use Scalar::Util qw( blessed ); # for checking if a reference is blessed
  3         4  
  3         174  
10 3     3   16 use List::MoreUtils qw( uniq ); # for array deduplication
  3         6  
  3         37  
11 3     3   1843 use Readonly; # for truly constant constants
  3         4  
  3         202  
12 3     3   19 use Types::Standard qw( ClassName ); # needed for _force_class
  3         6  
  3         40  
13              
14             # set things up for using UTF-8
15 3     3   2146 use 5.016; # min Perl for good UTF-8 support, implies feature 'unicode_strings'
  3         17  
16 3     3   14 use Encode qw(encode decode);
  3         6  
  3         170  
17 3     3   14 use utf8;
  3         6  
  3         50  
18             binmode STDOUT, ':encoding(UTF-8)';
19              
20             ## no critic (ProhibitAutomaticExportation);
21 3     3   130 use base qw( Exporter );
  3         5  
  3         459  
22             our @EXPORT = qw( _do_debug _debug _warn _error _force_class _force_instance );
23             ## use critic
24              
25             # import (or not) optional modules
26             our $_CAN_STACK_TRACE = eval{
27             require Devel::StackTrace; # for better error reporting when debugging
28             };
29              
30             #==============================================================================#
31             # A helper class implemented shared functionality
32             #==============================================================================#
33             #
34             # All classes in the Crypt::HSXKPasswd package should inherit from this one as
35             # it provides basic functionality like error reporting.
36             #
37             #==============================================================================#
38              
39             #
40             # === CONSTANTS & Package Vars ================================================#
41             #
42              
43             # version info
44 3     3   17 use version; our $VERSION = qv('1.0');
  3         4  
  3         20  
45              
46             # utility variables
47             Readonly my $_CLASS => __PACKAGE__;
48             Readonly my $BASE_PACKAGE => 'Crypt::HSXKPasswd';
49              
50             # Debug and logging configuration
51             our $_LOG_STREAM = *STDERR; # default to logging to STDERR
52             our $_LOG_ERRORS = 0; # default to not logging errors
53             our $_DEBUG = 0; # default to not having debugging enabled
54              
55             # Declare Package-level variable needed to control Carp
56             our @CARP_NOT;
57              
58             #
59             # === 'Private' Functions to be Exported ======================================#
60             #
61              
62             #####-SUB-######################################################################
63             # Type : SUBROUTINE (PRIVATE) - EXPORTED
64             # Purpose : Return 1 if we are in debug mode, and 0 otherwise.
65             # Returns : 1 or 0
66             # Arguments : NONE
67             # Throws : NOTHING
68             # Notes :
69             # See Also :
70             sub _do_debug{ ## no critic (ProhibitUnusedPrivateSubroutines)
71 332 50   332   1319 return $_DEBUG ? 1 : 0;
72             }
73              
74             #####-SUB-######################################################################
75             # Type : SUBROUTINE (PRIVATE) - EXPORTED
76             # Purpose : Function for printing a debug message
77             # Returns : Always return 1 (to keep perlcritic happpy)
78             # Arguments : 1. the debug message to log
79             # 2. OPTIONAL - a number of function calls to hide from users in
80             # the output.
81             # Throws : Croaks on invalid invocation
82             # Notes : a wrapper for __log() which invokes that function with a severity
83             # of 'DEBUG'
84             # See Also : __log()
85             sub _debug{ ## no critic (ProhibitUnusedPrivateSubroutines)
86 446     446   2014 my $message = shift;
87 446         450 my $stack_increment = shift;
88            
89             #pass the call on to __log
90 446         686 return __log('DEBUG', $message, $stack_increment);
91             }
92              
93             #####-SUB-######################################################################
94             # Type : SUBROUTINE (PRIVATE) - EXPORTED
95             # Purpose : Function for issuing a warning
96             # Returns : Always returns 1 to keep perlcritic happy
97             # Arguments : 1. the warning message to log
98             # 2. OPTIONAL - a number of function calls to hide from users in
99             # the output.
100             # Throws : Croaks on invalid invocation
101             # Notes : a wrapper for __log() which invokes that function with a severity
102             # of 'WARNING'
103             # See Also : __log()
104             sub _warn{ ## no critic (ProhibitUnusedPrivateSubroutines)
105 0     0   0 my $message = shift;
106 0         0 my $stack_increment = shift;
107            
108             #pass the call on to __log
109 0         0 return __log('WARNING', $message, $stack_increment);
110             }
111              
112             #####-SUB-######################################################################
113             # Type : SUBROUTINE (PRIVATE) - EXPORTED
114             # Purpose : Function for throwing an error
115             # Returns : Always returns 1 to keep perlcritic happy
116             # Arguments : 1. the error message to log
117             # 2. OPTIONAL - a number of function calls to hide from users in
118             # the output.
119             # Throws : Croaks on invalid invocation
120             # Notes : a wrapper for __log() which invokes that function with a severity
121             # of 'ERROR'
122             # See Also : __log()
123             sub _error{
124 2     2   6 my $message = shift;
125 2         3 my $stack_increment = shift;
126            
127             #pass the call on to __log
128 2         6 return __log('ERROR', $message, $stack_increment);
129             }
130              
131             #####-SUB-######################################################################
132             # Type : SUBROUTINE (PRIVATE) - EXPORTED
133             # Purpose : Test the $class in a class function to make sure it was actually
134             # invoked on the correct class.
135             # Returns : Always returns 1 (to keep perlcritic happy)
136             # Arguments : 1) the $class variable to test
137             # Throws : Croaks if $class is not valid
138             # Notes :
139             # See Also :
140             sub _force_class{ ## no critic (ProhibitUnusedPrivateSubroutines)
141 11564     11564   10430 my $test_class = shift;
142            
143             # find the package hosting the call to _force_class
144 11564         15603 my $host_class = __calling_package();
145 11564 50       28024 unless($host_class){
146 0         0 _error(q{failed to determine package hosting the funciton who's invocation should be tested});
147             }
148            
149             # test the class
150 11564 50       20719 unless(ClassName->check($test_class)){
151             # try get the data needed to get the bare function name
152 0         0 my $calling_sub = (caller 1)[3];
153            
154 0 0       0 if($calling_sub){
155             # print a nicer error message
156 0         0 $calling_sub =~ s/^$host_class[:]{2}//sx; # strip the package name from the sub
157 0         0 _error('invalid invocation - must be invoked on the class, e.g. '.$host_class.q{->}.$calling_sub.q{() - invocation on child classes also OK}, 1);
158             }else{
159             # fall back to the less nice output
160 0         0 _error("invalid invocation - must be invoked on the class $host_class (or on a child class)", 1);
161             }
162             }
163            
164 11564         126540 return 1;
165             }
166              
167             #####-SUB-######################################################################
168             # Type : SUBROUTINE (PRIVATE) - EXPORTED
169             # Purpose : Test the $self in an instance function to make sure it was
170             # actually invoked as an instance function.
171             # Returns : Always returns 1 (to keep PerlCritic happy)
172             # Arguments : 1) the $self variable to test
173             # Throws : Croaks if the $self variable is not an instance of this class.
174             # Notes :
175             # See Also :
176             sub _force_instance{ ## no critic (ProhibitUnusedPrivateSubroutines)
177 407     407   345 my $test_self = shift;
178            
179             # test against the direct caller
180 407         727 my $required_package = __calling_package();
181 407 50 33     3658 unless(defined $test_self && blessed($test_self) && $test_self->isa($required_package)){
      33        
182 0         0 _error("invalid invocation - must be invoked on an instance of $required_package", 1);
183             }
184            
185 407         724 return 1;
186             }
187              
188             #
189             # === VERY Private Functions (not exported) ===================================#
190             #
191              
192             #####-SUB-######################################################################
193             # Type : SUBROUTINE (VERY PRIVATE)
194             # Purpose : A helper function to determine the package directly calling the
195             # caller of this function.
196             # Returns : A package name as a string.
197             # Arguments : NONE
198             # Throws : NOTHING
199             # Notes : This function should only EVER be called from one of the exported
200             # helper functions.
201             # See Also :
202             sub __calling_package{
203 11971     11971   59836 return (caller 1)[0];
204             }
205              
206             #####-SUB-######################################################################
207             # Type : SUBROUTINE (VERY PRIVATE)
208             # Purpose : A helper function to return an array of all calling packages that
209             # are within Crypt::HSXKPasswd (not including this package itself).
210             # Returns : An array of strings which may be empty
211             # Arguments : NONE
212             # Throws : NOTHING
213             # Notes : This function continues up the caller tree as long as it finds
214             # that the calling package is within Crypt::HSXKPassd, then it
215             # stops and returns.
216             # See Also :
217             sub __interal_calling_packages{
218 448     448   561 my @internal_packages = ();
219            
220             # loop through the caller tree until we go outside the base package
221 448         403 my $i = 0;
222             CALLER:
223 448         981 while(caller $i){
224 3488         11102 my $package = (caller $i)[0];
225            
226             # if there is no package defined, stop
227 3488 50       6499 last CALLER unless $package;
228            
229             # check if the caller is internal or not
230 3488 100       7812 if($package =~ m/^$BASE_PACKAGE/sx){
231             # we are still internal, so save the package
232 3040         15084 push @internal_packages, $package;
233             }else{
234             # we are external, so we are done
235 448         2108 last CALLER;
236             }
237            
238             # move on to the next caller
239 3040         5885 $i++;
240             }
241            
242             # deduplicate the list & return
243 448         2631 return uniq(@internal_packages);
244             }
245              
246             #####-SUB-######################################################################
247             # Type : SUBROUTINE (VERY PRIVATE)
248             # Purpose : Function to log output from the module - SHOULD NEVER BE CALLED
249             # DIRECTLY
250             # Returns : Always returns 1 (to keep perlcritic happy)
251             # Arguments : 1. the severity of the message (one of 'DEBUG', 'WARNING', or
252             # 'ERROR')
253             # 2. the message to log
254             # 3. OPTIONAL - an increment to add to the argument to caller - to
255             # allow functions like _force_instance to invisibly invoke
256             # _debug(), _warn() & _error().
257             # Throws : Croaks on invalid invocation
258             # Notes : THIS FUNCTION SHOULD NEVER BE CALLED DIRECTLY, but always called
259             # via _debug(), _warn(), or _error().
260             # This function does not croak on invalid args, it confess with as
261             # useful an output as it can.
262             # If the function prints output, it will do so to $LOG_STREAM. The
263             # severity determines the functions exact behaviour:
264             # * 'DEBUG' - message is always printed without a stack trace
265             # * 'WARNING' - output is carped, and, if $LOG_ERRORS is true the
266             # message is also printed
267             # * 'ERROR' - output is confessed if $DEBUG and croaked otherwise.
268             # If $LOG_ERRORS is true the message is also printed with a
269             # stack trace (the stack trace is omited if Devel::StackTrace) is
270             # not installed.
271             # See Also : _debug(), _warn() & _error()
272             ## no critic (ProhibitExcessComplexity);
273             sub __log{
274 448     448   634 my $severity = uc shift;
275 448         594 my $message = shift;
276 448         464 my $stack_increment = shift;
277            
278             # before doing anything that could invoke a Carp function, get the list of
279             # internal referrers, and set up @CARP_NOT
280             # NOTE - the use of local is recommended in the Carp docs
281 448         638 local @CARP_NOT = __interal_calling_packages(); ## no critic (ProhibitLocalVars);
282            
283             # validate the args
284 448 50 33     2858 unless(defined $severity && ref $severity eq q{} && length $severity > 1){
      33        
285 0         0 $severity = 'UNKNOWN_SEVERITY';
286             }
287 448 50 33     1771 unless(defined $message && ref $message eq q{}){
288 0         0 my $output = 'ERROR - '.(caller 0)[3]."(): invoked with severity '$severity' without message at ".(caller 1)[1].q{:}.(caller 1)[2];
289 0 0       0 if($_LOG_ERRORS){
290 0         0 my $log_output = $output;
291 0 0       0 if($_CAN_STACK_TRACE){
292 0         0 $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string();
293             }
294 0         0 print {$_LOG_STREAM} $log_output."\n";
  0         0  
295             }
296 0         0 confess($output);
297             }
298 448 50       740 if(defined $stack_increment){
299 0 0 0     0 unless(ref $stack_increment eq q{} && $stack_increment =~ m/^\d+$/sx){
300 0         0 carp((caller 0)[3].'(): passed invalid stack increment - ignoring');
301 0         0 $stack_increment = 0;
302             }
303             }else{
304 448         455 $stack_increment = 0;
305             }
306              
307             # figure out the correct index for the function that is really responsible
308 448         512 my $caller_index = 2 + $stack_increment;
309 448         1675 my $calling_func = (caller 1)[3];
310 448 50       1460 unless($calling_func =~ m/^$_CLASS[:]{2}((_debug)|(_warn)|(_error))$/sx){
311 0         0 print {$_LOG_STREAM} 'WARNING - '.(caller 0)[3].q{(): invoked directly rather than via _debug(), _warn() or _error() - DO NOT DO THIS!};
  0         0  
312 0         0 $caller_index++;
313             }
314              
315             # deal with evals
316 448         3310 my $true_caller = q{};
317 448         1971 my @caller = caller $caller_index;
318 448 50       987 if(@caller){
319 448         491 $true_caller = $caller[3];
320             }
321 448         491 my $eval_depth = 0;
322 448         902 while($true_caller eq '(eval)'){
323 64         59 $eval_depth++;
324 64         63 $caller_index++;
325 64         278 my @next_caller = caller $caller_index;
326 64 50       150 if(@next_caller){
327 64         196 $true_caller = $next_caller[3];
328             }else{
329 0         0 $true_caller = q{};
330             }
331             }
332 448 50       753 if($true_caller eq q{}){
333 0         0 $true_caller = 'UNKNOWN_FUNCTION';
334             }
335              
336             # deal with the message as appropriate
337 448         618 my $output = "$severity - ";
338 448 100       638 if($eval_depth > 0){
339 64 50       101 if($eval_depth == 1){
340 64         99 $output .= "eval() within $true_caller";
341             }else{
342 0         0 $output .= "$eval_depth deep eval()s within $true_caller";
343             }
344             }else{
345 384         400 $output .= $true_caller;
346             }
347 448         1147 $output .= "(): $message";
348 448 100       817 if($severity eq 'DEBUG'){
    50          
    50          
349             # debugging, so always print and do nothing more
350 446 50       761 print {$_LOG_STREAM} "$output\n" if $_DEBUG;
  0         0  
351             }elsif($severity eq 'WARNING'){
352             # warning - always carp, but first print if needed
353 0 0       0 if($_LOG_ERRORS){
354 0         0 print {$_LOG_STREAM} "$output\n";
  0         0  
355             }
356 0         0 carp($output);
357             }elsif($severity eq 'ERROR'){
358             # error - print if needed, then confess or croak depending on whether or not debugging
359 2 50       6 if($_LOG_ERRORS){
360 0         0 my $log_output = $output;
361 0 0 0     0 if($_DEBUG && $_CAN_STACK_TRACE){
362 0         0 $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string();
363 0         0 print {$_LOG_STREAM} "$output\n";
  0         0  
364             }
365 0         0 print {$_LOG_STREAM} "$log_output\n";
  0         0  
366             }
367 2 50       5 if($_DEBUG){
368 0         0 confess($output);
369             }else{
370 2         443 croak($output);
371             }
372             }else{
373             # we have an unknown severity, so assume the worst and confess (also log if needed)
374 0 0       0 if($_LOG_ERRORS){
375 0         0 my $log_output = $output;
376 0 0       0 if($_CAN_STACK_TRACE){
377 0         0 $log_output .= "\nStack Trace:\n".Devel::StackTrace->new()->as_string();
378             }
379 0         0 print {$_LOG_STREAM} "$log_output\n";
  0         0  
380             }
381 0         0 confess($output);
382             }
383            
384             # to keep perlcritic happy
385 446         1817 return 1;
386             }
387             ## use critic
388              
389             1; # because perl is a tad odd :)