File Coverage

blib/lib/String/Tools.pm
Criterion Covered Total %
statement 80 89 89.8
branch 41 62 66.1
condition 21 33 63.6
subroutine 13 13 100.0
pod 10 10 100.0
total 165 207 79.7


line stmt bran cond sub pod time code
1 1     1   595 use v5.12;
  1         4  
2              
3 1     1   5 use warnings;
  1         2  
  1         65  
4              
5             package String::Tools;
6             # ABSTRACT: Various tools for manipulating strings.
7             our $VERSION = '0.18.279'; # VERSION
8              
9              
10 1     1   6 use Exporter 'import';
  1         2  
  1         1324  
11              
12             our @EXPORT = qw();
13             our @EXPORT_OK = qw(
14             define
15             is_blank
16             shrink
17             stitch
18             stitcher
19             stringify
20             subst
21             subst_vars
22             trim
23             trim_lines
24             );
25              
26             ### Variables ###
27              
28              
29             our $BLANK = '[[:cntrl:][:space:]]';
30              
31              
32             our $SUBST_VAR = qr/[[:alpha:]_]+\w*(?:[[:punct:]]\w+)*/;
33              
34              
35             our $THREAD = ' ';
36              
37             ### Functions ###
38              
39             sub stringify(_); # Forward declaration
40              
41              
42 6   66 6 1 42 sub define(_) { return $_[0] // !!undef }
43              
44              
45             sub is_blank(_) {
46 43     43 1 72 local $_ = &stringify;
47 43   100     303 return not( length() && !/\A$BLANK+\z/ );
48             }
49              
50              
51             sub shrink(_) {
52 3     3 1 6 local $_ = trim(&stringify);
53 3         33 s/$BLANK+/$THREAD/g;
54 3         15 return $_;
55             }
56              
57              
58             sub stitch {
59 7     7 1 11 my $str = '';
60 7         13 my $was_blank = 1;
61              
62 7         9 local $_;
63 7         17 foreach my $s (map stringify, @_) {
64 30         60 my $is_blank = is_blank($s);
65 30 100 100     84 $str .= $THREAD unless ( $was_blank || $is_blank );
66 30         41 $str .= $s;
67 30         46 $was_blank = $is_blank;
68             }
69              
70 7         29 return $str;
71             }
72              
73              
74             sub stitcher {
75 3   33 3 1 13 local $THREAD = shift // $THREAD;
76 3         7 return &stitch;
77             }
78              
79              
80             sub stringify(_) {
81 120     120 1 305 local ($_) = @_;
82              
83 120 100 33     315 if ( not defined() ) {
    100          
    100          
    100          
    50          
    50          
84 2         5 return define();
85             } elsif ( not( my $ref = ref() ) ) {
86 112         255 return $_;
87             } elsif ( $ref eq 'ARRAY' ) {
88 2         14 return "@$_";
89             #return join( $" // ' ', map stringify, @$_ ); # What about loops?
90             } elsif ( $ref eq 'HASH' ) {
91 2         3 return "@{[%$_]}";
  2         15  
92             #return join( $" // ' ', map stringify, %$_ ); # What about loops?
93             } elsif ( $ref eq 'REF' && ref($$_) ne 'REF' ) {
94 0         0 return stringify($$_);
95             } elsif ( $ref eq 'SCALAR' ) {
96 0         0 return stringify($$_);
97             }
98 2         48 return "$_";
99             }
100              
101              
102             sub subst {
103 5     5 1 557 my $str = stringify( shift );
104 5 100 100     27 @_ = ( $_ ) if defined($_) && ! @_;
105              
106 5         6 my %subst;
107 5 100       12 if ( 1 == @_ ) {
108 3 100       13 if ( not( my $ref = ref $_[0] ) ) { %subst = ( _ => +shift ) }
  1 100       3  
    50          
109 1         3 elsif ( $ref eq 'ARRAY' ) { %subst = @{ +shift } }
  1         3  
110 1         2 elsif ( $ref eq 'HASH' ) { %subst = %{ +shift } }
  1         4  
111 0         0 else { %subst = ( _ => +shift ) }
112 2         7 } else { %subst = @_ }
113              
114 5 50       13 if (%subst) {
115 5         7 local $_;
116             my $names = '(?:'
117             . join( '|',
118             map quotemeta,
119 3 0       23 sort { length($b) <=> length($a) || $a cmp $b }
120 5 50       15 grep { length() && /\A$SUBST_VAR\z/ }
  8         161  
121             keys %subst
122             )
123             . ')\b';
124 5   66     103 $str =~ s[\$(?:\{\s*($names)\s*\}|($names))]
  8         42  
125             [ stringify( $subst{ $1 // $2 } ) ]eg;
126             }
127 5         29  
128             return $str;
129             }
130              
131              
132 2     2 1 6 sub subst_vars(_) {
133             local ($_) = &stringify;
134 2         127  
135 2         6 my @vars = /\$(\{\s*$SUBST_VAR\s*\}|$SUBST_VAR\b)/g;
136 9         35 my %seen = ();
137 2         6 return grep { !$seen{$_}++ }
  9         31  
138             map { trim( $_, qr/\{\s*/, qr/\s*\}/ ) } @vars;
139             }
140              
141              
142 19 50   19 1 56 sub trim {
143             local $_ = stringify( @_ ? shift : $_ );
144 19         33  
145 19         24 my ( $lead, $rear );
146 19 100       41 my $count = scalar @_;
    50          
147 0         0 if ($count == 0) {}
148             elsif ($count == 1) { $lead = shift; }
149             else {
150             # Could be:
151             # 1. l => $value
152             # 2. r => $value
153             # 3. l => $value, r => $value
154             # or r => $value, l => $value
155 15         32 # 4. $lead, $rear
156 15 100       71 my %lr = @_;
157 15 100       31 $lead = delete $lr{l} if exists $lr{l};
158             $rear = delete $lr{r} if exists $lr{r};
159             # At this point, there should be nothing in %lr,
160 15 100       39 # so if there is, then this must be case 4.
161             ( $lead, $rear ) = @_ if %lr;
162             }
163 19   66     50  
164 19 100       160 $lead //= $BLANK . '+';
165             s/\A$lead// if ( length $lead );
166 19   66     58  
167 19 100       135 $rear //= $lead;
168             s/$rear\z// if ( length $rear );
169 19         77  
170             return $_;
171             }
172              
173              
174 2 50   2 1 11 sub trim_lines {
175             local $_ = stringify( @_ ? shift : $_ );
176 2         4  
177 2         5 my ( $lead, $rear );
178 2 50       5 my $count = scalar @_;
    0          
179 0         0 if ($count == 0) {}
180             elsif ($count == 1) { $lead = shift; }
181             else {
182             # Could be:
183             # 1. l => $value
184             # 2. r => $value
185             # 3. l => $value, r => $value
186             # or r => $value, l => $value
187 0         0 # 4. $lead, $rear
188 0 0       0 my %lr = @_;
189 0 0       0 $lead = delete $lr{l} if exists $lr{l};
190             $rear = delete $lr{r} if exists $lr{r};
191             # At this point, there should be nothing in %lr,
192 0 0       0 # so if there is, then this must be case 4.
193             ( $lead, $rear ) = @_ if %lr;
194             }
195 2   33     13  
196 2 50       40 $lead //= $BLANK . '+';
197             s/^$lead//gm if ( length $lead );
198 2   33     11  
199 2 50       29 $rear //= $lead;
200             s/$rear$//gm if ( length $rear );
201 2         10  
202             return $_;
203             }
204              
205             1;
206              
207             __END__