]>
Dogcows Code - chaz/git-codeowners/blob - lib/File/Codeowners.pm
1 package File
::Codeowners
;
2 # ABSTRACT: Read and write CODEOWNERS files
4 use v5
.10
.1; # defined-or
10 use Scalar
::Util
qw(openhandle);
11 use Text
::Gitignore
qw(build_gitignore_matcher);
13 our $VERSION = '0.46'; # VERSION
15 sub _croak
{ require Carp
; Carp
::croak
(@_); }
16 sub _usage
{ _croak
("Usage: @_\n") }
21 my $self = bless {}, $class;
27 my $input = shift or _usage
(q{$codeowners->parse($input)});
29 return $self->parse_from_array($input, @_) if @_;
30 return $self->parse_from_array($input) if ref($input) eq 'ARRAY';
31 return $self->parse_from_string($input) if ref($input) eq 'SCALAR';
32 return $self->parse_from_fh($input) if openhandle
($input);
33 return $self->parse_from_filepath($input);
37 sub parse_from_filepath
{
39 my $path = shift or _usage
(q{$codeowners->parse_from_filepath($filepath)});
41 $self = bless({}, $self) if !ref($self);
43 return $self->parse_from_fh(path
($path)->openr_utf8);
49 my $fh = shift or _usage
(q{$codeowners->parse_from_fh($fh)});
51 $self = bless({}, $self) if !ref($self);
59 while (my $line = <$fh>) {
62 if ($line eq '### UNOWNED (File::Codeowners)') {
66 elsif ($line =~ /^\h*#(.*)/) {
68 if ($comment =~ /^\h*Project:\h*(.+?)\h*$/i) {
69 $current_project = $1 || undef;
75 elsif ($line =~ /^\h*$/) {
78 elsif ($line =~ /^\h*(.+?)(?<!\\)\h+(.+)/) {
80 my @owners = $2 =~ /( (?:\@+"[^"]*") | (?:\H+) )/gx;
84 $current_project ? (project
=> $current_project) : (),
88 die "Parse error on line $.: $line\n";
93 while (my $line = <$fh>) {
95 if ($line =~ /# (.+)/) {
97 $unowned{$filepath}++;
102 $self->{lines
} = \
@lines;
103 $self->{unowned
} = \
%unowned;
109 sub parse_from_array
{
111 my $arr = shift or _usage
(q{$codeowners->parse_from_array(\@lines)});
113 $self = bless({}, $self) if !ref($self);
115 $arr = [$arr, @_] if @_;
116 my $str = join("\n", @$arr);
117 return $self->parse_from_string(\
$str);
121 sub parse_from_string
{
123 my $str = shift or _usage
(q{$codeowners->parse_from_string(\$string)});
125 $self = bless({}, $self) if !ref($self);
127 my $ref = ref($str) eq 'SCALAR' ? $str : \
$str;
128 open(my $fh, '<:encoding(UTF-8)', $ref) or die "open failed: $!";
130 return $self->parse_from_fh($fh);
134 sub write_to_filepath
{
136 my $path = shift or _usage
(q{$codeowners->write_to_filepath($filepath)});
138 path
($path)->spew_utf8([map { "$_\n" } @{$self->write_to_array('')}]);
144 my $fh = shift or _usage
(q{$codeowners->write_to_fh($fh)});
146 for my $line (@{$self->write_to_array}) {
152 sub write_to_string
{
155 my $str = join("\n", @{$self->write_to_array}) . "\n";
162 my $charset = shift // 'UTF-8';
166 for my $line (@{$self->_lines}) {
167 if (my $comment = $line->{comment
}) {
168 push @format, "#$comment";
170 elsif (my $pattern = $line->{pattern
}) {
171 my $owners = join(' ', @{$line->{owners
}});
172 push @format, "$pattern $owners";
179 my @unowned = sort keys %{$self->_unowned};
181 push @format, '' if $format[-1];
182 push @format, '### UNOWNED (File::Codeowners)';
183 for my $unowned (@unowned) {
184 push @format, "# $unowned";
189 $_ = encode
($charset, $_) for @format;
197 my $filepath = shift or _usage
(q{$codeowners->match($filepath)});
199 my $lines = $self->{match_lines
} ||= [reverse grep { ($_ || {})->{pattern
} } @{$self->_lines}];
201 for my $line (@$lines) {
202 my $matcher = $line->{matcher
} ||= build_gitignore_matcher
([$line->{pattern
}]);
204 pattern
=> $line->{pattern
},
205 owners
=> [@{$line->{owners
} || []}],
206 $line->{project
} ? (project
=> $line->{project
}) : (),
207 } if $matcher->($filepath);
210 return undef; ## no critic (Subroutines::ProhibitExplicitReturn)
218 return $self->{owners
} if !$pattern && $self->{owners
};
221 for my $line (@{$self->_lines}) {
222 next if $pattern && $line->{pattern
} && $pattern ne $line->{pattern
};
223 $owners{$_}++ for (@{$line->{owners
} || []});
226 my $owners = [sort keys %owners];
227 $self->{owners
} = $owners if !$pattern;
237 return $self->{patterns
} if !$owner && $self->{patterns
};
240 for my $line (@{$self->_lines}) {
241 next if $owner && !grep { $_ eq $owner } @{$line->{owners
} || []};
242 my $pattern = $line->{pattern
};
243 $patterns{$pattern}++ if $pattern;
246 my $patterns = [sort keys %patterns];
247 $self->{patterns
} = $patterns if !$owner;
256 return $self->{projects
} if $self->{projects
};
259 for my $line (@{$self->_lines}) {
260 my $project = $line->{project
};
261 $projects{$project}++ if $project;
264 my $projects = [sort keys %projects];
265 $self->{projects
} = $projects;
275 $pattern && $owners or _usage
(q{$codeowners->update_owners($pattern => \@owners)});
277 $owners = [$owners] if ref($owners) ne 'ARRAY';
281 for my $line (@{$self->_lines}) {
282 next if !$line->{pattern
};
283 next if $pattern ne $line->{pattern
};
284 $line->{owners
} = [@$owners];
292 push @{$self->_lines}, (@_ ? {@_} : undef);
299 unshift @{$self->_lines}, (@_ ? {@_} : undef);
305 [sort keys %{$self->{unowned
} || {}}];
311 $self->_unowned->{$_}++ for @_;
317 delete $self->_unowned->{$_} for @_;
322 my $filepath = shift;
323 $self->_unowned->{$filepath};
329 $self->{unowned
} = {};
332 sub _lines
{ shift-
>{lines
} ||= [] }
333 sub _unowned
{ shift-
>{unowned
} ||= {} }
337 delete $self->{match_lines
};
338 delete $self->{owners
};
339 delete $self->{patterns
};
340 delete $self->{projects
};
353 File::Codeowners - Read and write CODEOWNERS files
363 $codeowners = File::Codeowners->new;
365 Construct a new L<File::Codeowners>.
369 $codeowners = File::Codeowners->parse('path/to/CODEOWNERS');
370 $codeowners = File::Codeowners->parse($filehandle);
371 $codeowners = File::Codeowners->parse(\@lines);
372 $codeowners = File::Codeowners->parse(\$string);
374 Parse a F<CODEOWNERS> file.
376 This is a shortcut for the C<parse_from_*> methods.
378 =head2 parse_from_filepath
380 $codeowners = File::Codeowners->parse_from_filepath('path/to/CODEOWNERS');
382 Parse a F<CODEOWNERS> file from the filesystem.
386 $codeowners = File::Codeowners->parse_from_fh($filehandle);
388 Parse a F<CODEOWNERS> file from an open filehandle.
390 =head2 parse_from_array
392 $codeowners = File::Codeowners->parse_from_array(\@lines);
394 Parse a F<CODEOWNERS> file stored as lines in an array.
396 =head2 parse_from_string
398 $codeowners = File::Codeowners->parse_from_string(\$string);
399 $codeowners = File::Codeowners->parse_from_string($string);
401 Parse a F<CODEOWNERS> file stored as a string. String should be UTF-8 encoded.
403 =head2 write_to_filepath
405 $codeowners->write_to_filepath($filepath);
407 Write the contents of the file to the filesystem atomically.
411 $codeowners->write_to_fh($fh);
413 Format the file contents and write to a filehandle.
415 =head2 write_to_string
417 $scalarref = $codeowners->write_to_string;
419 Format the file contents and return a reference to a formatted string.
421 =head2 write_to_array
423 $lines = $codeowners->write_to_array;
425 Format the file contents as an arrayref of lines.
429 $owners = $codeowners->match($filepath);
431 Match the given filepath against the available patterns and return just the
432 owners for the matching pattern. Patterns are checked in the reverse order
433 they were defined in the file.
435 Returns C<undef> if no patterns match.
439 $owners = $codeowners->owners; # get all defined owners
440 $owners = $codeowners->owners($pattern);
442 Get an arrayref of owners defined in the file. If a pattern argument is given,
443 only owners for the given pattern are returned (or empty arrayref if the
444 pattern does not exist). If no argument is given, simply returns all owners
449 $patterns = $codeowners->patterns;
450 $patterns = $codeowners->patterns($owner);
452 Get an arrayref of all patterns defined.
456 $projects = $codeowners->projects;
458 Get an arrayref of all projects defined.
462 $codeowners->update_owners($pattern => \@new_owners);
464 Set a new set of owners for a given pattern. If for some reason the file has
465 multiple such patterns, they will all be updated.
467 Nothing happens if the file does not already have at least one such pattern.
471 $codeowners->append(comment => $str);
472 $codeowners->append(pattern => $pattern, owners => \@owners);
473 $codeowners->append(); # blank line
479 $codeowners->prepend(comment => $str);
480 $codeowners->prepend(pattern => $pattern, owners => \@owners);
481 $codeowners->prepend(); # blank line
487 $filepaths = $codeowners->unowned;
489 Get the list of filepaths in the "unowned" section.
491 This parser supports an "extension" to the F<CODEOWNERS> file format which
492 lists unowned files at the end of the file. This list can be useful to have in
493 order to figure out what files we know are unowned versus what files we don't
498 $codeowners->add_unowned($filepath, ...);
500 Add one or more filepaths to the "unowned" list.
502 This method does not check to make sure the filepath(s) actually do not match
503 any patterns in the file, so you might want to call L</match> first.
505 See L</unowned> for an explanation.
507 =head2 remove_unowned
509 $codeowners->remove_unowned($filepath, ...);
511 Remove one or more filepaths from the "unowned" list.
513 Silently ignores filepaths that are already not listed.
515 See L</unowned> for an explanation.
519 $codeowners->clear_unowned;
521 Remove all filepaths from the "unowned" list.
523 See L</unowned> for an explanation.
527 Please report any bugs or feature requests on the bugtracker website
528 L<https://github.com/chazmcgarvey/git-codeowners/issues>
530 When submitting a bug or request, please include a test-file or a
531 patch to an existing test-file that illustrates the bug or desired
536 Charles McGarvey <chazmcgarvey@brokenzipper.com>
538 =head1 COPYRIGHT AND LICENSE
540 This software is copyright (c) 2019 by Charles McGarvey.
542 This is free software; you can redistribute it and/or modify it under
543 the same terms as the Perl 5 programming language system itself.
This page took 0.066315 seconds and 4 git commands to generate.