App-ShellCompleter-cpanm
view release on metacpan or search on metacpan
lib/App/ShellCompleter/cpanm.pm view on Meta::CPAN
use Complete::Util qw(answer_has_entries complete_array_elem);
use Getopt::Long::Complete qw(GetOptionsWithCompletion);
our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
our $DATE = '2023-07-08'; # DATE
our $DIST = 'App-ShellCompleter-cpanm'; # DIST
our $VERSION = '0.212'; # VERSION
my $noop = sub {};
# complete with list of installed modules
my $comp_installed_mods = sub {
require Complete::Module;
my %args = @_;
log_trace("[_cpanm] Adding completion: installed modules");
Complete::Module::complete_module(
word => $args{word},
path_sep => '::',
);
};
# complete with installable stuff
my $comp_installable = sub {
my %args = @_;
my $word = $args{word} // '';
# try completing script name if we are in SCRIPT_MODE
{
last unless $ENV{SCRIPT_MODE};
last unless $word eq '' || $word =~ /\A\w[\w-]*\z/;
my $dbh = _connect_lcpan() or last;
my $sth;
$sth = $dbh->prepare(
"SELECT name FROM script WHERE name LIKE '$word%' ORDER BY name");
$sth->execute;
my @scripts;
my %seen;
while (my @row = $sth->fetchrow_array) {
my $script = $row[0];
push @scripts, $script unless $seen{$script}++;
}
return \@scripts if @scripts;
}
# first, we try the cheapest method first, which is local files
{
log_trace("[_cpanm] Trying completion: tarballs & dirs");
local $Complete::Common::OPT_FUZZY = 0;
local $Complete::Common::OPT_WORD_MODE = 0;
local $Complete::Common::OPT_CHAR_MODE = 0;
my $answer = complete_file(
filter => sub { log_trace(" $_"); /\.(zip|tar\.gz|tar\.bz2)$/i || (-d $_) },
word => $word,
);
}
# if that fails, and the word looks like the start of module name, try
# searching for CPAN module. currently we only query local CPAN for speed.
# if user already types something that looks like a path instead of module
# name, like '../' or perhaps 'C:\' (windows) then don't bother to complete
# with module name because it will just delay things without getting any
# result.
{
last unless $word eq '' || $word =~ /\A(\w+)(::\w+)*(::)?\z/;
use experimental 'smartmatch';
my $dbh = _connect_lcpan() or last;
my $sth;
my $mod_prefix = $args{mod_prefix} // '';
my $prefixed_word = "$mod_prefix$word";
my $num_sep = 0; while ($prefixed_word =~ /::/g) { $num_sep++ }
if ($prefixed_word eq '') {
$sth = $dbh->prepare("SELECT name,has_child FROM namespace WHERE name='' AND num_sep=0 ORDER BY name");
} else {
$sth = $dbh->prepare("SELECT name,has_child FROM namespace WHERE name LIKE '$prefixed_word%' AND num_sep=$num_sep ORDER BY name");
}
$sth->execute;
my @mods;
while (my @row = $sth->fetchrow_array) {
my $mod = $row[0];
$mod =~ s/\A\Q$mod_prefix\E//;
push @mods, $mod unless grep { $_ eq $mod } @mods;
if ($row[1]) {
$mod .= '::';
push @mods, $mod unless grep { $_ eq $mod } @mods;
}
};
return \@mods if @mods;
}
# TODO module name can be suffixed with '@<version>'
[];
};
sub _connect_lcpan {
no warnings 'once';
eval "use App::lcpan 0.32"; ## no critic: TestingAndDebugging::ProhibitNoStrict
if ($@) {
log_trace("[_cpanm] App::lcpan not available, skipped ".
"trying to complete from CPAN module names");
return;
}
require Perinci::CmdLine::Util::Config;
my %lcpanargs;
my $res = Perinci::CmdLine::Util::Config::read_config(
program_name => "lcpan",
);
unless ($res->[0] == 200) {
log_trace("[_cpanm] Can't get config for lcpan: %s", $res);
last;
}
my $config = $res->[2];
$res = Perinci::CmdLine::Util::Config::get_args_from_config(
( run in 0.999 second using v1.01-cache-2.11-cpan-39bf76dae61 )