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   57300 use warnings;
  2         21  
  2         54  
6 2     2   9 use strict;
  2         3  
  2         48  
7              
8 2     2   9 use Exporter qw(import);
  2         4  
  2         62  
9 2     2   777 use File::Codeowners::Util;
  2         47520  
  2         96  
10 2     2   17 use Path::Tiny;
  2         4  
  2         1886  
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.51'; # VERSION
27              
28              
29 1     1 1 8274 sub find_nearest_codeowners { goto &File::Codeowners::Util::find_nearest_codeowners }
30              
31              
32 6     6 1 8584 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 320374 sub run_git { goto &File::Codeowners::Util::run_git }
39              
40              
41 6     6 1 13581 sub git_ls_files { goto &File::Codeowners::Util::git_ls_files }
42              
43              
44 5     5 1 12039 sub git_toplevel { goto &File::Codeowners::Util::git_toplevel }
45              
46              
47             sub colorstrip {
48 15   100 15 1 41 my $str = shift || '';
49 15         36 $str =~ s/\e\[[\d;]*m//g;
50 15         61 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   76 my ($args, $orig, $alignment, $min_width,
66             $max_width, $passme, $formchar) = @_;
67              
68             # For unknown escapes, return the orignial
69 15 50       46 return $orig unless defined $args->{$formchar};
70              
71 15 50       31 $alignment = '+' unless defined $alignment;
72              
73 15         21 my $replacement = $args->{$formchar};
74 15 50       31 if (ref $replacement eq 'CODE') {
75             # $passme gets passed to subrefs.
76 15   50     57 $passme ||= "";
77 15         22 $passme =~ tr/{}//d;
78 15         32 $replacement = $replacement->($passme);
79             }
80              
81 15         18 my $replength;
82 15 50       21 if (eval { require Unicode::GCString }) {
  15         680  
83 15         14071 my $gcstring = Unicode::GCString->new(colorstrip($replacement));
84 15         266 $replength = $gcstring->columns;
85             }
86             else {
87 0         0 $replength = length colorstrip($replacement);
88             }
89              
90 15   66     63 $min_width ||= $replength;
91 15   66     48 $max_width ||= $replength;
92              
93             # length of replacement is between min and max
94 15 50 33     31 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       21 if ($replength > $max_width) {
100 0         0 return substr($replacement, 0, $max_width);
101             }
102              
103 15         16 my $padding = $min_width - $replength;
104 15 50       27 $padding = 0 if $padding < 0;
105              
106             # length of replacement is less than min: pad
107 15 50       21 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         89 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 32 my $format = shift || return;
125 6 50       83 my $args = UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ };
126 6 50       32 $args->{'n'} = "\n" unless exists $args->{'n'};
127 6 50       37 $args->{'t'} = "\t" unless exists $args->{'t'};
128 6 50       37 $args->{'%'} = "%" unless exists $args->{'%'};
129              
130 6         68 $format =~ s/$regex/_replace($args, $1, $2, $3, $4, $5, $6)/ge;
  15         45  
131              
132 6         24 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 19 my $max = -1;
158 9   66     43 $max < $#$_ && ( $max = $#$_ ) foreach @_;
159             map {
160 9         22 my $ix = $_;
  24         33  
161 24         105 map $_->[$ix], @_;
162             } 0 .. $max;
163             }
164              
165             1;
166              
167             __END__