]> Dogcows Code - chaz/git-codeowners/blob - lib/App/Codeowners.pm
fix printing wide char with YAML formatter
[chaz/git-codeowners] / lib / App / Codeowners.pm
1 package App::Codeowners;
2 # ABSTRACT: A tool for managing CODEOWNERS files
3
4 use v5.10.1; # defined-or
5 use utf8;
6 use warnings;
7 use strict;
8
9 use App::Codeowners::Formatter;
10 use App::Codeowners::Options;
11 use App::Codeowners::Util qw(find_codeowners_in_directory run_git git_ls_files git_toplevel);
12 use Color::ANSI::Util 0.03 qw(ansifg);
13 use File::Codeowners;
14 use Path::Tiny;
15
16 our $VERSION = '9999.999'; # VERSION
17
18 =method main
19
20 App::Codeowners->main(@ARGV);
21
22 Run the script and exit; does not return.
23
24 =cut
25
26 sub main {
27 my $class = shift;
28 my $self = bless {}, $class;
29
30 my $opts = App::Codeowners::Options->new(@_);
31
32 my $color = $opts->{color};
33 local $ENV{NO_COLOR} = 1 if defined $color && !$color;
34
35 my $command = $opts->command;
36 my $handler = $self->can("_command_$command")
37 or die "Unknown command: $command\n";
38
39 binmode(STDOUT, ':encoding(UTF-8)');
40 $self->$handler($opts);
41
42 exit 0;
43 }
44
45 sub _command_show {
46 my $self = shift;
47 my $opts = shift;
48
49 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
50
51 my $codeowners_path = find_codeowners_in_directory($toplevel)
52 or die "No CODEOWNERS file in $toplevel\n";
53 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
54
55 my ($proc, $cdup) = run_git(qw{rev-parse --show-cdup});
56 $proc->wait and exit 1;
57
58 my $show_projects = $opts->{projects} // scalar @{$codeowners->projects};
59
60 my $formatter = App::Codeowners::Formatter->new(
61 format => $opts->{format} || ' * %-50F %O',
62 handle => *STDOUT,
63 columns => [
64 'File',
65 $opts->{patterns} ? 'Pattern' : (),
66 'Owner',
67 $show_projects ? 'Project' : (),
68 ],
69 );
70
71 my %filter_owners = map { $_ => 1 } @{$opts->{owner}};
72 my %filter_projects = map { $_ => 1 } @{$opts->{project}};
73 my %filter_patterns = map { $_ => 1 } @{$opts->{pattern}};
74
75 $proc = git_ls_files('.', $opts->args);
76 while (my $filepath = $proc->next) {
77 my $match = $codeowners->match(path($filepath)->relative($cdup));
78 if (%filter_owners) {
79 for my $owner (@{$match->{owners}}) {
80 goto ADD_RESULT if $filter_owners{$owner};
81 }
82 next;
83 }
84 if (%filter_patterns) {
85 goto ADD_RESULT if $filter_patterns{$match->{pattern} || ''};
86 next;
87 }
88 if (%filter_projects) {
89 goto ADD_RESULT if $filter_projects{$match->{project} || ''};
90 next;
91 }
92 ADD_RESULT:
93 $formatter->add_result([
94 $filepath,
95 $opts->{patterns} ? $match->{pattern} : (),
96 $match->{owners},
97 $show_projects ? $match->{project} : (),
98 ]);
99 }
100 $proc->wait and exit 1;
101 }
102
103 sub _command_owners {
104 my $self = shift;
105 my $opts = shift;
106
107 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
108
109 my $codeowners_path = find_codeowners_in_directory($toplevel)
110 or die "No CODEOWNERS file in $toplevel\n";
111 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
112
113 my $results = $codeowners->owners($opts->{pattern});
114
115 my $formatter = App::Codeowners::Formatter->new(
116 format => $opts->{format} || '%O',
117 handle => *STDOUT,
118 columns => [qw(Owner)],
119 );
120 $formatter->add_result(map { [$_] } @$results);
121 }
122
123 sub _command_patterns {
124 my $self = shift;
125 my $opts = shift;
126
127 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
128
129 my $codeowners_path = find_codeowners_in_directory($toplevel)
130 or die "No CODEOWNERS file in $toplevel\n";
131 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
132
133 my $results = $codeowners->patterns($opts->{owner});
134
135 my $formatter = App::Codeowners::Formatter->new(
136 format => $opts->{format} || '%T',
137 handle => *STDOUT,
138 columns => [qw(Pattern)],
139 );
140 $formatter->add_result(map { [$_] } @$results);
141 }
142
143 sub _command_projects {
144 my $self = shift;
145 my $opts = shift;
146
147 my $toplevel = git_toplevel('.') or die "Not a git repo\n";
148
149 my $codeowners_path = find_codeowners_in_directory($toplevel)
150 or die "No CODEOWNERS file in $toplevel\n";
151 my $codeowners = File::Codeowners->parse_from_filepath($codeowners_path);
152
153 my $results = $codeowners->projects;
154
155 my $formatter = App::Codeowners::Formatter->new(
156 format => $opts->{format} || '%P',
157 handle => *STDOUT,
158 columns => [qw(Project)],
159 );
160 $formatter->add_result(map { [$_] } @$results);
161 }
162
163 sub _command_create { goto &_command_update }
164 sub _command_update {
165 my $self = shift;
166 my $opts = shift;
167
168 my ($filepath) = $opts->args;
169
170 my $path = path($filepath || '.');
171 my $repopath;
172
173 die "Does not exist: $path\n" if !$path->parent->exists;
174
175 if ($path->is_dir) {
176 $repopath = $path;
177 $path = find_codeowners_in_directory($path) || $repopath->child('CODEOWNERS');
178 }
179
180 my $is_new = !$path->is_file;
181
182 my $codeowners;
183 if ($is_new) {
184 $codeowners = File::Codeowners->new;
185 my $template = <<'END';
186 This file shows mappings between subdirs/files and the individuals and
187 teams who own them. You can read this file yourself or use tools to query it,
188 so you can quickly determine who to speak with or send pull requests to.
189
190 Simply write a gitignore pattern followed by one or more names/emails/groups.
191 Examples:
192 /project_a/** @team1
193 *.js @harry @javascript-cabal
194 END
195 for my $line (split(/\n/, $template)) {
196 $codeowners->append(comment => $line);
197 }
198 }
199 else {
200 $codeowners = File::Codeowners->parse_from_filepath($path);
201 }
202
203 if ($repopath) {
204 # if there is a repo we can try to update the list of unowned files
205 my ($proc, @filepaths) = git_ls_files($repopath);
206 $proc->wait and exit 1;
207 $codeowners->clear_unowned;
208 $codeowners->add_unowned(grep { !$codeowners->match($_) } @filepaths);
209 }
210
211 $codeowners->write_to_filepath($path);
212 print STDERR "Wrote $path\n";
213 }
214
215 1;
216 __END__
217
218 =head1 DESCRIPTION
219
220 This is the implementation of the F<git-codeowners> command.
221
222 See L<git-codeowners> for documentation.
223
224 =cut
This page took 0.051495 seconds and 4 git commands to generate.