File Coverage

blib/lib/Data/Random/Flexible.pm
Criterion Covered Total %
statement 17 140 12.1
branch 0 62 0.0
condition 0 15 0.0
subroutine 5 25 20.0
pod 3 10 30.0
total 25 252 9.9


line stmt bran cond sub pod time code
1             package Data::Random::Flexible;
2              
3 1     1   90989 use strict;
  1         4  
  1         37  
4 1     1   8 use warnings;
  1         2  
  1         37  
5              
6 1     1   678 use Try::Tiny;
  1         2733  
  1         72  
7 1     1   651 use Module::Runtime qw(require_module);
  1         2388  
  1         7  
8              
9             my $engines;
10              
11             BEGIN {
12 1     1   163 my @optional = qw(
13             Math::Random::Secure
14             Math::Random::MTwist
15             Math::Random::Xorshift
16             Math::Random::MT
17             Math::Random::ISAAC
18             Crypt::PRNG
19             );
20              
21 1         4 foreach my $module (@optional) {
22             try {
23 6         204 require_module $module;
24 0         0 $engines->{$module} = 1;
25 6         1307 };
26             }
27              
28             # Add Core::rand back
29 1         2588 $engines->{'CORE'} = 1;
30             }
31              
32             =head1 NAME
33              
34             Data::Random::Flexible - Flexible fast-to-write profilable randoms
35              
36             =head1 VERSION
37              
38             Version 1.06
39              
40             =cut
41              
42             our $VERSION = '1.06';
43              
44              
45             =head1 SYNOPSIS
46              
47             A more flexible set of randoms for when you want to be random FAST
48              
49             use Data::Random::Flexible;
50              
51             use feature "say";
52              
53             my $random = Data::Random::Flexible->new();
54              
55             say "32 Characters of random numbers?, sure: ".$random->int(32);
56              
57             say "16 Characters of random letters?, sure: ".$random->char(16);
58              
59             say "16 Of a mixture of numbers and letters?, sure: ".$random->mix(16);
60              
61             say "Random mixture of 16 your own characters?, sure: ".$random->profile('irc',16, [qw(I r C 1 2 3)]);
62            
63             say "Random mixture of 16 your own characters from a saved profile?, sure: ".$random->profile('irc',16);
64              
65             The module can also use alternative providers for rand(), for more detail look at the engine() function,
66             the currently supported providers of random are:
67              
68             Math::Random::Secure
69             Math::Random::MTwist
70             Math::Random::Xorshift
71             Math::Random::MT
72             Math::Random::ISAAC
73             Math::Random::ISAAC::XS (Not selectable will be used AUTO if availible by Math::Random::ISAAC)
74             Crypt::PRNG
75             Your own code reference.
76              
77              
78             =head1 new()
79              
80             Create a new Math::Random::Flexible object, accepts 1 optional argument, a hashref of profiles
81              
82             =cut
83              
84             sub new {
85 0     0 0   my ($class,$profiles) = @_;
86 0 0         $profiles = {} if (!$profiles);
87            
88 0           my $return = bless { profiles=>$profiles }, $class;
89 0           $return->engine('CORE');
90              
91 0           return $return;
92             }
93              
94             =head1 engine()
95              
96             Return a list of availible engines for rand(), by default the module will always use
97             CORE::rand, that being perls inbuilt rand. If you want to change it simply provide
98             your choice as the first argument.
99              
100             If you pass in a reference to your own random function it will attempt a test against it
101             if successful it will use that!
102              
103             An example of passing your own:
104              
105             sub mycode { return int(rand(9)) }
106              
107             $random->engine(\&mycode);
108              
109             If you pass something weird that is not a known engine or a reference, it will not switch
110             engines but will raise a warning.
111              
112             NOTE Normal every day users just wanting a nice way to get random numbers and such
113             of a set length need not pay attention to it!
114              
115             =cut
116              
117             sub engine {
118 0     0 0   my ($self,$select) = @_;
119            
120 0 0         if ($select) {
121 0           my $newengine;
122              
123             # Check if its a custom engine
124 0 0         if ( ref $select eq 'CODE' ) {
125             # It is, we can assume its all ready to test
126 0           my $testresult = 1;
127             try {
128 0     0     my $value = CORE::int( &{$select}(9) );
  0            
129 0 0 0       if ( $value >= 0 && $value <9 ) {
130 0           $testresult = 0;
131             }
132 0           };
133 0 0         if ($testresult) {
134 0           warn "Engine passed in via coderef does not return a sane value! (ignored)";
135 0           return;
136             }
137 0           $self->{engine}->{selected} = 'USER';
138 0     0     $self->{engine}->{USER} = sub { &{ $select }(@_) };
  0            
  0            
139 0           return;
140             }
141              
142             # As its not a reference it must be one of ours...
143 0           foreach (keys %$engines) {
144 0 0         if ( m#^\Q$select\E$#i ) {
145 0           $self->{engine}->{last} = $self->{engine}->{selected};
146 0           $self->{engine}->{selected} = $_;
147 0           $newengine = 1;
148 0           last;
149             }
150             }
151              
152             # Different engines require different initilization, lets handle that here
153 0 0         if (! $newengine ) {
    0          
154 0           warn "The engine you chose, '$select' could not be selected, you sure its known to us?";
155             return
156 0           }
157             elsif (! $self->{engine}->{$self->{engine}->{selected}} ) {
158             # Initilize the engine
159 0           my $engine = $self->{engine}->{selected};
160 0           my @seed = map join('',map int(rand(9)),@{[1..10]}),@{[1..4]};
  0            
  0            
161              
162 0 0         if ( $engine eq 'Math::Random::Secure' ) {
    0          
    0          
    0          
    0          
    0          
    0          
163             # Do not really need to do anything for this one, does not even have an object method
164 0     0     $self->{engine}->{$engine} = sub { Math::Random::Secure::rand(shift) };
  0            
165             }
166             elsif ( $engine eq 'Math::Random::MTwist' ) {
167             # Seeds from dev/random no need to
168 0           $self->{engine}->{obj}->{$engine} = Math::Random::MTwist->new();
169 0     0     $self->{engine}->{$engine} = sub { $self->{engine}->{obj}->{$engine}->rand(shift) };
  0            
170             }
171             elsif ( $engine eq 'Math::Random::Xorshift' ) {
172 0           $self->{engine}->{obj}->{$engine} = Math::Random::Xorshift->new( @seed );
173 0     0     $self->{engine}->{$engine} = sub { $self->{engine}->{obj}->{$engine}->rand(shift) };
  0            
174             }
175             elsif ( $engine eq 'Math::Random::MT' ) {
176 0           $self->{engine}->{obj}->{$engine} = Math::Random::MT->new( @seed );
177 0     0     $self->{engine}->{$engine} = sub { $self->{engine}->{obj}->{$engine}->rand(shift) };
  0            
178             }
179             elsif ( $engine eq 'Math::Random::ISAAC' ) {
180 0           $self->{engine}->{obj}->{$engine} = Math::Random::ISAAC->new( @seed );
181 0     0     $self->{engine}->{$engine} = sub { $self->{engine}->{obj}->{$engine}->rand(shift) };
  0            
182             }
183             elsif ( $engine eq 'Crypt::PRNG' ) {
184             # Seeds from dev/random no need to
185 0           $self->{engine}->{obj}->{$engine} = Crypt::PRNG->new( );
186 0     0     $self->{engine}->{$engine} = sub { $self->{engine}->{obj}->{$engine}->double(shift) };
  0            
187             }
188             elsif ( $engine eq 'CORE' ) {
189 0     0     $self->{engine}->{$engine} = sub { CORE::rand(shift) };
  0            
190             }
191             }
192 0           return;
193             }
194              
195 0           return keys %$engines;
196             }
197              
198             =head1 store()
199              
200             Set and/or return the stored profiles, will always return the currently used profiles,
201             unless you pass it something it did not expect as a first argument, where it will return
202             a blank hashref.
203              
204             =cut
205              
206             sub store {
207 0     0 0   my ($self,$new_profiles) = @_;
208              
209 0 0         if (!$new_profiles) {
    0          
210 0           return $self->{profiles};
211             }
212             elsif (ref $new_profiles ne 'HASH') {
213 0           warn "First argument for profiles() must be a hashref!";
214 0           return {};
215             }
216             else {
217 0           $self->{profiles} = $new_profiles;
218             }
219              
220 0           return $self->{profiles};
221             }
222              
223             sub _rand {
224 0     0     my ($self,$option) = @_;
225 0           return $self->{engine}->{$self->{engine}->{selected}}->($option);
226             }
227              
228             =head1 alpha()
229              
230             Return a random alpha character uppercase or lowercase, accepts 1 argument 'length',
231             if length is ommited return a single alpha-char;
232              
233             =head2 char()
234              
235             Though technically wrong, it is a shorthand to alpha()
236              
237             =cut
238              
239             sub char {
240 0     0 1   return alpha(@_)
241             }
242              
243             sub alpha {
244 0     0 0   my ($self,$length) = @_;
245              
246 0 0 0       if ( !defined $length || $length !~ m#^\d+$# ) {
    0          
247 0           $length = 1;
248             }
249             elsif (!$length) {
250             # If we got 0 passed as a length
251 0           return;
252             }
253              
254 0           my $randAlpha = "";
255              
256 0           for ( 1..$length ) {
257 0           my $key = 'a';
258 0           for ( 1..CORE::int($self->_rand(26)) ) { $key++ }
  0            
259 0 0         if ( CORE::int($self->_rand(2)) ) { $key = uc($key) }
  0            
260 0           $randAlpha .= $key;
261             }
262              
263 0           return $randAlpha;
264             }
265              
266             =head1 numeric()
267              
268             Return a random whole number, accepts 1 argument 'length', if length is ommited
269             return a single number.
270              
271             =head2 int()
272              
273             A shorthand for numeric()
274              
275             =cut
276              
277             sub int {
278 0     0 1   return numeric(@_);
279             }
280              
281             sub numeric {
282 0     0 0   my ($self,$length) = @_;
283              
284 0 0 0       if ( !defined $length || $length !~ m#^\d+$# ) {
    0          
285 0           $length = 1;
286             }
287             elsif (!$length) {
288             # If we got 0 passed as a length
289 0           return;
290             }
291              
292             # Never allow the first number to be a 0 as it does not
293             # really exist as a prefixed number.
294 0           my $randInt = 1+CORE::int($self->_rand(9));
295 0           $length--;
296              
297 0           for (1..$length) {
298 0           $randInt .= CORE::int($self->_rand(10));
299             }
300              
301 0           return $randInt;
302             }
303              
304             =head1 alphanumeric()
305              
306             Return a random alphanumeric string, accepts 1 argument 'length', if length is ommited
307             return a single random alpha or number.
308              
309             =head2 mix()
310              
311             A shorthand for alphanumeric()
312              
313             =cut
314              
315             sub mix {
316 0     0 1   return alphanumeric(@_);
317             }
318              
319             sub alphanumeric {
320 0     0 0   my ($self,$length) = @_;
321              
322 0 0 0       if ( !defined $length || $length !~ m#^\d+$# ) {
    0          
323 0           $length = 1;
324             }
325             elsif (!$length) {
326             # If we got 0 passed as a length
327 0           return;
328             }
329              
330 0           my $randAN = "";
331              
332 0           for ( 1..$length) {
333 0 0         if ( CORE::int($self->_rand(2)) ) { $randAN .= $self->numeric() }
  0            
334 0           else { $randAN .= $self->alpha() }
335             }
336              
337 0           return $randAN;
338             }
339              
340             =head1 profile()
341              
342             Set or adjust a profile of characters to be used for randoms, accepts 3 arguments in
343             the following usages:
344              
345             Create or edit a profile named some_name and return a 16 long string from it
346              
347             $random->profile('some_name',16,[qw(1 2 3)]);
348              
349              
350             Return 16 chars from the pre-saved profile 'some_name'
351              
352             $random->profile('some_name',16);
353              
354              
355             Delete a stored profile
356              
357             $random->profile('some_name',0,[]);
358              
359             =cut
360              
361             sub profile {
362 0     0 0   my ($self,$profile_name,$length,$charset) = @_;
363              
364 0 0 0       if ( !defined $length || $length !~ m#^\d+$# ) {
    0          
365 0           $length = 1;
366             }
367             elsif (!$length) {
368             # If we got 0 passed as a length
369 0           return;
370             }
371              
372             # Maybe we are adding or overwriting a profile
373 0 0         if ( $charset ) {
374 0 0         if ( ref $charset ne 'ARRAY' ) {
    0          
375 0           warn "Charset MUST be an arrayref!";
376 0           return;
377             }
378 0           elsif ( scalar @{ $charset } == 0 ) {
379 0           return delete $self->{profiles}->{$profile_name};
380             }
381              
382 0           $self->{profiles}->{$profile_name} = $charset;
383              
384 0           return $self->profile( $profile_name, $length );
385             }
386              
387             # Ok lets check we have the profile, if not return nothing
388 0 0         if (! $self->{profiles}->{$profile_name} ) {
389 0           return " "x$length;
390             }
391              
392             # All looks good..
393 0           my $randProf = "";
394 0           my $key_max = scalar @{ $self->{profiles}->{$profile_name} };
  0            
395              
396 0           for ( 1..$length ) {
397 0           $randProf .= $self->{profiles}->{$profile_name}->[ CORE::int( $self->_rand( $key_max ) ) ];
398             }
399              
400 0           return $randProf;
401             }
402              
403              
404             =head1 AUTHOR
405              
406             Paul G Webster, C<< >>
407              
408             =head1 BUGS
409              
410             Please report any bugs to: L
411              
412             =head1 SUPPORT
413              
414             You can find documentation for this module with the perldoc command.
415              
416             perldoc p5::Data::Random::Flexible
417              
418              
419             You can also look for information at:
420              
421             =over 4
422              
423             =item * AnnoCPAN: Annotated CPAN documentation
424              
425             L
426              
427             =item * CPAN Ratings
428              
429             L
430              
431             =item * Search CPAN
432              
433             L
434              
435             =back
436              
437             =head1 LICENSE AND COPYRIGHT
438              
439             Copyright 2017 Paul G Webster.
440              
441             This program is distributed under the (Revised) BSD License:
442             L
443              
444             Redistribution and use in source and binary forms, with or without
445             modification, are permitted provided that the following conditions
446             are met:
447              
448             * Redistributions of source code must retain the above copyright
449             notice, this list of conditions and the following disclaimer.
450              
451             * Redistributions in binary form must reproduce the above copyright
452             notice, this list of conditions and the following disclaimer in the
453             documentation and/or other materials provided with the distribution.
454              
455             * Neither the name of Paul G Webster's Organization
456             nor the names of its contributors may be used to endorse or promote
457             products derived from this software without specific prior written
458             permission.
459              
460             THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
461             "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
462             LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
463             A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
464             OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
465             SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
466             LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
467             DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
468             THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
469             (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
470             OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
471              
472              
473             =cut
474              
475             1; # End of p5::Data::Random::Flexible