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
|
|
71210
|
use warnings; |
|
2
|
|
|
|
|
20
|
|
|
2
|
|
|
|
|
60
|
|
6
|
2
|
|
|
2
|
|
9
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
54
|
|
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
1033
|
use Encode qw(decode); |
|
2
|
|
|
|
|
18790
|
|
|
2
|
|
|
|
|
135
|
|
9
|
2
|
|
|
2
|
|
13
|
use Exporter qw(import); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
49
|
|
10
|
2
|
|
|
2
|
|
1412
|
use Path::Tiny; |
|
2
|
|
|
|
|
25680
|
|
|
2
|
|
|
|
|
3514
|
|
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.49'; # VERSION |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
sub find_nearest_codeowners { |
30
|
1
|
|
50
|
1
|
1
|
3254
|
my $path = path(shift || '.')->absolute; |
31
|
|
|
|
|
|
|
|
32
|
1
|
|
|
|
|
66
|
while (!$path->is_rootdir) { |
33
|
4
|
|
|
|
|
191
|
my $filepath = find_codeowners_in_directory($path); |
34
|
4
|
100
|
|
|
|
79
|
return $filepath if $filepath; |
35
|
3
|
|
|
|
|
13
|
$path = $path->parent; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
} |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
sub find_codeowners_in_directory { |
41
|
10
|
50
|
|
10
|
1
|
3391
|
my $path = path(shift) or die; |
42
|
|
|
|
|
|
|
|
43
|
10
|
|
|
|
|
490
|
my @tries = ( |
44
|
|
|
|
|
|
|
[qw(CODEOWNERS)], |
45
|
|
|
|
|
|
|
[qw(docs CODEOWNERS)], |
46
|
|
|
|
|
|
|
[qw(.bitbucket CODEOWNERS)], |
47
|
|
|
|
|
|
|
[qw(.github CODEOWNERS)], |
48
|
|
|
|
|
|
|
[qw(.gitlab CODEOWNERS)], |
49
|
|
|
|
|
|
|
); |
50
|
|
|
|
|
|
|
|
51
|
10
|
|
|
|
|
62
|
for my $parts (@tries) { |
52
|
28
|
|
|
|
|
478
|
my $try = $path->child(@$parts); |
53
|
28
|
100
|
|
|
|
992
|
return $try if $try->is_file; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub run_command { |
58
|
40
|
|
|
40
|
0
|
226
|
my $filter; |
59
|
40
|
100
|
|
|
|
173
|
$filter = pop if ref($_[-1]) eq 'CODE'; |
60
|
|
|
|
|
|
|
|
61
|
40
|
50
|
|
|
|
114
|
print STDERR "# @_\n" if $ENV{GIT_CODEOWNERS_DEBUG}; |
62
|
|
|
|
|
|
|
|
63
|
40
|
|
|
|
|
104
|
my ($child_in, $child_out); |
64
|
40
|
|
|
|
|
1313
|
require IPC::Open2; |
65
|
40
|
|
|
|
|
7283
|
my $pid = IPC::Open2::open2($child_out, $child_in, @_); |
66
|
40
|
|
|
|
|
139049
|
close($child_in); |
67
|
|
|
|
|
|
|
|
68
|
2
|
|
|
2
|
|
40
|
binmode($child_out, ':encoding(UTF-8)'); |
|
2
|
|
|
|
|
10
|
|
|
2
|
|
|
|
|
82
|
|
|
40
|
|
|
|
|
1692
|
|
69
|
|
|
|
|
|
|
|
70
|
40
|
|
|
|
|
10626
|
my $proc = App::Codeowners::Util::Process->new( |
71
|
|
|
|
|
|
|
pid => $pid, |
72
|
|
|
|
|
|
|
fh => $child_out, |
73
|
|
|
|
|
|
|
filter => $filter, |
74
|
|
|
|
|
|
|
); |
75
|
|
|
|
|
|
|
|
76
|
40
|
100
|
|
|
|
667
|
return wantarray ? ($proc, @{$proc->all}) : $proc; |
|
13
|
|
|
|
|
102
|
|
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub run_git { |
80
|
40
|
|
|
40
|
0
|
115943
|
return run_command('git', @_); |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub git_ls_files { |
84
|
6
|
|
50
|
6
|
0
|
1447
|
my $dir = shift || '.'; |
85
|
6
|
|
|
|
|
43
|
return run_git('-C', $dir, 'ls-files', @_, \&_unescape_git_filepath); |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Depending on git's "core.quotepath" config, non-ASCII chars may be |
89
|
|
|
|
|
|
|
# escaped (identified by surrounding dquotes), so try to unescape. |
90
|
|
|
|
|
|
|
sub _unescape_git_filepath { |
91
|
14
|
50
|
|
14
|
|
154
|
return $_ if $_ !~ /^"(.+)"$/; |
92
|
0
|
|
|
|
|
0
|
return decode('UTF-8', unbackslash($1)); |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub git_toplevel { |
96
|
5
|
|
50
|
5
|
0
|
2884
|
my $dir = shift || '.'; |
97
|
|
|
|
|
|
|
|
98
|
5
|
|
|
|
|
27
|
my ($proc, $path) = run_git('-C', $dir, qw{rev-parse --show-toplevel}); |
99
|
|
|
|
|
|
|
|
100
|
5
|
50
|
33
|
|
|
62
|
return if $proc->wait != 0 || !$path; |
101
|
5
|
|
|
|
|
111
|
return path($path); |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
sub colorstrip { |
105
|
15
|
|
100
|
15
|
0
|
62
|
my $str = shift || ''; |
106
|
15
|
|
|
|
|
45
|
$str =~ s/\e\[[\d;]*m//g; |
107
|
15
|
|
|
|
|
76
|
return $str; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub stringify { |
111
|
0
|
|
|
0
|
0
|
0
|
my $item = shift; |
112
|
0
|
0
|
|
|
|
0
|
return ref($item) eq 'ARRAY' ? join(',', @$item) : $item; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# The zip code is from List::SomeUtils (thanks DROLSKY), copied just so as not |
116
|
|
|
|
|
|
|
# to bring in the extra dependency. |
117
|
|
|
|
|
|
|
sub zip (\@\@) { ## no critic (Subroutines::ProhibitSubroutinePrototypes) |
118
|
9
|
|
|
9
|
0
|
20
|
my $max = -1; |
119
|
9
|
|
66
|
|
|
48
|
$max < $#$_ && ( $max = $#$_ ) foreach @_; |
120
|
|
|
|
|
|
|
map { |
121
|
9
|
|
|
|
|
23
|
my $ix = $_; |
|
24
|
|
|
|
|
39
|
|
122
|
24
|
|
|
|
|
160
|
map $_->[$ix], @_; |
123
|
|
|
|
|
|
|
} 0 .. $max; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# The stringf code is from String::Format (thanks SREZIC), with changes: |
127
|
|
|
|
|
|
|
# - Use Unicode::GCString for better Unicode character padding, |
128
|
|
|
|
|
|
|
# - Strip ANSI color sequences, |
129
|
|
|
|
|
|
|
# - Prevent 'Negative repeat count does nothing' warnings |
130
|
|
|
|
|
|
|
sub _replace { |
131
|
15
|
|
|
15
|
|
89
|
my ($args, $orig, $alignment, $min_width, |
132
|
|
|
|
|
|
|
$max_width, $passme, $formchar) = @_; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# For unknown escapes, return the orignial |
135
|
15
|
50
|
|
|
|
47
|
return $orig unless defined $args->{$formchar}; |
136
|
|
|
|
|
|
|
|
137
|
15
|
50
|
|
|
|
41
|
$alignment = '+' unless defined $alignment; |
138
|
|
|
|
|
|
|
|
139
|
15
|
|
|
|
|
29
|
my $replacement = $args->{$formchar}; |
140
|
15
|
50
|
|
|
|
39
|
if (ref $replacement eq 'CODE') { |
141
|
|
|
|
|
|
|
# $passme gets passed to subrefs. |
142
|
15
|
|
50
|
|
|
75
|
$passme ||= ""; |
143
|
15
|
|
|
|
|
26
|
$passme =~ tr/{}//d; |
144
|
15
|
|
|
|
|
40
|
$replacement = $replacement->($passme); |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
15
|
|
|
|
|
32
|
my $replength; |
148
|
15
|
50
|
|
|
|
25
|
if (eval { require Unicode::GCString }) { |
|
15
|
|
|
|
|
960
|
|
149
|
15
|
|
|
|
|
16280
|
my $gcstring = Unicode::GCString->new(colorstrip($replacement)); |
150
|
15
|
|
|
|
|
285
|
$replength = $gcstring->columns; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
else { |
153
|
0
|
|
|
|
|
0
|
$replength = length colorstrip($replacement); |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
15
|
|
66
|
|
|
78
|
$min_width ||= $replength; |
157
|
15
|
|
66
|
|
|
49
|
$max_width ||= $replength; |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# length of replacement is between min and max |
160
|
15
|
50
|
33
|
|
|
37
|
if (($replength > $min_width) && ($replength < $max_width)) { |
161
|
0
|
|
|
|
|
0
|
return $replacement; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# length of replacement is longer than max; truncate |
165
|
15
|
50
|
|
|
|
26
|
if ($replength > $max_width) { |
166
|
0
|
|
|
|
|
0
|
return substr($replacement, 0, $max_width); |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
15
|
|
|
|
|
29
|
my $padding = $min_width - $replength; |
170
|
15
|
50
|
|
|
|
32
|
$padding = 0 if $padding < 0; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# length of replacement is less than min: pad |
173
|
15
|
50
|
|
|
|
26
|
if ($alignment eq '-') { |
174
|
|
|
|
|
|
|
# left align; pad in front |
175
|
0
|
|
|
|
|
0
|
return $replacement . ' ' x $padding; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
# right align, pad at end |
179
|
15
|
|
|
|
|
143
|
return ' ' x $padding . $replacement; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
my $regex = qr/ |
182
|
|
|
|
|
|
|
(% # leading '%' |
183
|
|
|
|
|
|
|
(-)? # left-align, rather than right |
184
|
|
|
|
|
|
|
(\d*)? # (optional) minimum field width |
185
|
|
|
|
|
|
|
(?:\.(\d*))? # (optional) maximum field width |
186
|
|
|
|
|
|
|
(\{.*?\})? # (optional) stuff inside |
187
|
|
|
|
|
|
|
(\S) # actual format character |
188
|
|
|
|
|
|
|
)/x; |
189
|
|
|
|
|
|
|
sub stringf { |
190
|
6
|
|
50
|
6
|
0
|
26
|
my $format = shift || return; |
191
|
6
|
50
|
|
|
|
51
|
my $args = UNIVERSAL::isa($_[0], 'HASH') ? shift : { @_ }; |
192
|
6
|
50
|
|
|
|
44
|
$args->{'n'} = "\n" unless exists $args->{'n'}; |
193
|
6
|
50
|
|
|
|
26
|
$args->{'t'} = "\t" unless exists $args->{'t'}; |
194
|
6
|
50
|
|
|
|
29
|
$args->{'%'} = "%" unless exists $args->{'%'}; |
195
|
|
|
|
|
|
|
|
196
|
6
|
|
|
|
|
93
|
$format =~ s/$regex/_replace($args, $1, $2, $3, $4, $5, $6)/ge; |
|
15
|
|
|
|
|
61
|
|
197
|
|
|
|
|
|
|
|
198
|
6
|
|
|
|
|
30
|
return $format; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# The unbacklash code is from String::Escape (thanks EVO), with changes: |
202
|
|
|
|
|
|
|
# - Handle \a, \b, \f and \v (thanks Berk Akinci) |
203
|
|
|
|
|
|
|
my %unbackslash; |
204
|
|
|
|
|
|
|
sub unbackslash { |
205
|
0
|
|
|
0
|
0
|
0
|
my $str = shift; |
206
|
|
|
|
|
|
|
# Earlier definitions are preferred to later ones, thus we output \n not \x0d |
207
|
|
|
|
|
|
|
%unbackslash = ( |
208
|
0
|
|
|
|
|
0
|
( map { $_ => $_ } ( '\\', '"', '$', '@' ) ), |
209
|
|
|
|
|
|
|
( 'r' => "\r", 'n' => "\n", 't' => "\t" ), |
210
|
0
|
|
|
|
|
0
|
( map { 'x' . unpack('H2', chr($_)) => chr($_) } (0..255) ), |
211
|
0
|
0
|
|
|
|
0
|
( map { sprintf('%03o', $_) => chr($_) } (0..255) ), |
|
0
|
|
|
|
|
0
|
|
212
|
|
|
|
|
|
|
( 'a' => "\x07", 'b' => "\x08", 'f' => "\x0c", 'v' => "\x0b" ), |
213
|
|
|
|
|
|
|
) if !%unbackslash; |
214
|
0
|
|
|
|
|
0
|
$str =~ s/ (\A|\G|[^\\]) \\ ( [0-7]{3} | x[\da-fA-F]{2} | . ) / $1 . $unbackslash{lc($2)} /gsxe; |
|
0
|
|
|
|
|
0
|
|
215
|
0
|
|
|
|
|
0
|
return $str; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
{ |
219
|
|
|
|
|
|
|
package App::Codeowners::Util::Process; |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub new { |
222
|
40
|
|
|
40
|
|
221
|
my $class = shift; |
223
|
40
|
|
|
|
|
570
|
return bless {@_}, $class; |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
sub next { |
227
|
12
|
|
|
12
|
|
53
|
my $self = shift; |
228
|
12
|
|
|
|
|
1821
|
my $line = readline($self->{fh}); |
229
|
12
|
100
|
|
|
|
100
|
if (defined $line) { |
230
|
9
|
|
|
|
|
20
|
chomp $line; |
231
|
9
|
50
|
|
|
|
34
|
if (my $filter = $self->{filter}) { |
232
|
9
|
|
|
|
|
28
|
local $_ = $line; |
233
|
9
|
|
|
|
|
48
|
$line = $filter->($line); |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
} |
236
|
12
|
|
|
|
|
91
|
$line; |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub all { |
240
|
13
|
|
|
13
|
|
32
|
my $self = shift; |
241
|
13
|
|
|
|
|
9115
|
chomp(my @lines = readline($self->{fh})); |
242
|
13
|
100
|
|
|
|
908
|
if (my $filter = $self->{filter}) { |
243
|
3
|
|
|
|
|
49
|
$_ = $filter->($_) for @lines; |
244
|
|
|
|
|
|
|
} |
245
|
13
|
|
|
|
|
400
|
\@lines; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub wait { |
249
|
76
|
|
|
76
|
|
162
|
my $self = shift; |
250
|
76
|
100
|
|
|
|
992
|
my $pid = $self->{pid} or return; |
251
|
40
|
50
|
|
|
|
125
|
if (my $fh = $self->{fh}) { |
252
|
40
|
|
|
|
|
587
|
close($fh); |
253
|
40
|
|
|
|
|
121
|
delete $self->{fh}; |
254
|
|
|
|
|
|
|
} |
255
|
40
|
|
|
|
|
106534
|
waitpid($pid, 0); |
256
|
40
|
|
|
|
|
553
|
my $status = $?; |
257
|
40
|
50
|
|
|
|
191
|
print STDERR "# -> status $status\n" if $ENV{GIT_CODEOWNERS_DEBUG}; |
258
|
40
|
|
|
|
|
96
|
delete $self->{pid}; |
259
|
40
|
|
|
|
|
611
|
return $status; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
sub DESTROY { |
263
|
40
|
|
|
40
|
|
638
|
my ($self, $global_destruction) = @_; |
264
|
40
|
50
|
|
|
|
126
|
return if $global_destruction; |
265
|
40
|
|
|
|
|
128
|
$self->wait; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
1; |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
__END__ |