File Coverage

blib/lib/Complete/Module.pm
Criterion Covered Total %
statement 56 56 100.0
branch 25 28 89.2
condition 33 35 94.2
subroutine 8 8 100.0
pod 1 1 100.0
total 123 128 96.0


line stmt bran cond sub pod time code
1             package Complete::Module;
2              
3             our $DATE = '2017-09-08'; # DATE
4             our $VERSION = '0.260'; # VERSION
5              
6 2     2   1853 use 5.010001;
  2         8  
7 2     2   13 use strict;
  2         5  
  2         48  
8 2     2   12 use warnings;
  2         4  
  2         64  
9             #use Log::Any '$log';
10              
11 2     2   679 use Complete::Common qw(:all);
  2         830  
  2         316  
12 2     2   17 use List::Util qw(uniq);
  2         5  
  2         2326  
13              
14             our %SPEC;
15             require Exporter;
16             our @ISA = qw(Exporter);
17             our @EXPORT_OK = qw(complete_module);
18              
19             our $OPT_SHORTCUT_PREFIXES;
20             if ($ENV{COMPLETE_MODULE_OPT_SHORTCUT_PREFIXES}) {
21             $OPT_SHORTCUT_PREFIXES =
22             { split /=|;/, $ENV{COMPLETE_MODULE_OPT_SHORTCUT_PREFIXES} };
23             } else {
24             $OPT_SHORTCUT_PREFIXES = {
25             #cp => 'Catalyst/Plugin/' # candidate
26             df => 'DateTime/Format/',
27             #dp => 'Dancer/Plugin/', # candidate
28             #d2p => 'Dancer2/Plugin/', # candidate
29             dz => 'Dist/Zilla/',
30             dzb => 'Dist/Zilla/PluginBundle/',
31             dzp => 'Dist/Zilla/Plugin/',
32             dzr => 'Dist/Zilla/Role/',
33             #pa => 'Plack/App/', # candidate
34             #pc => 'POE/Component/', # candidate
35             #pc => 'Perl/Critic/', # candidate?
36             #pcp => 'Perl/Critic/Policy/', # candidate?
37             #pd => 'Padre/Document/', # candidate
38             #pm => 'Plack/Middleware/', # candidate
39             #pp => 'Padre/Plugin/', # candidate
40             pw => 'Pod/Weaver/',
41             pwb => 'Pod/Weaver/PluginBundle/',
42             pwp => 'Pod/Weaver/Plugin/',
43             pwr => 'Pod/Weaver/Role/',
44             pws => 'Pod/Weaver/Section/',
45             #rtx => 'RT/Extension/', # candidate
46             #se => 'Search/Elasticsearch/', # candidate
47             #sec => 'Search/Elasticsearch/Client/', # candidate
48             #ser => 'Search/Elasticsearch/Role/', # candidate
49             #tp => 'Template/Plugin/', # candidate
50             #tw => 'Tickit/Widget/', # candidate
51              
52             # MooseX, MooX
53             # Moose::Exception
54             # Finance::Bank
55             # Mojo::*, MojoX::*, Mojolicious::*
56             };
57             }
58              
59             $SPEC{complete_module} = {
60             v => 1.1,
61             summary => 'Complete with installed Perl module names',
62             description => <<'_',
63              
64             For each directory in `@INC` (coderefs are ignored), find Perl modules and
65             module prefixes which have `word` as prefix. So for example, given `Te` as
66             `word`, will return e.g. `[Template, Template::, Term::, Test, Test::, Text::]`.
67             Given `Text::` will return `[Text::ASCIITable, Text::Abbrev, ...]` and so on.
68              
69             This function has a bit of overlapping functionality with , but
70             this function is geared towards shell tab completion. Compared to Module::List,
71             here are some differences: 1) list modules where prefix is incomplete; 2)
72             interface slightly different; 3) (currently) doesn't do recursing; 4) contains
73             conveniences for completion, e.g. map casing, expand intermediate paths (see
74             `Complete` for more details on those features), autoselection of path separator
75             character, some shortcuts, and so on.
76              
77             _
78             args => {
79             %arg_word,
80             path_sep => {
81             summary => 'Path separator',
82             schema => 'str*',
83             description => <<'_',
84              
85             For convenience in shell (bash) completion, instead of defaulting to `::` all
86             the time, will look at `word`. If word does not contain any `::` then will
87             default to `/`. This is because `::` (contains colon) is rather problematic as
88             it is by default a word-break character in bash and the word needs to be quoted
89             to avoid word-breaking by bash.
90              
91             _
92             },
93             find_pm => {
94             summary => 'Whether to find .pm files',
95             schema => 'bool*',
96             default => 1,
97             },
98             find_pod => {
99             summary => 'Whether to find .pod files',
100             schema => 'bool*',
101             default => 1,
102             },
103             find_pmc => {
104             summary => 'Whether to find .pmc files',
105             schema => 'bool*',
106             default => 1,
107             },
108             find_prefix => {
109             summary => 'Whether to find module prefixes',
110             schema => 'bool*',
111             default => 1,
112             },
113             ns_prefix => {
114             summary => 'Namespace prefix',
115             schema => 'str*',
116             description => <<'_',
117              
118             This is useful if you want to complete module under a specific namespace
119             (instead of the root). For example, if you set `ns_prefix` to
120             `Dist::Zilla::Plugin` (or `Dist::Zilla::Plugin::`) and word is `F`, you can get
121             `['FakeRelease', 'FileFinder::', 'FinderCode']` (those are modules under the
122             `Dist::Zilla::Plugin::` namespace).
123              
124             _
125             },
126             },
127             result_naked => 1,
128             };
129             sub complete_module {
130 20     20 1 112480 require Complete::Path;
131              
132 20         56 my %args = @_;
133              
134 20   50     80 my $word = $args{word} // '';
135             #$log->tracef('[compmod] Entering complete_module(), word=<%s>', $word);
136             #$log->tracef('[compmod] args=%s', \%args);
137              
138 20   100     79 my $ns_prefix = $args{ns_prefix} // '';
139 20         44 $ns_prefix =~ s/(::)+\z//;
140              
141             # convenience: allow Foo/Bar.{pm,pod,pmc}
142 20         43 $word =~ s/\.(pm|pmc|pod)\z//;
143              
144             # convenience (and compromise): if word doesn't contain :: we use the
145             # "safer" separator /, but if already contains '::' we use '::'. (Can also
146             # use '.' if user uses that.) Using "::" in bash means user needs to use
147             # quote (' or ") to make completion behave as expected since : is by default
148             # a word break character in bash/readline.
149 20         37 my $sep = $args{path_sep};
150 20 50       47 unless (defined $sep) {
151 20 50       84 $sep = $word =~ /::/ ? '::' :
    100          
152             $word =~ /\./ ? '.' : '/';
153             }
154              
155             # find shortcut prefixes
156             {
157 20         34 my $tmp = lc $word;
  20         47  
158 20         103 for (keys %$OPT_SHORTCUT_PREFIXES) {
159 191 100       2184 if ($tmp =~ /\A\Q$_\E(?:(\Q$sep\E).*|\z)/) {
160             substr($word, 0, length($_) + length($1 // '')) =
161 1   50     9 $OPT_SHORTCUT_PREFIXES->{$_};
162 1         2 last;
163             }
164             }
165             }
166              
167 20         151 $word =~ s!(::|/|\.)!::!g;
168              
169 20   100     107 my $find_pm = $args{find_pm} // 1;
170 20   100     69 my $find_pmc = $args{find_pmc} // 1;
171 20   100     64 my $find_pod = $args{find_pod} // 1;
172 20   100     64 my $find_prefix = $args{find_prefix} // 1;
173              
174             #$log->tracef('[compmod] invoking complete_path, word=<%s>', $word);
175             my $res = Complete::Path::complete_path(
176             word => $word,
177             starting_path => $ns_prefix,
178             list_func => sub {
179 62     62   15907 my ($path, $intdir, $isint) = @_;
180 62         170 (my $fspath = $path) =~ s!::!/!g;
181 62         94 my @res;
182 62         96 for my $inc (@INC) {
183 682 50       1220 next if ref($inc);
184 682 100       1386 my $dir = $inc . (length($fspath) ? "/$fspath" : "");
185 682 100       6495 opendir my($dh), $dir or next;
186 222         6382 for (readdir $dh) {
187 5058 100 100     12081 next if $_ eq '.' || $_ eq '..';
188 4614 100       12957 next unless /\A\w+(\.\w+)?\z/;
189 4406         26178 my $is_dir = (-d "$dir/$_");
190 4406 100 100     11847 next if $isint && !$is_dir;
191 2523 100 100     7113 push @res, "$_\::" if $is_dir && ($isint || $find_prefix);
      100        
192 2523 100 100     5356 push @res, $1 if /(.+)\.pm\z/ && $find_pm;
193 2523 100 100     3868 push @res, $1 if /(.+)\.pmc\z/ && $find_pmc;
194 2523 100 100     5234 push @res, $1 if /(.+)\.pod\z/ && $find_pod;
195             }
196             }
197 62         1884 [sort(uniq(@res))];
198             },
199             path_sep => '::',
200       26     is_dir_func => sub { }, # not needed, we already suffix "dirs" with ::
201 20         166 );
202              
203 20         2565 for (@$res) { s/::/$sep/g }
  44         127  
204              
205 20         78 $res = { words=>$res, path_sep=>$sep };
206             #$log->tracef('[compmod] Leaving complete_module(), result=<%s>', $res);
207 20         83 $res;
208             }
209              
210             1;
211             # ABSTRACT: Complete with installed Perl module names
212              
213             __END__