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