File Coverage

blib/lib/String/Random.pm
Criterion Covered Total %
statement 68 75 90.6
branch 15 22 68.1
condition 1 3 33.3
subroutine 12 13 92.3
pod 8 8 100.0
total 104 121 85.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.32';
15             require 5.006_001;
16              
17 8     8   586519 use strict;
  8         107  
  8         258  
18 8     8   43 use warnings;
  8         13  
  8         214  
19              
20 8     8   40 use Carp;
  8         15  
  8         450  
21 8     8   4161 use parent qw(Exporter);
  8         2728  
  8         48  
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 592     592   804 my ($max) = @_;
206 592         1351 return int rand $max;
207             }
208              
209             sub new {
210 31     31 1 674 my ( $proto, @args ) = @_;
211 31   33     146 my $class = ref($proto) || $proto;
212 31         47 my $self;
213 31         182 $self = {%old_patterns}; # makes $self refer to a copy of %old_patterns
214 31         66 my %args = ();
215 31 100       191 if (@args) { %args = @args }
  1         4  
216 31 50       123 if ( defined( $args{'max'} ) ) {
217 0         0 $self->{'_max'} = $args{'max'};
218             }
219             else {
220 31         106 $self->{'_max'} = 10;
221             }
222 31 100       67 if ( defined( $args{'rand_gen'} ) ) {
223 1         2 $self->{'_rand'} = $args{'rand_gen'};
224             }
225             else {
226 30         67 $self->{'_rand'} = \&_rand;
227             }
228 31         99 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 6929 my $self = shift;
235 40 50       99 croak 'called without a reference' if ( !ref($self) );
236              
237 40         62 my @strings = ();
238              
239 40         88 while ( defined( my $pattern = shift ) ) {
240 74         100 my $ch;
241 74         106 my @string = ();
242 74         104 my $string = q{};
243              
244             # Split the characters in the pattern
245             # up into a list for easier parsing.
246 74         218 my @chars = split( //, $pattern );
247              
248 74         186 while ( defined( $ch = shift(@chars) ) ) {
249 198 100       380 if ( defined( $regch{$ch} ) ) {
    50          
250 194         373 $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         122 foreach my $ch (@string) {
264 552         703 $string .= $ch->[ $self->{'_rand'}( scalar( @{$ch} ) ) ];
  552         848  
265             }
266              
267 74         307 push( @strings, $string );
268             }
269              
270 40 100       202 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 16     16 1 13557 my $self = shift;
283 16 50       50 croak 'called without a reference' if ( !ref($self) );
284              
285 16         35 my @strings = ();
286              
287 16         47 while ( defined( my $pattern = shift ) ) {
288 24         37 my $string = q{};
289              
290 24         74 for my $ch ( split( //, $pattern ) ) {
291 30 50       84 if ( defined( $self->{$ch} ) ) {
292             $string .= $self->{$ch}
293 30         47 ->[ $self->{'_rand'}( scalar( @{ $self->{$ch} } ) ) ];
  30         79  
294             }
295             else {
296 0         0 croak qq(Unknown pattern character "$ch"!);
297             }
298             }
299 24         87 push( @strings, $string );
300             }
301              
302 16 100       104 return wantarray ? @strings : join( q{}, @strings );
303             }
304              
305             sub get_pattern {
306 55     55 1 9277 my ( $self, $name ) = @_;
307 55         176 return $self->{ $name };
308             }
309              
310             sub set_pattern {
311 4     4 1 286 my ( $self, $name, $charset ) = @_;
312 4         16 $self->{ $name } = $charset;
313             }
314              
315             sub random_regex {
316 20     20 1 5796 my (@args) = @_;
317 20         59 my $foo = String::Random->new;
318 20         50 return $foo->randregex(@args);
319             }
320              
321             sub random_string {
322 1     1 1 487 my ( $pattern, @list ) = @_;
323              
324 1         3 my $foo = String::Random->new;
325              
326 1         4 for my $n ( 0 .. $#list ) {
327 3         5 $foo->{$n} = [ @{ $list[$n] } ];
  3         14  
328             }
329              
330 1         4 return $foo->randpattern($pattern);
331             }
332              
333             1;
334              
335             =pod
336              
337             =encoding UTF-8
338              
339             =head1 NAME
340              
341             String::Random - Perl module to generate random strings based on a pattern
342              
343             =head1 VERSION
344              
345             version 0.32
346              
347             =head1 SYNOPSIS
348              
349             use String::Random;
350             my $string_gen = String::Random->new;
351             print $string_gen->randregex('\d\d\d'); # Prints 3 random digits
352             # Prints 3 random printable characters
353             print $string_gen->randpattern("...");
354              
355             I
356              
357             use String::Random qw(random_regex random_string);
358             print random_regex('\d\d\d'); # Also prints 3 random digits
359             print random_string("..."); # Also prints 3 random printable characters
360              
361             =head1 DESCRIPTION
362              
363             This module makes it trivial to generate random strings.
364              
365             As an example, let's say you are writing a script that needs to generate a
366             random password for a user. The relevant code might look something like
367             this:
368              
369             use String::Random;
370             my $pass = String::Random->new;
371             print "Your password is ", $pass->randpattern("CCcc!ccn"), "\n";
372              
373             This would output something like this:
374              
375             Your password is UDwp$tj5
376              
377             B: currently, C defaults to Perl's built-in predictable
378             random number generator so the passwords generated by it are insecure. See the
379             C option to C constructor to specify a more secure
380             random number generator. There is no equivalent to this in the procedural
381             interface, you must use the object-oriented interface to get this
382             functionality.
383              
384             If you are more comfortable dealing with regular expressions, the following
385             code would have a similar result:
386              
387             use String::Random;
388             my $pass = String::Random->new;
389             print "Your password is ",
390             $pass->randregex('[A-Z]{2}[a-z]{2}.[a-z]{2}\d'), "\n";
391              
392             =head2 Patterns
393              
394             The pre-defined patterns (for use with C and C)
395             are as follows:
396              
397             c Any Latin lowercase character [a-z]
398             C Any Latin uppercase character [A-Z]
399             n Any digit [0-9]
400             ! A punctuation character [~`!@$%^&*()-_+={}[]|\:;"'.<>?/#,]
401             . Any of the above
402             s A "salt" character [A-Za-z0-9./]
403             b Any binary data
404              
405             These can be modified, but if you need a different pattern it is better to
406             create another pattern, possibly using one of the pre-defined as a base.
407             For example, if you wanted a pattern C that contained all upper and lower
408             case letters (C<[A-Za-z]>), the following would work:
409              
410             my $gen = String::Random->new;
411             $gen->{'A'} = [ 'A'..'Z', 'a'..'z' ];
412              
413             I
414              
415             my $gen = String::Random->new;
416             $gen->{'A'} = [ @{$gen->{'C'}}, @{$gen->{'c'}} ];
417              
418             I
419              
420             my $gen = String::Random->new;
421             $gen->set_pattern(A => [ 'A'..'Z', 'a'..'z' ]);
422              
423             The random_string function, described below, has an alternative interface
424             for adding patterns.
425              
426             =head2 Methods
427              
428             =over 8
429              
430             =item new
431              
432             =item new max =E I
433              
434             =item new rand_gen =E I
435              
436             Create a new String::Random object.
437              
438             Optionally a parameter C can be included to specify the maximum number
439             of characters to return for C<*> and other regular expression patterns that
440             do not return a fixed number of characters.
441              
442             Optionally a parameter C can be included to specify a subroutine
443             coderef for generating the random numbers used in this module. The coderef
444             must accept one argument C and return an integer between 0 and C.
445             The default rand_gen coderef is
446              
447             sub {
448             my ($max) = @_;
449             return int rand $max;
450             }
451              
452             =item randpattern LIST
453              
454             The randpattern method returns a random string based on the concatenation
455             of all the pattern strings in the list.
456              
457             It will return a list of random strings corresponding to the pattern
458             strings when used in list context.
459              
460             =item randregex LIST
461              
462             The randregex method returns a random string that will match the regular
463             expression passed in the list argument.
464              
465             Please note that the arguments to randregex are not real regular
466             expressions. Only a small subset of regular expression syntax is actually
467             supported. So far, the following regular expression elements are
468             supported:
469              
470             \w Alphanumeric + "_".
471             \d Digits.
472             \W Printable characters other than those in \w.
473             \D Printable characters other than those in \d.
474             . Printable characters.
475             [] Character classes.
476             {} Repetition.
477             * Same as {0,}.
478             ? Same as {0,1}.
479             + Same as {1,}.
480              
481             Regular expression support is still somewhat incomplete. Currently special
482             characters inside [] are not supported (with the exception of "-" to denote
483             ranges of characters). The parser doesn't care for spaces in the "regular
484             expression" either.
485              
486             =item get_pattern STRING
487              
488             Return a pattern given a name.
489              
490             my $gen = String::Random->new;
491             $gen->get_pattern('C');
492              
493             (Added in version 0.32.)
494              
495             =item set_pattern STRING ARRAYREF
496              
497             Add or redefine a pattern given a name and a character set.
498              
499             my $gen = String::Random->new;
500             $gen->set_pattern(A => [ 'A'..'Z', 'a'..'z' ]);
501              
502             (Added in version 0.32.)
503              
504             =item from_pattern
505              
506             B - for compatibility with an old version. B
507              
508             =back
509              
510             =head2 Functions
511              
512             =over 8
513              
514             =item random_string PATTERN,LIST
515              
516             =item random_string PATTERN
517              
518             When called with a single scalar argument, random_string returns a random
519             string using that scalar as a pattern. Optionally, references to lists
520             containing other patterns can be passed to the function. Those lists will
521             be used for 0 through 9 in the pattern (meaning the maximum number of lists
522             that can be passed is 10). For example, the following code:
523              
524             print random_string("0101",
525             ["a", "b", "c"],
526             ["d", "e", "f"]), "\n";
527              
528             would print something like this:
529              
530             cebd
531              
532             =item random_regex REGEX_IN_STRING
533              
534             Prints a string for the regular expression given as the string. See the
535             synposis for example.
536              
537             =back
538              
539             =head1 BUGS
540              
541             This is Bug Free™ code. (At least until somebody finds one…)
542              
543             Please report bugs here:
544              
545             L .
546              
547             =head1 AUTHOR
548              
549             Original Author: Steven Pritchard C<< steve@silug.org >>
550              
551             Now maintained by: Shlomi Fish ( L ).
552              
553             =head1 LICENSE
554              
555             This program is free software; you can redistribute it and/or modify it
556             under the same terms as Perl itself.
557              
558             =head1 SEE ALSO
559              
560             perl(1).
561              
562             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
563              
564             =head1 SUPPORT
565              
566             =head2 Websites
567              
568             The following websites have more information about this module, and may be of help to you. As always,
569             in addition to those websites please use your favorite search engine to discover more resources.
570              
571             =over 4
572              
573             =item *
574              
575             MetaCPAN
576              
577             A modern, open-source CPAN search engine, useful to view POD in HTML format.
578              
579             L
580              
581             =item *
582              
583             RT: CPAN's Bug Tracker
584              
585             The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
586              
587             L
588              
589             =item *
590              
591             CPANTS
592              
593             The CPANTS is a website that analyzes the Kwalitee ( code metrics ) of a distribution.
594              
595             L
596              
597             =item *
598              
599             CPAN Testers
600              
601             The CPAN Testers is a network of smoke testers who run automated tests on uploaded CPAN distributions.
602              
603             L
604              
605             =item *
606              
607             CPAN Testers Matrix
608              
609             The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
610              
611             L
612              
613             =item *
614              
615             CPAN Testers Dependencies
616              
617             The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
618              
619             L
620              
621             =back
622              
623             =head2 Bugs / Feature Requests
624              
625             Please report any bugs or feature requests by email to C, or through
626             the web interface at L. You will be automatically notified of any
627             progress on the request by the system.
628              
629             =head2 Source Code
630              
631             The code is open to the world, and available for you to hack on. Please feel free to browse it and play
632             with it, or whatever. If you want to contribute patches, please send me a diff or prod me to pull
633             from your repository :)
634              
635             L
636              
637             git clone http://github.com/shlomif/String-Random
638              
639             =head1 AUTHOR
640              
641             Shlomi Fish
642              
643             =head1 BUGS
644              
645             Please report any bugs or feature requests on the bugtracker website
646             L
647              
648             When submitting a bug or request, please include a test-file or a
649             patch to an existing test-file that illustrates the bug or desired
650             feature.
651              
652             =head1 COPYRIGHT AND LICENSE
653              
654             This software is copyright (c) 2021 by Shlomi Fish.
655              
656             This is free software; you can redistribute it and/or modify it under
657             the same terms as the Perl 5 programming language system itself.
658              
659             =cut
660              
661             __END__