package App::ArchLinux::PackagerTools::CPAN; use strictures; use autodie; use CPAN::DistnameInfo; use Carp; use Function::Parameters; use Log::Any qw($log); use Syntax::Keyword::Try; use version; use App::ArchLinux::PackagerTools::CPAN::PackagesDetailsFetcher; =head1 NAME App::ArchLinux::PackagerTools::CPAN - Methods to interact with CPAN =head1 SYNOPSIS use App::ArchLinux::PackagerTools::CPAN; my $cpan = App::ArchLinux::PackagerTools::CPAN->new(); =head1 DESCRIPTION Utility methods to search for distributions in CPAN. Maintains an internal database that allows to perform case-insensitive searches for distribution names. =head1 METHODS =head2 new my $cpan = App::ArchLinux::PackagerTools::CPAN->new(); Returns a new instance. =cut method new($class: $context, $deps = {}) { $deps->{packages_details_fetcher} //= App::ArchLinux::PackagerTools::CPAN::PackagesDetailsFetcher->new($context); return $class->new_no_defaults($context, $deps); } method new_no_defaults($class: $context, $deps = {}) { return $context->{$class} if defined $context->{$class}; my $self = {}; bless $self, $class; $self->{deps} = $deps; $self->{dist_lc_map} = {}; $self->{dists} = {}; $context->{$class} = $self; $log->debug("Initialized new CPAN object"); return $self; } =head2 Public Methods =head3 is_newer_version_available if ($cpan->is_newer_version_available($dist_name, $version)) {...} Return 1 if a newer version of the distribution is available, 0 otherwise. =cut method is_newer_version_available($dist_name, $version) { my $dist = $self->get_dist($dist_name); my $dist_version = version->parse($dist->{version}); if ($dist_version > version->parse($version)) { return 1; } else { return 0; } } =head3 search_dist my $name = "App::ArchLinux::PackagerTools"; my $dist = $cpan->search_dist($name); Search for a distribution that matches the supplied, case-insensitive name. Either returns a hash that contains the dist_name and version of the found distribution or undef if none is found. Note that it searches for a distribution only, not for a module. =cut method search_dist($name) { $name =~ s/::/-/g; $log->tracef("Searching for CPAN distribution matching '%s'", $name); $self->_build_dist_index(); my $index_name = $self->{dist_lc_map}->{lc($name)}; if (defined $index_name) { return $self->{dists}->{$index_name}; } return; } =head3 get_dist my $name = "App::ArchLinux::PackagerTools"; my $dist = $cpan->get_dist($name); Return a distribution that matches the supplied, case-insensitive name. Returns a hash that contains the dist_name and version of the found distribution or raises an exception if no distribution is found. Note that it searches for a distribution only, not for a module. =cut method get_dist($dist_name) { $dist_name =~ s/::/-/g; $log->tracef("Getting CPAN data for distribution '%s'", $dist_name); $self->_build_dist_index(); my $index_name = $self->{dist_lc_map}->{lc($dist_name)}; if (defined $index_name) { return $self->{dists}->{$index_name}; } croak $log->errorf("Failed to find distribution with name %s", $dist_name); } =head2 Private Methods =head3 _build_dist_index $self->_build_dist_index(); Set up the internal index of CPAN distributions =cut method _build_dist_index() { return if (scalar(keys $self->{dists}->%*) > 0); $log->debug("Fetching CPAN modules index"); my $out = $self->{deps}->{packages_details_fetcher}->get_packages_data();; my %seen; my $line_number = 0; $log->debug("Building index..."); for (split /\n/, $out) { # skip meta info (first 9 lines) next if $line_number++ < 9; #$log->tracef("Parsing line: %s", $_); my ($module_name, $module_version, $file) = split /\s+/; next if $seen{$file}++ > 0; my $dist = CPAN::DistnameInfo->new($file); if (!defined $dist->{dist}) { $log->debugf("Failed to parse file: %s", $file); next; } my $dist_name = $dist->{dist}; my $dist_name_lc = lc($dist_name); my $dist_version; try { $dist_version = version->parse($dist->{version}); } catch { $log->debugf("Skipping dist with invalid version: %s", $@); $log->debugf("Skipped dist is: %s", $dist); next; } my $index_dist; my $index_version = version->parse($self->{dists}->{$dist_name}->{version}); my $index_name = $self->{dist_lc_map}->{$dist_name_lc}; $index_dist = $self->{dists}->{$index_name} if defined $index_name; if (!defined($index_dist) || (defined($index_dist) && $index_name eq $dist_name && $dist_version > $index_version) ) { $self->{dist_lc_map}->{$dist_name_lc} = $dist_name; $self->{dists}->{$dist_name} = { dist_name => $dist_name, version => $dist->{version}, }; } else { if ($dist_name ne $index_name && $dist_version < $index_version) { confess $log->errorf("Key '%s' already exists in lc_map. This requires a manual exception to map correctly.\nExisting: %s\nCurrent: %s", $dist_name_lc, $index_dist, {$dist->%{qw(dist version)}}); } } } $log->debugf("CPAN modules index contains %s dists", scalar(keys $self->{dists}->%*)); } 1; __END__