]>
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.49'; # 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*#(.*)/) {
69 if ($comment =~ /^\h*Project:\h*(.+?)\h*$/i) {
70 $project = $current_project = $1 || undef;
74 $project ? (project
=> $project) : (),
77 elsif ($line =~ /^\h*$/) {
80 elsif ($line =~ /^\h*(.+?)(?<!\\)\h+(.+)/) {
82 my @owners = $2 =~ /( (?:\@+"[^"]*") | (?:\H+) )/gx;
86 $current_project ? (project
=> $current_project) : (),
90 die "Parse error on line $.: $line\n";
95 while (my $line = <$fh>) {
97 if ($line =~ /# (.+)/) {
99 $unowned{$filepath}++;
104 $self->{lines
} = \
@lines;
105 $self->{unowned
} = \
%unowned;
111 sub parse_from_array
{
113 my $arr = shift or _usage
(q{$codeowners->parse_from_array(\@lines)});
115 $self = bless({}, $self) if !ref($self);
117 $arr = [$arr, @_] if @_;
118 my $str = join("\n", @$arr);
119 return $self->parse_from_string(\
$str);
123 sub parse_from_string
{
125 my $str = shift or _usage
(q{$codeowners->parse_from_string(\$string)});
127 $self = bless({}, $self) if !ref($self);
129 my $ref = ref($str) eq 'SCALAR' ? $str : \
$str;
130 open(my $fh, '<:encoding(UTF-8)', $ref) or die "open failed: $!";
132 return $self->parse_from_fh($fh);
136 sub write_to_filepath
{
138 my $path = shift or _usage
(q{$codeowners->write_to_filepath($filepath)});
140 path
($path)->spew_utf8([map { "$_\n" } @{$self->write_to_array}]);
146 my $fh = shift or _usage
(q{$codeowners->write_to_fh($fh)});
149 for my $line (@{$self->write_to_array($charset)}) {
155 sub write_to_string
{
159 my $str = join("\n", @{$self->write_to_array($charset)}) . "\n";
170 for my $line (@{$self->_lines}) {
171 if (my $comment = $line->{comment
}) {
172 push @format, "#$comment";
174 elsif (my $pattern = $line->{pattern
}) {
175 my $owners = join(' ', @{$line->{owners
}});
176 push @format, "$pattern $owners";
183 my @unowned = sort keys %{$self->_unowned};
185 push @format, '' if $format[-1];
186 push @format, '### UNOWNED (File::Codeowners)';
187 for my $unowned (@unowned) {
188 push @format, "# $unowned";
192 if (defined $charset) {
193 $_ = encode
($charset, $_) for @format;
201 my $filepath = shift or _usage
(q{$codeowners->match($filepath)});
203 my $lines = $self->{match_lines
} ||= [reverse grep { ($_ || {})->{pattern
} } @{$self->_lines}];
205 for my $line (@$lines) {
206 my $matcher = $line->{matcher
} ||= build_gitignore_matcher
([$line->{pattern
}]);
208 pattern
=> $line->{pattern
},
209 owners
=> [@{$line->{owners
} || []}],
210 $line->{project
} ? (project
=> $line->{project
}) : (),
211 } if $matcher->($filepath);
214 return undef; ## no critic (Subroutines::ProhibitExplicitReturn)
222 return $self->{owners
} if !$pattern && $self->{owners
};
225 for my $line (@{$self->_lines}) {
226 next if $pattern && $line->{pattern
} && $pattern ne $line->{pattern
};
227 $owners{$_}++ for (@{$line->{owners
} || []});
230 my $owners = [sort keys %owners];
231 $self->{owners
} = $owners if !$pattern;
241 return $self->{patterns
} if !$owner && $self->{patterns
};
244 for my $line (@{$self->_lines}) {
245 next if $owner && !grep { $_ eq $owner } @{$line->{owners
} || []};
246 my $pattern = $line->{pattern
};
247 $patterns{$pattern}++ if $pattern;
250 my $patterns = [sort keys %patterns];
251 $self->{patterns
} = $patterns if !$owner;
260 return $self->{projects
} if $self->{projects
};
263 for my $line (@{$self->_lines}) {
264 my $project = $line->{project
};
265 $projects{$project}++ if $project;
268 my $projects = [sort keys %projects];
269 $self->{projects
} = $projects;
279 $pattern && $owners or _usage
(q{$codeowners->update_owners($pattern => \@owners)});
281 $owners = [$owners] if ref($owners) ne 'ARRAY';
287 for my $line (@{$self->_lines}) {
288 next if !$line->{pattern
};
289 next if $pattern ne $line->{pattern
};
290 $line->{owners
} = [@$owners];
298 sub update_owners_by_project
{
302 $project && $owners or _usage
(q{$codeowners->update_owners_by_project($project => \@owners)});
304 $owners = [$owners] if ref($owners) ne 'ARRAY';
310 for my $line (@{$self->_lines}) {
311 next if !$line->{project
} || !$line->{owners
};
312 next if $project ne $line->{project
};
313 $line->{owners
} = [@$owners];
323 my $old_owner = shift;
324 my $new_owner = shift;
325 $old_owner && $new_owner or _usage
(q{$codeowners->rename_owner($owner => $new_owner)});
331 for my $line (@{$self->_lines}) {
332 next if !exists $line->{owners
};
333 for (my $i = 0; $i < @{$line->{owners
}}; ++$i) {
334 next if $line->{owners
}[$i] ne $old_owner;
335 $line->{owners
}[$i] = $new_owner;
346 my $old_project = shift;
347 my $new_project = shift;
348 $old_project && $new_project or _usage
(q{$codeowners->rename_project($project => $new_project)});
354 for my $line (@{$self->_lines}) {
355 next if !exists $line->{project
} || $old_project ne $line->{project
};
356 $line->{project
} = $new_project;
357 $line->{comment
} = " Project: $new_project" if exists $line->{comment
};
368 push @{$self->_lines}, (@_ ? {@_} : undef);
375 unshift @{$self->_lines}, (@_ ? {@_} : undef);
381 [sort keys %{$self->{unowned
} || {}}];
387 $self->_unowned->{$_}++ for @_;
393 delete $self->_unowned->{$_} for @_;
398 my $filepath = shift;
399 $self->_unowned->{$filepath};
405 $self->{unowned
} = {};
408 sub _lines
{ shift-
>{lines
} ||= [] }
409 sub _unowned
{ shift-
>{unowned
} ||= {} }
413 delete $self->{match_lines
};
414 delete $self->{owners
};
415 delete $self->{patterns
};
416 delete $self->{projects
};
429 File::Codeowners - Read and write CODEOWNERS files
439 $codeowners = File::Codeowners->new;
441 Construct a new L<File::Codeowners>.
445 $codeowners = File::Codeowners->parse('path/to/CODEOWNERS');
446 $codeowners = File::Codeowners->parse($filehandle);
447 $codeowners = File::Codeowners->parse(\@lines);
448 $codeowners = File::Codeowners->parse(\$string);
450 Parse a F<CODEOWNERS> file.
452 This is a shortcut for the C<parse_from_*> methods.
454 =head2 parse_from_filepath
456 $codeowners = File::Codeowners->parse_from_filepath('path/to/CODEOWNERS');
458 Parse a F<CODEOWNERS> file from the filesystem.
462 $codeowners = File::Codeowners->parse_from_fh($filehandle);
464 Parse a F<CODEOWNERS> file from an open filehandle.
466 =head2 parse_from_array
468 $codeowners = File::Codeowners->parse_from_array(\@lines);
470 Parse a F<CODEOWNERS> file stored as lines in an array.
472 =head2 parse_from_string
474 $codeowners = File::Codeowners->parse_from_string(\$string);
475 $codeowners = File::Codeowners->parse_from_string($string);
477 Parse a F<CODEOWNERS> file stored as a string. String should be UTF-8 encoded.
479 =head2 write_to_filepath
481 $codeowners->write_to_filepath($filepath);
483 Write the contents of the file to the filesystem atomically.
487 $codeowners->write_to_fh($fh);
489 Format the file contents and write to a filehandle.
491 =head2 write_to_string
493 $scalarref = $codeowners->write_to_string;
495 Format the file contents and return a reference to a formatted string.
497 =head2 write_to_array
499 $lines = $codeowners->write_to_array;
501 Format the file contents as an arrayref of lines.
505 $owners = $codeowners->match($filepath);
507 Match the given filepath against the available patterns and return just the
508 owners for the matching pattern. Patterns are checked in the reverse order
509 they were defined in the file.
511 Returns C<undef> if no patterns match.
515 $owners = $codeowners->owners; # get all defined owners
516 $owners = $codeowners->owners($pattern);
518 Get an arrayref of owners defined in the file. If a pattern argument is given,
519 only owners for the given pattern are returned (or empty arrayref if the
520 pattern does not exist). If no argument is given, simply returns all owners
525 $patterns = $codeowners->patterns;
526 $patterns = $codeowners->patterns($owner);
528 Get an arrayref of all patterns defined.
532 $projects = $codeowners->projects;
534 Get an arrayref of all projects defined.
538 $codeowners->update_owners($pattern => \@new_owners);
540 Set a new set of owners for a given pattern. If for some reason the file has
541 multiple such patterns, they will all be updated.
543 Nothing happens if the file does not already have at least one such pattern.
545 =head2 update_owners_by_project
547 $codeowners->update_owners_by_project($project => \@new_owners);
549 Set a new set of owners for all patterns under the given project.
551 Nothing happens if the file does not have a project with the given name.
555 $codeowners->rename_owner($old_name => $new_name);
559 Nothing happens if the file does not have an owner with the old name.
561 =head2 rename_project
563 $codeowners->rename_project($old_name => $new_name);
567 Nothing happens if the file does not have a project with the old name.
571 $codeowners->append(comment => $str);
572 $codeowners->append(pattern => $pattern, owners => \@owners);
573 $codeowners->append(); # blank line
579 $codeowners->prepend(comment => $str);
580 $codeowners->prepend(pattern => $pattern, owners => \@owners);
581 $codeowners->prepend(); # blank line
587 $filepaths = $codeowners->unowned;
589 Get the list of filepaths in the "unowned" section.
591 This parser supports an "extension" to the F<CODEOWNERS> file format which
592 lists unowned files at the end of the file. This list can be useful to have in
593 order to figure out what files we know are unowned versus what files we don't
598 $codeowners->add_unowned($filepath, ...);
600 Add one or more filepaths to the "unowned" list.
602 This method does not check to make sure the filepath(s) actually do not match
603 any patterns in the file, so you might want to call L</match> first.
605 See L</unowned> for an explanation.
607 =head2 remove_unowned
609 $codeowners->remove_unowned($filepath, ...);
611 Remove one or more filepaths from the "unowned" list.
613 Silently ignores filepaths that are already not listed.
615 See L</unowned> for an explanation.
619 $codeowners->clear_unowned;
621 Remove all filepaths from the "unowned" list.
623 See L</unowned> for an explanation.
627 Please report any bugs or feature requests on the bugtracker website
628 L<https://github.com/chazmcgarvey/git-codeowners/issues>
630 When submitting a bug or request, please include a test-file or a
631 patch to an existing test-file that illustrates the bug or desired
636 Charles McGarvey <chazmcgarvey@brokenzipper.com>
638 =head1 COPYRIGHT AND LICENSE
640 This software is copyright (c) 2019 by Charles McGarvey.
642 This is free software; you can redistribute it and/or modify it under
643 the same terms as the Perl 5 programming language system itself.
This page took 0.064604 seconds and 4 git commands to generate.