File Coverage

blib/lib/App/Codeowners/Util.pm
Criterion Covered Total %
statement 57 72 79.1
branch 12 28 42.8
condition 11 18 61.1
subroutine 14 17 82.3
pod 11 11 100.0
total 105 146 71.9


line stmt bran cond sub pod time code
1             package App::Codeowners::Util;
2             # ABSTRACT: Grab bag of utility subs for Codeowners modules
3              
4              
5 2     2   79963 use warnings;
  2         21  
  2         79  
6 2     2   11 use strict;
  2         5  
  2         57  
7              
8 2     2   32 use Exporter qw(import);
  2         4  
  2         97  
9 2     2   1051 use File::Codeowners::Util;
  2         60507  
  2         114  
10 2     2   25 use Path::Tiny;
  2         5  
  2         2388  
11              
12             our @EXPORT_OK = qw(
13             colorstrip
14             find_codeowners_in_directory
15             find_nearest_codeowners
16             git_ls_files
17             git_toplevel
18             run_command
19             run_git
20             stringf
21             stringify
22             unbackslash
23             zip
24             );
25              
26             our $VERSION = '0.50'; # VERSION
27              
28              
29 1     1 1 11382 sub find_nearest_codeowners { goto &File::Codeowners::Util::find_nearest_codeowners }
30              
31              
32 6     6 1 11558 sub find_codeowners_in_directory { goto &File::Codeowners::Util::find_codeowners_in_directory }
33              
34              
35 0     0 1 0 sub run_command { goto &File::Codeowners::Util::run_command }
36              
37              
38 29     29 1 297877 sub run_git { goto &File::Codeowners::Util::run_git }
39              
40              
41 6     6 1 18054 sub git_ls_files { goto &File::Codeowners::Util::git_ls_files }
42              
43              
44 5     5 1 15847 sub git_toplevel { goto &File::Codeowners::Util::git_toplevel }
45              
46              
47             sub colorstrip {
48 15   100 15 1 76 my $str = shift || '';
49 15         46 $str =~ s/\e\[[\d;]*m//g;
50 15         85 return $str;
51             }
52              
53              
54             sub stringify {
55 0     0 1 0 my $item = shift;
56 0 0       0 return ref($item) eq 'ARRAY' ? join(',', @$item) : $item;
57             }
58              
59              
60             # The stringf code is from String::Format (thanks SREZIC), with changes:
61             # - Use Unicode::GCString for better Unicode character padding,
62             # - Strip ANSI color sequences,
63             # - Prevent 'Negative repeat count does nothing' warnings
64             sub _replace {
65 15     15   95 my ($args, $orig, $alignment, $min_width,
66             $max_width, $passme, $formchar) = @_;
67              
68             # For unknown escapes, return the orignial
69 15 50       52 return $orig unless defined $args->{$formchar};
70              
71 15 50       43 $alignment = '+' unless defined $alignment;
72              
73 15         35 my $replacement = $args->{$formchar};
74 15 50       43 if (ref $replacement eq 'CODE') {
75             # $passme gets passed to subrefs.
76 15   50     70 $passme ||= "";
77 15         26 $passme =~ tr/{}//d;
78 15         45 $replacement = $replacement->($passme);
79             }
80              
81 15         24 my $replength;
82 15 50       35 if (eval { require Unicode::GCString }) {
  15         910  
83 15         16421 my $gcstring = Unicode::GCString->new(colorstrip($replacement));
84 15         353 $replength = $gcstring->columns;
85             }
86             else {
87 0         0 $replength = length colorstrip($replacement);
88             }
89              
90 15   66     78 $min_width ||= $replength;
91 15   66     54 $max_width ||= $replength;
92              
93             # length of replacement is between min and max
94 15 50 33     36 if (($replength > $min_width) && ($replength < $max_width)) {
95 0         0 return $replacement;
96             }
97              
98             # length of replacement is longer than max; truncate
99 15 50       31 if ($replength > $max_width) {
100 0         0 return substr($replacement, 0, $max_width);
101             }
102              
103 15         28 my $padding = $min_width - $replength;
104 15 50       30 $padding = 0 if $padding < 0;
105              
106             # length of replacement is less than min: pad
107 15 50       28 if ($alignment eq '-') {
108             # left align; pad in front
109 0         0 return $replacement . ' ' x $padding;
110             }
111              
112             # right align, pad at end
113 15         116 return ' ' x $padding . $replacement;
114             }
115             my $regex = qr/
116             (% # leading '%'
117             (-)? # left-align, rather than right
118             (\d*)? # (optional) minimum field width
119             (?:\.(\d*))? # (optional) maximum field width
120             (\{.*?\})? # (optional) stuff inside
121             (\S) # actual format character
122             )/x;
123             sub stringf {
124 6   50 6 1 26 my $format = shift || return;
125 6 50       61 my $args = UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
126 6 50       41 $args->{'n'} = "\n" unless exists $args->{'n'};
127 6 50       42 $args->{'t'} = "\t" unless exists $args->{'t'};
128 6 50       24 $args->{'%'} = "%" unless exists $args->{'%'};
129              
130 6         93 $format =~ s/$regex/_replace($args, $1, $2, $3, $4, $5, $6)/ge;
  15         56  
131              
132 6         31 return $format;
133             }
134              
135              
136             # The unbacklash code is from String::Escape (thanks EVO), with changes:
137             # - Handle \a, \b, \f and \v (thanks Berk Akinci)
138             my %unbackslash;
139             sub unbackslash {
140 0     0 1 0 my $str = shift;
141             # Earlier definitions are preferred to later ones, thus we output \n not \x0d
142             %unbackslash = (
143 0         0 ( map { $_ => $_ } ( '\\', '"', '$', '@' ) ),
144             ( 'r' => "\r", 'n' => "\n", 't' => "\t" ),
145 0         0 ( map { 'x' . unpack('H2', chr($_)) => chr($_) } (0..255) ),
146 0 0       0 ( map { sprintf('%03o', $_) => chr($_) } (0..255) ),
  0         0  
147             ( 'a' => "\x07", 'b' => "\x08", 'f' => "\x0c", 'v' => "\x0b" ),
148             ) if !%unbackslash;
149 0         0 $str =~ s/ (\A|\G|[^\\]) \\ ( [0-7]{3} | x[\da-fA-F]{2} | . ) / $1 . $unbackslash{lc($2)} /gsxe;
  0         0  
150 0         0 return $str;
151             }
152              
153              
154             # The zip code is from List::SomeUtils (thanks DROLSKY), copied just so as not
155             # to bring in the extra dependency.
156             sub zip (\@\@) { ## no critic (Subroutines::ProhibitSubroutinePrototypes)
157 9     9 1 18 my $max = -1;
158 9   66     44 $max < $#$_ && ( $max = $#$_ ) foreach @_;
159             map {
160 9         26 my $ix = $_;
  24         39  
161 24         153 map $_->[$ix], @_;
162             } 0 .. $max;
163             }
164              
165             1;
166              
167             __END__