�ɲɾ�����ӯ�����һ��ˣ��������С���˴��ͣ�������P���ҹ��ñ˽��ά�Բ��������˸߸ԣ�������ơ��ҹ��ñ�����ά�Բ���ˡ���˳^�ӣ������ӡ� ���ͯj�ӣ��ƺ���ӣ� ? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!? PNG ?%k25u25%fgd5n!PK0]_ Config.pmnu[# This CPAN::Config was automatically generated using /usr/local/cpanel/scripts/cpan_config # at Wed Mar 13 16:56:46 2024 # # If this Config.pm replaced an existing version, then it would be located at: # /usr/share/perl5/CPAN/Config.pm.1710349006 # This is CPAN.pm's systemwide configuration file. This file provides # defaults for users, and the values can be changed in a per-user # configuration file. $CPAN::Config = { 'auto_commit' => q[1], 'build_cache' => q[10], 'build_dir' => q[/home/.cpan/build], 'cache_metadata' => q[1], 'connect_to_internet_ok' => q[no], 'cpan_home' => q[/home/.cpan], 'dontload_hash' => { }, 'ftp' => q[/bin/false], 'ftp_proxy' => q[], 'getcwd' => q[cwd], 'gzip' => q[/bin/gzip], 'histfile' => q[/home/.cpan/histfile], 'histsize' => q[100], 'http_proxy' => q[], 'inactivity_timeout' => q[310], 'index_expire' => q[1], 'inhibit_startup_message' => q[1], 'keep_source_where' => q[/home/.cpan/sources], 'lynx' => q[], 'make' => q[/usr/bin/make], 'make_arg' => q[], 'make_install_arg' => q[UNINST=1], 'makepl_arg' => q[], 'mbuild_arg' => q[], 'mbuild_install_arg' => q[], 'mbuild_install_build_command' => q[./Build], 'mbuildpl_arg' => q[], 'ncftpget' => q[], 'no_proxy' => q[], 'pager' => q[/usr/bin/less], 'prefer_installer' => q[EUMM], 'prerequisites_policy' => q[follow], 'scan_cache' => q[atstart], 'shell' => q[/bin/bash], 'tar' => q[/bin/gtar], 'term_is_latin' => q[1], 'unzip' => q[/usr/bin/unzip], 'urllist' => [q[http://www.cpan.org//], q[http://103.17.8.8/pub/CPAN/], q[http://103.120.66.133/pub/CPAN/], q[http://103.48.119.120/pub/CPAN/], q[http://208.74.123.61/pub/CPAN/], q[http://185.125.185.32/pub/CPAN/], q[http://63.247.64.58/pub/CPAN/], q[http://185.15.22.168/pub/CPAN/], q[http://184.94.196.94/pub/CPAN/], q[http://203.174.85.202/pub/CPAN/], q[http://201.159.169.169/pub/CPAN/], q[http://110.232.143.226/pub/CPAN/], q[http://103.101.161.235/pub/CPAN/], q[http://184.94.196.97/pub/CPAN/], q[http://184.94.196.93/pub/CPAN/], q[http://209.133.199.70/pub/CPAN/], q[http://45.234.92.141/pub/CPAN/], q[http://103.15.48.49/pub/CPAN/], q[http://184.94.196.92/pub/CPAN/], q[http://118.88.24.67/pub/CPAN/], q[http://185.157.208.252/pub/CPAN/], q[http://208.100.0.204/pub/CPAN/], q[http://31.22.112.31/pub/CPAN/], q[http://208.74.123.62/pub/CPAN/], q[http://84.95.247.234/pub/CPAN/], q[http://102.22.80.80/pub/CPAN/]], 'use_sqlite' => q[0], 'wget' => q[/usr/bin/wget], }; 1; __END__ PK1]@~Meta/Requirements/.packlistnu[/usr/local/share/man/man3/CPAN::Meta::Requirements.3pm /usr/local/share/man/man3/CPAN::Meta::Requirements::Range.3pm /usr/local/share/perl5/CPAN/Meta/Requirements.pm /usr/local/share/perl5/CPAN/Meta/Requirements/Range.pm PK1]XX .packlistnu[/usr/local/bin/cpan /usr/local/bin/cpan-mirrors /usr/local/share/man/man1/cpan-mirrors.1 /usr/local/share/man/man1/cpan.1 /usr/local/share/man/man3/App::Cpan.3pm /usr/local/share/man/man3/CPAN.3pm /usr/local/share/man/man3/CPAN::API::HOWTO.3pm /usr/local/share/man/man3/CPAN::Admin.3pm /usr/local/share/man/man3/CPAN::Debug.3pm /usr/local/share/man/man3/CPAN::Distroprefs.3pm /usr/local/share/man/man3/CPAN::FirstTime.3pm /usr/local/share/man/man3/CPAN::HandleConfig.3pm /usr/local/share/man/man3/CPAN::Kwalify.3pm /usr/local/share/man/man3/CPAN::Mirrors.3pm /usr/local/share/man/man3/CPAN::Nox.3pm /usr/local/share/man/man3/CPAN::Plugin.3pm /usr/local/share/man/man3/CPAN::Plugin::Specfile.3pm /usr/local/share/man/man3/CPAN::Queue.3pm /usr/local/share/man/man3/CPAN::Tarzip.3pm /usr/local/share/man/man3/CPAN::Version.3pm /usr/local/share/perl5/App/Cpan.pm /usr/local/share/perl5/CPAN.pm /usr/local/share/perl5/CPAN/API/HOWTO.pod /usr/local/share/perl5/CPAN/Admin.pm /usr/local/share/perl5/CPAN/Author.pm /usr/local/share/perl5/CPAN/Bundle.pm /usr/local/share/perl5/CPAN/CacheMgr.pm /usr/local/share/perl5/CPAN/Complete.pm /usr/local/share/perl5/CPAN/Debug.pm /usr/local/share/perl5/CPAN/DeferredCode.pm /usr/local/share/perl5/CPAN/Distribution.pm /usr/local/share/perl5/CPAN/Distroprefs.pm /usr/local/share/perl5/CPAN/Distrostatus.pm /usr/local/share/perl5/CPAN/Exception/RecursiveDependency.pm /usr/local/share/perl5/CPAN/Exception/blocked_urllist.pm /usr/local/share/perl5/CPAN/Exception/yaml_not_installed.pm /usr/local/share/perl5/CPAN/Exception/yaml_process_error.pm /usr/local/share/perl5/CPAN/FTP.pm /usr/local/share/perl5/CPAN/FTP/netrc.pm /usr/local/share/perl5/CPAN/FirstTime.pm /usr/local/share/perl5/CPAN/HTTP/Client.pm /usr/local/share/perl5/CPAN/HTTP/Credentials.pm /usr/local/share/perl5/CPAN/HandleConfig.pm /usr/local/share/perl5/CPAN/Index.pm /usr/local/share/perl5/CPAN/InfoObj.pm /usr/local/share/perl5/CPAN/Kwalify.pm /usr/local/share/perl5/CPAN/Kwalify/distroprefs.dd /usr/local/share/perl5/CPAN/Kwalify/distroprefs.yml /usr/local/share/perl5/CPAN/LWP/UserAgent.pm /usr/local/share/perl5/CPAN/Mirrors.pm /usr/local/share/perl5/CPAN/Module.pm /usr/local/share/perl5/CPAN/Nox.pm /usr/local/share/perl5/CPAN/Plugin.pm /usr/local/share/perl5/CPAN/Plugin/Specfile.pm /usr/local/share/perl5/CPAN/Prompt.pm /usr/local/share/perl5/CPAN/Queue.pm /usr/local/share/perl5/CPAN/Shell.pm /usr/local/share/perl5/CPAN/Tarzip.pm /usr/local/share/perl5/CPAN/URL.pm /usr/local/share/perl5/CPAN/Version.pm PK2]n3y.. Version.pmnu6$package CPAN::Version; use strict; use vars qw($VERSION); $VERSION = "5.5003"; # CPAN::Version::vcmp courtesy Jost Krieger sub vcmp { my($self,$l,$r) = @_; local($^W) = 0; CPAN->debug("l[$l] r[$r]") if $CPAN::DEBUG; # treat undef as zero $l = 0 if $l eq 'undef'; $r = 0 if $r eq 'undef'; return 0 if $l eq $r; # short circuit for quicker success for ($l,$r) { s/_//g; } CPAN->debug("l[$l] r[$r]") if $CPAN::DEBUG; for ($l,$r) { next unless tr/.// > 1 || /^v/; s/^v?/v/; 1 while s/\.0+(\d)/.$1/; # remove leading zeroes per group } CPAN->debug("l[$l] r[$r]") if $CPAN::DEBUG; if ($l=~/^v/ <=> $r=~/^v/) { for ($l,$r) { next if /^v/; $_ = $self->float2vv($_); } } CPAN->debug("l[$l] r[$r]") if $CPAN::DEBUG; my $lvstring = "v0"; my $rvstring = "v0"; if ($] >= 5.006 && $l =~ /^v/ && $r =~ /^v/) { $lvstring = $self->vstring($l); $rvstring = $self->vstring($r); CPAN->debug(sprintf "lv[%vd] rv[%vd]", $lvstring, $rvstring) if $CPAN::DEBUG; } return ( ($l ne "undef") <=> ($r ne "undef") || $lvstring cmp $rvstring || $l <=> $r || $l cmp $r ); } sub vgt { my($self,$l,$r) = @_; $self->vcmp($l,$r) > 0; } sub vlt { my($self,$l,$r) = @_; $self->vcmp($l,$r) < 0; } sub vge { my($self,$l,$r) = @_; $self->vcmp($l,$r) >= 0; } sub vle { my($self,$l,$r) = @_; $self->vcmp($l,$r) <= 0; } sub vstring { my($self,$n) = @_; $n =~ s/^v// or die "CPAN::Version::vstring() called with invalid arg [$n]"; pack "U*", split /\./, $n; } # vv => visible vstring sub float2vv { my($self,$n) = @_; my($rev) = int($n); $rev ||= 0; my($mantissa) = $n =~ /\.(\d{1,12})/; # limit to 12 digits to limit # architecture influence $mantissa ||= 0; $mantissa .= "0" while length($mantissa)%3; my $ret = "v" . $rev; while ($mantissa) { $mantissa =~ s/(\d{1,3})// or die "Panic: length>0 but not a digit? mantissa[$mantissa]"; $ret .= ".".int($1); } # warn "n[$n]ret[$ret]"; $ret =~ s/(\.0)+/.0/; # v1.0.0 => v1.0 $ret; } sub readable { my($self,$n) = @_; $n =~ /^([\w\-\+\.]+)/; return $1 if defined $1 && length($1)>0; # if the first user reaches version v43, he will be treated as "+". # We'll have to decide about a new rule here then, depending on what # will be the prevailing versioning behavior then. if ($] < 5.006) { # or whenever v-strings were introduced # we get them wrong anyway, whatever we do, because 5.005 will # have already interpreted 0.2.4 to be "0.24". So even if he # indexer sends us something like "v0.2.4" we compare wrongly. # And if they say v1.2, then the old perl takes it as "v12" if (defined $CPAN::Frontend) { $CPAN::Frontend->mywarn("Suspicious version string seen [$n]\n"); } else { warn("Suspicious version string seen [$n]\n"); } return $n; } my $better = sprintf "v%vd", $n; CPAN->debug("n[$n] better[$better]") if $CPAN::DEBUG; return $better; } 1; __END__ =head1 NAME CPAN::Version - utility functions to compare CPAN versions =head1 SYNOPSIS use CPAN::Version; CPAN::Version->vgt("1.1","1.1.1"); # 1 bc. 1.1 > 1.001001 CPAN::Version->vlt("1.1","1.1"); # 0 bc. 1.1 not < 1.1 CPAN::Version->vcmp("1.1","1.1.1"); # 1 bc. first is larger CPAN::Version->vcmp("1.1.1","1.1"); # -1 bc. first is smaller CPAN::Version->readable(v1.2.3); # "v1.2.3" CPAN::Version->vstring("v1.2.3"); # v1.2.3 CPAN::Version->float2vv(1.002003); # "v1.2.3" =head1 DESCRIPTION This module mediates between some version that perl sees in a package and the version that is published by the CPAN indexer. It's only written as a helper module for both CPAN.pm and CPANPLUS.pm. As it stands it predates version.pm but has the same goal: make version strings visible and comparable. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut # Local Variables: # mode: cperl # cperl-indent-level: 4 # End: PK2]C Plugin.pmnu6$package CPAN::Plugin; use strict; use warnings; our $VERSION = '0.97'; require CPAN; ###################################################################### sub new { # ; my ($class, %params) = @_; my $self = +{ (ref $class ? (%$class) : ()), %params, }; $self = bless $self, ref $class ? ref $class : $class; unless (ref $class) { local $_; no warnings 'once'; $CPAN::META->use_inst ($_) for $self->plugin_requires; } $self; } ###################################################################### sub plugin_requires { # ; } ###################################################################### sub distribution_object { # ; my ($self) = @_; $self->{distribution_object}; } ###################################################################### sub distribution { # ; my ($self) = @_; my $distribution = $self->distribution_object->id; CPAN::Shell->expand("Distribution",$distribution) or $self->frontend->mydie("Unknowns distribution '$distribution'\n"); } ###################################################################### sub distribution_info { # ; my ($self) = @_; CPAN::DistnameInfo->new ($self->distribution->id); } ###################################################################### sub build_dir { # ; my ($self) = @_; my $build_dir = $self->distribution->{build_dir} or $self->frontend->mydie("Distribution has not been built yet, cannot proceed"); } ###################################################################### sub is_xs { # my ($self) = @_; my @xs = glob File::Spec->catfile ($self->build_dir, '*.xs'); # quick try unless (@xs) { require ExtUtils::Manifest; my $manifest_file = File::Spec->catfile ($self->build_dir, "MANIFEST"); my $manifest = ExtUtils::Manifest::maniread($manifest_file); @xs = grep /\.xs$/, keys %$manifest; } scalar @xs; } ###################################################################### package CPAN::Plugin; 1; __END__ =pod =head1 NAME CPAN::Plugin - Base class for CPAN shell extensions =head1 SYNOPSIS package CPAN::Plugin::Flurb; use parent 'CPAN::Plugin'; sub post_test { my ($self, $distribution_object) = @_; $self = $self->new (distribution_object => $distribution_object); ...; } =head1 DESCRIPTION =head2 Alpha Status The plugin system in the CPAN shell was introduced in version 2.07 and is still considered experimental. =head2 How Plugins work? See L. =head1 METHODS =head2 plugin_requires returns list of packages given plugin requires for functionality. This list is evaluated using C<< CPAN->use_inst >> method. =head2 distribution_object Get current distribution object. =head2 distribution =head2 distribution_info =head2 build_dir Simple delegatees for misc parameters derived from distribution =head2 is_xs Predicate to detect whether package contains XS. =head1 AUTHOR Branislav Zahradnik =cut PK2]< Kwalify/distroprefs.ddnu6$$VAR1 = { "mapping" => { "comment" => { "type" => "text" }, "cpanconfig" => { "mapping" => { "=" => { "type" => "text" } }, "type" => "map" }, "depends" => { "mapping" => { "build_requires" => { "mapping" => { "=" => { "type" => "text" } }, "type" => "map" }, "configure_requires" => {}, "requires" => {} }, "type" => "map" }, "disabled" => { "enum" => [ 0, 1 ], "type" => "int" }, "features" => { "sequence" => [ { "type" => "text" } ], "type" => "seq" }, "goto" => { "type" => "text" }, "install" => { "mapping" => { "args" => { "sequence" => [ { "type" => "text" } ], "type" => "seq" }, "commandline" => { "type" => "text" }, "eexpect" => { "mapping" => { "mode" => { "enum" => [ "deterministic", "anyorder" ], "type" => "text" }, "reuse" => { "type" => "int" }, "talk" => { "sequence" => [ { "type" => "text" } ], "type" => "seq" }, "timeout" => { "type" => "number" } }, "type" => "map" }, "env" => { "mapping" => { "=" => { "type" => "text" } }, "type" => "map" }, "expect" => { "sequence" => [ { "type" => "text" } ], "type" => "seq" } }, "type" => "map" }, "make" => {}, "match" => { "mapping" => { "distribution" => { "type" => "text" }, "env" => { "mapping" => { "=" => { "type" => "text" } }, "type" => "map" }, "module" => { "type" => "text" }, "perl" => { "type" => "text" }, "perlconfig" => {} }, "type" => "map" }, "patches" => { "sequence" => [ { "type" => "text" } ], "type" => "seq" }, "pl" => {}, "reminder" => { "type" => "text" }, "test" => {} }, "type" => "map" }; $VAR1->{"mapping"}{"depends"}{"mapping"}{"configure_requires"} = $VAR1->{"mapping"}{"depends"}{"mapping"}{"build_requires"}; $VAR1->{"mapping"}{"depends"}{"mapping"}{"requires"} = $VAR1->{"mapping"}{"depends"}{"mapping"}{"build_requires"}; $VAR1->{"mapping"}{"make"} = $VAR1->{"mapping"}{"install"}; $VAR1->{"mapping"}{"match"}{"mapping"}{"perlconfig"} = $VAR1->{"mapping"}{"match"}{"mapping"}{"env"}; $VAR1->{"mapping"}{"pl"} = $VAR1->{"mapping"}{"install"}; $VAR1->{"mapping"}{"test"} = $VAR1->{"mapping"}{"install"}; PK2]w)#99Kwalify/distroprefs.ymlnu6$--- type: map mapping: comment: type: text depends: type: map mapping: configure_requires: &requires_common type: map mapping: =: type: text build_requires: *requires_common requires: *requires_common match: type: map mapping: distribution: type: text module: type: text perl: type: text perlconfig: &matchhash_common type: map mapping: =: type: text env: *matchhash_common install: &args_env_expect type: map mapping: args: type: seq sequence: - type: text commandline: type: text env: type: map mapping: =: type: text expect: type: seq sequence: - type: text eexpect: type: map mapping: mode: type: text enum: - deterministic - anyorder timeout: type: number reuse: type: int talk: type: seq sequence: - type: text make: *args_env_expect pl: *args_env_expect test: *args_env_expect patches: type: seq sequence: - type: text disabled: type: int enum: - 0 - 1 goto: type: text cpanconfig: type: map mapping: =: type: text features: type: seq sequence: - type: text reminder: type: text PK2]z¤!!Plugin/Specfile.pmnu6$=head1 NAME CPAN::Plugin::Specfile - Proof of concept implementation of a trivial CPAN::Plugin =head1 SYNOPSIS # once in the cpan shell o conf plugin_list push CPAN::Plugin::Specfile # make permanent o conf commit # any time in the cpan shell to write a spec file test Acme::Meta # disable # if it is the last in plugin_list: o conf plugin_list pop # otherwise, determine the index to splice: o conf plugin_list # and then use splice, e.g. to splice position 3: o conf plugin_list splice 3 1 =head1 DESCRIPTION Implemented as a post-test hook, this plugin writes a specfile after every successful test run. The content is also written to the terminal. As a side effect, the timestamps of the written specfiles reflect the linear order of all dependencies. B This code is just a small demo how to use the plugin system of the CPAN shell, not a full fledged spec file writer. Do not expect new features in this plugin. =head2 OPTIONS The target directory to store the spec files in can be set using C as in o conf plugin_list push CPAN::Plugin::Specfile=dir,/tmp/specfiles-000042 The default directory for this is the C directory in the I directory. =head1 AUTHOR Andreas Koenig , Branislav Zahradnik =cut package CPAN::Plugin::Specfile; our $VERSION = '0.02'; use File::Path; use File::Spec; sub __accessor { my ($class, $key) = @_; no strict 'refs'; *{$class . '::' . $key} = sub { my $self = shift; if (@_) { $self->{$key} = shift; } return $self->{$key}; }; } BEGIN { __PACKAGE__->__accessor($_) for qw(dir dir_default) } sub new { my($class, @rest) = @_; my $self = bless {}, $class; while (my($arg,$val) = splice @rest, 0, 2) { $self->$arg($val); } $self->dir_default(File::Spec->catdir($CPAN::Config->{cpan_home},"plugins",__PACKAGE__)); $self; } sub post_test { my $self = shift; my $distribution_object = shift; my $distribution = $distribution_object->pretty_id; unless ($CPAN::META->has_inst("CPAN::DistnameInfo")){ $CPAN::Frontend->mydie("CPAN::DistnameInfo not installed; cannot continue"); } my $d = CPAN::Shell->expand("Distribution",$distribution) or $CPAN::Frontend->mydie("Unknowns distribution '$distribution'\n"); my $build_dir = $d->{build_dir} or $CPAN::Frontend->mydie("Distribution has not been built yet, cannot proceed"); my %contains = map {($_ => undef)} $d->containsmods; my @m; my $width = 16; my $header = sub { my($header,$value) = @_; push @m, sprintf("%-s:%*s%s\n", $header, $width-length($header), "", $value); }; my $dni = CPAN::DistnameInfo->new($distribution); my $dist = $dni->dist; my $summary = CPAN::Shell->_guess_manpage($d,\%contains,$dist); $header->("Name", "perl-$dist"); my $version = $dni->version; $header->("Version", $version); $header->("Release", "1%{?dist}"); #Summary: Template processing system #Group: Development/Libraries #License: GPL+ or Artistic #URL: http://www.template-toolkit.org/ #Source0: http://search.cpan.org/CPAN/authors/id/A/AB/ABW/Template-Toolkit-%{version}.tar.gz #Patch0: Template-2.22-SREZIC-01.patch #BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) for my $h_tuple ([Summary => $summary], [Group => "Development/Libraries"], [License =>], [URL =>], [BuildRoot => "%{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)"], [Requires => "perl(:MODULE_COMPAT_%(eval \"`%{__perl} -V:version`\"; echo \$version))"], ) { my($h,$v) = @$h_tuple; $v = "unknown" unless defined $v; $header->($h, $v); } $header->("Source0", sprintf( "http://search.cpan.org/CPAN/authors/id/%s/%s/%s", substr($distribution,0,1), substr($distribution,0,2), $distribution )); require POSIX; my @xs = glob "$build_dir/*.xs"; # quick try unless (@xs) { require ExtUtils::Manifest; my $manifest_file = "$build_dir/MANIFEST"; my $manifest = ExtUtils::Manifest::maniread($manifest_file); @xs = grep /\.xs$/, keys %$manifest; } if (! @xs) { $header->('BuildArch', 'noarch'); } for my $k (sort keys %contains) { my $m = CPAN::Shell->expand("Module",$k); my $v = $contains{$k} = $m->cpan_version; my $vspec = $v eq "undef" ? "" : " = $v"; $header->("Provides", "perl($k)$vspec"); } if (my $prereq_pm = $d->{prereq_pm}) { my %req; for my $reqkey (keys %$prereq_pm) { while (my($k,$v) = each %{$prereq_pm->{$reqkey}}) { $req{$k} = $v; } } if (-e "$build_dir/Build.PL" && ! exists $req{"Module::Build"}) { $req{"Module::Build"} = 0; } for my $k (sort keys %req) { next if $k eq "perl"; my $v = $req{$k}; my $vspec = defined $v && length $v && $v > 0 ? " >= $v" : ""; $header->(BuildRequires => "perl($k)$vspec"); next if $k =~ /^(Module::Build)$/; # MB is always only a # BuildRequires; if we # turn it into a # Requires, then we # would have to make it # a BuildRequires # everywhere we depend # on *one* MB built # module. $header->(Requires => "perl($k)$vspec"); } } push @m, "\n%define _use_internal_dependency_generator 0 %define __find_requires %{nil} %define __find_provides %{nil} "; push @m, "\n%description\n%{summary}.\n"; push @m, "\n%prep\n%setup -q -n $dist-%{version}\n"; if (-e "$build_dir/Build.PL") { # see http://www.redhat.com/archives/rpm-list/2002-July/msg00110.html about RPM_BUILD_ROOT vs %{buildroot} push @m, <<'EOF'; %build %{__perl} Build.PL --installdirs=vendor --libdoc installvendorman3dir ./Build %install rm -rf $RPM_BUILD_ROOT ./Build install destdir=$RPM_BUILD_ROOT create_packlist=0 find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null \; %{_fixperms} $RPM_BUILD_ROOT/* %check ./Build test EOF } elsif (-e "$build_dir/Makefile.PL") { push @m, <<'EOF'; %build %{__perl} Makefile.PL INSTALLDIRS=vendor make %{?_smp_mflags} %install rm -rf $RPM_BUILD_ROOT make pure_install DESTDIR=$RPM_BUILD_ROOT find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null ';' %{_fixperms} $RPM_BUILD_ROOT/* %check make test EOF } else { $CPAN::Frontend->mydie("'$distribution' has neither a Build.PL nor a Makefile.PL\n"); } push @m, "\n%clean\nrm -rf \$RPM_BUILD_ROOT\n"; my $vendorlib = @xs ? "vendorarch" : "vendorlib"; my $date = POSIX::strftime("%a %b %d %Y", gmtime); my @doc = grep { -e "$build_dir/$_" } qw(README Changes); my $exe_stanza = "\n"; if (my $exe_files = $d->_exe_files) { if (@$exe_files) { $exe_stanza = "%{_mandir}/man1/*.1*\n"; for my $e (@$exe_files) { unless (CPAN->has_inst("File::Basename")) { $CPAN::Frontend->mydie("File::Basename not installed, cannot continue"); } my $basename = File::Basename::basename($e); $exe_stanza .= "/usr/bin/$basename\n"; } } } push @m, < - $version-1 - autogenerated by CPAN::Plugin::Specfile() EOF my $ret = join "", @m; $CPAN::Frontend->myprint($ret); my $target_dir = $self->dir || $self->dir_default; File::Path::mkpath($target_dir); my $outfile = File::Spec->catfile($target_dir, "perl-$dist.spec"); open my $specout, ">", $outfile or $CPAN::Frontend->mydie("Could not open >$outfile: $!"); print $specout $ret; $CPAN::Frontend->myprint("Wrote $outfile"); $ret; } 1; PK2]2]Nox.pmnu6$package CPAN::Nox; use strict; use vars qw($VERSION @EXPORT); BEGIN{ $CPAN::Suppress_readline=1 unless defined $CPAN::term; } use Exporter (); @CPAN::ISA = ('Exporter'); use CPAN; $VERSION = "5.5001"; $CPAN::META->has_inst('Digest::MD5','no'); $CPAN::META->has_inst('LWP','no'); $CPAN::META->has_inst('Compress::Zlib','no'); @EXPORT = @CPAN::EXPORT; *AUTOLOAD = \&CPAN::AUTOLOAD; 1; __END__ =head1 NAME CPAN::Nox - Wrapper around CPAN.pm without using any XS module =head1 SYNOPSIS Interactive mode: perl -MCPAN::Nox -e shell; =head1 DESCRIPTION This package has the same functionality as CPAN.pm, but tries to prevent the usage of compiled extensions during its own execution. Its primary purpose is a rescue in case you upgraded perl and broke binary compatibility somehow. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L =cut PK2]((HTTP/Client.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::HTTP::Client; use strict; use vars qw(@ISA); use CPAN::HTTP::Credentials; use HTTP::Tiny 0.005; $CPAN::HTTP::Client::VERSION = $CPAN::HTTP::Client::VERSION = "1.9602"; # CPAN::HTTP::Client is adapted from parts of cpanm by Tatsuhiko Miyagawa # and parts of LWP by Gisle Aas sub new { my $class = shift; my %args = @_; for my $k ( keys %args ) { $args{$k} = '' unless defined $args{$k}; } $args{no_proxy} = [split(",", $args{no_proxy}) ] if $args{no_proxy}; return bless \%args, $class; } # This executes a request with redirection (up to 5) and returns the # response structure generated by HTTP::Tiny # # If authentication fails, it will attempt to get new authentication # information and repeat up to 5 times sub mirror { my($self, $uri, $path) = @_; my $want_proxy = $self->_want_proxy($uri); my $http = HTTP::Tiny->new( verify_SSL => 1, $want_proxy ? (proxy => $self->{proxy}) : () ); my ($response, %headers); my $retries = 0; while ( $retries++ < 5 ) { $response = $http->mirror( $uri, $path, {headers => \%headers} ); if ( $response->{status} eq '401' ) { last unless $self->_get_auth_params( $response, 'non_proxy' ); } elsif ( $response->{status} eq '407' ) { last unless $self->_get_auth_params( $response, 'proxy' ); } else { last; # either success or failure } my %headers = ( $self->_auth_headers( $uri, 'non_proxy' ), ( $want_proxy ? $self->_auth_headers($uri, 'proxy') : () ), ); } return $response; } sub _want_proxy { my ($self, $uri) = @_; return unless $self->{proxy}; my($host) = $uri =~ m|://([^/:]+)|; return ! grep { $host =~ /\Q$_\E$/ } @{ $self->{no_proxy} || [] }; } # Generates the authentication headers for a given mode # C is 'proxy' or 'non_proxy' # C<_${mode}_type> is 'basic' or 'digest' # C<_${mode}_params> will be the challenge parameters from the 401/407 headers sub _auth_headers { my ($self, $uri, $mode) = @_; # Get names for our mode-specific attributes my ($type_key, $param_key) = map {"_" . $mode . $_} qw/_type _params/; # If _prepare_auth has not been called, we can't prepare headers return unless $self->{$type_key}; # Get user credentials for mode my $cred_method = "get_" . ($mode ? "proxy" : "non_proxy") ."_credentials"; my ($user, $pass) = CPAN::HTTP::Credentials->$cred_method; # Generate the header for the mode & type my $header = $mode eq 'proxy' ? 'Proxy-Authorization' : 'Authorization'; my $value_method = "_" . $self->{$type_key} . "_auth"; my $value = $self->$value_method($user, $pass, $self->{$param_key}, $uri); # If we didn't get a value, we didn't have the right modules available return $value ? ( $header, $value ) : (); } # Extract authentication parameters from headers, but clear any prior # credentials if we failed (so we might prompt user for password again) sub _get_auth_params { my ($self, $response, $mode) = @_; my $prefix = $mode eq 'proxy' ? 'Proxy' : 'WWW'; my ($type_key, $param_key) = map {"_" . $mode . $_} qw/_type _params/; if ( ! $response->{success} ) { # auth failed my $method = "clear_${mode}_credentials"; CPAN::HTTP::Credentials->$method; delete $self->{$_} for $type_key, $param_key; } ($self->{$type_key}, $self->{$param_key}) = $self->_get_challenge( $response, "${prefix}-Authenticate"); return $self->{$type_key}; } # Extract challenge type and parameters for a challenge list sub _get_challenge { my ($self, $response, $auth_header) = @_; my $auth_list = $response->{headers}(lc $auth_header); return unless defined $auth_list; $auth_list = [$auth_list] unless ref $auth_list; for my $challenge (@$auth_list) { $challenge =~ tr/,/;/; # "," is used to separate auth-params!! ($challenge) = $self->split_header_words($challenge); my $scheme = shift(@$challenge); shift(@$challenge); # no value $challenge = { @$challenge }; # make rest into a hash unless ($scheme =~ /^(basic|digest)$/) { next; # bad scheme } $scheme = $1; # untainted now return ($scheme, $challenge); } return; } # Generate a basic authentication header value sub _basic_auth { my ($self, $user, $pass) = @_; unless ( $CPAN::META->has_usable('MIME::Base64') ) { $CPAN::Frontend->mywarn( "MIME::Base64 is required for 'Basic' style authentication" ); return; } return "Basic " . MIME::Base64::encode_base64("$user\:$pass", q{}); } # Generate a digest authentication header value sub _digest_auth { my ($self, $user, $pass, $auth_param, $uri) = @_; unless ( $CPAN::META->has_usable('Digest::MD5') ) { $CPAN::Frontend->mywarn( "Digest::MD5 is required for 'Digest' style authentication" ); return; } my $nc = sprintf "%08X", ++$self->{_nonce_count}{$auth_param->{nonce}}; my $cnonce = sprintf "%8x", time; my ($path) = $uri =~ m{^\w+?://[^/]+(/.*)$}; $path = "/" unless defined $path; my $md5 = Digest::MD5->new; my(@digest); $md5->add(join(":", $user, $auth_param->{realm}, $pass)); push(@digest, $md5->hexdigest); $md5->reset; push(@digest, $auth_param->{nonce}); if ($auth_param->{qop}) { push(@digest, $nc, $cnonce, ($auth_param->{qop} =~ m|^auth[,;]auth-int$|) ? 'auth' : $auth_param->{qop}); } $md5->add(join(":", 'GET', $path)); push(@digest, $md5->hexdigest); $md5->reset; $md5->add(join(":", @digest)); my($digest) = $md5->hexdigest; $md5->reset; my %resp = map { $_ => $auth_param->{$_} } qw(realm nonce opaque); @resp{qw(username uri response algorithm)} = ($user, $path, $digest, "MD5"); if (($auth_param->{qop} || "") =~ m|^auth([,;]auth-int)?$|) { @resp{qw(qop cnonce nc)} = ("auth", $cnonce, $nc); } my(@order) = qw(username realm qop algorithm uri nonce nc cnonce response opaque); my @pairs; for (@order) { next unless defined $resp{$_}; push(@pairs, "$_=" . qq("$resp{$_}")); } my $auth_value = "Digest " . join(", ", @pairs); return $auth_value; } # split_header_words adapted from HTTP::Headers::Util sub split_header_words { my ($self, @words) = @_; my @res = $self->_split_header_words(@words); for my $arr (@res) { for (my $i = @$arr - 2; $i >= 0; $i -= 2) { $arr->[$i] = lc($arr->[$i]); } } return @res; } sub _split_header_words { my($self, @val) = @_; my @res; for (@val) { my @cur; while (length) { if (s/^\s*(=*[^\s=;,]+)//) { # 'token' or parameter 'attribute' push(@cur, $1); # a quoted value if (s/^\s*=\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"//) { my $val = $1; $val =~ s/\\(.)/$1/g; push(@cur, $val); # some unquoted value } elsif (s/^\s*=\s*([^;,\s]*)//) { my $val = $1; $val =~ s/\s+$//; push(@cur, $val); # no value, a lone token } else { push(@cur, undef); } } elsif (s/^\s*,//) { push(@res, [@cur]) if @cur; @cur = (); } elsif (s/^\s*;// || s/^\s+//) { # continue } else { die "This should not happen: '$_'"; } } push(@res, \@cur) if @cur; } @res; } 1; PK2]3yI I HTTP/Credentials.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::HTTP::Credentials; use strict; use vars qw($USER $PASSWORD $PROXY_USER $PROXY_PASSWORD); $CPAN::HTTP::Credentials::VERSION = $CPAN::HTTP::Credentials::VERSION = "1.9601"; sub clear_credentials { clear_non_proxy_credentials(); clear_proxy_credentials(); } sub clear_non_proxy_credentials { undef $USER; undef $PASSWORD; } sub clear_proxy_credentials { undef $PROXY_USER; undef $PROXY_PASSWORD; } sub get_proxy_credentials { my $self = shift; if ($PROXY_USER && $PROXY_PASSWORD) { return ($PROXY_USER, $PROXY_PASSWORD); } if ( defined $CPAN::Config->{proxy_user} && $CPAN::Config->{proxy_user} ) { $PROXY_USER = $CPAN::Config->{proxy_user}; $PROXY_PASSWORD = $CPAN::Config->{proxy_pass} || ""; return ($PROXY_USER, $PROXY_PASSWORD); } my $username_prompt = "\nProxy authentication needed! (Note: to permanently configure username and password run o conf proxy_user your_username o conf proxy_pass your_password )\nUsername:"; ($PROXY_USER, $PROXY_PASSWORD) = _get_username_and_password_from_user($username_prompt); return ($PROXY_USER,$PROXY_PASSWORD); } sub get_non_proxy_credentials { my $self = shift; if ($USER && $PASSWORD) { return ($USER, $PASSWORD); } if ( defined $CPAN::Config->{username} ) { $USER = $CPAN::Config->{username}; $PASSWORD = $CPAN::Config->{password} || ""; return ($USER, $PASSWORD); } my $username_prompt = "\nAuthentication needed! (Note: to permanently configure username and password run o conf username your_username o conf password your_password )\nUsername:"; ($USER, $PASSWORD) = _get_username_and_password_from_user($username_prompt); return ($USER,$PASSWORD); } sub _get_username_and_password_from_user { my $username_message = shift; my ($username,$password); ExtUtils::MakeMaker->import(qw(prompt)); $username = prompt($username_message); if ($CPAN::META->has_inst("Term::ReadKey")) { Term::ReadKey::ReadMode("noecho"); } else { $CPAN::Frontend->mywarn( "Warning: Term::ReadKey seems not to be available, your password will be echoed to the terminal!\n" ); } $password = prompt("Password:"); if ($CPAN::META->has_inst("Term::ReadKey")) { Term::ReadKey::ReadMode("restore"); } $CPAN::Frontend->myprint("\n\n"); return ($username,$password); } 1; PK2]bJGJG Mirrors.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: =head1 NAME CPAN::Mirrors - Get CPAN mirror information and select a fast one =head1 SYNOPSIS use CPAN::Mirrors; my $mirrors = CPAN::Mirrors->new( $mirrored_by_file ); my $seen = {}; my $best_continent = $mirrors->find_best_continents( { seen => $seen } ); my @mirrors = $mirrors->get_mirrors_by_continents( $best_continent ); my $callback = sub { my( $m ) = @_; printf "%s = %s\n", $m->hostname, $m->rtt }; $mirrors->get_mirrors_timings( \@mirrors, $seen, $callback, %args ); @mirrors = sort { $a->rtt <=> $b->rtt } @mirrors; print "Best mirrors are ", map( { $_->rtt } @mirrors[0..3] ), "\n"; =head1 DESCRIPTION =over =cut package CPAN::Mirrors; use strict; use vars qw($VERSION $urllist $silent); $VERSION = "2.27"; use Carp; use FileHandle; use Fcntl ":flock"; use Net::Ping (); use CPAN::Version; =item new( LOCAL_FILE_NAME ) Create a new CPAN::Mirrors object from LOCAL_FILE_NAME. This file should look like that in http://www.cpan.org/MIRRORED.BY . =cut sub new { my ($class, $file) = @_; croak "CPAN::Mirrors->new requires a filename" unless defined $file; croak "The file [$file] was not found" unless -e $file; my $self = bless { mirrors => [], geography => {}, }, $class; $self->parse_mirrored_by( $file ); return $self; } sub parse_mirrored_by { my ($self, $file) = @_; my $handle = FileHandle->new; $handle->open($file) or croak "Couldn't open $file: $!"; flock $handle, LOCK_SH; $self->_parse($file,$handle); flock $handle, LOCK_UN; $handle->close; } =item continents() Return a list of continents based on those defined in F. =cut sub continents { my ($self) = @_; return sort keys %{$self->{geography} || {}}; } =item countries( [CONTINENTS] ) Return a list of countries based on those defined in F. It only returns countries for the continents you specify (as defined in C). If you don't specify any continents, it returns all of the countries listed in F. =cut sub countries { my ($self, @continents) = @_; @continents = $self->continents unless @continents; my @countries; for my $c (@continents) { push @countries, sort keys %{ $self->{geography}{$c} || {} }; } return @countries; } =item mirrors( [COUNTRIES] ) Return a list of mirrors based on those defined in F. It only returns mirrors for the countries you specify (as defined in C). If you don't specify any countries, it returns all of the mirrors listed in F. =cut sub mirrors { my ($self, @countries) = @_; return @{$self->{mirrors}} unless @countries; my %wanted = map { $_ => 1 } @countries; my @found; for my $m (@{$self->{mirrors}}) { push @found, $m if exists $wanted{$m->country}; } return @found; } =item get_mirrors_by_countries( [COUNTRIES] ) A more sensible synonym for mirrors. =cut sub get_mirrors_by_countries { &mirrors } =item get_mirrors_by_continents( [CONTINENTS] ) Return a list of mirrors for all of continents you specify. If you don't specify any continents, it returns all of the mirrors. You can specify a single continent or an array reference of continents. =cut sub get_mirrors_by_continents { my ($self, $continents ) = @_; $continents = [ $continents ] unless ref $continents; eval { $self->mirrors( $self->get_countries_by_continents( @$continents ) ); }; } =item get_countries_by_continents( [CONTINENTS] ) A more sensible synonym for countries. =cut sub get_countries_by_continents { &countries } =item default_mirror Returns the default mirror, http://www.cpan.org/ . This mirror uses dynamic DNS to give a close mirror. =cut sub default_mirror { CPAN::Mirrored::By->new({ http => 'http://www.cpan.org/'}); } =item best_mirrors C checks for the best mirrors based on the list of continents you pass, or, without that, all continents, as defined by C. It pings each mirror, up to the value of C. In list context, it returns up to C mirrors. In scalar context, it returns the single best mirror. Arguments how_many - the number of mirrors to return. Default: 1 callback - a callback for find_best_continents verbose - true or false on all the whining and moaning. Default: false continents - an array ref of the continents to check external_ping - if true, use external ping via Net::Ping::External. Default: false If you don't specify the continents, C calls C to get the list of continents to check. If you don't have L v2.13 or later, needed for timings, this returns the default mirror. C should be set and then C needs to be installed, if the local network has a transparent proxy. =cut sub best_mirrors { my ($self, %args) = @_; my $how_many = $args{how_many} || 1; my $callback = $args{callback}; my $verbose = defined $args{verbose} ? $args{verbose} : 0; my $continents = $args{continents} || []; $continents = [$continents] unless ref $continents; $args{external_ping} = 0 unless defined $args{external_ping}; my $external_ping = $args{external_ping}; # Old Net::Ping did not do timings at all my $min_version = '2.13'; unless( CPAN::Version->vgt(Net::Ping->VERSION, $min_version) ) { carp sprintf "Net::Ping version is %s (< %s). Returning %s", Net::Ping->VERSION, $min_version, $self->default_mirror; return $self->default_mirror; } my $seen = {}; if ( ! @$continents ) { print "Searching for the best continent ...\n" if $verbose; my @best_continents = $self->find_best_continents( seen => $seen, verbose => $verbose, callback => $callback, external_ping => $external_ping, ); # Only add enough continents to find enough mirrors my $count = 0; for my $continent ( @best_continents ) { push @$continents, $continent; $count += $self->mirrors( $self->countries($continent) ); last if $count >= $how_many; } } return $self->default_mirror unless @$continents; print "Scanning " . join(", ", @$continents) . " ...\n" if $verbose; my $trial_mirrors = $self->get_n_random_mirrors_by_continents( 3 * $how_many, $continents->[0] ); my $timings = $self->get_mirrors_timings( $trial_mirrors, $seen, $callback, %args, ); return $self->default_mirror unless @$timings; $how_many = @$timings if $how_many > @$timings; return wantarray ? @{$timings}[0 .. $how_many-1] : $timings->[0]; } =item get_n_random_mirrors_by_continents( N, [CONTINENTS] ) Returns up to N random mirrors for the specified continents. Specify the continents as an array reference. =cut sub get_n_random_mirrors_by_continents { my( $self, $n, $continents ) = @_; $n ||= 3; $continents = [ $continents ] unless ref $continents; if ( $n <= 0 ) { return wantarray ? () : []; } my @long_list = $self->get_mirrors_by_continents( $continents ); if ( $n eq '*' or $n > @long_list ) { return wantarray ? @long_list : \@long_list; } @long_list = map {$_->[0]} sort {$a->[1] <=> $b->[1]} map {[$_, rand]} @long_list; splice @long_list, $n; # truncate \@long_list; } =item get_mirrors_timings( MIRROR_LIST, SEEN, CALLBACK, %ARGS ); Pings the listed mirrors and returns a list of mirrors sorted in ascending ping times. C is an anonymous array of C objects to ping. The optional argument C is a hash reference used to track the mirrors you've already pinged. The optional argument C is a subroutine reference to call after each ping. It gets the C object after each ping. =cut sub get_mirrors_timings { my( $self, $mirror_list, $seen, $callback, %args ) = @_; $seen = {} unless defined $seen; croak "The mirror list argument must be an array reference" unless ref $mirror_list eq ref []; croak "The seen argument must be a hash reference" unless ref $seen eq ref {}; croak "callback must be a subroutine" if( defined $callback and ref $callback ne ref sub {} ); my $timings = []; for my $m ( @$mirror_list ) { $seen->{$m->hostname} = $m; next unless eval{ $m->http }; if( $self->_try_a_ping( $seen, $m, ) ) { my $ping = $m->ping(%args); next unless defined $ping; # printf "m %s ping %s\n", $m, $ping; push @$timings, $m; $callback->( $m ) if $callback; } else { push @$timings, $seen->{$m->hostname} if defined $seen->{$m->hostname}->rtt; } } my @best = sort { if( defined $a->rtt and defined $b->rtt ) { $a->rtt <=> $b->rtt } elsif( defined $a->rtt and ! defined $b->rtt ) { return -1; } elsif( ! defined $a->rtt and defined $b->rtt ) { return 1; } elsif( ! defined $a->rtt and ! defined $b->rtt ) { return 0; } } @$timings; return wantarray ? @best : \@best; } =item find_best_continents( HASH_REF ); C goes through each continent and pings C random mirrors on that continent. It then orders the continents by ascending median ping time. In list context, it returns the ordered list of continent. In scalar context, it returns the same list as an anonymous array. Arguments: n - the number of hosts to ping for each continent. Default: 3 seen - a hashref of cached hostname ping times verbose - true or false for noisy or quiet. Default: false callback - a subroutine to run after each ping. ping_cache_limit - how long, in seconds, to reuse previous ping times. Default: 1 day The C hash has hostnames as keys and anonymous arrays as values. The anonymous array is a triplet of a C object, a ping time, and the epoch time for the measurement. The callback subroutine gets the C object, the ping time, and measurement time (the same things in the C hashref) as arguments. C doesn't care what the callback does and ignores the return value. With a low value for C, a single mirror might skew the results enough to choose a worse continent. If you have that problem, try a larger value. =cut sub find_best_continents { my ($self, %args) = @_; $args{n} ||= 3; $args{verbose} = 0 unless defined $args{verbose}; $args{seen} = {} unless defined $args{seen}; croak "The seen argument must be a hash reference" unless ref $args{seen} eq ref {}; $args{ping_cache_limit} = 24 * 60 * 60 unless defined $args{ping_cache_limit}; croak "callback must be a subroutine" if( defined $args{callback} and ref $args{callback} ne ref sub {} ); my %medians; CONT: for my $c ( $self->continents ) { my @mirrors = $self->mirrors( $self->countries($c) ); printf "Testing %s (%d mirrors)\n", $c, scalar @mirrors if $args{verbose}; next CONT unless @mirrors; my $n = (@mirrors < $args{n}) ? @mirrors : $args{n}; my @tests; my $tries = 0; RANDOM: while ( @mirrors && @tests < $n && $tries++ < 15 ) { my $m = splice( @mirrors, int(rand(@mirrors)), 1 ); if( $self->_try_a_ping( $args{seen}, $m, $args{ping_cache_limit} )) { $self->get_mirrors_timings( [ $m ], $args{seen}, $args{callback}, %args, ); next RANDOM unless defined $args{seen}{$m->hostname}->rtt; } printf "(%s -> %0.2f ms)", $m->hostname, join ' ', 1000 * $args{seen}{$m->hostname}->rtt if $args{verbose}; push @tests, $args{seen}{$m->hostname}->rtt; } my $median = $self->_get_median_ping_time( \@tests, $args{verbose} ); $medians{$c} = $median if defined $median; } my @best_cont = sort { $medians{$a} <=> $medians{$b} } keys %medians; if ( $args{verbose} ) { print "Median result by continent:\n"; if ( @best_cont ) { for my $c ( @best_cont ) { printf( " %7.2f ms %s\n", $medians{$c}*1000, $c ); } } else { print " **** No results found ****\n" } } return wantarray ? @best_cont : $best_cont[0]; } # retry if sub _try_a_ping { my ($self, $seen, $mirror, $ping_cache_limit ) = @_; ( ! exists $seen->{$mirror->hostname} or ! defined $seen->{$mirror->hostname}->rtt or ! defined $ping_cache_limit or time - $seen->{$mirror->hostname}->ping_time > $ping_cache_limit ) } sub _get_median_ping_time { my ($self, $tests, $verbose ) = @_; my @sorted = sort { $a <=> $b } @$tests; my $median = do { if ( @sorted == 0 ) { undef } elsif ( @sorted == 1 ) { $sorted[0] } elsif ( @sorted % 2 ) { $sorted[ int(@sorted / 2) ] } else { my $mid_high = int(@sorted/2); ($sorted[$mid_high-1] + $sorted[$mid_high])/2; } }; if ($verbose){ if ($median) { printf " => median time: %.2f ms\n", $median * 1000 } else { printf " => **** no median time ****\n"; } } return $median; } # Adapted from Parse::CPAN::MirroredBy by Adam Kennedy sub _parse { my ($self, $file, $handle) = @_; my $output = $self->{mirrors}; my $geo = $self->{geography}; local $/ = "\012"; my $line = 0; my $mirror = undef; while ( 1 ) { # Next line my $string = <$handle>; last if ! defined $string; $line = $line + 1; # Remove the useless lines chomp( $string ); next if $string =~ /^\s*$/; next if $string =~ /^\s*#/; # Hostname or property? if ( $string =~ /^\s/ ) { # Property unless ( $string =~ /^\s+(\w+)\s+=\s+\"(.*)\"$/ ) { croak("Invalid property on line $line"); } my ($prop, $value) = ($1,$2); $mirror ||= {}; if ( $prop eq 'dst_location' ) { my (@location,$continent,$country); @location = (split /\s*,\s*/, $value) and ($continent, $country) = @location[-1,-2]; $continent =~ s/\s\(.*//; $continent =~ s/\W+$//; # if Jarkko doesn't know latitude/longitude $geo->{$continent}{$country} = 1 if $continent && $country; $mirror->{continent} = $continent || "unknown"; $mirror->{country} = $country || "unknown"; } elsif ( $prop eq 'dst_http' ) { $mirror->{http} = $value; } elsif ( $prop eq 'dst_ftp' ) { $mirror->{ftp} = $value; } elsif ( $prop eq 'dst_rsync' ) { $mirror->{rsync} = $value; } else { $prop =~ s/^dst_//; $mirror->{$prop} = $value; } } else { # Hostname unless ( $string =~ /^([\w\.-]+)\:\s*$/ ) { croak("Invalid host name on line $line"); } my $current = $mirror; $mirror = { hostname => "$1" }; if ( $current ) { push @$output, CPAN::Mirrored::By->new($current); } } } if ( $mirror ) { push @$output, CPAN::Mirrored::By->new($mirror); } return; } #--------------------------------------------------------------------------# package CPAN::Mirrored::By; use strict; use Net::Ping (); sub new { my($self,$arg) = @_; $arg ||= {}; bless $arg, $self; } sub hostname { shift->{hostname} } sub continent { shift->{continent} } sub country { shift->{country} } sub http { shift->{http} || '' } sub ftp { shift->{ftp} || '' } sub rsync { shift->{rsync} || '' } sub rtt { shift->{rtt} } sub ping_time { shift->{ping_time} } sub url { my $self = shift; return $self->{http} || $self->{ftp}; } sub ping { my($self, %args) = @_; my $external_ping = $args{external_ping}; if ($external_ping) { eval { require Net::Ping::External } or die "Net::Ping::External required to use external ping command"; } my $ping = Net::Ping->new( $external_ping ? 'external' : $^O eq 'VMS' ? 'icmp' : 'tcp', 1 ); my ($proto) = $self->url =~ m{^([^:]+)}; my $port = $proto eq 'http' ? 80 : 21; return unless $port; if ( $ping->can('port_number') ) { $ping->port_number($port); } else { $ping->{'port_num'} = $port; } $ping->hires(1) if $ping->can('hires'); my ($alive,$rtt) = eval { $ping->ping($self->hostname); }; my $verbose = $args{verbose}; if ($verbose && !$alive) { printf "(host %s not alive)", $self->hostname; } $self->{rtt} = $alive ? $rtt : undef; $self->{ping_time} = time; $self->rtt; } 1; =back =head1 AUTHOR Andreas Koenig C<< >>, David Golden C<< >>, brian d foy C<< >> =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L =cut PK2]!+Shell.pmnu6$package CPAN::Shell; use strict; # -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: use vars qw( $ADVANCED_QUERY $AUTOLOAD $COLOR_REGISTERED $Help $autoload_recursion $reload @ISA @relo $VERSION ); @relo = ( "CPAN.pm", "CPAN/Author.pm", "CPAN/CacheMgr.pm", "CPAN/Complete.pm", "CPAN/Debug.pm", "CPAN/DeferredCode.pm", "CPAN/Distribution.pm", "CPAN/Distroprefs.pm", "CPAN/Distrostatus.pm", "CPAN/Exception/RecursiveDependency.pm", "CPAN/Exception/yaml_not_installed.pm", "CPAN/FirstTime.pm", "CPAN/FTP.pm", "CPAN/FTP/netrc.pm", "CPAN/HandleConfig.pm", "CPAN/Index.pm", "CPAN/InfoObj.pm", "CPAN/Kwalify.pm", "CPAN/LWP/UserAgent.pm", "CPAN/Module.pm", "CPAN/Prompt.pm", "CPAN/Queue.pm", "CPAN/Reporter/Config.pm", "CPAN/Reporter/History.pm", "CPAN/Reporter/PrereqCheck.pm", "CPAN/Reporter.pm", "CPAN/Shell.pm", "CPAN/SQLite.pm", "CPAN/Tarzip.pm", "CPAN/Version.pm", ); $VERSION = "5.5009"; # record the initial timestamp for reload. $reload = { map {$INC{$_} ? ($_,(stat $INC{$_})[9]) : ()} @relo }; @CPAN::Shell::ISA = qw(CPAN::Debug); use Cwd qw(chdir); use Carp (); $COLOR_REGISTERED ||= 0; $Help = { '?' => \"help", '!' => "eval the rest of the line as perl", a => "whois author", autobundle => "write inventory into a bundle file", b => "info about bundle", bye => \"quit", clean => "clean up a distribution's build directory", # cvs_import d => "info about a distribution", # dump exit => \"quit", failed => "list all failed actions within current session", fforce => "redo a command from scratch", force => "redo a command", get => "download a distribution", h => \"help", help => "overview over commands; 'help ...' explains specific commands", hosts => "statistics about recently used hosts", i => "info about authors/bundles/distributions/modules", install => "install a distribution", install_tested => "install all distributions tested OK", is_tested => "list all distributions tested OK", look => "open a subshell in a distribution's directory", ls => "list distributions matching a fileglob", m => "info about a module", make => "make/build a distribution", mkmyconfig => "write current config into a CPAN/MyConfig.pm file", notest => "run a (usually install) command but leave out the test phase", o => "'o conf ...' for config stuff; 'o debug ...' for debugging", perldoc => "try to get a manpage for a module", q => \"quit", quit => "leave the cpan shell", r => "review upgradable modules", readme => "display the README of a distro with a pager", recent => "show recent uploads to the CPAN", # recompile reload => "'reload cpan' or 'reload index'", report => "test a distribution and send a test report to cpantesters", reports => "info about reported tests from cpantesters", # scripts # smoke test => "test a distribution", u => "display uninstalled modules", upgrade => "combine 'r' command with immediate installation", }; { $autoload_recursion ||= 0; #-> sub CPAN::Shell::AUTOLOAD ; sub AUTOLOAD { ## no critic $autoload_recursion++; my($l) = $AUTOLOAD; my $class = shift(@_); # warn "autoload[$l] class[$class]"; $l =~ s/.*:://; if ($CPAN::Signal) { warn "Refusing to autoload '$l' while signal pending"; $autoload_recursion--; return; } if ($autoload_recursion > 1) { my $fullcommand = join " ", map { "'$_'" } $l, @_; warn "Refusing to autoload $fullcommand in recursion\n"; $autoload_recursion--; return; } if ($l =~ /^w/) { # XXX needs to be reconsidered if ($CPAN::META->has_inst('CPAN::WAIT')) { CPAN::WAIT->$l(@_); } else { $CPAN::Frontend->mywarn(qq{ Commands starting with "w" require CPAN::WAIT to be installed. Please consider installing CPAN::WAIT to use the fulltext index. For this you just need to type install CPAN::WAIT }); } } else { $CPAN::Frontend->mywarn(qq{Unknown shell command '$l'. }. qq{Type ? for help. }); } $autoload_recursion--; } } #-> sub CPAN::Shell::h ; sub h { my($class,$about) = @_; if (defined $about) { my $help; if (exists $Help->{$about}) { if (ref $Help->{$about}) { # aliases $about = ${$Help->{$about}}; } $help = $Help->{$about}; } else { $help = "No help available"; } $CPAN::Frontend->myprint("$about\: $help\n"); } else { my $filler = " " x (80 - 28 - length($CPAN::VERSION)); $CPAN::Frontend->myprint(qq{ Display Information $filler (ver $CPAN::VERSION) command argument description a,b,d,m WORD or /REGEXP/ about authors, bundles, distributions, modules i WORD or /REGEXP/ about any of the above ls AUTHOR or GLOB about files in the author's directory (with WORD being a module, bundle or author name or a distribution name of the form AUTHOR/DISTRIBUTION) Download, Test, Make, Install... get download clean make clean make make (implies get) look open subshell in dist directory test make test (implies make) readme display these README files install make install (implies test) perldoc display POD documentation Upgrade installed modules r WORDs or /REGEXP/ or NONE report updates for some/matching/all upgrade WORDs or /REGEXP/ or NONE upgrade some/matching/all modules Pragmas force CMD try hard to do command fforce CMD try harder notest CMD skip testing Other h,? display this menu ! perl-code eval a perl command o conf [opt] set and query options q quit the cpan shell reload cpan load CPAN.pm again reload index load newer indices autobundle Snapshot recent latest CPAN uploads}); } } *help = \&h; #-> sub CPAN::Shell::a ; sub a { my($self,@arg) = @_; # authors are always UPPERCASE for (@arg) { $_ = uc $_ unless /=/; } $CPAN::Frontend->myprint($self->format_result('Author',@arg)); } #-> sub CPAN::Shell::globls ; sub globls { my($self,$s,$pragmas) = @_; # ls is really very different, but we had it once as an ordinary # command in the Shell (up to rev. 321) and we could not handle # force well then my(@accept,@preexpand); if ($s =~ /[\*\?\/]/) { if ($CPAN::META->has_inst("Text::Glob")) { if (my($au,$pathglob) = $s =~ m|(.*?)/(.*)|) { my $rau = Text::Glob::glob_to_regex(uc $au); CPAN::Shell->debug("au[$au]pathglob[$pathglob]rau[$rau]") if $CPAN::DEBUG; push @preexpand, map { $_->id . "/" . $pathglob } CPAN::Shell->expand_by_method('CPAN::Author',['id'],"/$rau/"); } else { my $rau = Text::Glob::glob_to_regex(uc $s); push @preexpand, map { $_->id } CPAN::Shell->expand_by_method('CPAN::Author', ['id'], "/$rau/"); } } else { $CPAN::Frontend->mydie("Text::Glob not installed, cannot proceed"); } } else { push @preexpand, uc $s; } for (@preexpand) { unless (/^[A-Z0-9\-]+(\/|$)/i) { $CPAN::Frontend->mywarn("ls command rejects argument $_: not an author\n"); next; } push @accept, $_; } my $silent = @accept>1; my $last_alpha = ""; my @results; for my $a (@accept) { my($author,$pathglob); if ($a =~ m|(.*?)/(.*)|) { my $a2 = $1; $pathglob = $2; $author = CPAN::Shell->expand_by_method('CPAN::Author', ['id'], $a2) or $CPAN::Frontend->mydie("No author found for $a2\n"); } else { $author = CPAN::Shell->expand_by_method('CPAN::Author', ['id'], $a) or $CPAN::Frontend->mydie("No author found for $a\n"); } if ($silent) { my $alpha = substr $author->id, 0, 1; my $ad; if ($alpha eq $last_alpha) { $ad = ""; } else { $ad = "[$alpha]"; $last_alpha = $alpha; } $CPAN::Frontend->myprint($ad); } for my $pragma (@$pragmas) { if ($author->can($pragma)) { $author->$pragma(); } } CPAN->debug("author[$author]pathglob[$pathglob]silent[$silent]") if $CPAN::DEBUG; push @results, $author->ls($pathglob,$silent); # silent if # more than one # author for my $pragma (@$pragmas) { my $unpragma = "un$pragma"; if ($author->can($unpragma)) { $author->$unpragma(); } } } @results; } #-> sub CPAN::Shell::local_bundles ; sub local_bundles { my($self,@which) = @_; my($incdir,$bdir,$dh); foreach $incdir ($CPAN::Config->{'cpan_home'},@INC) { my @bbase = "Bundle"; while (my $bbase = shift @bbase) { $bdir = File::Spec->catdir($incdir,split /::/, $bbase); CPAN->debug("bdir[$bdir]\@bbase[@bbase]") if $CPAN::DEBUG; if ($dh = DirHandle->new($bdir)) { # may fail my($entry); for $entry ($dh->read) { next if $entry =~ /^\./; next unless $entry =~ /^\w+(\.pm)?(?!\n)\Z/; if (-d File::Spec->catdir($bdir,$entry)) { push @bbase, "$bbase\::$entry"; } else { next unless $entry =~ s/\.pm(?!\n)\Z//; $CPAN::META->instance('CPAN::Bundle',"$bbase\::$entry"); } } } } } } #-> sub CPAN::Shell::b ; sub b { my($self,@which) = @_; CPAN->debug("which[@which]") if $CPAN::DEBUG; $self->local_bundles; $CPAN::Frontend->myprint($self->format_result('Bundle',@which)); } #-> sub CPAN::Shell::d ; sub d { $CPAN::Frontend->myprint(shift->format_result('Distribution',@_));} #-> sub CPAN::Shell::m ; sub m { # emacs confused here }; sub mimimimimi { # emacs in sync here my $self = shift; my @m = @_; for (@m) { if (m|(?:\w+/)*\w+\.pm$|) { # same regexp in expandany s/.pm$//; s|/|::|g; } } $CPAN::Frontend->myprint($self->format_result('Module',@m)); } #-> sub CPAN::Shell::i ; sub i { my($self) = shift; my(@args) = @_; @args = '/./' unless @args; my(@result); for my $type (qw/Bundle Distribution Module/) { push @result, $self->expand($type,@args); } # Authors are always uppercase. push @result, $self->expand("Author", map { uc $_ } @args); my $result = @result == 1 ? $result[0]->as_string : @result == 0 ? "No objects found of any type for argument @args\n" : join("", (map {$_->as_glimpse} @result), scalar @result, " items found\n", ); $CPAN::Frontend->myprint($result); } #-> sub CPAN::Shell::o ; # CPAN::Shell::o and CPAN::HandleConfig::edit are closely related. 'o # conf' calls through to CPAN::HandleConfig::edit. 'o conf' should # probably have been called 'set' and 'o debug' maybe 'set debug' or # 'debug'; 'o conf ARGS' calls ->edit in CPAN/HandleConfig.pm sub o { my($self,$o_type,@o_what) = @_; $o_type ||= ""; CPAN->debug("o_type[$o_type] o_what[".join(" | ",@o_what)."]\n"); if ($o_type eq 'conf') { my($cfilter); ($cfilter) = $o_what[0] =~ m|^/(.*)/$| if @o_what; if (!@o_what or $cfilter) { # print all things, "o conf" $cfilter ||= ""; my $qrfilter = eval 'qr/$cfilter/'; if ($@) { $CPAN::Frontend->mydie("Cannot parse commandline: $@"); } my($k,$v); my $configpm = CPAN::HandleConfig->require_myconfig_or_config; $CPAN::Frontend->myprint("\$CPAN::Config options from $configpm\:\n"); for $k (sort keys %CPAN::HandleConfig::can) { next unless $k =~ /$qrfilter/; $v = $CPAN::HandleConfig::can{$k}; $CPAN::Frontend->myprint(sprintf " %-18s [%s]\n", $k, $v); } $CPAN::Frontend->myprint("\n"); for $k (sort keys %CPAN::HandleConfig::keys) { next unless $k =~ /$qrfilter/; CPAN::HandleConfig->prettyprint($k); } $CPAN::Frontend->myprint("\n"); } else { if (CPAN::HandleConfig->edit(@o_what)) { } else { $CPAN::Frontend->myprint(qq{Type 'o conf' to view all configuration }. qq{items\n\n}); } } } elsif ($o_type eq 'debug') { my(%valid); @o_what = () if defined $o_what[0] && $o_what[0] =~ /help/i; if (@o_what) { while (@o_what) { my($what) = shift @o_what; if ($what =~ s/^-// && exists $CPAN::DEBUG{$what}) { $CPAN::DEBUG &= $CPAN::DEBUG ^ $CPAN::DEBUG{$what}; next; } if ( exists $CPAN::DEBUG{$what} ) { $CPAN::DEBUG |= $CPAN::DEBUG{$what}; } elsif ($what =~ /^\d/) { $CPAN::DEBUG = $what; } elsif (lc $what eq 'all') { my($max) = 0; for (values %CPAN::DEBUG) { $max += $_; } $CPAN::DEBUG = $max; } else { my($known) = 0; for (keys %CPAN::DEBUG) { next unless lc($_) eq lc($what); $CPAN::DEBUG |= $CPAN::DEBUG{$_}; $known = 1; } $CPAN::Frontend->myprint("unknown argument [$what]\n") unless $known; } } } else { my $raw = "Valid options for debug are ". join(", ",sort(keys %CPAN::DEBUG), 'all'). qq{ or a number. Completion works on the options. }. qq{Case is ignored.}; require Text::Wrap; $CPAN::Frontend->myprint(Text::Wrap::fill("","",$raw)); $CPAN::Frontend->myprint("\n\n"); } if ($CPAN::DEBUG) { $CPAN::Frontend->myprint("Options set for debugging ($CPAN::DEBUG):\n"); my($k,$v); for $k (sort {$CPAN::DEBUG{$a} <=> $CPAN::DEBUG{$b}} keys %CPAN::DEBUG) { $v = $CPAN::DEBUG{$k}; $CPAN::Frontend->myprint(sprintf " %-14s(%s)\n", $k, $v) if $v & $CPAN::DEBUG; } } else { $CPAN::Frontend->myprint("Debugging turned off completely.\n"); } } else { $CPAN::Frontend->myprint(qq{ Known options: conf set or get configuration variables debug set or get debugging options }); } } # CPAN::Shell::paintdots_onreload sub paintdots_onreload { my($ref) = shift; sub { if ( $_[0] =~ /[Ss]ubroutine ([\w:]+) redefined/ ) { my($subr) = $1; ++$$ref; local($|) = 1; # $CPAN::Frontend->myprint(".($subr)"); $CPAN::Frontend->myprint("."); if ($subr =~ /\bshell\b/i) { # warn "debug[$_[0]]"; # It would be nice if we could detect that a # subroutine has actually changed, but for now we # practically always set the GOTOSHELL global $CPAN::GOTOSHELL=1; } return; } warn @_; }; } #-> sub CPAN::Shell::hosts ; sub hosts { my($self) = @_; my $fullstats = CPAN::FTP->_ftp_statistics(); my $history = $fullstats->{history} || []; my %S; # statistics while (my $last = pop @$history) { my $attempts = $last->{attempts} or next; my $start; if (@$attempts) { $start = $attempts->[-1]{start}; if ($#$attempts > 0) { for my $i (0..$#$attempts-1) { my $url = $attempts->[$i]{url} or next; $S{no}{$url}++; } } } else { $start = $last->{start}; } next unless $last->{thesiteurl}; # C-C? bad filenames? $S{start} = $start; $S{end} ||= $last->{end}; my $dltime = $last->{end} - $start; my $dlsize = $last->{filesize} || 0; my $url = ref $last->{thesiteurl} ? $last->{thesiteurl}->text : $last->{thesiteurl}; my $s = $S{ok}{$url} ||= {}; $s->{n}++; $s->{dlsize} ||= 0; $s->{dlsize} += $dlsize/1024; $s->{dltime} ||= 0; $s->{dltime} += $dltime; } my $res; for my $url (sort keys %{$S{ok}}) { next if $S{ok}{$url}{dltime} == 0; # div by zero push @{$res->{ok}}, [@{$S{ok}{$url}}{qw(n dlsize dltime)}, $S{ok}{$url}{dlsize}/$S{ok}{$url}{dltime}, $url, ]; } for my $url (sort keys %{$S{no}}) { push @{$res->{no}}, [$S{no}{$url}, $url, ]; } my $R = ""; # report if ($S{start} && $S{end}) { $R .= sprintf "Log starts: %s\n", $S{start} ? scalar(localtime $S{start}) : "unknown"; $R .= sprintf "Log ends : %s\n", $S{end} ? scalar(localtime $S{end}) : "unknown"; } if ($res->{ok} && @{$res->{ok}}) { $R .= sprintf "\nSuccessful downloads: N kB secs kB/s url\n"; my $i = 20; for (sort { $b->[3] <=> $a->[3] } @{$res->{ok}}) { $R .= sprintf "%4d %8d %5d %9.1f %s\n", @$_; last if --$i<=0; } } if ($res->{no} && @{$res->{no}}) { $R .= sprintf "\nUnsuccessful downloads:\n"; my $i = 20; for (sort { $b->[0] <=> $a->[0] } @{$res->{no}}) { $R .= sprintf "%4d %s\n", @$_; last if --$i<=0; } } $CPAN::Frontend->myprint($R); } # here is where 'reload cpan' is done #-> sub CPAN::Shell::reload ; sub reload { my($self,$command,@arg) = @_; $command ||= ""; $self->debug("self[$self]command[$command]arg[@arg]") if $CPAN::DEBUG; if ($command =~ /^cpan$/i) { my $redef = 0; chdir "$CPAN::iCwd" if $CPAN::iCwd; # may fail my $failed; MFILE: for my $f (@relo) { next unless exists $INC{$f}; my $p = $f; $p =~ s/\.pm$//; $p =~ s|/|::|g; $CPAN::Frontend->myprint("($p"); local($SIG{__WARN__}) = paintdots_onreload(\$redef); $self->_reload_this($f) or $failed++; my $v = eval "$p\::->VERSION"; $CPAN::Frontend->myprint("v$v)"); } $CPAN::Frontend->myprint("\n$redef subroutines redefined\n"); if ($failed) { my $errors = $failed == 1 ? "error" : "errors"; $CPAN::Frontend->mywarn("\n$failed $errors during reload. You better quit ". "this session.\n"); } } elsif ($command =~ /^index$/i) { CPAN::Index->force_reload; } else { $CPAN::Frontend->myprint(qq{cpan re-evals the CPAN modules index re-reads the index files\n}); } } # reload means only load again what we have loaded before #-> sub CPAN::Shell::_reload_this ; sub _reload_this { my($self,$f,$args) = @_; CPAN->debug("f[$f]") if $CPAN::DEBUG; return 1 unless $INC{$f}; # we never loaded this, so we do not # reload but say OK my $pwd = CPAN::anycwd(); CPAN->debug("pwd[$pwd]") if $CPAN::DEBUG; my($file); for my $inc (@INC) { $file = File::Spec->catfile($inc,split /\//, $f); last if -f $file; $file = ""; } CPAN->debug("file[$file]") if $CPAN::DEBUG; my @inc = @INC; unless ($file && -f $file) { # this thingy is not in the INC path, maybe CPAN/MyConfig.pm? $file = $INC{$f}; unless (CPAN->has_inst("File::Basename")) { @inc = File::Basename::dirname($file); } else { # do we ever need this? @inc = substr($file,0,-length($f)-1); # bring in back to me! } } CPAN->debug("file[$file]inc[@inc]") if $CPAN::DEBUG; unless (-f $file) { $CPAN::Frontend->mywarn("Found no file to reload for '$f'\n"); return; } my $mtime = (stat $file)[9]; $reload->{$f} ||= -1; my $must_reload = $mtime != $reload->{$f}; $args ||= {}; $must_reload ||= $args->{reloforce}; # o conf defaults needs this if ($must_reload) { my $fh = FileHandle->new($file) or $CPAN::Frontend->mydie("Could not open $file: $!"); my $content; { local($/); local $^W = 1; $content = <$fh>; } CPAN->debug(sprintf("reload file[%s] content[%s...]",$file,substr($content,0,128))) if $CPAN::DEBUG; my $includefile; if ($includefile = $INC{$f} and -e $includefile) { $f = $includefile; } delete $INC{$f}; local @INC = @inc; eval "require '$f'"; if ($@) { warn $@; return; } $reload->{$f} = $mtime; } else { $CPAN::Frontend->myprint("__unchanged__"); } return 1; } #-> sub CPAN::Shell::mkmyconfig ; sub mkmyconfig { my($self) = @_; if ( my $configpm = $INC{'CPAN/MyConfig.pm'} ) { $CPAN::Frontend->myprint( "CPAN::MyConfig already exists as $configpm.\n" . "Running configuration again...\n" ); require CPAN::FirstTime; CPAN::FirstTime::init($configpm); } else { # force some missing values to be filled in with defaults delete $CPAN::Config->{$_} for qw/build_dir cpan_home keep_source_where histfile/; CPAN::HandleConfig->load( make_myconfig => 1 ); } } #-> sub CPAN::Shell::_binary_extensions ; sub _binary_extensions { my($self) = shift @_; my(@result,$module,%seen,%need,$headerdone); for $module ($self->expand('Module','/./')) { my $file = $module->cpan_file; next if $file eq "N/A"; next if $file =~ /^Contact Author/; my $dist = $CPAN::META->instance('CPAN::Distribution',$file); next if $dist->isa_perl; next unless $module->xs_file; local($|) = 1; $CPAN::Frontend->myprint("."); push @result, $module; } # print join " | ", @result; $CPAN::Frontend->myprint("\n"); return @result; } #-> sub CPAN::Shell::recompile ; sub recompile { my($self) = shift @_; my($module,@module,$cpan_file,%dist); @module = $self->_binary_extensions(); for $module (@module) { # we force now and compile later, so we # don't do it twice $cpan_file = $module->cpan_file; my $pack = $CPAN::META->instance('CPAN::Distribution',$cpan_file); $pack->force; $dist{$cpan_file}++; } for $cpan_file (sort keys %dist) { $CPAN::Frontend->myprint(" CPAN: Recompiling $cpan_file\n\n"); my $pack = $CPAN::META->instance('CPAN::Distribution',$cpan_file); $pack->install; $CPAN::Signal = 0; # it's tempting to reset Signal, so we can # stop a package from recompiling, # e.g. IO-1.12 when we have perl5.003_10 } } #-> sub CPAN::Shell::scripts ; sub scripts { my($self, $arg) = @_; $CPAN::Frontend->mywarn(">>>> experimental command, currently unsupported <<<<\n\n"); for my $req (qw( HTML::LinkExtor Sort::Versions List::Util )) { unless ($CPAN::META->has_inst($req)) { $CPAN::Frontend->mywarn(" $req not available\n"); } } my $p = HTML::LinkExtor->new(); my $indexfile = "/home/ftp/pub/PAUSE/scripts/new/index.html"; unless (-f $indexfile) { $CPAN::Frontend->mydie("found no indexfile[$indexfile]\n"); } $p->parse_file($indexfile); my @hrefs; my $qrarg; if ($arg =~ s|^/(.+)/$|$1|) { $qrarg = eval 'qr/$arg/'; # hide construct from 5.004 } for my $l ($p->links) { my $tag = shift @$l; next unless $tag eq "a"; my %att = @$l; my $href = $att{href}; next unless $href =~ s|^\.\./authors/id/./../||; if ($arg) { if ($qrarg) { if ($href =~ $qrarg) { push @hrefs, $href; } } else { if ($href =~ /\Q$arg\E/) { push @hrefs, $href; } } } else { push @hrefs, $href; } } # now filter for the latest version if there is more than one of a name my %stems; for (sort @hrefs) { my $href = $_; s/-v?\d.*//; my $stem = $_; $stems{$stem} ||= []; push @{$stems{$stem}}, $href; } for (sort keys %stems) { my $highest; if (@{$stems{$_}} > 1) { $highest = List::Util::reduce { Sort::Versions::versioncmp($a,$b) > 0 ? $a : $b } @{$stems{$_}}; } else { $highest = $stems{$_}[0]; } $CPAN::Frontend->myprint("$highest\n"); } } sub _guess_manpage { my($self,$d,$contains,$dist) = @_; $dist =~ s/-/::/g; my $module; if (exists $contains->{$dist}) { $module = $dist; } elsif (1 == keys %$contains) { ($module) = keys %$contains; } my $manpage; if ($module) { my $m = $self->expand("Module",$module); $m->as_string; # called for side-effects, shame $manpage = $m->{MANPAGE}; } else { $manpage = "unknown"; } return $manpage; } #-> sub CPAN::Shell::_specfile ; sub _specfile { die "CPAN::Shell::_specfile() has been moved to CPAN::Plugin::Specfile::post_test()"; } #-> sub CPAN::Shell::report ; sub report { my($self,@args) = @_; unless ($CPAN::META->has_inst("CPAN::Reporter")) { $CPAN::Frontend->mydie("CPAN::Reporter not installed; cannot continue"); } local $CPAN::Config->{test_report} = 1; $self->force("test",@args); # force is there so that the test be # re-run (as documented) } # compare with is_tested #-> sub CPAN::Shell::install_tested sub install_tested { my($self,@some) = @_; $CPAN::Frontend->mywarn("install_tested() must not be called with arguments.\n"), return if @some; CPAN::Index->reload; for my $b (reverse $CPAN::META->_list_sorted_descending_is_tested) { my $yaml = "$b.yml"; unless (-f $yaml) { $CPAN::Frontend->mywarn("No YAML file for $b available, skipping\n"); next; } my $yaml_content = CPAN->_yaml_loadfile($yaml); my $id = $yaml_content->[0]{distribution}{ID}; unless ($id) { $CPAN::Frontend->mywarn("No ID found in '$yaml', skipping\n"); next; } my $do = CPAN::Shell->expandany($id); unless ($do) { $CPAN::Frontend->mywarn("Could not expand ID '$id', skipping\n"); next; } unless ($do->{build_dir}) { $CPAN::Frontend->mywarn("Distro '$id' has no build_dir, skipping\n"); next; } unless ($do->{build_dir} eq $b) { $CPAN::Frontend->mywarn("Distro '$id' has build_dir '$do->{build_dir}' but expected '$b', skipping\n"); next; } push @some, $do; } $CPAN::Frontend->mywarn("No tested distributions found.\n"), return unless @some; @some = grep { $_->{make_test} && ! $_->{make_test}->failed } @some; $CPAN::Frontend->mywarn("No distributions tested with this build of perl found.\n"), return unless @some; # @some = grep { not $_->uptodate } @some; # $CPAN::Frontend->mywarn("No non-uptodate distributions tested with this build of perl found.\n"), # return unless @some; CPAN->debug("some[@some]"); for my $d (@some) { my $id = $d->can("pretty_id") ? $d->pretty_id : $d->id; $CPAN::Frontend->myprint("install_tested: Running for $id\n"); $CPAN::Frontend->mysleep(1); $self->install($d); } } #-> sub CPAN::Shell::upgrade ; sub upgrade { my($self,@args) = @_; $self->install($self->r(@args)); } #-> sub CPAN::Shell::_u_r_common ; sub _u_r_common { my($self) = shift @_; my($what) = shift @_; CPAN->debug("self[$self] what[$what] args[@_]") if $CPAN::DEBUG; Carp::croak "Usage: \$obj->_u_r_common(a|r|u)" unless $what && $what =~ /^[aru]$/; my(@args) = @_; @args = '/./' unless @args; my(@result,$module,%seen,%need,$headerdone, $version_undefs,$version_zeroes, @version_undefs,@version_zeroes); $version_undefs = $version_zeroes = 0; my $sprintf = "%s%-25s%s %9s %9s %s\n"; my @expand = $self->expand('Module',@args); if ($CPAN::DEBUG) { # Looks like noise to me, was very useful for debugging # for metadata cache my $expand = scalar @expand; $CPAN::Frontend->myprint(sprintf "%d matches in the database, time[%d]\n", $expand, time); } my @sexpand; if ($] < 5.008) { # hard to believe that the more complex sorting can lead to # stack curruptions on older perl @sexpand = sort {$a->id cmp $b->id} @expand; } else { @sexpand = map { $_->[1] } sort { $b->[0] <=> $a->[0] || $a->[1]{ID} cmp $b->[1]{ID}, } map { [$_->_is_representative_module, $_ ] } @expand; } if ($CPAN::DEBUG) { $CPAN::Frontend->myprint(sprintf "sorted at time[%d]\n", time); sleep 1; } MODULE: for $module (@sexpand) { my $file = $module->cpan_file; next MODULE unless defined $file; # ?? $file =~ s!^./../!!; my($latest) = $module->cpan_version; my($inst_file) = $module->inst_file; CPAN->debug("file[$file]latest[$latest]") if $CPAN::DEBUG; my($have); return if $CPAN::Signal; my($next_MODULE); eval { # version.pm involved! if ($inst_file) { if ($what eq "a") { $have = $module->inst_version; } elsif ($what eq "r") { $have = $module->inst_version; local($^W) = 0; if ($have eq "undef") { $version_undefs++; push @version_undefs, $module->as_glimpse; } elsif (CPAN::Version->vcmp($have,0)==0) { $version_zeroes++; push @version_zeroes, $module->as_glimpse; } ++$next_MODULE unless CPAN::Version->vgt($latest, $have); # to be pedantic we should probably say: # && !($have eq "undef" && $latest ne "undef" && $latest gt ""); # to catch the case where CPAN has a version 0 and we have a version undef } elsif ($what eq "u") { ++$next_MODULE; } } else { if ($what eq "a") { ++$next_MODULE; } elsif ($what eq "r") { ++$next_MODULE; } elsif ($what eq "u") { $have = "-"; } } }; next MODULE if $next_MODULE; if ($@) { $CPAN::Frontend->mywarn (sprintf("Error while comparing cpan/installed versions of '%s': INST_FILE: %s INST_VERSION: %s %s CPAN_VERSION: %s %s ", $module->id, $inst_file || "", (defined $have ? $have : "[UNDEFINED]"), (ref $have ? ref $have : ""), $latest, (ref $latest ? ref $latest : ""), )); next MODULE; } return if $CPAN::Signal; # this is sometimes lengthy $seen{$file} ||= 0; if ($what eq "a") { push @result, sprintf "%s %s\n", $module->id, $have; } elsif ($what eq "r") { push @result, $module->id; next MODULE if $seen{$file}++; } elsif ($what eq "u") { push @result, $module->id; next MODULE if $seen{$file}++; next MODULE if $file =~ /^Contact/; } unless ($headerdone++) { $CPAN::Frontend->myprint("\n"); $CPAN::Frontend->myprint(sprintf( $sprintf, "", "Package namespace", "", "installed", "latest", "in CPAN file" )); } my $color_on = ""; my $color_off = ""; if ( $COLOR_REGISTERED && $CPAN::META->has_inst("Term::ANSIColor") && $module->description ) { $color_on = Term::ANSIColor::color("green"); $color_off = Term::ANSIColor::color("reset"); } $CPAN::Frontend->myprint(sprintf $sprintf, $color_on, $module->id, $color_off, $have, $latest, $file); $need{$module->id}++; } unless (%need) { if (!@expand || $what eq "u") { $CPAN::Frontend->myprint("No modules found for @args\n"); } elsif ($what eq "r") { $CPAN::Frontend->myprint("All modules are up to date for @args\n"); } } if ($what eq "r") { if ($version_zeroes) { my $s_has = $version_zeroes > 1 ? "s have" : " has"; $CPAN::Frontend->myprint(qq{$version_zeroes installed module$s_has }. qq{a version number of 0\n}); if ($CPAN::Config->{show_zero_versions}) { local $" = "\t"; $CPAN::Frontend->myprint(qq{ they are\n\t@version_zeroes\n}); $CPAN::Frontend->myprint(qq{(use 'o conf show_zero_versions 0' }. qq{to hide them)\n}); } else { $CPAN::Frontend->myprint(qq{(use 'o conf show_zero_versions 1' }. qq{to show them)\n}); } } if ($version_undefs) { my $s_has = $version_undefs > 1 ? "s have" : " has"; $CPAN::Frontend->myprint(qq{$version_undefs installed module$s_has no }. qq{parsable version number\n}); if ($CPAN::Config->{show_unparsable_versions}) { local $" = "\t"; $CPAN::Frontend->myprint(qq{ they are\n\t@version_undefs\n}); $CPAN::Frontend->myprint(qq{(use 'o conf show_unparsable_versions 0' }. qq{to hide them)\n}); } else { $CPAN::Frontend->myprint(qq{(use 'o conf show_unparsable_versions 1' }. qq{to show them)\n}); } } } @result; } #-> sub CPAN::Shell::r ; sub r { shift->_u_r_common("r",@_); } #-> sub CPAN::Shell::u ; sub u { shift->_u_r_common("u",@_); } #-> sub CPAN::Shell::failed ; sub failed { my($self,$only_id,$silent) = @_; my @failed = $self->find_failed($only_id); my $scope; if ($only_id) { $scope = "this command"; } elsif ($CPAN::Index::HAVE_REANIMATED) { $scope = "this or a previous session"; # it might be nice to have a section for previous session and # a second for this } else { $scope = "this session"; } if (@failed) { my $print; my $debug = 0; if ($debug) { $print = join "", map { sprintf "%5d %-45s: %s %s\n", @$_ } sort { $a->[0] <=> $b->[0] } @failed; } else { $print = join "", map { sprintf " %-45s: %s %s\n", @$_[1..3] } sort { $a->[0] <=> $b->[0] || $a->[4] <=> $b->[4] } @failed; } $CPAN::Frontend->myprint("Failed during $scope:\n$print"); } elsif (!$only_id || !$silent) { $CPAN::Frontend->myprint("Nothing failed in $scope\n"); } } sub find_failed { my($self,$only_id) = @_; my @failed; DIST: for my $d (sort { $a->id cmp $b->id } $CPAN::META->all_objects("CPAN::Distribution")) { my $failed = ""; NAY: for my $nosayer ( # order matters! "unwrapped", "writemakefile", "signature_verify", "make", "make_test", "install", "make_clean", ) { next unless exists $d->{$nosayer}; next unless defined $d->{$nosayer}; next unless ( UNIVERSAL::can($d->{$nosayer},"failed") ? $d->{$nosayer}->failed : $d->{$nosayer} =~ /^NO/ ); next NAY if $only_id && $only_id != ( UNIVERSAL::can($d->{$nosayer},"commandid") ? $d->{$nosayer}->commandid : $CPAN::CurrentCommandId ); $failed = $nosayer; last; } next DIST unless $failed; my $id = $d->id; $id =~ s|^./../||; ### XXX need to flag optional modules as '(optional)' if they are # from recommends/suggests -- i.e. *show* failure, but make it clear # it was failure of optional module -- xdg, 2012-04-01 $id = "(optional) $id" if ! $d->{mandatory}; #$print .= sprintf( # " %-45s: %s %s\n", push @failed, ( UNIVERSAL::can($d->{$failed},"failed") ? [ $d->{$failed}->commandid, $id, $failed, $d->{$failed}->text, $d->{$failed}{TIME}||0, !! $d->{mandatory}, ] : [ 1, $id, $failed, $d->{$failed}, 0, !! $d->{mandatory}, ] ); } return @failed; } sub mandatory_dist_failed { my ($self) = @_; return grep { $_->[5] } $self->find_failed($CPAN::CurrentCommandID); } # XXX intentionally undocumented because completely bogus, unportable, # useless, etc. #-> sub CPAN::Shell::status ; sub status { my($self) = @_; require Devel::Size; my $ps = FileHandle->new; open $ps, "/proc/$$/status"; my $vm = 0; while (<$ps>) { next unless /VmSize:\s+(\d+)/; $vm = $1; last; } $CPAN::Frontend->mywarn(sprintf( "%-27s %6d\n%-27s %6d\n", "vm", $vm, "CPAN::META", Devel::Size::total_size($CPAN::META)/1024, )); for my $k (sort keys %$CPAN::META) { next unless substr($k,0,4) eq "read"; warn sprintf " %-26s %6d\n", $k, Devel::Size::total_size($CPAN::META->{$k})/1024; for my $k2 (sort keys %{$CPAN::META->{$k}}) { warn sprintf " %-25s %6d (keys: %6d)\n", $k2, Devel::Size::total_size($CPAN::META->{$k}{$k2})/1024, scalar keys %{$CPAN::META->{$k}{$k2}}; } } } # compare with install_tested #-> sub CPAN::Shell::is_tested sub is_tested { my($self) = @_; CPAN::Index->reload; for my $b (reverse $CPAN::META->_list_sorted_descending_is_tested) { my $time; if ($CPAN::META->{is_tested}{$b}) { $time = scalar(localtime $CPAN::META->{is_tested}{$b}); } else { $time = scalar localtime; $time =~ s/\S/?/g; } $CPAN::Frontend->myprint(sprintf "%s %s\n", $time, $b); } } #-> sub CPAN::Shell::autobundle ; sub autobundle { my($self) = shift; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; my(@bundle) = $self->_u_r_common("a",@_); my($todir) = File::Spec->catdir($CPAN::Config->{'cpan_home'},"Bundle"); File::Path::mkpath($todir); unless (-d $todir) { $CPAN::Frontend->myprint("Couldn't mkdir $todir for some reason\n"); return; } my($y,$m,$d) = (localtime)[5,4,3]; $y+=1900; $m++; my($c) = 0; my($me) = sprintf "Snapshot_%04d_%02d_%02d_%02d", $y, $m, $d, $c; my($to) = File::Spec->catfile($todir,"$me.pm"); while (-f $to) { $me = sprintf "Snapshot_%04d_%02d_%02d_%02d", $y, $m, $d, ++$c; $to = File::Spec->catfile($todir,"$me.pm"); } my($fh) = FileHandle->new(">$to") or Carp::croak "Can't open >$to: $!"; $fh->print( "package Bundle::$me;\n\n", "\$","VERSION = '0.01';\n\n", # hide from perl-reversion "1;\n\n", "__END__\n\n", "=head1 NAME\n\n", "Bundle::$me - Snapshot of installation on ", $Config::Config{'myhostname'}, " on ", scalar(localtime), "\n\n=head1 SYNOPSIS\n\n", "perl -MCPAN -e 'install Bundle::$me'\n\n", "=head1 CONTENTS\n\n", join("\n", @bundle), "\n\n=head1 CONFIGURATION\n\n", Config->myconfig, "\n\n=head1 AUTHOR\n\n", "This Bundle has been generated automatically ", "by the autobundle routine in CPAN.pm.\n", ); $fh->close; $CPAN::Frontend->myprint("\nWrote bundle file $to\n\n"); return $to; } #-> sub CPAN::Shell::expandany ; sub expandany { my($self,$s) = @_; CPAN->debug("s[$s]") if $CPAN::DEBUG; my $module_as_path = ""; if ($s =~ m|(?:\w+/)*\w+\.pm$|) { # same regexp in sub m $module_as_path = $s; $module_as_path =~ s/.pm$//; $module_as_path =~ s|/|::|g; } if ($module_as_path) { if ($module_as_path =~ m|^Bundle::|) { $self->local_bundles; return $self->expand('Bundle',$module_as_path); } else { return $self->expand('Module',$module_as_path) if $CPAN::META->exists('CPAN::Module',$module_as_path); } } elsif ($s =~ m|/| or substr($s,-1,1) eq ".") { # looks like a file or a directory $s = CPAN::Distribution->normalize($s); return $CPAN::META->instance('CPAN::Distribution',$s); # Distributions spring into existence, not expand } elsif ($s =~ m|^Bundle::|) { $self->local_bundles; # scanning so late for bundles seems # both attractive and crumpy: always # current state but easy to forget # somewhere return $self->expand('Bundle',$s); } else { return $self->expand('Module',$s) if $CPAN::META->exists('CPAN::Module',$s); } return; } #-> sub CPAN::Shell::expand ; sub expand { my $self = shift; my($type,@args) = @_; CPAN->debug("type[$type]args[@args]") if $CPAN::DEBUG; my $class = "CPAN::$type"; my $methods = ['id']; for my $meth (qw(name)) { next unless $class->can($meth); push @$methods, $meth; } $self->expand_by_method($class,$methods,@args); } #-> sub CPAN::Shell::expand_by_method ; sub expand_by_method { my $self = shift; my($class,$methods,@args) = @_; my($arg,@m); for $arg (@args) { my($regex,$command); if ($arg =~ m|^/(.*)/$|) { $regex = $1; # FIXME: there seem to be some ='s in the author data, which trigger # a failure here. This needs to be contemplated. # } elsif ($arg =~ m/=/) { # $command = 1; } my $obj; CPAN->debug(sprintf "class[%s]regex[%s]command[%s]", $class, defined $regex ? $regex : "UNDEFINED", defined $command ? $command : "UNDEFINED", ) if $CPAN::DEBUG; if (defined $regex) { if (CPAN::_sqlite_running()) { CPAN::Index->reload; $CPAN::SQLite->search($class, $regex); } for $obj ( $CPAN::META->all_objects($class) ) { unless ($obj && UNIVERSAL::can($obj,"id") && $obj->id) { # BUG, we got an empty object somewhere require Data::Dumper; CPAN->debug(sprintf( "Bug in CPAN: Empty id on obj[%s][%s]", $obj, Data::Dumper::Dumper($obj) )) if $CPAN::DEBUG; next; } for my $method (@$methods) { my $match = eval {$obj->$method() =~ /$regex/i}; if ($@) { my($err) = $@ =~ /^(.+) at .+? line \d+\.$/; $err ||= $@; # if we were too restrictive above $CPAN::Frontend->mydie("$err\n"); } elsif ($match) { push @m, $obj; last; } } } } elsif ($command) { die "equal sign in command disabled (immature interface), ". "you can set ! \$CPAN::Shell::ADVANCED_QUERY=1 to enable it. But please note, this is HIGHLY EXPERIMENTAL code that may go away anytime.\n" unless $ADVANCED_QUERY; my($method,$criterion) = $arg =~ /(.+?)=(.+)/; my($matchcrit) = $criterion =~ m/^~(.+)/; for my $self ( sort {$a->id cmp $b->id} $CPAN::META->all_objects($class) ) { my $lhs = $self->$method() or next; # () for 5.00503 if ($matchcrit) { push @m, $self if $lhs =~ m/$matchcrit/; } else { push @m, $self if $lhs eq $criterion; } } } else { my($xarg) = $arg; if ( $class eq 'CPAN::Bundle' ) { $xarg =~ s/^(Bundle::)?(.*)/Bundle::$2/; } elsif ($class eq "CPAN::Distribution") { $xarg = CPAN::Distribution->normalize($arg); } else { $xarg =~ s/:+/::/g; } if ($CPAN::META->exists($class,$xarg)) { $obj = $CPAN::META->instance($class,$xarg); } elsif ($CPAN::META->exists($class,$arg)) { $obj = $CPAN::META->instance($class,$arg); } else { next; } push @m, $obj; } } @m = sort {$a->id cmp $b->id} @m; if ( $CPAN::DEBUG ) { my $wantarray = wantarray; my $join_m = join ",", map {$_->id} @m; # $self->debug("wantarray[$wantarray]join_m[$join_m]"); my $count = scalar @m; $self->debug("class[$class]wantarray[$wantarray]count m[$count]"); } return wantarray ? @m : $m[0]; } #-> sub CPAN::Shell::format_result ; sub format_result { my($self) = shift; my($type,@args) = @_; @args = '/./' unless @args; my(@result) = $self->expand($type,@args); my $result = @result == 1 ? $result[0]->as_string : @result == 0 ? "No objects of type $type found for argument @args\n" : join("", (map {$_->as_glimpse} @result), scalar @result, " items found\n", ); $result; } #-> sub CPAN::Shell::report_fh ; { my $installation_report_fh; my $previously_noticed = 0; sub report_fh { return $installation_report_fh if $installation_report_fh; if ($CPAN::META->has_usable("File::Temp")) { $installation_report_fh = File::Temp->new( dir => File::Spec->tmpdir, template => 'cpan_install_XXXX', suffix => '.txt', unlink => 0, ); } unless ( $installation_report_fh ) { warn("Couldn't open installation report file; " . "no report file will be generated." ) unless $previously_noticed++; } } } # The only reason for this method is currently to have a reliable # debugging utility that reveals which output is going through which # channel. No, I don't like the colors ;-) # to turn colordebugging on, write # cpan> o conf colorize_output 1 #-> sub CPAN::Shell::colorize_output ; { my $print_ornamented_have_warned = 0; sub colorize_output { my $colorize_output = $CPAN::Config->{colorize_output}; if ($colorize_output && $^O eq 'MSWin32' && !$CPAN::META->has_inst("Win32::Console::ANSI")) { unless ($print_ornamented_have_warned++) { # no myprint/mywarn within myprint/mywarn! warn "Colorize_output is set to true but Win32::Console::ANSI is not installed. To activate colorized output, please install Win32::Console::ANSI.\n\n"; } $colorize_output = 0; } if ($colorize_output && !$CPAN::META->has_inst("Term::ANSIColor")) { unless ($print_ornamented_have_warned++) { # no myprint/mywarn within myprint/mywarn! warn "Colorize_output is set to true but Term::ANSIColor is not installed. To activate colorized output, please install Term::ANSIColor.\n\n"; } $colorize_output = 0; } return $colorize_output; } } #-> sub CPAN::Shell::print_ornamented ; sub print_ornamented { my($self,$what,$ornament) = @_; return unless defined $what; local $| = 1; # Flush immediately if ( $CPAN::Be_Silent ) { # WARNING: variable Be_Silent is poisoned and must be eliminated. print {report_fh()} $what; return; } my $swhat = "$what"; # stringify if it is an object if ($CPAN::Config->{term_is_latin}) { # note: deprecated, need to switch to $LANG and $LC_* # courtesy jhi: $swhat =~ s{([\xC0-\xDF])([\x80-\xBF])}{chr(ord($1)<<6&0xC0|ord($2)&0x3F)}eg; #}; } if ($self->colorize_output) { if ( $CPAN::DEBUG && $swhat =~ /^Debug\(/ ) { # if you want to have this configurable, please file a bug report $ornament = $CPAN::Config->{colorize_debug} || "black on_cyan"; } my $color_on = eval { Term::ANSIColor::color($ornament) } || ""; if ($@) { print "Term::ANSIColor rejects color[$ornament]: $@\n Please choose a different color (Hint: try 'o conf init /color/')\n"; } # GGOLDBACH/Test-GreaterVersion-0.008 broke without this # $trailer construct. We want the newline be the last thing if # there is a newline at the end ensuring that the next line is # empty for other players my $trailer = ""; $trailer = $1 if $swhat =~ s/([\r\n]+)\z//; print $color_on, $swhat, Term::ANSIColor::color("reset"), $trailer; } else { print $swhat; } } #-> sub CPAN::Shell::myprint ; # where is myprint/mywarn/Frontend/etc. documented? Where to use what? # I think, we send everything to STDOUT and use print for normal/good # news and warn for news that need more attention. Yes, this is our # working contract for now. sub myprint { my($self,$what) = @_; $self->print_ornamented($what, $CPAN::Config->{colorize_print}||'bold blue on_white', ); } my %already_printed; #-> sub CPAN::Shell::mywarnonce ; sub myprintonce { my($self,$what) = @_; $self->myprint($what) unless $already_printed{$what}++; } sub optprint { my($self,$category,$what) = @_; my $vname = $category . "_verbosity"; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; if (!$CPAN::Config->{$vname} || $CPAN::Config->{$vname} =~ /^v/ ) { $CPAN::Frontend->myprint($what); } } #-> sub CPAN::Shell::myexit ; sub myexit { my($self,$what) = @_; $self->myprint($what); exit; } #-> sub CPAN::Shell::mywarn ; sub mywarn { my($self,$what) = @_; $self->print_ornamented($what, $CPAN::Config->{colorize_warn}||'bold red on_white'); } my %already_warned; #-> sub CPAN::Shell::mywarnonce ; sub mywarnonce { my($self,$what) = @_; $self->mywarn($what) unless $already_warned{$what}++; } # only to be used for shell commands #-> sub CPAN::Shell::mydie ; sub mydie { my($self,$what) = @_; $self->mywarn($what); # If it is the shell, we want the following die to be silent, # but if it is not the shell, we would need a 'die $what'. We need # to take care that only shell commands use mydie. Is this # possible? die "\n"; } # sub CPAN::Shell::colorable_makemaker_prompt ; sub colorable_makemaker_prompt { my($foo,$bar,$ornament) = @_; $ornament ||= "colorize_print"; if (CPAN::Shell->colorize_output) { my $ornament = $CPAN::Config->{$ornament}||'bold blue on_white'; my $color_on = eval { Term::ANSIColor::color($ornament); } || ""; print $color_on; } my $ans = ExtUtils::MakeMaker::prompt($foo,$bar); if (CPAN::Shell->colorize_output) { print Term::ANSIColor::color('reset'); } return $ans; } # use this only for unrecoverable errors! #-> sub CPAN::Shell::unrecoverable_error ; sub unrecoverable_error { my($self,$what) = @_; my @lines = split /\n/, $what; my $longest = 0; for my $l (@lines) { $longest = length $l if length $l > $longest; } $longest = 62 if $longest > 62; for my $l (@lines) { if ($l =~ /^\s*$/) { $l = "\n"; next; } $l = "==> $l"; if (length $l < 66) { $l = pack "A66 A*", $l, "<=="; } $l .= "\n"; } unshift @lines, "\n"; $self->mydie(join "", @lines); } #-> sub CPAN::Shell::mysleep ; sub mysleep { return if $ENV{AUTOMATED_TESTING} || ! -t STDOUT; my($self, $sleep) = @_; if (CPAN->has_inst("Time::HiRes")) { Time::HiRes::sleep($sleep); } else { sleep($sleep < 1 ? 1 : int($sleep + 0.5)); } } #-> sub CPAN::Shell::setup_output ; sub setup_output { return if -t STDOUT; my $odef = select STDERR; $| = 1; select STDOUT; $| = 1; select $odef; } #-> sub CPAN::Shell::rematein ; # RE-adme||MA-ke||TE-st||IN-stall : nearly everything runs through here sub rematein { my $self = shift; # this variable was global and disturbed programmers, so localize: local $CPAN::Distrostatus::something_has_failed_at; my($meth,@some) = @_; my @pragma; while($meth =~ /^(ff?orce|notest)$/) { push @pragma, $meth; $meth = shift @some or $CPAN::Frontend->mydie("Pragma $pragma[-1] used without method: ". "cannot continue"); } setup_output(); CPAN->debug("pragma[@pragma]meth[$meth]some[@some]") if $CPAN::DEBUG; # Here is the place to set "test_count" on all involved parties to # 0. We then can pass this counter on to the involved # distributions and those can refuse to test if test_count > X. In # the first stab at it we could use a 1 for "X". # But when do I reset the distributions to start with 0 again? # Jost suggested to have a random or cycling interaction ID that # we pass through. But the ID is something that is just left lying # around in addition to the counter, so I'd prefer to set the # counter to 0 now, and repeat at the end of the loop. But what # about dependencies? They appear later and are not reset, they # enter the queue but not its copy. How do they get a sensible # test_count? # With configure_requires, "get" is vulnerable in recursion. my $needs_recursion_protection = "get|make|test|install"; # construct the queue my($s,@s,@qcopy); STHING: foreach $s (@some) { my $obj; if (ref $s) { CPAN->debug("s is an object[$s]") if $CPAN::DEBUG; $obj = $s; } elsif ($s =~ m|[\$\@\%]|) { # looks like a perl variable } elsif ($s =~ m|^/|) { # looks like a regexp if (substr($s,-1,1) eq ".") { $obj = CPAN::Shell->expandany($s); } else { my @obj; CLASS: for my $class (qw(Distribution Bundle Module)) { if (@obj = $self->expand($class,$s)) { last CLASS; } } if (@obj) { if (1==@obj) { $obj = $obj[0]; } else { $CPAN::Frontend->mywarn("Sorry, $meth with a regular expression is ". "only supported when unambiguous.\nRejecting argument '$s'\n"); $CPAN::Frontend->mysleep(2); next STHING; } } } } elsif ($meth eq "ls") { $self->globls($s,\@pragma); next STHING; } else { CPAN->debug("calling expandany [$s]") if $CPAN::DEBUG; $obj = CPAN::Shell->expandany($s); } if (0) { } elsif (ref $obj) { if ($meth =~ /^($needs_recursion_protection)$/) { # it would be silly to check for recursion for look or dump # (we are in CPAN::Shell::rematein) CPAN->debug("Testing against recursion") if $CPAN::DEBUG; eval { $obj->color_cmd_tmps(0,1); }; if ($@) { if (ref $@ and $@->isa("CPAN::Exception::RecursiveDependency")) { $CPAN::Frontend->mywarn($@); } else { if (0) { require Carp; Carp::confess(sprintf "DEBUG: \$\@[%s]ref[%s]", $@, ref $@); } die; } } } CPAN::Queue->queue_item(qmod => $obj->id, reqtype => "c", optional => ''); push @qcopy, $obj; } elsif ($CPAN::META->exists('CPAN::Author',uc($s))) { $obj = $CPAN::META->instance('CPAN::Author',uc($s)); if ($meth =~ /^(dump|ls|reports)$/) { $obj->$meth(); } else { $CPAN::Frontend->mywarn( join "", "Don't be silly, you can't $meth ", $obj->fullname, " ;-)\n" ); $CPAN::Frontend->mysleep(2); } } elsif ($s =~ m|[\$\@\%]| && $meth eq "dump") { CPAN::InfoObj->dump($s); } else { $CPAN::Frontend ->mywarn(qq{Warning: Cannot $meth $s, }. qq{don't know what it is. Try the command i /$s/ to find objects with matching identifiers. }); $CPAN::Frontend->mysleep(2); } } # queuerunner (please be warned: when I started to change the # queue to hold objects instead of names, I made one or two # mistakes and never found which. I reverted back instead) QITEM: while (my $q = CPAN::Queue->first) { my $obj; my $s = $q->as_string; my $reqtype = $q->reqtype || ""; my $optional = $q->optional || ""; $obj = CPAN::Shell->expandany($s); unless ($obj) { # don't know how this can happen, maybe we should panic, # but maybe we get a solution from the first user who hits # this unfortunate exception? $CPAN::Frontend->mywarn("Warning: Could not expand string '$s' ". "to an object. Skipping.\n"); $CPAN::Frontend->mysleep(5); CPAN::Queue->delete_first($s); next QITEM; } $obj->{reqtype} ||= ""; my $type = ref $obj; if ( $type eq 'CPAN::Distribution' || $type eq 'CPAN::Bundle' ) { $obj->{mandatory} ||= ! $optional; # once mandatory, always mandatory } elsif ( $type eq 'CPAN::Module' ) { $obj->{mandatory} ||= ! $optional; # once mandatory, always mandatory if (my $d = $obj->distribution) { $d->{mandatory} ||= ! $optional; # once mandatory, always mandatory } elsif ($optional) { # the queue object does not know who was recommending/suggesting us:( # So we only vaguely write "optional". $CPAN::Frontend->mywarn("Warning: optional module '$s' ". "not known. Skipping.\n"); CPAN::Queue->delete_first($s); next QITEM; } } { # force debugging because CPAN::SQLite somehow delivers us # an empty object; # local $CPAN::DEBUG = 1024; # Shell; probably fixed now CPAN->debug("s[$s]obj-reqtype[$obj->{reqtype}]". "q-reqtype[$reqtype]") if $CPAN::DEBUG; } if ($obj->{reqtype}) { if ($obj->{reqtype} eq "b" && $reqtype =~ /^[rc]$/) { $obj->{reqtype} = $reqtype; if ( exists $obj->{install} && ( UNIVERSAL::can($obj->{install},"failed") ? $obj->{install}->failed : $obj->{install} =~ /^NO/ ) ) { delete $obj->{install}; $CPAN::Frontend->mywarn ("Promoting $obj->{ID} from 'build_requires' to 'requires'"); } } } else { $obj->{reqtype} = $reqtype; } for my $pragma (@pragma) { if ($pragma && $obj->can($pragma)) { $obj->$pragma($meth); } } if (UNIVERSAL::can($obj, 'called_for')) { $obj->called_for($s) unless $obj->called_for; } CPAN->debug(qq{pragma[@pragma]meth[$meth]}. qq{ID[$obj->{ID}]}) if $CPAN::DEBUG; push @qcopy, $obj; if ($meth =~ /^(report)$/) { # they came here with a pragma? $self->$meth($obj); } elsif (! UNIVERSAL::can($obj,$meth)) { # Must never happen my $serialized = ""; if (0) { } elsif ($CPAN::META->has_inst("YAML::Syck")) { $serialized = YAML::Syck::Dump($obj); } elsif ($CPAN::META->has_inst("YAML")) { $serialized = YAML::Dump($obj); } elsif ($CPAN::META->has_inst("Data::Dumper")) { $serialized = Data::Dumper::Dumper($obj); } else { require overload; $serialized = overload::StrVal($obj); } CPAN->debug("Going to panic. meth[$meth]s[$s]") if $CPAN::DEBUG; $CPAN::Frontend->mydie("Panic: obj[$serialized] cannot meth[$meth]"); } else { my $upgraded_meth = $meth; if ( $meth eq "make" and $obj->{reqtype} eq "b" ) { # rt 86915 $upgraded_meth = "test"; } if ($obj->$upgraded_meth()) { CPAN::Queue->delete($s); CPAN->debug("Succeeded and deleted from queue. pragma[@pragma]meth[$meth][s][$s]") if $CPAN::DEBUG; } else { CPAN->debug("Failed. pragma[@pragma]meth[$meth]s[$s]") if $CPAN::DEBUG; } } $obj->undelay; for my $pragma (@pragma) { my $unpragma = "un$pragma"; if ($obj->can($unpragma)) { $obj->$unpragma(); } } # if any failures occurred and the current object is mandatory, we # still don't know if *it* failed or if it was another (optional) # module, so we have to check that explicitly (and expensively) if ( $CPAN::Config->{halt_on_failure} && $obj->{mandatory} && CPAN::Distrostatus::something_has_just_failed() && $self->mandatory_dist_failed() ) { $CPAN::Frontend->mywarn("Stopping: '$meth' failed for '$s'.\n"); CPAN::Queue->nullify_queue; last QITEM; } CPAN::Queue->delete_first($s); } if ($meth =~ /^($needs_recursion_protection)$/) { for my $obj (@qcopy) { $obj->color_cmd_tmps(0,0); } } } #-> sub CPAN::Shell::recent ; sub recent { my($self) = @_; if ($CPAN::META->has_inst("XML::LibXML")) { my $url = $CPAN::Defaultrecent; $CPAN::Frontend->myprint("Fetching '$url'\n"); unless ($CPAN::META->has_usable("LWP")) { $CPAN::Frontend->mydie("LWP not installed; cannot continue"); } CPAN::LWP::UserAgent->config; my $Ua; eval { $Ua = CPAN::LWP::UserAgent->new; }; if ($@) { $CPAN::Frontend->mydie("CPAN::LWP::UserAgent->new dies with $@\n"); } my $resp = $Ua->get($url); unless ($resp->is_success) { $CPAN::Frontend->mydie(sprintf "Could not download '%s': %s\n", $url, $resp->code); } $CPAN::Frontend->myprint("DONE\n\n"); my $xml = XML::LibXML->new->parse_string($resp->content); if (0) { my $s = $xml->serialize(2); $s =~ s/\n\s*\n/\n/g; $CPAN::Frontend->myprint($s); return; } my @distros; if ($url =~ /winnipeg/) { my $pubdate = $xml->findvalue("/rss/channel/pubDate"); $CPAN::Frontend->myprint(" pubDate: $pubdate\n\n"); for my $eitem ($xml->findnodes("/rss/channel/item")) { my $distro = $eitem->findvalue("enclosure/\@url"); $distro =~ s|.*?/authors/id/./../||; my $size = $eitem->findvalue("enclosure/\@length"); my $desc = $eitem->findvalue("description"); $desc =~ s/.+? - //; $CPAN::Frontend->myprint("$distro [$size b]\n $desc\n"); push @distros, $distro; } } elsif ($url =~ /search.*uploads.rdf/) { # xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" # xmlns="http://purl.org/rss/1.0/" # xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" # xmlns:dc="http://purl.org/dc/elements/1.1/" # xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" # xmlns:admin="http://webns.net/mvcb/" my $dc_date = $xml->findvalue("//*[local-name(.) = 'RDF']/*[local-name(.) = 'channel']/*[local-name(.) = 'date']"); $CPAN::Frontend->myprint(" dc:date: $dc_date\n\n"); my $finish_eitem = 0; local $SIG{INT} = sub { $finish_eitem = 1 }; EITEM: for my $eitem ($xml->findnodes("//*[local-name(.) = 'RDF']/*[local-name(.) = 'item']")) { my $distro = $eitem->findvalue("\@rdf:about"); $distro =~ s|.*~||; # remove up to the tilde before the name $distro =~ s|/$||; # remove trailing slash $distro =~ s|([^/]+)|\U$1\E|; # upcase the name my $author = uc $1 or die "distro[$distro] without author, cannot continue"; my $desc = $eitem->findvalue("*[local-name(.) = 'description']"); my $i = 0; SUBDIRTEST: while () { last SUBDIRTEST if ++$i >= 6; # half a dozen must do! if (my @ret = $self->globls("$distro*")) { @ret = grep {$_->[2] !~ /meta/} @ret; @ret = grep {length $_->[2]} @ret; if (@ret) { $distro = "$author/$ret[0][2]"; last SUBDIRTEST; } } $distro =~ s|/|/*/|; # allow it to reside in a subdirectory } next EITEM if $distro =~ m|\*|; # did not find the thing $CPAN::Frontend->myprint("____$desc\n"); push @distros, $distro; last EITEM if $finish_eitem; } } return \@distros; } else { # deprecated old version $CPAN::Frontend->mydie("no XML::LibXML installed, cannot continue\n"); } } #-> sub CPAN::Shell::smoke ; sub smoke { my($self) = @_; my $distros = $self->recent; DISTRO: for my $distro (@$distros) { next if $distro =~ m|/Bundle-|; # XXX crude heuristic to skip bundles $CPAN::Frontend->myprint(sprintf "Downloading and testing '$distro'\n"); { my $skip = 0; local $SIG{INT} = sub { $skip = 1 }; for (0..9) { $CPAN::Frontend->myprint(sprintf "\r%2d (Hit ^C to skip)", 10-$_); sleep 1; if ($skip) { $CPAN::Frontend->myprint(" skipped\n"); next DISTRO; } } } $CPAN::Frontend->myprint("\r \n"); # leave the dirty line with a newline $self->test($distro); } } { # set up the dispatching methods no strict "refs"; for my $command (qw( clean cvs_import dump force fforce get install look ls make notest perldoc readme reports test )) { *$command = sub { shift->rematein($command, @_); }; } } 1; PK2]naX677 Prompt.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Prompt; use overload '""' => "as_string"; use vars qw($prompt); use vars qw( $VERSION ); $VERSION = "5.5"; $prompt = "cpan> "; $CPAN::CurrentCommandId ||= 0; sub new { bless {}, shift; } sub as_string { my $word = "cpan"; unless ($CPAN::META->{LOCK}) { $word = "nolock_cpan"; } if ($CPAN::Config->{commandnumber_in_prompt}) { sprintf "$word\[%d]> ", $CPAN::CurrentCommandId; } else { "$word> "; } } 1; PK2]: Complete.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Complete; use strict; @CPAN::Complete::ISA = qw(CPAN::Debug); # Q: where is the "How do I add a new command" HOWTO? # A: git log -p -1 355c44e9caaec857e4b12f51afb96498833c3e36 where andk added the report command @CPAN::Complete::COMMANDS = sort qw( ? ! a b d h i m o q r u autobundle bye clean cvs_import dump exit failed force fforce hosts install install_tested is_tested look ls make mkmyconfig notest perldoc quit readme recent recompile reload report reports scripts smoke test upgrade ); use vars qw( $VERSION ); $VERSION = "5.5001"; package CPAN::Complete; use strict; sub gnu_cpl { my($text, $line, $start, $end) = @_; my(@perlret) = cpl($text, $line, $start); # find longest common match. Can anybody show me how to peruse # T::R::Gnu to have this done automatically? Seems expensive. return () unless @perlret; my($newtext) = $text; for (my $i = length($text)+1;;$i++) { last unless length($perlret[0]) && length($perlret[0]) >= $i; my $try = substr($perlret[0],0,$i); my @tries = grep {substr($_,0,$i) eq $try} @perlret; # warn "try[$try]tries[@tries]"; if (@tries == @perlret) { $newtext = $try; } else { last; } } ($newtext,@perlret); } #-> sub CPAN::Complete::cpl ; sub cpl { my($word,$line,$pos) = @_; $word ||= ""; $line ||= ""; $pos ||= 0; CPAN->debug("word [$word] line[$line] pos[$pos]") if $CPAN::DEBUG; $line =~ s/^\s*//; if ($line =~ s/^((?:notest|f?force)\s*)//) { $pos -= length($1); } my @return; if ($pos == 0 || $line =~ /^(?:h(?:elp)?|\?)\s/) { @return = grep /^\Q$word\E/, @CPAN::Complete::COMMANDS; } elsif ( $line !~ /^[\!abcdghimorutl]/ ) { @return = (); } elsif ($line =~ /^a\s/) { @return = cplx('CPAN::Author',uc($word)); } elsif ($line =~ /^ls\s/) { my($author,$rest) = $word =~ m|([^/]+)/?(.*)|; @return = $rest ? () : map {"$_/"} cplx('CPAN::Author',uc($author||"")); if (0 && 1==@return) { # XXX too slow and even wrong when there is a * already @return = grep /^\Q$word\E/, map {"$author/$_->[2]"} CPAN::Shell->expand("Author",$author)->ls("$rest*","2"); } } elsif ($line =~ /^b\s/) { CPAN::Shell->local_bundles; @return = cplx('CPAN::Bundle',$word); } elsif ($line =~ /^d\s/) { @return = cplx('CPAN::Distribution',$word); } elsif ($line =~ m/^( [mru]|make|clean|dump|get|test|install|readme|look|cvs_import|perldoc|recent )\s/x ) { if ($word =~ /^Bundle::/) { CPAN::Shell->local_bundles; } @return = (cplx('CPAN::Module',$word),cplx('CPAN::Bundle',$word)); } elsif ($line =~ /^i\s/) { @return = cpl_any($word); } elsif ($line =~ /^reload\s/) { @return = cpl_reload($word,$line,$pos); } elsif ($line =~ /^o\s/) { @return = cpl_option($word,$line,$pos); } elsif ($line =~ m/^\S+\s/ ) { # fallback for future commands and what we have forgotten above @return = (cplx('CPAN::Module',$word),cplx('CPAN::Bundle',$word)); } else { @return = (); } return @return; } #-> sub CPAN::Complete::cplx ; sub cplx { my($class, $word) = @_; if (CPAN::_sqlite_running()) { $CPAN::SQLite->search($class, "^\Q$word\E"); } my $method = "id"; $method = "pretty_id" if $class eq "CPAN::Distribution"; sort grep /^\Q$word\E/, map { $_->$method() } $CPAN::META->all_objects($class); } #-> sub CPAN::Complete::cpl_any ; sub cpl_any { my($word) = shift; return ( cplx('CPAN::Author',$word), cplx('CPAN::Bundle',$word), cplx('CPAN::Distribution',$word), cplx('CPAN::Module',$word), ); } #-> sub CPAN::Complete::cpl_reload ; sub cpl_reload { my($word,$line,$pos) = @_; $word ||= ""; my(@words) = split " ", $line; CPAN->debug("word[$word] line[$line] pos[$pos]") if $CPAN::DEBUG; my(@ok) = qw(cpan index); return @ok if @words == 1; return grep /^\Q$word\E/, @ok if @words == 2 && $word; } #-> sub CPAN::Complete::cpl_option ; sub cpl_option { my($word,$line,$pos) = @_; $word ||= ""; my(@words) = split " ", $line; CPAN->debug("word[$word] line[$line] pos[$pos]") if $CPAN::DEBUG; my(@ok) = qw(conf debug); return @ok if @words == 1; return grep /^\Q$word\E/, @ok if @words == 2 && length($word); if (0) { } elsif ($words[1] eq 'index') { return (); } elsif ($words[1] eq 'conf') { return CPAN::HandleConfig::cpl(@_); } elsif ($words[1] eq 'debug') { return sort grep /^\Q$word\E/i, sort keys %CPAN::DEBUG, 'all'; } } 1; PK2]Ʈޒw^w^HandleConfig.pmnu6$package CPAN::HandleConfig; use strict; use vars qw(%can %keys $loading $VERSION); use File::Path (); use File::Spec (); use File::Basename (); use Carp (); =head1 NAME CPAN::HandleConfig - internal configuration handling for CPAN.pm =cut $VERSION = "5.5012"; # see also CPAN::Config::VERSION at end of file %can = ( commit => "Commit changes to disk", defaults => "Reload defaults from disk", help => "Short help about 'o conf' usage", init => "Interactive setting of all options", ); # Q: where is the "How do I add a new config option" HOWTO? # A1: svn diff -r 757:758 # where dagolden added test_report [git e997b71de88f1019a1472fc13cb97b1b7f96610f] # A2: svn diff -r 985:986 # where andk added yaml_module [git 312b6d9b12b1bdec0b6e282d853482145475021f] # A3: 1. add new config option to %keys below # 2. add a Pod description in CPAN::FirstTime in the DESCRIPTION # section; it should include a prompt line; see others for # examples # 3. add a "matcher" section in CPAN::FirstTime::init that includes # a prompt function; see others for examples # 4. add config option to documentation section in CPAN.pm %keys = map { $_ => undef } ( "allow_installing_module_downgrades", "allow_installing_outdated_dists", "applypatch", "auto_commit", "build_cache", "build_dir", "build_dir_reuse", "build_requires_install_policy", "bzip2", "cache_metadata", "check_sigs", "cleanup_after_install", "colorize_debug", "colorize_output", "colorize_print", "colorize_warn", "commandnumber_in_prompt", "commands_quote", "connect_to_internet_ok", "cpan_home", "curl", "dontload_hash", # deprecated after 1.83_68 (rev. 581) "dontload_list", "ftp", "ftp_passive", "ftp_proxy", "ftpstats_size", "ftpstats_period", "getcwd", "gpg", "gzip", "halt_on_failure", "histfile", "histsize", "http_proxy", "inactivity_timeout", "index_expire", "inhibit_startup_message", "keep_source_where", "load_module_verbosity", "lynx", "make", "make_arg", "make_install_arg", "make_install_make_command", "makepl_arg", "mbuild_arg", "mbuild_install_arg", "mbuild_install_build_command", "mbuildpl_arg", "ncftp", "ncftpget", "no_proxy", "pager", "password", "patch", "patches_dir", "perl5lib_verbosity", "plugin_list", "prefer_external_tar", "prefer_installer", "prefs_dir", "prerequisites_policy", "proxy_pass", "proxy_user", "pushy_https", "randomize_urllist", "recommends_policy", "scan_cache", "shell", "show_unparsable_versions", "show_upload_date", "show_zero_versions", "suggests_policy", "tar", "tar_verbosity", "term_is_latin", "term_ornaments", "test_report", "trust_test_report_history", "unzip", "urllist", "urllist_ping_verbose", "urllist_ping_external", "use_prompt_default", "use_sqlite", "username", "version_timeout", "wait_list", "wget", "yaml_load_code", "yaml_module", ); my %prefssupport = map { $_ => 1 } ( "allow_installing_module_downgrades", "allow_installing_outdated_dists", "build_requires_install_policy", "check_sigs", "make", "make_install_make_command", "prefer_installer", "test_report", ); # returns true on successful action sub edit { my($self,@args) = @_; return unless @args; CPAN->debug("self[$self]args[".join(" | ",@args)."]"); my($o,$str,$func,$args,$key_exists); $o = shift @args; if($can{$o}) { my $success = $self->$o(args => \@args); # o conf init => sub init => sub load unless ($success) { die "Panic: could not configure CPAN.pm for args [@args]. Giving up."; } } else { CPAN->debug("o[$o]") if $CPAN::DEBUG; unless (exists $keys{$o}) { $CPAN::Frontend->mywarn("Warning: unknown configuration variable '$o'\n"); } my $changed; # one day I used randomize_urllist for a boolean, so we must # list them explicitly --ak if (0) { } elsif ($o =~ /^(wait_list|urllist|dontload_list|plugin_list)$/) { # # ARRAYS # $func = shift @args; $func ||= ""; CPAN->debug("func[$func]args[@args]") if $CPAN::DEBUG; # Let's avoid eval, it's easier to comprehend without. if ($func eq "push") { push @{$CPAN::Config->{$o}}, @args; $changed = 1; } elsif ($func eq "pop") { pop @{$CPAN::Config->{$o}}; $changed = 1; } elsif ($func eq "shift") { shift @{$CPAN::Config->{$o}}; $changed = 1; } elsif ($func eq "unshift") { unshift @{$CPAN::Config->{$o}}, @args; $changed = 1; } elsif ($func eq "splice") { my $offset = shift @args || 0; my $length = shift @args || 0; splice @{$CPAN::Config->{$o}}, $offset, $length, @args; # may warn $changed = 1; } elsif ($func) { $CPAN::Config->{$o} = [$func, @args]; $changed = 1; } else { $self->prettyprint($o); } if ($changed) { if ($o eq "urllist") { # reset the cached values undef $CPAN::FTP::Thesite; undef $CPAN::FTP::Themethod; $CPAN::Index::LAST_TIME = 0; } elsif ($o eq "dontload_list") { # empty it, it will be built up again $CPAN::META->{dontload_hash} = {}; } } } elsif ($o =~ /_hash$/) { # # HASHES # if (@args==1 && $args[0] eq "") { @args = (); } elsif (@args % 2) { push @args, ""; } $CPAN::Config->{$o} = { @args }; $changed = 1; } else { # # SCALARS # if (defined $args[0]) { $CPAN::CONFIG_DIRTY = 1; $CPAN::Config->{$o} = $args[0]; $changed = 1; } $self->prettyprint($o) if exists $keys{$o} or defined $CPAN::Config->{$o}; } if ($changed) { if ($CPAN::Config->{auto_commit}) { $self->commit; } else { $CPAN::CONFIG_DIRTY = 1; $CPAN::Frontend->myprint("Please use 'o conf commit' to ". "make the config permanent!\n\n"); } } } } sub prettyprint { my($self,$k) = @_; my $v = $CPAN::Config->{$k}; if (ref $v) { my(@report); if (ref $v eq "ARRAY") { @report = map {"\t$_ \[$v->[$_]]\n"} 0..$#$v; } else { @report = map { sprintf "\t%-18s => %s\n", "[$_]", defined $v->{$_} ? "[$v->{$_}]" : "undef" } sort keys %$v; } $CPAN::Frontend->myprint( join( "", sprintf( " %-18s\n", $k ), @report ) ); } elsif (defined $v) { $CPAN::Frontend->myprint(sprintf " %-18s [%s]\n", $k, $v); } else { $CPAN::Frontend->myprint(sprintf " %-18s undef\n", $k); } } # generally, this should be called without arguments so that the currently # loaded config file is where changes are committed. sub commit { my($self,@args) = @_; CPAN->debug("args[@args]") if $CPAN::DEBUG; if ($CPAN::RUN_DEGRADED) { $CPAN::Frontend->mydie( "'o conf commit' disabled in ". "degraded mode. Maybe try\n". " !undef \$CPAN::RUN_DEGRADED\n" ); } my ($configpm, $must_reload); # XXX does anything do this? can it be simplified? -- dagolden, 2011-01-19 if (@args) { if ($args[0] eq "args") { # we have not signed that contract } else { $configpm = $args[0]; } } # use provided name or the current config or create a new MyConfig $configpm ||= require_myconfig_or_config() || make_new_config(); # commit to MyConfig if we can't write to Config if ( ! -w $configpm && $configpm =~ m{CPAN/Config\.pm} ) { my $myconfig = _new_config_name(); $CPAN::Frontend->mywarn( "Your $configpm file\n". "is not writable. I will attempt to write your configuration to\n" . "$myconfig instead.\n\n" ); $configpm = make_new_config(); $must_reload++; # so it gets loaded as $INC{'CPAN/MyConfig.pm'} } # XXX why not just "-w $configpm"? -- dagolden, 2011-01-19 my($mode); if (-f $configpm) { $mode = (stat $configpm)[2]; if ($mode && ! -w _) { _die_cant_write_config($configpm); } } $self->_write_config_file($configpm); require_myconfig_or_config() if $must_reload; #$mode = 0444 | ( $mode & 0111 ? 0111 : 0 ); #chmod $mode, $configpm; ###why was that so? $self->defaults; $CPAN::Frontend->myprint("commit: wrote '$configpm'\n"); $CPAN::CONFIG_DIRTY = 0; 1; } sub _write_config_file { my ($self, $configpm) = @_; my $msg; $msg = <new; rename $configpm, "$configpm~" if -f $configpm; open $fh, ">$configpm" or $CPAN::Frontend->mydie("Couldn't open >$configpm: $!"); $fh->print(qq[$msg\$CPAN::Config = \{\n]); foreach (sort keys %$CPAN::Config) { unless (exists $keys{$_}) { # do not drop them: forward compatibility! $CPAN::Frontend->mywarn("Unknown config variable '$_'\n"); next; } $fh->print( " '$_' => ", $self->neatvalue($CPAN::Config->{$_}), ",\n" ); } $fh->print("};\n1;\n__END__\n"); close $fh; return; } # stolen from MakeMaker; not taking the original because it is buggy; # bugreport will have to say: keys of hashes remain unquoted and can # produce syntax errors sub neatvalue { my($self, $v) = @_; return "undef" unless defined $v; my($t) = ref $v; unless ($t) { $v =~ s/\\/\\\\/g; return "q[$v]"; } if ($t eq 'ARRAY') { my(@m, @neat); push @m, "["; foreach my $elem (@$v) { push @neat, "q[$elem]"; } push @m, join ", ", @neat; push @m, "]"; return join "", @m; } return "$v" unless $t eq 'HASH'; my @m; foreach my $key (sort keys %$v) { my $val = $v->{$key}; push(@m,"q[$key]=>".$self->neatvalue($val)) ; } return "{ ".join(', ',@m)." }"; } sub defaults { my($self) = @_; if ($CPAN::RUN_DEGRADED) { $CPAN::Frontend->mydie( "'o conf defaults' disabled in ". "degraded mode. Maybe try\n". " !undef \$CPAN::RUN_DEGRADED\n" ); } my $done; for my $config (qw(CPAN/MyConfig.pm CPAN/Config.pm)) { if ($INC{$config}) { CPAN->debug("INC{'$config'}[$INC{$config}]") if $CPAN::DEBUG; CPAN::Shell->_reload_this($config,{reloforce => 1}); $CPAN::Frontend->myprint("'$INC{$config}' reread\n"); last; } } $CPAN::CONFIG_DIRTY = 0; 1; } =head2 C<< CLASS->safe_quote ITEM >> Quotes an item to become safe against spaces in shell interpolation. An item is enclosed in double quotes if: - the item contains spaces in the middle - the item does not start with a quote This happens to avoid shell interpolation problems when whitespace is present in directory names. This method uses C to determine the correct quote. If C is a space, no quoting will take place. if it starts and ends with the same quote character: leave it as it is if it contains no whitespace: leave it as it is if it contains whitespace, then if it contains quotes: better leave it as it is else: quote it with the correct quote type for the box we're on =cut { # Instead of patching the guess, set commands_quote # to the right value my ($quotes,$use_quote) = $^O eq 'MSWin32' ? ('"', '"') : (q{"'}, "'") ; sub safe_quote { my ($self, $command) = @_; # Set up quote/default quote my $quote = $CPAN::Config->{commands_quote} || $quotes; if ($quote ne ' ' and defined($command ) and $command =~ /\s/ and $command !~ /[$quote]/) { return qq<$use_quote$command$use_quote> } return $command; } } sub init { my($self,@args) = @_; CPAN->debug("self[$self]args[".join(",",@args)."]"); $self->load(do_init => 1, @args); 1; } # Loads CPAN::MyConfig or fall-back to CPAN::Config. Will not reload a file # if already loaded. Returns the path to the file %INC or else the empty string # # Note -- if CPAN::Config were loaded and CPAN::MyConfig subsequently # created, calling this again will leave *both* in %INC sub require_myconfig_or_config () { if ( $INC{"CPAN/MyConfig.pm"} || _try_loading("CPAN::MyConfig", cpan_home())) { return $INC{"CPAN/MyConfig.pm"}; } elsif ( $INC{"CPAN/Config.pm"} || _try_loading("CPAN::Config") ) { return $INC{"CPAN/Config.pm"}; } else { return q{}; } } # Load a module, but ignore "can't locate..." errors # Optionally take a list of directories to add to @INC for the load sub _try_loading { my ($module, @dirs) = @_; (my $file = $module) =~ s{::}{/}g; $file .= ".pm"; local @INC = @INC; for my $dir ( @dirs ) { if ( -f File::Spec->catfile($dir, $file) ) { unshift @INC, $dir; last; } } eval { require $file }; my $err_myconfig = $@; if ($err_myconfig and $err_myconfig !~ m#locate \Q$file\E#) { die "Error while requiring ${module}:\n$err_myconfig"; } return $INC{$file}; } # prioritized list of possible places for finding "CPAN/MyConfig.pm" sub cpan_home_dir_candidates { my @dirs; my $old_v = $CPAN::Config->{load_module_verbosity}; $CPAN::Config->{load_module_verbosity} = q[none]; if ($CPAN::META->has_usable('File::HomeDir')) { if ($^O ne 'darwin') { push @dirs, File::HomeDir->my_data; # my_data is ~/Library/Application Support on darwin, # which causes issues in the toolchain. } push @dirs, File::HomeDir->my_home; } # Windows might not have HOME, so check it first push @dirs, $ENV{HOME} if $ENV{HOME}; # Windows might have these instead push( @dirs, File::Spec->catpath($ENV{HOMEDRIVE}, $ENV{HOMEPATH}, '') ) if $ENV{HOMEDRIVE} && $ENV{HOMEPATH}; push @dirs, $ENV{USERPROFILE} if $ENV{USERPROFILE}; $CPAN::Config->{load_module_verbosity} = $old_v; my $dotcpan = $^O eq 'VMS' ? '_cpan' : '.cpan'; @dirs = map { File::Spec->catdir($_, $dotcpan) } grep { defined } @dirs; return wantarray ? @dirs : $dirs[0]; } sub load { my($self, %args) = @_; $CPAN::Be_Silent+=0; # protect against 'used only once' $CPAN::Be_Silent++ if $args{be_silent}; # do not use; planned to be removed in 2011 my $do_init = delete $args{do_init} || 0; my $make_myconfig = delete $args{make_myconfig}; $loading = 0 unless defined $loading; my $configpm = require_myconfig_or_config; my @miss = $self->missing_config_data; CPAN->debug("do_init[$do_init]loading[$loading]miss[@miss]") if $CPAN::DEBUG; return unless $do_init || @miss; if (@miss==1 and $miss[0] eq "pushy_https" && !$do_init) { $CPAN::Frontend->myprint(<<'END'); Starting with version 2.29 of the cpan shell, a new download mechanism is the default which exclusively uses cpan.org as the host to download from. The configuration variable pushy_https can be used to (de)select the new mechanism. Please read more about it and make your choice between the old and the new mechanism by running o conf init pushy_https Once you have done that and stored the config variable this dialog will disappear. END return; } # I'm not how we'd ever wind up in a recursive loop, but I'm leaving # this here for safety's sake -- dagolden, 2011-01-19 return if $loading; local $loading = ($loading||0) + 1; # Warn if we have a config file, but things were found missing if ($configpm && @miss && !$do_init) { if ($make_myconfig || ( ! -w $configpm && $configpm =~ m{CPAN/Config\.pm})) { $configpm = make_new_config(); $CPAN::Frontend->myprint(<> END } else { $CPAN::Frontend->myprint(<> END } } require CPAN::FirstTime; return CPAN::FirstTime::init($configpm || make_new_config(), %args); } # Creates a new, empty config file at the preferred location # Any existing will be renamed with a ".bak" suffix if possible # If the file cannot be created, an exception is thrown sub make_new_config { my $configpm = _new_config_name(); my $configpmdir = File::Basename::dirname( $configpm ); File::Path::mkpath($configpmdir) unless -d $configpmdir; if ( -w $configpmdir ) { #_#_# following code dumped core on me with 5.003_11, a.k. if( -f $configpm ) { my $configpm_bak = "$configpm.bak"; unlink $configpm_bak if -f $configpm_bak; if( rename $configpm, $configpm_bak ) { $CPAN::Frontend->mywarn(<new; if ($fh->open(">$configpm")) { $fh->print("1;\n"); return $configpm; } } _die_cant_write_config($configpm); } sub _die_cant_write_config { my ($configpm) = @_; $CPAN::Frontend->mydie(<<"END"); WARNING: CPAN.pm is unable to write a configuration file. You must be able to create and write to '$configpm'. Aborting configuration. END } # From candidate directories, we would like (in descending preference order): # * the one that contains a MyConfig file # * one that exists (even without MyConfig) # * the first one on the list sub cpan_home { my @dirs = cpan_home_dir_candidates(); for my $d (@dirs) { return $d if -f "$d/CPAN/MyConfig.pm"; } for my $d (@dirs) { return $d if -d $d; } return $dirs[0]; } sub _new_config_name { return File::Spec->catfile(cpan_home(), 'CPAN', 'MyConfig.pm'); } # returns mandatory but missing entries in the Config sub missing_config_data { my(@miss); for ( "auto_commit", "build_cache", "build_dir", "cache_metadata", "cpan_home", "ftp_proxy", #"gzip", "http_proxy", "index_expire", #"inhibit_startup_message", "keep_source_where", #"make", "make_arg", "make_install_arg", "makepl_arg", "mbuild_arg", "mbuild_install_arg", ($^O eq "MSWin32" ? "" : "mbuild_install_build_command"), "mbuildpl_arg", "no_proxy", #"pager", "prerequisites_policy", "pushy_https", "scan_cache", #"tar", #"unzip", "urllist", ) { next unless exists $keys{$_}; push @miss, $_ unless defined $CPAN::Config->{$_}; } return @miss; } sub help { $CPAN::Frontend->myprint(q[ Known options: commit commit session changes to disk defaults reload default config values from disk help this help init enter a dialog to set all or a set of parameters Edit key values as in the following (the "o" is a literal letter o): o conf build_cache 15 o conf build_dir "/foo/bar" o conf urllist shift o conf urllist unshift ftp://ftp.foo.bar/ o conf inhibit_startup_message 1 ]); 1; #don't reprint CPAN::Config } sub cpl { my($word,$line,$pos) = @_; $word ||= ""; CPAN->debug("word[$word] line[$line] pos[$pos]") if $CPAN::DEBUG; my(@words) = split " ", substr($line,0,$pos+1); if ( defined($words[2]) and $words[2] =~ /list$/ and ( @words == 3 || @words == 4 && length($word) ) ) { return grep /^\Q$word\E/, qw(splice shift unshift pop push); } elsif (defined($words[2]) and $words[2] eq "init" and ( @words == 3 || @words >= 4 && length($word) )) { return sort grep /^\Q$word\E/, keys %keys; } elsif (@words >= 4) { return (); } my %seen; my(@o_conf) = sort grep { !$seen{$_}++ } keys %can, keys %$CPAN::Config, keys %keys; return grep /^\Q$word\E/, @o_conf; } sub prefs_lookup { my($self,$distro,$what) = @_; if ($prefssupport{$what}) { return $CPAN::Config->{$what} unless $distro and $distro->prefs and $distro->prefs->{cpanconfig} and defined $distro->prefs->{cpanconfig}{$what}; return $distro->prefs->{cpanconfig}{$what}; } else { $CPAN::Frontend->mywarn("Warning: $what not yet officially ". "supported for distroprefs, doing a normal lookup\n"); return $CPAN::Config->{$what}; } } { package CPAN::Config; ####::###### #hide from indexer # note: J. Nick Koston wrote me that they are using # CPAN::Config->commit although undocumented. I suggested # CPAN::Shell->o("conf","commit") even when ugly it is at least # documented # that's why I added the CPAN::Config class with autoload and # deprecated warning use strict; use vars qw($AUTOLOAD $VERSION); $VERSION = "5.5012"; # formerly CPAN::HandleConfig was known as CPAN::Config sub AUTOLOAD { ## no critic my $class = shift; # e.g. in dh-make-perl: CPAN::Config my($l) = $AUTOLOAD; $CPAN::Frontend->mywarn("Dispatching deprecated method '$l' to CPAN::HandleConfig\n"); $l =~ s/.*:://; CPAN::HandleConfig->$l(@_); } } 1; __END__ =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut # Local Variables: # mode: cperl # cperl-indent-level: 4 # End: # vim: ts=4 sts=4 sw=4: PK2]o CacheMgr.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::CacheMgr; use strict; use CPAN::InfoObj; @CPAN::CacheMgr::ISA = qw(CPAN::InfoObj CPAN); use Cwd qw(chdir); use File::Find; use vars qw( $VERSION ); $VERSION = "5.5002"; package CPAN::CacheMgr; use strict; #-> sub CPAN::CacheMgr::as_string ; sub as_string { eval { require Data::Dumper }; if ($@) { return shift->SUPER::as_string; } else { return Data::Dumper::Dumper(shift); } } #-> sub CPAN::CacheMgr::cachesize ; sub cachesize { shift->{DU}; } #-> sub CPAN::CacheMgr::tidyup ; sub tidyup { my($self) = @_; return unless $CPAN::META->{LOCK}; return unless -d $self->{ID}; my @toremove = grep { $self->{SIZE}{$_}==0 } @{$self->{FIFO}}; for my $current (0..$#toremove) { my $toremove = $toremove[$current]; $CPAN::Frontend->myprint(sprintf( "DEL(%d/%d): %s \n", $current+1, scalar @toremove, $toremove, ) ); return if $CPAN::Signal; $self->_clean_cache($toremove); return if $CPAN::Signal; } $self->{FIFO} = []; } #-> sub CPAN::CacheMgr::dir ; sub dir { shift->{ID}; } #-> sub CPAN::CacheMgr::entries ; sub entries { my($self,$dir) = @_; return unless defined $dir; $self->debug("reading dir[$dir]") if $CPAN::DEBUG; $dir ||= $self->{ID}; my($cwd) = CPAN::anycwd(); chdir $dir or Carp::croak("Can't chdir to $dir: $!"); my $dh = DirHandle->new(File::Spec->curdir) or Carp::croak("Couldn't opendir $dir: $!"); my(@entries); for ($dh->read) { next if $_ eq "." || $_ eq ".."; if (-f $_) { push @entries, File::Spec->catfile($dir,$_); } elsif (-d _) { push @entries, File::Spec->catdir($dir,$_); } else { $CPAN::Frontend->mywarn("Warning: weird direntry in $dir: $_\n"); } } chdir $cwd or Carp::croak("Can't chdir to $cwd: $!"); sort { -M $a <=> -M $b} @entries; } #-> sub CPAN::CacheMgr::disk_usage ; sub disk_usage { my($self,$dir,$fast) = @_; return if exists $self->{SIZE}{$dir}; return if $CPAN::Signal; my($Du) = 0; if (-e $dir) { if (-d $dir) { unless (-x $dir) { unless (chmod 0755, $dir) { $CPAN::Frontend->mywarn("I have neither the -x permission nor the ". "permission to change the permission; cannot ". "estimate disk usage of '$dir'\n"); $CPAN::Frontend->mysleep(5); return; } } } elsif (-f $dir) { # nothing to say, no matter what the permissions } } else { $CPAN::Frontend->mywarn("File or directory '$dir' has gone, ignoring\n"); return; } if ($fast) { $Du = 0; # placeholder } else { find( sub { $File::Find::prune++ if $CPAN::Signal; return if -l $_; if ($^O eq 'MacOS') { require Mac::Files; my $cat = Mac::Files::FSpGetCatInfo($_); $Du += $cat->ioFlLgLen() + $cat->ioFlRLgLen() if $cat; } else { if (-d _) { unless (-x _) { unless (chmod 0755, $_) { $CPAN::Frontend->mywarn("I have neither the -x permission nor ". "the permission to change the permission; ". "can only partially estimate disk usage ". "of '$_'\n"); $CPAN::Frontend->mysleep(5); return; } } } else { $Du += (-s _); } } }, $dir ); } return if $CPAN::Signal; $self->{SIZE}{$dir} = $Du/1024/1024; unshift @{$self->{FIFO}}, $dir; $self->debug("measured $dir is $Du") if $CPAN::DEBUG; $self->{DU} += $Du/1024/1024; $self->{DU}; } #-> sub CPAN::CacheMgr::_clean_cache ; sub _clean_cache { my($self,$dir) = @_; return unless -e $dir; unless (File::Spec->canonpath(File::Basename::dirname($dir)) eq File::Spec->canonpath($CPAN::Config->{build_dir})) { $CPAN::Frontend->mywarn("Directory '$dir' not below $CPAN::Config->{build_dir}, ". "will not remove\n"); $CPAN::Frontend->mysleep(5); return; } $self->debug("have to rmtree $dir, will free $self->{SIZE}{$dir}") if $CPAN::DEBUG; File::Path::rmtree($dir); my $id_deleted = 0; if ($dir !~ /\.yml$/ && -f "$dir.yml") { my $yaml_module = CPAN::_yaml_module(); if ($CPAN::META->has_inst($yaml_module)) { my($peek_yaml) = eval { CPAN->_yaml_loadfile("$dir.yml"); }; if ($@) { $CPAN::Frontend->mywarn("(parse error on '$dir.yml' removing anyway)"); unlink "$dir.yml" or $CPAN::Frontend->mywarn("(Could not unlink '$dir.yml': $!)"); return; } elsif (my $id = $peek_yaml->[0]{distribution}{ID}) { $CPAN::META->delete("CPAN::Distribution", $id); # XXX we should restore the state NOW, otherwise this # distro does not exist until we read an index. BUG ALERT(?) # $CPAN::Frontend->mywarn (" +++\n"); $id_deleted++; } } unlink "$dir.yml"; # may fail unless ($id_deleted) { CPAN->debug("no distro found associated with '$dir'"); } } $self->{DU} -= $self->{SIZE}{$dir}; delete $self->{SIZE}{$dir}; } #-> sub CPAN::CacheMgr::new ; sub new { my($class,$phase) = @_; $phase ||= "atstart"; my $time = time; my($debug,$t2); $debug = ""; my $self = { ID => $CPAN::Config->{build_dir}, MAX => $CPAN::Config->{'build_cache'}, SCAN => $CPAN::Config->{'scan_cache'} || 'atstart', DU => 0 }; $CPAN::Frontend->mydie("Unknown scan_cache argument: $self->{SCAN}") unless $self->{SCAN} =~ /never|atstart|atexit/; File::Path::mkpath($self->{ID}); my $dh = DirHandle->new($self->{ID}); bless $self, $class; $self->scan_cache($phase); $t2 = time; $debug .= "timing of CacheMgr->new: ".($t2 - $time); $time = $t2; CPAN->debug($debug) if $CPAN::DEBUG; $self; } #-> sub CPAN::CacheMgr::scan_cache ; sub scan_cache { my ($self, $phase) = @_; $phase = '' unless defined $phase; return unless $phase eq $self->{SCAN}; return unless $CPAN::META->{LOCK}; $CPAN::Frontend->myprint( sprintf("Scanning cache %s for sizes\n", $self->{ID})); my $e; my @entries = $self->entries($self->{ID}); my $i = 0; my $painted = 0; for $e (@entries) { my $symbol = "."; if ($self->{DU} > $self->{MAX}) { $symbol = "-"; $self->disk_usage($e,1); } else { $self->disk_usage($e); } $i++; while (($painted/76) < ($i/@entries)) { $CPAN::Frontend->myprint($symbol); $painted++; } return if $CPAN::Signal; } $CPAN::Frontend->myprint("DONE\n"); $self->tidyup; } 1; PK2]M :..Distroprefs.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: use 5.006; use strict; package CPAN::Distroprefs; use vars qw($VERSION); $VERSION = '6.0001'; package CPAN::Distroprefs::Result; use File::Spec; sub new { bless $_[1] || {} => $_[0] } sub abs { File::Spec->catfile($_[0]->dir, $_[0]->file) } sub __cloner { my ($class, $name, $newclass) = @_; $newclass = 'CPAN::Distroprefs::Result::' . $newclass; no strict 'refs'; *{$class . '::' . $name} = sub { $newclass->new({ %{ $_[0] }, %{ $_[1] }, }); }; } BEGIN { __PACKAGE__->__cloner(as_warning => 'Warning') } BEGIN { __PACKAGE__->__cloner(as_fatal => 'Fatal') } BEGIN { __PACKAGE__->__cloner(as_success => 'Success') } sub __accessor { my ($class, $key) = @_; no strict 'refs'; *{$class . '::' . $key} = sub { $_[0]->{$key} }; } BEGIN { __PACKAGE__->__accessor($_) for qw(type file ext dir) } sub is_warning { 0 } sub is_fatal { 0 } sub is_success { 0 } package CPAN::Distroprefs::Result::Error; use vars qw(@ISA); BEGIN { @ISA = 'CPAN::Distroprefs::Result' } ## no critic BEGIN { __PACKAGE__->__accessor($_) for qw(msg) } sub as_string { my ($self) = @_; if ($self->msg) { return sprintf $self->fmt_reason, $self->file, $self->msg; } else { return sprintf $self->fmt_unknown, $self->file; } } package CPAN::Distroprefs::Result::Warning; use vars qw(@ISA); BEGIN { @ISA = 'CPAN::Distroprefs::Result::Error' } ## no critic sub is_warning { 1 } sub fmt_reason { "Error reading distroprefs file %s, skipping: %s" } sub fmt_unknown { "Unknown error reading distroprefs file %s, skipping." } package CPAN::Distroprefs::Result::Fatal; use vars qw(@ISA); BEGIN { @ISA = 'CPAN::Distroprefs::Result::Error' } ## no critic sub is_fatal { 1 } sub fmt_reason { "Error reading distroprefs file %s: %s" } sub fmt_unknown { "Unknown error reading distroprefs file %s." } package CPAN::Distroprefs::Result::Success; use vars qw(@ISA); BEGIN { @ISA = 'CPAN::Distroprefs::Result' } ## no critic BEGIN { __PACKAGE__->__accessor($_) for qw(prefs extension) } sub is_success { 1 } package CPAN::Distroprefs::Iterator; sub new { bless $_[1] => $_[0] } sub next { $_[0]->() } package CPAN::Distroprefs; use Carp (); use DirHandle; sub _load_method { my ($self, $loader, $result) = @_; return '_load_yaml' if $loader eq 'CPAN' or $loader =~ /^YAML(::|$)/; return '_load_' . $result->ext; } sub _load_yaml { my ($self, $loader, $result) = @_; my $data = eval { $loader eq 'CPAN' ? $loader->_yaml_loadfile($result->abs) : [ $loader->can('LoadFile')->($result->abs) ] }; if (my $err = $@) { die $result->as_warning({ msg => $err, }); } elsif (!$data) { die $result->as_warning; } else { return @$data; } } sub _load_dd { my ($self, $loader, $result) = @_; my @data; { package CPAN::Eval; # this caused a die in CPAN.pm, and I am leaving it 'fatal', though I'm # not sure why we wouldn't just skip the file as we do for all other # errors. -- hdp my $abs = $result->abs; open FH, "<$abs" or die $result->as_fatal(msg => "$!"); local $/; my $eval = ; close FH; no strict; eval $eval; if (my $err = $@) { die $result->as_warning({ msg => $err }); } my $i = 1; while (${"VAR$i"}) { push @data, ${"VAR$i"}; $i++; } } return @data; } sub _load_st { my ($self, $loader, $result) = @_; # eval because Storable is never forward compatible my @data = eval { @{scalar $loader->can('retrieve')->($result->abs) } }; if (my $err = $@) { die $result->as_warning({ msg => $err }); } return @data; } sub _build_file_list { if (@_ > 3) { die "_build_file_list should be called with 3 arguments, was called with more. First argument is '$_[0]'."; } my ($dir, $dir1, $ext_re) = @_; my @list; my $dh; unless (opendir($dh, $dir)) { $CPAN::Frontend->mywarn("ignoring prefs directory '$dir': $!"); return @list; } while (my $fn = readdir $dh) { next if $fn eq '.' || $fn eq '..'; if (-d "$dir/$fn") { next if $fn =~ /^[._]/; # prune .svn, .git, .hg, _darcs and what the user wants to hide push @list, _build_file_list("$dir/$fn", "$dir1$fn/", $ext_re); } else { if ($fn =~ $ext_re) { push @list, "$dir1$fn"; } } } return @list; } sub find { my ($self, $dir, $ext_map) = @_; return CPAN::Distroprefs::Iterator->new(sub { return }) unless %$ext_map; my $possible_ext = join "|", map { quotemeta } keys %$ext_map; my $ext_re = qr/\.($possible_ext)$/; my @files = _build_file_list($dir, '', $ext_re); @files = sort @files if @files; # label the block so that we can use redo in the middle return CPAN::Distroprefs::Iterator->new(sub { LOOP: { my $fn = shift @files; return unless defined $fn; my ($ext) = $fn =~ $ext_re; my $loader = $ext_map->{$ext}; my $result = CPAN::Distroprefs::Result->new({ file => $fn, ext => $ext, dir => $dir }); # copied from CPAN.pm; is this ever actually possible? redo unless -f $result->abs; my $load_method = $self->_load_method($loader, $result); my @prefs = eval { $self->$load_method($loader, $result) }; if (my $err = $@) { if (ref($err) && eval { $err->isa('CPAN::Distroprefs::Result') }) { return $err; } # rethrow any exceptions that we did not generate die $err; } elsif (!@prefs) { # the loader should have handled this, but just in case: return $result->as_warning; } return $result->as_success({ prefs => [ map { CPAN::Distroprefs::Pref->new({ data => $_ }) } @prefs ], }); } }); } package CPAN::Distroprefs::Pref; use Carp (); sub new { bless $_[1] => $_[0] } sub data { shift->{data} } sub has_any_match { $_[0]->data->{match} ? 1 : 0 } sub has_match { my $match = $_[0]->data->{match} || return 0; exists $match->{$_[1]} || exists $match->{"not_$_[1]"} } sub has_valid_subkeys { grep { exists $_[0]->data->{match}{$_} } map { $_, "not_$_" } $_[0]->match_attributes } sub _pattern { my $re = shift; my $p = eval sprintf 'qr{%s}', $re; if ($@) { $@ =~ s/\n$//; die "Error in Distroprefs pattern qr{$re}\n$@"; } return $p; } sub _match_scalar { my ($match, $data) = @_; my $qr = _pattern($match); return $data =~ /$qr/; } sub _match_hash { my ($match, $data) = @_; for my $mkey (keys %$match) { (my $dkey = $mkey) =~ s/^not_//; my $val = defined $data->{$dkey} ? $data->{$dkey} : ''; if (_match_scalar($match->{$mkey}, $val)) { return 0 if $mkey =~ /^not_/; } else { return 0 if $mkey !~ /^not_/; } } return 1; } sub _match { my ($self, $key, $data, $matcher) = @_; my $m = $self->data->{match}; if (exists $m->{$key}) { return 0 unless $matcher->($m->{$key}, $data); } if (exists $m->{"not_$key"}) { return 0 if $matcher->($m->{"not_$key"}, $data); } return 1; } sub _scalar_match { my ($self, $key, $data) = @_; return $self->_match($key, $data, \&_match_scalar); } sub _hash_match { my ($self, $key, $data) = @_; return $self->_match($key, $data, \&_match_hash); } # do not take the order of C because "module" is by far the # slowest sub match_attributes { qw(env distribution perl perlconfig module) } sub match_module { my ($self, $modules) = @_; return $self->_match("module", $modules, sub { my($match, $data) = @_; my $qr = _pattern($match); for my $module (@$data) { return 1 if $module =~ /$qr/; } return 0; }); } sub match_distribution { shift->_scalar_match(distribution => @_) } sub match_perl { shift->_scalar_match(perl => @_) } sub match_perlconfig { shift->_hash_match(perlconfig => @_) } sub match_env { shift->_hash_match(env => @_) } sub matches { my ($self, $arg) = @_; my $default_match = 0; for my $key (grep { $self->has_match($_) } $self->match_attributes) { unless (exists $arg->{$key}) { Carp::croak "Can't match pref: missing argument key $key"; } $default_match = 1; my $val = $arg->{$key}; # make it possible to avoid computing things until we have to if (ref($val) eq 'CODE') { $val = $val->() } my $meth = "match_$key"; return 0 unless $self->$meth($val); } return $default_match; } 1; __END__ =head1 NAME CPAN::Distroprefs -- read and match distroprefs =head1 SYNOPSIS use CPAN::Distroprefs; my %info = (... distribution/environment info ...); my $finder = CPAN::Distroprefs->find($prefs_dir, \%ext_map); while (my $result = $finder->next) { die $result->as_string if $result->is_fatal; warn($result->as_string), next if $result->is_warning; for my $pref (@{ $result->prefs }) { if ($pref->matches(\%info)) { return $pref; } } } =head1 DESCRIPTION This module encapsulates reading L and matching them against CPAN distributions. =head1 INTERFACE my $finder = CPAN::Distroprefs->find($dir, \%ext_map); while (my $result = $finder->next) { ... } Build an iterator which finds distroprefs files in the tree below the given directory. Within the tree directories matching C are pruned. C<%ext_map> is a hashref whose keys are file extensions and whose values are modules used to load matching files: { 'yml' => 'YAML::Syck', 'dd' => 'Data::Dumper', ... } Each time C<< $finder->next >> is called, the iterator returns one of two possible values: =over =item * a CPAN::Distroprefs::Result object =item * C, indicating that no prefs files remain to be found =back =head1 RESULTS L|/INTERFACE> returns CPAN::Distroprefs::Result objects to indicate success or failure when reading a prefs file. =head2 Common All results share some common attributes: =head3 type C, C, or C =head3 file the file from which these prefs were read, or to which this error refers (relative filename) =head3 ext the file's extension, which determines how to load it =head3 dir the directory the file was read from =head3 abs the absolute path to the file =head2 Errors Error results (warning and fatal) contain: =head3 msg the error message (usually either C<$!> or a YAML error) =head2 Successes Success results contain: =head3 prefs an arrayref of CPAN::Distroprefs::Pref objects =head1 PREFS CPAN::Distroprefs::Pref objects represent individual distroprefs documents. They are constructed automatically as part of C results from C. =head3 data the pref information as a hashref, suitable for e.g. passing to Kwalify =head3 match_attributes returns a list of the valid match attributes (see the Distroprefs section in L) currently: C =head3 has_any_match true if this pref has a 'match' attribute at all =head3 has_valid_subkeys true if this pref has a 'match' attribute and at least one valid match attribute =head3 matches if ($pref->matches(\%arg)) { ... } true if this pref matches the passed-in hashref, which must have a value for each of the C (above) =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK2]䍽DeferredCode.pmnu6$package CPAN::DeferredCode; use strict; use vars qw/$VERSION/; use overload fallback => 1, map { ($_ => 'run') } qw/ bool "" 0+ /; $VERSION = "5.50"; sub run { $_[0]->(); } 1; PK2]7S Author.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Author; use strict; use CPAN::InfoObj; @CPAN::Author::ISA = qw(CPAN::InfoObj); use vars qw( $VERSION ); $VERSION = "5.5002"; package CPAN::Author; use strict; #-> sub CPAN::Author::force sub force { my $self = shift; $self->{force}++; } #-> sub CPAN::Author::force sub unforce { my $self = shift; delete $self->{force}; } #-> sub CPAN::Author::id sub id { my $self = shift; my $id = $self->{ID}; $CPAN::Frontend->mydie("Illegal author id[$id]") unless $id =~ /^[A-Z]/; $id; } #-> sub CPAN::Author::as_glimpse ; sub as_glimpse { my($self) = @_; my(@m); my $class = ref($self); $class =~ s/^CPAN:://; push @m, sprintf(qq{%-15s %s ("%s" <%s>)\n}, $class, $self->{ID}, $self->fullname, $self->email); join "", @m; } #-> sub CPAN::Author::fullname ; sub fullname { shift->ro->{FULLNAME}; } *name = \&fullname; #-> sub CPAN::Author::email ; sub email { shift->ro->{EMAIL}; } #-> sub CPAN::Author::ls ; sub ls { my $self = shift; my $glob = shift || ""; my $silent = shift || 0; my $id = $self->id; # adapted from CPAN::Distribution::verifyCHECKSUM ; my(@csf); # chksumfile @csf = $self->id =~ /(.)(.)(.*)/; $csf[1] = join "", @csf[0,1]; $csf[2] = join "", @csf[1,2]; # ("A","AN","ANDK") my(@dl); @dl = $self->dir_listing([$csf[0],"CHECKSUMS"], 0, 1); unless (grep {$_->[2] eq $csf[1]} @dl) { $CPAN::Frontend->myprint("Directory $csf[1]/ does not exist\n") unless $silent ; return; } @dl = $self->dir_listing([@csf[0,1],"CHECKSUMS"], 0, 1); unless (grep {$_->[2] eq $csf[2]} @dl) { $CPAN::Frontend->myprint("Directory $id/ does not exist\n") unless $silent; return; } @dl = $self->dir_listing([@csf,"CHECKSUMS"], 1, 1); if ($glob) { if ($CPAN::META->has_inst("Text::Glob")) { $glob =~ s|/$|/*|; my $rglob = Text::Glob::glob_to_regex($glob); CPAN->debug("glob[$glob]rglob[$rglob]dl[@dl]") if $CPAN::DEBUG; my @tmpdl = grep { $_->[2] =~ /$rglob/ } @dl; if (1==@tmpdl && $tmpdl[0][0]==0) { $rglob = Text::Glob::glob_to_regex("$glob/*"); @dl = grep { $_->[2] =~ /$rglob/ } @dl; } else { @dl = @tmpdl; } CPAN->debug("rglob[$rglob]dl[@dl]") if $CPAN::DEBUG; } else { $CPAN::Frontend->mydie("Text::Glob not installed, cannot proceed"); } } unless ($silent >= 2) { $CPAN::Frontend->myprint ( join "", map { sprintf ( "%8d %10s %s/%s%s\n", $_->[0], $_->[1], $id, $_->[2], 0==$_->[0]?"/":"", ) } sort { $a->[2] cmp $b->[2] } @dl ); } @dl; } # returns an array of arrays, the latter contain (size,mtime,filename) #-> sub CPAN::Author::dir_listing ; sub dir_listing { my $self = shift; my $chksumfile = shift; my $recursive = shift; my $may_ftp = shift; my $lc_want = File::Spec->catfile($CPAN::Config->{keep_source_where}, "authors", "id", @$chksumfile); my $fh; CPAN->debug("chksumfile[@$chksumfile]recursive[$recursive]may_ftp[$may_ftp]") if $CPAN::DEBUG; # Purge and refetch old (pre-PGP) CHECKSUMS; they are a security # hazard. (Without GPG installed they are not that much better, # though.) $fh = FileHandle->new; if (open($fh, $lc_want)) { my $line = <$fh>; close $fh; unlink($lc_want) unless $line =~ /PGP/; } local($") = "/"; # connect "force" argument with "index_expire". my $force = $self->{force}; if (my @stat = stat $lc_want) { $force ||= $stat[9] + $CPAN::Config->{index_expire}*86400 <= time; } my $lc_file; if ($may_ftp) { $lc_file = eval { CPAN::FTP->localize ( "authors/id/@$chksumfile", $lc_want, $force, ); }; unless ($lc_file) { $CPAN::Frontend->myprint("Trying $lc_want.gz\n"); $chksumfile->[-1] .= ".gz"; $lc_file = eval { CPAN::FTP->localize ("authors/id/@$chksumfile", "$lc_want.gz", 1, ); }; if ($lc_file) { $lc_file =~ s{\.gz(?!\n)\Z}{}; #}; eval{CPAN::Tarzip->new("$lc_file.gz")->gunzip($lc_file)}; } else { return; } } } else { $lc_file = $lc_want; # we *could* second-guess and if the user has a file: URL, # then we could look there. But on the other hand, if they do # have a file: URL, why did they choose to set # $CPAN::Config->{show_upload_date} to false? } # adapted from CPAN::Distribution::CHECKSUM_check_file ; $fh = FileHandle->new; my($cksum); if (open $fh, $lc_file) { local($/); my $eval = <$fh>; $eval =~ s/\015?\012/\n/g; close $fh; my($compmt) = Safe->new(); $cksum = $compmt->reval($eval); if ($@) { rename $lc_file, "$lc_file.bad"; Carp::confess($@) if $@; } } elsif ($may_ftp) { Carp::carp ("Could not open '$lc_file' for reading."); } else { # Maybe should warn: "You may want to set show_upload_date to a true value" return; } my(@result,$f); for $f (sort keys %$cksum) { if (exists $cksum->{$f}{isdir}) { if ($recursive) { my(@dir) = @$chksumfile; pop @dir; push @dir, $f, "CHECKSUMS"; push @result, [ 0, "-", $f ]; push @result, map { [$_->[0], $_->[1], "$f/$_->[2]"] } $self->dir_listing(\@dir,1,$may_ftp); } else { push @result, [ 0, "-", $f ]; } } else { push @result, [ ($cksum->{$f}{"size"}||0), $cksum->{$f}{"mtime"}||"---", $f ]; } } @result; } #-> sub CPAN::Author::reports sub reports { $CPAN::Frontend->mywarn("reports on authors not implemented. Please file a bugreport if you need this.\n"); } 1; PK2]Y3AA Tarzip.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- package CPAN::Tarzip; use strict; use vars qw($VERSION @ISA $BUGHUNTING); use CPAN::Debug; use File::Basename qw(basename); $VERSION = "5.5013"; # module is internal to CPAN.pm @ISA = qw(CPAN::Debug); ## no critic $BUGHUNTING ||= 0; # released code must have turned off # it's ok if file doesn't exist, it just matters if it is .gz or .bz2 sub new { my($class,$file) = @_; $CPAN::Frontend->mydie("CPAN::Tarzip->new called without arg") unless defined $file; my $me = { FILE => $file }; if ($file =~ /\.(bz2|gz|zip|tbz|tgz)$/i) { $me->{ISCOMPRESSED} = 1; } else { $me->{ISCOMPRESSED} = 0; } if (0) { } elsif ($file =~ /\.(?:bz2|tbz)$/i) { unless ($me->{UNGZIPPRG} = $CPAN::Config->{bzip2}) { my $bzip2 = _my_which("bzip2"); if ($bzip2) { $me->{UNGZIPPRG} = $bzip2; } else { $CPAN::Frontend->mydie(qq{ CPAN.pm needs the external program bzip2 in order to handle '$file'. Please install it now and run 'o conf init bzip2' from the CPAN shell prompt to register it as external program. }); } } } else { $me->{UNGZIPPRG} = _my_which("gzip"); } $me->{TARPRG} = _my_which("tar") || _my_which("gtar"); bless $me, $class; } sub _zlib_ok () { $CPAN::META->has_inst("Compress::Zlib") or return; Compress::Zlib->can('gzopen'); } sub _my_which { my($what) = @_; if ($CPAN::Config->{$what}) { return $CPAN::Config->{$what}; } if ($CPAN::META->has_inst("File::Which")) { return File::Which::which($what); } my @cand = MM->maybe_command($what); return $cand[0] if @cand; require File::Spec; my $component; PATH_COMPONENT: foreach $component (File::Spec->path()) { next unless defined($component) && $component; my($abs) = File::Spec->catfile($component,$what); if (MM->maybe_command($abs)) { return $abs; } } return; } sub gzip { my($self,$read) = @_; my $write = $self->{FILE}; if (_zlib_ok) { my($buffer,$fhw); $fhw = FileHandle->new($read) or $CPAN::Frontend->mydie("Could not open $read: $!"); my $cwd = `pwd`; my $gz = Compress::Zlib::gzopen($write, "wb") or $CPAN::Frontend->mydie("Cannot gzopen $write: $! (pwd is $cwd)\n"); binmode($fhw); $gz->gzwrite($buffer) while read($fhw,$buffer,4096) > 0 ; $gz->gzclose() ; $fhw->close; return 1; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); system(qq{$command -c "$read" > "$write"})==0; } } sub gunzip { my($self,$write) = @_; my $read = $self->{FILE}; if (_zlib_ok) { my($buffer,$fhw); $fhw = FileHandle->new(">$write") or $CPAN::Frontend->mydie("Could not open >$write: $!"); my $gz = Compress::Zlib::gzopen($read, "rb") or $CPAN::Frontend->mydie("Cannot gzopen $read: $!\n"); binmode($fhw); $fhw->print($buffer) while $gz->gzread($buffer) > 0 ; $CPAN::Frontend->mydie("Error reading from $read: $!\n") if $gz->gzerror != Compress::Zlib::Z_STREAM_END(); $gz->gzclose() ; $fhw->close; return 1; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); system(qq{$command -d -c "$read" > "$write"})==0; } } sub gtest { my($self) = @_; return $self->{GTEST} if exists $self->{GTEST}; defined $self->{FILE} or $CPAN::Frontend->mydie("gtest called but no FILE specified"); my $read = $self->{FILE}; my $success; if ($read=~/\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { my($buffer,$len); $len = 0; my $gz = Compress::Bzip2::bzopen($read, "rb") or $CPAN::Frontend->mydie(sprintf("Cannot bzopen %s: %s\n", $read, $Compress::Bzip2::bzerrno)); while ($gz->bzread($buffer) > 0 ) { $len += length($buffer); $buffer = ""; } my $err = $gz->bzerror; $success = ! $err || $err == Compress::Bzip2::BZ_STREAM_END(); if ($len == -s $read) { $success = 0; CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; } $gz->gzclose(); CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; } elsif ( $read=~/\.(?:gz|tgz)$/ && _zlib_ok ) { # After I had reread the documentation in zlib.h, I discovered that # uncompressed files do not lead to an gzerror (anymore?). my($buffer,$len); $len = 0; my $gz = Compress::Zlib::gzopen($read, "rb") or $CPAN::Frontend->mydie(sprintf("Cannot gzopen %s: %s\n", $read, $Compress::Zlib::gzerrno)); while ($gz->gzread($buffer) > 0 ) { $len += length($buffer); $buffer = ""; } my $err = $gz->gzerror; $success = ! $err || $err == Compress::Zlib::Z_STREAM_END(); if ($len == -s $read) { $success = 0; CPAN->debug("hit an uncompressed file") if $CPAN::DEBUG; } $gz->gzclose(); CPAN->debug("err[$err]success[$success]") if $CPAN::DEBUG; } elsif (!$self->{ISCOMPRESSED}) { $success = 0; } else { my $command = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); $success = 0==system(qq{$command -qdt "$read"}); } return $self->{GTEST} = $success; } sub TIEHANDLE { my($class,$file) = @_; my $ret; $class->debug("file[$file]"); my $self = $class->new($file); if (0) { } elsif (!$self->gtest) { my $fh = FileHandle->new($file) or $CPAN::Frontend->mydie("Could not open file[$file]: $!"); binmode $fh; $self->{FH} = $fh; $class->debug("via uncompressed FH"); } elsif ($file =~ /\.(?:bz2|tbz)$/ && $CPAN::META->has_inst("Compress::Bzip2")) { my $gz = Compress::Bzip2::bzopen($file,"rb") or $CPAN::Frontend->mydie("Could not bzopen $file"); $self->{GZ} = $gz; $class->debug("via Compress::Bzip2"); } elsif ($file =~/\.(?:gz|tgz)$/ && _zlib_ok) { my $gz = Compress::Zlib::gzopen($file,"rb") or $CPAN::Frontend->mydie("Could not gzopen $file"); $self->{GZ} = $gz; $class->debug("via Compress::Zlib"); } else { my $gzip = CPAN::HandleConfig->safe_quote($self->{UNGZIPPRG}); my $pipe = "$gzip -d -c $file |"; my $fh = FileHandle->new($pipe) or $CPAN::Frontend->mydie("Could not pipe[$pipe]: $!"); binmode $fh; $self->{FH} = $fh; $class->debug("via external $gzip"); } $self; } sub READLINE { my($self) = @_; if (exists $self->{GZ}) { my $gz = $self->{GZ}; my($line,$bytesread); $bytesread = $gz->gzreadline($line); return undef if $bytesread <= 0; return $line; } else { my $fh = $self->{FH}; return scalar <$fh>; } } sub READ { my($self,$ref,$length,$offset) = @_; $CPAN::Frontend->mydie("read with offset not implemented") if defined $offset; if (exists $self->{GZ}) { my $gz = $self->{GZ}; my $byteread = $gz->gzread($$ref,$length);# 30eaf79e8b446ef52464b5422da328a8 return $byteread; } else { my $fh = $self->{FH}; return read($fh,$$ref,$length); } } sub DESTROY { my($self) = @_; if (exists $self->{GZ}) { my $gz = $self->{GZ}; $gz->gzclose() if defined $gz; # hard to say if it is allowed # to be undef ever. AK, 2000-09 } else { my $fh = $self->{FH}; $fh->close if defined $fh; } undef $self; } sub untar { my($self) = @_; my $file = $self->{FILE}; my($prefer) = 0; my $exttar = $self->{TARPRG} || ""; $exttar = "" if $exttar =~ /^\s+$/; # user refuses to use it my $extgzip = $self->{UNGZIPPRG} || ""; $extgzip = "" if $extgzip =~ /^\s+$/; # user refuses to use it if (0) { # makes changing order easier } elsif ($BUGHUNTING) { $prefer=2; } elsif ($CPAN::Config->{prefer_external_tar}) { $prefer = 1; } elsif ( $CPAN::META->has_usable("Archive::Tar") && _zlib_ok ) { my $prefer_external_tar = $CPAN::Config->{prefer_external_tar}; unless (defined $prefer_external_tar) { if ($^O =~ /(MSWin32|solaris)/) { $prefer_external_tar = 0; } else { $prefer_external_tar = 1; } } $prefer = $prefer_external_tar ? 1 : 2; } elsif ($exttar && $extgzip) { # no modules and not bz2 $prefer = 1; # but solaris binary tar is a problem if ($^O eq 'solaris' && qx($exttar --version 2>/dev/null) !~ /gnu/i) { $CPAN::Frontend->mywarn(<< 'END_WARN'); WARNING: Many CPAN distributions were archived with GNU tar and some of them may be incompatible with Solaris tar. We respectfully suggest you configure CPAN to use a GNU tar instead ("o conf init tar") or install a recent Archive::Tar instead; END_WARN } } else { my $foundtar = $exttar ? "'$exttar'" : "nothing"; my $foundzip = $extgzip ? "'$extgzip'" : $foundtar ? "nothing" : "also nothing"; my $foundAT; if ($CPAN::META->has_usable("Archive::Tar")) { $foundAT = sprintf "'%s'", "Archive::Tar::"->VERSION; } else { $foundAT = "nothing"; } my $foundCZ; if (_zlib_ok) { $foundCZ = sprintf "'%s'", "Compress::Zlib::"->VERSION; } elsif ($foundAT) { $foundCZ = "nothing"; } else { $foundCZ = "also nothing"; } $CPAN::Frontend->mydie(qq{ CPAN.pm needs either the external programs tar and gzip -or- both modules Archive::Tar and Compress::Zlib installed. For tar I found $foundtar, for gzip $foundzip. For Archive::Tar I found $foundAT, for Compress::Zlib $foundCZ; Can't continue cutting file '$file'. }); } my $tar_verb = "v"; if (defined $CPAN::Config->{tar_verbosity}) { $tar_verb = $CPAN::Config->{tar_verbosity} eq "none" ? "" : $CPAN::Config->{tar_verbosity}; } if ($prefer==1) { # 1 => external gzip+tar my($system); my $is_compressed = $self->gtest(); my $tarcommand = CPAN::HandleConfig->safe_quote($exttar); if ($is_compressed) { my $command = CPAN::HandleConfig->safe_quote($extgzip); $system = qq{$command -d -c }. qq{< "$file" | $tarcommand x${tar_verb}f -}; } else { $system = qq{$tarcommand x${tar_verb}f "$file"}; } if (system($system) != 0) { # people find the most curious tar binaries that cannot handle # pipes if ($is_compressed) { (my $ungzf = $file) =~ s/\.gz(?!\n)\Z//; $ungzf = basename $ungzf; my $ct = CPAN::Tarzip->new($file); if ($ct->gunzip($ungzf)) { $CPAN::Frontend->myprint(qq{Uncompressed $file successfully\n}); } else { $CPAN::Frontend->mydie(qq{Couldn\'t uncompress $file\n}); } $file = $ungzf; } $system = qq{$tarcommand x${tar_verb}f "$file"}; $CPAN::Frontend->myprint(qq{Using Tar:$system:\n}); my $ret = system($system); if ($ret==0) { $CPAN::Frontend->myprint(qq{Untarred $file successfully\n}); } else { if ($? == -1) { $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: '%s'\n}, $file, $!); } elsif ($? & 127) { $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: child died with signal %d, %s coredump\n}, $file, ($? & 127), ($? & 128) ? 'with' : 'without'); } else { $CPAN::Frontend->mydie(sprintf qq{Couldn\'t untar %s: child exited with value %d\n}, $file, $? >> 8); } } return 1; } else { return 1; } } elsif ($prefer==2) { # 2 => modules unless ($CPAN::META->has_usable("Archive::Tar")) { $CPAN::Frontend->mydie("Archive::Tar not installed, please install it to continue"); } # Make sure AT does not use uid/gid/permissions in the archive # This leaves it to the user's umask instead local $Archive::Tar::CHMOD = 1; local $Archive::Tar::SAME_PERMISSIONS = 0; # Make sure AT leaves current user as owner local $Archive::Tar::CHOWN = 0; my $tar = Archive::Tar->new($file,1); my $af; # archive file my @af; if ($BUGHUNTING) { # RCS 1.337 had this code, it turned out unacceptable slow but # it revealed a bug in Archive::Tar. Code is only here to hunt # the bug again. It should never be enabled in published code. # GDGraph3d-0.53 was an interesting case according to Larry # Virden. warn(">>>Bughunting code enabled<<< " x 20); for $af ($tar->list_files) { if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } $CPAN::Frontend->myprint("$af\n"); $tar->extract($af); # slow but effective for finding the bug return if $CPAN::Signal; } } else { for $af ($tar->list_files) { if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } if ($tar_verb eq "v" || $tar_verb eq "vv") { $CPAN::Frontend->myprint("$af\n"); } push @af, $af; return if $CPAN::Signal; } $tar->extract(@af) or $CPAN::Frontend->mydie("Could not untar with Archive::Tar."); } Mac::BuildTools::convert_files([$tar->list_files], 1) if ($^O eq 'MacOS'); return 1; } } sub unzip { my($self) = @_; my $file = $self->{FILE}; if ($CPAN::META->has_inst("Archive::Zip")) { # blueprint of the code from Archive::Zip::Tree::extractTree(); my $zip = Archive::Zip->new(); my $status; $status = $zip->read($file); $CPAN::Frontend->mydie("Read of file[$file] failed\n") if $status != Archive::Zip::AZ_OK(); $CPAN::META->debug("Successfully read file[$file]") if $CPAN::DEBUG; my @members = $zip->members(); for my $member ( @members ) { my $af = $member->fileName(); if ($af =~ m!^(/|\.\./)!) { $CPAN::Frontend->mydie("ALERT: Archive contains ". "illegal member [$af]"); } $status = $member->extractToFileNamed( $af ); $CPAN::META->debug("af[$af]status[$status]") if $CPAN::DEBUG; $CPAN::Frontend->mydie("Extracting of file[$af] from zipfile[$file] failed\n") if $status != Archive::Zip::AZ_OK(); return if $CPAN::Signal; } return 1; } elsif ( my $unzip = $CPAN::Config->{unzip} ) { my @system = ($unzip, $file); return system(@system) == 0; } else { $CPAN::Frontend->mydie(<<"END"); Can't unzip '$file': You have not configured an 'unzip' program and do not have Archive::Zip installed. Please either install Archive::Zip or else configure 'unzip' by running the command 'o conf init unzip' from the CPAN shell prompt. END } } 1; __END__ =head1 NAME CPAN::Tarzip - internal handling of tar archives for CPAN.pm =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK2]iۨDDistrostatus.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Distrostatus; use overload '""' => "as_string", fallback => 1; use vars qw($something_has_failed_at); use vars qw( $VERSION ); $VERSION = "5.5"; sub new { my($class,$arg) = @_; my $failed = substr($arg,0,2) eq "NO"; if ($failed) { $something_has_failed_at = $CPAN::CurrentCommandId; } bless { TEXT => $arg, FAILED => $failed, COMMANDID => $CPAN::CurrentCommandId, TIME => time, }, $class; } sub something_has_just_failed () { defined $something_has_failed_at && $something_has_failed_at == $CPAN::CurrentCommandId; } sub commandid { shift->{COMMANDID} } sub failed { shift->{FAILED} } sub text { my($self,$set) = @_; if (defined $set) { $self->{TEXT} = $set; } $self->{TEXT}; } sub as_string { my($self) = @_; $self->text; } 1; PK2]*^Exception/blocked_urllist.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Exception::blocked_urllist; use strict; use overload '""' => "as_string"; use vars qw( $VERSION ); $VERSION = "1.001"; sub new { my($class) = @_; bless {}, $class; } sub as_string { my($self) = shift; if ($CPAN::Config->{connect_to_internet_ok}) { return qq{ You have not configured a urllist for CPAN mirrors. Configure it with o conf init urllist }; } else { return qq{ You have not configured a urllist and do not allow connections to the internet to get a list of mirrors. If you wish to get a list of CPAN mirrors to pick from, use this command o conf init connect_to_internet_ok urllist If you do not wish to get a list of mirrors and would prefer to set your urllist manually, use just this command instead o conf init urllist }; } } 1; PK2]1.. Exception/RecursiveDependency.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Exception::RecursiveDependency; use strict; use overload '""' => "as_string"; use vars qw( $VERSION ); $VERSION = "5.5001"; { package CPAN::Exception::RecursiveDependency::na; use overload '""' => "as_string"; sub new { bless {}, shift }; sub as_string { "N/A" }; } my $NA = CPAN::Exception::RecursiveDependency::na->new; # a module sees its distribution (no version) # a distribution sees its prereqs (which are module names) (usually with versions) # a bundle sees its module names and/or its distributions (no version) sub new { my($class) = shift; my($deps_arg) = shift; my (@deps,%seen,$loop_starts_with); DCHAIN: for my $dep (@$deps_arg) { push @deps, {name => $dep, display_as => $dep}; if ($seen{$dep}++) { $loop_starts_with = $dep; last DCHAIN; } } my $in_loop = 0; my %mark; DWALK: for my $i (0..$#deps) { my $x = $deps[$i]{name}; $in_loop ||= $loop_starts_with && $x eq $loop_starts_with; my $xo = CPAN::Shell->expandany($x) or next; if ($xo->isa("CPAN::Module")) { my $have = $xo->inst_version || $NA; my($want,$d,$want_type); if ($i>0 and $d = $deps[$i-1]{name}) { my $do = CPAN::Shell->expandany($d); $want = $do->{prereq_pm}{requires}{$x}; if (defined $want) { $want_type = "requires: "; } else { $want = $do->{prereq_pm}{build_requires}{$x}; if (defined $want) { $want_type = "build_requires: "; } else { $want_type = "unknown status"; $want = "???"; } } } else { $want = $xo->cpan_version; $want_type = "want: "; } $deps[$i]{have} = $have; $deps[$i]{want_type} = $want_type; $deps[$i]{want} = $want; $deps[$i]{display_as} = "$x (have: $have; $want_type$want)"; if ((! ref $have || !$have->isa('CPAN::Exception::RecursiveDependency::na')) && CPAN::Version->vge($have, $want)) { # https://rt.cpan.org/Ticket/Display.html?id=115340 undef $loop_starts_with; last DWALK; } } elsif ($xo->isa("CPAN::Distribution")) { my $pretty = $deps[$i]{display_as} = $xo->pretty_id; my $mark_as; if ($in_loop) { $mark_as = CPAN::Distrostatus->new("NO cannot resolve circular dependency"); } else { $mark_as = CPAN::Distrostatus->new("NO one dependency ($loop_starts_with) is a circular dependency"); } $mark{$pretty} = { xo => $xo, mark_as => $mark_as }; } } if ($loop_starts_with) { while (my($k,$v) = each %mark) { my $xo = $v->{xo}; $xo->{make} = $v->{mark_as}; $xo->store_persistent_state; # otherwise I will not reach # all involved parties for # the next session } } bless { deps => \@deps, loop_starts_with => $loop_starts_with }, $class; } sub is_resolvable { ! defined shift->{loop_starts_with}; } sub as_string { my($self) = shift; my $deps = $self->{deps}; my $loop_starts_with = $self->{loop_starts_with}; unless ($loop_starts_with) { return "--not a recursive/circular dependency--"; } my $ret = "\nRecursive dependency detected:\n "; $ret .= join("\n => ", map {$_->{display_as}} @$deps); $ret .= ".\nCannot resolve.\n"; $ret; } 1; PK2]TException/yaml_not_installed.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Exception::yaml_not_installed; use strict; use overload '""' => "as_string"; use vars qw( $VERSION ); $VERSION = "5.5"; sub new { my($class,$module,$file,$during) = @_; bless { module => $module, file => $file, during => $during }, $class; } sub as_string { my($self) = shift; "'$self->{module}' not installed, cannot $self->{during} '$self->{file}'\n"; } 1; PK2]ܑHException/yaml_process_error.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Exception::yaml_process_error; use strict; use overload '""' => "as_string"; use vars qw( $VERSION ); $VERSION = "5.5"; sub new { my($class,$module,$file,$during,$error) = @_; # my $at = Carp::longmess(""); # XXX find something more beautiful bless { module => $module, file => $file, during => $during, error => $error, # at => $at, }, $class; } sub as_string { my($self) = shift; if ($self->{during}) { if ($self->{file}) { if ($self->{module}) { if ($self->{error}) { return "Alert: While trying to '$self->{during}' YAML file\n". " '$self->{file}'\n". "with '$self->{module}' the following error was encountered:\n". " $self->{error}\n"; } else { return "Alert: While trying to '$self->{during}' YAML file\n". " '$self->{file}'\n". "with '$self->{module}' some unknown error was encountered\n"; } } else { return "Alert: While trying to '$self->{during}' YAML file\n". " '$self->{file}'\n". "some unknown error was encountered\n"; } } else { return "Alert: While trying to '$self->{during}' some YAML file\n". "some unknown error was encountered\n"; } } else { return "Alert: unknown error encountered\n"; } } 1; PK2]ך9LLURL.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::URL; use overload '""' => "as_string", fallback => 1; # accessors: TEXT(the url string), FROM(DEF=>defaultlist,USER=>urllist), # planned are things like age or quality use vars qw( $VERSION ); $VERSION = "5.5"; sub new { my($class,%args) = @_; bless { %args }, $class; } sub as_string { my($self) = @_; $self->text; } sub text { my($self,$set) = @_; if (defined $set) { $self->{TEXT} = $set; } $self->{TEXT}; } 1; PK2]>+LWP/UserAgent.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::LWP::UserAgent; use strict; use vars qw(@ISA $USER $PASSWD $SETUPDONE); use CPAN::HTTP::Credentials; # we delay requiring LWP::UserAgent and setting up inheritance until we need it $CPAN::LWP::UserAgent::VERSION = $CPAN::LWP::UserAgent::VERSION = "1.9601"; sub config { return if $SETUPDONE; if ($CPAN::META->has_usable('LWP::UserAgent')) { require LWP::UserAgent; @ISA = qw(Exporter LWP::UserAgent); ## no critic $SETUPDONE++; } else { $CPAN::Frontend->mywarn(" LWP::UserAgent not available\n"); } } sub get_basic_credentials { my($self, $realm, $uri, $proxy) = @_; if ( $proxy ) { return CPAN::HTTP::Credentials->get_proxy_credentials(); } else { return CPAN::HTTP::Credentials->get_non_proxy_credentials(); } } sub no_proxy { my ( $self, $no_proxy ) = @_; return $self->SUPER::no_proxy( split(',',$no_proxy) ); } # mirror(): Its purpose is to deal with proxy authentication. When we # call SUPER::mirror, we really call the mirror method in # LWP::UserAgent. LWP::UserAgent will then call # $self->get_basic_credentials or some equivalent and this will be # $self->dispatched to our own get_basic_credentials method. # Our own get_basic_credentials sets $USER and $PASSWD, two globals. # 407 stands for HTTP_PROXY_AUTHENTICATION_REQUIRED. Which means # although we have gone through our get_basic_credentials, the proxy # server refuses to connect. This could be a case where the username or # password has changed in the meantime, so I'm trying once again without # $USER and $PASSWD to give the get_basic_credentials routine another # chance to set $USER and $PASSWD. sub mirror { my($self,$url,$aslocal) = @_; my $result = $self->SUPER::mirror($url,$aslocal); if ($result->code == 407) { CPAN::HTTP::Credentials->clear_credentials; $result = $self->SUPER::mirror($url,$aslocal); } $result; } 1; PK2]riri Meta/YAML.pmnu[use 5.008001; # sane UTF-8 support use strict; use warnings; package CPAN::Meta::YAML; # git description: v1.68-2-gcc5324e # XXX-INGY is 5.8.1 too old/broken for utf8? # XXX-XDG Lancaster consensus was that it was sufficient until # proven otherwise $CPAN::Meta::YAML::VERSION = '0.018'; ; # original $VERSION removed by Doppelgaenger ##################################################################### # The CPAN::Meta::YAML API. # # These are the currently documented API functions/methods and # exports: use Exporter; our @ISA = qw{ Exporter }; our @EXPORT = qw{ Load Dump }; our @EXPORT_OK = qw{ LoadFile DumpFile freeze thaw }; ### # Functional/Export API: sub Dump { return CPAN::Meta::YAML->new(@_)->_dump_string; } # XXX-INGY Returning last document seems a bad behavior. # XXX-XDG I think first would seem more natural, but I don't know # that it's worth changing now sub Load { my $self = CPAN::Meta::YAML->_load_string(@_); if ( wantarray ) { return @$self; } else { # To match YAML.pm, return the last document return $self->[-1]; } } # XXX-INGY Do we really need freeze and thaw? # XXX-XDG I don't think so. I'd support deprecating them. BEGIN { *freeze = \&Dump; *thaw = \&Load; } sub DumpFile { my $file = shift; return CPAN::Meta::YAML->new(@_)->_dump_file($file); } sub LoadFile { my $file = shift; my $self = CPAN::Meta::YAML->_load_file($file); if ( wantarray ) { return @$self; } else { # Return only the last document to match YAML.pm, return $self->[-1]; } } ### # Object Oriented API: # Create an empty CPAN::Meta::YAML object # XXX-INGY Why do we use ARRAY object? # NOTE: I get it now, but I think it's confusing and not needed. # Will change it on a branch later, for review. # # XXX-XDG I don't support changing it yet. It's a very well-documented # "API" of CPAN::Meta::YAML. I'd support deprecating it, but Adam suggested # we not change it until YAML.pm's own OO API is established so that # users only have one API change to digest, not two sub new { my $class = shift; bless [ @_ ], $class; } # XXX-INGY It probably doesn't matter, and it's probably too late to # change, but 'read/write' are the wrong names. Read and Write # are actions that take data from storage to memory # characters/strings. These take the data to/from storage to native # Perl objects, which the terms dump and load are meant. As long as # this is a legacy quirk to CPAN::Meta::YAML it's ok, but I'd prefer not # to add new {read,write}_* methods to this API. sub read_string { my $self = shift; $self->_load_string(@_); } sub write_string { my $self = shift; $self->_dump_string(@_); } sub read { my $self = shift; $self->_load_file(@_); } sub write { my $self = shift; $self->_dump_file(@_); } ##################################################################### # Constants # Printed form of the unprintable characters in the lowest range # of ASCII characters, listed by ASCII ordinal position. my @UNPRINTABLE = qw( 0 x01 x02 x03 x04 x05 x06 a b t n v f r x0E x0F x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x1A e x1C x1D x1E x1F ); # Printable characters for escapes my %UNESCAPES = ( 0 => "\x00", z => "\x00", N => "\x85", a => "\x07", b => "\x08", t => "\x09", n => "\x0a", v => "\x0b", f => "\x0c", r => "\x0d", e => "\x1b", '\\' => '\\', ); # XXX-INGY # I(ngy) need to decide if these values should be quoted in # CPAN::Meta::YAML or not. Probably yes. # These 3 values have special meaning when unquoted and using the # default YAML schema. They need quotes if they are strings. my %QUOTE = map { $_ => 1 } qw{ null true false }; # The commented out form is simpler, but overloaded the Perl regex # engine due to recursion and backtracking problems on strings # larger than 32,000ish characters. Keep it for reference purposes. # qr/\"((?:\\.|[^\"])*)\"/ my $re_capture_double_quoted = qr/\"([^\\"]*(?:\\.[^\\"]*)*)\"/; my $re_capture_single_quoted = qr/\'([^\']*(?:\'\'[^\']*)*)\'/; # unquoted re gets trailing space that needs to be stripped my $re_capture_unquoted_key = qr/([^:]+(?::+\S(?:[^:]*|.*?(?=:)))*)(?=\s*\:(?:\s+|$))/; my $re_trailing_comment = qr/(?:\s+\#.*)?/; my $re_key_value_separator = qr/\s*:(?:\s+(?:\#.*)?|$)/; ##################################################################### # CPAN::Meta::YAML Implementation. # # These are the private methods that do all the work. They may change # at any time. ### # Loader functions: # Create an object from a file sub _load_file { my $class = ref $_[0] ? ref shift : shift; # Check the file my $file = shift or $class->_error( 'You did not specify a file name' ); $class->_error( "File '$file' does not exist" ) unless -e $file; $class->_error( "'$file' is a directory, not a file" ) unless -f _; $class->_error( "Insufficient permissions to read '$file'" ) unless -r _; # Open unbuffered with strict UTF-8 decoding and no translation layers open( my $fh, "<:unix:encoding(UTF-8)", $file ); unless ( $fh ) { $class->_error("Failed to open file '$file': $!"); } # flock if available (or warn if not possible for OS-specific reasons) if ( _can_flock() ) { flock( $fh, Fcntl::LOCK_SH() ) or warn "Couldn't lock '$file' for reading: $!"; } # slurp the contents my $contents = eval { use warnings FATAL => 'utf8'; local $/; <$fh> }; if ( my $err = $@ ) { $class->_error("Error reading from file '$file': $err"); } # close the file (release the lock) unless ( close $fh ) { $class->_error("Failed to close file '$file': $!"); } $class->_load_string( $contents ); } # Create an object from a string sub _load_string { my $class = ref $_[0] ? ref shift : shift; my $self = bless [], $class; my $string = $_[0]; eval { unless ( defined $string ) { die \"Did not provide a string to load"; } # Check if Perl has it marked as characters, but it's internally # inconsistent. E.g. maybe latin1 got read on a :utf8 layer if ( utf8::is_utf8($string) && ! utf8::valid($string) ) { die \<<'...'; Read an invalid UTF-8 string (maybe mixed UTF-8 and 8-bit character set). Did you decode with lax ":utf8" instead of strict ":encoding(UTF-8)"? ... } # Ensure Unicode character semantics, even for 0x80-0xff utf8::upgrade($string); # Check for and strip any leading UTF-8 BOM $string =~ s/^\x{FEFF}//; # Check for some special cases return $self unless length $string; # Split the file into lines my @lines = grep { ! /^\s*(?:\#.*)?\z/ } split /(?:\015{1,2}\012|\015|\012)/, $string; # Strip the initial YAML header @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines; # A nibbling parser my $in_document = 0; while ( @lines ) { # Do we have a document header? if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) { # Handle scalar documents shift @lines; if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) { push @$self, $self->_load_scalar( "$1", [ undef ], \@lines ); next; } $in_document = 1; } if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) { # A naked document push @$self, undef; while ( @lines and $lines[0] !~ /^---/ ) { shift @lines; } $in_document = 0; # XXX The final '-+$' is to look for -- which ends up being an # error later. } elsif ( ! $in_document && @$self ) { # only the first document can be explicit die \"CPAN::Meta::YAML failed to classify the line '$lines[0]'"; } elsif ( $lines[0] =~ /^\s*\-(?:\s|$|-+$)/ ) { # An array at the root my $document = [ ]; push @$self, $document; $self->_load_array( $document, [ 0 ], \@lines ); } elsif ( $lines[0] =~ /^(\s*)\S/ ) { # A hash at the root my $document = { }; push @$self, $document; $self->_load_hash( $document, [ length($1) ], \@lines ); } else { # Shouldn't get here. @lines have whitespace-only lines # stripped, and previous match is a line with any # non-whitespace. So this clause should only be reachable via # a perlbug where \s is not symmetric with \S # uncoverable statement die \"CPAN::Meta::YAML failed to classify the line '$lines[0]'"; } } }; my $err = $@; if ( ref $err eq 'SCALAR' ) { $self->_error(${$err}); } elsif ( $err ) { $self->_error($err); } return $self; } sub _unquote_single { my ($self, $string) = @_; return '' unless length $string; $string =~ s/\'\'/\'/g; return $string; } sub _unquote_double { my ($self, $string) = @_; return '' unless length $string; $string =~ s/\\"/"/g; $string =~ s{\\([Nnever\\fartz0b]|x([0-9a-fA-F]{2}))} {(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}}gex; return $string; } # Load a YAML scalar string to the actual Perl scalar sub _load_scalar { my ($self, $string, $indent, $lines) = @_; # Trim trailing whitespace $string =~ s/\s*\z//; # Explitic null/undef return undef if $string eq '~'; # Single quote if ( $string =~ /^$re_capture_single_quoted$re_trailing_comment\z/ ) { return $self->_unquote_single($1); } # Double quote. if ( $string =~ /^$re_capture_double_quoted$re_trailing_comment\z/ ) { return $self->_unquote_double($1); } # Special cases if ( $string =~ /^[\'\"!&]/ ) { die \"CPAN::Meta::YAML does not support a feature in line '$string'"; } return {} if $string =~ /^{}(?:\s+\#.*)?\z/; return [] if $string =~ /^\[\](?:\s+\#.*)?\z/; # Regular unquoted string if ( $string !~ /^[>|]/ ) { die \"CPAN::Meta::YAML found illegal characters in plain scalar: '$string'" if $string =~ /^(?:-(?:\s|$)|[\@\%\`])/ or $string =~ /:(?:\s|$)/; $string =~ s/\s+#.*\z//; return $string; } # Error die \"CPAN::Meta::YAML failed to find multi-line scalar content" unless @$lines; # Check the indent depth $lines->[0] =~ /^(\s*)/; $indent->[-1] = length("$1"); if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) { die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'"; } # Pull the lines my @multiline = (); while ( @$lines ) { $lines->[0] =~ /^(\s*)/; last unless length($1) >= $indent->[-1]; push @multiline, substr(shift(@$lines), length($1)); } my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n"; my $t = (substr($string, 1, 1) eq '-') ? '' : "\n"; return join( $j, @multiline ) . $t; } # Load an array sub _load_array { my ($self, $array, $indent, $lines) = @_; while ( @$lines ) { # Check for a new document if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) { while ( @$lines and $lines->[0] !~ /^---/ ) { shift @$lines; } return 1; } # Check the indent level $lines->[0] =~ /^(\s*)/; if ( length($1) < $indent->[-1] ) { return 1; } elsif ( length($1) > $indent->[-1] ) { die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'"; } if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) { # Inline nested hash my $indent2 = length("$1"); $lines->[0] =~ s/-/ /; push @$array, { }; $self->_load_hash( $array->[-1], [ @$indent, $indent2 ], $lines ); } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) { shift @$lines; unless ( @$lines ) { push @$array, undef; return 1; } if ( $lines->[0] =~ /^(\s*)\-/ ) { my $indent2 = length("$1"); if ( $indent->[-1] == $indent2 ) { # Null array entry push @$array, undef; } else { # Naked indenter push @$array, [ ]; $self->_load_array( $array->[-1], [ @$indent, $indent2 ], $lines ); } } elsif ( $lines->[0] =~ /^(\s*)\S/ ) { push @$array, { }; $self->_load_hash( $array->[-1], [ @$indent, length("$1") ], $lines ); } else { die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'"; } } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) { # Array entry with a value shift @$lines; push @$array, $self->_load_scalar( "$2", [ @$indent, undef ], $lines ); } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) { # This is probably a structure like the following... # --- # foo: # - list # bar: value # # ... so lets return and let the hash parser handle it return 1; } else { die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'"; } } return 1; } # Load a hash sub _load_hash { my ($self, $hash, $indent, $lines) = @_; while ( @$lines ) { # Check for a new document if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) { while ( @$lines and $lines->[0] !~ /^---/ ) { shift @$lines; } return 1; } # Check the indent level $lines->[0] =~ /^(\s*)/; if ( length($1) < $indent->[-1] ) { return 1; } elsif ( length($1) > $indent->[-1] ) { die \"CPAN::Meta::YAML found bad indenting in line '$lines->[0]'"; } # Find the key my $key; # Quoted keys if ( $lines->[0] =~ s/^\s*$re_capture_single_quoted$re_key_value_separator// ) { $key = $self->_unquote_single($1); } elsif ( $lines->[0] =~ s/^\s*$re_capture_double_quoted$re_key_value_separator// ) { $key = $self->_unquote_double($1); } elsif ( $lines->[0] =~ s/^\s*$re_capture_unquoted_key$re_key_value_separator// ) { $key = $1; $key =~ s/\s+$//; } elsif ( $lines->[0] =~ /^\s*\?/ ) { die \"CPAN::Meta::YAML does not support a feature in line '$lines->[0]'"; } else { die \"CPAN::Meta::YAML failed to classify line '$lines->[0]'"; } if ( exists $hash->{$key} ) { warn "CPAN::Meta::YAML found a duplicate key '$key' in line '$lines->[0]'"; } # Do we have a value? if ( length $lines->[0] ) { # Yes $hash->{$key} = $self->_load_scalar( shift(@$lines), [ @$indent, undef ], $lines ); } else { # An indent shift @$lines; unless ( @$lines ) { $hash->{$key} = undef; return 1; } if ( $lines->[0] =~ /^(\s*)-/ ) { $hash->{$key} = []; $self->_load_array( $hash->{$key}, [ @$indent, length($1) ], $lines ); } elsif ( $lines->[0] =~ /^(\s*)./ ) { my $indent2 = length("$1"); if ( $indent->[-1] >= $indent2 ) { # Null hash entry $hash->{$key} = undef; } else { $hash->{$key} = {}; $self->_load_hash( $hash->{$key}, [ @$indent, length($1) ], $lines ); } } } } return 1; } ### # Dumper functions: # Save an object to a file sub _dump_file { my $self = shift; require Fcntl; # Check the file my $file = shift or $self->_error( 'You did not specify a file name' ); my $fh; # flock if available (or warn if not possible for OS-specific reasons) if ( _can_flock() ) { # Open without truncation (truncate comes after lock) my $flags = Fcntl::O_WRONLY()|Fcntl::O_CREAT(); sysopen( $fh, $file, $flags ); unless ( $fh ) { $self->_error("Failed to open file '$file' for writing: $!"); } # Use no translation and strict UTF-8 binmode( $fh, ":raw:encoding(UTF-8)"); flock( $fh, Fcntl::LOCK_EX() ) or warn "Couldn't lock '$file' for reading: $!"; # truncate and spew contents truncate $fh, 0; seek $fh, 0, 0; } else { open $fh, ">:unix:encoding(UTF-8)", $file; } # serialize and spew to the handle print {$fh} $self->_dump_string; # close the file (release the lock) unless ( close $fh ) { $self->_error("Failed to close file '$file': $!"); } return 1; } # Save an object to a string sub _dump_string { my $self = shift; return '' unless ref $self && @$self; # Iterate over the documents my $indent = 0; my @lines = (); eval { foreach my $cursor ( @$self ) { push @lines, '---'; # An empty document if ( ! defined $cursor ) { # Do nothing # A scalar document } elsif ( ! ref $cursor ) { $lines[-1] .= ' ' . $self->_dump_scalar( $cursor ); # A list at the root } elsif ( ref $cursor eq 'ARRAY' ) { unless ( @$cursor ) { $lines[-1] .= ' []'; next; } push @lines, $self->_dump_array( $cursor, $indent, {} ); # A hash at the root } elsif ( ref $cursor eq 'HASH' ) { unless ( %$cursor ) { $lines[-1] .= ' {}'; next; } push @lines, $self->_dump_hash( $cursor, $indent, {} ); } else { die \("Cannot serialize " . ref($cursor)); } } }; if ( ref $@ eq 'SCALAR' ) { $self->_error(${$@}); } elsif ( $@ ) { $self->_error($@); } join '', map { "$_\n" } @lines; } sub _has_internal_string_value { my $value = shift; my $b_obj = B::svref_2object(\$value); # for round trip problem return $b_obj->FLAGS & B::SVf_POK(); } sub _dump_scalar { my $string = $_[1]; my $is_key = $_[2]; # Check this before checking length or it winds up looking like a string! my $has_string_flag = _has_internal_string_value($string); return '~' unless defined $string; return "''" unless length $string; if (Scalar::Util::looks_like_number($string)) { # keys and values that have been used as strings get quoted if ( $is_key || $has_string_flag ) { return qq['$string']; } else { return $string; } } if ( $string =~ /[\x00-\x09\x0b-\x0d\x0e-\x1f\x7f-\x9f\'\n]/ ) { $string =~ s/\\/\\\\/g; $string =~ s/"/\\"/g; $string =~ s/\n/\\n/g; $string =~ s/[\x85]/\\N/g; $string =~ s/([\x00-\x1f])/\\$UNPRINTABLE[ord($1)]/g; $string =~ s/([\x7f-\x9f])/'\x' . sprintf("%X",ord($1))/ge; return qq|"$string"|; } if ( $string =~ /(?:^[~!@#%&*|>?:,'"`{}\[\]]|^-+$|\s|:\z)/ or $QUOTE{$string} ) { return "'$string'"; } return $string; } sub _dump_array { my ($self, $array, $indent, $seen) = @_; if ( $seen->{refaddr($array)}++ ) { die \"CPAN::Meta::YAML does not support circular references"; } my @lines = (); foreach my $el ( @$array ) { my $line = (' ' x $indent) . '-'; my $type = ref $el; if ( ! $type ) { $line .= ' ' . $self->_dump_scalar( $el ); push @lines, $line; } elsif ( $type eq 'ARRAY' ) { if ( @$el ) { push @lines, $line; push @lines, $self->_dump_array( $el, $indent + 1, $seen ); } else { $line .= ' []'; push @lines, $line; } } elsif ( $type eq 'HASH' ) { if ( keys %$el ) { push @lines, $line; push @lines, $self->_dump_hash( $el, $indent + 1, $seen ); } else { $line .= ' {}'; push @lines, $line; } } else { die \"CPAN::Meta::YAML does not support $type references"; } } @lines; } sub _dump_hash { my ($self, $hash, $indent, $seen) = @_; if ( $seen->{refaddr($hash)}++ ) { die \"CPAN::Meta::YAML does not support circular references"; } my @lines = (); foreach my $name ( sort keys %$hash ) { my $el = $hash->{$name}; my $line = (' ' x $indent) . $self->_dump_scalar($name, 1) . ":"; my $type = ref $el; if ( ! $type ) { $line .= ' ' . $self->_dump_scalar( $el ); push @lines, $line; } elsif ( $type eq 'ARRAY' ) { if ( @$el ) { push @lines, $line; push @lines, $self->_dump_array( $el, $indent + 1, $seen ); } else { $line .= ' []'; push @lines, $line; } } elsif ( $type eq 'HASH' ) { if ( keys %$el ) { push @lines, $line; push @lines, $self->_dump_hash( $el, $indent + 1, $seen ); } else { $line .= ' {}'; push @lines, $line; } } else { die \"CPAN::Meta::YAML does not support $type references"; } } @lines; } ##################################################################### # DEPRECATED API methods: # Error storage (DEPRECATED as of 1.57) our $errstr = ''; # Set error sub _error { require Carp; $errstr = $_[1]; $errstr =~ s/ at \S+ line \d+.*//; Carp::croak( $errstr ); } # Retrieve error my $errstr_warned; sub errstr { require Carp; Carp::carp( "CPAN::Meta::YAML->errstr and \$CPAN::Meta::YAML::errstr is deprecated" ) unless $errstr_warned++; $errstr; } ##################################################################### # Helper functions. Possibly not needed. # Use to detect nv or iv use B; # XXX-INGY Is flock CPAN::Meta::YAML's responsibility? # Some platforms can't flock :-( # XXX-XDG I think it is. When reading and writing files, we ought # to be locking whenever possible. People (foolishly) use YAML # files for things like session storage, which has race issues. my $HAS_FLOCK; sub _can_flock { if ( defined $HAS_FLOCK ) { return $HAS_FLOCK; } else { require Config; my $c = \%Config::Config; $HAS_FLOCK = grep { $c->{$_} } qw/d_flock d_fcntl_can_lock d_lockf/; require Fcntl if $HAS_FLOCK; return $HAS_FLOCK; } } # XXX-INGY Is this core in 5.8.1? Can we remove this? # XXX-XDG Scalar::Util 1.18 didn't land until 5.8.8, so we need this ##################################################################### # Use Scalar::Util if possible, otherwise emulate it use Scalar::Util (); BEGIN { local $@; if ( eval { Scalar::Util->VERSION(1.18); } ) { *refaddr = *Scalar::Util::refaddr; } else { eval <<'END_PERL'; # Scalar::Util failed to load or too old sub refaddr { my $pkg = ref($_[0]) or return undef; if ( !! UNIVERSAL::can($_[0], 'can') ) { bless $_[0], 'Scalar::Util::Fake'; } else { $pkg = undef; } "$_[0]" =~ /0x(\w+)/; my $i = do { no warnings 'portable'; hex $1 }; bless $_[0], $pkg if defined $pkg; $i; } END_PERL } } delete $CPAN::Meta::YAML::{refaddr}; 1; # XXX-INGY Doc notes I'm putting up here. Changing the doc when it's wrong # but leaving grey area stuff up here. # # I would like to change Read/Write to Load/Dump below without # changing the actual API names. # # It might be better to put Load/Dump API in the SYNOPSIS instead of the # dubious OO API. # # null and bool explanations may be outdated. =pod =encoding UTF-8 =head1 NAME CPAN::Meta::YAML - Read and write a subset of YAML for CPAN Meta files =head1 VERSION version 0.018 =head1 SYNOPSIS use CPAN::Meta::YAML; # reading a META file open $fh, "<:utf8", "META.yml"; $yaml_text = do { local $/; <$fh> }; $yaml = CPAN::Meta::YAML->read_string($yaml_text) or die CPAN::Meta::YAML->errstr; # finding the metadata $meta = $yaml->[0]; # writing a META file $yaml_text = $yaml->write_string or die CPAN::Meta::YAML->errstr; open $fh, ">:utf8", "META.yml"; print $fh $yaml_text; =head1 DESCRIPTION This module implements a subset of the YAML specification for use in reading and writing CPAN metadata files like F and F. It should not be used for any other general YAML parsing or generation task. NOTE: F (and F) files should be UTF-8 encoded. Users are responsible for proper encoding and decoding. In particular, the C and C methods do B support UTF-8 and should not be used. =head1 SUPPORT This module is currently derived from L by Adam Kennedy. If there are bugs in how it parses a particular META.yml file, please file a bug report in the YAML::Tiny bugtracker: L =head1 SEE ALSO L, L, L =head1 AUTHORS =over 4 =item * Adam Kennedy =item * David Golden =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2010 by Adam Kennedy. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __END__ # ABSTRACT: Read and write a subset of YAML for CPAN Meta files PK2]9QT4T4Meta/Prereqs.pmnu[use 5.006; use strict; use warnings; package CPAN::Meta::Prereqs; our $VERSION = '2.150010'; #pod =head1 DESCRIPTION #pod #pod A CPAN::Meta::Prereqs object represents the prerequisites for a CPAN #pod distribution or one of its optional features. Each set of prereqs is #pod organized by phase and type, as described in L. #pod #pod =cut use Carp qw(confess); use Scalar::Util qw(blessed); use CPAN::Meta::Requirements 2.121; #pod =method new #pod #pod my $prereq = CPAN::Meta::Prereqs->new( \%prereq_spec ); #pod #pod This method returns a new set of Prereqs. The input should look like the #pod contents of the C field described in L, meaning #pod something more or less like this: #pod #pod my $prereq = CPAN::Meta::Prereqs->new({ #pod runtime => { #pod requires => { #pod 'Some::Module' => '1.234', #pod ..., #pod }, #pod ..., #pod }, #pod ..., #pod }); #pod #pod You can also construct an empty set of prereqs with: #pod #pod my $prereqs = CPAN::Meta::Prereqs->new; #pod #pod This empty set of prereqs is useful for accumulating new prereqs before finally #pod dumping the whole set into a structure or string. #pod #pod =cut # note we also accept anything matching /\Ax_/i sub __legal_phases { qw(configure build test runtime develop) } sub __legal_types { qw(requires recommends suggests conflicts) } # expect a prereq spec from META.json -- rjbs, 2010-04-11 sub new { my ($class, $prereq_spec) = @_; $prereq_spec ||= {}; my %is_legal_phase = map {; $_ => 1 } $class->__legal_phases; my %is_legal_type = map {; $_ => 1 } $class->__legal_types; my %guts; PHASE: for my $phase (keys %$prereq_spec) { next PHASE unless $phase =~ /\Ax_/i or $is_legal_phase{$phase}; my $phase_spec = $prereq_spec->{ $phase }; next PHASE unless keys %$phase_spec; TYPE: for my $type (keys %$phase_spec) { next TYPE unless $type =~ /\Ax_/i or $is_legal_type{$type}; my $spec = $phase_spec->{ $type }; next TYPE unless keys %$spec; $guts{prereqs}{$phase}{$type} = CPAN::Meta::Requirements->from_string_hash( $spec ); } } return bless \%guts => $class; } #pod =method requirements_for #pod #pod my $requirements = $prereqs->requirements_for( $phase, $type ); #pod #pod This method returns a L object for the given #pod phase/type combination. If no prerequisites are registered for that #pod combination, a new CPAN::Meta::Requirements object will be returned, and it may #pod be added to as needed. #pod #pod If C<$phase> or C<$type> are undefined or otherwise invalid, an exception will #pod be raised. #pod #pod =cut sub requirements_for { my ($self, $phase, $type) = @_; confess "requirements_for called without phase" unless defined $phase; confess "requirements_for called without type" unless defined $type; unless ($phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases) { confess "requested requirements for unknown phase: $phase"; } unless ($type =~ /\Ax_/i or grep { $type eq $_ } $self->__legal_types) { confess "requested requirements for unknown type: $type"; } my $req = ($self->{prereqs}{$phase}{$type} ||= CPAN::Meta::Requirements->new); $req->finalize if $self->is_finalized; return $req; } #pod =method phases #pod #pod my @phases = $prereqs->phases; #pod #pod This method returns the list of all phases currently populated in the prereqs #pod object, suitable for iterating. #pod #pod =cut sub phases { my ($self) = @_; my %is_legal_phase = map {; $_ => 1 } $self->__legal_phases; grep { /\Ax_/i or $is_legal_phase{$_} } keys %{ $self->{prereqs} }; } #pod =method types_in #pod #pod my @runtime_types = $prereqs->types_in('runtime'); #pod #pod This method returns the list of all types currently populated in the prereqs #pod object for the provided phase, suitable for iterating. #pod #pod =cut sub types_in { my ($self, $phase) = @_; return unless $phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases; my %is_legal_type = map {; $_ => 1 } $self->__legal_types; grep { /\Ax_/i or $is_legal_type{$_} } keys %{ $self->{prereqs}{$phase} }; } #pod =method with_merged_prereqs #pod #pod my $new_prereqs = $prereqs->with_merged_prereqs( $other_prereqs ); #pod #pod my $new_prereqs = $prereqs->with_merged_prereqs( \@other_prereqs ); #pod #pod This method returns a new CPAN::Meta::Prereqs objects in which all the #pod other prerequisites given are merged into the current set. This is primarily #pod provided for combining a distribution's core prereqs with the prereqs of one of #pod its optional features. #pod #pod The new prereqs object has no ties to the originals, and altering it further #pod will not alter them. #pod #pod =cut sub with_merged_prereqs { my ($self, $other) = @_; my @other = blessed($other) ? $other : @$other; my @prereq_objs = ($self, @other); my %new_arg; for my $phase (__uniq(map { $_->phases } @prereq_objs)) { for my $type (__uniq(map { $_->types_in($phase) } @prereq_objs)) { my $req = CPAN::Meta::Requirements->new; for my $prereq (@prereq_objs) { my $this_req = $prereq->requirements_for($phase, $type); next unless $this_req->required_modules; $req->add_requirements($this_req); } next unless $req->required_modules; $new_arg{ $phase }{ $type } = $req->as_string_hash; } } return (ref $self)->new(\%new_arg); } #pod =method merged_requirements #pod #pod my $new_reqs = $prereqs->merged_requirements( \@phases, \@types ); #pod my $new_reqs = $prereqs->merged_requirements( \@phases ); #pod my $new_reqs = $prereqs->merged_requirements(); #pod #pod This method joins together all requirements across a number of phases #pod and types into a new L object. If arguments #pod are omitted, it defaults to "runtime", "build" and "test" for phases #pod and "requires" and "recommends" for types. #pod #pod =cut sub merged_requirements { my ($self, $phases, $types) = @_; $phases = [qw/runtime build test/] unless defined $phases; $types = [qw/requires recommends/] unless defined $types; confess "merged_requirements phases argument must be an arrayref" unless ref $phases eq 'ARRAY'; confess "merged_requirements types argument must be an arrayref" unless ref $types eq 'ARRAY'; my $req = CPAN::Meta::Requirements->new; for my $phase ( @$phases ) { unless ($phase =~ /\Ax_/i or grep { $phase eq $_ } $self->__legal_phases) { confess "requested requirements for unknown phase: $phase"; } for my $type ( @$types ) { unless ($type =~ /\Ax_/i or grep { $type eq $_ } $self->__legal_types) { confess "requested requirements for unknown type: $type"; } $req->add_requirements( $self->requirements_for($phase, $type) ); } } $req->finalize if $self->is_finalized; return $req; } #pod =method as_string_hash #pod #pod This method returns a hashref containing structures suitable for dumping into a #pod distmeta data structure. It is made up of hashes and strings, only; there will #pod be no Prereqs, CPAN::Meta::Requirements, or C objects inside it. #pod #pod =cut sub as_string_hash { my ($self) = @_; my %hash; for my $phase ($self->phases) { for my $type ($self->types_in($phase)) { my $req = $self->requirements_for($phase, $type); next unless $req->required_modules; $hash{ $phase }{ $type } = $req->as_string_hash; } } return \%hash; } #pod =method is_finalized #pod #pod This method returns true if the set of prereqs has been marked "finalized," and #pod cannot be altered. #pod #pod =cut sub is_finalized { $_[0]{finalized} } #pod =method finalize #pod #pod Calling C on a Prereqs object will close it for further modification. #pod Attempting to make any changes that would actually alter the prereqs will #pod result in an exception being thrown. #pod #pod =cut sub finalize { my ($self) = @_; $self->{finalized} = 1; for my $phase (keys %{ $self->{prereqs} }) { $_->finalize for values %{ $self->{prereqs}{$phase} }; } } #pod =method clone #pod #pod my $cloned_prereqs = $prereqs->clone; #pod #pod This method returns a Prereqs object that is identical to the original object, #pod but can be altered without affecting the original object. Finalization does #pod not survive cloning, meaning that you may clone a finalized set of prereqs and #pod then modify the clone. #pod #pod =cut sub clone { my ($self) = @_; my $clone = (ref $self)->new( $self->as_string_hash ); } sub __uniq { my (%s, $u); grep { defined($_) ? !$s{$_}++ : !$u++ } @_; } 1; # ABSTRACT: a set of distribution prerequisites by phase and type =pod =encoding UTF-8 =head1 NAME CPAN::Meta::Prereqs - a set of distribution prerequisites by phase and type =head1 VERSION version 2.150010 =head1 DESCRIPTION A CPAN::Meta::Prereqs object represents the prerequisites for a CPAN distribution or one of its optional features. Each set of prereqs is organized by phase and type, as described in L. =head1 METHODS =head2 new my $prereq = CPAN::Meta::Prereqs->new( \%prereq_spec ); This method returns a new set of Prereqs. The input should look like the contents of the C field described in L, meaning something more or less like this: my $prereq = CPAN::Meta::Prereqs->new({ runtime => { requires => { 'Some::Module' => '1.234', ..., }, ..., }, ..., }); You can also construct an empty set of prereqs with: my $prereqs = CPAN::Meta::Prereqs->new; This empty set of prereqs is useful for accumulating new prereqs before finally dumping the whole set into a structure or string. =head2 requirements_for my $requirements = $prereqs->requirements_for( $phase, $type ); This method returns a L object for the given phase/type combination. If no prerequisites are registered for that combination, a new CPAN::Meta::Requirements object will be returned, and it may be added to as needed. If C<$phase> or C<$type> are undefined or otherwise invalid, an exception will be raised. =head2 phases my @phases = $prereqs->phases; This method returns the list of all phases currently populated in the prereqs object, suitable for iterating. =head2 types_in my @runtime_types = $prereqs->types_in('runtime'); This method returns the list of all types currently populated in the prereqs object for the provided phase, suitable for iterating. =head2 with_merged_prereqs my $new_prereqs = $prereqs->with_merged_prereqs( $other_prereqs ); my $new_prereqs = $prereqs->with_merged_prereqs( \@other_prereqs ); This method returns a new CPAN::Meta::Prereqs objects in which all the other prerequisites given are merged into the current set. This is primarily provided for combining a distribution's core prereqs with the prereqs of one of its optional features. The new prereqs object has no ties to the originals, and altering it further will not alter them. =head2 merged_requirements my $new_reqs = $prereqs->merged_requirements( \@phases, \@types ); my $new_reqs = $prereqs->merged_requirements( \@phases ); my $new_reqs = $prereqs->merged_requirements(); This method joins together all requirements across a number of phases and types into a new L object. If arguments are omitted, it defaults to "runtime", "build" and "test" for phases and "requires" and "recommends" for types. =head2 as_string_hash This method returns a hashref containing structures suitable for dumping into a distmeta data structure. It is made up of hashes and strings, only; there will be no Prereqs, CPAN::Meta::Requirements, or C objects inside it. =head2 is_finalized This method returns true if the set of prereqs has been marked "finalized," and cannot be altered. =head2 finalize Calling C on a Prereqs object will close it for further modification. Attempting to make any changes that would actually alter the prereqs will result in an exception being thrown. =head2 clone my $cloned_prereqs = $prereqs->clone; This method returns a Prereqs object that is identical to the original object, but can be altered without affecting the original object. Finalization does not survive cloning, meaning that you may clone a finalized set of prereqs and then modify the clone. =head1 BUGS Please report any bugs or feature using the CPAN Request Tracker. Bugs can be submitted through the web interface at L When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature. =head1 AUTHORS =over 4 =item * David Golden =item * Ricardo Signes =item * Adam Kennedy =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2010 by David Golden, Ricardo Signes, Adam Kennedy and Contributors. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __END__ # vim: ts=2 sts=2 sw=2 et : PK2]AMeta/History.pmnu[# vi:tw=72 use 5.006; use strict; use warnings; package CPAN::Meta::History; our $VERSION = '2.150010'; 1; # ABSTRACT: history of CPAN Meta Spec changes __END__ =pod =encoding UTF-8 =head1 NAME CPAN::Meta::History - history of CPAN Meta Spec changes =head1 VERSION version 2.150010 =head1 DESCRIPTION The CPAN Meta Spec has gone through several iterations. It was originally written in HTML and later revised into POD (though published in HTML generated from the POD). Fields were added, removed or changed, sometimes by design and sometimes to reflect real-world usage after the fact. This document reconstructs the history of the CPAN Meta Spec based on change logs, repository commit messages and the published HTML files. In some cases, particularly prior to version 1.2, the exact version when certain fields were introduced or changed is inconsistent between sources. When in doubt, the published HTML files for versions 1.0 to 1.4 as they existed when version 2 was developed are used as the definitive source. Starting with version 2, the specification document is part of the CPAN-Meta distribution and will be published on CPAN as L. Going forward, specification version numbers will be integers and decimal portions will correspond to a release date for the CPAN::Meta library. =head1 HISTORY =head2 Version 2 April 2010 =over =item * Revised spec examples as perl data structures rather than YAML =item * Switched to JSON serialization from YAML =item * Specified allowed version number formats =item * Replaced 'requires', 'build_requires', 'configure_requires', 'recommends' and 'conflicts' with new 'prereqs' data structure divided by I (configure, build, test, runtime, etc.) and I (requires, recommends, suggests, conflicts) =item * Added support for 'develop' phase for requirements for maintaining a list of authoring tools =item * Changed 'license' to a list and revised the set of valid licenses =item * Made 'dynamic_config' mandatory to reduce confusion =item * Changed 'resources' subkey 'repository' to a hash that clarifies repository type, url for browsing and url for checkout =item * Changed 'resources' subkey 'bugtracker' to a hash for either web or mailto resource =item * Changed specification of 'optional_features': =over =item * Added formal specification and usage guide instead of just example =item * Changed to use new prereqs data structure instead of individual keys =back =item * Clarified intended use of 'author' as generalized contact list =item * Added 'release_status' field to indicate stable, testing or unstable status to provide hints to indexers =item * Added 'description' field for a longer description of the distribution =item * Formalized use of "x_" or "X_" for all custom keys not listed in the official spec =back =head2 Version 1.4 June 2008 =over =item * Noted explicit support for 'perl' in prerequisites =item * Added 'configure_requires' prerequisite type =item * Changed 'optional_features' =over =item * Example corrected to show map of maps instead of list of maps (though descriptive text said 'map' even in v1.3) =item * Removed 'requires_packages', 'requires_os' and 'excluded_os' as valid subkeys =back =back =head2 Version 1.3 November 2006 =over =item * Added 'no_index' subkey 'directory' and removed 'dir' to match actual usage in the wild =item * Added a 'repository' subkey to 'resources' =back =head2 Version 1.2 August 2005 =over =item * Re-wrote and restructured spec in POD syntax =item * Changed 'name' to be mandatory =item * Changed 'generated_by' to be mandatory =item * Changed 'license' to be mandatory =item * Added version range specifications for prerequisites =item * Added required 'abstract' field =item * Added required 'author' field =item * Added required 'meta-spec' field to define 'version' (and 'url') of the CPAN Meta Spec used for metadata =item * Added 'provides' field =item * Added 'no_index' field and deprecated 'private' field. 'no_index' subkeys include 'file', 'dir', 'package' and 'namespace' =item * Added 'keywords' field =item * Added 'resources' field with subkeys 'homepage', 'license', and 'bugtracker' =item * Added 'optional_features' field as an alternate under 'recommends'. Includes 'description', 'requires', 'build_requires', 'conflicts', 'requires_packages', 'requires_os' and 'excluded_os' as valid subkeys =item * Removed 'license_uri' field =back =head2 Version 1.1 May 2003 =over =item * Changed 'version' to be mandatory =item * Added 'private' field =item * Added 'license_uri' field =back =head2 Version 1.0 March 2003 =over =item * Original release (in HTML format only) =item * Included 'name', 'version', 'license', 'distribution_type', 'requires', 'recommends', 'build_requires', 'conflicts', 'dynamic_config', 'generated_by' =back =head1 AUTHORS =over 4 =item * David Golden =item * Ricardo Signes =item * Adam Kennedy =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2010 by David Golden, Ricardo Signes, Adam Kennedy and Contributors. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut PK2]somFUFUMeta/Requirements.pmnu6$use v5.10; use strict; use warnings; package CPAN::Meta::Requirements; # ABSTRACT: a set of version requirements for a CPAN dist our $VERSION = '2.143'; use CPAN::Meta::Requirements::Range; #pod =head1 SYNOPSIS #pod #pod use CPAN::Meta::Requirements; #pod #pod my $build_requires = CPAN::Meta::Requirements->new; #pod #pod $build_requires->add_minimum('Library::Foo' => 1.208); #pod #pod $build_requires->add_minimum('Library::Foo' => 2.602); #pod #pod $build_requires->add_minimum('Module::Bar' => 'v1.2.3'); #pod #pod $METAyml->{build_requires} = $build_requires->as_string_hash; #pod #pod =head1 DESCRIPTION #pod #pod A CPAN::Meta::Requirements object models a set of version constraints like #pod those specified in the F or F files in CPAN distributions, #pod and as defined by L. #pod It can be built up by adding more and more constraints, and it will reduce them #pod to the simplest representation. #pod #pod Logically impossible constraints will be identified immediately by thrown #pod exceptions. #pod #pod =cut use Carp (); #pod =method new #pod #pod my $req = CPAN::Meta::Requirements->new; #pod #pod This returns a new CPAN::Meta::Requirements object. It takes an optional #pod hash reference argument. Currently, only one key is supported: #pod #pod =for :list #pod * C -- if provided, when a version cannot be parsed into #pod a version object, this code reference will be called with the invalid #pod version string as first argument, and the module name as second #pod argument. It must return a valid version object. #pod #pod All other keys are ignored. #pod #pod =cut my @valid_options = qw( bad_version_hook ); sub new { my ($class, $options) = @_; $options ||= {}; Carp::croak "Argument to $class\->new() must be a hash reference" unless ref $options eq 'HASH'; my %self = map {; $_ => $options->{$_}} @valid_options; return bless \%self => $class; } #pod =method add_minimum #pod #pod $req->add_minimum( $module => $version ); #pod #pod This adds a new minimum version requirement. If the new requirement is #pod redundant to the existing specification, this has no effect. #pod #pod Minimum requirements are inclusive. C<$version> is required, along with any #pod greater version number. #pod #pod This method returns the requirements object. #pod #pod =method add_maximum #pod #pod $req->add_maximum( $module => $version ); #pod #pod This adds a new maximum version requirement. If the new requirement is #pod redundant to the existing specification, this has no effect. #pod #pod Maximum requirements are inclusive. No version strictly greater than the given #pod version is allowed. #pod #pod This method returns the requirements object. #pod #pod =method add_exclusion #pod #pod $req->add_exclusion( $module => $version ); #pod #pod This adds a new excluded version. For example, you might use these three #pod method calls: #pod #pod $req->add_minimum( $module => '1.00' ); #pod $req->add_maximum( $module => '1.82' ); #pod #pod $req->add_exclusion( $module => '1.75' ); #pod #pod Any version between 1.00 and 1.82 inclusive would be acceptable, except for #pod 1.75. #pod #pod This method returns the requirements object. #pod #pod =method exact_version #pod #pod $req->exact_version( $module => $version ); #pod #pod This sets the version required for the given module to I the given #pod version. No other version would be considered acceptable. #pod #pod This method returns the requirements object. #pod #pod =cut BEGIN { for my $type (qw(maximum exclusion exact_version)) { my $method = "with_$type"; my $to_add = $type eq 'exact_version' ? $type : "add_$type"; my $code = sub { my ($self, $name, $version) = @_; $self->__modify_entry_for($name, $method, $version); return $self; }; no strict 'refs'; *$to_add = $code; } } # add_minimum is optimized compared to generated subs above because # it is called frequently and with "0" or equivalent input sub add_minimum { my ($self, $name, $version) = @_; # stringify $version so that version->new("0.00")->stringify ne "0" # which preserves the user's choice of "0.00" as the requirement if (not defined $version or "$version" eq '0') { return $self if $self->__entry_for($name); Carp::croak("can't add new requirements to finalized requirements") if $self->is_finalized; $self->{requirements}{ $name } = CPAN::Meta::Requirements::Range->with_minimum('0', $name); } else { $self->__modify_entry_for($name, 'with_minimum', $version); } return $self; } #pod =method version_range_for_module #pod #pod $req->version_range_for_module( $another_req_object ); #pod #pod =cut sub version_range_for_module { my ($self, $module) = @_; return $self->{requirements}{$module}; } #pod =method add_requirements #pod #pod $req->add_requirements( $another_req_object ); #pod #pod This method adds all the requirements in the given CPAN::Meta::Requirements #pod object to the requirements object on which it was called. If there are any #pod conflicts, an exception is thrown. #pod #pod This method returns the requirements object. #pod #pod =cut sub add_requirements { my ($self, $req) = @_; for my $module ($req->required_modules) { my $new_range = $req->version_range_for_module($module); $self->__modify_entry_for($module, 'with_range', $new_range); } return $self; } #pod =method accepts_module #pod #pod my $bool = $req->accepts_module($module => $version); #pod #pod Given an module and version, this method returns true if the version #pod specification for the module accepts the provided version. In other words, #pod given: #pod #pod Module => '>= 1.00, < 2.00' #pod #pod We will accept 1.00 and 1.75 but not 0.50 or 2.00. #pod #pod For modules that do not appear in the requirements, this method will return #pod true. #pod #pod =cut sub accepts_module { my ($self, $module, $version) = @_; return 1 unless my $range = $self->__entry_for($module); return $range->accepts($version); } #pod =method clear_requirement #pod #pod $req->clear_requirement( $module ); #pod #pod This removes the requirement for a given module from the object. #pod #pod This method returns the requirements object. #pod #pod =cut sub clear_requirement { my ($self, $module) = @_; return $self unless $self->__entry_for($module); Carp::croak("can't clear requirements on finalized requirements") if $self->is_finalized; delete $self->{requirements}{ $module }; return $self; } #pod =method requirements_for_module #pod #pod $req->requirements_for_module( $module ); #pod #pod This returns a string containing the version requirements for a given module in #pod the format described in L or undef if the given module has no #pod requirements. This should only be used for informational purposes such as error #pod messages and should not be interpreted or used for comparison (see #pod L instead). #pod #pod =cut sub requirements_for_module { my ($self, $module) = @_; my $entry = $self->__entry_for($module); return unless $entry; return $entry->as_string; } #pod =method structured_requirements_for_module #pod #pod $req->structured_requirements_for_module( $module ); #pod #pod This returns a data structure containing the version requirements for a given #pod module or undef if the given module has no requirements. This should #pod not be used for version checks (see L instead). #pod #pod Added in version 2.134. #pod #pod =cut sub structured_requirements_for_module { my ($self, $module) = @_; my $entry = $self->__entry_for($module); return unless $entry; return $entry->as_struct; } #pod =method required_modules #pod #pod This method returns a list of all the modules for which requirements have been #pod specified. #pod #pod =cut sub required_modules { keys %{ $_[0]{requirements} } } #pod =method clone #pod #pod $req->clone; #pod #pod This method returns a clone of the invocant. The clone and the original object #pod can then be changed independent of one another. #pod #pod =cut sub clone { my ($self) = @_; my $new = (ref $self)->new; return $new->add_requirements($self); } sub __entry_for { $_[0]{requirements}{ $_[1] } } sub __modify_entry_for { my ($self, $name, $method, $version) = @_; my $fin = $self->is_finalized; my $old = $self->__entry_for($name); Carp::croak("can't add new requirements to finalized requirements") if $fin and not $old; my $new = ($old || 'CPAN::Meta::Requirements::Range') ->$method($version, $name, $self->{bad_version_hook}); Carp::croak("can't modify finalized requirements") if $fin and $old->as_string ne $new->as_string; $self->{requirements}{ $name } = $new; } #pod =method is_simple #pod #pod This method returns true if and only if all requirements are inclusive minimums #pod -- that is, if their string expression is just the version number. #pod #pod =cut sub is_simple { my ($self) = @_; for my $module ($self->required_modules) { # XXX: This is a complete hack, but also entirely correct. return if not $self->__entry_for($module)->is_simple; } return 1; } #pod =method is_finalized #pod #pod This method returns true if the requirements have been finalized by having the #pod C method called on them. #pod #pod =cut sub is_finalized { $_[0]{finalized} } #pod =method finalize #pod #pod This method marks the requirements finalized. Subsequent attempts to change #pod the requirements will be fatal, I they would result in a change. If they #pod would not alter the requirements, they have no effect. #pod #pod If a finalized set of requirements is cloned, the cloned requirements are not #pod also finalized. #pod #pod =cut sub finalize { $_[0]{finalized} = 1 } #pod =method as_string_hash #pod #pod This returns a reference to a hash describing the requirements using the #pod strings in the L specification. #pod #pod For example after the following program: #pod #pod my $req = CPAN::Meta::Requirements->new; #pod #pod $req->add_minimum('CPAN::Meta::Requirements' => 0.102); #pod #pod $req->add_minimum('Library::Foo' => 1.208); #pod #pod $req->add_maximum('Library::Foo' => 2.602); #pod #pod $req->add_minimum('Module::Bar' => 'v1.2.3'); #pod #pod $req->add_exclusion('Module::Bar' => 'v1.2.8'); #pod #pod $req->exact_version('Xyzzy' => '6.01'); #pod #pod my $hashref = $req->as_string_hash; #pod #pod C<$hashref> would contain: #pod #pod { #pod 'CPAN::Meta::Requirements' => '0.102', #pod 'Library::Foo' => '>= 1.208, <= 2.206', #pod 'Module::Bar' => '>= v1.2.3, != v1.2.8', #pod 'Xyzzy' => '== 6.01', #pod } #pod #pod =cut sub as_string_hash { my ($self) = @_; my %hash = map {; $_ => $self->{requirements}{$_}->as_string } $self->required_modules; return \%hash; } #pod =method add_string_requirement #pod #pod $req->add_string_requirement('Library::Foo' => '>= 1.208, <= 2.206'); #pod $req->add_string_requirement('Library::Foo' => v1.208); #pod #pod This method parses the passed in string and adds the appropriate requirement #pod for the given module. A version can be a Perl "v-string". It understands #pod version ranges as described in the L. For #pod example: #pod #pod =over 4 #pod #pod =item 1.3 #pod #pod =item >= 1.3 #pod #pod =item <= 1.3 #pod #pod =item == 1.3 #pod #pod =item != 1.3 #pod #pod =item > 1.3 #pod #pod =item < 1.3 #pod #pod =item >= 1.3, != 1.5, <= 2.0 #pod #pod A version number without an operator is equivalent to specifying a minimum #pod (C=>). Extra whitespace is allowed. #pod #pod =back #pod #pod =cut sub add_string_requirement { my ($self, $module, $req) = @_; $self->__modify_entry_for($module, 'with_string_requirement', $req); } #pod =method from_string_hash #pod #pod my $req = CPAN::Meta::Requirements->from_string_hash( \%hash ); #pod my $req = CPAN::Meta::Requirements->from_string_hash( \%hash, \%opts ); #pod #pod This is an alternate constructor for a CPAN::Meta::Requirements #pod object. It takes a hash of module names and version requirement #pod strings and returns a new CPAN::Meta::Requirements object. As with #pod add_string_requirement, a version can be a Perl "v-string". Optionally, #pod you can supply a hash-reference of options, exactly as with the L #pod method. #pod #pod =cut sub from_string_hash { my ($class, $hash, $options) = @_; my $self = $class->new($options); for my $module (keys %$hash) { my $req = $hash->{$module}; $self->add_string_requirement($module, $req); } return $self; } 1; # vim: ts=2 sts=2 sw=2 et: __END__ =pod =encoding UTF-8 =head1 NAME CPAN::Meta::Requirements - a set of version requirements for a CPAN dist =head1 VERSION version 2.143 =head1 SYNOPSIS use CPAN::Meta::Requirements; my $build_requires = CPAN::Meta::Requirements->new; $build_requires->add_minimum('Library::Foo' => 1.208); $build_requires->add_minimum('Library::Foo' => 2.602); $build_requires->add_minimum('Module::Bar' => 'v1.2.3'); $METAyml->{build_requires} = $build_requires->as_string_hash; =head1 DESCRIPTION A CPAN::Meta::Requirements object models a set of version constraints like those specified in the F or F files in CPAN distributions, and as defined by L. It can be built up by adding more and more constraints, and it will reduce them to the simplest representation. Logically impossible constraints will be identified immediately by thrown exceptions. =head1 METHODS =head2 new my $req = CPAN::Meta::Requirements->new; This returns a new CPAN::Meta::Requirements object. It takes an optional hash reference argument. Currently, only one key is supported: =over 4 =item * C -- if provided, when a version cannot be parsed into a version object, this code reference will be called with the invalid version string as first argument, and the module name as second argument. It must return a valid version object. =back All other keys are ignored. =head2 add_minimum $req->add_minimum( $module => $version ); This adds a new minimum version requirement. If the new requirement is redundant to the existing specification, this has no effect. Minimum requirements are inclusive. C<$version> is required, along with any greater version number. This method returns the requirements object. =head2 add_maximum $req->add_maximum( $module => $version ); This adds a new maximum version requirement. If the new requirement is redundant to the existing specification, this has no effect. Maximum requirements are inclusive. No version strictly greater than the given version is allowed. This method returns the requirements object. =head2 add_exclusion $req->add_exclusion( $module => $version ); This adds a new excluded version. For example, you might use these three method calls: $req->add_minimum( $module => '1.00' ); $req->add_maximum( $module => '1.82' ); $req->add_exclusion( $module => '1.75' ); Any version between 1.00 and 1.82 inclusive would be acceptable, except for 1.75. This method returns the requirements object. =head2 exact_version $req->exact_version( $module => $version ); This sets the version required for the given module to I the given version. No other version would be considered acceptable. This method returns the requirements object. =head2 version_range_for_module $req->version_range_for_module( $another_req_object ); =head2 add_requirements $req->add_requirements( $another_req_object ); This method adds all the requirements in the given CPAN::Meta::Requirements object to the requirements object on which it was called. If there are any conflicts, an exception is thrown. This method returns the requirements object. =head2 accepts_module my $bool = $req->accepts_module($module => $version); Given an module and version, this method returns true if the version specification for the module accepts the provided version. In other words, given: Module => '>= 1.00, < 2.00' We will accept 1.00 and 1.75 but not 0.50 or 2.00. For modules that do not appear in the requirements, this method will return true. =head2 clear_requirement $req->clear_requirement( $module ); This removes the requirement for a given module from the object. This method returns the requirements object. =head2 requirements_for_module $req->requirements_for_module( $module ); This returns a string containing the version requirements for a given module in the format described in L or undef if the given module has no requirements. This should only be used for informational purposes such as error messages and should not be interpreted or used for comparison (see L instead). =head2 structured_requirements_for_module $req->structured_requirements_for_module( $module ); This returns a data structure containing the version requirements for a given module or undef if the given module has no requirements. This should not be used for version checks (see L instead). Added in version 2.134. =head2 required_modules This method returns a list of all the modules for which requirements have been specified. =head2 clone $req->clone; This method returns a clone of the invocant. The clone and the original object can then be changed independent of one another. =head2 is_simple This method returns true if and only if all requirements are inclusive minimums -- that is, if their string expression is just the version number. =head2 is_finalized This method returns true if the requirements have been finalized by having the C method called on them. =head2 finalize This method marks the requirements finalized. Subsequent attempts to change the requirements will be fatal, I they would result in a change. If they would not alter the requirements, they have no effect. If a finalized set of requirements is cloned, the cloned requirements are not also finalized. =head2 as_string_hash This returns a reference to a hash describing the requirements using the strings in the L specification. For example after the following program: my $req = CPAN::Meta::Requirements->new; $req->add_minimum('CPAN::Meta::Requirements' => 0.102); $req->add_minimum('Library::Foo' => 1.208); $req->add_maximum('Library::Foo' => 2.602); $req->add_minimum('Module::Bar' => 'v1.2.3'); $req->add_exclusion('Module::Bar' => 'v1.2.8'); $req->exact_version('Xyzzy' => '6.01'); my $hashref = $req->as_string_hash; C<$hashref> would contain: { 'CPAN::Meta::Requirements' => '0.102', 'Library::Foo' => '>= 1.208, <= 2.206', 'Module::Bar' => '>= v1.2.3, != v1.2.8', 'Xyzzy' => '== 6.01', } =head2 add_string_requirement $req->add_string_requirement('Library::Foo' => '>= 1.208, <= 2.206'); $req->add_string_requirement('Library::Foo' => v1.208); This method parses the passed in string and adds the appropriate requirement for the given module. A version can be a Perl "v-string". It understands version ranges as described in the L. For example: =over 4 =item 1.3 =item >= 1.3 =item <= 1.3 =item == 1.3 =item != 1.3 =item > 1.3 =item < 1.3 =item >= 1.3, != 1.5, <= 2.0 A version number without an operator is equivalent to specifying a minimum (C=>). Extra whitespace is allowed. =back =head2 from_string_hash my $req = CPAN::Meta::Requirements->from_string_hash( \%hash ); my $req = CPAN::Meta::Requirements->from_string_hash( \%hash, \%opts ); This is an alternate constructor for a CPAN::Meta::Requirements object. It takes a hash of module names and version requirement strings and returns a new CPAN::Meta::Requirements object. As with add_string_requirement, a version can be a Perl "v-string". Optionally, you can supply a hash-reference of options, exactly as with the L method. =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements.git =head1 AUTHORS =over 4 =item * David Golden =item * Ricardo Signes =back =head1 CONTRIBUTORS =for stopwords Ed J Graham Knop Karen Etheridge Leon Timmermans Paul Howarth Ricardo Signes robario Tatsuhiko Miyagawa =over 4 =item * Ed J =item * Graham Knop =item * Karen Etheridge =item * Leon Timmermans =item * Paul Howarth =item * Ricardo Signes =item * robario =item * Tatsuhiko Miyagawa =item * Tatsuhiko Miyagawa =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2010 by David Golden and Ricardo Signes. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut PK2]Xa a Meta/Feature.pmnu[use 5.006; use strict; use warnings; package CPAN::Meta::Feature; our $VERSION = '2.150010'; use CPAN::Meta::Prereqs; #pod =head1 DESCRIPTION #pod #pod A CPAN::Meta::Feature object describes an optional feature offered by a CPAN #pod distribution and specified in the distribution's F (or F) #pod file. #pod #pod For the most part, this class will only be used when operating on the result of #pod the C or C methods on a L object. #pod #pod =method new #pod #pod my $feature = CPAN::Meta::Feature->new( $identifier => \%spec ); #pod #pod This returns a new Feature object. The C<%spec> argument to the constructor #pod should be the same as the value of the C entry in the #pod distmeta. It must contain entries for C and C. #pod #pod =cut sub new { my ($class, $identifier, $spec) = @_; my %guts = ( identifier => $identifier, description => $spec->{description}, prereqs => CPAN::Meta::Prereqs->new($spec->{prereqs}), ); bless \%guts => $class; } #pod =method identifier #pod #pod This method returns the feature's identifier. #pod #pod =cut sub identifier { $_[0]{identifier} } #pod =method description #pod #pod This method returns the feature's long description. #pod #pod =cut sub description { $_[0]{description} } #pod =method prereqs #pod #pod This method returns the feature's prerequisites as a L #pod object. #pod #pod =cut sub prereqs { $_[0]{prereqs} } 1; # ABSTRACT: an optional feature provided by a CPAN distribution =pod =encoding UTF-8 =head1 NAME CPAN::Meta::Feature - an optional feature provided by a CPAN distribution =head1 VERSION version 2.150010 =head1 DESCRIPTION A CPAN::Meta::Feature object describes an optional feature offered by a CPAN distribution and specified in the distribution's F (or F) file. For the most part, this class will only be used when operating on the result of the C or C methods on a L object. =head1 METHODS =head2 new my $feature = CPAN::Meta::Feature->new( $identifier => \%spec ); This returns a new Feature object. The C<%spec> argument to the constructor should be the same as the value of the C entry in the distmeta. It must contain entries for C and C. =head2 identifier This method returns the feature's identifier. =head2 description This method returns the feature's long description. =head2 prereqs This method returns the feature's prerequisites as a L object. =head1 BUGS Please report any bugs or feature using the CPAN Request Tracker. Bugs can be submitted through the web interface at L When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature. =head1 AUTHORS =over 4 =item * David Golden =item * Ricardo Signes =item * Adam Kennedy =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2010 by David Golden, Ricardo Signes, Adam Kennedy and Contributors. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut __END__ # vim: ts=2 sts=2 sw=2 et : PK2]]sHHHHMeta/History/Meta_1_3.podnu[=for :stopwords MailingList PODs RWS subcontext =head1 NAME CPAN::Meta::History::Meta_1_3 - Version 1.3 metadata specification for META.yml =head1 PREFACE This is a historical copy of the version 1.3 specification for F files, copyright by Ken Williams and licensed under the same terms as Perl itself. Modifications from the original: =over =item * Various spelling corrections =item * Include list of valid licenses from L 0.2805 rather than linking to the module, with minor updates to text and links to reflect versions at the time of publication. =item * Fixed some dead links to point to active resources. =back =head1 SYNOPSIS --- #YAML:1.0 name: Module-Build abstract: Build and install Perl modules version: 0.20 author: - Ken Williams license: perl distribution_type: module requires: Config: 0 Cwd: 0 Data::Dumper: 0 ExtUtils::Install: 0 File::Basename: 0 File::Compare: 0 File::Copy: 0 File::Find: 0 File::Path: 0 File::Spec: 0 IO::File: 0 perl: 5.005_03 recommends: Archive::Tar: 1.00 ExtUtils::Install: 0.3 ExtUtils::ParseXS: 2.02 Pod::Text: 0 YAML: 0.35 build_requires: Test: 0 urls: license: http://dev.perl.org/licenses/ meta-spec: version: 1.3 url: http://module-build.sourceforge.net/META-spec-v1.3.html generated_by: Module::Build version 0.20 =head1 DESCRIPTION This document describes version 1.3 of the F specification. The F file describes important properties of contributed Perl distributions such as the ones found on CPAN. It is typically created by tools like Module::Build, Module::Install, and ExtUtils::MakeMaker. The fields in the F file are meant to be helpful for people maintaining module collections (like CPAN), for people writing installation tools (like CPAN.pm or CPANPLUS), or just for people who want to know some stuff about a distribution before downloading it and starting to install it. I, and the latest development version (which may include things that won't make it into the stable version) can always be found at L.> =head1 FORMAT F files are written in the YAML format (see L). See the following links to learn why we chose YAML instead of, say, XML or Data::Dumper: =over 4 =item * L =item * L =item * L =back =head1 TERMINOLOGY =over 4 =item distribution This is the primary object described by the F specification. In the context of this document it usually refers to a collection of modules, scripts, and/or documents that are distributed together for other developers to use. Examples of distributions are C, C, or C. =item module This refers to a reusable library of code typically contained in a single file. Currently, we primarily talk of perl modules, but this specification should be open enough to apply to other languages as well (ex. python, ruby). Examples of modules are C, C, or C. =back =head1 HEADER The first line of a F file should be a valid YAML document header like C<"--- #YAML:1.0">. =head1 FIELDS The rest of the F file is one big YAML mapping whose keys are described here. =head2 meta-spec Example: meta-spec: version: 1.3 url: http://module-build.sourceforge.net/META-spec-v1.3.html (Spec 1.1) [required] {URL} This field indicates the location of the version of the META.yml specification used. =head2 name Example: name: Module-Build (Spec 1.0) [required] {string} The name of the distribution which is often created by taking the "main module" in the distribution and changing "::" to "-". Sometimes it's completely different, however, as in the case of the libwww-perl distribution (see L). =head2 version Example: version: 0.20 (Spec 1.0) [required] {version} The version of the distribution to which the F file refers. =head2 abstract Example: abstract: Build and install Perl modules. (Spec 1.1) [required] {string} A short description of the purpose of the distribution. =head2 author Example: author: - Ken Williams (Spec 1.1) [required] {list of strings} A YAML sequence indicating the author(s) of the distribution. The preferred form is author-name . =head2 license Example: license: perl (Spec 1.0) [required] {string} The license under which this distribution may be used and redistributed. Must be one of the following licenses: =over 4 =item apache The distribution is licensed under the Apache Software License version 1.1 (L). =item artistic The distribution is licensed under the Artistic License version 1, as specified by the Artistic file in the standard perl distribution (L). =item bsd The distribution is licensed under the BSD 3-Clause License (L). =item gpl The distribution is distributed under the terms of the GNU General Public License version 2 (L). =item lgpl The distribution is distributed under the terms of the GNU Lesser General Public License version 2 (L). =item mit The distribution is licensed under the MIT License (L). =item mozilla The distribution is licensed under the Mozilla Public License. (L or L) =item open_source The distribution is licensed under some other Open Source Initiative-approved license listed at L. =item perl The distribution may be copied and redistributed under the same terms as perl itself (this is by far the most common licensing option for modules on CPAN). This is a dual license, in which the user may choose between either the GPL version 1 or the Artistic version 1 license. =item restrictive The distribution may not be redistributed without special permission from the author and/or copyright holder. =item unrestricted The distribution is licensed under a license that is not approved by L but that allows distribution without restrictions. =back =head2 distribution_type Example: distribution_type: module (Spec 1.0) [optional] {string} What kind of stuff is contained in this distribution. Most things on CPAN are Cs (which can also mean a collection of modules), but some things are C