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.30';
15             require 5.006_001;
16              
17 7     7   564285 use strict;
  7         87  
  7         1167  
18 7     7   53 use warnings;
  7         19  
  7         251  
19              
20 7     7   49 use Carp;
  7         18  
  7         494  
21 7     7   4082 use parent qw(Exporter);
  7         2480  
  7         47  
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 574     574   1092 my ($max) = @_;
206 574         1858 return int rand $max;
207             }
208              
209             sub new {
210 28     28 1 573 my ( $proto, @args ) = @_;
211 28   33     143 my $class = ref($proto) || $proto;
212 28         50 my $self;
213 28         182 $self = {%old_patterns}; # makes $self refer to a copy of %old_patterns
214 28         77 my %args = ();
215 28 100       157 if (@args) { %args = @args }
  1         5  
216 28 50       85 if ( defined( $args{'max'} ) ) {
217 0         0 $self->{'_max'} = $args{'max'};
218             }
219             else {
220 28         92 $self->{'_max'} = 10;
221             }
222 28 100       65 if ( defined( $args{'rand_gen'} ) ) {
223 1         3 $self->{'_rand'} = $args{'rand_gen'};
224             }
225             else {
226 27         65 $self->{'_rand'} = \&_rand;
227             }
228 28         103 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 8489 my $self = shift;
235 40 50       122 croak 'called without a reference' if ( !ref($self) );
236              
237 40         84 my @strings = ();
238              
239 40         112 while ( defined( my $pattern = shift ) ) {
240 74         122 my $ch;
241 74         130 my @string = ();
242 74         134 my $string = q{};
243              
244             # Split the characters in the pattern
245             # up into a list for easier parsing.
246 74         323 my @chars = split( //, $pattern );
247              
248 74         250 while ( defined( $ch = shift(@chars) ) ) {
249 198 100       539 if ( defined( $regch{$ch} ) ) {
    50          
250 194         493 $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         19 push( @string, [$ch] );
260             }
261             }
262              
263 74         172 foreach my $ch (@string) {
264 546         1015 $string .= $ch->[ $self->{'_rand'}( scalar( @{$ch} ) ) ];
  546         1227  
265             }
266              
267 74         381 push( @strings, $string );
268             }
269              
270 40 100       254 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 10324 my $self = shift;
283 9 50       33 croak 'called without a reference' if ( !ref($self) );
284              
285 9         17 my @strings = ();
286              
287 9         29 while ( defined( my $pattern = shift ) ) {
288 13         21 my $string = q{};
289              
290 13         34 for my $ch ( split( //, $pattern ) ) {
291 18 50       50 if ( defined( $self->{$ch} ) ) {
292             $string .= $self->{$ch}
293 18         28 ->[ $self->{'_rand'}( scalar( @{ $self->{$ch} } ) ) ];
  18         49  
294             }
295             else {
296 0         0 croak qq(Unknown pattern character "$ch"!);
297             }
298             }
299 13         46 push( @strings, $string );
300             }
301              
302 9 100       61 return wantarray ? @strings : join( q{}, @strings );
303             }
304              
305             sub random_regex {
306 20     20 1 6600 my (@args) = @_;
307 20         68 my $foo = String::Random->new;
308 20         53 return $foo->randregex(@args);
309             }
310              
311             sub random_string {
312 1     1 1 585 my ( $pattern, @list ) = @_;
313              
314 1         6 my $foo = String::Random->new;
315              
316 1         5 for my $n ( 0 .. $#list ) {
317 3         6 $foo->{$n} = [ @{ $list[$n] } ];
  3         19  
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.30
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 VERSION
507              
508             version 0.30
509              
510             =head1 BUGS
511              
512             This is Bug Free™ code. (At least until somebody finds one…)
513              
514             Please report bugs here:
515              
516             L .
517              
518             =head1 AUTHOR
519              
520             Original Author: Steven Pritchard C<< steve@silug.org >>
521              
522             Now maintained by: Shlomi Fish ( L ).
523              
524             =head1 LICENSE
525              
526             This program is free software; you can redistribute it and/or modify it
527             under the same terms as Perl itself.
528              
529             =head1 SEE ALSO
530              
531             perl(1).
532              
533             =head1 AUTHOR
534              
535             Shlomi Fish
536              
537             =head1 COPYRIGHT AND LICENSE
538              
539             This software is copyright (c) 2018 by Shlomi Fish.
540              
541             This is free software; you can redistribute it and/or modify it under
542             the same terms as the Perl 5 programming language system itself.
543              
544             =head1 BUGS
545              
546             Please report any bugs or feature requests on the bugtracker website
547             L
548              
549             When submitting a bug or request, please include a test-file or a
550             patch to an existing test-file that illustrates the bug or desired
551             feature.
552              
553             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
554              
555             =head1 SUPPORT
556              
557             =head2 Perldoc
558              
559             You can find documentation for this module with the perldoc command.
560              
561             perldoc String::Random
562              
563             =head2 Websites
564              
565             The following websites have more information about this module, and may be of help to you. As always,
566             in addition to those websites please use your favorite search engine to discover more resources.
567              
568             =over 4
569              
570             =item *
571              
572             MetaCPAN
573              
574             A modern, open-source CPAN search engine, useful to view POD in HTML format.
575              
576             L
577              
578             =item *
579              
580             Search CPAN
581              
582             The default CPAN search engine, useful to view POD in HTML format.
583              
584             L
585              
586             =item *
587              
588             RT: CPAN's Bug Tracker
589              
590             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
591              
592             L
593              
594             =item *
595              
596             AnnoCPAN
597              
598             The AnnoCPAN is a website that allows community annotations of Perl module documentation.
599              
600             L
601              
602             =item *
603              
604             CPAN Ratings
605              
606             The CPAN Ratings is a website that allows community ratings and reviews of Perl modules.
607              
608             L
609              
610             =item *
611              
612             CPANTS
613              
614             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
615              
616             L
617              
618             =item *
619              
620             CPAN Testers
621              
622             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
623              
624             L
625              
626             =item *
627              
628             CPAN Testers Matrix
629              
630             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
631              
632             L
633              
634             =item *
635              
636             CPAN Testers Dependencies
637              
638             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
639              
640             L
641              
642             =back
643              
644             =head2 Bugs / Feature Requests
645              
646             Please report any bugs or feature requests by email to C, or through
647             the web interface at L. You will be automatically notified of any
648             progress on the request by the system.
649              
650             =head2 Source Code
651              
652             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
653             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
654             from your repository :)
655              
656             L
657              
658             git clone http://github.com/shlomif/String-Random
659              
660             =cut
661              
662             __END__