]>
Dogcows Code - chaz/git-codeowners/blob - lib/File/Codeowners.pm
f987561554bfdbe464169a490c2964ec1aa759ec
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.41'; # 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;
257 $pattern && $owners or _usage
(q{$codeowners->update_owners($pattern => \@owners)});
259 $owners = [$owners] if ref($owners) ne 'ARRAY';
263 for my $line (@{$self->_lines}) {
264 next if !$line->{pattern
};
265 next if $pattern ne $line->{pattern
};
266 $line->{owners
} = [@$owners];
274 push @{$self->_lines}, (@_ ? {@_} : undef);
281 unshift @{$self->_lines}, (@_ ? {@_} : undef);
287 [sort keys %{$self->{unowned
} || {}}];
293 $self->_unowned->{$_}++ for @_;
299 delete $self->_unowned->{$_} for @_;
304 my $filepath = shift;
305 $self->_unowned->{$filepath};
311 $self->{unowned
} = {};
314 sub _lines
{ shift-
>{lines
} ||= [] }
315 sub _unowned
{ shift-
>{unowned
} ||= {} }
319 delete $self->{match_lines
};
320 delete $self->{owners
};
321 delete $self->{patterns
};
334 File::Codeowners - Read and write CODEOWNERS files
344 $codeowners = File::Codeowners->new;
346 Construct a new L<File::Codeowners>.
350 $codeowners = File::Codeowners->parse('path/to/CODEOWNERS');
351 $codeowners = File::Codeowners->parse($filehandle);
352 $codeowners = File::Codeowners->parse(\@lines);
353 $codeowners = File::Codeowners->parse(\$string);
355 Parse a F<CODEOWNERS> file.
357 This is a shortcut for the C<parse_from_*> methods.
359 =head2 parse_from_filepath
361 $codeowners = File::Codeowners->parse_from_filepath('path/to/CODEOWNERS');
363 Parse a F<CODEOWNERS> file from the filesystem.
367 $codeowners = File::Codeowners->parse_from_fh($filehandle);
369 Parse a F<CODEOWNERS> file from an open filehandle.
371 =head2 parse_from_array
373 $codeowners = File::Codeowners->parse_from_array(\@lines);
375 Parse a F<CODEOWNERS> file stored as lines in an array.
377 =head2 parse_from_string
379 $codeowners = File::Codeowners->parse_from_string(\$string);
380 $codeowners = File::Codeowners->parse_from_string($string);
382 Parse a F<CODEOWNERS> file stored as a string. String should be UTF-8 encoded.
384 =head2 write_to_filepath
386 $codeowners->write_to_filepath($filepath);
388 Write the contents of the file to the filesystem atomically.
392 $codeowners->write_to_fh($fh);
394 Format the file contents and write to a filehandle.
396 =head2 write_to_string
398 $scalarref = $codeowners->write_to_string;
400 Format the file contents and return a reference to a formatted string.
402 =head2 write_to_array
404 $lines = $codeowners->write_to_array;
406 Format the file contents as an arrayref of lines.
410 $owners = $codeowners->match($filepath);
412 Match the given filepath against the available patterns and return just the
413 owners for the matching pattern. Patterns are checked in the reverse order
414 they were defined in the file.
416 Returns C<undef> if no patterns match.
420 $owners = $codeowners->owners; # get all defined owners
421 $owners = $codeowners->owners($pattern);
423 Get an arrayref of owners defined in the file. If a pattern argument is given,
424 only owners for the given pattern are returned (or empty arrayref if the
425 pattern does not exist). If no argument is given, simply returns all owners
430 $patterns = $codeowners->patterns;
431 $patterns = $codeowners->patterns($owner);
433 Get an arrayref of all patterns defined.
437 $codeowners->update_owners($pattern => \@new_owners);
439 Set a new set of owners for a given pattern. If for some reason the file has
440 multiple such patterns, they will all be updated.
442 Nothing happens if the file does not already have at least one such pattern.
446 $codeowners->append(comment => $str);
447 $codeowners->append(pattern => $pattern, owners => \@owners);
448 $codeowners->append(); # blank line
454 $codeowners->prepend(comment => $str);
455 $codeowners->prepend(pattern => $pattern, owners => \@owners);
456 $codeowners->prepend(); # blank line
462 $filepaths = $codeowners->unowned;
464 Get the list of filepaths in the "unowned" section.
466 This parser supports an "extension" to the F<CODEOWNERS> file format which
467 lists unowned files at the end of the file. This list can be useful to have in
468 order to figure out what files we know are unowned versus what files we don't
473 $codeowners->add_unowned($filepath, ...);
475 Add one or more filepaths to the "unowned" list.
477 This method does not check to make sure the filepath(s) actually do not match
478 any patterns in the file, so you might want to call L</match> first.
480 See L</unowned> for an explanation.
482 =head2 remove_unowned
484 $codeowners->remove_unowned($filepath, ...);
486 Remove one or more filepaths from the "unowned" list.
488 Silently ignores filepaths that are already not listed.
490 See L</unowned> for an explanation.
494 $codeowners->clear_unowned;
496 Remove all filepaths from the "unowned" list.
498 See L</unowned> for an explanation.
502 Please report any bugs or feature requests on the bugtracker website
503 L<https://github.com/chazmcgarvey/git-codeowners/issues>
505 When submitting a bug or request, please include a test-file or a
506 patch to an existing test-file that illustrates the bug or desired
511 Charles McGarvey <chazmcgarvey@brokenzipper.com>
513 =head1 COPYRIGHT AND LICENSE
515 This software is copyright (c) 2019 by Charles McGarvey.
517 This is free software; you can redistribute it and/or modify it under
518 the same terms as the Perl 5 programming language system itself.
This page took 0.064893 seconds and 3 git commands to generate.