]>
Dogcows Code - chaz/git-codeowners/blob - lib/File/Codeowners.pm
ee50d99f14356e5bfd94cea9297ce200cdeaa412
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.48'; # 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)});
148 for my $line (@{$self->write_to_array}) {
154 sub write_to_string
{
157 my $str = join("\n", @{$self->write_to_array}) . "\n";
164 my $charset = shift // 'UTF-8';
168 for my $line (@{$self->_lines}) {
169 if (my $comment = $line->{comment
}) {
170 push @format, "#$comment";
172 elsif (my $pattern = $line->{pattern
}) {
173 my $owners = join(' ', @{$line->{owners
}});
174 push @format, "$pattern $owners";
181 my @unowned = sort keys %{$self->_unowned};
183 push @format, '' if $format[-1];
184 push @format, '### UNOWNED (File::Codeowners)';
185 for my $unowned (@unowned) {
186 push @format, "# $unowned";
191 $_ = encode
($charset, $_) for @format;
199 my $filepath = shift or _usage
(q{$codeowners->match($filepath)});
201 my $lines = $self->{match_lines
} ||= [reverse grep { ($_ || {})->{pattern
} } @{$self->_lines}];
203 for my $line (@$lines) {
204 my $matcher = $line->{matcher
} ||= build_gitignore_matcher
([$line->{pattern
}]);
206 pattern
=> $line->{pattern
},
207 owners
=> [@{$line->{owners
} || []}],
208 $line->{project
} ? (project
=> $line->{project
}) : (),
209 } if $matcher->($filepath);
212 return undef; ## no critic (Subroutines::ProhibitExplicitReturn)
220 return $self->{owners
} if !$pattern && $self->{owners
};
223 for my $line (@{$self->_lines}) {
224 next if $pattern && $line->{pattern
} && $pattern ne $line->{pattern
};
225 $owners{$_}++ for (@{$line->{owners
} || []});
228 my $owners = [sort keys %owners];
229 $self->{owners
} = $owners if !$pattern;
239 return $self->{patterns
} if !$owner && $self->{patterns
};
242 for my $line (@{$self->_lines}) {
243 next if $owner && !grep { $_ eq $owner } @{$line->{owners
} || []};
244 my $pattern = $line->{pattern
};
245 $patterns{$pattern}++ if $pattern;
248 my $patterns = [sort keys %patterns];
249 $self->{patterns
} = $patterns if !$owner;
258 return $self->{projects
} if $self->{projects
};
261 for my $line (@{$self->_lines}) {
262 my $project = $line->{project
};
263 $projects{$project}++ if $project;
266 my $projects = [sort keys %projects];
267 $self->{projects
} = $projects;
277 $pattern && $owners or _usage
(q{$codeowners->update_owners($pattern => \@owners)});
279 $owners = [$owners] if ref($owners) ne 'ARRAY';
285 for my $line (@{$self->_lines}) {
286 next if !$line->{pattern
};
287 next if $pattern ne $line->{pattern
};
288 $line->{owners
} = [@$owners];
296 sub update_owners_by_project
{
300 $project && $owners or _usage
(q{$codeowners->update_owners_by_project($project => \@owners)});
302 $owners = [$owners] if ref($owners) ne 'ARRAY';
308 for my $line (@{$self->_lines}) {
309 next if !$line->{project
} || !$line->{owners
};
310 next if $project ne $line->{project
};
311 $line->{owners
} = [@$owners];
321 my $old_project = shift;
322 my $new_project = shift;
323 $old_project && $new_project or _usage
(q{$codeowners->rename_project($project => $new_project)});
329 for my $line (@{$self->_lines}) {
330 next if !exists $line->{project
} || $old_project ne $line->{project
};
331 $line->{project
} = $new_project;
332 $line->{comment
} = " Project: $new_project" if exists $line->{comment
};
343 push @{$self->_lines}, (@_ ? {@_} : undef);
350 unshift @{$self->_lines}, (@_ ? {@_} : undef);
356 [sort keys %{$self->{unowned
} || {}}];
362 $self->_unowned->{$_}++ for @_;
368 delete $self->_unowned->{$_} for @_;
373 my $filepath = shift;
374 $self->_unowned->{$filepath};
380 $self->{unowned
} = {};
383 sub _lines
{ shift-
>{lines
} ||= [] }
384 sub _unowned
{ shift-
>{unowned
} ||= {} }
388 delete $self->{match_lines
};
389 delete $self->{owners
};
390 delete $self->{patterns
};
391 delete $self->{projects
};
404 File::Codeowners - Read and write CODEOWNERS files
414 $codeowners = File::Codeowners->new;
416 Construct a new L<File::Codeowners>.
420 $codeowners = File::Codeowners->parse('path/to/CODEOWNERS');
421 $codeowners = File::Codeowners->parse($filehandle);
422 $codeowners = File::Codeowners->parse(\@lines);
423 $codeowners = File::Codeowners->parse(\$string);
425 Parse a F<CODEOWNERS> file.
427 This is a shortcut for the C<parse_from_*> methods.
429 =head2 parse_from_filepath
431 $codeowners = File::Codeowners->parse_from_filepath('path/to/CODEOWNERS');
433 Parse a F<CODEOWNERS> file from the filesystem.
437 $codeowners = File::Codeowners->parse_from_fh($filehandle);
439 Parse a F<CODEOWNERS> file from an open filehandle.
441 =head2 parse_from_array
443 $codeowners = File::Codeowners->parse_from_array(\@lines);
445 Parse a F<CODEOWNERS> file stored as lines in an array.
447 =head2 parse_from_string
449 $codeowners = File::Codeowners->parse_from_string(\$string);
450 $codeowners = File::Codeowners->parse_from_string($string);
452 Parse a F<CODEOWNERS> file stored as a string. String should be UTF-8 encoded.
454 =head2 write_to_filepath
456 $codeowners->write_to_filepath($filepath);
458 Write the contents of the file to the filesystem atomically.
462 $codeowners->write_to_fh($fh);
464 Format the file contents and write to a filehandle.
466 =head2 write_to_string
468 $scalarref = $codeowners->write_to_string;
470 Format the file contents and return a reference to a formatted string.
472 =head2 write_to_array
474 $lines = $codeowners->write_to_array;
476 Format the file contents as an arrayref of lines.
480 $owners = $codeowners->match($filepath);
482 Match the given filepath against the available patterns and return just the
483 owners for the matching pattern. Patterns are checked in the reverse order
484 they were defined in the file.
486 Returns C<undef> if no patterns match.
490 $owners = $codeowners->owners; # get all defined owners
491 $owners = $codeowners->owners($pattern);
493 Get an arrayref of owners defined in the file. If a pattern argument is given,
494 only owners for the given pattern are returned (or empty arrayref if the
495 pattern does not exist). If no argument is given, simply returns all owners
500 $patterns = $codeowners->patterns;
501 $patterns = $codeowners->patterns($owner);
503 Get an arrayref of all patterns defined.
507 $projects = $codeowners->projects;
509 Get an arrayref of all projects defined.
513 $codeowners->update_owners($pattern => \@new_owners);
515 Set a new set of owners for a given pattern. If for some reason the file has
516 multiple such patterns, they will all be updated.
518 Nothing happens if the file does not already have at least one such pattern.
520 =head2 update_owners_by_project
522 $codeowners->update_owners_by_project($project => \@new_owners);
524 Set a new set of owners for all patterns under the given project.
526 Nothing happens if the file does not have a project with the given name.
528 =head2 rename_project
530 $codeowners->rename_project($old_name => $new_name);
534 Nothing happens if the file does not have a project with the old name.
538 $codeowners->append(comment => $str);
539 $codeowners->append(pattern => $pattern, owners => \@owners);
540 $codeowners->append(); # blank line
546 $codeowners->prepend(comment => $str);
547 $codeowners->prepend(pattern => $pattern, owners => \@owners);
548 $codeowners->prepend(); # blank line
554 $filepaths = $codeowners->unowned;
556 Get the list of filepaths in the "unowned" section.
558 This parser supports an "extension" to the F<CODEOWNERS> file format which
559 lists unowned files at the end of the file. This list can be useful to have in
560 order to figure out what files we know are unowned versus what files we don't
565 $codeowners->add_unowned($filepath, ...);
567 Add one or more filepaths to the "unowned" list.
569 This method does not check to make sure the filepath(s) actually do not match
570 any patterns in the file, so you might want to call L</match> first.
572 See L</unowned> for an explanation.
574 =head2 remove_unowned
576 $codeowners->remove_unowned($filepath, ...);
578 Remove one or more filepaths from the "unowned" list.
580 Silently ignores filepaths that are already not listed.
582 See L</unowned> for an explanation.
586 $codeowners->clear_unowned;
588 Remove all filepaths from the "unowned" list.
590 See L</unowned> for an explanation.
594 Please report any bugs or feature requests on the bugtracker website
595 L<https://github.com/chazmcgarvey/git-codeowners/issues>
597 When submitting a bug or request, please include a test-file or a
598 patch to an existing test-file that illustrates the bug or desired
603 Charles McGarvey <chazmcgarvey@brokenzipper.com>
605 =head1 COPYRIGHT AND LICENSE
607 This software is copyright (c) 2019 by Charles McGarvey.
609 This is free software; you can redistribute it and/or modify it under
610 the same terms as the Perl 5 programming language system itself.
This page took 0.062746 seconds and 3 git commands to generate.