File Coverage

blib/lib/App/Codeowners/Options.pm
Criterion Covered Total %
statement 73 144 50.6
branch 20 54 37.0
condition 4 35 11.4
subroutine 14 21 66.6
pod 6 6 100.0
total 117 260 45.0


line stmt bran cond sub pod time code
1             package App::Codeowners::Options;
2             # ABSTRACT: Getopt and shell completion for App::Codeowners
3              
4 1     1   10 use v5.10.1;
  1         3  
5 1     1   5 use warnings;
  1         2  
  1         20  
6 1     1   5 use strict;
  1         1  
  1         29  
7              
8 1     1   5 use Encode qw(decode);
  1         2  
  1         68  
9 1     1   614 use Getopt::Long 2.39 ();
  1         8890  
  1         42  
10 1     1   6 use Path::Tiny;
  1         2  
  1         380  
11              
12             our $VERSION = '0.51'; # VERSION
13              
14             sub _pod2usage {
15 2     2   537 eval { require Pod::Usage };
  2         522  
16 2 50       33392 if ($@) {
17 0 0       0 my $ref = $VERSION eq '9999.999' ? 'master' : "v$VERSION";
18             my $exit = (@_ == 1 && $_[0] =~ /^\d+$/ && $_[0]) //
19 0   0     0 (@_ % 2 == 0 && {@_}->{'-exitval'}) // 2;
      0        
      0        
      0        
20 0         0 print STDERR <
21             Online documentation is available at:
22              
23             https://github.com/chazmcgarvey/git-codeowners/blob/$ref/README.md
24              
25             Tip: To enable inline documentation, install the Pod::Usage module.
26              
27             END
28 0         0 exit $exit;
29             }
30             else {
31 2         9 Pod::Usage::pod2usage(@_);
32             }
33             }
34              
35             sub _early_options {
36             return {
37 7 50   7   234 'color|colour!' => (-t STDOUT ? 1 : 0), ## no critic (InputOutput::ProhibitInteractiveTest)
38             'format|f=s' => undef,
39             'help|h|?' => 0,
40             'manual|man' => 0,
41             'shell-completion:s' => undef,
42             'version|v' => 0,
43             };
44             }
45              
46             sub _command_options {
47             return {
48 9     9   265 'create' => {},
49             'owners' => {
50             'pattern=s' => '',
51             },
52             'patterns' => {
53             'owner=s' => '',
54             },
55             'projects' => {},
56             'show' => {
57             'owner=s@' => [],
58             'pattern=s@' => [],
59             'project=s@' => [],
60             'patterns!' => 0,
61             'projects!' => undef,
62             'expand-aliases!' => 0,
63             },
64             'update' => {},
65             };
66             }
67              
68             sub _commands {
69 0     0   0 my $self = shift;
70 0         0 my @commands = sort keys %{$self->_command_options};
  0         0  
71 0         0 return @commands;
72             }
73              
74             sub _options {
75 0     0   0 my $self = shift;
76 0         0 my @command_options;
77 0 0       0 if (my $command = $self->{command}) {
78 0 0       0 @command_options = keys %{$self->_command_options->{$command} || {}};
  0         0  
79             }
80 0         0 return (keys %{$self->_early_options}, @command_options);
  0         0  
81             }
82              
83              
84             sub new {
85 7     7 1 37 my $class = shift;
86 7         43 my @args = @_;
87              
88             # assume UTF-8 args if non-ASCII
89 1 50   1   28 @args = map { decode('UTF-8', $_) } @args if grep { /\P{ASCII}/ } @args;
  1         3  
  1         15  
  7         28  
  0         0  
  15         94  
90              
91 7         44 my $self = bless {}, $class;
92              
93 7         47 my @args_copy = @args;
94              
95 7 50       84 my $opts = $self->get_options(
96             args => \@args,
97             spec => $self->_early_options,
98             config => 'pass_through',
99             ) or _pod2usage(2);
100              
101 7 50       47 if ($ENV{CODEOWNERS_COMPLETIONS}) {
102 0   0     0 $self->{command} = $args[0] || '';
103 0         0 my $cword = $ENV{CWORD};
104 0   0     0 my $cur = $ENV{CUR} || '';
105             # Adjust cword to remove progname
106 0         0 while (0 < --$cword) {
107 0 0 0     0 last if $cur eq ($args_copy[$cword] || '');
108             }
109 0         0 $self->completions($cword, @args_copy);
110 0         0 exit 0;
111             }
112              
113 7 100       22 if ($opts->{version}) {
114 1         19 my $progname = path($0)->basename;
115 1         123 print "${progname} ${VERSION}\n";
116 1         7 exit 0;
117             }
118 6 100       28 if ($opts->{help}) {
119 1         17 _pod2usage(-exitval => 0, -verbose => 99, -sections => [qw(NAME SYNOPSIS OPTIONS COMMANDS)]);
120             }
121 5 50       22 if ($opts->{manual}) {
122 0         0 _pod2usage(-exitval => 0, -verbose => 2);
123             }
124 5 50       17 if (defined $opts->{shell_completion}) {
125 0         0 $self->shell_completion($opts->{shell_completion});
126 0         0 exit 0;
127             }
128              
129             # figure out the command (or default to "show")
130 5         18 my $command = shift @args;
131 5   50     23 my $command_options = $self->_command_options->{$command || ''};
132 5 50       30 if (!$command_options) {
133 0 0       0 unshift @args, $command if defined $command;
134 0         0 $command = 'show';
135 0         0 $command_options = $self->_command_options->{$command};
136             }
137              
138 5 100       21 my $more_opts = $self->get_options(
139             args => \@args,
140             spec => $command_options,
141             ) or _pod2usage(2);
142              
143 4         103 %$self = (%$opts, %$more_opts, command => $command, args => \@args);
144 4         39 return $self;
145             }
146              
147              
148             sub command {
149 4     4 1 10 my $self = shift;
150 4         12 my $command = $self->{command};
151 4         13 my @commands = sort keys %{$self->_command_options};
  4         17  
152 4 50       21 return if not grep { $_ eq $command } @commands;
  24         50  
153 4         28 $command =~ s/[^a-z]/_/g;
154 4         18 return $command;
155             }
156              
157              
158             sub args {
159 4     4 1 15 my $self = shift;
160 4 50       8 return @{$self->{args} || []};
  4         45  
161             }
162              
163              
164             sub get_options {
165 12     12 1 42 my $self = shift;
166 12 50 33     146 my $args = {@_ == 1 && ref $_[0] eq 'HASH' ? %{$_[0]} : @_};
  0         0  
167              
168 12         29 my %options;
169             my %results;
170 12         18 while (my ($opt, $default_value) = each %{$args->{spec}}) {
  78         230  
171 66         216 my ($name) = $opt =~ /^([^=:!|]+)/;
172 66         163 $name =~ s/-/_/g;
173 66         173 $results{$name} = $default_value;
174 66         164 $options{$opt} = \$results{$name};
175             }
176              
177 12 50       46 if (my $fn = $args->{callback}) {
178             $options{'<>'} = sub {
179 0     0   0 my $arg = shift;
180 0         0 $fn->($arg, \%results);
181 0         0 };
182             }
183              
184 12         129 my $p = Getopt::Long::Parser->new;
185 12   100     376 $p->configure($args->{config} || 'default');
186 12 100       892 return if !$p->getoptionsfromarray($args->{args}, %options);
187              
188 11         6872 return \%results;
189             }
190              
191              
192             sub shell_completion {
193 0     0 1   my $self = shift;
194 0   0       my $type = lc(shift || 'bash');
195              
196 0 0         if ($type eq 'bash') {
197 0           print <<'END';
198             # git-codeowners - Bash completion
199             # To use, eval this code:
200             # eval "$(git-codeowners --shell-completion)"
201             # This will work without the bash-completion package, but handling of colons
202             # in the completion word will work better with bash-completion installed and
203             # enabled.
204             _git_codeowners() {
205             local cur words cword
206             if declare -f _get_comp_words_by_ref >/dev/null
207             then
208             _get_comp_words_by_ref -n : cur cword words
209             else
210             words=("${COMP_WORDS[@]}")
211             cword=${COMP_CWORD}
212             cur=${words[cword]}
213             fi
214             local IFS=$'\n'
215             COMPREPLY=($(CODEOWNERS_COMPLETIONS=1 CWORD="$cword" CUR="$cur" ${words[@]}))
216             # COMPREPLY=($(${words[0]} --completions "$cword" "${words[@]}"))
217             if [[ "$?" -eq 9 ]]
218             then
219             COMPREPLY=($(compgen -A "${COMPREPLY[0]}" -- "$cur"))
220             fi
221             declare -f __ltrim_colon_completions >/dev/null && \
222             __ltrim_colon_completions "$cur"
223             return 0
224             }
225             complete -F _git_codeowners git-codeowners
226             END
227             }
228             else {
229             # TODO - Would be nice to support Zsh
230 0           warn "No such shell completion: $type\n";
231             }
232             }
233              
234              
235             sub completions {
236 0     0 1   my $self = shift;
237 0           my $cword = shift;
238 0           my @words = @_;
239              
240 0   0       my $current = $words[$cword] || '';
241 0   0       my $prev = $words[$cword - 1] || '';
242              
243 0           my $reply;
244              
245 0 0 0       if ($prev eq '--format' || $prev eq '-f') {
    0          
246 0           $reply = $self->_completion_formats;
247             }
248             elsif ($current =~ /^-/) {
249 0           $reply = $self->_completion_options;
250             }
251             else {
252 0 0         if (!$self->command) {
253 0           $reply = [$self->_commands, @{$self->_completion_options([keys %{$self->_early_options}])}];
  0            
  0            
254             }
255             else {
256 0           print 'file';
257 0           exit 9;
258             }
259             }
260              
261 0           local $, = "\n";
262 0           print grep { /^\Q$current\E/ } @$reply;
  0            
263 0           exit 0;
264             }
265              
266             sub _completion_options {
267 0     0     my $self = shift;
268 0   0       my $opts = shift || [$self->_options];
269              
270 0           my @options;
271              
272 0           for my $option (@$opts) {
273 0           my ($names, $op, $vtype) = $option =~ /^([^=:!]+)([=:!]?)(.*)$/;
274 0           my @names = split(/\|/, $names);
275              
276 0           for my $name (@names) {
277 0 0         if ($op eq '!') {
278 0           push @options, "--$name", "--no-$name";
279             }
280             else {
281 0 0         if (length($name) > 1) {
282 0           push @options, "--$name";
283             }
284             else {
285 0           push @options, "-$name";
286             }
287             }
288             }
289             }
290              
291 0           return [sort @options];
292             }
293              
294 0     0     sub _completion_formats { [qw(csv json json:pretty tsv yaml)] }
295              
296             1;
297              
298             __END__