File Coverage

blib/lib/String/Random.pm
Criterion Covered Total %
statement 64 71 90.1
branch 15 22 68.1
condition 1 3 33.3
subroutine 10 11 90.9
pod 6 6 100.0
total 96 113 84.9


line stmt bran cond sub pod time code
1             # String::Random - Generates a random string from a pattern
2             # Copyright (C) 1999-2006 Steven Pritchard
3             #
4             # This program is free software; you can redistribute it
5             # and/or modify it under the same terms as Perl itself.
6             #
7             # This program is distributed in the hope that it will be useful,
8             # but WITHOUT ANY WARRANTY; without even the implied warranty of
9             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10             #
11             # $Id: Random.pm,v 1.4 2006/09/21 17:34:07 steve Exp $
12              
13             package String::Random;
14             $String::Random::VERSION = '0.31';
15             require 5.006_001;
16              
17 7     7   440449 use strict;
  7         66  
  7         254  
18 7     7   34 use warnings;
  7         10  
  7         167  
19              
20 7     7   29 use Carp;
  7         12  
  7         331  
21 7     7   3146 use parent qw(Exporter);
  7         1990  
  7         32  
22              
23             our %EXPORT_TAGS = (
24             'all' => [
25             qw(
26             &random_string
27             &random_regex
28             )
29             ]
30             );
31             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
32              
33             # These are the various character sets.
34             my @upper = ( 'A' .. 'Z' );
35             my @lower = ( 'a' .. 'z' );
36             my @digit = ( '0' .. '9' );
37             my @punct = map {chr} ( 33 .. 47, 58 .. 64, 91 .. 96, 123 .. 126 );
38             my @any = ( @upper, @lower, @digit, @punct );
39             my @salt = ( @upper, @lower, @digit, '.', '/' );
40             my @binary = map {chr} ( 0 .. 255 );
41              
42             # What's important is how they relate to the pattern characters.
43             # These are the old patterns for randpattern/random_string.
44             my %old_patterns = (
45             'C' => [@upper],
46             'c' => [@lower],
47             'n' => [@digit],
48             '!' => [@punct],
49             '.' => [@any],
50             's' => [@salt],
51             'b' => [@binary],
52             );
53              
54             # These are the regex-based patterns.
55             my %patterns = (
56              
57             # These are the regex-equivalents.
58             '.' => [@any],
59             '\d' => [@digit],
60             '\D' => [ @upper, @lower, @punct ],
61             '\w' => [ @upper, @lower, @digit, '_' ],
62             '\W' => [ grep { $_ ne '_' } @punct ],
63             '\s' => [ q{ }, "\t" ], # Would anything else make sense?
64             '\S' => [ @upper, @lower, @digit, @punct ],
65              
66             # These are translated to their double quoted equivalents.
67             '\t' => ["\t"],
68             '\n' => ["\n"],
69             '\r' => ["\r"],
70             '\f' => ["\f"],
71             '\a' => ["\a"],
72             '\e' => ["\e"],
73             );
74              
75             # This is used for cache of parsed range patterns in %regch
76             my %parsed_range_patterns = ();
77              
78             # These characters are treated specially in randregex().
79             my %regch = (
80             '\\' => sub {
81             my ( $self, $ch, $chars, $string ) = @_;
82             if ( @{$chars} ) {
83             my $tmp = shift( @{$chars} );
84             if ( $tmp eq 'x' ) {
85              
86             # This is supposed to be a number in hex, so
87             # there had better be at least 2 characters left.
88             $tmp = shift( @{$chars} ) . shift( @{$chars} );
89             push( @{$string}, [ chr( hex($tmp) ) ] );
90             }
91             elsif ( $tmp =~ /[0-7]/ ) {
92             carp 'octal parsing not implemented. treating literally.';
93             push( @{$string}, [$tmp] );
94             }
95             elsif ( defined( $patterns{"\\$tmp"} ) ) {
96             $ch .= $tmp;
97             push( @{$string}, $patterns{$ch} );
98             }
99             else {
100             if ( $tmp =~ /\w/ ) {
101             carp "'\\$tmp' being treated as literal '$tmp'";
102             }
103             push( @{$string}, [$tmp] );
104             }
105             }
106             else {
107             croak 'regex not terminated';
108             }
109             },
110             '.' => sub {
111             my ( $self, $ch, $chars, $string ) = @_;
112             push( @{$string}, $patterns{$ch} );
113             },
114             '[' => sub {
115             my ( $self, $ch, $chars, $string ) = @_;
116             my @tmp;
117             while ( defined( $ch = shift( @{$chars} ) ) && ( $ch ne ']' ) ) {
118             if ( ( $ch eq '-' ) && @{$chars} && @tmp ) {
119             my $begin_ch = $tmp[-1];
120             $ch = shift( @{$chars} );
121             my $key = "$begin_ch-$ch";
122             if ( defined( $parsed_range_patterns{$key} ) ) {
123             push( @tmp, @{ $parsed_range_patterns{$key} } );
124             }
125             else {
126             my @chs;
127             for my $n ( ( ord($begin_ch) + 1 ) .. ord($ch) ) {
128             push @chs, chr($n);
129             }
130             $parsed_range_patterns{$key} = \@chs;
131             push @tmp, @chs;
132             }
133             }
134             else {
135             carp "'$ch' will be treated literally inside []"
136             if ( $ch =~ /\W/ );
137             push( @tmp, $ch );
138             }
139             }
140             croak 'unmatched []' if ( $ch ne ']' );
141             push( @{$string}, \@tmp );
142             },
143             '*' => sub {
144             my ( $self, $ch, $chars, $string ) = @_;
145             unshift( @{$chars}, split( //, '{0,}' ) );
146             },
147             '+' => sub {
148             my ( $self, $ch, $chars, $string ) = @_;
149             unshift( @{$chars}, split( //, '{1,}' ) );
150             },
151             '?' => sub {
152             my ( $self, $ch, $chars, $string ) = @_;
153             unshift( @{$chars}, split( //, '{0,1}' ) );
154             },
155             '{' => sub {
156             my ( $self, $ch, $chars, $string ) = @_;
157             my $closed;
158             CLOSED:
159             for my $c ( @{$chars} ) {
160             if ( $c eq '}' ) {
161             $closed = 1;
162             last CLOSED;
163             }
164             }
165             if ($closed) {
166             my $tmp;
167             while ( defined( $ch = shift( @{$chars} ) ) && ( $ch ne '}' ) ) {
168             croak "'$ch' inside {} not supported" if ( $ch !~ /[\d,]/ );
169             $tmp .= $ch;
170             }
171             if ( $tmp =~ /,/ ) {
172             if ( my ( $min, $max ) = $tmp =~ /^(\d*),(\d*)$/ ) {
173             if ( !length($min) ) { $min = 0 }
174             if ( !length($max) ) { $max = $self->{'_max'} }
175             croak "bad range {$tmp}" if ( $min > $max );
176             if ( $min == $max ) {
177             $tmp = $min;
178             }
179             else {
180             $tmp = $min + $self->{'_rand'}( $max - $min + 1 );
181             }
182             }
183             else {
184             croak "malformed range {$tmp}";
185             }
186             }
187             if ($tmp) {
188             my $prev_ch = $string->[-1];
189              
190             push @{$string}, ( ($prev_ch) x ( $tmp - 1 ) );
191             }
192             else {
193             pop( @{$string} );
194             }
195             }
196             else {
197             # { isn't closed, so treat it literally.
198             push( @{$string}, [$ch] );
199             }
200             },
201             );
202              
203             # Default rand function
204             sub _rand {
205 584     584   720 my ($max) = @_;
206 584         1017 return int rand $max;
207             }
208              
209             sub new {
210 28     28 1 484 my ( $proto, @args ) = @_;
211 28   33     111 my $class = ref($proto) || $proto;
212 28         32 my $self;
213 28         145 $self = {%old_patterns}; # makes $self refer to a copy of %old_patterns
214 28         49 my %args = ();
215 28 100       134 if (@args) { %args = @args }
  1         3  
216 28 50       68 if ( defined( $args{'max'} ) ) {
217 0         0 $self->{'_max'} = $args{'max'};
218             }
219             else {
220 28         65 $self->{'_max'} = 10;
221             }
222 28 100       54 if ( defined( $args{'rand_gen'} ) ) {
223 1         2 $self->{'_rand'} = $args{'rand_gen'};
224             }
225             else {
226 27         47 $self->{'_rand'} = \&_rand;
227             }
228 28         80 return bless( $self, $class );
229             }
230              
231             # Returns a random string for each regular expression given as an
232             # argument, or the strings concatenated when used in a scalar context.
233             sub randregex {
234 40     40 1 5936 my $self = shift;
235 40 50       81 croak 'called without a reference' if ( !ref($self) );
236              
237 40         52 my @strings = ();
238              
239 40         90 while ( defined( my $pattern = shift ) ) {
240 74         93 my $ch;
241 74         148 my @string = ();
242 74         124 my $string = q{};
243              
244             # Split the characters in the pattern
245             # up into a list for easier parsing.
246 74         202 my @chars = split( //, $pattern );
247              
248 74         134 while ( defined( $ch = shift(@chars) ) ) {
249 198 100       376 if ( defined( $regch{$ch} ) ) {
    50          
250 194         467 $regch{$ch}->( $self, $ch, \@chars, \@string );
251             }
252             elsif ( $ch =~ /[\$\^\*\(\)\+\{\}\]\|\?]/ ) {
253              
254             # At least some of these probably should have special meaning.
255 0         0 carp "'$ch' not implemented. treating literally.";
256 0         0 push( @string, [$ch] );
257             }
258             else {
259 4         13 push( @string, [$ch] );
260             }
261             }
262              
263 74         108 foreach my $ch (@string) {
264 556         583 $string .= $ch->[ $self->{'_rand'}( scalar( @{$ch} ) ) ];
  556         811  
265             }
266              
267 74         299 push( @strings, $string );
268             }
269              
270 40 100       174 return wantarray ? @strings : join( q{}, @strings );
271             }
272              
273             # For compatibility with an ancient version, please ignore...
274             sub from_pattern {
275 0     0 1 0 my ( $self, @args ) = @_;
276 0 0       0 croak 'called without a reference' if ( !ref($self) );
277              
278 0         0 return $self->randpattern(@args);
279             }
280              
281             sub randpattern {
282 9     9 1 9808 my $self = shift;
283 9 50       36 croak 'called without a reference' if ( !ref($self) );
284              
285 9         18 my @strings = ();
286              
287 9         21 while ( defined( my $pattern = shift ) ) {
288 13         16 my $string = q{};
289              
290 13         32 for my $ch ( split( //, $pattern ) ) {
291 18 50       42 if ( defined( $self->{$ch} ) ) {
292             $string .= $self->{$ch}
293 18         25 ->[ $self->{'_rand'}( scalar( @{ $self->{$ch} } ) ) ];
  18         37  
294             }
295             else {
296 0         0 croak qq(Unknown pattern character "$ch"!);
297             }
298             }
299 13         42 push( @strings, $string );
300             }
301              
302 9 100       63 return wantarray ? @strings : join( q{}, @strings );
303             }
304              
305             sub random_regex {
306 20     20 1 4707 my (@args) = @_;
307 20         53 my $foo = String::Random->new;
308 20         40 return $foo->randregex(@args);
309             }
310              
311             sub random_string {
312 1     1 1 426 my ( $pattern, @list ) = @_;
313              
314 1         3 my $foo = String::Random->new;
315              
316 1         3 for my $n ( 0 .. $#list ) {
317 3         4 $foo->{$n} = [ @{ $list[$n] } ];
  3         12  
318             }
319              
320 1         5 return $foo->randpattern($pattern);
321             }
322              
323             1;
324              
325             =pod
326              
327             =encoding UTF-8
328              
329             =head1 NAME
330              
331             String::Random - Perl module to generate random strings based on a pattern
332              
333             =head1 VERSION
334              
335             version 0.31
336              
337             =head1 SYNOPSIS
338              
339             use String::Random;
340             my $string_gen = String::Random->new;
341             print $string_gen->randregex('\d\d\d'); # Prints 3 random digits
342             # Prints 3 random printable characters
343             print $string_gen->randpattern("...");
344              
345             I
346              
347             use String::Random qw(random_regex random_string);
348             print random_regex('\d\d\d'); # Also prints 3 random digits
349             print random_string("..."); # Also prints 3 random printable characters
350              
351             =head1 DESCRIPTION
352              
353             This module makes it trivial to generate random strings.
354              
355             As an example, let's say you are writing a script that needs to generate a
356             random password for a user. The relevant code might look something like
357             this:
358              
359             use String::Random;
360             my $pass = String::Random->new;
361             print "Your password is ", $pass->randpattern("CCcc!ccn"), "\n";
362              
363             This would output something like this:
364              
365             Your password is UDwp$tj5
366              
367             B: currently, C defaults to Perl's built-in predictable
368             random number generator so the passwords generated by it are insecure. See the
369             C option to C constructor to specify a more secure
370             random number generator. There is no equivalent to this in the procedural
371             interface, you must use the object-oriented interface to get this
372             functionality.
373              
374             If you are more comfortable dealing with regular expressions, the following
375             code would have a similar result:
376              
377             use String::Random;
378             my $pass = String::Random->new;
379             print "Your password is ",
380             $pass->randregex('[A-Z]{2}[a-z]{2}.[a-z]{2}\d'), "\n";
381              
382             =head2 Patterns
383              
384             The pre-defined patterns (for use with C and C)
385             are as follows:
386              
387             c Any Latin lowercase character [a-z]
388             C Any Latin uppercase character [A-Z]
389             n Any digit [0-9]
390             ! A punctuation character [~`!@$%^&*()-_+={}[]|\:;"'.<>?/#,]
391             . Any of the above
392             s A "salt" character [A-Za-z0-9./]
393             b Any binary data
394              
395             These can be modified, but if you need a different pattern it is better to
396             create another pattern, possibly using one of the pre-defined as a base.
397             For example, if you wanted a pattern C that contained all upper and lower
398             case letters (C<[A-Za-z]>), the following would work:
399              
400             my $gen = String::Random->new;
401             $gen->{'A'} = [ 'A'..'Z', 'a'..'z' ];
402              
403             I
404              
405             my $gen = String::Random->new;
406             $gen->{'A'} = [ @{$gen->{'C'}}, @{$gen->{'c'}} ];
407              
408             The random_string function, described below, has an alternative interface
409             for adding patterns.
410              
411             =head2 Methods
412              
413             =over 8
414              
415             =item new
416              
417             =item new max =E I
418              
419             =item new rand_gen =E I
420              
421             Create a new String::Random object.
422              
423             Optionally a parameter C can be included to specify the maximum number
424             of characters to return for C<*> and other regular expression patterns that
425             do not return a fixed number of characters.
426              
427             Optionally a parameter C can be included to specify a subroutine
428             coderef for generating the random numbers used in this module. The coderef
429             must accept one argument C and return an integer between 0 and C.
430             The default rand_gen coderef is
431              
432             sub {
433             my ($max) = @_;
434             return int rand $max;
435             }
436              
437             =item randpattern LIST
438              
439             The randpattern method returns a random string based on the concatenation
440             of all the pattern strings in the list.
441              
442             It will return a list of random strings corresponding to the pattern
443             strings when used in list context.
444              
445             =item randregex LIST
446              
447             The randregex method returns a random string that will match the regular
448             expression passed in the list argument.
449              
450             Please note that the arguments to randregex are not real regular
451             expressions. Only a small subset of regular expression syntax is actually
452             supported. So far, the following regular expression elements are
453             supported:
454              
455             \w Alphanumeric + "_".
456             \d Digits.
457             \W Printable characters other than those in \w.
458             \D Printable characters other than those in \d.
459             . Printable characters.
460             [] Character classes.
461             {} Repetition.
462             * Same as {0,}.
463             ? Same as {0,1}.
464             + Same as {1,}.
465              
466             Regular expression support is still somewhat incomplete. Currently special
467             characters inside [] are not supported (with the exception of "-" to denote
468             ranges of characters). The parser doesn't care for spaces in the "regular
469             expression" either.
470              
471             =item from_pattern
472              
473             B - for compatibility with an old version. B
474              
475             =back
476              
477             =head2 Functions
478              
479             =over 8
480              
481             =item random_string PATTERN,LIST
482              
483             =item random_string PATTERN
484              
485             When called with a single scalar argument, random_string returns a random
486             string using that scalar as a pattern. Optionally, references to lists
487             containing other patterns can be passed to the function. Those lists will
488             be used for 0 through 9 in the pattern (meaning the maximum number of lists
489             that can be passed is 10). For example, the following code:
490              
491             print random_string("0101",
492             ["a", "b", "c"],
493             ["d", "e", "f"]), "\n";
494              
495             would print something like this:
496              
497             cebd
498              
499             =item random_regex REGEX_IN_STRING
500              
501             Prints a string for the regular expression given as the string. See the
502             synposis for example.
503              
504             =back
505              
506             =head1 BUGS
507              
508             This is Bug Free™ code. (At least until somebody finds one…)
509              
510             Please report bugs here:
511              
512             L .
513              
514             =head1 AUTHOR
515              
516             Original Author: Steven Pritchard C<< steve@silug.org >>
517              
518             Now maintained by: Shlomi Fish ( L ).
519              
520             =head1 LICENSE
521              
522             This program is free software; you can redistribute it and/or modify it
523             under the same terms as Perl itself.
524              
525             =head1 SEE ALSO
526              
527             perl(1).
528              
529             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
530              
531             =head1 SUPPORT
532              
533             =head2 Websites
534              
535             The following websites have more information about this module, and may be of help to you. As always,
536             in addition to those websites please use your favorite search engine to discover more resources.
537              
538             =over 4
539              
540             =item *
541              
542             MetaCPAN
543              
544             A modern, open-source CPAN search engine, useful to view POD in HTML format.
545              
546             L
547              
548             =item *
549              
550             RT: CPAN's Bug Tracker
551              
552             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
553              
554             L
555              
556             =item *
557              
558             CPANTS
559              
560             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
561              
562             L
563              
564             =item *
565              
566             CPAN Testers
567              
568             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
569              
570             L
571              
572             =item *
573              
574             CPAN Testers Matrix
575              
576             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
577              
578             L
579              
580             =item *
581              
582             CPAN Testers Dependencies
583              
584             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
585              
586             L
587              
588             =back
589              
590             =head2 Bugs / Feature Requests
591              
592             Please report any bugs or feature requests by email to C, or through
593             the web interface at L. You will be automatically notified of any
594             progress on the request by the system.
595              
596             =head2 Source Code
597              
598             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
599             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
600             from your repository :)
601              
602             L
603              
604             git clone http://github.com/shlomif/String-Random
605              
606             =head1 AUTHOR
607              
608             Shlomi Fish
609              
610             =head1 BUGS
611              
612             Please report any bugs or feature requests on the bugtracker website
613             L
614              
615             When submitting a bug or request, please include a test-file or a
616             patch to an existing test-file that illustrates the bug or desired
617             feature.
618              
619             =head1 COPYRIGHT AND LICENSE
620              
621             This software is copyright (c) 2020 by Shlomi Fish.
622              
623             This is free software; you can redistribute it and/or modify it under
624             the same terms as the Perl 5 programming language system itself.
625              
626             =cut
627              
628             __END__