File Coverage

lib/App/oo_modulino_zsh_completion_helper.pm
Criterion Covered Total %
statement 137 152 90.1
branch 51 68 75.0
condition 21 26 80.7
subroutine 22 24 91.6
pod 0 13 0.0
total 231 283 81.6


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package App::oo_modulino_zsh_completion_helper;
3 13     13   707000 use 5.010;
  13         55  
4 13     13   80 use strict;
  13         48  
  13         565  
5 13     13   68 use warnings;
  13         29  
  13         1752  
6              
7             our $VERSION = "0.07";
8              
9 13         373 use MOP4Import::Base::CLI_JSON -as_base
10             , [fields =>
11             [eol => default => "\n"],
12             [lib =>
13             doc => "library directory list. SCALAR, ARRAY or ':' separated STRING",
14             zsh_completer => ": :_directories",
15             ],
16             ]
17 13     13   8514 ;
  13         1760273  
18              
19 13     13   51186 use MOP4Import::FieldSpec;
  13         34  
  13         1040  
20 13     13   114 use MOP4Import::Util qw/fields_hash fields_array/;
  13         29  
  13         910  
21 13     13   7553 use MOP4Import::Util::FindMethods;
  13         8506  
  13         898  
22              
23 13     13   109 use Module::Runtime ();
  13         28  
  13         345  
24              
25 13     13   6890 use MOP4Import::Util::ResolveSymlinks;
  13         11375  
  13         706  
26              
27             use MOP4Import::Types
28 13         207 ZshParams => [[fields => qw/pmfile words NUMERIC CURRENT BUFFER CURSOR/]]
29 13     13   101 ;
  13         30  
