X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=lib%2FApp%2FCodeowners.pm;h=8a83a68496a7c7281ad28672426970551d6df139;hb=d7841cb4430c9f7b3529b156fe1c2d599071cfa5;hp=113a3a10b4140daec443061731b8868258af9770;hpb=e3541feec258bc63e2cf5ac387e264816a4bf278;p=chaz%2Fgit-codeowners diff --git a/lib/App/Codeowners.pm b/lib/App/Codeowners.pm index 113a3a1..8a83a68 100644 --- a/lib/App/Codeowners.pm +++ b/lib/App/Codeowners.pm @@ -6,9 +6,10 @@ use utf8; use warnings; use strict; +use App::Codeowners::Formatter; use App::Codeowners::Options; -use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel stringf); -use Color::ANSI::Util qw(ansifg ansi_reset); +use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel); +use Color::ANSI::Util 0.03 qw(ansifg); use Encode qw(encode); use File::Codeowners; use Path::Tiny; @@ -50,26 +51,52 @@ sub _command_show { or die "No CODEOWNERS file in $toplevel\n"; my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path); - my ($cdup) = run_git(qw{rev-parse --show-cdup}); + my ($proc, $cdup) = run_git(qw{rev-parse --show-cdup}); + $proc->wait and exit 1; - my @results; + my $show_projects = $opts->{projects} // scalar @{$codeowners->projects}; - my $filepaths = git_ls_files('.', $opts->args) or die "Cannot list files\n"; - for my $filepath (@$filepaths) { + my $formatter = App::Codeowners::Formatter->new( + format => $opts->{format} || ' * %-50F %O', + handle => *STDOUT, + columns => [ + 'File', + $opts->{patterns} ? 'Pattern' : (), + 'Owner', + $show_projects ? 'Project' : (), + ], + ); + + my %filter_owners = map { $_ => 1 } @{$opts->{owner}}; + my %filter_projects = map { $_ => 1 } @{$opts->{project}}; + my %filter_patterns = map { $_ => 1 } @{$opts->{pattern}}; + + $proc = git_ls_files('.', $opts->args); + while (my $filepath = $proc->next) { my $match = $codeowners->match(path($filepath)->relative($cdup)); - push @results, [ + if (%filter_owners) { + for my $owner (@{$match->{owners}}) { + goto ADD_RESULT if $filter_owners{$owner}; + } + next; + } + if (%filter_patterns) { + goto ADD_RESULT if $filter_patterns{$match->{pattern} || ''}; + next; + } + if (%filter_projects) { + goto ADD_RESULT if $filter_projects{$match->{project} || ''}; + next; + } + ADD_RESULT: + $formatter->add_result([ $filepath, + $opts->{patterns} ? $match->{pattern} : (), $match->{owners}, - $opts->{project} ? $match->{project} : (), - ]; + $show_projects ? $match->{project} : (), + ]); } - - _format( - format => $opts->{format} || ' * %-50F %O', - out => *STDOUT, - headers => [qw(File Owner), $opts->{project} ? 'Project' : ()], - rows => \@results, - ); + $proc->wait and exit 1; } sub _command_owners { @@ -84,12 +111,12 @@ sub _command_owners { my $results = $codeowners->owners($opts->{pattern}); - _format( + my $formatter = App::Codeowners::Formatter->new( format => $opts->{format} || '%O', - out => *STDOUT, - headers => [qw(Owner)], - rows => [map { [$_] } @$results], + handle => *STDOUT, + columns => [qw(Owner)], ); + $formatter->add_result(map { [$_] } @$results); } sub _command_patterns { @@ -104,12 +131,32 @@ sub _command_patterns { my $results = $codeowners->patterns($opts->{owner}); - _format( + my $formatter = App::Codeowners::Formatter->new( format => $opts->{format} || '%T', - out => *STDOUT, - headers => [qw(Pattern)], - rows => [map { [$_] } @$results], + handle => *STDOUT, + columns => [qw(Pattern)], ); + $formatter->add_result(map { [$_] } @$results); +} + +sub _command_projects { + my $self = shift; + my $opts = shift; + + my $toplevel = git_toplevel('.') or die "Not a git repo\n"; + + my $codeowners_path = find_codeowners_in_directory($toplevel) + or die "No CODEOWNERS file in $toplevel\n"; + my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path); + + my $results = $codeowners->projects; + + my $formatter = App::Codeowners::Formatter->new( + format => $opts->{format} || '%P', + handle => *STDOUT, + columns => [qw(Project)], + ); + $formatter->add_result(map { [$_] } @$results); } sub _command_create { goto &_command_update } @@ -154,176 +201,14 @@ END if ($repopath) { # if there is a repo we can try to update the list of unowned files - my $git_files = git_ls_files($repopath); - if (@$git_files) { - $codeowners->clear_unowned; - $codeowners->add_unowned(grep { !$codeowners->match($_) } @$git_files); - } + my ($proc, @filepaths) = git_ls_files($repopath); + $proc->wait and exit 1; + $codeowners->clear_unowned; + $codeowners->add_unowned(grep { !$codeowners->match($_) } @filepaths); } $codeowners->write_to_filepath($path); print STDERR "Wrote $path\n"; } -sub _format { - my %args = @_; - - my $format = $args{format} || 'table'; - my $fh = $args{out} || *STDOUT; - my $headers = $args{headers} || []; - my $rows = $args{rows} || []; - - if ($format eq 'table') { - eval { require Text::Table::Any } or die "Missing dependency: Text::Table::Any\n"; - - my $table = Text::Table::Any::table( - header_row => 1, - rows => [$headers, map { [map { _stringify($_) } @$_] } @$rows], - backend => $ENV{PERL_TEXT_TABLE}, - ); - print { $fh } encode('UTF-8', $table); - } - elsif ($format =~ /^json(:pretty)?$/) { - my $pretty = !!$1; - eval { require JSON::MaybeXS } or die "Missing dependency: JSON::MaybeXS\n"; - - my $json = JSON::MaybeXS->new(canonical => 1, utf8 => 1, pretty => $pretty); - my $data = _combine_headers_rows($headers, $rows); - print { $fh } $json->encode($data); - } - elsif ($format =~ /^([ct])sv$/) { - my $sep = $1 eq 'c' ? ',' : "\t"; - eval { require Text::CSV } or die "Missing dependency: Text::CSV\n"; - - my $csv = Text::CSV->new({binary => 1, eol => $/, sep => $sep}); - $csv->print($fh, $headers); - $csv->print($fh, [map { encode('UTF-8', _stringify($_)) } @$_]) for @$rows; - } - elsif ($format =~ /^ya?ml$/) { - eval { require YAML } or die "Missing dependency: YAML\n"; - - my $data = _combine_headers_rows($headers, $rows); - print { $fh } encode('UTF-8', YAML::Dump($data)); - } - else { - my $data = _combine_headers_rows($headers, $rows); - - # https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/ - my @contrasting_colors = qw( - e6194b 3cb44b ffe119 4363d8 f58231 - 911eb4 42d4f4 f032e6 bfef45 fabebe - 469990 e6beff 9a6324 fffac8 800000 - aaffc3 808000 ffd8b1 000075 a9a9a9 - ); - - # assign a color to each owner, on demand - my %owner_colors; - my $num = -1; - my $owner_color = sub { - my $owner = shift or return; - $owner_colors{$owner} ||= do { - $num = ($num + 1) % scalar @contrasting_colors; - $contrasting_colors[$num]; - }; - }; - - my %filter = ( - quote => sub { local $_ = $_[0]; s/"/\"/s; "\"$_\"" }, - ); - - my $create_filterer = sub { - my $value = shift || ''; - my $color = shift || ''; - my $gencolor = ref($color) eq 'CODE' ? $color : sub { $color }; - return sub { - my $arg = shift; - my ($filters, $color) = _expand_filter_args($arg); - if (ref($value) eq 'ARRAY') { - $value = join(',', map { _colored($_, $color // $gencolor->($_)) } @$value); - } - else { - $value = _colored($value, $color // $gencolor->($value)); - } - for my $key (@$filters) { - if (my $filter = $filter{$key}) { - $value = $filter->($value); - } - else { - warn "Unknown filter: $key\n" - } - } - $value || ''; - }; - }; - - for my $row (@$data) { - my %info = ( - F => $create_filterer->($row->{File}, undef), - O => $create_filterer->($row->{Owner}, $owner_color), - P => $create_filterer->($row->{Project}, undef), - T => $create_filterer->($row->{Pattern}, undef), - ); - - my $text = stringf($format, %info); - print { $fh } encode('UTF-8', $text), "\n"; - } - } -} - -sub _expand_filter_args { - my $arg = shift || ''; - - my @filters = split(/,/, $arg); - my $color_override; - - for (my $i = 0; $i < @filters; ++$i) { - my $filter = $filters[$i] or next; - if ($filter =~ /^(?:nocolor|color:([0-9a-fA-F]{3,6}))$/) { - $color_override = $1 || ''; - splice(@filters, $i, 1); - redo; - } - } - - return (\@filters, $color_override); -} - -sub _colored { - my $text = shift; - my $rgb = shift or return $text; - - # ansifg honors NO_COLOR already, but ansi_reset does not. - return $text if $ENV{NO_COLOR}; - - $rgb =~ s/^(.)(.)(.)$/$1$1$2$2$3$3/; - if ($rgb !~ m/^[0-9a-fA-F]{6}$/) { - warn "Color value must be in 'ffffff' or 'fff' form.\n"; - return $text; - } - - my ($begin, $end) = (ansifg($rgb), ansi_reset); - return "${begin}${text}${end}"; -} - -sub _combine_headers_rows { - my $headers = shift; - my $rows = shift; - - my @new_rows; - - for my $row (@$rows) { - push @new_rows, (my $new_row = {}); - for (my $i = 0; $i < @$headers; ++$i) { - $new_row->{$headers->[$i]} = $row->[$i]; - } - } - - return \@new_rows; -} - -sub _stringify { - my $item = shift; - return ref($item) eq 'ARRAY' ? join(',', @$item) : $item; -} - 1;