30              
31             sub cli_inspector {
32 68     68 0 172 (my MY $self) = @_;
33 68         6752 require MOP4Import::Util::Inspector;
34 68         84852 'MOP4Import::Util::Inspector'->new(lib => $self->{lib});
35             }
36              
37             sub onconfigure_zero {
38 0     0 0 0 (my MY $self) = @_;
39 0         0 $self->{eol} = "\0";
40             }
41              
42             sub cmd_joined {
43 0     0 0 0 (my MY $self, my ($method, @args)) = @_;
44 0         0 my @completion = $self->$method(@args);
45 0         0 print join($self->{eol}, @completion), $self->{eol};
46             }
47              
48 2     2 0 13 sub IGNORE_OPTIONS_FROM {'MOP4Import::Base::CLI_JSON'}
49              
50             sub zsh_options {
51 5     5 0 40161 (my MY $self, my %opts) = @_;
52              
53 5         15 my ZshParams $opts = \%opts;
54              
55             my ($targetClass, $has_shbang) = $self->load_module_from_pm($opts->{pmfile})
56 5 50       46 or Carp::croak "Can't extract class name from $opts->{pmfile}";
57              
58 5         64 my $optionPrefix = $self->word_prefix($opts);
59 5 100       27 $optionPrefix =~ s/^--?// if defined $optionPrefix;
60              
61 5         17 my $universal_argument = $opts->{NUMERIC};
62              
63 5         42 my @options = $self->cli_inspector->list_options_of($targetClass);
64 5 100 100     3898 if (defined $optionPrefix and $optionPrefix ne '') {
65 1         4 @options = grep {/^$optionPrefix/} @options;
  4         34  
66             }
67              
68             my @grouped = map {
69 5         25 my ($implClass, @specs) = @$_;
  6         4126  
70 6 50       28 if (not $universal_argument) {
71 6 100 100     55 ($implClass eq '' || $implClass ne $self->IGNORE_OPTIONS_FROM) ? @specs : ();
72             } else {
73 0         0 @specs;
74             }
75             } $self->cli_inspector->group_options_of($targetClass, @options);
76              
77             map {
78 5         37 my $optSpec;
  20         33  
79 20 100       52 if (ref (my FieldSpec $spec = $_)) {
80 4         11 $optSpec = "--$spec->{name}=-";
81 4 100       13 if ($spec->{doc}) {
82 1         8 $optSpec .= "[".$self->zsh_escape_doc($spec->{doc})."]"
83             }
84 4 50       12 if ($spec->{zsh_completer}) {
85 0         0 $optSpec .= $spec->{zsh_completer};
86             }
87             } else {
88 16         33 $optSpec = "--$_=-";
89             }
90 20         74 $optSpec;
91             } @grouped;
92             }
93              
94             sub zsh_escape_doc {
95 1     1 0 3 (my MY $self, my $doc) = @_;
96 1         4 $doc =~ s/[\\\[\]]/\\$&/g;
97 1         6 $doc;
98             }
99              
100             sub zsh_methods {
101 6     6 0 50540 (my MY $self, my %opts) = @_;
102              
103 6         33 my ZshParams $opts = \%opts;
104              
105             my ($targetClass, $has_shbang, $is_class)
106             = $self->load_module_from_pm($opts->{pmfile})
107 6 50       61 or Carp::croak "Can't extract class name from $opts->{pmfile}";
108              
109 6         92 my $insp = $self->cli_inspector;
110              
111 6         2742 my $methodPrefix = $self->word_prefix($opts);
112              
113 6         20 my $universal_argument = $opts->{NUMERIC};
114              
115             # default => methods implemented in $targetClass only.
116             # one universal_argument => find superclasses too.
117             # two universal_argument => find all methods including getters, new,...
118              
119 6         17 my @gather_default = (is_class => $is_class, do {
120 6 100 100     59 if ($methodPrefix || (($universal_argument || 0) >= 4*4)) {
      66        
121 1         3 (all => 1)
122             } else {
123 5 100       53 (no_getter => 1,
124             ($is_class ? (method_only => 1) : ()),
125             )
126             }
127             });
128              
129 6         57 my @methods = $self->gather_methods_from($targetClass, undef, @gather_default);
130 6 100 100     1743 if ($methodPrefix or $universal_argument) {
131 2         5 my %seen; $seen{$_} = 1 for @methods;
  2         29  
132 2         5 (undef, my @super) = @{mro::get_linear_isa($targetClass)};
  2         19  
133 2         6 foreach my $super (@super) {
134 1         5 push @methods, $self->gather_methods_from($super, \%seen, @gather_default);
135             }
136             }
137              
138 6 100       89 if ($methodPrefix) {
139 1         3 @methods = grep {/^$methodPrefix/} @methods;
  3         20  
140             }
141              
142             map {
143 6 50       24 my $method = $targetClass->can("cmd_$_") ? "cmd_$_" : $_;
  15         173  
144 15 100       65 if (defined (my $doc = $insp->info_method_doc_of($targetClass, $method, 1))) {
145 2         134 "$_:$doc"
146             } else {
147 13         793 $_;
148             }
149             } @methods;
150             }
151              
152             sub word_prefix {
153 11     11 0 37 (my MY $self, my ZshParams $opts) = @_;
154              
155 11 100 66     82 unless ($opts->{words} && $opts->{CURRENT}) {
156 8         30 return undef;
157             }
158              
159 3         16 $opts->{words}[$opts->{CURRENT} - 1];
160             }
161              
162             sub gather_methods_from {
163 7     7 0 39 (my MY $self, my $targetClass, my $seenDict, my %opts) = @_;
164 7         21 my $no_getter = delete $opts{no_getter};
165 7         20 my $all = delete $opts{all};
166 7         17 my $meth_only = delete $opts{method_only};
167 7         18 my $is_class = delete $opts{is_class};
168 7 50       28 if (%opts) {
169 0         0 Carp::croak "Unknown options: ".join(", ", keys %opts);
170             }
171 7         29 $self->cli_inspector->require_module($targetClass);
172             MOP4Import::Util::function_names(
173             from => $targetClass,
174             matching => qr{^(?:cmd_)?[a-z]},
175             grep => sub {
176 25     25   7662 my ($realName, $code) = @_;
177 25 100 100     110 if ($is_class and $_ eq "new") {
178 1         4 return 0;
179             }
180 24         65 s/^cmd_//;
181 24 50       101 if ($seenDict->{$_}++) {
182 0         0 return 0;
183             }
184 24 50       69 if (/^onconfigure_/) {
185 0         0 return 0;
186             }
187 24 50       112 if ($self->cli_inspector->info_code_attribute(MetaOnly => $code)) {
188 0         0 return 0;
189             }
190 24 100       9308 if ($all) {
    100          
191 3         9 return 1;
192             }
193             elsif ($meth_only) {
194 3 100       12 return 0
195             if not $self->cli_inspector->info_code_attribute(method => $code);
196             }
197             else {
198 18 100       272 return 0 if MOP4Import::Base::Configure->can($_);
199             }
200 18 50       842 if ($no_getter) {
201 18         91 return not $self->cli_inspector->is_getter_of($targetClass, $_);
202             }
203 0         0 1;
204             # MOP4Import::Util::has_method_attr($code); # Too strict.
205             },
206 7         2794 %opts,
207             );
208             }
209              
210             sub load_module_from_pm {
211 11     11 0 36 (my MY $self, my $pmFile) = @_;
212              
213 11 50       74 my ($modname, $libpath, $has_shbang, $is_class)
214             = $self->find_package_from_pm($pmFile)
215             or Carp::croak "Can't find module name and library root from $pmFile'";
216              
217             {
218 11         47 local @INC = ($libpath, @INC);
  11         69  
219 11         107 Module::Runtime::require_module($modname);
220             }
221              
222 11 50       169 wantarray ? ($modname, $has_shbang, $is_class) : $modname;
223             }
224              
225             sub find_package_from_pm {
226 12     12 0 7107 (my MY $self, my $pmFile) = @_;
227              
228             # This is a workaround for broken MOP4Import::Util::ResolveSymlinks::normalize
229 12 100       843 my $realFn = File::Spec->rel2abs(
230             -l $pmFile
231             ? MOP4Import::Util::ResolveSymlinks->resolve_symlink($pmFile)
232             : $pmFile
233             );
234 12         909 $realFn =~ s/\.\w+\z//;
235              
236 12         94 my @dir = $self->splitdir($realFn);
237              
238 12         125 local $_ = $self->cli_read_file__($pmFile);
239              
240 12         2144 my $has_shbang = m{^\#!};
241              
242 12         201 while (/(?:^|\n) [\ \t]* (?# line beginning + space)
243              
244             (? package|class) [\n\ \t]+
245             (?# newline is allowed here)
246              
247             (? [\w:]+)
248             (?# module name)
249             \s* [;\{] (?# statement or block)
250             /xsg) {
251              
252 12         164 my $modname = $+{modName};
253 12         87 my $is_class = $+{keyword} eq "class";
254              
255             # Tail of $modname should be equal to it's rootname.
256 12 50       136 if (my $libprefix = $self->test_modname_with_path($modname, \@dir)) {
257             return wantarray
258 12 50       138 ? ($modname, $libprefix, $has_shbang, $is_class)
259             : $modname;
260             }
261             }
262 0         0 return;
263             }
264              
265             sub test_modname_with_path {
266 12     12 0 71 (my MY $self, my ($modname, $pathlist)) = @_;
267 12         79 my @modpath = split /::/, $modname;
268 12   33     119 shift @modpath while @modpath and $modpath[0] eq '';
269 12         73 my @copy = @$pathlist;
270 12   66     29 do {
271 14 50       254 if (pop(@copy) ne pop(@modpath)) {
272 0         0 return;
273             }
274             } while (@copy and @modpath);
275 12 50       157 if (@modpath) {
    50          
276 0         0 return;
277             }
278             elsif (@copy) {
279 12         294 File::Spec->catdir(@copy)
280             }
281             }
282              
283             sub splitdir {
284 12     12 0 43 (my MY $self, my $fn) = @_;
285 12         198 File::Spec->splitdir($fn);
286             }
287              
288             MY->cli_run(\@ARGV, {0 => 'zero'}) unless caller;
289              
290              
291              
292             1;
293             __END__