JFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3RbrJFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3RbrPK!o1Dperl5/Scope/Guard.pmnu6$package Scope::Guard; use strict; use warnings; use Carp qw(confess); use Exporter (); our @ISA = qw(Exporter); our @EXPORT_OK = qw(guard scope_guard); our $VERSION = '0.21'; sub new { confess "Can't create a Scope::Guard in void context" unless (defined wantarray); my $class = shift; my $handler = shift() || die 'Scope::Guard::new: no handler supplied'; my $ref = ref $handler || ''; die "Scope::Guard::new: invalid handler - expected CODE ref, got: '$ref'" unless ref($handler) eq 'CODE'; bless [ 0, $handler ], ref $class || $class; } sub dismiss { my $self = shift; my $dismiss = @_ ? shift : 1; $self->[0] = $dismiss; } sub guard(&) { __PACKAGE__->new(shift) } sub scope_guard($) { __PACKAGE__->new(shift) } sub DESTROY { my $self = shift; my ($dismiss, $handler) = @$self; $handler->() unless ($dismiss); } 1; __END__ =pod =head1 NAME Scope::Guard - lexically-scoped resource management =head1 SYNOPSIS my $guard = guard { ... }; # or my $guard = scope_guard \&handler; # or my $guard = Scope::Guard->new(sub { ... }); $guard->dismiss(); # disable the handler =head1 DESCRIPTION This module provides a convenient way to perform cleanup or other forms of resource management at the end of a scope. It is particularly useful when dealing with exceptions: the C constructor takes a reference to a subroutine that is guaranteed to be called even if the thread of execution is aborted prematurely. This effectively allows lexically-scoped "promises" to be made that are automatically honoured by perl's garbage collector. For more information, see: L =head1 METHODS =head2 new my $guard = Scope::Guard->new(sub { ... }); # or my $guard = Scope::Guard->new(\&handler); The C method creates a new C object which calls the supplied handler when its C method is called, typically at the end of the scope. =head2 dismiss $guard->dismiss(); # or $guard->dismiss(1); C detaches the handler from the C object. This revokes the "promise" to call the handler when the object is destroyed. The handler can be re-enabled by calling: $guard->dismiss(0); =head1 EXPORTS =head2 guard C takes a block and returns a new C object. It can be used as a shorthand for: Scope::Guard->new(...) e.g. my $guard = guard { ... }; Note: calling C anonymously, i.e. in void context, will raise an exception. This is because anonymous guards are destroyed B (rather than at the end of the scope), which is unlikely to be the desired behaviour. =head2 scope_guard C is the same as C, but it takes a code ref rather than a block. e.g. my $guard = scope_guard \&handler; or: my $guard = scope_guard sub { ... }; or: my $guard = scope_guard $handler; As with C, calling C in void context will raise an exception. =head1 VERSION 0.21 =head1 SEE ALSO =over =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =back =head1 AUTHOR chocolateboy =head1 COPYRIGHT Copyright (c) 2005-2015, chocolateboy. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself. =cut PK!@NCCperl5/HTTP/Response.pmnu6$package HTTP::Response; use strict; use warnings; our $VERSION = '6.45'; use parent 'HTTP::Message'; use HTTP::Status (); sub new { my($class, $rc, $msg, $header, $content) = @_; my $self = $class->SUPER::new($header, $content); $self->code($rc); $self->message($msg); $self; } sub parse { my($class, $str) = @_; Carp::carp('Undefined argument to parse()') if $^W && ! defined $str; my $status_line; if (defined $str && $str =~ s/^(.*)\n//) { $status_line = $1; } else { $status_line = $str; $str = ""; } $status_line =~ s/\r\z// if defined $status_line; my $self = $class->SUPER::parse($str); if (defined $status_line) { my($protocol, $code, $message); if ($status_line =~ /^\d{3} /) { # Looks like a response created by HTTP::Response->new ($code, $message) = split(' ', $status_line, 2); } else { ($protocol, $code, $message) = split(' ', $status_line, 3); } $self->protocol($protocol) if $protocol; $self->code($code) if defined($code); $self->message($message) if defined($message); } $self; } sub clone { my $self = shift; my $clone = bless $self->SUPER::clone, ref($self); $clone->code($self->code); $clone->message($self->message); $clone->request($self->request->clone) if $self->request; # we don't clone previous $clone; } sub code { shift->_elem('_rc', @_); } sub message { shift->_elem('_msg', @_); } sub previous { shift->_elem('_previous',@_); } sub request { shift->_elem('_request', @_); } sub status_line { my $self = shift; my $code = $self->{'_rc'} || "000"; my $mess = $self->{'_msg'} || HTTP::Status::status_message($code) || "Unknown code"; return "$code $mess"; } sub base { my $self = shift; my $base = ( $self->header('Content-Base'), # used to be HTTP/1.1 $self->header('Base'), # HTTP/1.0 )[0]; if ($base && $base =~ /^$URI::scheme_re:/o) { # already absolute return $HTTP::URI_CLASS->new($base); } my $req = $self->request; if ($req) { # if $base is undef here, the return value is effectively # just a copy of $self->request->uri. return $HTTP::URI_CLASS->new_abs($base, $req->uri); } # can't find an absolute base return undef; } sub redirects { my $self = shift; my @r; my $r = $self; while (my $p = $r->previous) { push(@r, $p); $r = $p; } return @r unless wantarray; return reverse @r; } sub filename { my $self = shift; my $file; my $cd = $self->header('Content-Disposition'); if ($cd) { require HTTP::Headers::Util; if (my @cd = HTTP::Headers::Util::split_header_words($cd)) { my ($disposition, undef, %cd_param) = @{$cd[-1]}; $file = $cd_param{filename}; # RFC 2047 encoded? if ($file && $file =~ /^=\?(.+?)\?(.+?)\?(.+)\?=$/) { my $charset = $1; my $encoding = uc($2); my $encfile = $3; if ($encoding eq 'Q' || $encoding eq 'B') { local($SIG{__DIE__}); eval { if ($encoding eq 'Q') { $encfile =~ s/_/ /g; require MIME::QuotedPrint; $encfile = MIME::QuotedPrint::decode($encfile); } else { # $encoding eq 'B' require MIME::Base64; $encfile = MIME::Base64::decode($encfile); } require Encode; require Encode::Locale; Encode::from_to($encfile, $charset, "locale_fs"); }; $file = $encfile unless $@; } } } } unless (defined($file) && length($file)) { my $uri; if (my $cl = $self->header('Content-Location')) { $uri = URI->new($cl); } elsif (my $request = $self->request) { $uri = $request->uri; } if ($uri) { $file = ($uri->path_segments)[-1]; } } if ($file) { $file =~ s,.*[\\/],,; # basename } if ($file && !length($file)) { $file = undef; } $file; } sub as_string { my $self = shift; my($eol) = @_; $eol = "\n" unless defined $eol; my $status_line = $self->status_line; my $proto = $self->protocol; $status_line = "$proto $status_line" if $proto; return join($eol, $status_line, $self->SUPER::as_string(@_)); } sub dump { my $self = shift; my $status_line = $self->status_line; my $proto = $self->protocol; $status_line = "$proto $status_line" if $proto; return $self->SUPER::dump( preheader => $status_line, @_, ); } sub is_info { HTTP::Status::is_info (shift->{'_rc'}); } sub is_success { HTTP::Status::is_success (shift->{'_rc'}); } sub is_redirect { HTTP::Status::is_redirect (shift->{'_rc'}); } sub is_error { HTTP::Status::is_error (shift->{'_rc'}); } sub is_client_error { HTTP::Status::is_client_error (shift->{'_rc'}); } sub is_server_error { HTTP::Status::is_server_error (shift->{'_rc'}); } sub error_as_HTML { my $self = shift; my $title = 'An Error Occurred'; my $body = $self->status_line; $body =~ s/&/&/g; $body =~ s/ $title

$title

$body

EOM } sub current_age { my $self = shift; my $time = shift; # Implementation of RFC 2616 section 13.2.3 # (age calculations) my $response_time = $self->client_date; my $date = $self->date; my $age = 0; if ($response_time && $date) { $age = $response_time - $date; # apparent_age $age = 0 if $age < 0; } my $age_v = $self->header('Age'); if ($age_v && $age_v > $age) { $age = $age_v; # corrected_received_age } if ($response_time) { my $request = $self->request; if ($request) { my $request_time = $request->date; if ($request_time && $request_time < $response_time) { # Add response_delay to age to get 'corrected_initial_age' $age += $response_time - $request_time; } } $age += ($time || time) - $response_time; } return $age; } sub freshness_lifetime { my($self, %opt) = @_; # First look for the Cache-Control: max-age=n header for my $cc ($self->header('Cache-Control')) { for my $cc_dir (split(/\s*,\s*/, $cc)) { return $1 if $cc_dir =~ /^max-age\s*=\s*(\d+)/i; } } # Next possibility is to look at the "Expires" header my $date = $self->date || $self->client_date || $opt{time} || time; if (my $expires = $self->expires) { return $expires - $date; } # Must apply heuristic expiration return undef if exists $opt{heuristic_expiry} && !$opt{heuristic_expiry}; # Default heuristic expiration parameters $opt{h_min} ||= 60; $opt{h_max} ||= 24 * 3600; $opt{h_lastmod_fraction} ||= 0.10; # 10% since last-mod suggested by RFC2616 $opt{h_default} ||= 3600; # Should give a warning if more than 24 hours according to # RFC 2616 section 13.2.4. Here we just make this the default # maximum value. if (my $last_modified = $self->last_modified) { my $h_exp = ($date - $last_modified) * $opt{h_lastmod_fraction}; return $opt{h_min} if $h_exp < $opt{h_min}; return $opt{h_max} if $h_exp > $opt{h_max}; return $h_exp; } # default when all else fails return $opt{h_min} if $opt{h_min} > $opt{h_default}; return $opt{h_default}; } sub is_fresh { my($self, %opt) = @_; $opt{time} ||= time; my $f = $self->freshness_lifetime(%opt); return undef unless defined($f); return $f > $self->current_age($opt{time}); } sub fresh_until { my($self, %opt) = @_; $opt{time} ||= time; my $f = $self->freshness_lifetime(%opt); return undef unless defined($f); return $f - $self->current_age($opt{time}) + $opt{time}; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Response - HTTP style response message =head1 VERSION version 6.45 =head1 SYNOPSIS Response objects are returned by the request() method of the C: # ... $response = $ua->request($request); if ($response->is_success) { print $response->decoded_content; } else { print STDERR $response->status_line, "\n"; } =head1 DESCRIPTION The C class encapsulates HTTP style responses. A response consists of a response line, some headers, and a content body. Note that the LWP library uses HTTP style responses even for non-HTTP protocol schemes. Instances of this class are usually created and returned by the request() method of an C object. C is a subclass of C and therefore inherits its methods. The following additional methods are available: =over 4 =item $r = HTTP::Response->new( $code ) =item $r = HTTP::Response->new( $code, $msg ) =item $r = HTTP::Response->new( $code, $msg, $header ) =item $r = HTTP::Response->new( $code, $msg, $header, $content ) Constructs a new C object describing a response with response code $code and optional message $msg. The optional $header argument should be a reference to an C object or a plain array reference of key/value pairs. The optional $content argument should be a string of bytes. The meanings of these arguments are described below. =item $r = HTTP::Response->parse( $str ) This constructs a new response object by parsing the given string. =item $r->code =item $r->code( $code ) This is used to get/set the code attribute. The code is a 3 digit number that encode the overall outcome of an HTTP response. The C module provide constants that provide mnemonic names for the code attribute. =item $r->message =item $r->message( $message ) This is used to get/set the message attribute. The message is a short human readable single line string that explains the response code. =item $r->header( $field ) =item $r->header( $field => $value ) This is used to get/set header values and it is inherited from C via C. See L for details and other similar methods that can be used to access the headers. =item $r->content =item $r->content( $bytes ) This is used to get/set the raw content and it is inherited from the C base class. See L for details and other methods that can be used to access the content. =item $r->decoded_content( %options ) This will return the content after any C and charsets have been decoded. See L for details. =item $r->request =item $r->request( $request ) This is used to get/set the request attribute. The request attribute is a reference to the request that caused this response. It does not have to be the same request passed to the $ua->request() method, because there might have been redirects and authorization retries in between. =item $r->previous =item $r->previous( $response ) This is used to get/set the previous attribute. The previous attribute is used to link together chains of responses. You get chains of responses if the first response is redirect or unauthorized. The value is C if this is the first response in a chain. Note that the method $r->redirects is provided as a more convenient way to access the response chain. =item $r->status_line Returns the string "Ecode> Emessage>". If the message attribute is not set then the official name of Ecode> (see L) is substituted. =item $r->base Returns the base URI for this response. The return value will be a reference to a URI object. The base URI is obtained from one the following sources (in priority order): =over 4 =item 1. Embedded in the document content, for instance in HTML documents. =item 2. A "Content-Base:" header in the response. For backwards compatibility with older HTTP implementations we will also look for the "Base:" header. =item 3. The URI used to request this response. This might not be the original URI that was passed to $ua->request() method, because we might have received some redirect responses first. =back If none of these sources provide an absolute URI, undef is returned. B: previous versions of HTTP::Response would also consider a "Content-Location:" header, as L said it should be. But this was never widely implemented by browsers, and now L says it should no longer be considered. When the LWP protocol modules produce the HTTP::Response object, then any base URI embedded in the document (step 1) will already have initialized the "Content-Base:" header. (See L). This means that this method only performs the last 2 steps (the content is not always available either). =item $r->filename Returns a filename for this response. Note that doing sanity checks on the returned filename (eg. removing characters that cannot be used on the target filesystem where the filename would be used, and laundering it for security purposes) are the caller's responsibility; the only related thing done by this method is that it makes a simple attempt to return a plain filename with no preceding path segments. The filename is obtained from one the following sources (in priority order): =over 4 =item 1. A "Content-Disposition:" header in the response. Proper decoding of RFC 2047 encoded filenames requires the C (for "Q" encoding), C (for "B" encoding), and C modules. =item 2. A "Content-Location:" header in the response. =item 3. The URI used to request this response. This might not be the original URI that was passed to $ua->request() method, because we might have received some redirect responses first. =back If a filename cannot be derived from any of these sources, undef is returned. =item $r->as_string =item $r->as_string( $eol ) Returns a textual representation of the response. =item $r->is_info =item $r->is_success =item $r->is_redirect =item $r->is_error =item $r->is_client_error =item $r->is_server_error These methods indicate if the response was informational, successful, a redirection, or an error. See L for the meaning of these. =item $r->error_as_HTML Returns a string containing a complete HTML document indicating what error occurred. This method should only be called when $r->is_error is TRUE. =item $r->redirects Returns the list of redirect responses that lead up to this response by following the $r->previous chain. The list order is oldest first. In scalar context return the number of redirect responses leading up to this one. =item $r->current_age Calculates the "current age" of the response as specified by RFC 2616 section 13.2.3. The age of a response is the time since it was sent by the origin server. The returned value is a number representing the age in seconds. =item $r->freshness_lifetime( %opt ) Calculates the "freshness lifetime" of the response as specified by RFC 2616 section 13.2.4. The "freshness lifetime" is the length of time between the generation of a response and its expiration time. The returned value is the number of seconds until expiry. If the response does not contain an "Expires" or a "Cache-Control" header, then this function will apply some simple heuristic based on the "Last-Modified" header to determine a suitable lifetime. The following options might be passed to control the heuristics: =over =item heuristic_expiry => $bool If passed as a FALSE value, don't apply heuristics and just return C when "Expires" or "Cache-Control" is lacking. =item h_lastmod_fraction => $num This number represent the fraction of the difference since the "Last-Modified" timestamp to make the expiry time. The default is C<0.10>, the suggested typical setting of 10% in RFC 2616. =item h_min => $sec This is the lower limit of the heuristic expiry age to use. The default is C<60> (1 minute). =item h_max => $sec This is the upper limit of the heuristic expiry age to use. The default is C<86400> (24 hours). =item h_default => $sec This is the expiry age to use when nothing else applies. The default is C<3600> (1 hour) or "h_min" if greater. =back =item $r->is_fresh( %opt ) Returns TRUE if the response is fresh, based on the values of freshness_lifetime() and current_age(). If the response is no longer fresh, then it has to be re-fetched or re-validated by the origin server. Options might be passed to control expiry heuristics, see the description of freshness_lifetime(). =item $r->fresh_until( %opt ) Returns the time (seconds since epoch) when this entity is no longer fresh. Options might be passed to control expiry heuristics, see the description of freshness_lifetime(). =back =head1 SEE ALSO L, L, L, L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: HTTP style response message PK!Cu%.%.perl5/HTTP/Config.pmnu6$package HTTP::Config; use strict; use warnings; our $VERSION = '6.45'; use URI; sub new { my $class = shift; return bless [], $class; } sub entries { my $self = shift; @$self; } sub empty { my $self = shift; not @$self; } sub add { if (@_ == 2) { my $self = shift; push(@$self, shift); return; } my($self, %spec) = @_; push(@$self, \%spec); return; } sub find2 { my($self, %spec) = @_; my @found; my @rest; ITEM: for my $item (@$self) { for my $k (keys %spec) { no warnings 'uninitialized'; if (!exists $item->{$k} || $spec{$k} ne $item->{$k}) { push(@rest, $item); next ITEM; } } push(@found, $item); } return \@found unless wantarray; return \@found, \@rest; } sub find { my $self = shift; my $f = $self->find2(@_); return @$f if wantarray; return $f->[0]; } sub remove { my($self, %spec) = @_; my($removed, $rest) = $self->find2(%spec); @$self = @$rest if @$removed; return @$removed; } my %MATCH = ( m_scheme => sub { my($v, $uri) = @_; return $uri->_scheme eq $v; # URI known to be canonical }, m_secure => sub { my($v, $uri) = @_; my $secure = $uri->can("secure") ? $uri->secure : $uri->_scheme eq "https"; return $secure == !!$v; }, m_host_port => sub { my($v, $uri) = @_; return unless $uri->can("host_port"); return $uri->host_port eq $v, 7; }, m_host => sub { my($v, $uri) = @_; return unless $uri->can("host"); return $uri->host eq $v, 6; }, m_port => sub { my($v, $uri) = @_; return unless $uri->can("port"); return $uri->port eq $v; }, m_domain => sub { my($v, $uri) = @_; return unless $uri->can("host"); my $h = $uri->host; $h = "$h.local" unless $h =~ /\./; $v = ".$v" unless $v =~ /^\./; return length($v), 5 if substr($h, -length($v)) eq $v; return 0; }, m_path => sub { my($v, $uri) = @_; return unless $uri->can("path"); return $uri->path eq $v, 4; }, m_path_prefix => sub { my($v, $uri) = @_; return unless $uri->can("path"); my $path = $uri->path; my $len = length($v); return $len, 3 if $path eq $v; return 0 if length($path) <= $len; $v .= "/" unless $v =~ m,/\z,,; return $len, 3 if substr($path, 0, length($v)) eq $v; return 0; }, m_path_match => sub { my($v, $uri) = @_; return unless $uri->can("path"); return $uri->path =~ $v; }, m_uri__ => sub { my($v, $k, $uri) = @_; return unless $uri->can($k); return 1 unless defined $v; return $uri->$k eq $v; }, m_method => sub { my($v, $uri, $request) = @_; return $request && $request->method eq $v; }, m_proxy => sub { my($v, $uri, $request) = @_; return $request && ($request->{proxy} || "") eq $v; }, m_code => sub { my($v, $uri, $request, $response) = @_; $v =~ s/xx\z//; return unless $response; return length($v), 2 if substr($response->code, 0, length($v)) eq $v; }, m_media_type => sub { # for request too?? my($v, $uri, $request, $response) = @_; return unless $response; return 1, 1 if $v eq "*/*"; my $ct = $response->content_type; return 2, 1 if $v =~ s,/\*\z,, && $ct =~ m,^\Q$v\E/,; return 3, 1 if $v eq "html" && $response->content_is_html; return 4, 1 if $v eq "xhtml" && $response->content_is_xhtml; return 10, 1 if $v eq $ct; return 0; }, m_header__ => sub { my($v, $k, $uri, $request, $response) = @_; return unless $request; my $req_header = $request->header($k); return 1 if defined($req_header) && $req_header eq $v; if ($response) { my $res_header = $response->header($k); return 1 if defined($res_header) && $res_header eq $v; } return 0; }, m_response_attr__ => sub { my($v, $k, $uri, $request, $response) = @_; return unless $response; return 1 if !defined($v) && exists $response->{$k}; return 0 unless exists $response->{$k}; return 1 if $response->{$k} eq $v; return 0; }, ); sub matching { my $self = shift; if (@_ == 1) { if ($_[0]->can("request")) { unshift(@_, $_[0]->request); unshift(@_, undef) unless defined $_[0]; } unshift(@_, $_[0]->uri_canonical) if $_[0] && $_[0]->can("uri_canonical"); } my($uri, $request, $response) = @_; $uri = URI->new($uri) unless ref($uri); my @m; ITEM: for my $item (@$self) { my $order; for my $ikey (keys %$item) { my $mkey = $ikey; my $k; $k = $1 if $mkey =~ s/__(.*)/__/; if (my $m = $MATCH{$mkey}) { #print "$ikey $mkey\n"; my($c, $o); my @arg = ( defined($k) ? $k : (), $uri, $request, $response ); my $v = $item->{$ikey}; $v = [$v] unless ref($v) eq "ARRAY"; for (@$v) { ($c, $o) = $m->($_, @arg); #print " - $_ ==> $c $o\n"; last if $c; } next ITEM unless $c; $order->[$o || 0] += $c; } } $order->[7] ||= 0; $item->{_order} = join(".", reverse map sprintf("%03d", $_ || 0), @$order); push(@m, $item); } @m = sort { $b->{_order} cmp $a->{_order} } @m; delete $_->{_order} for @m; return @m if wantarray; return $m[0]; } sub add_item { my $self = shift; my $item = shift; return $self->add(item => $item, @_); } sub remove_items { my $self = shift; return map $_->{item}, $self->remove(@_); } sub matching_items { my $self = shift; return map $_->{item}, $self->matching(@_); } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Config - Configuration for request and response objects =head1 VERSION version 6.45 =head1 SYNOPSIS use HTTP::Config; my $c = HTTP::Config->new; $c->add(m_domain => ".example.com", m_scheme => "http", verbose => 1); use HTTP::Request; my $request = HTTP::Request->new(GET => "http://www.example.com"); if (my @m = $c->matching($request)) { print "Yadayada\n" if $m[0]->{verbose}; } =head1 DESCRIPTION An C object is a list of entries that can be matched against request or request/response pairs. Its purpose is to hold configuration data that can be looked up given a request or response object. Each configuration entry is a hash. Some keys specify matching to occur against attributes of request/response objects. Other keys can be used to hold user data. The following methods are provided: =over 4 =item $conf = HTTP::Config->new Constructs a new empty C object and returns it. =item $conf->entries Returns the list of entries in the configuration object. In scalar context returns the number of entries. =item $conf->empty Return true if there are no entries in the configuration object. This is just a shorthand for C<< not $conf->entries >>. =item $conf->add( %matchspec, %other ) =item $conf->add( \%entry ) Adds a new entry to the configuration. You can either pass separate key/value pairs or a hash reference. =item $conf->remove( %spec ) Removes (and returns) the entries that have matches for all the key/value pairs in %spec. If %spec is empty this will match all entries; so it will empty the configuration object. =item $conf->matching( $uri, $request, $response ) =item $conf->matching( $uri ) =item $conf->matching( $request ) =item $conf->matching( $response ) Returns the entries that match the given $uri, $request and $response triplet. If called with a single $request object then the $uri is obtained by calling its 'uri_canonical' method. If called with a single $response object, then the request object is obtained by calling its 'request' method; and then the $uri is obtained as if a single $request was provided. The entries are returned with the most specific matches first. In scalar context returns the most specific match or C in none match. =item $conf->add_item( $item, %matchspec ) =item $conf->remove_items( %spec ) =item $conf->matching_items( $uri, $request, $response ) Wrappers that hides the entries themselves. =back =head2 Matching The following keys on a configuration entry specify matching. For all of these you can provide an array of values instead of a single value. The entry matches if at least one of the values in the array matches. Entries that require match against a response object attribute will never match unless a response object was provided. =over =item m_scheme => $scheme Matches if the URI uses the specified scheme; e.g. "http". =item m_secure => $bool If $bool is TRUE; matches if the URI uses a secure scheme. If $bool is FALSE; matches if the URI does not use a secure scheme. An example of a secure scheme is "https". =item m_host_port => "$hostname:$port" Matches if the URI's host_port method return the specified value. =item m_host => $hostname Matches if the URI's host method returns the specified value. =item m_port => $port Matches if the URI's port method returns the specified value. =item m_domain => ".$domain" Matches if the URI's host method return a value that within the given domain. The hostname "www.example.com" will for instance match the domain ".com". =item m_path => $path Matches if the URI's path method returns the specified value. =item m_path_prefix => $path Matches if the URI's path is the specified path or has the specified path as prefix. =item m_path_match => $Regexp Matches if the regular expression matches the URI's path. Eg. qr/\.html$/. =item m_method => $method Matches if the request method matches the specified value. Eg. "GET" or "POST". =item m_code => $digit =item m_code => $status_code Matches if the response status code matches. If a single digit is specified; matches for all response status codes beginning with that digit. =item m_proxy => $url Matches if the request is to be sent to the given Proxy server. =item m_media_type => "*/*" =item m_media_type => "text/*" =item m_media_type => "html" =item m_media_type => "xhtml" =item m_media_type => "text/html" Matches if the response media type matches. With a value of "html" matches if $response->content_is_html returns TRUE. With a value of "xhtml" matches if $response->content_is_xhtml returns TRUE. =item m_uri__I<$method> => undef Matches if the URI object provides the method. =item m_uri__I<$method> => $string Matches if the URI's $method method returns the given value. =item m_header__I<$field> => $string Matches if either the request or the response have a header $field with the given value. =item m_response_attr__I<$key> => undef =item m_response_attr__I<$key> => $string Matches if the response object has that key, or the entry has the given value. =back =head1 SEE ALSO L, L, L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: Configuration for request and response objects PK!K>aaperl5/HTTP/Headers.pmnu6$package HTTP::Headers; use strict; use warnings; our $VERSION = '6.45'; use Clone qw(clone); use Carp (); # The $TRANSLATE_UNDERSCORE variable controls whether '_' can be used # as a replacement for '-' in header field names. our $TRANSLATE_UNDERSCORE = 1 unless defined $TRANSLATE_UNDERSCORE; # "Good Practice" order of HTTP message headers: # - General-Headers # - Request-Headers # - Response-Headers # - Entity-Headers my @general_headers = qw( Cache-Control Connection Date Pragma Trailer Transfer-Encoding Upgrade Via Warning ); my @request_headers = qw( Accept Accept-Charset Accept-Encoding Accept-Language Authorization Expect From Host If-Match If-Modified-Since If-None-Match If-Range If-Unmodified-Since Max-Forwards Proxy-Authorization Range Referer TE User-Agent ); my @response_headers = qw( Accept-Ranges Age ETag Location Proxy-Authenticate Retry-After Server Vary WWW-Authenticate ); my @entity_headers = qw( Allow Content-Encoding Content-Language Content-Length Content-Location Content-MD5 Content-Range Content-Type Expires Last-Modified ); my %entity_header = map { lc($_) => 1 } @entity_headers; my @header_order = ( @general_headers, @request_headers, @response_headers, @entity_headers, ); # Make alternative representations of @header_order. This is used # for sorting and case matching. my %header_order; my %standard_case; { my $i = 0; for (@header_order) { my $lc = lc $_; $header_order{$lc} = ++$i; $standard_case{$lc} = $_; } } sub new { my($class) = shift; my $self = bless {}, $class; $self->header(@_) if @_; # set up initial headers $self; } sub header { my $self = shift; Carp::croak('Usage: $h->header($field, ...)') unless @_; my(@old); my %seen; while (@_) { my $field = shift; my $op = @_ ? ($seen{lc($field)}++ ? 'PUSH' : 'SET') : 'GET'; @old = $self->_header($field, shift, $op); } return @old if wantarray; return $old[0] if @old <= 1; join(", ", @old); } sub clear { my $self = shift; %$self = (); } sub push_header { my $self = shift; return $self->_header(@_, 'PUSH_H') if @_ == 2; while (@_) { $self->_header(splice(@_, 0, 2), 'PUSH_H'); } } sub init_header { Carp::croak('Usage: $h->init_header($field, $val)') if @_ != 3; shift->_header(@_, 'INIT'); } sub remove_header { my($self, @fields) = @_; my $field; my @values; foreach $field (@fields) { $field =~ tr/_/-/ if $field !~ /^:/ && $TRANSLATE_UNDERSCORE; my $v = delete $self->{lc $field}; push(@values, ref($v) eq 'ARRAY' ? @$v : $v) if defined $v; } return @values; } sub remove_content_headers { my $self = shift; unless (defined(wantarray)) { # fast branch that does not create return object delete @$self{grep $entity_header{$_} || /^content-/, keys %$self}; return; } my $c = ref($self)->new; for my $f (grep $entity_header{$_} || /^content-/, keys %$self) { $c->{$f} = delete $self->{$f}; } if (exists $self->{'::std_case'}) { $c->{'::std_case'} = $self->{'::std_case'}; } $c; } sub _header { my($self, $field, $val, $op) = @_; Carp::croak("Illegal field name '$field'") if rindex($field, ':') > 1 || !length($field); unless ($field =~ /^:/) { $field =~ tr/_/-/ if $TRANSLATE_UNDERSCORE; my $old = $field; $field = lc $field; unless($standard_case{$field} || $self->{'::std_case'}{$field}) { # generate a %std_case entry for this field $old =~ s/\b(\w)/\u$1/g; $self->{'::std_case'}{$field} = $old; } } $op ||= defined($val) ? 'SET' : 'GET'; if ($op eq 'PUSH_H') { # Like PUSH but where we don't care about the return value if (exists $self->{$field}) { my $h = $self->{$field}; if (ref($h) eq 'ARRAY') { push(@$h, ref($val) eq "ARRAY" ? @$val : $val); } else { $self->{$field} = [$h, ref($val) eq "ARRAY" ? @$val : $val] } return; } $self->{$field} = $val; return; } my $h = $self->{$field}; my @old = ref($h) eq 'ARRAY' ? @$h : (defined($h) ? ($h) : ()); unless ($op eq 'GET' || ($op eq 'INIT' && @old)) { if (defined($val)) { my @new = ($op eq 'PUSH') ? @old : (); if (ref($val) ne 'ARRAY') { push(@new, $val); } else { push(@new, @$val); } $self->{$field} = @new > 1 ? \@new : $new[0]; } elsif ($op ne 'PUSH') { delete $self->{$field}; } } @old; } sub _sorted_field_names { my $self = shift; return [ sort { ($header_order{$a} || 999) <=> ($header_order{$b} || 999) || $a cmp $b } grep !/^::/, keys %$self ]; } sub header_field_names { my $self = shift; return map $standard_case{$_} || $self->{'::std_case'}{$_} || $_, @{ $self->_sorted_field_names }, if wantarray; return grep !/^::/, keys %$self; } sub scan { my($self, $sub) = @_; my $key; for $key (@{ $self->_sorted_field_names }) { my $vals = $self->{$key}; if (ref($vals) eq 'ARRAY') { my $val; for $val (@$vals) { $sub->($standard_case{$key} || $self->{'::std_case'}{$key} || $key, $val); } } else { $sub->($standard_case{$key} || $self->{'::std_case'}{$key} || $key, $vals); } } } sub flatten { my($self)=@_; ( map { my $k = $_; map { ( $k => $_ ) } $self->header($_); } $self->header_field_names ); } sub as_string { my($self, $endl) = @_; $endl = "\n" unless defined $endl; my @result = (); for my $key (@{ $self->_sorted_field_names }) { next if index($key, '_') == 0; my $vals = $self->{$key}; if ( ref($vals) eq 'ARRAY' ) { for my $val (@$vals) { $val = '' if not defined $val; my $field = $standard_case{$key} || $self->{'::std_case'}{$key} || $key; $field =~ s/^://; if ( index($val, "\n") >= 0 ) { $val = _process_newline($val, $endl); } push @result, $field . ': ' . $val; } } else { $vals = '' if not defined $vals; my $field = $standard_case{$key} || $self->{'::std_case'}{$key} || $key; $field =~ s/^://; if ( index($vals, "\n") >= 0 ) { $vals = _process_newline($vals, $endl); } push @result, $field . ': ' . $vals; } } join($endl, @result, ''); } sub _process_newline { local $_ = shift; my $endl = shift; # must handle header values with embedded newlines with care s/\s+$//; # trailing newlines and space must go s/\n(\x0d?\n)+/\n/g; # no empty lines s/\n([^\040\t])/\n $1/g; # initial space for continuation s/\n/$endl/g; # substitute with requested line ending $_; } sub _date_header { require HTTP::Date; my($self, $header, $time) = @_; my($old) = $self->_header($header); if (defined $time) { $self->_header($header, HTTP::Date::time2str($time)); } $old =~ s/;.*// if defined($old); HTTP::Date::str2time($old); } sub date { shift->_date_header('Date', @_); } sub expires { shift->_date_header('Expires', @_); } sub if_modified_since { shift->_date_header('If-Modified-Since', @_); } sub if_unmodified_since { shift->_date_header('If-Unmodified-Since', @_); } sub last_modified { shift->_date_header('Last-Modified', @_); } # This is used as a private LWP extension. The Client-Date header is # added as a timestamp to a response when it has been received. sub client_date { shift->_date_header('Client-Date', @_); } # The retry_after field is dual format (can also be a expressed as # number of seconds from now), so we don't provide an easy way to # access it until we have know how both these interfaces can be # addressed. One possibility is to return a negative value for # relative seconds and a positive value for epoch based time values. #sub retry_after { shift->_date_header('Retry-After', @_); } sub content_type { my $self = shift; my $ct = $self->{'content-type'}; $self->{'content-type'} = shift if @_; $ct = $ct->[0] if ref($ct) eq 'ARRAY'; return '' unless defined($ct) && length($ct); my @ct = split(/;\s*/, $ct, 2); for ($ct[0]) { s/\s+//g; $_ = lc($_); } wantarray ? @ct : $ct[0]; } sub content_type_charset { my $self = shift; require HTTP::Headers::Util; my $h = $self->{'content-type'}; $h = $h->[0] if ref($h); $h = "" unless defined $h; my @v = HTTP::Headers::Util::split_header_words($h); if (@v) { my($ct, undef, %ct_param) = @{$v[0]}; my $charset = $ct_param{charset}; if ($ct) { $ct = lc($ct); $ct =~ s/\s+//; } if ($charset) { $charset = uc($charset); $charset =~ s/^\s+//; $charset =~ s/\s+\z//; undef($charset) if $charset eq ""; } return $ct, $charset if wantarray; return $charset; } return undef, undef if wantarray; return undef; } sub content_is_text { my $self = shift; return $self->content_type =~ m,^text/,; } sub content_is_html { my $self = shift; return $self->content_type eq 'text/html' || $self->content_is_xhtml; } sub content_is_xhtml { my $ct = shift->content_type; return $ct eq "application/xhtml+xml" || $ct eq "application/vnd.wap.xhtml+xml"; } sub content_is_xml { my $ct = shift->content_type; return 1 if $ct eq "text/xml"; return 1 if $ct eq "application/xml"; return 1 if $ct =~ /\+xml$/; return 0; } sub referer { my $self = shift; if (@_ && $_[0] =~ /#/) { # Strip fragment per RFC 2616, section 14.36. my $uri = shift; if (ref($uri)) { $uri = $uri->clone; $uri->fragment(undef); } else { $uri =~ s/\#.*//; } unshift @_, $uri; } ($self->_header('Referer', @_))[0]; } *referrer = \&referer; # on tchrist's request sub title { (shift->_header('Title', @_))[0] } sub content_encoding { (shift->_header('Content-Encoding', @_))[0] } sub content_language { (shift->_header('Content-Language', @_))[0] } sub content_length { (shift->_header('Content-Length', @_))[0] } sub user_agent { (shift->_header('User-Agent', @_))[0] } sub server { (shift->_header('Server', @_))[0] } sub from { (shift->_header('From', @_))[0] } sub warning { (shift->_header('Warning', @_))[0] } sub www_authenticate { (shift->_header('WWW-Authenticate', @_))[0] } sub authorization { (shift->_header('Authorization', @_))[0] } sub proxy_authenticate { (shift->_header('Proxy-Authenticate', @_))[0] } sub proxy_authorization { (shift->_header('Proxy-Authorization', @_))[0] } sub authorization_basic { shift->_basic_auth("Authorization", @_) } sub proxy_authorization_basic { shift->_basic_auth("Proxy-Authorization", @_) } sub _basic_auth { require MIME::Base64; my($self, $h, $user, $passwd) = @_; my($old) = $self->_header($h); if (defined $user) { Carp::croak("Basic authorization user name can't contain ':'") if $user =~ /:/; $passwd = '' unless defined $passwd; $self->_header($h => 'Basic ' . MIME::Base64::encode("$user:$passwd", '')); } if (defined $old && $old =~ s/^\s*Basic\s+//) { my $val = MIME::Base64::decode($old); return $val unless wantarray; return split(/:/, $val, 2); } return; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Headers - Class encapsulating HTTP Message headers =head1 VERSION version 6.45 =head1 SYNOPSIS require HTTP::Headers; $h = HTTP::Headers->new; $h->header('Content-Type' => 'text/plain'); # set $ct = $h->header('Content-Type'); # get $h->remove_header('Content-Type'); # delete =head1 DESCRIPTION The C class encapsulates HTTP-style message headers. The headers consist of attribute-value pairs also called fields, which may be repeated, and which are printed in a particular order. The field names are cases insensitive. Instances of this class are usually created as member variables of the C and C classes, internal to the library. The following methods are available: =over 4 =item $h = HTTP::Headers->new Constructs a new C object. You might pass some initial attribute-value pairs as parameters to the constructor. I: $h = HTTP::Headers->new( Date => 'Thu, 03 Feb 1994 00:00:00 GMT', Content_Type => 'text/html; version=3.2', Content_Base => 'http://www.perl.org/'); The constructor arguments are passed to the C
method which is described below. =item $h->clone Returns a copy of this C object. =item $h->header( $field ) =item $h->header( $field => $value ) =item $h->header( $f1 => $v1, $f2 => $v2, ... ) Get or set the value of one or more header fields. The header field name ($field) is not case sensitive. To make the life easier for perl users who wants to avoid quoting before the => operator, you can use '_' as a replacement for '-' in header names. The header() method accepts multiple ($field => $value) pairs, which means that you can update several fields with a single invocation. The $value argument may be a plain string or a reference to an array of strings for a multi-valued field. If the $value is provided as C then the field is removed. If the $value is not given, then that header field will remain unchanged. In addition to being a string, $value may be something that stringifies. The old value (or values) of the last of the header fields is returned. If no such field exists C will be returned. A multi-valued field will be returned as separate values in list context and will be concatenated with ", " as separator in scalar context. The HTTP spec (RFC 2616) promises that joining multiple values in this way will not change the semantic of a header field, but in practice there are cases like old-style Netscape cookies (see L) where "," is used as part of the syntax of a single field value. Examples: $header->header(MIME_Version => '1.0', User_Agent => 'My-Web-Client/0.01'); $header->header(Accept => "text/html, text/plain, image/*"); $header->header(Accept => [qw(text/html text/plain image/*)]); @accepts = $header->header('Accept'); # get multiple values $accepts = $header->header('Accept'); # get values as a single string =item $h->push_header( $field => $value ) =item $h->push_header( $f1 => $v1, $f2 => $v2, ... ) Add a new field value for the specified header field. Previous values for the same field are retained. As for the header() method, the field name ($field) is not case sensitive and '_' can be used as a replacement for '-'. The $value argument may be a scalar or a reference to a list of scalars. $header->push_header(Accept => 'image/jpeg'); $header->push_header(Accept => [map "image/$_", qw(gif png tiff)]); =item $h->init_header( $field => $value ) Set the specified header to the given value, but only if no previous value for that field is set. The header field name ($field) is not case sensitive and '_' can be used as a replacement for '-'. The $value argument may be a scalar or a reference to a list of scalars. =item $h->remove_header( $field, ... ) This function removes the header fields with the specified names. The header field names ($field) are not case sensitive and '_' can be used as a replacement for '-'. The return value is the values of the fields removed. In scalar context the number of fields removed is returned. Note that if you pass in multiple field names then it is generally not possible to tell which of the returned values belonged to which field. =item $h->remove_content_headers This will remove all the header fields used to describe the content of a message. All header field names prefixed with C fall into this category, as well as C, C and C. RFC 2616 denotes these fields as I. The return value is a new C object that contains the removed headers only. =item $h->clear This will remove all header fields. =item $h->header_field_names Returns the list of distinct names for the fields present in the header. The field names have case as suggested by HTTP spec, and the names are returned in the recommended "Good Practice" order. In scalar context return the number of distinct field names. =item $h->scan( \&process_header_field ) Apply a subroutine to each header field in turn. The callback routine is called with two parameters; the name of the field and a single value (a string). If a header field is multi-valued, then the routine is called once for each value. The field name passed to the callback routine has case as suggested by HTTP spec, and the headers will be visited in the recommended "Good Practice" order. Any return values of the callback routine are ignored. The loop can be broken by raising an exception (C), but the caller of scan() would have to trap the exception itself. =item $h->flatten() Returns the list of pairs of keys and values. =item $h->as_string =item $h->as_string( $eol ) Return the header fields as a formatted MIME header. Since it internally uses the C method to build the string, the result will use case as suggested by HTTP spec, and it will follow recommended "Good Practice" of ordering the header fields. Long header values are not folded. The optional $eol parameter specifies the line ending sequence to use. The default is "\n". Embedded "\n" characters in header field values will be substituted with this line ending sequence. =back =head1 CONVENIENCE METHODS The most frequently used headers can also be accessed through the following convenience methods. Most of these methods can both be used to read and to set the value of a header. The header value is set if you pass an argument to the method. The old header value is always returned. If the given header did not exist then C is returned. Methods that deal with dates/times always convert their value to system time (seconds since Jan 1, 1970) and they also expect this kind of value when the header value is set. =over 4 =item $h->date This header represents the date and time at which the message was originated. I: $h->date(time); # set current date =item $h->expires This header gives the date and time after which the entity should be considered stale. =item $h->if_modified_since =item $h->if_unmodified_since These header fields are used to make a request conditional. If the requested resource has (or has not) been modified since the time specified in this field, then the server will return a C<304 Not Modified> response instead of the document itself. =item $h->last_modified This header indicates the date and time at which the resource was last modified. I: # check if document is more than 1 hour old if (my $last_mod = $h->last_modified) { if ($last_mod < time - 60*60) { ... } } =item $h->content_type The Content-Type header field indicates the media type of the message content. I: $h->content_type('text/html'); The value returned will be converted to lower case, and potential parameters will be chopped off and returned as a separate value if in an array context. If there is no such header field, then the empty string is returned. This makes it safe to do the following: if ($h->content_type eq 'text/html') { # we enter this place even if the real header value happens to # be 'TEXT/HTML; version=3.0' ... } =item $h->content_type_charset Returns the upper-cased charset specified in the Content-Type header. In list context return the lower-cased bare content type followed by the upper-cased charset. Both values will be C if not specified in the header. =item $h->content_is_text Returns TRUE if the Content-Type header field indicate that the content is textual. =item $h->content_is_html Returns TRUE if the Content-Type header field indicate that the content is some kind of HTML (including XHTML). This method can't be used to set Content-Type. =item $h->content_is_xhtml Returns TRUE if the Content-Type header field indicate that the content is XHTML. This method can't be used to set Content-Type. =item $h->content_is_xml Returns TRUE if the Content-Type header field indicate that the content is XML. This method can't be used to set Content-Type. =item $h->content_encoding The Content-Encoding header field is used as a modifier to the media type. When present, its value indicates what additional encoding mechanism has been applied to the resource. =item $h->content_length A decimal number indicating the size in bytes of the message content. =item $h->content_language The natural language(s) of the intended audience for the message content. The value is one or more language tags as defined by RFC 1766. Eg. "no" for some kind of Norwegian and "en-US" for English the way it is written in the US. =item $h->title The title of the document. In libwww-perl this header will be initialized automatically from the ETITLE>...E/TITLE> element of HTML documents. I =item $h->user_agent This header field is used in request messages and contains information about the user agent originating the request. I: $h->user_agent('Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0)'); =item $h->server The server header field contains information about the software being used by the originating server program handling the request. =item $h->from This header should contain an Internet e-mail address for the human user who controls the requesting user agent. The address should be machine-usable, as defined by RFC822. E.g.: $h->from('King Kong '); I =item $h->referer Used to specify the address (URI) of the document from which the requested resource address was obtained. The "Free On-line Dictionary of Computing" as this to say about the word I: A misspelling of "referrer" which somehow made it into the {HTTP} standard. A given {web page}'s referer (sic) is the {URL} of whatever web page contains the link that the user followed to the current page. Most browsers pass this information as part of a request. (1998-10-19) By popular demand C exists as an alias for this method so you can avoid this misspelling in your programs and still send the right thing on the wire. When setting the referrer, this method removes the fragment from the given URI if it is present, as mandated by RFC2616. Note that the removal does I happen automatically if using the header(), push_header() or init_header() methods to set the referrer. =item $h->www_authenticate This header must be included as part of a C<401 Unauthorized> response. The field value consist of a challenge that indicates the authentication scheme and parameters applicable to the requested URI. =item $h->proxy_authenticate This header must be included in a C<407 Proxy Authentication Required> response. =item $h->authorization =item $h->proxy_authorization A user agent that wishes to authenticate itself with a server or a proxy, may do so by including these headers. =item $h->authorization_basic This method is used to get or set an authorization header that use the "Basic Authentication Scheme". In array context it will return two values; the user name and the password. In scalar context it will return I<"uname:password"> as a single string value. When used to set the header value, it expects two arguments. I: $h->authorization_basic($uname, $password); The method will croak if the $uname contains a colon ':'. =item $h->proxy_authorization_basic Same as authorization_basic() but will set the "Proxy-Authorization" header instead. =back =head1 NON-CANONICALIZED FIELD NAMES The header field name spelling is normally canonicalized including the '_' to '-' translation. There are some application where this is not appropriate. Prefixing field names with ':' allow you to force a specific spelling. For example if you really want a header field name to show up as C instead of "Foo-Bar", you might set it like this: $h->header(":foo_bar" => 1); These field names are returned with the ':' intact for $h->header_field_names and the $h->scan callback, but the colons do not show in $h->as_string. =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: Class encapsulating HTTP Message headers PK!t8..perl5/HTTP/Date.pmnu6$package HTTP::Date; use strict; our $VERSION = '6.06'; require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(time2str str2time); our @EXPORT_OK = qw(parse_date time2iso time2isoz); require Time::Local; our ( @DoW, @MoY, %MoY ); @DoW = qw(Sun Mon Tue Wed Thu Fri Sat); @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); @MoY{@MoY} = ( 1 .. 12 ); my %GMT_ZONE = ( GMT => 1, UTC => 1, UT => 1, Z => 1 ); sub time2str (;$) { my $time = shift; $time = time unless defined $time; my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime($time); sprintf( "%s, %02d %s %04d %02d:%02d:%02d GMT", $DoW[$wday], $mday, $MoY[$mon], $year + 1900, $hour, $min, $sec ); } sub str2time ($;$) { my $str = shift; return undef unless defined $str; # fast exit for strictly conforming string if ( $str =~ /^[SMTWF][a-z][a-z], (\d\d) ([JFMAJSOND][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT$/ ) { return eval { my $t = Time::Local::timegm( $6, $5, $4, $1, $MoY{$2} - 1, $3 ); $t < 0 ? undef : $t; }; } my @d = parse_date($str); return undef unless @d; $d[1]--; # month my $tz = pop(@d); unless ( defined $tz ) { unless ( defined( $tz = shift ) ) { return eval { my $frac = $d[-1]; $frac -= ( $d[-1] = int($frac) ); my $t = Time::Local::timelocal( reverse @d ) + $frac; $t < 0 ? undef : $t; }; } } my $offset = 0; if ( $GMT_ZONE{ uc $tz } ) { # offset already zero } elsif ( $tz =~ /^([-+])?(\d\d?):?(\d\d)?$/ ) { $offset = 3600 * $2; $offset += 60 * $3 if $3; $offset *= -1 if $1 && $1 eq '-'; } else { eval { require Time::Zone } || return undef; $offset = Time::Zone::tz_offset($tz); return undef unless defined $offset; } return eval { my $frac = $d[-1]; $frac -= ( $d[-1] = int($frac) ); my $t = Time::Local::timegm( reverse @d ) + $frac; $t < 0 ? undef : $t - $offset; }; } sub parse_date ($) { local ($_) = shift; return unless defined; # More lax parsing below s/^\s+//; # kill leading space s/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)[a-z]*,?\s*//i; # Useless weekday my ( $day, $mon, $yr, $hr, $min, $sec, $tz, $ampm ); # Then we are able to check for most of the formats with this regexp ( ( $day, $mon, $yr, $hr, $min, $sec, $tz ) = /^ (\d\d?) # day (?:\s+|[-\/]) (\w+) # month (?:\s+|[-\/]) (\d+) # year (?: (?:\s+|:) # separator before clock (\d\d?):(\d\d) # hour:min (?::(\d\d))? # optional seconds )? # optional clock \s* ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone \s* (?:\(\w+\)|\w{3,})? # ASCII representation of timezone. \s*$ /x ) || # Try the ctime and asctime format ( ( $mon, $day, $hr, $min, $sec, $tz, $yr ) = /^ (\w{1,3}) # month \s+ (\d\d?) # day \s+ (\d\d?):(\d\d) # hour:min (?::(\d\d))? # optional seconds \s+ (?:([A-Za-z]+)\s+)? # optional timezone (\d+) # year \s*$ # allow trailing whitespace /x ) || # Then the Unix 'ls -l' date format ( ( $mon, $day, $yr, $hr, $min, $sec ) = /^ (\w{3}) # month \s+ (\d\d?) # day \s+ (?: (\d\d\d\d) | # year (\d{1,2}):(\d{2}) # hour:min (?::(\d\d))? # optional seconds ) \s*$ /x ) || # ISO 8601 format '1996-02-29 12:00:00 -0100' and variants ( ( $yr, $mon, $day, $hr, $min, $sec, $tz ) = /^ (\d{4}) # year [-\/]? (\d\d?) # numerical month [-\/]? (\d\d?) # day (?: (?:\s+|[-:Tt]) # separator before clock (\d\d?):?(\d\d) # hour:min (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) )? # optional clock \s* ([-+]?\d\d?:?(:?\d\d)? |Z|z)? # timezone (Z is "zero meridian", i.e. GMT) \s*$ /x ) || # Windows 'dir': '11-12-96 03:52PM' and four-digit year variant ( ( $mon, $day, $yr, $hr, $min, $ampm ) = /^ (\d{2}) # numerical month - (\d{2}) # day - (\d{2,4}) # year \s+ (\d\d?):(\d\d)([APap][Mm]) # hour:min AM or PM \s*$ /x ) || return; # unrecognized format # Translate month name to number $mon = $MoY{$mon} || $MoY{"\u\L$mon"} || ( $mon =~ /^\d\d?$/ && $mon >= 1 && $mon <= 12 && int($mon) ) || return; # If the year is missing, we assume first date before the current, # because of the formats we support such dates are mostly present # on "ls -l" listings. unless ( defined $yr ) { my $cur_mon; ( $cur_mon, $yr ) = (localtime)[ 4, 5 ]; $yr += 1900; $cur_mon++; $yr-- if $mon > $cur_mon; } elsif ( length($yr) < 3 ) { # Find "obvious" year my $cur_yr = (localtime)[5] + 1900; my $m = $cur_yr % 100; my $tmp = $yr; $yr += $cur_yr - $m; $m -= $tmp; $yr += ( $m > 0 ) ? 100 : -100 if abs($m) > 50; } # Make sure clock elements are defined $hr = 0 unless defined($hr); $min = 0 unless defined($min); $sec = 0 unless defined($sec); # Compensate for AM/PM if ($ampm) { $ampm = uc $ampm; $hr = 0 if $hr == 12 && $ampm eq 'AM'; $hr += 12 if $ampm eq 'PM' && $hr != 12; } return ( $yr, $mon, $day, $hr, $min, $sec, $tz ) if wantarray; if ( defined $tz ) { $tz = "Z" if $tz =~ /^(GMT|UTC?|[-+]?0+)$/; } else { $tz = ""; } return sprintf( "%04d-%02d-%02d %02d:%02d:%02d%s", $yr, $mon, $day, $hr, $min, $sec, $tz ); } sub time2iso (;$) { my $time = shift; $time = time unless defined $time; my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time); sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); } sub time2isoz (;$) { my $time = shift; $time = time unless defined $time; my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime($time); sprintf( "%04d-%02d-%02d %02d:%02d:%02dZ", $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); } 1; # ABSTRACT: HTTP::Date - date conversion routines # __END__ =pod =encoding UTF-8 =head1 NAME HTTP::Date - HTTP::Date - date conversion routines =head1 VERSION version 6.06 =head1 SYNOPSIS use HTTP::Date; $string = time2str($time); # Format as GMT ASCII time $time = str2time($string); # convert ASCII date to machine time =head1 DESCRIPTION This module provides functions that deal the date formats used by the HTTP protocol (and then some more). Only the first two functions, time2str() and str2time(), are exported by default. =over 4 =item time2str( [$time] ) The time2str() function converts a machine time (seconds since epoch) to a string. If the function is called without an argument or with an undefined argument, it will use the current time. The string returned is in the format preferred for the HTTP protocol. This is a fixed length subset of the format defined by RFC 1123, represented in Universal Time (GMT). An example of a time stamp in this format is: Sun, 06 Nov 1994 08:49:37 GMT =item str2time( $str [, $zone] ) The str2time() function converts a string to machine time. It returns C if the format of $str is unrecognized, otherwise whatever the C functions can make out of the parsed time. Dates before the system's epoch may not work on all operating systems. The time formats recognized are the same as for parse_date(). The function also takes an optional second argument that specifies the default time zone to use when converting the date. This parameter is ignored if the zone is found in the date string itself. If this parameter is missing, and the date string format does not contain any zone specification, then the local time zone is assumed. If the zone is not "C" or numerical (like "C<-0800>" or "C<+0100>"), then the C module must be installed in order to get the date recognized. =item parse_date( $str ) This function will try to parse a date string, and then return it as a list of numerical values followed by a (possible undefined) time zone specifier; ($year, $month, $day, $hour, $min, $sec, $tz). The $year will be the full 4-digit year, and $month numbers start with 1 (for January). In scalar context the numbers are interpolated in a string of the "YYYY-MM-DD hh:mm:ss TZ"-format and returned. If the date is unrecognized, then the empty list is returned (C in scalar context). The function is able to parse the following formats: "Wed, 09 Feb 1994 22:23:32 GMT" -- HTTP format "Thu Feb 3 17:03:55 GMT 1994" -- ctime(3) format "Thu Feb 3 00:00:00 1994", -- ANSI C asctime() format "Tuesday, 08-Feb-94 14:15:29 GMT" -- old rfc850 HTTP format "Tuesday, 08-Feb-1994 14:15:29 GMT" -- broken rfc850 HTTP format "03/Feb/1994:17:03:55 -0700" -- common logfile format "09 Feb 1994 22:23:32 GMT" -- HTTP format (no weekday) "08-Feb-94 14:15:29 GMT" -- rfc850 format (no weekday) "08-Feb-1994 14:15:29 GMT" -- broken rfc850 format (no weekday) "1994-02-03 14:15:29 -0100" -- ISO 8601 format "1994-02-03 14:15:29" -- zone is optional "1994-02-03" -- only date "1994-02-03T14:15:29" -- Use T as separator "19940203T141529Z" -- ISO 8601 compact format "19940203" -- only date "08-Feb-94" -- old rfc850 HTTP format (no weekday, no time) "08-Feb-1994" -- broken rfc850 HTTP format (no weekday, no time) "09 Feb 1994" -- proposed new HTTP format (no weekday, no time) "03/Feb/1994" -- common logfile format (no time, no offset) "Feb 3 1994" -- Unix 'ls -l' format "Feb 3 17:03" -- Unix 'ls -l' format "11-15-96 03:52PM" -- Windows 'dir' format "11-15-1996 03:52PM" -- Windows 'dir' format with four-digit year The parser ignores leading and trailing whitespace. It also allow the seconds to be missing and the month to be numerical in most formats. If the year is missing, then we assume that the date is the first matching date I current month. If the year is given with only 2 digits, then parse_date() will select the century that makes the year closest to the current date. =item time2iso( [$time] ) Same as time2str(), but returns a "YYYY-MM-DD hh:mm:ss"-formatted string representing time in the local time zone. =item time2isoz( [$time] ) Same as time2str(), but returns a "YYYY-MM-DD hh:mm:ssZ"-formatted string representing Universal Time. =back =head1 SEE ALSO L, L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1995 by Gisle Aas. 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 PK!V:hp6p6perl5/HTTP/Status.pmnu6$package HTTP::Status; use strict; use warnings; our $VERSION = '6.45'; use Exporter 5.57 'import'; our @EXPORT = qw(is_info is_success is_redirect is_error status_message); our @EXPORT_OK = qw(is_client_error is_server_error is_cacheable_by_default status_constant_name status_codes); # Note also addition of mnemonics to @EXPORT below # Unmarked codes are from RFC 7231 (2017-12-20) # See also: # https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml my %StatusCode = ( 100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', # RFC 2518: WebDAV 103 => 'Early Hints', # RFC 8297: Indicating Hints # 104 .. 199 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', # RFC 7233: Range Requests 207 => 'Multi-Status', # RFC 4918: WebDAV 208 => 'Already Reported', # RFC 5842: WebDAV bindings # 209 .. 225 226 => 'IM Used', # RFC 3229: Delta encoding # 227 .. 299 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', # RFC 7232: Conditional Request 305 => 'Use Proxy', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect', # RFC 7528: Permanent Redirect # 309 .. 399 400 => 'Bad Request', 401 => 'Unauthorized', # RFC 7235: Authentication 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', # RFC 7235: Authentication 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', # RFC 7232: Conditional Request 413 => 'Payload Too Large', 414 => 'URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', # RFC 7233: Range Requests 417 => 'Expectation Failed', # 418 .. 420 421 => 'Misdirected Request', # RFC 7540: HTTP/2 422 => 'Unprocessable Entity', # RFC 4918: WebDAV 423 => 'Locked', # RFC 4918: WebDAV 424 => 'Failed Dependency', # RFC 4918: WebDAV 425 => 'Too Early', # RFC 8470: Using Early Data in HTTP 426 => 'Upgrade Required', # 427 428 => 'Precondition Required', # RFC 6585: Additional Codes 429 => 'Too Many Requests', # RFC 6585: Additional Codes # 430 431 => 'Request Header Fields Too Large', # RFC 6585: Additional Codes # 432 .. 450 451 => 'Unavailable For Legal Reasons', # RFC 7725: Legal Obstacles # 452 .. 499 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', # RFC 2295: Transparant Ngttn 507 => 'Insufficient Storage', # RFC 4918: WebDAV 508 => 'Loop Detected', # RFC 5842: WebDAV bindings # 509 510 => 'Not Extended', # RFC 2774: Extension Framework 511 => 'Network Authentication Required', # RFC 6585: Additional Codes ); my %StatusCodeName; # keep some unofficial codes that used to be in this distribution %StatusCode = ( %StatusCode, 418 => 'I\'m a teapot', # RFC 2324: HTCPC/1.0 1-april 449 => 'Retry with', # microsoft 509 => 'Bandwidth Limit Exceeded', # Apache / cPanel ); my $mnemonicCode = ''; my ($code, $message); while (($code, $message) = each %StatusCode) { # create mnemonic subroutines $message =~ s/I'm/I am/; $message =~ tr/a-z \-/A-Z__/; my $constant_name = "HTTP_".$message; $mnemonicCode .= "sub $constant_name () { $code }\n"; $mnemonicCode .= "*RC_$message = \\&HTTP_$message;\n"; # legacy $mnemonicCode .= "push(\@EXPORT_OK, 'HTTP_$message');\n"; $mnemonicCode .= "push(\@EXPORT, 'RC_$message');\n"; $StatusCodeName{$code} = $constant_name } eval $mnemonicCode; # only one eval for speed die if $@; # backwards compatibility *RC_MOVED_TEMPORARILY = \&RC_FOUND; # 302 was renamed in the standard push(@EXPORT, "RC_MOVED_TEMPORARILY"); my %compat = ( REQUEST_ENTITY_TOO_LARGE => \&HTTP_PAYLOAD_TOO_LARGE, REQUEST_URI_TOO_LARGE => \&HTTP_URI_TOO_LONG, REQUEST_RANGE_NOT_SATISFIABLE => \&HTTP_RANGE_NOT_SATISFIABLE, NO_CODE => \&HTTP_TOO_EARLY, UNORDERED_COLLECTION => \&HTTP_TOO_EARLY, ); foreach my $name (keys %compat) { push(@EXPORT, "RC_$name"); push(@EXPORT_OK, "HTTP_$name"); no strict 'refs'; *{"RC_$name"} = $compat{$name}; *{"HTTP_$name"} = $compat{$name}; } our %EXPORT_TAGS = ( constants => [grep /^HTTP_/, @EXPORT_OK], is => [grep /^is_/, @EXPORT, @EXPORT_OK], ); sub status_message ($) { $StatusCode{$_[0]}; } sub status_constant_name ($) { exists($StatusCodeName{$_[0]}) ? $StatusCodeName{$_[0]} : undef; } sub is_info ($) { $_[0] && $_[0] >= 100 && $_[0] < 200; } sub is_success ($) { $_[0] && $_[0] >= 200 && $_[0] < 300; } sub is_redirect ($) { $_[0] && $_[0] >= 300 && $_[0] < 400; } sub is_error ($) { $_[0] && $_[0] >= 400 && $_[0] < 600; } sub is_client_error ($) { $_[0] && $_[0] >= 400 && $_[0] < 500; } sub is_server_error ($) { $_[0] && $_[0] >= 500 && $_[0] < 600; } sub is_cacheable_by_default ($) { $_[0] && ( $_[0] == 200 # OK || $_[0] == 203 # Non-Authoritative Information || $_[0] == 204 # No Content || $_[0] == 206 # Not Acceptable || $_[0] == 300 # Multiple Choices || $_[0] == 301 # Moved Permanently || $_[0] == 308 # Permanent Redirect || $_[0] == 404 # Not Found || $_[0] == 405 # Method Not Allowed || $_[0] == 410 # Gone || $_[0] == 414 # Request-URI Too Large || $_[0] == 451 # Unavailable For Legal Reasons || $_[0] == 501 # Not Implemented ); } sub status_codes { %StatusCode; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Status - HTTP Status code processing =head1 VERSION version 6.45 =head1 SYNOPSIS use HTTP::Status qw(:constants :is status_message); if ($rc != HTTP_OK) { print status_message($rc), "\n"; } if (is_success($rc)) { ... } if (is_error($rc)) { ... } if (is_redirect($rc)) { ... } =head1 DESCRIPTION I is a library of routines for defining and classifying HTTP status codes for libwww-perl. Status codes are used to encode the overall outcome of an HTTP response message. Codes correspond to those defined in RFC 2616 and RFC 2518. =head1 CONSTANTS The following constant functions can be used as mnemonic status code names. None of these are exported by default. Use the C<:constants> tag to import them all. HTTP_CONTINUE (100) HTTP_SWITCHING_PROTOCOLS (101) HTTP_PROCESSING (102) HTTP_EARLY_HINTS (103) HTTP_OK (200) HTTP_CREATED (201) HTTP_ACCEPTED (202) HTTP_NON_AUTHORITATIVE_INFORMATION (203) HTTP_NO_CONTENT (204) HTTP_RESET_CONTENT (205) HTTP_PARTIAL_CONTENT (206) HTTP_MULTI_STATUS (207) HTTP_ALREADY_REPORTED (208) HTTP_IM_USED (226) HTTP_MULTIPLE_CHOICES (300) HTTP_MOVED_PERMANENTLY (301) HTTP_FOUND (302) HTTP_SEE_OTHER (303) HTTP_NOT_MODIFIED (304) HTTP_USE_PROXY (305) HTTP_TEMPORARY_REDIRECT (307) HTTP_PERMANENT_REDIRECT (308) HTTP_BAD_REQUEST (400) HTTP_UNAUTHORIZED (401) HTTP_PAYMENT_REQUIRED (402) HTTP_FORBIDDEN (403) HTTP_NOT_FOUND (404) HTTP_METHOD_NOT_ALLOWED (405) HTTP_NOT_ACCEPTABLE (406) HTTP_PROXY_AUTHENTICATION_REQUIRED (407) HTTP_REQUEST_TIMEOUT (408) HTTP_CONFLICT (409) HTTP_GONE (410) HTTP_LENGTH_REQUIRED (411) HTTP_PRECONDITION_FAILED (412) HTTP_PAYLOAD_TOO_LARGE (413) HTTP_URI_TOO_LONG (414) HTTP_UNSUPPORTED_MEDIA_TYPE (415) HTTP_RANGE_NOT_SATISFIABLE (416) HTTP_EXPECTATION_FAILED (417) HTTP_MISDIRECTED REQUEST (421) HTTP_UNPROCESSABLE_ENTITY (422) HTTP_LOCKED (423) HTTP_FAILED_DEPENDENCY (424) HTTP_TOO_EARLY (425) HTTP_UPGRADE_REQUIRED (426) HTTP_PRECONDITION_REQUIRED (428) HTTP_TOO_MANY_REQUESTS (429) HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE (431) HTTP_UNAVAILABLE_FOR_LEGAL_REASONS (451) HTTP_INTERNAL_SERVER_ERROR (500) HTTP_NOT_IMPLEMENTED (501) HTTP_BAD_GATEWAY (502) HTTP_SERVICE_UNAVAILABLE (503) HTTP_GATEWAY_TIMEOUT (504) HTTP_HTTP_VERSION_NOT_SUPPORTED (505) HTTP_VARIANT_ALSO_NEGOTIATES (506) HTTP_INSUFFICIENT_STORAGE (507) HTTP_LOOP_DETECTED (508) HTTP_NOT_EXTENDED (510) HTTP_NETWORK_AUTHENTICATION_REQUIRED (511) =head1 FUNCTIONS The following additional functions are provided. Most of them are exported by default. The C<:is> import tag can be used to import all the classification functions. =over 4 =item status_message( $code ) The status_message() function will translate status codes to human readable strings. The string is the same as found in the constant names above. For example, C will return C<"Not Found">. If the $code is not registered in the L then C is returned. =item status_constant_name( $code ) The status_constant_name() function will translate a status code to a string which has the name of the constant for that status code. For example, C will return C<"HTTP_NOT_FOUND">. If the C<$code> is not registered in the L then C is returned. =item is_info( $code ) Return TRUE if C<$code> is an I status code (1xx). This class of status code indicates a provisional response which can't have any content. =item is_success( $code ) Return TRUE if C<$code> is a I status code (2xx). =item is_redirect( $code ) Return TRUE if C<$code> is a I status code (3xx). This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. =item is_error( $code ) Return TRUE if C<$code> is an I status code (4xx or 5xx). The function returns TRUE for both client and server error status codes. =item is_client_error( $code ) Return TRUE if C<$code> is a I status code (4xx). This class of status code is intended for cases in which the client seems to have erred. This function is B exported by default. =item is_server_error( $code ) Return TRUE if C<$code> is a I status code (5xx). This class of status codes is intended for cases in which the server is aware that it has erred or is incapable of performing the request. This function is B exported by default. =item is_cacheable_by_default( $code ) Return TRUE if C<$code> indicates that a response is cacheable by default, and it can be reused by a cache with heuristic expiration. All other status codes are not cacheable by default. See L. This function is B exported by default. =item status_codes Returns a hash mapping numerical HTTP status code (e.g. 200) to text status messages (e.g. "OK") This function is B exported by default. =back =head1 SEE ALSO L =head1 BUGS For legacy reasons all the C constants are exported by default with the prefix C. It's recommended to use explicit imports and the C<:constants> tag instead of relying on this. =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: HTTP Status code processing PK!mperl5/HTTP/Headers/Util.pmnu6$package HTTP::Headers::Util; use strict; use warnings; our $VERSION = '6.45'; use Exporter 5.57 'import'; our @EXPORT_OK=qw(split_header_words _split_header_words join_header_words); sub split_header_words { my @res = &_split_header_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(@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+// || s/^=//) { # continue } else { die "This should not happen: '$_'"; } } push(@res, \@cur) if @cur; } @res; } sub join_header_words { @_ = ([@_]) if @_ && !ref($_[0]); my @res; for (@_) { my @cur = @$_; my @attr; while (@cur) { my $k = shift @cur; my $v = shift @cur; if (defined $v) { if ($v =~ /[\x00-\x20()<>@,;:\\\"\/\[\]?={}\x7F-\xFF]/ || !length($v)) { $v =~ s/([\"\\])/\\$1/g; # escape " and \ $k .= qq(="$v"); } else { # token $k .= "=$v"; } } push(@attr, $k); } push(@res, join("; ", @attr)) if @attr; } join(", ", @res); } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Headers::Util - Header value parsing utility functions =head1 VERSION version 6.45 =head1 SYNOPSIS use HTTP::Headers::Util qw(split_header_words); @values = split_header_words($h->header("Content-Type")); =head1 DESCRIPTION This module provides a few functions that helps parsing and construction of valid HTTP header values. None of the functions are exported by default. The following functions are available: =over 4 =item split_header_words( @header_values ) This function will parse the header values given as argument into a list of anonymous arrays containing key/value pairs. The function knows how to deal with ",", ";" and "=" as well as quoted values after "=". A list of space separated tokens are parsed as if they were separated by ";". If the @header_values passed as argument contains multiple values, then they are treated as if they were a single value separated by comma ",". This means that this function is useful for parsing header fields that follow this syntax (BNF as from the HTTP/1.1 specification, but we relax the requirement for tokens). headers = #header header = (token | parameter) *( [";"] (token | parameter)) token = 1* separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) qdtext = > quoted-pair = "\" CHAR parameter = attribute "=" value attribute = token value = token | quoted-string Each I
is represented by an anonymous array of key/value pairs. The keys will be all be forced to lower case. The value for a simple token (not part of a parameter) is C. Syntactically incorrect headers will not necessarily be parsed as you would want. This is easier to describe with some examples: split_header_words('foo="bar"; port="80,81"; DISCARD, BAR=baz'); split_header_words('text/html; charset="iso-8859-1"'); split_header_words('Basic realm="\\"foo\\\\bar\\""'); will return [foo=>'bar', port=>'80,81', discard=> undef], [bar=>'baz' ] ['text/html' => undef, charset => 'iso-8859-1'] [basic => undef, realm => "\"foo\\bar\""] If you don't want the function to convert tokens and attribute keys to lower case you can call it as C<_split_header_words> instead (with a leading underscore). =item join_header_words( @arrays ) This will do the opposite of the conversion done by split_header_words(). It takes a list of anonymous arrays as arguments (or a list of key/value pairs) and produces a single header value. Attribute values are quoted if needed. Example: join_header_words(["text/plain" => undef, charset => "iso-8859/1"]); join_header_words("text/plain" => undef, charset => "iso-8859/1"); will both return the string: text/plain; charset="iso-8859/1" =back =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: Header value parsing utility functions PK![ perl5/HTTP/Headers/ETag.pmnu6$package HTTP::Headers::ETag; use strict; use warnings; our $VERSION = '6.45'; require HTTP::Date; require HTTP::Headers; package HTTP::Headers; sub _etags { my $self = shift; my $header = shift; my @old = _split_etag_list($self->_header($header)); if (@_) { $self->_header($header => join(", ", _split_etag_list(@_))); } wantarray ? @old : join(", ", @old); } sub etag { shift->_etags("ETag", @_); } sub if_match { shift->_etags("If-Match", @_); } sub if_none_match { shift->_etags("If-None-Match", @_); } sub if_range { # Either a date or an entity-tag my $self = shift; my @old = $self->_header("If-Range"); if (@_) { my $new = shift; if (!defined $new) { $self->remove_header("If-Range"); } elsif ($new =~ /^\d+$/) { $self->_date_header("If-Range", $new); } else { $self->_etags("If-Range", $new); } } return unless defined(wantarray); for (@old) { my $t = HTTP::Date::str2time($_); $_ = $t if $t; } wantarray ? @old : join(", ", @old); } # Split a list of entity tag values. The return value is a list # consisting of one element per entity tag. Suitable for parsing # headers like C, C. You might even want to # use it on C and C entity tag values, because it will # normalize them to the common form. # # entity-tag = [ weak ] opaque-tag # weak = "W/" # opaque-tag = quoted-string sub _split_etag_list { my(@val) = @_; my @res; for (@val) { while (length) { my $weak = ""; $weak = "W/" if s,^\s*[wW]/,,; my $etag = ""; if (s/^\s*(\"[^\"\\]*(?:\\.[^\"\\]*)*\")//) { push(@res, "$weak$1"); } elsif (s/^\s*,//) { push(@res, qq(W/"")) if $weak; } elsif (s/^\s*([^,\s]+)//) { $etag = $1; $etag =~ s/([\"\\])/\\$1/g; push(@res, qq($weak"$etag")); } elsif (s/^\s+// || !length) { push(@res, qq(W/"")) if $weak; } else { die "This should not happen: '$_'"; } } } @res; } 1; __END__ =pod =encoding UTF-8 =head1 NAME HTTP::Headers::ETag =head1 VERSION version 6.45 =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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 PK!2&. . perl5/HTTP/Headers/Auth.pmnu6$package HTTP::Headers::Auth; use strict; use warnings; our $VERSION = '6.45'; use HTTP::Headers; package HTTP::Headers; BEGIN { # we provide a new (and better) implementations below undef(&www_authenticate); undef(&proxy_authenticate); } require HTTP::Headers::Util; sub _parse_authenticate { my @ret; for (HTTP::Headers::Util::split_header_words(@_)) { if (!defined($_->[1])) { # this is a new auth scheme push(@ret, shift(@$_) => {}); shift @$_; } if (@ret) { # this a new parameter pair for the last auth scheme while (@$_) { my $k = shift @$_; my $v = shift @$_; $ret[-1]{$k} = $v; } } else { # something wrong, parameter pair without any scheme seen # IGNORE } } @ret; } sub _authenticate { my $self = shift; my $header = shift; my @old = $self->_header($header); if (@_) { $self->remove_header($header); my @new = @_; while (@new) { my $a_scheme = shift(@new); if ($a_scheme =~ /\s/) { # assume complete valid value, pass it through $self->push_header($header, $a_scheme); } else { my @param; if (@new) { my $p = $new[0]; if (ref($p) eq "ARRAY") { @param = @$p; shift(@new); } elsif (ref($p) eq "HASH") { @param = %$p; shift(@new); } } my $val = ucfirst(lc($a_scheme)); if (@param) { my $sep = " "; while (@param) { my $k = shift @param; my $v = shift @param; if ($v =~ /[^0-9a-zA-Z]/ || lc($k) eq "realm") { # must quote the value $v =~ s,([\\\"]),\\$1,g; $v = qq("$v"); } $val .= "$sep$k=$v"; $sep = ", "; } } $self->push_header($header, $val); } } } return unless defined wantarray; wantarray ? _parse_authenticate(@old) : join(", ", @old); } sub www_authenticate { shift->_authenticate("WWW-Authenticate", @_) } sub proxy_authenticate { shift->_authenticate("Proxy-Authenticate", @_) } 1; __END__ =pod =encoding UTF-8 =head1 NAME HTTP::Headers::Auth =head1 VERSION version 6.45 =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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 PK!##perl5/HTTP/Cookies/Microsoft.pmnu6$package HTTP::Cookies::Microsoft; use strict; our $VERSION = '6.11'; require HTTP::Cookies; our @ISA=qw(HTTP::Cookies); sub load_cookies_from_file { my ($file) = @_; my @cookies; open (my $fh, '<', $file) || return; while (my $key = <$fh>) { chomp $key; my ($value, $domain_path, $flags, $lo_expire, $hi_expire); my ($lo_create, $hi_create, $sep); chomp($value = <$fh>); chomp($domain_path= <$fh>); chomp($flags = <$fh>); # 0x0001 bit is for secure chomp($lo_expire = <$fh>); chomp($hi_expire = <$fh>); chomp($lo_create = <$fh>); chomp($hi_create = <$fh>); chomp($sep = <$fh>); if (!defined($key) || !defined($value) || !defined($domain_path) || !defined($flags) || !defined($hi_expire) || !defined($lo_expire) || !defined($hi_create) || !defined($lo_create) || !defined($sep) || ($sep ne '*')) { last; } if ($domain_path =~ /^([^\/]+)(\/.*)$/) { my $domain = $1; my $path = $2; push @cookies, { KEY => $key, VALUE => $value, DOMAIN => $domain, PATH => $path, FLAGS =>$flags, HIXP =>$hi_expire, LOXP => $lo_expire, HICREATE => $hi_create, LOCREATE => $lo_create }; } } return \@cookies; } sub get_user_name { use Win32; use locale; my $user = lc(Win32::LoginName()); return $user; } # MSIE stores create and expire times as Win32 FILETIME, # which is 64 bits of 100 nanosecond intervals since Jan 01 1601 # # But Cookies code expects time in 32-bit value expressed # in seconds since Jan 01 1970 # sub epoch_time_offset_from_win32_filetime { my ($high, $low) = @_; #-------------------------------------------------------- # USEFUL CONSTANT #-------------------------------------------------------- # 0x019db1de 0xd53e8000 is 1970 Jan 01 00:00:00 in Win32 FILETIME # # 100 nanosecond intervals == 0.1 microsecond intervals my $filetime_low32_1970 = 0xd53e8000; my $filetime_high32_1970 = 0x019db1de; #------------------------------------ # ALGORITHM #------------------------------------ # To go from 100 nanosecond intervals to seconds since 00:00 Jan 01 1970: # # 1. Adjust 100 nanosecond intervals to Jan 01 1970 base # 2. Divide by 10 to get to microseconds (1/millionth second) # 3. Divide by 1000000 (10 ^ 6) to get to seconds # # We can combine Step 2 & 3 into one divide. # # After much trial and error, I came up with the following code which # avoids using Math::BigInt or floating pt, but still gives correct answers # If the filetime is before the epoch, return 0 if (($high < $filetime_high32_1970) || (($high == $filetime_high32_1970) && ($low < $filetime_low32_1970))) { return 0; } # Can't multiply by 0x100000000, (1 << 32), # without Perl issuing an integer overflow warning # # So use two multiplies by 0x10000 instead of one multiply by 0x100000000 # # The result is the same. # my $date1970 = (($filetime_high32_1970 * 0x10000) * 0x10000) + $filetime_low32_1970; my $time = (($high * 0x10000) * 0x10000) + $low; $time -= $date1970; $time /= 10000000; return $time; } sub load_cookie { my($self, $file) = @_; my $now = time() - $HTTP::Cookies::EPOCH_OFFSET; my $cookie_data; if (-f $file) { # open the cookie file and get the data $cookie_data = load_cookies_from_file($file); foreach my $cookie (@{$cookie_data}) { my $secure = ($cookie->{FLAGS} & 1) != 0; my $expires = epoch_time_offset_from_win32_filetime($cookie->{HIXP}, $cookie->{LOXP}); $self->set_cookie(undef, $cookie->{KEY}, $cookie->{VALUE}, $cookie->{PATH}, $cookie->{DOMAIN}, undef, 0, $secure, $expires-$now, 0); } } } sub load { my($self, $cookie_index) = @_; my $now = time() - $HTTP::Cookies::EPOCH_OFFSET; my $cookie_dir = ''; my $delay_load = (defined($self->{'delayload'}) && $self->{'delayload'}); my $user_name = get_user_name(); my $data; $cookie_index ||= $self->{'file'} || return; if ($cookie_index =~ /[\\\/][^\\\/]+$/) { $cookie_dir = $` . "\\"; } open (my $fh, '<:raw', $cookie_index) || return; if (256 != read($fh, $data, 256)) { warn "$cookie_index file is not large enough"; return; } # Cookies' index.dat file starts with 32 bytes of signature # followed by an offset to the first record, stored as a little-endian DWORD my ($sig, $size) = unpack('a32 V', $data); # check that sig is valid (only tested in IE6.0) if (($sig !~ /^Client UrlCache MMF Ver 5\.2/) || (0x4000 != $size)) { warn "$cookie_index ['$sig' $size] does not seem to contain cookies"; return; } # move the file ptr to start of the first record if (0 == seek($fh, $size, 0)) { return; } # Cookies are usually stored in 'URL ' records in two contiguous 0x80 byte sectors (256 bytes) # so read in two 0x80 byte sectors and adjust if not a Cookie. while (256 == read($fh, $data, 256)) { # each record starts with a 4-byte signature # and a count (little-endian DWORD) of 0x80 byte sectors for the record ($sig, $size) = unpack('a4 V', $data); # Cookies are found in 'URL ' records if ('URL ' ne $sig) { # skip over uninteresting record: I've seen 'HASH' and 'LEAK' records if (($sig eq 'HASH') || ($sig eq 'LEAK')) { # '-2' takes into account the two 0x80 byte sectors we've just read in if (($size > 0) && ($size != 2)) { if (0 == seek($fh, ($size-2)*0x80, 1)) { # Seek failed. Something's wrong. Gonna stop. last; } } } next; } #$REMOVE Need to check if URL records in Cookies' index.dat will # ever use more than two 0x80 byte sectors if ($size > 2) { my $more_data = ($size-2)*0x80; if ($more_data != read($fh, $data, $more_data, 256)) { last; } } (my $user_name2 = $user_name) =~ s/ /_/g; if ($data =~ /Cookie:\Q$user_name\E@([\x21-\xFF]+).*?((?:\Q$user_name\E|\Q$user_name2\E)@[\x21-\xFF]+\.txt)/) { my $cookie_file = $cookie_dir . $2; # form full pathname if (!$delay_load) { $self->load_cookie($cookie_file); } else { my $domain = $1; # grab only the domain name, drop everything from the first dir sep on if ($domain =~ m{[\\/]}) { $domain = $`; } # set the delayload cookie for this domain with # the cookie_file as cookie for later-loading info $self->set_cookie(undef, 'cookie', $cookie_file, '//+delayload', $domain, undef, 0, 0, $now+86_400, 0); } } } 1; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Cookies::Microsoft - Access to Microsoft cookies files =head1 VERSION version 6.11 =head1 SYNOPSIS use LWP; use HTTP::Cookies::Microsoft; use Win32::TieRegistry(Delimiter => "/"); my $cookies_dir = $Registry-> {"CUser/Software/Microsoft/Windows/CurrentVersion/Explorer/Shell Folders/Cookies"}; $cookie_jar = HTTP::Cookies::Microsoft->new( file => "$cookies_dir\\index.dat", 'delayload' => 1, ); my $browser = LWP::UserAgent->new; $browser->cookie_jar( $cookie_jar ); =head1 DESCRIPTION This is a subclass of C which loads Microsoft Internet Explorer 5.x and 6.x for Windows (MSIE) cookie files. See the documentation for L. =head1 METHODS The following methods are provided: =over 4 =item $cookie_jar = HTTP::Cookies::Microsoft->new; The constructor takes hash style parameters. In addition to the regular HTTP::Cookies parameters, HTTP::Cookies::Microsoft recognizes the following: delayload: delay loading of cookie data until a request is actually made. This results in faster runtime unless you use most of the cookies since only the domain's cookie data is loaded on demand. =back =head1 CAVEATS Please note that the code DOESN'T support saving to the MSIE cookie file format. =head1 AUTHOR Johnny Lee =head1 COPYRIGHT Copyright 2002 Johnny Lee This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2002 by Gisle Aas. 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: Access to Microsoft cookies files PK!rLHK K perl5/HTTP/Cookies/Netscape.pmnu6$package HTTP::Cookies::Netscape; use strict; our $VERSION = '6.11'; require HTTP::Cookies; our @ISA=qw(HTTP::Cookies); sub load { my ($self, $file) = @_; $file ||= $self->{'file'} || return; local $/ = "\n"; # make sure we got standard record separator open (my $fh, '<', $file) || return; my $magic = <$fh>; chomp $magic; unless ($magic =~ /^#(?: Netscape)? HTTP Cookie File/) { warn "$file does not look like a netscape cookies file"; return; } my $now = time() - $HTTP::Cookies::EPOCH_OFFSET; while (my $line = <$fh>) { chomp($line); $line =~ s/\s*\#HttpOnly_//; next if $line =~ /^\s*\#/; next if $line =~ /^\s*$/; $line =~ tr/\n\r//d; my($domain,$bool1,$path,$secure, $expires,$key,$val) = split(/\t/, $line); $secure = ($secure eq "TRUE"); $self->set_cookie(undef, $key, $val, $path, $domain, undef, 0, $secure, $expires-$now, 0); } 1; } sub save { my $self = shift; my %args = ( file => $self->{'file'}, ignore_discard => $self->{'ignore_discard'}, @_ == 1 ? ( file => $_[0] ) : @_ ); Carp::croak('Unexpected argument to save method') if keys %args > 2; my $file = $args{'file'} || return; open(my $fh, '>', $file) || return; # Use old, now broken link to the old cookie spec just in case something # else (not us!) requires the comment block exactly this way. print {$fh} <scan(sub { my ($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $expires, $discard, $rest) = @_; return if $discard && !$args{'ignore_discard'}; $expires = $expires ? $expires - $HTTP::Cookies::EPOCH_OFFSET : 0; return if $now > $expires; $secure = $secure ? "TRUE" : "FALSE"; my $bool = $domain =~ /^\./ ? "TRUE" : "FALSE"; print {$fh} join("\t", $domain, $bool, $path, $secure, $expires, $key, $val), "\n"; }); 1; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Cookies::Netscape - Access to Netscape cookies files =head1 VERSION version 6.11 =head1 SYNOPSIS use LWP; use HTTP::Cookies::Netscape; $cookie_jar = HTTP::Cookies::Netscape->new( file => "c:/program files/netscape/users/ZombieCharity/cookies.txt", ); my $browser = LWP::UserAgent->new; $browser->cookie_jar( $cookie_jar ); =head1 DESCRIPTION This is a subclass of C that reads (and optionally writes) Netscape/Mozilla cookie files. See the documentation for L. =head1 CAVEATS Please note that the Netscape/Mozilla cookie file format can't store all the information available in the Set-Cookie2 headers, so you will probably lose some information if you save in this format. At time of writing, this module seems to work fine with Mozilla Phoenix/Firebird. =head1 SEE ALSO L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2002 by Gisle Aas. 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: Access to Netscape cookies files PK!c`L^^perl5/HTTP/Cookies.pmnu6$package HTTP::Cookies; use strict; use HTTP::Date qw(str2time parse_date time2str); use HTTP::Headers::Util qw(_split_header_words join_header_words); our $EPOCH_OFFSET; our $VERSION = '6.11'; # Legacy: because "use "HTTP::Cookies" used be the ONLY way # to load the class HTTP::Cookies::Netscape. require HTTP::Cookies::Netscape; $EPOCH_OFFSET = 0; # difference from Unix epoch # A HTTP::Cookies object is a hash. The main attribute is the # COOKIES 3 level hash: $self->{COOKIES}{$domain}{$path}{$key}. sub new { my $class = shift; my $self = bless { COOKIES => {}, }, $class; my %cnf = @_; for (keys %cnf) { $self->{lc($_)} = $cnf{$_}; } $self->load; $self; } sub add_cookie_header { my $self = shift; my $request = shift || return; my $url = $request->uri; my $scheme = $url->scheme; unless ($scheme =~ /^https?\z/) { return; } my $domain = _host($request, $url); $domain = "$domain.local" unless $domain =~ /\./; my $secure_request = ($scheme eq "https"); my $req_path = _url_path($url); my $req_port = $url->port; my $now = time(); _normalize_path($req_path) if $req_path =~ /%/; my @cval; # cookie values for the "Cookie" header my $set_ver; my $netscape_only = 0; # An exact domain match applies to any cookie while ($domain =~ /\./) { # Checking $domain for cookies" my $cookies = $self->{COOKIES}{$domain}; next unless $cookies; if ($self->{delayload} && defined($cookies->{'//+delayload'})) { my $cookie_data = $cookies->{'//+delayload'}{'cookie'}; delete $self->{COOKIES}{$domain}; $self->load_cookie($cookie_data->[1]); $cookies = $self->{COOKIES}{$domain}; next unless $cookies; # should not really happen } # Want to add cookies corresponding to the most specific paths # first (i.e. longest path first) my $path; for $path (sort {length($b) <=> length($a) } keys %$cookies) { if (index($req_path, $path) != 0) { next; } my($key,$array); while (($key,$array) = each %{$cookies->{$path}}) { my($version,$val,$port,$path_spec,$secure,$expires) = @$array; if ($secure && !$secure_request) { next; } if ($expires && $expires < $now) { next; } if ($port) { my $found; if ($port =~ s/^_//) { # The corresponding Set-Cookie attribute was empty $found++ if $port eq $req_port; $port = ""; } else { my $p; for $p (split(/,/, $port)) { $found++, last if $p eq $req_port; } } unless ($found) { next; } } if ($version > 0 && $netscape_only) { next; } # set version number of cookie header. # XXX: What should it be if multiple matching # Set-Cookie headers have different versions themselves if (!$set_ver++) { if ($version >= 1) { push(@cval, "\$Version=$version"); } elsif (!$self->{hide_cookie2}) { $request->header(Cookie2 => '$Version="1"'); } } # do we need to quote the value if ($val =~ /\W/ && $version) { $val =~ s/([\\\"])/\\$1/g; $val = qq("$val"); } # and finally remember this cookie push(@cval, "$key=$val"); if ($version >= 1) { push(@cval, qq(\$Path="$path")) if $path_spec; push(@cval, qq(\$Domain="$domain")) if $domain =~ /^\./; if (defined $port) { my $p = '$Port'; $p .= qq(="$port") if length $port; push(@cval, $p); } } } } } continue { # Try with a more general domain, alternately stripping # leading name components and leading dots. When this # results in a domain with no leading dot, it is for # Netscape cookie compatibility only: # # a.b.c.net Any cookie # .b.c.net Any cookie # b.c.net Netscape cookie only # .c.net Any cookie if ($domain =~ s/^\.+//) { $netscape_only = 1; } else { $domain =~ s/[^.]*//; $netscape_only = 0; } } if (@cval) { if (my $old = $request->header("Cookie")) { unshift(@cval, $old); } $request->header(Cookie => join("; ", @cval)); if (my $hash = $request->{_http_cookies}) { %$hash = (map split(/=/, $_, 2), @cval); } } $request; } sub get_cookies { my $self = shift; my $url = shift; $url = "https://$url" unless $url =~ m,^[a-zA-Z][a-zA-Z0-9.+\-]*:,; require HTTP::Request; my $req = HTTP::Request->new(GET => $url); my $cookies = $req->{_http_cookies} = {}; $self->add_cookie_header($req); if (@_) { return map $cookies->{$_}, @_ if wantarray; return $cookies->{$_[0]}; } return $cookies; } sub extract_cookies { my $self = shift; my $response = shift || return; my @set = _split_header_words($response->_header("Set-Cookie2")); my @ns_set = $response->_header("Set-Cookie"); return $response unless @set || @ns_set; # quick exit my $request = $response->request; my $url = $request->uri; my $req_host = _host($request, $url); $req_host = "$req_host.local" unless $req_host =~ /\./; my $req_port = $url->port; my $req_path = _url_path($url); _normalize_path($req_path) if $req_path =~ /%/; if (@ns_set) { # The old Netscape cookie format for Set-Cookie # http://curl.haxx.se/rfc/cookie_spec.html # can for instance contain an unquoted "," in the expires # field, so we have to use this ad-hoc parser. my $now = time(); # Build a hash of cookies that was present in Set-Cookie2 # headers. We need to skip them if we also find them in a # Set-Cookie header. my %in_set2; for (@set) { $in_set2{$_->[0]}++; } my $set; for $set (@ns_set) { $set =~ s/^\s+//; my @cur; my $param; my $expires; my $first_param = 1; for $param (@{_split_text($set)}) { next unless length($param); my($k,$v) = split(/\s*=\s*/, $param, 2); if (defined $v) { $v =~ s/\s+$//; #print "$k => $v\n"; } else { $k =~ s/\s+$//; #print "$k => undef"; } if (!$first_param && lc($k) eq "expires") { my $etime = str2time($v); if (defined $etime) { push(@cur, "Max-Age" => $etime - $now); $expires++; } else { # parse_date can deal with years outside the range of time_t, my($year, $mon, $day, $hour, $min, $sec, $tz) = parse_date($v); if ($year) { my $thisyear = (gmtime)[5] + 1900; if ($year < $thisyear) { push(@cur, "Max-Age" => -1); # any negative value will do $expires++; } elsif ($year >= $thisyear + 10) { # the date is at least 10 years into the future, just replace # it with something approximate push(@cur, "Max-Age" => 10 * 365 * 24 * 60 * 60); $expires++; } } } } elsif (!$first_param && lc($k) eq 'max-age') { $expires++; } elsif (!$first_param && lc($k) =~ /^(?:version|discard|ns-cookie)/) { # ignore } else { push(@cur, $k => $v); } $first_param = 0; } next unless @cur; next if $in_set2{$cur[0]}; # push(@cur, "Port" => $req_port); push(@cur, "Discard" => undef) unless $expires; push(@cur, "Version" => 0); push(@cur, "ns-cookie" => 1); push(@set, \@cur); } } SET_COOKIE: for my $set (@set) { next unless @$set >= 2; my $key = shift @$set; my $val = shift @$set; my %hash; while (@$set) { my $k = shift @$set; my $v = shift @$set; my $lc = lc($k); # don't loose case distinction for unknown fields $k = $lc if $lc =~ /^(?:discard|domain|max-age| path|port|secure|version)$/x; if ($k eq "discard" || $k eq "secure") { $v = 1 unless defined $v; } next if exists $hash{$k}; # only first value is significant $hash{$k} = $v; }; my %orig_hash = %hash; my $version = delete $hash{version}; $version = 1 unless defined($version); my $discard = delete $hash{discard}; my $secure = delete $hash{secure}; my $maxage = delete $hash{'max-age'}; my $ns_cookie = delete $hash{'ns-cookie'}; # Check domain my $domain = delete $hash{domain}; $domain = lc($domain) if defined $domain; if (defined($domain) && $domain ne $req_host && $domain ne ".$req_host") { if ($domain !~ /\./ && $domain ne "local") { next SET_COOKIE; } $domain = ".$domain" unless $domain =~ /^\./; if ($domain =~ /\.\d+$/) { next SET_COOKIE; } my $len = length($domain); unless (substr($req_host, -$len) eq $domain) { next SET_COOKIE; } my $hostpre = substr($req_host, 0, length($req_host) - $len); if ($hostpre =~ /\./ && !$ns_cookie) { next SET_COOKIE; } } else { $domain = $req_host; } my $path = delete $hash{path}; my $path_spec; if (defined $path && $path ne '') { $path_spec++; _normalize_path($path) if $path =~ /%/; if (!$ns_cookie && substr($req_path, 0, length($path)) ne $path) { next SET_COOKIE; } } else { $path = $req_path; $path =~ s,/[^/]*$,,; $path = "/" unless length($path); } my $port; if (exists $hash{port}) { $port = delete $hash{port}; if (defined $port) { $port =~ s/\s+//g; my $found; for my $p (split(/,/, $port)) { unless ($p =~ /^\d+$/) { next SET_COOKIE; } $found++ if $p eq $req_port; } unless ($found) { next SET_COOKIE; } } else { $port = "_$req_port"; } } $self->set_cookie($version,$key,$val,$path,$domain,$port,$path_spec,$secure,$maxage,$discard, \%hash) if $self->set_cookie_ok(\%orig_hash); } $response; } sub set_cookie_ok { 1; } sub set_cookie { my $self = shift; my($version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, $rest) = @_; # path and key can not be empty (key can't start with '$') return $self if !defined($path) || $path !~ m,^/, || !defined($key) || $key =~ m,^\$,; # ensure legal port if (defined $port) { return $self unless $port =~ /^_?\d+(?:,\d+)*$/; } my $expires; if (defined $maxage) { if ($maxage <= 0) { delete $self->{COOKIES}{$domain}{$path}{$key}; return $self; } $expires = time() + $maxage; } $version = 0 unless defined $version; my @array = ($version, $val,$port, $path_spec, $secure, $expires, $discard); push(@array, {%$rest}) if defined($rest) && %$rest; # trim off undefined values at end pop(@array) while !defined $array[-1]; $self->{COOKIES}{$domain}{$path}{$key} = \@array; $self; } sub save { my $self = shift; my %args = ( file => $self->{'file'}, ignore_discard => $self->{'ignore_discard'}, @_ == 1 ? ( file => $_[0] ) : @_ ); Carp::croak('Unexpected argument to save method') if keys %args > 2; my $file = $args{'file'} || return; open(my $fh, '>', $file) or die "Can't open $file: $!"; print {$fh} "#LWP-Cookies-1.0\n"; print {$fh} $self->as_string(!$args{'ignore_discard'}); close $fh or die "Can't close $file: $!"; 1; } sub load { my $self = shift; my $file = shift || $self->{'file'} || return; local $/ = "\n"; # make sure we got standard record separator open(my $fh, '<', $file) or return; # check that we have the proper header my $magic = <$fh>; chomp $magic; unless ($magic =~ /^#LWP-Cookies-\d+\.\d+/) { warn "$file does not seem to contain cookies"; return; } # go through the file while (my $line = <$fh>) { chomp $line; next unless $line =~ s/^Set-Cookie3:\s*//; my $cookie; for $cookie (_split_header_words($line)) { my($key,$val) = splice(@$cookie, 0, 2); my %hash; while (@$cookie) { my $k = shift @$cookie; my $v = shift @$cookie; $hash{$k} = $v; } my $version = delete $hash{version}; my $path = delete $hash{path}; my $domain = delete $hash{domain}; my $port = delete $hash{port}; my $expires = str2time(delete $hash{expires}); my $path_spec = exists $hash{path_spec}; delete $hash{path_spec}; my $secure = exists $hash{secure}; delete $hash{secure}; my $discard = exists $hash{discard}; delete $hash{discard}; my @array = ($version, $val, $port, $path_spec, $secure, $expires, $discard); push(@array, \%hash) if %hash; $self->{COOKIES}{$domain}{$path}{$key} = \@array; } } 1; } sub revert { my $self = shift; $self->clear->load; $self; } sub clear { my $self = shift; if (@_ == 0) { $self->{COOKIES} = {}; } elsif (@_ == 1) { delete $self->{COOKIES}{$_[0]}; } elsif (@_ == 2) { delete $self->{COOKIES}{$_[0]}{$_[1]}; } elsif (@_ == 3) { delete $self->{COOKIES}{$_[0]}{$_[1]}{$_[2]}; } else { require Carp; Carp::carp('Usage: $c->clear([domain [,path [,key]]])'); } $self; } sub clear_temporary_cookies { my($self) = @_; $self->scan(sub { if($_[9] or # "Discard" flag set not $_[8]) { # No expire field? $_[8] = -1; # Set the expire/max_age field $self->set_cookie(@_); # Clear the cookie } }); } sub DESTROY { my $self = shift; local($., $@, $!, $^E, $?); $self->save if $self->{'autosave'}; } sub scan { my($self, $cb) = @_; my($domain,$path,$key); for $domain (sort keys %{$self->{COOKIES}}) { for $path (sort keys %{$self->{COOKIES}{$domain}}) { for $key (sort keys %{$self->{COOKIES}{$domain}{$path}}) { my($version,$val,$port,$path_spec, $secure,$expires,$discard,$rest) = @{$self->{COOKIES}{$domain}{$path}{$key}}; $rest = {} unless defined($rest); &$cb($version,$key,$val,$path,$domain,$port, $path_spec,$secure,$expires,$discard,$rest); } } } } sub as_string { my($self, $skip_discard) = @_; my @res; $self->scan(sub { my($version,$key,$val,$path,$domain,$port, $path_spec,$secure,$expires,$discard,$rest) = @_; return if $discard && $skip_discard; my @h = ($key, $val); push(@h, "path", $path); push(@h, "domain" => $domain); push(@h, "port" => $port) if defined $port; push(@h, "path_spec" => undef) if $path_spec; push(@h, "secure" => undef) if $secure; push(@h, "expires" => HTTP::Date::time2isoz($expires)) if $expires; push(@h, "discard" => undef) if $discard; my $k; for $k (sort keys %$rest) { push(@h, $k, $rest->{$k}); } push(@h, "version" => $version); push(@res, "Set-Cookie3: " . join_header_words(\@h)); }); join("\n", @res, ""); } sub _host { my($request, $url) = @_; if (my $h = $request->header("Host")) { $h =~ s/:\d+$//; # might have a port as well return lc($h); } return lc($url->host); } sub _url_path { my $url = shift; my $path; if($url->can('epath')) { $path = $url->epath; # URI::URL method } else { $path = $url->path; # URI::_generic method } $path = "/" unless length $path; $path; } sub _normalize_path # so that plain string compare can be used { my $x; $_[0] =~ s/%([0-9a-fA-F][0-9a-fA-F])/ $x = uc($1); $x eq "2F" || $x eq "25" ? "%$x" : pack("C", hex($x)); /eg; $_[0] =~ s/([\0-\x20\x7f-\xff])/sprintf("%%%02X",ord($1))/eg; } # deals with splitting values by ; and the fact that they could # be in quotes which can also have escaping. sub _split_text { my $val = shift; my @vals = grep { $_ ne q{} } split(/([;\\"])/, $val); my @chunks; # divide it up into chunks to be processed. my $in_string = 0; my @current_string; for(my $i = 0; $i < @vals; $i++) { my $chunk = $vals[$i]; if($in_string) { if($chunk eq q{\\}) { # don't care about next char probably. # having said that, probably need to be appending to the chunks # just dropping this. $i++; if($i < @vals) { push @current_string, $vals[$i]; } } elsif($chunk eq q{"}) { $in_string = 0; } else { push @current_string, $chunk; } } else { if($chunk eq q{"}) { $in_string = 1; } elsif($chunk eq q{;}) { push @chunks, join(q{}, @current_string); @current_string = (); } else { push @current_string, $chunk; } } } push @chunks, join(q{}, @current_string) if @current_string; s/^\s+// for @chunks; return \@chunks; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Cookies - HTTP cookie jars =head1 VERSION version 6.11 =head1 SYNOPSIS use HTTP::Cookies; $cookie_jar = HTTP::Cookies->new( file => "$ENV{'HOME'}/lwp_cookies.dat", autosave => 1, ); use LWP; my $browser = LWP::UserAgent->new; $browser->cookie_jar($cookie_jar); Or for an empty and temporary cookie jar: use LWP; my $browser = LWP::UserAgent->new; $browser->cookie_jar( {} ); =head1 DESCRIPTION This class is for objects that represent a "cookie jar" -- that is, a database of all the HTTP cookies that a given LWP::UserAgent object knows about. Cookies are a general mechanism which server side connections can use to both store and retrieve information on the client side of the connection. For more information about cookies refer to L and L. This module also implements the new style cookies described in L. The two variants of cookies are supposed to be able to coexist happily. Instances of the class I are able to store a collection of Set-Cookie2: and Set-Cookie: headers and are able to use this information to initialize Cookie-headers in I objects. The state of a I object can be saved in and restored from files. =head1 LIMITATIONS This module does not support L<< Public Suffix|https://publicsuffix.org/ >> encouraged by a more recent standard, L<< RFC 6265|https://tools.ietf.org/html/rfc6265 >>. This module's shortcomings mean that a malicious Web site can set cookies to track your user agent across all sites under a top level domain. See F<< t/publicsuffix.t >> in this module's distribution for details. L<< HTTP::CookieJar::LWP >> supports Public Suffix, but only provides a limited subset of this module's functionality and L<< does not support|HTTP::CookieJar/LIMITATIONS-AND-CAVEATS >> standards older than I. =head1 METHODS The following methods are provided: =over 4 =item $cookie_jar = HTTP::Cookies->new The constructor takes hash style parameters. The following parameters are recognized: file: name of the file to restore cookies from and save cookies to autosave: save during destruction (bool) ignore_discard: save even cookies that are requested to be discarded (bool) hide_cookie2: do not add Cookie2 header to requests Future parameters might include (not yet implemented): max_cookies 300 max_cookies_per_domain 20 max_cookie_size 4096 no_cookies list of domain names that we never return cookies to =item $cookie_jar->get_cookies( $url_or_domain ) =item $cookie_jar->get_cookies( $url_or_domain, $cookie_key,... ) Returns a hash of the cookies that applies to the given URL. If a domainname is given as argument, then a prefix of "https://" is assumed. If one or more $cookie_key parameters are provided return the given values, or C if the cookie isn't available. =item $cookie_jar->add_cookie_header( $request ) The add_cookie_header() method will set the appropriate Cookie:-header for the I object given as argument. The $request must have a valid url attribute before this method is called. =item $cookie_jar->extract_cookies( $response ) The extract_cookies() method will look for Set-Cookie: and Set-Cookie2: headers in the I object passed as argument. Any of these headers that are found are used to update the state of the $cookie_jar. =item $cookie_jar->set_cookie( $version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest ) The set_cookie() method updates the state of the $cookie_jar. The $key, $val, $domain, $port and $path arguments are strings. The $path_spec, $secure, $discard arguments are boolean values. The $maxage value is a number indicating number of seconds that this cookie will live. A value of $maxage <= 0 will delete this cookie. The $version argument sets the version of the cookie; the default value is 0 ( original Netscape spec ). Setting $version to another value indicates the RFC to which the cookie conforms (e.g. version 1 for RFC 2109). %rest defines various other attributes like "Comment" and "CommentURL". =item $cookie_jar->save =item $cookie_jar->save( $file ) =item $cookie_jar->save( file => $file, ignore_discard => $ignore_discard ) This method saves the state of the $cookie_jar to a file. The state can then be restored later using the load() method. If a filename is not specified we will use the name specified during construction. If the $ignore_discard value is true (or not specified, but attribute I was set at cookie jar construction), then we will even save cookies that are marked to be discarded. The default is to save a sequence of "Set-Cookie3" lines. "Set-Cookie3" is a proprietary LWP format, not known to be compatible with any browser. The I sub-class can be used to save in a format compatible with Netscape. =item $cookie_jar->load =item $cookie_jar->load( $file ) This method reads the cookies from the file and adds them to the $cookie_jar. The file must be in the format written by the save() method. =item $cookie_jar->revert This method empties the $cookie_jar and re-loads the $cookie_jar from the last save file. =item $cookie_jar->clear =item $cookie_jar->clear( $domain ) =item $cookie_jar->clear( $domain, $path ) =item $cookie_jar->clear( $domain, $path, $key ) Invoking this method without arguments will empty the whole $cookie_jar. If given a single argument only cookies belonging to that domain will be removed. If given two arguments, cookies belonging to the specified path within that domain are removed. If given three arguments, then the cookie with the specified key, path and domain is removed. =item $cookie_jar->clear_temporary_cookies Discard all temporary cookies. Scans for all cookies in the jar with either no expire field or a true C flag. To be called when the user agent shuts down according to RFC 2965. =item $cookie_jar->scan( \&callback ) The argument is a subroutine that will be invoked for each cookie stored in the $cookie_jar. The subroutine will be invoked with the following arguments: 0 version 1 key 2 val 3 path 4 domain 5 port 6 path_spec 7 secure 8 expires 9 discard 10 hash =item $cookie_jar->as_string =item $cookie_jar->as_string( $skip_discardables ) The as_string() method will return the state of the $cookie_jar represented as a sequence of "Set-Cookie3" header lines separated by "\n". If $skip_discardables is TRUE, it will not return lines for cookies with the I attribute. =back =head1 SEE ALSO L, L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2002 by Gisle Aas. 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: HTTP cookie jars PK!new(@$header); } else { $header = $header->clone; } } else { $header = HTTP::Headers->new; } if (defined $content) { _utf8_downgrade($content); } else { $content = ''; } bless { '_headers' => $header, '_content' => $content, '_max_body_size' => $HTTP::Message::MAXIMUM_BODY_SIZE, }, $class; } sub parse { my($class, $str) = @_; my @hdr; while (1) { if ($str =~ s/^([^\s:]+)[ \t]*: ?(.*)\n?//) { push(@hdr, $1, $2); $hdr[-1] =~ s/\r\z//; } elsif (@hdr && $str =~ s/^([ \t].*)\n?//) { $hdr[-1] .= "\n$1"; $hdr[-1] =~ s/\r\z//; } else { $str =~ s/^\r?\n//; last; } } local $HTTP::Headers::TRANSLATE_UNDERSCORE; new($class, \@hdr, $str); } sub clone { my $self = shift; my $clone = HTTP::Message->new($self->headers, $self->content); $clone->protocol($self->protocol); $clone; } sub clear { my $self = shift; $self->{_headers}->clear; $self->content(""); delete $self->{_parts}; return; } sub protocol { shift->_elem('_protocol', @_); } sub headers { my $self = shift; # recalculation of _content might change headers, so we # need to force it now $self->_content unless exists $self->{_content}; $self->{_headers}; } sub headers_as_string { shift->headers->as_string(@_); } sub content { my $self = $_[0]; if (defined(wantarray)) { $self->_content unless exists $self->{_content}; my $old = $self->{_content}; $old = $$old if ref($old) eq "SCALAR"; &_set_content if @_ > 1; return $old; } if (@_ > 1) { &_set_content; } else { Carp::carp("Useless content call in void context") if $^W; } } sub _set_content { my $self = $_[0]; _utf8_downgrade($_[1]); if (!ref($_[1]) && ref($self->{_content}) eq "SCALAR") { ${$self->{_content}} = defined( $_[1] ) ? $_[1] : ''; } else { die "Can't set content to be a scalar reference" if ref($_[1]) eq "SCALAR"; $self->{_content} = defined( $_[1] ) ? $_[1] : ''; delete $self->{_content_ref}; } delete $self->{_parts} unless $_[2]; } sub add_content { my $self = shift; $self->_content unless exists $self->{_content}; my $chunkref = \$_[0]; $chunkref = $$chunkref if ref($$chunkref); # legacy _utf8_downgrade($$chunkref); my $ref = ref($self->{_content}); if (!$ref) { $self->{_content} .= $$chunkref; } elsif ($ref eq "SCALAR") { ${$self->{_content}} .= $$chunkref; } else { Carp::croak("Can't append to $ref content"); } delete $self->{_parts}; } sub add_content_utf8 { my($self, $buf) = @_; utf8::upgrade($buf); utf8::encode($buf); $self->add_content($buf); } sub content_ref { my $self = shift; $self->_content unless exists $self->{_content}; delete $self->{_parts}; my $old = \$self->{_content}; my $old_cref = $self->{_content_ref}; if (@_) { my $new = shift; Carp::croak("Setting content_ref to a non-ref") unless ref($new); delete $self->{_content}; # avoid modifying $$old $self->{_content} = $new; $self->{_content_ref}++; } $old = $$old if $old_cref; return $old; } sub content_charset { my $self = shift; if (my $charset = $self->content_type_charset) { return $charset; } # time to start guessing my $cref = $self->decoded_content(ref => 1, charset => "none"); # Unicode BOM for ($$cref) { return "UTF-8" if /^\xEF\xBB\xBF/; return "UTF-32LE" if /^\xFF\xFE\x00\x00/; return "UTF-32BE" if /^\x00\x00\xFE\xFF/; return "UTF-16LE" if /^\xFF\xFE/; return "UTF-16BE" if /^\xFE\xFF/; } if ($self->content_is_xml) { # http://www.w3.org/TR/2006/REC-xml-20060816/#sec-guessing # XML entity not accompanied by external encoding information and not # in UTF-8 or UTF-16 encoding must begin with an XML encoding declaration, # in which the first characters must be ')/) { if ($1 =~ /\sencoding\s*=\s*(["'])(.*?)\1/) { my $enc = $2; $enc =~ s/^\s+//; $enc =~ s/\s+\z//; return $enc if $enc; } } } return "UTF-8"; } elsif ($self->content_is_html) { # look for or # http://dev.w3.org/html5/spec/Overview.html#determining-the-character-encoding require IO::HTML; # Use relaxed search to match previous versions of HTTP::Message: my $encoding = IO::HTML::find_charset_in($$cref, { encoding => 1, need_pragma => 0 }); return $encoding->mime_name if $encoding; } elsif ($self->content_type eq "application/json") { for ($$cref) { # RFC 4627, ch 3 return "UTF-32BE" if /^\x00\x00\x00./s; return "UTF-32LE" if /^.\x00\x00\x00/s; return "UTF-16BE" if /^\x00.\x00./s; return "UTF-16LE" if /^.\x00.\x00/s; return "UTF-8"; } } if ($self->content_type =~ /^text\//) { for ($$cref) { if (length) { return "US-ASCII" unless /[\x80-\xFF]/; require Encode; eval { Encode::decode_utf8($_, Encode::FB_CROAK() | Encode::LEAVE_SRC()); }; return "UTF-8" unless $@; return "ISO-8859-1"; } } } return undef; } sub max_body_size { my $self = $_[0]; my $old = $self->{_max_body_size}; $self->_set_max_body_size($_[1]) if @_ > 1; return $old; } sub _set_max_body_size { my $self = $_[0]; $self->{_max_body_size} = $_[1]; } sub decoded_content { my($self, %opt) = @_; my $content_ref; my $content_ref_iscopy; eval { $content_ref = $self->content_ref; die "Can't decode ref content" if ref($content_ref) ne "SCALAR"; my $content_limit = exists $opt{ max_body_size } ? $opt{ max_body_size } : defined $self->max_body_size ? $self->max_body_size : undef ; my %limiter_options; if( defined $content_limit ) { %limiter_options = (LimitOutput => 1, Bufsize => $content_limit); }; if (my $h = $self->header("Content-Encoding")) { $h =~ s/^\s+//; $h =~ s/\s+$//; for my $ce (reverse split(/\s*,\s*/, lc($h))) { next unless $ce; next if $ce eq "identity" || $ce eq "none"; if ($ce eq "gzip" || $ce eq "x-gzip") { require Compress::Raw::Zlib; # 'WANT_GZIP_OR_ZLIB', 'Z_BUF_ERROR'; if( ! $content_ref_iscopy and keys %limiter_options) { # Create a copy of the input because Zlib will overwrite it # :-( my $input = "$$content_ref"; $content_ref = \$input; $content_ref_iscopy++; }; my ($i, $status) = Compress::Raw::Zlib::Inflate->new( %limiter_options, ConsumeInput => 0, # overridden by Zlib if we have %limiter_options :-( WindowBits => Compress::Raw::Zlib::WANT_GZIP_OR_ZLIB(), ); my $res = $i->inflate( $content_ref, \my $output ); $res == Compress::Raw::Zlib::Z_BUF_ERROR() and Carp::croak("Decoded content would be larger than $content_limit octets"); $res == Compress::Raw::Zlib::Z_OK() or $res == Compress::Raw::Zlib::Z_STREAM_END() or die "Can't gunzip content: $res"; $content_ref = \$output; $content_ref_iscopy++; } elsif ($ce eq 'br') { require IO::Uncompress::Brotli; my $bro = IO::Uncompress::Brotli->create; my $output; if( defined $content_limit ) { $output = eval { $bro->decompress( $$content_ref, $content_limit ); } } else { $output = eval { $bro->decompress($$content_ref) }; } $@ and die "Can't unbrotli content: $@"; $content_ref = \$output; $content_ref_iscopy++; } elsif ($ce eq "x-bzip2" or $ce eq "bzip2") { require Compress::Raw::Bzip2; if( ! $content_ref_iscopy ) { # Create a copy of the input because Bzlib2 will overwrite it # :-( my $input = "$$content_ref"; $content_ref = \$input; $content_ref_iscopy++; }; my ($i, $status) = Compress::Raw::Bunzip2->new( 1, # appendInput 0, # consumeInput 0, # small $limiter_options{ LimitOutput } || 0, ); my $output; $output = "\0" x $limiter_options{ Bufsize } if $limiter_options{ Bufsize }; my $res = $i->bzinflate( $content_ref, \$output ); $res == Compress::Raw::Bzip2::BZ_OUTBUFF_FULL() and Carp::croak("Decoded content would be larger than $content_limit octets"); $res == Compress::Raw::Bzip2::BZ_OK() or $res == Compress::Raw::Bzip2::BZ_STREAM_END() or die "Can't bunzip content: $res"; $content_ref = \$output; $content_ref_iscopy++; } elsif ($ce eq "deflate") { require IO::Uncompress::Inflate; my $output; my $status = IO::Uncompress::Inflate::inflate($content_ref, \$output, Transparent => 0); my $error = $IO::Uncompress::Inflate::InflateError; unless ($status) { # "Content-Encoding: deflate" is supposed to mean the # "zlib" format of RFC 1950, but Microsoft got that # wrong, so some servers sends the raw compressed # "deflate" data. This tries to inflate this format. $output = undef; require IO::Uncompress::RawInflate; unless (IO::Uncompress::RawInflate::rawinflate($content_ref, \$output)) { $self->push_header("Client-Warning" => "Could not raw inflate content: $IO::Uncompress::RawInflate::RawInflateError"); $output = undef; } } die "Can't inflate content: $error" unless defined $output; $content_ref = \$output; $content_ref_iscopy++; } elsif ($ce eq "compress" || $ce eq "x-compress") { die "Can't uncompress content"; } elsif ($ce eq "base64") { # not really C-T-E, but should be harmless require MIME::Base64; $content_ref = \MIME::Base64::decode($$content_ref); $content_ref_iscopy++; } elsif ($ce eq "quoted-printable") { # not really C-T-E, but should be harmless require MIME::QuotedPrint; $content_ref = \MIME::QuotedPrint::decode($$content_ref); $content_ref_iscopy++; } else { die "Don't know how to decode Content-Encoding '$ce'"; } } } if ($self->content_is_text || (my $is_xml = $self->content_is_xml)) { my $charset = lc( $opt{charset} || $self->content_type_charset || $opt{default_charset} || $self->content_charset || "ISO-8859-1" ); if ($charset eq "none") { # leave it as is } elsif ($charset eq "us-ascii" || $charset eq "iso-8859-1") { if ($$content_ref =~ /[^\x00-\x7F]/ && defined &utf8::upgrade) { unless ($content_ref_iscopy) { my $copy = $$content_ref; $content_ref = \$copy; $content_ref_iscopy++; } utf8::upgrade($$content_ref); } } else { require Encode; eval { $content_ref = \Encode::decode($charset, $$content_ref, ($opt{charset_strict} ? Encode::FB_CROAK() : 0) | Encode::LEAVE_SRC()); }; if ($@) { my $retried; if ($@ =~ /^Unknown encoding/) { my $alt_charset = lc($opt{alt_charset} || ""); if ($alt_charset && $charset ne $alt_charset) { # Retry decoding with the alternative charset $content_ref = \Encode::decode($alt_charset, $$content_ref, ($opt{charset_strict} ? Encode::FB_CROAK() : 0) | Encode::LEAVE_SRC()) unless $alt_charset eq "none"; $retried++; } } die unless $retried; } die "Encode::decode() returned undef improperly" unless defined $$content_ref; if ($is_xml) { # Get rid of the XML encoding declaration if present $$content_ref =~ s/^\x{FEFF}//; if ($$content_ref =~ /^(\s*<\?xml[^\x00]*?\?>)/) { substr($$content_ref, 0, length($1)) =~ s/\sencoding\s*=\s*(["']).*?\1//; } } } } }; if ($@) { Carp::croak($@) if $opt{raise_error}; return undef; } return $opt{ref} ? $content_ref : $$content_ref; } sub decodable { # should match the Content-Encoding values that decoded_content can deal with my $self = shift; my @enc; local $@; # XXX preferably we should determine if the modules are available without loading # them here eval { require Compress::Raw::Zlib; push(@enc, "gzip", "x-gzip"); }; eval { require IO::Uncompress::Inflate; require IO::Uncompress::RawInflate; push(@enc, "deflate"); }; eval { require Compress::Raw::Bzip2; push(@enc, "x-bzip2", "bzip2"); }; eval { require IO::Uncompress::Brotli; push(@enc, 'br'); }; # we don't care about announcing the 'identity', 'base64' and # 'quoted-printable' stuff return wantarray ? @enc : join(", ", @enc); } sub decode { my $self = shift; return 1 unless $self->header("Content-Encoding"); if (defined(my $content = $self->decoded_content(charset => "none"))) { $self->remove_header("Content-Encoding", "Content-Length", "Content-MD5"); $self->content($content); return 1; } return 0; } sub encode { my($self, @enc) = @_; Carp::croak("Can't encode multipart/* messages") if $self->content_type =~ m,^multipart/,; Carp::croak("Can't encode message/* messages") if $self->content_type =~ m,^message/,; return 1 unless @enc; # nothing to do my $content = $self->content; for my $encoding (@enc) { if ($encoding eq "identity" || $encoding eq "none") { # nothing to do } elsif ($encoding eq "base64") { require MIME::Base64; $content = MIME::Base64::encode($content); } elsif ($encoding eq "gzip" || $encoding eq "x-gzip") { require IO::Compress::Gzip; my $output; IO::Compress::Gzip::gzip(\$content, \$output, Minimal => 1) or die "Can't gzip content: $IO::Compress::Gzip::GzipError"; $content = $output; } elsif ($encoding eq "deflate") { require IO::Compress::Deflate; my $output; IO::Compress::Deflate::deflate(\$content, \$output) or die "Can't deflate content: $IO::Compress::Deflate::DeflateError"; $content = $output; } elsif ($encoding eq "x-bzip2" || $encoding eq "bzip2") { require IO::Compress::Bzip2; my $output; IO::Compress::Bzip2::bzip2(\$content, \$output) or die "Can't bzip2 content: $IO::Compress::Bzip2::Bzip2Error"; $content = $output; } elsif ($encoding eq "br") { require IO::Compress::Brotli; my $output; eval { $output = IO::Compress::Brotli::bro($content) } or die "Can't brotli content: $@"; $content = $output; } elsif ($encoding eq "rot13") { # for the fun of it $content =~ tr/A-Za-z/N-ZA-Mn-za-m/; } else { return 0; } } my $h = $self->header("Content-Encoding"); unshift(@enc, $h) if $h; $self->header("Content-Encoding", join(", ", @enc)); $self->remove_header("Content-Length", "Content-MD5"); $self->content($content); return 1; } sub as_string { my($self, $eol) = @_; $eol = "\n" unless defined $eol; # The calculation of content might update the headers # so we need to do that first. my $content = $self->content; return join("", $self->{'_headers'}->as_string($eol), $eol, $content, (@_ == 1 && length($content) && $content !~ /\n\z/) ? "\n" : "", ); } sub dump { my($self, %opt) = @_; my $content = $self->content; my $chopped = 0; if (!ref($content)) { my $maxlen = $opt{maxlength}; $maxlen = 512 unless defined($maxlen); if ($maxlen && length($content) > $maxlen * 1.1 + 3) { $chopped = length($content) - $maxlen; $content = substr($content, 0, $maxlen) . "..."; } $content =~ s/\\/\\\\/g; $content =~ s/\t/\\t/g; $content =~ s/\r/\\r/g; # no need for 3 digits in escape for these $content =~ s/([\0-\11\13-\037])(?!\d)/sprintf('\\%o',ord($1))/eg; $content =~ s/([\0-\11\13-\037\177-\377])/sprintf('\\x%02X',ord($1))/eg; $content =~ s/([^\12\040-\176])/sprintf('\\x{%X}',ord($1))/eg; # remaining whitespace $content =~ s/( +)\n/("\\40" x length($1)) . "\n"/eg; $content =~ s/(\n+)\n/("\\n" x length($1)) . "\n"/eg; $content =~ s/\n\z/\\n/; my $no_content = $opt{no_content}; $no_content = "(no content)" unless defined $no_content; if ($content eq $no_content) { # escape our $no_content marker $content =~ s/^(.)/sprintf('\\x%02X',ord($1))/eg; } elsif ($content eq "") { $content = $no_content; } } my @dump; push(@dump, $opt{preheader}) if $opt{preheader}; push(@dump, $self->{_headers}->as_string, $content); push(@dump, "(+ $chopped more bytes not shown)") if $chopped; my $dump = join("\n", @dump, ""); $dump =~ s/^/$opt{prefix}/gm if $opt{prefix}; print $dump unless defined wantarray; return $dump; } # allow subclasses to override what will handle individual parts sub _part_class { return __PACKAGE__; } sub parts { my $self = shift; if (defined(wantarray) && (!exists $self->{_parts} || ref($self->{_content}) eq "SCALAR")) { $self->_parts; } my $old = $self->{_parts}; if (@_) { my @parts = map { ref($_) eq 'ARRAY' ? @$_ : $_ } @_; my $ct = $self->content_type || ""; if ($ct =~ m,^message/,) { Carp::croak("Only one part allowed for $ct content") if @parts > 1; } elsif ($ct !~ m,^multipart/,) { $self->remove_content_headers; $self->content_type("multipart/mixed"); } $self->{_parts} = \@parts; _stale_content($self); } return @$old if wantarray; return $old->[0]; } sub add_part { my $self = shift; if (($self->content_type || "") !~ m,^multipart/,) { my $p = $self->_part_class->new( $self->remove_content_headers, $self->content(""), ); $self->content_type("multipart/mixed"); $self->{_parts} = []; if ($p->headers->header_field_names || $p->content ne "") { push(@{$self->{_parts}}, $p); } } elsif (!exists $self->{_parts} || ref($self->{_content}) eq "SCALAR") { $self->_parts; } push(@{$self->{_parts}}, @_); _stale_content($self); return; } sub _stale_content { my $self = shift; if (ref($self->{_content}) eq "SCALAR") { # must recalculate now $self->_content; } else { # just invalidate cache delete $self->{_content}; delete $self->{_content_ref}; } } # delegate all other method calls to the headers object. our $AUTOLOAD; sub AUTOLOAD { my ( $package, $method ) = $AUTOLOAD =~ m/\A(.+)::([^:]*)\z/; my $code = $_[0]->can($method); Carp::croak( qq(Can't locate object method "$method" via package "$package")) unless $code; goto &$code; } sub can { my ( $self, $method ) = @_; if ( my $own_method = $self->SUPER::can($method) ) { return $own_method; } my $headers = ref($self) ? $self->headers : 'HTTP::Headers'; if ( $headers->can($method) ) { # We create the function here so that it will not need to be # autoloaded or recreated the next time. no strict 'refs'; *$method = sub { local $Carp::Internal{ +__PACKAGE__ } = 1; shift->headers->$method(@_); }; return \&$method; } return undef; } sub DESTROY { } # avoid AUTOLOADing it # Private method to access members in %$self sub _elem { my $self = shift; my $elem = shift; my $old = $self->{$elem}; $self->{$elem} = $_[0] if @_; return $old; } # Create private _parts attribute from current _content sub _parts { my $self = shift; my $ct = $self->content_type; if ($ct =~ m,^multipart/,) { require HTTP::Headers::Util; my @h = HTTP::Headers::Util::split_header_words($self->header("Content-Type")); die "Assert" unless @h; my %h = @{$h[0]}; if (defined(my $b = $h{boundary})) { my $str = $self->content; $str =~ s/\r?\n--\Q$b\E--.*//s; if ($str =~ s/(^|.*?\r?\n)--\Q$b\E\r?\n//s) { $self->{_parts} = [map $self->_part_class->parse($_), split(/\r?\n--\Q$b\E\r?\n/, $str)] } } } elsif ($ct eq "message/http") { require HTTP::Request; require HTTP::Response; my $content = $self->content; my $class = ($content =~ m,^(HTTP/.*)\n,) ? "HTTP::Response" : "HTTP::Request"; $self->{_parts} = [$class->parse($content)]; } elsif ($ct =~ m,^message/,) { $self->{_parts} = [ $self->_part_class->parse($self->content) ]; } $self->{_parts} ||= []; } # Create private _content attribute from current _parts sub _content { my $self = shift; my $ct = $self->{_headers}->header("Content-Type") || "multipart/mixed"; if ($ct =~ m,^\s*message/,i) { _set_content($self, $self->{_parts}[0]->as_string($CRLF), 1); return; } require HTTP::Headers::Util; my @v = HTTP::Headers::Util::split_header_words($ct); Carp::carp("Multiple Content-Type headers") if @v > 1; @v = @{$v[0]}; my $boundary; my $boundary_index; for (my @tmp = @v; @tmp;) { my($k, $v) = splice(@tmp, 0, 2); if ($k eq "boundary") { $boundary = $v; $boundary_index = @v - @tmp - 1; last; } } my @parts = map $_->as_string($CRLF), @{$self->{_parts}}; my $bno = 0; $boundary = _boundary() unless defined $boundary; CHECK_BOUNDARY: { for (@parts) { if (index($_, $boundary) >= 0) { # must have a better boundary $boundary = _boundary(++$bno); redo CHECK_BOUNDARY; } } } if ($boundary_index) { $v[$boundary_index] = $boundary; } else { push(@v, boundary => $boundary); } $ct = HTTP::Headers::Util::join_header_words(@v); $self->{_headers}->header("Content-Type", $ct); _set_content($self, "--$boundary$CRLF" . join("$CRLF--$boundary$CRLF", @parts) . "$CRLF--$boundary--$CRLF", 1); } sub _boundary { my $size = shift || return "xYzZY"; require MIME::Base64; my $b = MIME::Base64::encode(join("", map chr(rand(256)), 1..$size*3), ""); $b =~ s/[\W]/X/g; # ensure alnum only $b; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Message - HTTP style message (base class) =head1 VERSION version 6.45 =head1 SYNOPSIS use parent 'HTTP::Message'; =head1 DESCRIPTION An C object contains some headers and a content body. The following methods are available: =over 4 =item $mess = HTTP::Message->new =item $mess = HTTP::Message->new( $headers ) =item $mess = HTTP::Message->new( $headers, $content ) This constructs a new message object. Normally you would want construct C or C objects instead. The optional $header argument should be a reference to an C object or a plain array reference of key/value pairs. If an C object is provided then a copy of it will be embedded into the constructed message, i.e. it will not be owned and can be modified afterwards without affecting the message. The optional $content argument should be a string of bytes. =item $mess = HTTP::Message->parse( $str ) This constructs a new message object by parsing the given string. =item $mess->headers Returns the embedded C object. =item $mess->headers_as_string =item $mess->headers_as_string( $eol ) Call the as_string() method for the headers in the message. This will be the same as $mess->headers->as_string but it will make your program a whole character shorter :-) =item $mess->content =item $mess->content( $bytes ) The content() method sets the raw content if an argument is given. If no argument is given the content is not touched. In either case the original raw content is returned. If the C argument is given, the content is reset to its default value, which is an empty string. Note that the content should be a string of bytes. Strings in perl can contain characters outside the range of a byte. The C module can be used to turn such strings into a string of bytes. =item $mess->add_content( $bytes ) The add_content() methods appends more data bytes to the end of the current content buffer. =item $mess->add_content_utf8( $string ) The add_content_utf8() method appends the UTF-8 bytes representing the string to the end of the current content buffer. =item $mess->content_ref =item $mess->content_ref( \$bytes ) The content_ref() method will return a reference to content buffer string. It can be more efficient to access the content this way if the content is huge, and it can even be used for direct manipulation of the content, for instance: ${$res->content_ref} =~ s/\bfoo\b/bar/g; This example would modify the content buffer in-place. If an argument is passed it will setup the content to reference some external source. The content() and add_content() methods will automatically dereference scalar references passed this way. For other references content() will return the reference itself and add_content() will refuse to do anything. =item $mess->content_charset This returns the charset used by the content in the message. The charset is either found as the charset attribute of the C header or by guessing. See L for details about how charset is determined. =item $mess->decoded_content( %options ) Returns the content with any C undone and, for textual content (C values starting with C, exactly matching C, or ending with C<+xml>), the raw content's character set decoded into Perl's Unicode string format. Note that this L attempt to decode declared character sets for any other content types like C or C. If the C or C of the message is unknown, this method will fail by returning C. The following options can be specified. =over =item C This overrides the charset parameter for text content. The value C can used to suppress decoding of the charset. =item C This overrides the default charset guessed by content_charset() or if that fails "ISO-8859-1". =item C If decoding fails because the charset specified in the Content-Type header isn't recognized by Perl's Encode module, then try decoding using this charset instead of failing. The C might be specified as C to simply return the string without any decoding of charset as alternative. =item C Abort decoding if malformed characters is found in the content. By default you get the substitution character ("\x{FFFD}") in place of malformed characters. =item C If TRUE then raise an exception if not able to decode content. Reason might be that the specified C or C is not supported. If this option is FALSE, then decoded_content() will return C on errors, but will still set $@. =item C If TRUE then a reference to decoded content is returned. This might be more efficient in cases where the decoded content is identical to the raw content as no data copying is required in this case. =back =item $mess->decodable =item HTTP::Message::decodable() This returns the encoding identifiers that decoded_content() can process. In scalar context returns a comma separated string of identifiers. This value is suitable for initializing the C request header field. =item $mess->decode This method tries to replace the content of the message with the decoded version and removes the C header. Returns TRUE if successful and FALSE if not. If the message does not have a C header this method does nothing and returns TRUE. Note that the content of the message is still bytes after this method has been called and you still need to call decoded_content() if you want to process its content as a string. =item $mess->encode( $encoding, ... ) Apply the given encodings to the content of the message. Returns TRUE if successful. The "identity" (non-)encoding is always supported; other currently supported encodings, subject to availability of required additional modules, are "gzip", "deflate", "x-bzip2", "base64" and "br". A successful call to this function will set the C header. Note that C or C messages can't be encoded and this method will croak if you try. =item $mess->parts =item $mess->parts( @parts ) =item $mess->parts( \@parts ) Messages can be composite, i.e. contain other messages. The composite messages have a content type of C or C. This method give access to the contained messages. The argumentless form will return a list of C objects. If the content type of $msg is not C or C then this will return the empty list. In scalar context only the first object is returned. The returned message parts should be regarded as read-only (future versions of this library might make it possible to modify the parent by modifying the parts). If the content type of $msg is C then there will only be one part returned. If the content type is C, then the return value will be either an C or an C object. If a @parts argument is given, then the content of the message will be modified. The array reference form is provided so that an empty list can be provided. The @parts array should contain C objects. The @parts objects are owned by $mess after this call and should not be modified or made part of other messages. When updating the message with this method and the old content type of $mess is not C or C, then the content type is set to C and all other content headers are cleared. This method will croak if the content type is C and more than one part is provided. =item $mess->add_part( $part ) This will add a part to a message. The $part argument should be another C object. If the previous content type of $mess is not C then the old content (together with all content headers) will be made part #1 and the content type made C before the new part is added. The $part object is owned by $mess after this call and should not be modified or made part of other messages. There is no return value. =item $mess->clear Will clear the headers and set the content to the empty string. There is no return value =item $mess->protocol =item $mess->protocol( $proto ) Sets the HTTP protocol used for the message. The protocol() is a string like C or C. =item $mess->clone Returns a copy of the message object. =item $mess->as_string =item $mess->as_string( $eol ) Returns the message formatted as a single string. The optional $eol parameter specifies the line ending sequence to use. The default is "\n". If no $eol is given then as_string will ensure that the returned string is newline terminated (even when the message content is not). No extra newline is appended if an explicit $eol is passed. =item $mess->dump( %opt ) Returns the message formatted as a string. In void context print the string. This differs from C<< $mess->as_string >> in that it escapes the bytes of the content so that it's safe to print them and it limits how much content to print. The escapes syntax used is the same as for Perl's double quoted strings. If there is no content the string "(no content)" is shown in its place. Options to influence the output can be passed as key/value pairs. The following options are recognized: =over =item maxlength => $num How much of the content to show. The default is 512. Set this to 0 for unlimited. If the content is longer then the string is chopped at the limit and the string "...\n(### more bytes not shown)" appended. =item no_content => $str Replaces the "(no content)" marker. =item prefix => $str A string that will be prefixed to each line of the dump. =back =back All methods unknown to C itself are delegated to the C object that is part of every message. This allows convenient access to these methods. Refer to L for details of these methods: $mess->header( $field => $val ) $mess->push_header( $field => $val ) $mess->init_header( $field => $val ) $mess->remove_header( $field ) $mess->remove_content_headers $mess->header_field_names $mess->scan( \&doit ) $mess->date $mess->expires $mess->if_modified_since $mess->if_unmodified_since $mess->last_modified $mess->content_type $mess->content_encoding $mess->content_length $mess->content_language $mess->title $mess->user_agent $mess->server $mess->from $mess->referer $mess->www_authenticate $mess->authorization $mess->proxy_authorization $mess->authorization_basic $mess->proxy_authorization_basic =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: HTTP style message (base class) PK!/_@@perl5/HTTP/Request/Common.pmnu6$package HTTP::Request::Common; use strict; use warnings; our $VERSION = '6.45'; our $DYNAMIC_FILE_UPLOAD ||= 0; # make it defined (don't know why) our $READ_BUFFER_SIZE = 8192; use Exporter 5.57 'import'; our @EXPORT =qw(GET HEAD PUT PATCH POST OPTIONS); our @EXPORT_OK = qw($DYNAMIC_FILE_UPLOAD DELETE); require HTTP::Request; use Carp(); use File::Spec; my $CRLF = "\015\012"; # "\r\n" is not portable sub GET { _simple_req('GET', @_); } sub HEAD { _simple_req('HEAD', @_); } sub DELETE { _simple_req('DELETE', @_); } sub PATCH { request_type_with_data('PATCH', @_); } sub POST { request_type_with_data('POST', @_); } sub PUT { request_type_with_data('PUT', @_); } sub OPTIONS { request_type_with_data('OPTIONS', @_); } sub request_type_with_data { my $type = shift; my $url = shift; my $req = HTTP::Request->new($type => $url); my $content; $content = shift if @_ and ref $_[0]; my($k, $v); while (($k,$v) = splice(@_, 0, 2)) { if (lc($k) eq 'content') { $content = $v; } else { $req->push_header($k, $v); } } my $ct = $req->header('Content-Type'); unless ($ct) { $ct = 'application/x-www-form-urlencoded'; } elsif ($ct eq 'form-data') { $ct = 'multipart/form-data'; } if (ref $content) { if ($ct =~ m,^multipart/form-data\s*(;|$),i) { require HTTP::Headers::Util; my @v = HTTP::Headers::Util::split_header_words($ct); Carp::carp("Multiple Content-Type headers") if @v > 1; @v = @{$v[0]}; my $boundary; my $boundary_index; for (my @tmp = @v; @tmp;) { my($k, $v) = splice(@tmp, 0, 2); if ($k eq "boundary") { $boundary = $v; $boundary_index = @v - @tmp - 1; last; } } ($content, $boundary) = form_data($content, $boundary, $req); if ($boundary_index) { $v[$boundary_index] = $boundary; } else { push(@v, boundary => $boundary); } $ct = HTTP::Headers::Util::join_header_words(@v); } else { # We use a temporary URI object to format # the application/x-www-form-urlencoded content. require URI; my $url = URI->new('http:'); $url->query_form(ref($content) eq "HASH" ? %$content : @$content); $content = $url->query; # HTML/4.01 says that line breaks are represented as "CR LF" pairs (i.e., `%0D%0A') $content =~ s/(?header('Content-Type' => $ct); # might be redundant if (defined($content)) { $req->header('Content-Length' => length($content)) unless ref($content); $req->content($content); } else { $req->header('Content-Length' => 0); } $req; } sub _simple_req { my($method, $url) = splice(@_, 0, 2); my $req = HTTP::Request->new($method => $url); my($k, $v); my $content; while (($k,$v) = splice(@_, 0, 2)) { if (lc($k) eq 'content') { $req->add_content($v); $content++; } else { $req->push_header($k, $v); } } if ($content && !defined($req->header("Content-Length"))) { $req->header("Content-Length", length(${$req->content_ref})); } $req; } sub form_data # RFC1867 { my($data, $boundary, $req) = @_; my @data = ref($data) eq "HASH" ? %$data : @$data; # copy my $fhparts; my @parts; while (my ($k,$v) = splice(@data, 0, 2)) { if (!ref($v)) { $k =~ s/([\\\"])/\\$1/g; # escape quotes and backslashes no warnings 'uninitialized'; push(@parts, qq(Content-Disposition: form-data; name="$k"$CRLF$CRLF$v)); } else { my($file, $usename, @headers) = @$v; unless (defined $usename) { $usename = $file; $usename = (File::Spec->splitpath($usename))[-1] if defined($usename); } $k =~ s/([\\\"])/\\$1/g; my $disp = qq(form-data; name="$k"); if (defined($usename) and length($usename)) { $usename =~ s/([\\\"])/\\$1/g; $disp .= qq(; filename="$usename"); } my $content = ""; my $h = HTTP::Headers->new(@headers); if ($file) { open(my $fh, "<", $file) or Carp::croak("Can't open file $file: $!"); binmode($fh); if ($DYNAMIC_FILE_UPLOAD) { # will read file later, close it now in order to # not accumulate to many open file handles close($fh); $content = \$file; } else { local($/) = undef; # slurp files $content = <$fh>; close($fh); } unless ($h->header("Content-Type")) { require LWP::MediaTypes; LWP::MediaTypes::guess_media_type($file, $h); } } if ($h->header("Content-Disposition")) { # just to get it sorted first $disp = $h->header("Content-Disposition"); $h->remove_header("Content-Disposition"); } if ($h->header("Content")) { $content = $h->header("Content"); $h->remove_header("Content"); } my $head = join($CRLF, "Content-Disposition: $disp", $h->as_string($CRLF), ""); if (ref $content) { push(@parts, [$head, $$content]); $fhparts++; } else { push(@parts, $head . $content); } } } return ("", "none") unless @parts; my $content; if ($fhparts) { $boundary = boundary(10) # hopefully enough randomness unless $boundary; # add the boundaries to the @parts array for (1..@parts-1) { splice(@parts, $_*2-1, 0, "$CRLF--$boundary$CRLF"); } unshift(@parts, "--$boundary$CRLF"); push(@parts, "$CRLF--$boundary--$CRLF"); # See if we can generate Content-Length header my $length = 0; for (@parts) { if (ref $_) { my ($head, $f) = @$_; my $file_size; unless ( -f $f && ($file_size = -s _) ) { # The file is either a dynamic file like /dev/audio # or perhaps a file in the /proc file system where # stat may return a 0 size even though reading it # will produce data. So we cannot make # a Content-Length header. undef $length; last; } $length += $file_size + length $head; } else { $length += length; } } $length && $req->header('Content-Length' => $length); # set up a closure that will return content piecemeal $content = sub { for (;;) { unless (@parts) { defined $length && $length != 0 && Carp::croak "length of data sent did not match calculated Content-Length header. Probably because uploaded file changed in size during transfer."; return; } my $p = shift @parts; unless (ref $p) { $p .= shift @parts while @parts && !ref($parts[0]); defined $length && ($length -= length $p); return $p; } my($buf, $fh) = @$p; unless (ref($fh)) { my $file = $fh; undef($fh); open($fh, "<", $file) || Carp::croak("Can't open file $file: $!"); binmode($fh); } my $buflength = length $buf; my $n = read($fh, $buf, $READ_BUFFER_SIZE, $buflength); if ($n) { $buflength += $n; unshift(@parts, ["", $fh]); } else { close($fh); } if ($buflength) { defined $length && ($length -= $buflength); return $buf } } }; } else { $boundary = boundary() unless $boundary; my $bno = 0; CHECK_BOUNDARY: { for (@parts) { if (index($_, $boundary) >= 0) { # must have a better boundary $boundary = boundary(++$bno); redo CHECK_BOUNDARY; } } last; } $content = "--$boundary$CRLF" . join("$CRLF--$boundary$CRLF", @parts) . "$CRLF--$boundary--$CRLF"; } wantarray ? ($content, $boundary) : $content; } sub boundary { my $size = shift || return "xYzZY"; require MIME::Base64; my $b = MIME::Base64::encode(join("", map chr(rand(256)), 1..$size*3), ""); $b =~ s/[\W]/X/g; # ensure alnum only $b; } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Request::Common - Construct common HTTP::Request objects =head1 VERSION version 6.45 =head1 SYNOPSIS use HTTP::Request::Common; $ua = LWP::UserAgent->new; $ua->request(GET 'http://www.sn.no/'); $ua->request(POST 'http://somewhere/foo', foo => bar, bar => foo); $ua->request(PATCH 'http://somewhere/foo', foo => bar, bar => foo); $ua->request(PUT 'http://somewhere/foo', foo => bar, bar => foo); $ua->request(OPTIONS 'http://somewhere/foo', foo => bar, bar => foo); =head1 DESCRIPTION This module provides functions that return newly created C objects. These functions are usually more convenient to use than the standard C constructor for the most common requests. Note that L has several convenience methods, including C, C, C, C and C. The following functions are provided: =over 4 =item GET $url =item GET $url, Header => Value,... The C function returns an L object initialized with the "GET" method and the specified URL. It is roughly equivalent to the following call HTTP::Request->new( GET => $url, HTTP::Headers->new(Header => Value,...), ) but is less cluttered. What is different is that a header named C will initialize the content part of the request instead of setting a header field. Note that GET requests should normally not have a content, so this hack makes more sense for the C, C and C functions described below. The C method of L exists as a shortcut for C<< $ua->request(GET ...) >>. =item HEAD $url =item HEAD $url, Header => Value,... Like GET() but the method in the request is "HEAD". The C method of L exists as a shortcut for C<< $ua->request(HEAD ...) >>. =item DELETE $url =item DELETE $url, Header => Value,... Like C but the method in the request is C. This function is not exported by default. =item PATCH $url =item PATCH $url, Header => Value,... =item PATCH $url, $form_ref, Header => Value,... =item PATCH $url, Header => Value,..., Content => $form_ref =item PATCH $url, Header => Value,..., Content => $content The same as C below, but the method in the request is C. =item PUT $url =item PUT $url, Header => Value,... =item PUT $url, $form_ref, Header => Value,... =item PUT $url, Header => Value,..., Content => $form_ref =item PUT $url, Header => Value,..., Content => $content The same as C below, but the method in the request is C =item OPTIONS $url =item OPTIONS $url, Header => Value,... =item OPTIONS $url, $form_ref, Header => Value,... =item OPTIONS $url, Header => Value,..., Content => $form_ref =item OPTIONS $url, Header => Value,..., Content => $content The same as C below, but the method in the request is C This was added in version 6.21, so you should require that in your code: use HTTP::Request::Common 6.21; =item POST $url =item POST $url, Header => Value,... =item POST $url, $form_ref, Header => Value,... =item POST $url, Header => Value,..., Content => $form_ref =item POST $url, Header => Value,..., Content => $content C, C and C all work with the same parameters. %data = ( title => 'something', body => something else' ); $ua = LWP::UserAgent->new(); $request = HTTP::Request::Common::POST( $url, [ %data ] ); $response = $ua->request($request); They take a second optional array or hash reference parameter C<$form_ref>. The content can also be specified directly using the C pseudo-header, and you may also provide the C<$form_ref> this way. The C pseudo-header steals a bit of the header field namespace as there is no way to directly specify a header that is actually called "Content". If you really need this you must update the request returned in a separate statement. The C<$form_ref> argument can be used to pass key/value pairs for the form content. By default we will initialize a request using the C content type. This means that you can emulate an HTML Eform> POSTing like this: POST 'http://www.perl.org/survey.cgi', [ name => 'Gisle Aas', email => 'gisle@aas.no', gender => 'M', born => '1964', perc => '3%', ]; This will create an L object that looks like this: POST http://www.perl.org/survey.cgi Content-Length: 66 Content-Type: application/x-www-form-urlencoded name=Gisle%20Aas&email=gisle%40aas.no&gender=M&born=1964&perc=3%25 Multivalued form fields can be specified by either repeating the field name or by passing the value as an array reference. The POST method also supports the C content used for I as specified in RFC 1867. You trigger this content format by specifying a content type of C<'form-data'> as one of the request headers. If one of the values in the C<$form_ref> is an array reference, then it is treated as a file part specification with the following interpretation: [ $file, $filename, Header => Value... ] [ undef, $filename, Header => Value,..., Content => $content ] The first value in the array ($file) is the name of a file to open. This file will be read and its content placed in the request. The routine will croak if the file can't be opened. Use an C as $file value if you want to specify the content directly with a C header. The $filename is the filename to report in the request. If this value is undefined, then the basename of the $file will be used. You can specify an empty string as $filename if you want to suppress sending the filename when you provide a $file value. If a $file is provided by no C header, then C and C will be filled in automatically with the values returned by C Sending my F<~/.profile> to the survey used as example above can be achieved by this: POST 'http://www.perl.org/survey.cgi', Content_Type => 'form-data', Content => [ name => 'Gisle Aas', email => 'gisle@aas.no', gender => 'M', born => '1964', init => ["$ENV{HOME}/.profile"], ] This will create an L object that almost looks this (the boundary and the content of your F<~/.profile> is likely to be different): POST http://www.perl.org/survey.cgi Content-Length: 388 Content-Type: multipart/form-data; boundary="6G+f" --6G+f Content-Disposition: form-data; name="name" Gisle Aas --6G+f Content-Disposition: form-data; name="email" gisle@aas.no --6G+f Content-Disposition: form-data; name="gender" M --6G+f Content-Disposition: form-data; name="born" 1964 --6G+f Content-Disposition: form-data; name="init"; filename=".profile" Content-Type: text/plain PATH=/local/perl/bin:$PATH export PATH --6G+f-- If you set the C<$DYNAMIC_FILE_UPLOAD> variable (exportable) to some TRUE value, then you get back a request object with a subroutine closure as the content attribute. This subroutine will read the content of any files on demand and return it in suitable chunks. This allow you to upload arbitrary big files without using lots of memory. You can even upload infinite files like F if you wish; however, if the file is not a plain file, there will be no C header defined for the request. Not all servers (or server applications) like this. Also, if the file(s) change in size between the time the C is calculated and the time that the last chunk is delivered, the subroutine will C. The C method of L exists as a shortcut for C<< $ua->request(POST ...) >>. =back =head1 SEE ALSO L, L Also, there are some examples in L that you might find useful. For example, batch requests are explained there. =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: Construct common HTTP::Request objects PK!""perl5/HTTP/Request.pmnu6$package HTTP::Request; use strict; use warnings; our $VERSION = '6.45'; use parent 'HTTP::Message'; sub new { my($class, $method, $uri, $header, $content) = @_; my $self = $class->SUPER::new($header, $content); $self->method($method); $self->uri($uri); $self; } sub parse { my($class, $str) = @_; Carp::carp('Undefined argument to parse()') if $^W && ! defined $str; my $request_line; if (defined $str && $str =~ s/^(.*)\n//) { $request_line = $1; } else { $request_line = $str; $str = ""; } my $self = $class->SUPER::parse($str); if (defined $request_line) { my($method, $uri, $protocol) = split(' ', $request_line); $self->method($method); $self->uri($uri) if defined($uri); $self->protocol($protocol) if $protocol; } $self; } sub clone { my $self = shift; my $clone = bless $self->SUPER::clone, ref($self); $clone->method($self->method); $clone->uri($self->uri); $clone; } sub method { shift->_elem('_method', @_); } sub uri { my $self = shift; my $old = $self->{'_uri'}; if (@_) { my $uri = shift; if (!defined $uri) { # that's ok } elsif (ref $uri) { Carp::croak("A URI can't be a " . ref($uri) . " reference") if ref($uri) eq 'HASH' or ref($uri) eq 'ARRAY'; Carp::croak("Can't use a " . ref($uri) . " object as a URI") unless $uri->can('scheme') && $uri->can('canonical'); $uri = $uri->clone; unless ($HTTP::URI_CLASS eq "URI") { # Argh!! Hate this... old LWP legacy! eval { local $SIG{__DIE__}; $uri = $uri->abs; }; die $@ if $@ && $@ !~ /Missing base argument/; } } else { $uri = $HTTP::URI_CLASS->new($uri); } $self->{'_uri'} = $uri; delete $self->{'_uri_canonical'}; } $old; } *url = \&uri; # legacy sub uri_canonical { my $self = shift; my $uri = $self->{_uri}; if (defined (my $canon = $self->{_uri_canonical})) { # early bailout if these are the exact same string; # rely on stringification of the URI objects return $canon if $canon eq $uri; } # otherwise we need to refresh the memoized value $self->{_uri_canonical} = $uri->canonical; } sub accept_decodable { my $self = shift; $self->header("Accept-Encoding", scalar($self->decodable)); } sub as_string { my $self = shift; my($eol) = @_; $eol = "\n" unless defined $eol; my $req_line = $self->method || "-"; my $uri = $self->uri; $uri = (defined $uri) ? $uri->as_string : "-"; $req_line .= " $uri"; my $proto = $self->protocol; $req_line .= " $proto" if $proto; return join($eol, $req_line, $self->SUPER::as_string(@_)); } sub dump { my $self = shift; my @pre = ($self->method || "-", $self->uri || "-"); if (my $prot = $self->protocol) { push(@pre, $prot); } return $self->SUPER::dump( preheader => join(" ", @pre), @_, ); } 1; =pod =encoding UTF-8 =head1 NAME HTTP::Request - HTTP style request message =head1 VERSION version 6.45 =head1 SYNOPSIS require HTTP::Request; $request = HTTP::Request->new(GET => 'http://www.example.com/'); and usually used like this: $ua = LWP::UserAgent->new; $response = $ua->request($request); =head1 DESCRIPTION C is a class encapsulating HTTP style requests, consisting of a request line, some headers, and a content body. Note that the LWP library uses HTTP style requests even for non-HTTP protocols. Instances of this class are usually passed to the request() method of an C object. C is a subclass of C and therefore inherits its methods. The following additional methods are available: =over 4 =item $r = HTTP::Request->new( $method, $uri ) =item $r = HTTP::Request->new( $method, $uri, $header ) =item $r = HTTP::Request->new( $method, $uri, $header, $content ) Constructs a new C object describing a request on the object $uri using method $method. The $method argument must be a string. The $uri argument can be either a string, or a reference to a C object. The optional $header argument should be a reference to an C object or a plain array reference of key/value pairs. The optional $content argument should be a string of bytes. =item $r = HTTP::Request->parse( $str ) This constructs a new request object by parsing the given string. =item $r->method =item $r->method( $val ) This is used to get/set the method attribute. The method should be a short string like "GET", "HEAD", "PUT", "PATCH" or "POST". =item $r->uri =item $r->uri( $val ) This is used to get/set the uri attribute. The $val can be a reference to a URI object or a plain string. If a string is given, then it should be parsable as an absolute URI. =item $r->header( $field ) =item $r->header( $field => $value ) This is used to get/set header values and it is inherited from C via C. See L for details and other similar methods that can be used to access the headers. =item $r->accept_decodable This will set the C header to the list of encodings that decoded_content() can decode. =item $r->content =item $r->content( $bytes ) This is used to get/set the content and it is inherited from the C base class. See L for details and other methods that can be used to access the content. Note that the content should be a string of bytes. Strings in perl can contain characters outside the range of a byte. The C module can be used to turn such strings into a string of bytes. =item $r->as_string =item $r->as_string( $eol ) Method returning a textual representation of the request. =back =head1 EXAMPLES Creating requests to be sent with L or others can be easy. Here are a few examples. =head2 Simple POST Here, we'll create a simple POST request that could be used to send JSON data to an endpoint. #!/usr/bin/env perl use strict; use warnings; use HTTP::Request (); use JSON::MaybeXS qw(encode_json); my $url = 'https://www.example.com/api/user/123'; my $header = ['Content-Type' => 'application/json; charset=UTF-8']; my $data = {foo => 'bar', baz => 'quux'}; my $encoded_data = encode_json($data); my $r = HTTP::Request->new('POST', $url, $header, $encoded_data); # at this point, we could send it via LWP::UserAgent # my $ua = LWP::UserAgent->new(); # my $res = $ua->request($r); =head2 Batch POST Request Some services, like Google, allow multiple requests to be sent in one batch. L for example. Using the C method from L makes this simple. #!/usr/bin/env perl use strict; use warnings; use HTTP::Request (); use JSON::MaybeXS qw(encode_json); my $auth_token = 'auth_token'; my $batch_url = 'https://www.googleapis.com/batch'; my $url = 'https://www.googleapis.com/drive/v3/files/fileId/permissions?fields=id'; my $url_no_email = 'https://www.googleapis.com/drive/v3/files/fileId/permissions?fields=id&sendNotificationEmail=false'; # generate a JSON post request for one of the batch entries my $req1 = build_json_request($url, { emailAddress => 'example@appsrocks.com', role => "writer", type => "user", }); # generate a JSON post request for one of the batch entries my $req2 = build_json_request($url_no_email, { domain => "appsrocks.com", role => "reader", type => "domain", }); # generate a multipart request to send all of the other requests my $r = HTTP::Request->new('POST', $batch_url, [ 'Accept-Encoding' => 'gzip', # if we don't provide a boundary here, HTTP::Message will generate # one for us. We could use UUID::uuid() here if we wanted. 'Content-Type' => 'multipart/mixed; boundary=END_OF_PART' ]); # add the two POST requests to the main request $r->add_part($req1, $req2); # at this point, we could send it via LWP::UserAgent # my $ua = LWP::UserAgent->new(); # my $res = $ua->request($r); exit(); sub build_json_request { my ($url, $href) = @_; my $header = ['Authorization' => "Bearer $auth_token", 'Content-Type' => 'application/json; charset=UTF-8']; return HTTP::Request->new('POST', $url, $header, encode_json($href)); } =head1 SEE ALSO L, L, L, L =head1 AUTHOR Gisle Aas =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1994 by Gisle Aas. 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: HTTP style request message PK!L==perl5/HTTP/Tiny.pmnu6$# vim: ts=4 sts=4 sw=4 et: package HTTP::Tiny; use strict; use warnings; # ABSTRACT: A small, simple, correct HTTP/1.1 client our $VERSION = '0.088'; sub _croak { require Carp; Carp::croak(@_) } #pod =method new #pod #pod $http = HTTP::Tiny->new( %attributes ); #pod #pod This constructor returns a new HTTP::Tiny object. Valid attributes include: #pod #pod =for :list #pod * C — A user-agent string (defaults to 'HTTP-Tiny/$VERSION'). If #pod C — ends in a space character, the default user-agent string is #pod appended. #pod * C — An instance of L — or equivalent class #pod that supports the C and C methods #pod * C — A hashref of default headers to apply to requests #pod * C — The local IP address to bind to #pod * C — Whether to reuse the last connection (if for the same #pod scheme, host and port) (defaults to 1) #pod * C — Maximum number of redirects allowed (defaults to 5) #pod * C — Maximum response size in bytes (only when not using a data #pod callback). If defined, requests with responses larger than this will return #pod a 599 status code. #pod * C — URL of a proxy server to use for HTTP connections #pod (default is C<$ENV{http_proxy}> — if set) #pod * C — URL of a proxy server to use for HTTPS connections #pod (default is C<$ENV{https_proxy}> — if set) #pod * C — URL of a generic proxy server for both HTTP and HTTPS #pod connections (default is C<$ENV{all_proxy}> — if set) #pod * C — List of domain suffixes that should not be proxied. Must #pod be a comma-separated string or an array reference. (default is #pod C<$ENV{no_proxy}> —) #pod * C — Request timeout in seconds (default is 60) If a socket open, #pod read or write takes longer than the timeout, the request response status code #pod will be 599. #pod * C — A boolean that indicates whether to validate the TLS/SSL #pod certificate of an C — connection (default is true). Changed from false #pod to true in version 0.083. #pod * C — A hashref of C — options to pass through to #pod L #pod * C<$ENV{PERL_HTTP_TINY_SSL_INSECURE_BY_DEFAULT}> - Changes the default #pod certificate verification behavior to not check server identity if set to 1. #pod Only effective if C is not set. Added in version 0.083. #pod #pod #pod An accessor/mutator method exists for each attribute. #pod #pod Passing an explicit C for C, C or C will #pod prevent getting the corresponding proxies from the environment. #pod #pod Errors during request execution will result in a pseudo-HTTP status code of 599 #pod and a reason of "Internal Exception". The content field in the response will #pod contain the text of the error. #pod #pod The C parameter enables a persistent connection, but only to a #pod single destination scheme, host and port. If any connection-relevant #pod attributes are modified via accessor, or if the process ID or thread ID change, #pod the persistent connection will be dropped. If you want persistent connections #pod across multiple destinations, use multiple HTTP::Tiny objects. #pod #pod See L for more on the C and C attributes. #pod #pod =cut my @attributes; BEGIN { @attributes = qw( cookie_jar default_headers http_proxy https_proxy keep_alive local_address max_redirect max_size proxy no_proxy SSL_options verify_SSL ); my %persist_ok = map {; $_ => 1 } qw( cookie_jar default_headers max_redirect max_size ); no strict 'refs'; no warnings 'uninitialized'; for my $accessor ( @attributes ) { *{$accessor} = sub { @_ > 1 ? do { delete $_[0]->{handle} if !$persist_ok{$accessor} && $_[1] ne $_[0]->{$accessor}; $_[0]->{$accessor} = $_[1] } : $_[0]->{$accessor}; }; } } sub agent { my($self, $agent) = @_; if( @_ > 1 ){ $self->{agent} = (defined $agent && $agent =~ / $/) ? $agent . $self->_agent : $agent; } return $self->{agent}; } sub timeout { my ($self, $timeout) = @_; if ( @_ > 1 ) { $self->{timeout} = $timeout; if ($self->{handle}) { $self->{handle}->timeout($timeout); } } return $self->{timeout}; } sub new { my($class, %args) = @_; # Support lower case verify_ssl argument, but only if verify_SSL is not # true. if ( exists $args{verify_ssl} ) { $args{verify_SSL} ||= $args{verify_ssl}; } my $self = { max_redirect => 5, timeout => defined $args{timeout} ? $args{timeout} : 60, keep_alive => 1, verify_SSL => defined $args{verify_SSL} ? $args{verify_SSL} : _verify_SSL_default(), no_proxy => $ENV{no_proxy}, }; bless $self, $class; $class->_validate_cookie_jar( $args{cookie_jar} ) if $args{cookie_jar}; for my $key ( @attributes ) { $self->{$key} = $args{$key} if exists $args{$key} } $self->agent( exists $args{agent} ? $args{agent} : $class->_agent ); $self->_set_proxies; return $self; } sub _verify_SSL_default { my ($self) = @_; # Check if insecure default certificate verification behaviour has been # changed by the user by setting PERL_HTTP_TINY_SSL_INSECURE_BY_DEFAULT=1 return (($ENV{PERL_HTTP_TINY_SSL_INSECURE_BY_DEFAULT} || '') eq '1') ? 0 : 1; } sub _set_proxies { my ($self) = @_; # get proxies from %ENV only if not provided; explicit undef will disable # getting proxies from the environment # generic proxy if (! exists $self->{proxy} ) { $self->{proxy} = $ENV{all_proxy} || $ENV{ALL_PROXY}; } if ( defined $self->{proxy} ) { $self->_split_proxy( 'generic proxy' => $self->{proxy} ); # validate } else { delete $self->{proxy}; } # http proxy if (! exists $self->{http_proxy} ) { # under CGI, bypass HTTP_PROXY as request sets it from Proxy header local $ENV{HTTP_PROXY} = ($ENV{CGI_HTTP_PROXY} || "") if $ENV{REQUEST_METHOD}; $self->{http_proxy} = $ENV{http_proxy} || $ENV{HTTP_PROXY} || $self->{proxy}; } if ( defined $self->{http_proxy} ) { $self->_split_proxy( http_proxy => $self->{http_proxy} ); # validate $self->{_has_proxy}{http} = 1; } else { delete $self->{http_proxy}; } # https proxy if (! exists $self->{https_proxy} ) { $self->{https_proxy} = $ENV{https_proxy} || $ENV{HTTPS_PROXY} || $self->{proxy}; } if ( $self->{https_proxy} ) { $self->_split_proxy( https_proxy => $self->{https_proxy} ); # validate $self->{_has_proxy}{https} = 1; } else { delete $self->{https_proxy}; } # Split no_proxy to array reference if not provided as such unless ( ref $self->{no_proxy} eq 'ARRAY' ) { $self->{no_proxy} = (defined $self->{no_proxy}) ? [ split /\s*,\s*/, $self->{no_proxy} ] : []; } return; } #pod =method get|head|put|post|patch|delete #pod #pod $response = $http->get($url); #pod $response = $http->get($url, \%options); #pod $response = $http->head($url); #pod #pod These methods are shorthand for calling C for the given method. The #pod URL must have unsafe characters escaped and international domain names encoded. #pod See C for valid options and a description of the response. #pod #pod The C field of the response will be true if the status code is 2XX. #pod #pod =cut for my $sub_name ( qw/get head put post patch delete/ ) { my $req_method = uc $sub_name; no strict 'refs'; eval <<"HERE"; ## no critic sub $sub_name { my (\$self, \$url, \$args) = \@_; \@_ == 2 || (\@_ == 3 && ref \$args eq 'HASH') or _croak(q/Usage: \$http->$sub_name(URL, [HASHREF])/ . "\n"); return \$self->request('$req_method', \$url, \$args || {}); } HERE } #pod =method post_form #pod #pod $response = $http->post_form($url, $form_data); #pod $response = $http->post_form($url, $form_data, \%options); #pod #pod This method executes a C request and sends the key/value pairs from a #pod form data hash or array reference to the given URL with a C of #pod C. If data is provided as an array #pod reference, the order is preserved; if provided as a hash reference, the terms #pod are sorted on key and value for consistency. See documentation for the #pod C method for details on the encoding. #pod #pod The URL must have unsafe characters escaped and international domain names #pod encoded. See C for valid options and a description of the response. #pod Any C header or content in the options hashref will be ignored. #pod #pod The C field of the response will be true if the status code is 2XX. #pod #pod =cut sub post_form { my ($self, $url, $data, $args) = @_; (@_ == 3 || @_ == 4 && ref $args eq 'HASH') or _croak(q/Usage: $http->post_form(URL, DATAREF, [HASHREF])/ . "\n"); my $headers = {}; while ( my ($key, $value) = each %{$args->{headers} || {}} ) { $headers->{lc $key} = $value; } return $self->request('POST', $url, { # Any existing 'headers' key in $args will be overridden with a # normalized version below. %$args, content => $self->www_form_urlencode($data), headers => { %$headers, 'content-type' => 'application/x-www-form-urlencoded' }, } ); } #pod =method mirror #pod #pod $response = $http->mirror($url, $file, \%options) #pod if ( $response->{success} ) { #pod print "$file is up to date\n"; #pod } #pod #pod Executes a C request for the URL and saves the response body to the file #pod name provided. The URL must have unsafe characters escaped and international #pod domain names encoded. If the file already exists, the request will include an #pod C header with the modification timestamp of the file. You #pod may specify a different C header yourself in the C<< #pod $options->{headers} >> hash. #pod #pod The C field of the response will be true if the status code is 2XX #pod or if the status code is 304 (unmodified). #pod #pod If the file was modified and the server response includes a properly #pod formatted C header, the file modification time will #pod be updated accordingly. #pod #pod =cut sub mirror { my ($self, $url, $file, $args) = @_; @_ == 3 || (@_ == 4 && ref $args eq 'HASH') or _croak(q/Usage: $http->mirror(URL, FILE, [HASHREF])/ . "\n"); if ( exists $args->{headers} ) { my $headers = {}; while ( my ($key, $value) = each %{$args->{headers} || {}} ) { $headers->{lc $key} = $value; } $args->{headers} = $headers; } if ( -e $file and my $mtime = (stat($file))[9] ) { $args->{headers}{'if-modified-since'} ||= $self->_http_date($mtime); } my $tempfile = $file . int(rand(2**31)); require Fcntl; sysopen my $fh, $tempfile, Fcntl::O_CREAT()|Fcntl::O_EXCL()|Fcntl::O_WRONLY() or _croak(qq/Error: Could not create temporary file $tempfile for downloading: $!\n/); binmode $fh; $args->{data_callback} = sub { print {$fh} $_[0] }; my $response = $self->request('GET', $url, $args); close $fh or _croak(qq/Error: Caught error closing temporary file $tempfile: $!\n/); if ( $response->{success} ) { rename $tempfile, $file or _croak(qq/Error replacing $file with $tempfile: $!\n/); my $lm = $response->{headers}{'last-modified'}; if ( $lm and my $mtime = $self->_parse_http_date($lm) ) { utime $mtime, $mtime, $file; } } $response->{success} ||= $response->{status} eq '304'; unlink $tempfile; return $response; } #pod =method request #pod #pod $response = $http->request($method, $url); #pod $response = $http->request($method, $url, \%options); #pod #pod Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST', #pod 'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and #pod international domain names encoded. #pod #pod B: Method names are B per the HTTP/1.1 specification. #pod Don't use C when you really want C. See L for #pod how this applies to redirection. #pod #pod If the URL includes a "user:password" stanza, they will be used for Basic-style #pod authorization headers. (Authorization headers will not be included in a #pod redirected request.) For example: #pod #pod $http->request('GET', 'http://Aladdin:open sesame@example.com/'); #pod #pod If the "user:password" stanza contains reserved characters, they must #pod be percent-escaped: #pod #pod $http->request('GET', 'http://john%40example.com:password@example.com/'); #pod #pod A hashref of options may be appended to modify the request. #pod #pod Valid options are: #pod #pod =for :list #pod * C — #pod A hashref containing headers to include with the request. If the value for #pod a header is an array reference, the header will be output multiple times with #pod each value in the array. These headers over-write any default headers. #pod * C — #pod A scalar to include as the body of the request OR a code reference #pod that will be called iteratively to produce the body of the request #pod * C — #pod A code reference that will be called if it exists to provide a hashref #pod of trailing headers (only used with chunked transfer-encoding) #pod * C — #pod A code reference that will be called for each chunks of the response #pod body received. #pod * C — #pod Override host resolution and force all connections to go only to a #pod specific peer address, regardless of the URL of the request. This will #pod include any redirections! This options should be used with extreme #pod caution (e.g. debugging or very special circumstances). It can be given as #pod either a scalar or a code reference that will receive the hostname and #pod whose response will be taken as the address. #pod #pod The C header is generated from the URL in accordance with RFC 2616. It #pod is a fatal error to specify C in the C option. Other headers #pod may be ignored or overwritten if necessary for transport compliance. #pod #pod If the C option is a code reference, it will be called iteratively #pod to provide the content body of the request. It should return the empty #pod string or undef when the iterator is exhausted. #pod #pod If the C option is the empty string, no C or #pod C headers will be generated. #pod #pod If the C option is provided, it will be called iteratively until #pod the entire response body is received. The first argument will be a string #pod containing a chunk of the response body, the second argument will be the #pod in-progress response hash reference, as described below. (This allows #pod customizing the action of the callback based on the C or C #pod received prior to the content body.) #pod #pod Content data in the request/response is handled as "raw bytes". Any #pod encoding/decoding (with associated headers) are the responsibility of the #pod caller. #pod #pod The C method returns a hashref containing the response. The hashref #pod will have the following keys: #pod #pod =for :list #pod * C — #pod Boolean indicating whether the operation returned a 2XX status code #pod * C — #pod URL that provided the response. This is the URL of the request unless #pod there were redirections, in which case it is the last URL queried #pod in a redirection chain #pod * C — #pod The HTTP status code of the response #pod * C — #pod The response phrase returned by the server #pod * C — #pod The body of the response. If the response does not have any content #pod or if a data callback is provided to consume the response body, #pod this will be the empty string #pod * C — #pod A hashref of header fields. All header field names will be normalized #pod to be lower case. If a header is repeated, the value will be an arrayref; #pod it will otherwise be a scalar string containing the value #pod * C - #pod If this field exists, it is the protocol of the response #pod such as HTTP/1.0 or HTTP/1.1 #pod * C #pod If this field exists, it is an arrayref of response hash references from #pod redirects in the same order that redirections occurred. If it does #pod not exist, then no redirections occurred. #pod #pod On an error during the execution of the request, the C field will #pod contain 599, and the C field will contain the text of the error. #pod #pod =cut my %idempotent = map { $_ => 1 } qw/GET HEAD PUT DELETE OPTIONS TRACE/; sub request { my ($self, $method, $url, $args) = @_; @_ == 3 || (@_ == 4 && ref $args eq 'HASH') or _croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/ . "\n"); $args ||= {}; # we keep some state in this during _request # RFC 2616 Section 8.1.4 mandates a single retry on broken socket my $response; for ( 0 .. 1 ) { $response = eval { $self->_request($method, $url, $args) }; last unless $@ && $idempotent{$method} && $@ =~ m{^(?:Socket closed|Unexpected end|SSL read error)}; } if (my $e = $@) { # maybe we got a response hash thrown from somewhere deep if ( ref $e eq 'HASH' && exists $e->{status} ) { $e->{redirects} = delete $args->{_redirects} if @{ $args->{_redirects} || []}; return $e; } # otherwise, stringify it $e = "$e"; $response = { url => $url, success => q{}, status => 599, reason => 'Internal Exception', content => $e, headers => { 'content-type' => 'text/plain', 'content-length' => length $e, }, ( @{$args->{_redirects} || []} ? (redirects => delete $args->{_redirects}) : () ), }; } return $response; } #pod =method www_form_urlencode #pod #pod $params = $http->www_form_urlencode( $data ); #pod $response = $http->get("http://example.com/query?$params"); #pod #pod This method converts the key/value pairs from a data hash or array reference #pod into a C string. The keys and values from the data #pod reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an #pod array reference, the key will be repeated with each of the values of the array #pod reference. If data is provided as a hash reference, the key/value pairs in the #pod resulting string will be sorted by key and value for consistent ordering. #pod #pod =cut sub www_form_urlencode { my ($self, $data) = @_; (@_ == 2 && ref $data) or _croak(q/Usage: $http->www_form_urlencode(DATAREF)/ . "\n"); (ref $data eq 'HASH' || ref $data eq 'ARRAY') or _croak("form data must be a hash or array reference\n"); my @params = ref $data eq 'HASH' ? %$data : @$data; @params % 2 == 0 or _croak("form data reference must have an even number of terms\n"); my @terms; while( @params ) { my ($key, $value) = splice(@params, 0, 2); _croak("form data keys must not be undef") if !defined($key); if ( ref $value eq 'ARRAY' ) { unshift @params, map { $key => $_ } @$value; } else { push @terms, join("=", map { $self->_uri_escape($_) } $key, $value); } } return join("&", (ref $data eq 'ARRAY') ? (@terms) : (sort @terms) ); } #pod =method can_ssl #pod #pod $ok = HTTP::Tiny->can_ssl; #pod ($ok, $why) = HTTP::Tiny->can_ssl; #pod ($ok, $why) = $http->can_ssl; #pod #pod Indicates if SSL support is available. When called as a class object, it #pod checks for the correct version of L and L. #pod When called as an object methods, if C is true or if C #pod is set in C, it checks that a CA file is available. #pod #pod In scalar context, returns a boolean indicating if SSL is available. #pod In list context, returns the boolean and a (possibly multi-line) string of #pod errors indicating why SSL isn't available. #pod #pod =cut sub can_ssl { my ($self) = @_; my($ok, $reason) = (1, ''); # Need IO::Socket::SSL 1.42 for SSL_create_ctx_callback local @INC = @INC; pop @INC if $INC[-1] eq '.'; unless (eval {require IO::Socket::SSL; IO::Socket::SSL->VERSION(1.42)}) { $ok = 0; $reason .= qq/IO::Socket::SSL 1.42 must be installed for https support\n/; } # Need Net::SSLeay 1.49 for MODE_AUTO_RETRY unless (eval {require Net::SSLeay; Net::SSLeay->VERSION(1.49)}) { $ok = 0; $reason .= qq/Net::SSLeay 1.49 must be installed for https support\n/; } # If an object, check that SSL config lets us get a CA if necessary if ( ref($self) && ( $self->{verify_SSL} || $self->{SSL_options}{SSL_verify_mode} ) ) { my $handle = HTTP::Tiny::Handle->new( SSL_options => $self->{SSL_options}, verify_SSL => $self->{verify_SSL}, ); unless ( eval { $handle->_find_CA_file; 1 } ) { $ok = 0; $reason .= "$@"; } } wantarray ? ($ok, $reason) : $ok; } #pod =method connected #pod #pod $host = $http->connected; #pod ($host, $port) = $http->connected; #pod #pod Indicates if a connection to a peer is being kept alive, per the C #pod option. #pod #pod In scalar context, returns the peer host and port, joined with a colon, or #pod C (if no peer is connected). #pod In list context, returns the peer host and port or an empty list (if no peer #pod is connected). #pod #pod B: This method cannot reliably be used to discover whether the remote #pod host has closed its end of the socket. #pod #pod =cut sub connected { my ($self) = @_; if ( $self->{handle} ) { return $self->{handle}->connected; } return; } #--------------------------------------------------------------------------# # private methods #--------------------------------------------------------------------------# my %DefaultPort = ( http => 80, https => 443, ); sub _agent { my $class = ref($_[0]) || $_[0]; (my $default_agent = $class) =~ s{::}{-}g; my $version = $class->VERSION; $default_agent .= "/$version" if defined $version; return $default_agent; } sub _request { my ($self, $method, $url, $args) = @_; my ($scheme, $host, $port, $path_query, $auth) = $self->_split_url($url); if ($scheme ne 'http' && $scheme ne 'https') { die(qq/Unsupported URL scheme '$scheme'\n/); } my $request = { method => $method, scheme => $scheme, host => $host, port => $port, host_port => ($port == $DefaultPort{$scheme} ? $host : "$host:$port"), uri => $path_query, headers => {}, }; my $peer = $args->{peer} || $host; # Allow 'peer' to be a coderef. if ('CODE' eq ref $peer) { $peer = $peer->($host); } # We remove the cached handle so it is not reused in the case of redirect. # If all is well, it will be recached at the end of _request. We only # reuse for the same scheme, host and port my $handle = delete $self->{handle}; if ( $handle ) { unless ( $handle->can_reuse( $scheme, $host, $port, $peer ) ) { $handle->close; undef $handle; } } $handle ||= $self->_open_handle( $request, $scheme, $host, $port, $peer ); $self->_prepare_headers_and_cb($request, $args, $url, $auth); $handle->write_request($request); my $response; do { $response = $handle->read_response_header } until (substr($response->{status},0,1) ne '1'); $self->_update_cookie_jar( $url, $response ) if $self->{cookie_jar}; my @redir_args = $self->_maybe_redirect($request, $response, $args); my $known_message_length; if ($method eq 'HEAD' || $response->{status} =~ /^[23]04/) { # response has no message body $known_message_length = 1; } else { # Ignore any data callbacks during redirection. my $cb_args = @redir_args ? +{} : $args; my $data_cb = $self->_prepare_data_cb($response, $cb_args); $known_message_length = $handle->read_body($data_cb, $response); } if ( $self->{keep_alive} && $handle->connected && $known_message_length && $response->{protocol} eq 'HTTP/1.1' && ($response->{headers}{connection} || '') ne 'close' ) { $self->{handle} = $handle; } else { $handle->close; } $response->{success} = substr( $response->{status}, 0, 1 ) eq '2'; $response->{url} = $url; # Push the current response onto the stack of redirects if redirecting. if (@redir_args) { push @{$args->{_redirects}}, $response; return $self->_request(@redir_args, $args); } # Copy the stack of redirects into the response before returning. $response->{redirects} = delete $args->{_redirects} if @{$args->{_redirects}}; return $response; } sub _open_handle { my ($self, $request, $scheme, $host, $port, $peer) = @_; my $handle = HTTP::Tiny::Handle->new( timeout => $self->{timeout}, SSL_options => $self->{SSL_options}, verify_SSL => $self->{verify_SSL}, local_address => $self->{local_address}, keep_alive => $self->{keep_alive} ); if ($self->{_has_proxy}{$scheme} && ! grep { $host =~ /\Q$_\E$/ } @{$self->{no_proxy}}) { return $self->_proxy_connect( $request, $handle ); } else { return $handle->connect($scheme, $host, $port, $peer); } } sub _proxy_connect { my ($self, $request, $handle) = @_; my @proxy_vars; if ( $request->{scheme} eq 'https' ) { _croak(qq{No https_proxy defined}) unless $self->{https_proxy}; @proxy_vars = $self->_split_proxy( https_proxy => $self->{https_proxy} ); if ( $proxy_vars[0] eq 'https' ) { _croak(qq{Can't proxy https over https: $request->{uri} via $self->{https_proxy}}); } } else { _croak(qq{No http_proxy defined}) unless $self->{http_proxy}; @proxy_vars = $self->_split_proxy( http_proxy => $self->{http_proxy} ); } my ($p_scheme, $p_host, $p_port, $p_auth) = @proxy_vars; if ( length $p_auth && ! defined $request->{headers}{'proxy-authorization'} ) { $self->_add_basic_auth_header( $request, 'proxy-authorization' => $p_auth ); } $handle->connect($p_scheme, $p_host, $p_port, $p_host); if ($request->{scheme} eq 'https') { $self->_create_proxy_tunnel( $request, $handle ); } else { # non-tunneled proxy requires absolute URI $request->{uri} = "$request->{scheme}://$request->{host_port}$request->{uri}"; } return $handle; } sub _split_proxy { my ($self, $type, $proxy) = @_; my ($scheme, $host, $port, $path_query, $auth) = eval { $self->_split_url($proxy) }; unless( defined($scheme) && length($scheme) && length($host) && length($port) && $path_query eq '/' ) { _croak(qq{$type URL must be in format http[s]://[auth@]:/\n}); } return ($scheme, $host, $port, $auth); } sub _create_proxy_tunnel { my ($self, $request, $handle) = @_; $handle->_assert_ssl; my $agent = exists($request->{headers}{'user-agent'}) ? $request->{headers}{'user-agent'} : $self->{agent}; my $connect_request = { method => 'CONNECT', uri => "$request->{host}:$request->{port}", headers => { host => "$request->{host}:$request->{port}", 'user-agent' => $agent, } }; if ( $request->{headers}{'proxy-authorization'} ) { $connect_request->{headers}{'proxy-authorization'} = delete $request->{headers}{'proxy-authorization'}; } $handle->write_request($connect_request); my $response; do { $response = $handle->read_response_header } until (substr($response->{status},0,1) ne '1'); # if CONNECT failed, throw the response so it will be # returned from the original request() method; unless (substr($response->{status},0,1) eq '2') { die $response; } # tunnel established, so start SSL handshake $handle->start_ssl( $request->{host} ); return; } sub _prepare_headers_and_cb { my ($self, $request, $args, $url, $auth) = @_; for ($self->{default_headers}, $args->{headers}) { next unless defined; while (my ($k, $v) = each %$_) { $request->{headers}{lc $k} = $v; $request->{header_case}{lc $k} = $k; } } if (exists $request->{headers}{'host'}) { die(qq/The 'Host' header must not be provided as header option\n/); } $request->{headers}{'host'} = $request->{host_port}; $request->{headers}{'user-agent'} ||= $self->{agent}; $request->{headers}{'connection'} = "close" unless $self->{keep_alive}; # Some servers error on an empty-body PUT/POST without a content-length if ( $request->{method} eq 'PUT' || $request->{method} eq 'POST' ) { if (!defined($args->{content}) || !length($args->{content}) ) { $request->{headers}{'content-length'} = 0; } } if ( defined $args->{content} ) { if ( ref $args->{content} eq 'CODE' ) { if ( exists $request->{'content-length'} && $request->{'content-length'} == 0 ) { $request->{cb} = sub { "" }; } else { $request->{headers}{'content-type'} ||= "application/octet-stream"; $request->{headers}{'transfer-encoding'} = 'chunked' unless exists $request->{headers}{'content-length'} || $request->{headers}{'transfer-encoding'}; $request->{cb} = $args->{content}; } } elsif ( length $args->{content} ) { my $content = $args->{content}; if ( $] ge '5.008' ) { utf8::downgrade($content, 1) or die(qq/Wide character in request message body\n/); } $request->{headers}{'content-type'} ||= "application/octet-stream"; $request->{headers}{'content-length'} = length $content unless $request->{headers}{'content-length'} || $request->{headers}{'transfer-encoding'}; $request->{cb} = sub { substr $content, 0, length $content, '' }; } $request->{trailer_cb} = $args->{trailer_callback} if ref $args->{trailer_callback} eq 'CODE'; } ### If we have a cookie jar, then maybe add relevant cookies if ( $self->{cookie_jar} ) { my $cookies = $self->cookie_jar->cookie_header( $url ); $request->{headers}{cookie} = $cookies if length $cookies; } # if we have Basic auth parameters, add them if ( length $auth && ! defined $request->{headers}{authorization} ) { $self->_add_basic_auth_header( $request, 'authorization' => $auth ); } return; } sub _add_basic_auth_header { my ($self, $request, $header, $auth) = @_; require MIME::Base64; $request->{headers}{$header} = "Basic " . MIME::Base64::encode_base64($auth, ""); return; } sub _prepare_data_cb { my ($self, $response, $args) = @_; my $data_cb = $args->{data_callback}; $response->{content} = ''; if (!$data_cb || $response->{status} !~ /^2/) { if (defined $self->{max_size}) { $data_cb = sub { $_[1]->{content} .= $_[0]; die(qq/Size of response body exceeds the maximum allowed of $self->{max_size}\n/) if length $_[1]->{content} > $self->{max_size}; }; } else { $data_cb = sub { $_[1]->{content} .= $_[0] }; } } return $data_cb; } sub _update_cookie_jar { my ($self, $url, $response) = @_; my $cookies = $response->{headers}->{'set-cookie'}; return unless defined $cookies; my @cookies = ref $cookies ? @$cookies : $cookies; $self->cookie_jar->add( $url, $_ ) for @cookies; return; } sub _validate_cookie_jar { my ($class, $jar) = @_; # duck typing for my $method ( qw/add cookie_header/ ) { _croak(qq/Cookie jar must provide the '$method' method\n/) unless ref($jar) && ref($jar)->can($method); } return; } sub _maybe_redirect { my ($self, $request, $response, $args) = @_; my $headers = $response->{headers}; my ($status, $method) = ($response->{status}, $request->{method}); $args->{_redirects} ||= []; if (($status eq '303' or ($status =~ /^30[1278]/ && $method =~ /^GET|HEAD$/)) and $headers->{location} and @{$args->{_redirects}} < $self->{max_redirect} ) { my $location = ($headers->{location} =~ /^\//) ? "$request->{scheme}://$request->{host_port}$headers->{location}" : $headers->{location} ; return (($status eq '303' ? 'GET' : $method), $location); } return; } sub _split_url { my $url = pop; # URI regex adapted from the URI module my ($scheme, $host, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)> or die(qq/Cannot parse URL: '$url'\n/); $scheme = lc $scheme; $path_query = "/$path_query" unless $path_query =~ m<\A/>; my $auth = ''; if ( (my $i = index $host, '@') != -1 ) { # user:pass@host $auth = substr $host, 0, $i, ''; # take up to the @ for auth substr $host, 0, 1, ''; # knock the @ off the host # userinfo might be percent escaped, so recover real auth info $auth =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; } my $port = $host =~ s/:(\d*)\z// && length $1 ? $1 : $scheme eq 'http' ? 80 : $scheme eq 'https' ? 443 : undef; return ($scheme, (length $host ? lc $host : "localhost") , $port, $path_query, $auth); } # Date conversions adapted from HTTP::Date my $DoW = "Sun|Mon|Tue|Wed|Thu|Fri|Sat"; my $MoY = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec"; sub _http_date { my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($_[1]); return sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", substr($DoW,$wday*4,3), $mday, substr($MoY,$mon*4,3), $year+1900, $hour, $min, $sec ); } sub _parse_http_date { my ($self, $str) = @_; require Time::Local; my @tl_parts; if ($str =~ /^[SMTWF][a-z]+, +(\d{1,2}) ($MoY) +(\d\d\d\d) +(\d\d):(\d\d):(\d\d) +GMT$/) { @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); } elsif ($str =~ /^[SMTWF][a-z]+, +(\d\d)-($MoY)-(\d{2,4}) +(\d\d):(\d\d):(\d\d) +GMT$/ ) { @tl_parts = ($6, $5, $4, $1, (index($MoY,$2)/4), $3); } elsif ($str =~ /^[SMTWF][a-z]+ +($MoY) +(\d{1,2}) +(\d\d):(\d\d):(\d\d) +(?:[^0-9]+ +)?(\d\d\d\d)$/ ) { @tl_parts = ($5, $4, $3, $2, (index($MoY,$1)/4), $6); } return eval { my $t = @tl_parts ? Time::Local::timegm(@tl_parts) : -1; $t < 0 ? undef : $t; }; } # URI escaping adapted from URI::Escape # c.f. http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 # perl 5.6 ready UTF-8 encoding adapted from JSON::PP my %escapes = map { chr($_) => sprintf("%%%02X", $_) } 0..255; $escapes{' '}="+"; my $unsafe_char = qr/[^A-Za-z0-9\-\._~]/; sub _uri_escape { my ($self, $str) = @_; return "" if !defined $str; if ( $] ge '5.008' ) { utf8::encode($str); } else { $str = pack("U*", unpack("C*", $str)) # UTF-8 encode a byte string if ( length $str == do { use bytes; length $str } ); $str = pack("C*", unpack("C*", $str)); # clear UTF-8 flag } $str =~ s/($unsafe_char)/$escapes{$1}/g; return $str; } package HTTP::Tiny::Handle; # hide from PAUSE/indexers use strict; use warnings; use Errno qw[EINTR EPIPE]; use IO::Socket qw[SOCK_STREAM]; use Socket qw[SOL_SOCKET SO_KEEPALIVE]; # PERL_HTTP_TINY_IPV4_ONLY is a private environment variable to force old # behavior if someone is unable to boostrap CPAN from a new perl install; it is # not intended for general, per-client use and may be removed in the future my $SOCKET_CLASS = $ENV{PERL_HTTP_TINY_IPV4_ONLY} ? 'IO::Socket::INET' : eval { require IO::Socket::IP; IO::Socket::IP->VERSION(0.32) } ? 'IO::Socket::IP' : 'IO::Socket::INET'; sub BUFSIZE () { 32768 } ## no critic my $Printable = sub { local $_ = shift; s/\r/\\r/g; s/\n/\\n/g; s/\t/\\t/g; s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge; $_; }; my $Token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/; my $Field_Content = qr/[[:print:]]+ (?: [\x20\x09]+ [[:print:]]+ )*/x; sub new { my ($class, %args) = @_; return bless { rbuf => '', timeout => 60, max_line_size => 16384, max_header_lines => 64, verify_SSL => HTTP::Tiny::_verify_SSL_default(), SSL_options => {}, %args }, $class; } sub timeout { my ($self, $timeout) = @_; if ( @_ > 1 ) { $self->{timeout} = $timeout; if ( $self->{fh} && $self->{fh}->can('timeout') ) { $self->{fh}->timeout($timeout); } } return $self->{timeout}; } sub connect { @_ == 5 || die(q/Usage: $handle->connect(scheme, host, port, peer)/ . "\n"); my ($self, $scheme, $host, $port, $peer) = @_; if ( $scheme eq 'https' ) { $self->_assert_ssl; } $self->{fh} = $SOCKET_CLASS->new( PeerHost => $peer, PeerPort => $port, $self->{local_address} ? ( LocalAddr => $self->{local_address} ) : (), Proto => 'tcp', Type => SOCK_STREAM, Timeout => $self->{timeout}, ) or die(qq/Could not connect to '$host:$port': $@\n/); binmode($self->{fh}) or die(qq/Could not binmode() socket: '$!'\n/); if ( $self->{keep_alive} ) { unless ( defined( $self->{fh}->setsockopt( SOL_SOCKET, SO_KEEPALIVE, 1 ) ) ) { CORE::close($self->{fh}); die(qq/Could not set SO_KEEPALIVE on socket: '$!'\n/); } } $self->start_ssl($host) if $scheme eq 'https'; $self->{scheme} = $scheme; $self->{host} = $host; $self->{peer} = $peer; $self->{port} = $port; $self->{pid} = $$; $self->{tid} = _get_tid(); return $self; } sub connected { my ($self) = @_; if ( $self->{fh} && $self->{fh}->connected ) { return wantarray ? ( $self->{fh}->peerhost, $self->{fh}->peerport ) : join( ':', $self->{fh}->peerhost, $self->{fh}->peerport ); } return; } sub start_ssl { my ($self, $host) = @_; # As this might be used via CONNECT after an SSL session # to a proxy, we shut down any existing SSL before attempting # the handshake if ( ref($self->{fh}) eq 'IO::Socket::SSL' ) { unless ( $self->{fh}->stop_SSL ) { my $ssl_err = IO::Socket::SSL->errstr; die(qq/Error halting prior SSL connection: $ssl_err/); } } my $ssl_args = $self->_ssl_args($host); IO::Socket::SSL->start_SSL( $self->{fh}, %$ssl_args, SSL_create_ctx_callback => sub { my $ctx = shift; Net::SSLeay::CTX_set_mode($ctx, Net::SSLeay::MODE_AUTO_RETRY()); }, ); unless ( ref($self->{fh}) eq 'IO::Socket::SSL' ) { my $ssl_err = IO::Socket::SSL->errstr; die(qq/SSL connection failed for $host: $ssl_err\n/); } } sub close { @_ == 1 || die(q/Usage: $handle->close()/ . "\n"); my ($self) = @_; CORE::close($self->{fh}) or die(qq/Could not close socket: '$!'\n/); } sub write { @_ == 2 || die(q/Usage: $handle->write(buf)/ . "\n"); my ($self, $buf) = @_; if ( $] ge '5.008' ) { utf8::downgrade($buf, 1) or die(qq/Wide character in write()\n/); } my $len = length $buf; my $off = 0; local $SIG{PIPE} = 'IGNORE'; while () { $self->can_write or die(qq/Timed out while waiting for socket to become ready for writing\n/); my $r = syswrite($self->{fh}, $buf, $len, $off); if (defined $r) { $len -= $r; $off += $r; last unless $len > 0; } elsif ($! == EPIPE) { die(qq/Socket closed by remote server: $!\n/); } elsif ($! != EINTR) { if ($self->{fh}->can('errstr')){ my $err = $self->{fh}->errstr(); die (qq/Could not write to SSL socket: '$err'\n /); } else { die(qq/Could not write to socket: '$!'\n/); } } } return $off; } sub read { @_ == 2 || @_ == 3 || die(q/Usage: $handle->read(len [, allow_partial])/ . "\n"); my ($self, $len, $allow_partial) = @_; my $buf = ''; my $got = length $self->{rbuf}; if ($got) { my $take = ($got < $len) ? $got : $len; $buf = substr($self->{rbuf}, 0, $take, ''); $len -= $take; } # Ignore SIGPIPE because SSL reads can result in writes that might error. # See "Expecting exactly the same behavior as plain sockets" in # https://metacpan.org/dist/IO-Socket-SSL/view/lib/IO/Socket/SSL.pod#Common-Usage-Errors local $SIG{PIPE} = 'IGNORE'; while ($len > 0) { $self->can_read or die(q/Timed out while waiting for socket to become ready for reading/ . "\n"); my $r = sysread($self->{fh}, $buf, $len, length $buf); if (defined $r) { last unless $r; $len -= $r; } elsif ($! != EINTR) { if ($self->{fh}->can('errstr')){ my $err = $self->{fh}->errstr(); die (qq/Could not read from SSL socket: '$err'\n /); } else { die(qq/Could not read from socket: '$!'\n/); } } } if ($len && !$allow_partial) { die(qq/Unexpected end of stream\n/); } return $buf; } sub readline { @_ == 1 || die(q/Usage: $handle->readline()/ . "\n"); my ($self) = @_; while () { if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) { return $1; } if (length $self->{rbuf} >= $self->{max_line_size}) { die(qq/Line size exceeds the maximum allowed size of $self->{max_line_size}\n/); } $self->can_read or die(qq/Timed out while waiting for socket to become ready for reading\n/); my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf}); if (defined $r) { last unless $r; } elsif ($! != EINTR) { if ($self->{fh}->can('errstr')){ my $err = $self->{fh}->errstr(); die (qq/Could not read from SSL socket: '$err'\n /); } else { die(qq/Could not read from socket: '$!'\n/); } } } die(qq/Unexpected end of stream while looking for line\n/); } sub read_header_lines { @_ == 1 || @_ == 2 || die(q/Usage: $handle->read_header_lines([headers])/ . "\n"); my ($self, $headers) = @_; $headers ||= {}; my $lines = 0; my $val; while () { my $line = $self->readline; if (++$lines >= $self->{max_header_lines}) { die(qq/Header lines exceeds maximum number allowed of $self->{max_header_lines}\n/); } elsif ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) { my ($field_name) = lc $1; if (exists $headers->{$field_name}) { for ($headers->{$field_name}) { $_ = [$_] unless ref $_ eq "ARRAY"; push @$_, $2; $val = \$_->[-1]; } } else { $val = \($headers->{$field_name} = $2); } } elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) { $val or die(qq/Unexpected header continuation line\n/); next unless length $1; $$val .= ' ' if length $$val; $$val .= $1; } elsif ($line =~ /\A \x0D?\x0A \z/x) { last; } else { die(q/Malformed header line: / . $Printable->($line) . "\n"); } } return $headers; } sub write_request { @_ == 2 || die(q/Usage: $handle->write_request(request)/ . "\n"); my($self, $request) = @_; $self->write_request_header(@{$request}{qw/method uri headers header_case/}); $self->write_body($request) if $request->{cb}; return; } # Standard request header names/case from HTTP/1.1 RFCs my @rfc_request_headers = qw( Accept Accept-Charset Accept-Encoding Accept-Language Authorization Cache-Control Connection Content-Length Expect From Host If-Match If-Modified-Since If-None-Match If-Range If-Unmodified-Since Max-Forwards Pragma Proxy-Authorization Range Referer TE Trailer Transfer-Encoding Upgrade User-Agent Via ); my @other_request_headers = qw( Content-Encoding Content-MD5 Content-Type Cookie DNT Date Origin X-XSS-Protection ); my %HeaderCase = map { lc($_) => $_ } @rfc_request_headers, @other_request_headers; # to avoid multiple small writes and hence nagle, you can pass the method line or anything else to # combine writes. sub write_header_lines { (@_ >= 2 && @_ <= 4 && ref $_[1] eq 'HASH') || die(q/Usage: $handle->write_header_lines(headers, [header_case, prefix])/ . "\n"); my($self, $headers, $header_case, $prefix_data) = @_; $header_case ||= {}; my $buf = (defined $prefix_data ? $prefix_data : ''); # Per RFC, control fields should be listed first my %seen; for my $k ( qw/host cache-control expect max-forwards pragma range te/ ) { next unless exists $headers->{$k}; $seen{$k}++; my $field_name = $HeaderCase{$k}; my $v = $headers->{$k}; for (ref $v eq 'ARRAY' ? @$v : $v) { $_ = '' unless defined $_; $buf .= "$field_name: $_\x0D\x0A"; } } # Other headers sent in arbitrary order while (my ($k, $v) = each %$headers) { my $field_name = lc $k; next if $seen{$field_name}; if (exists $HeaderCase{$field_name}) { $field_name = $HeaderCase{$field_name}; } else { if (exists $header_case->{$field_name}) { $field_name = $header_case->{$field_name}; } else { $field_name =~ s/\b(\w)/\u$1/g; } $field_name =~ /\A $Token+ \z/xo or die(q/Invalid HTTP header field name: / . $Printable->($field_name) . "\n"); $HeaderCase{lc $field_name} = $field_name; } for (ref $v eq 'ARRAY' ? @$v : $v) { # unwrap a field value if pre-wrapped by user s/\x0D?\x0A\s+/ /g; die(qq/Invalid HTTP header field value ($field_name): / . $Printable->($_). "\n") unless $_ eq '' || /\A $Field_Content \z/xo; $_ = '' unless defined $_; $buf .= "$field_name: $_\x0D\x0A"; } } $buf .= "\x0D\x0A"; return $self->write($buf); } # return value indicates whether message length was defined; this is generally # true unless there was no content-length header and we just read until EOF. # Other message length errors are thrown as exceptions sub read_body { @_ == 3 || die(q/Usage: $handle->read_body(callback, response)/ . "\n"); my ($self, $cb, $response) = @_; my $te = $response->{headers}{'transfer-encoding'} || ''; my $chunked = grep { /chunked/i } ( ref $te eq 'ARRAY' ? @$te : $te ) ; return $chunked ? $self->read_chunked_body($cb, $response) : $self->read_content_body($cb, $response); } sub write_body { @_ == 2 || die(q/Usage: $handle->write_body(request)/ . "\n"); my ($self, $request) = @_; if (exists $request->{headers}{'content-length'}) { return unless $request->{headers}{'content-length'}; return $self->write_content_body($request); } else { return $self->write_chunked_body($request); } } sub read_content_body { @_ == 3 || @_ == 4 || die(q/Usage: $handle->read_content_body(callback, response, [read_length])/ . "\n"); my ($self, $cb, $response, $content_length) = @_; $content_length ||= $response->{headers}{'content-length'}; if ( defined $content_length ) { my $len = $content_length; while ($len > 0) { my $read = ($len > BUFSIZE) ? BUFSIZE : $len; $cb->($self->read($read, 0), $response); $len -= $read; } return length($self->{rbuf}) == 0; } my $chunk; $cb->($chunk, $response) while length( $chunk = $self->read(BUFSIZE, 1) ); return; } sub write_content_body { @_ == 2 || die(q/Usage: $handle->write_content_body(request)/ . "\n"); my ($self, $request) = @_; my ($len, $content_length) = (0, $request->{headers}{'content-length'}); while () { my $data = $request->{cb}->(); defined $data && length $data or last; if ( $] ge '5.008' ) { utf8::downgrade($data, 1) or die(qq/Wide character in write_content()\n/); } $len += $self->write($data); } $len == $content_length or die(qq/Content-Length mismatch (got: $len expected: $content_length)\n/); return $len; } sub read_chunked_body { @_ == 3 || die(q/Usage: $handle->read_chunked_body(callback, $response)/ . "\n"); my ($self, $cb, $response) = @_; while () { my $head = $self->readline; $head =~ /\A ([A-Fa-f0-9]+)/x or die(q/Malformed chunk head: / . $Printable->($head) . "\n"); my $len = hex($1) or last; $self->read_content_body($cb, $response, $len); $self->read(2) eq "\x0D\x0A" or die(qq/Malformed chunk: missing CRLF after chunk data\n/); } $self->read_header_lines($response->{headers}); return 1; } sub write_chunked_body { @_ == 2 || die(q/Usage: $handle->write_chunked_body(request)/ . "\n"); my ($self, $request) = @_; my $len = 0; while () { my $data = $request->{cb}->(); defined $data && length $data or last; if ( $] ge '5.008' ) { utf8::downgrade($data, 1) or die(qq/Wide character in write_chunked_body()\n/); } $len += length $data; my $chunk = sprintf '%X', length $data; $chunk .= "\x0D\x0A"; $chunk .= $data; $chunk .= "\x0D\x0A"; $self->write($chunk); } $self->write("0\x0D\x0A"); if ( ref $request->{trailer_cb} eq 'CODE' ) { $self->write_header_lines($request->{trailer_cb}->()) } else { $self->write("\x0D\x0A"); } return $len; } sub read_response_header { @_ == 1 || die(q/Usage: $handle->read_response_header()/ . "\n"); my ($self) = @_; my $line = $self->readline; $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) (?: [\x09\x20]+ ([^\x0D\x0A]*) )? \x0D?\x0A/x or die(q/Malformed Status-Line: / . $Printable->($line). "\n"); my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4); $reason = "" unless defined $reason; die (qq/Unsupported HTTP protocol: $protocol\n/) unless $version =~ /0*1\.0*[01]/; return { status => $status, reason => $reason, headers => $self->read_header_lines, protocol => $protocol, }; } sub write_request_header { @_ == 5 || die(q/Usage: $handle->write_request_header(method, request_uri, headers, header_case)/ . "\n"); my ($self, $method, $request_uri, $headers, $header_case) = @_; return $self->write_header_lines($headers, $header_case, "$method $request_uri HTTP/1.1\x0D\x0A"); } sub _do_timeout { my ($self, $type, $timeout) = @_; $timeout = $self->{timeout} unless defined $timeout && $timeout >= 0; my $fd = fileno $self->{fh}; defined $fd && $fd >= 0 or die(qq/select(2): 'Bad file descriptor'\n/); my $initial = time; my $pending = $timeout; my $nfound; vec(my $fdset = '', $fd, 1) = 1; while () { $nfound = ($type eq 'read') ? select($fdset, undef, undef, $pending) : select(undef, $fdset, undef, $pending) ; if ($nfound == -1) { $! == EINTR or die(qq/select(2): '$!'\n/); redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0; $nfound = 0; } last; } $! = 0; return $nfound; } sub can_read { @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_read([timeout])/ . "\n"); my $self = shift; if ( ref($self->{fh}) eq 'IO::Socket::SSL' ) { return 1 if $self->{fh}->pending; } return $self->_do_timeout('read', @_) } sub can_write { @_ == 1 || @_ == 2 || die(q/Usage: $handle->can_write([timeout])/ . "\n"); my $self = shift; return $self->_do_timeout('write', @_) } sub _assert_ssl { my($ok, $reason) = HTTP::Tiny->can_ssl(); die $reason unless $ok; } sub can_reuse { my ($self,$scheme,$host,$port,$peer) = @_; return 0 if $self->{pid} != $$ || $self->{tid} != _get_tid() || length($self->{rbuf}) || $scheme ne $self->{scheme} || $host ne $self->{host} || $port ne $self->{port} || $peer ne $self->{peer} || eval { $self->can_read(0) } || $@ ; return 1; } # Try to find a CA bundle to validate the SSL cert, # prefer Mozilla::CA or fallback to a system file sub _find_CA_file { my $self = shift(); my $ca_file = defined( $self->{SSL_options}->{SSL_ca_file} ) ? $self->{SSL_options}->{SSL_ca_file} : $ENV{SSL_CERT_FILE}; if ( defined $ca_file ) { unless ( -r $ca_file ) { die qq/SSL_ca_file '$ca_file' not found or not readable\n/; } return $ca_file; } local @INC = @INC; pop @INC if $INC[-1] eq '.'; return Mozilla::CA::SSL_ca_file() if eval { require Mozilla::CA; 1 }; # cert list copied from golang src/crypto/x509/root_unix.go foreach my $ca_bundle ( "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc. "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL "/etc/ssl/ca-bundle.pem", # OpenSUSE "/etc/openssl/certs/ca-certificates.crt", # NetBSD "/etc/ssl/cert.pem", # OpenBSD "/usr/local/share/certs/ca-root-nss.crt", # FreeBSD/DragonFly "/etc/pki/tls/cacert.pem", # OpenELEC "/etc/certs/ca-certificates.crt", # Solaris 11.2+ ) { return $ca_bundle if -e $ca_bundle; } die qq/Couldn't find a CA bundle with which to verify the SSL certificate.\n/ . qq/Try installing Mozilla::CA from CPAN\n/; } # for thread safety, we need to know thread id if threads are loaded sub _get_tid { no warnings 'reserved'; # for 'threads' return threads->can("tid") ? threads->tid : 0; } sub _ssl_args { my ($self, $host) = @_; my %ssl_args; # This test reimplements IO::Socket::SSL::can_client_sni(), which wasn't # added until IO::Socket::SSL 1.84 if ( Net::SSLeay::OPENSSL_VERSION_NUMBER() >= 0x01000000 ) { $ssl_args{SSL_hostname} = $host, # Sane SNI support } if ($self->{verify_SSL}) { $ssl_args{SSL_verifycn_scheme} = 'http'; # enable CN validation $ssl_args{SSL_verifycn_name} = $host; # set validation hostname $ssl_args{SSL_verify_mode} = 0x01; # enable cert validation $ssl_args{SSL_ca_file} = $self->_find_CA_file; } else { $ssl_args{SSL_verifycn_scheme} = 'none'; # disable CN validation $ssl_args{SSL_verify_mode} = 0x00; # disable cert validation } # user options override settings from verify_SSL for my $k ( keys %{$self->{SSL_options}} ) { $ssl_args{$k} = $self->{SSL_options}{$k} if $k =~ m/^SSL_/; } return \%ssl_args; } 1; __END__ =pod =encoding UTF-8 =head1 NAME HTTP::Tiny - A small, simple, correct HTTP/1.1 client =head1 VERSION version 0.088 =head1 SYNOPSIS use HTTP::Tiny; my $response = HTTP::Tiny->new->get('http://example.com/'); die "Failed!\n" unless $response->{success}; print "$response->{status} $response->{reason}\n"; while (my ($k, $v) = each %{$response->{headers}}) { for (ref $v eq 'ARRAY' ? @$v : $v) { print "$k: $_\n"; } } print $response->{content} if length $response->{content}; =head1 DESCRIPTION This is a very simple HTTP/1.1 client, designed for doing simple requests without the overhead of a large framework like L. It is more correct and more complete than L. It supports proxies and redirection. It also correctly resumes after EINTR. If L 0.25 or later is installed, HTTP::Tiny will use it instead of L for transparent support for both IPv4 and IPv6. Cookie support requires L or an equivalent class. =head1 METHODS =head2 new $http = HTTP::Tiny->new( %attributes ); This constructor returns a new HTTP::Tiny object. Valid attributes include: =over 4 =item * C — A user-agent string (defaults to 'HTTP-Tiny/$VERSION'). If C — ends in a space character, the default user-agent string is appended. =item * C — An instance of L — or equivalent class that supports the C and C methods =item * C — A hashref of default headers to apply to requests =item * C — The local IP address to bind to =item * C — Whether to reuse the last connection (if for the same scheme, host and port) (defaults to 1) =item * C — Maximum number of redirects allowed (defaults to 5) =item * C — Maximum response size in bytes (only when not using a data callback). If defined, requests with responses larger than this will return a 599 status code. =item * C — URL of a proxy server to use for HTTP connections (default is C<$ENV{http_proxy}> — if set) =item * C — URL of a proxy server to use for HTTPS connections (default is C<$ENV{https_proxy}> — if set) =item * C — URL of a generic proxy server for both HTTP and HTTPS connections (default is C<$ENV{all_proxy}> — if set) =item * C — List of domain suffixes that should not be proxied. Must be a comma-separated string or an array reference. (default is C<$ENV{no_proxy}> —) =item * C — Request timeout in seconds (default is 60) If a socket open, read or write takes longer than the timeout, the request response status code will be 599. =item * C — A boolean that indicates whether to validate the TLS/SSL certificate of an C — connection (default is true). Changed from false to true in version 0.083. =item * C — A hashref of C — options to pass through to L =item * C<$ENV{PERL_HTTP_TINY_SSL_INSECURE_BY_DEFAULT}> - Changes the default certificate verification behavior to not check server identity if set to 1. Only effective if C is not set. Added in version 0.083. =back An accessor/mutator method exists for each attribute. Passing an explicit C for C, C or C will prevent getting the corresponding proxies from the environment. Errors during request execution will result in a pseudo-HTTP status code of 599 and a reason of "Internal Exception". The content field in the response will contain the text of the error. The C parameter enables a persistent connection, but only to a single destination scheme, host and port. If any connection-relevant attributes are modified via accessor, or if the process ID or thread ID change, the persistent connection will be dropped. If you want persistent connections across multiple destinations, use multiple HTTP::Tiny objects. See L for more on the C and C attributes. =head2 get|head|put|post|patch|delete $response = $http->get($url); $response = $http->get($url, \%options); $response = $http->head($url); These methods are shorthand for calling C for the given method. The URL must have unsafe characters escaped and international domain names encoded. See C for valid options and a description of the response. The C field of the response will be true if the status code is 2XX. =head2 post_form $response = $http->post_form($url, $form_data); $response = $http->post_form($url, $form_data, \%options); This method executes a C request and sends the key/value pairs from a form data hash or array reference to the given URL with a C of C. If data is provided as an array reference, the order is preserved; if provided as a hash reference, the terms are sorted on key and value for consistency. See documentation for the C method for details on the encoding. The URL must have unsafe characters escaped and international domain names encoded. See C for valid options and a description of the response. Any C header or content in the options hashref will be ignored. The C field of the response will be true if the status code is 2XX. =head2 mirror $response = $http->mirror($url, $file, \%options) if ( $response->{success} ) { print "$file is up to date\n"; } Executes a C request for the URL and saves the response body to the file name provided. The URL must have unsafe characters escaped and international domain names encoded. If the file already exists, the request will include an C header with the modification timestamp of the file. You may specify a different C header yourself in the C<< $options->{headers} >> hash. The C field of the response will be true if the status code is 2XX or if the status code is 304 (unmodified). If the file was modified and the server response includes a properly formatted C header, the file modification time will be updated accordingly. =head2 request $response = $http->request($method, $url); $response = $http->request($method, $url, \%options); Executes an HTTP request of the given method type ('GET', 'HEAD', 'POST', 'PUT', etc.) on the given URL. The URL must have unsafe characters escaped and international domain names encoded. B: Method names are B per the HTTP/1.1 specification. Don't use C when you really want C. See L for how this applies to redirection. If the URL includes a "user:password" stanza, they will be used for Basic-style authorization headers. (Authorization headers will not be included in a redirected request.) For example: $http->request('GET', 'http://Aladdin:open sesame@example.com/'); If the "user:password" stanza contains reserved characters, they must be percent-escaped: $http->request('GET', 'http://john%40example.com:password@example.com/'); A hashref of options may be appended to modify the request. Valid options are: =over 4 =item * C — A hashref containing headers to include with the request. If the value for a header is an array reference, the header will be output multiple times with each value in the array. These headers over-write any default headers. =item * C — A scalar to include as the body of the request OR a code reference that will be called iteratively to produce the body of the request =item * C — A code reference that will be called if it exists to provide a hashref of trailing headers (only used with chunked transfer-encoding) =item * C — A code reference that will be called for each chunks of the response body received. =item * C — Override host resolution and force all connections to go only to a specific peer address, regardless of the URL of the request. This will include any redirections! This options should be used with extreme caution (e.g. debugging or very special circumstances). It can be given as either a scalar or a code reference that will receive the hostname and whose response will be taken as the address. =back The C header is generated from the URL in accordance with RFC 2616. It is a fatal error to specify C in the C option. Other headers may be ignored or overwritten if necessary for transport compliance. If the C option is a code reference, it will be called iteratively to provide the content body of the request. It should return the empty string or undef when the iterator is exhausted. If the C option is the empty string, no C or C headers will be generated. If the C option is provided, it will be called iteratively until the entire response body is received. The first argument will be a string containing a chunk of the response body, the second argument will be the in-progress response hash reference, as described below. (This allows customizing the action of the callback based on the C or C received prior to the content body.) Content data in the request/response is handled as "raw bytes". Any encoding/decoding (with associated headers) are the responsibility of the caller. The C method returns a hashref containing the response. The hashref will have the following keys: =over 4 =item * C — Boolean indicating whether the operation returned a 2XX status code =item * C — URL that provided the response. This is the URL of the request unless there were redirections, in which case it is the last URL queried in a redirection chain =item * C — The HTTP status code of the response =item * C — The response phrase returned by the server =item * C — The body of the response. If the response does not have any content or if a data callback is provided to consume the response body, this will be the empty string =item * C — A hashref of header fields. All header field names will be normalized to be lower case. If a header is repeated, the value will be an arrayref; it will otherwise be a scalar string containing the value =item * C - If this field exists, it is the protocol of the response such as HTTP/1.0 or HTTP/1.1 =item * C If this field exists, it is an arrayref of response hash references from redirects in the same order that redirections occurred. If it does not exist, then no redirections occurred. =back On an error during the execution of the request, the C field will contain 599, and the C field will contain the text of the error. =head2 www_form_urlencode $params = $http->www_form_urlencode( $data ); $response = $http->get("http://example.com/query?$params"); This method converts the key/value pairs from a data hash or array reference into a C string. The keys and values from the data reference will be UTF-8 encoded and escaped per RFC 3986. If a value is an array reference, the key will be repeated with each of the values of the array reference. If data is provided as a hash reference, the key/value pairs in the resulting string will be sorted by key and value for consistent ordering. =head2 can_ssl $ok = HTTP::Tiny->can_ssl; ($ok, $why) = HTTP::Tiny->can_ssl; ($ok, $why) = $http->can_ssl; Indicates if SSL support is available. When called as a class object, it checks for the correct version of L and L. When called as an object methods, if C is true or if C is set in C, it checks that a CA file is available. In scalar context, returns a boolean indicating if SSL is available. In list context, returns the boolean and a (possibly multi-line) string of errors indicating why SSL isn't available. =head2 connected $host = $http->connected; ($host, $port) = $http->connected; Indicates if a connection to a peer is being kept alive, per the C option. In scalar context, returns the peer host and port, joined with a colon, or C (if no peer is connected). In list context, returns the peer host and port or an empty list (if no peer is connected). B: This method cannot reliably be used to discover whether the remote host has closed its end of the socket. =for Pod::Coverage SSL_options agent cookie_jar default_headers http_proxy https_proxy keep_alive local_address max_redirect max_size no_proxy proxy timeout verify_SSL =head1 TLS/SSL SUPPORT Direct C connections are supported only if L 1.56 or greater and L 1.49 or greater are installed. An error will occur if new enough versions of these modules are not installed or if the TLS encryption fails. You can also use C utility function that returns boolean to see if the required modules are installed. An C connection may be made via an C proxy that supports the CONNECT command (i.e. RFC 2817). You may not proxy C via a proxy that itself requires C to communicate. TLS/SSL provides two distinct capabilities: =over 4 =item * Encrypted communication channel =item * Verification of server identity =back B. This was changed in version 0.083 due to security concerns. The previous default behavior can be enabled by setting C<$ENV{PERL_HTTP_TINY_SSL_INSECURE_BY_DEFAULT}> to 1. Verification is done by checking that that the TLS/SSL connection has a valid certificate corresponding to the host name of the connection and that the certificate has been verified by a CA. Assuming you trust the CA, this will protect against L. Certificate verification requires a file containing trusted CA certificates. If the environment variable C is present, HTTP::Tiny will try to find a CA certificate file in that location. If the L module is installed, HTTP::Tiny will use the CA file included with it as a source of trusted CA's. If that module is not available, then HTTP::Tiny will search several system-specific default locations for a CA certificate file: =over 4 =item * /etc/ssl/certs/ca-certificates.crt =item * /etc/pki/tls/certs/ca-bundle.crt =item * /etc/ssl/ca-bundle.pem =item * /etc/openssl/certs/ca-certificates.crt =item * /etc/ssl/cert.pem =item * /usr/local/share/certs/ca-root-nss.crt =item * /etc/pki/tls/cacert.pem =item * /etc/certs/ca-certificates.crt =back An error will be occur if C is true and no CA certificate file is available. If you desire complete control over TLS/SSL connections, the C attribute lets you provide a hash reference that will be passed through to C, overriding any options set by HTTP::Tiny. For example, to provide your own trusted CA file: SSL_options => { SSL_ca_file => $file_path, } The C attribute could also be used for such things as providing a client certificate for authentication to a server or controlling the choice of cipher used for the TLS/SSL connection. See L documentation for details. =head1 PROXY SUPPORT HTTP::Tiny can proxy both C and C requests. Only Basic proxy authorization is supported and it must be provided as part of the proxy URL: C. HTTP::Tiny supports the following proxy environment variables: =over 4 =item * http_proxy or HTTP_PROXY =item * https_proxy or HTTPS_PROXY =item * all_proxy or ALL_PROXY =back If the C environment variable is set, then this might be a CGI process and C would be set from the C header, which is a security risk. If C is set, C (the upper case variant only) is ignored, but C is considered instead. Tunnelling C over an C proxy using the CONNECT method is supported. If your proxy uses C itself, you can not tunnel C over it. Be warned that proxying an C connection opens you to the risk of a man-in-the-middle attack by the proxy server. The C environment variable is supported in the format of a comma-separated list of domain extensions proxy should not be used for. Proxy arguments passed to C will override their corresponding environment variables. =head1 LIMITATIONS HTTP::Tiny is I with the L: =over 4 =item * "Message Syntax and Routing" [RFC7230] =item * "Semantics and Content" [RFC7231] =item * "Conditional Requests" [RFC7232] =item * "Range Requests" [RFC7233] =item * "Caching" [RFC7234] =item * "Authentication" [RFC7235] =back It attempts to meet all "MUST" requirements of the specification, but does not implement all "SHOULD" requirements. (Note: it was developed against the earlier RFC 2616 specification and may not yet meet the revised RFC 7230-7235 spec.) Additionally, HTTP::Tiny supports the C method of RFC 5789. Some particular limitations of note include: =over =item * HTTP::Tiny focuses on correct transport. Users are responsible for ensuring that user-defined headers and content are compliant with the HTTP/1.1 specification. =item * Users must ensure that URLs are properly escaped for unsafe characters and that international domain names are properly encoded to ASCII. See L, L and L. =item * Redirection is very strict against the specification. Redirection is only automatic for response codes 301, 302, 307 and 308 if the request method is 'GET' or 'HEAD'. Response code 303 is always converted into a 'GET' redirection, as mandated by the specification. There is no automatic support for status 305 ("Use proxy") redirections. =item * There is no provision for delaying a request body using an C header. Unexpected C<1XX> responses are silently ignored as per the specification. =item * Only 'chunked' C is supported. =item * There is no support for a Request-URI of '*' for the 'OPTIONS' request. =item * Headers mentioned in the RFCs and some other, well-known headers are generated with their canonical case. Other headers are sent in the case provided by the user. Except for control headers (which are sent first), headers are sent in arbitrary order. =back Despite the limitations listed above, HTTP::Tiny is considered feature-complete. New feature requests should be directed to L. =head1 SEE ALSO =over 4 =item * L - Higher level UA features for HTTP::Tiny =item * L - HTTP::Tiny wrapper with L/L compatibility =item * L - Wrap L instance in HTTP::Tiny compatible interface =item * L - Required for IPv6 support =item * L - Required for SSL support =item * L - If HTTP::Tiny isn't enough for you, this is the "standard" way to do things =item * L - Required if you want to validate SSL certificates =item * L - Required for SSL support =back =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/HTTP-Tiny.git =head1 AUTHORS =over 4 =item * Christian Hansen =item * David Golden =back =head1 CONTRIBUTORS =for stopwords Alan Gardner Alessandro Ghedini A. Sinan Unur Brad Gilbert brian m. carlson Chris Nehren Weyl Claes Jakobsson Clinton Gormley Craig Berry David Golden Mitchell Dean Pearce Edward Zborowski Felipe Gasper Graham Knop Greg Kennedy James E Keenan Raspass Jeremy Mates Jess Robinson Karen Etheridge Lukas Eklund Martin J. Evans Martin-Louis Bright Matthew Horsfall Michael R. Davis Mike Doherty Nicolas Rochelemagne Olaf Alders Olivier Mengué Petr Písař sanjay-cpu Serguei Trouchelle Shoichi Kaji SkyMarshal Sören Kornetzki Steve Grazzini Stig Palmquist Syohei YOSHIDA Tatsuhiko Miyagawa Tom Hukins Tony Cook Xavier Guimard =over 4 =item * Alan Gardner =item * Alessandro Ghedini =item * A. Sinan Unur =item * Brad Gilbert =item * brian m. carlson =item * Chris Nehren =item * Chris Weyl =item * Claes Jakobsson =item * Clinton Gormley =item * Craig A. Berry =item * Craig Berry =item * David Golden =item * David Mitchell =item * Dean Pearce =item * Edward Zborowski =item * Felipe Gasper =item * Graham Knop =item * Greg Kennedy =item * James E Keenan =item * James Raspass =item * Jeremy Mates =item * Jess Robinson =item * Karen Etheridge =item * Lukas Eklund =item * Martin J. Evans =item * Martin-Louis Bright =item * Matthew Horsfall =item * Michael R. Davis =item * Mike Doherty =item * Nicolas Rochelemagne =item * Olaf Alders =item * Olivier Mengué =item * Petr Písař =item * sanjay-cpu =item * Serguei Trouchelle =item * Shoichi Kaji =item * SkyMarshal =item * Sören Kornetzki =item * Steve Grazzini =item * Stig Palmquist =item * Syohei YOSHIDA =item * Tatsuhiko Miyagawa =item * Tom Hukins =item * Tony Cook =item * Xavier Guimard =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2023 by Christian Hansen. 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 PK!8o>>perl5/HTTP/Negotiate.pmnu6$package HTTP::Negotiate; $VERSION = "6.01"; sub Version { $VERSION; } require Exporter; @ISA = qw(Exporter); @EXPORT = qw(choose); require HTTP::Headers; $DEBUG = 0; sub choose ($;$) { my($variants, $request) = @_; my(%accept); unless (defined $request) { # Create a request object from the CGI environment variables $request = HTTP::Headers->new; $request->header('Accept', $ENV{HTTP_ACCEPT}) if $ENV{HTTP_ACCEPT}; $request->header('Accept-Charset', $ENV{HTTP_ACCEPT_CHARSET}) if $ENV{HTTP_ACCEPT_CHARSET}; $request->header('Accept-Encoding', $ENV{HTTP_ACCEPT_ENCODING}) if $ENV{HTTP_ACCEPT_ENCODING}; $request->header('Accept-Language', $ENV{HTTP_ACCEPT_LANGUAGE}) if $ENV{HTTP_ACCEPT_LANGUAGE}; } # Get all Accept values from the request. Build a hash initialized # like this: # # %accept = ( type => { 'audio/*' => { q => 0.2, mbx => 20000 }, # 'audio/basic' => { q => 1 }, # }, # language => { 'no' => { q => 1 }, # } # ); $request->scan(sub { my($key, $val) = @_; my $type; if ($key =~ s/^Accept-//) { $type = lc($key); } elsif ($key eq "Accept") { $type = "type"; } else { return; } $val =~ s/\s+//g; my $default_q = 1; for my $name (split(/,/, $val)) { my(%param, $param); if ($name =~ s/;(.*)//) { for $param (split(/;/, $1)) { my ($pk, $pv) = split(/=/, $param, 2); $param{lc $pk} = $pv; } } $name = lc $name; if (defined $param{'q'}) { $param{'q'} = 1 if $param{'q'} > 1; $param{'q'} = 0 if $param{'q'} < 0; } else { $param{'q'} = $default_q; # This makes sure that the first ones are slightly better off # and therefore more likely to be chosen. $default_q -= 0.0001; } $accept{$type}{$name} = \%param; } }); # Check if any of the variants specify a language. We do this # because it influences how we treat those without (they default to # 0.5 instead of 1). my $any_lang = 0; for $var (@$variants) { if ($var->[5]) { $any_lang = 1; last; } } if ($DEBUG) { print "Negotiation parameters in the request\n"; for $type (keys %accept) { print " $type:\n"; for $name (keys %{$accept{$type}}) { print " $name\n"; for $pv (keys %{$accept{$type}{$name}}) { print " $pv = $accept{$type}{$name}{$pv}\n"; } } } } my @Q = (); # This is where we collect the results of the # quality calculations # Calculate quality for all the variants that are available. for (@$variants) { my($id, $qs, $ct, $enc, $cs, $lang, $bs) = @$_; $qs = 1 unless defined $qs; $ct = '' unless defined $ct; $bs = 0 unless defined $bs; $lang = lc($lang) if $lang; # lg tags are always case-insensitive if ($DEBUG) { print "\nEvaluating $id (ct='$ct')\n"; printf " qs = %.3f\n", $qs; print " enc = $enc\n" if $enc && !ref($enc); print " enc = @$enc\n" if $enc && ref($enc); print " cs = $cs\n" if $cs; print " lang = $lang\n" if $lang; print " bs = $bs\n" if $bs; } # Calculate encoding quality my $qe = 1; # If the variant has no assigned Content-Encoding, or if no # Accept-Encoding field is present, then the value assigned # is "qe=1". If *all* of the variant's content encodings # are listed in the Accept-Encoding field, then the value # assigned is "qw=1". If *any* of the variant's content # encodings are not listed in the provided Accept-Encoding # field, then the value assigned is "qe=0" if (exists $accept{'encoding'} && $enc) { my @enc = ref($enc) ? @$enc : ($enc); for (@enc) { print "Is encoding $_ accepted? " if $DEBUG; unless(exists $accept{'encoding'}{$_}) { print "no\n" if $DEBUG; $qe = 0; last; } else { print "yes\n" if $DEBUG; } } } # Calculate charset quality my $qc = 1; # If the variant's media-type has no charset parameter, # or the variant's charset is US-ASCII, or if no Accept-Charset # field is present, then the value assigned is "qc=1". If the # variant's charset is listed in the Accept-Charset field, # then the value assigned is "qc=1. Otherwise, if the variant's # charset is not listed in the provided Accept-Encoding field, # then the value assigned is "qc=0". if (exists $accept{'charset'} && $cs && $cs ne 'us-ascii' ) { $qc = 0 unless $accept{'charset'}{$cs}; } # Calculate language quality my $ql = 1; if ($lang && exists $accept{'language'}) { my @lang = ref($lang) ? @$lang : ($lang); # If any of the variant's content languages are listed # in the Accept-Language field, the the value assigned is # the largest of the "q" parameter values for those language # tags. my $q = undef; for (@lang) { next unless exists $accept{'language'}{$_}; my $this_q = $accept{'language'}{$_}{'q'}; $q = $this_q unless defined $q; $q = $this_q if $this_q > $q; } if(defined $q) { $DEBUG and print " -- Exact language match at q=$q\n"; } else { # If there was no exact match and at least one of # the Accept-Language field values is a complete # subtag prefix of the content language tag(s), then # the "q" parameter value of the largest matching # prefix is used. $DEBUG and print " -- No exact language match\n"; my $selected = undef; for $al (keys %{ $accept{'language'} }) { if (index($al, "$lang-") == 0) { # $lang starting with $al isn't enough, or else # Accept-Language: hu (Hungarian) would seem # to accept a document in hup (Hupa) $DEBUG and print " -- $al ISA $lang\n"; $selected = $al unless defined $selected; $selected = $al if length($al) > length($selected); } else { $DEBUG and print " -- $lang isn't a $al\n"; } } $q = $accept{'language'}{$selected}{'q'} if $selected; # If none of the variant's content language tags or # tag prefixes are listed in the provided # Accept-Language field, then the value assigned # is "ql=0.001" $q = 0.001 unless defined $q; } $ql = $q; } else { $ql = 0.5 if $any_lang && exists $accept{'language'}; } my $q = 1; my $mbx = undef; # If no Accept field is given, then the value assigned is "q=1". # If at least one listed media range matches the variant's media # type, then the "q" parameter value assigned to the most specific # of those matched is used (e.g. "text/html;version=3.0" is more # specific than "text/html", which is more specific than "text/*", # which in turn is more specific than "*/*"). If not media range # in the provided Accept field matches the variant's media type, # then the value assigned is "q=0". if (exists $accept{'type'} && $ct) { # First we clean up our content-type $ct =~ s/\s+//g; my $params = ""; $params = $1 if $ct =~ s/;(.*)//; my($type, $subtype) = split("/", $ct, 2); my %param = (); for $param (split(/;/, $params)) { my($pk,$pv) = split(/=/, $param, 2); $param{$pk} = $pv; } my $sel_q = undef; my $sel_mbx = undef; my $sel_specificness = 0; ACCEPT_TYPE: for $at (keys %{ $accept{'type'} }) { print "Consider $at...\n" if $DEBUG; my($at_type, $at_subtype) = split("/", $at, 2); # Is it a match on the type next if $at_type ne '*' && $at_type ne $type; next if $at_subtype ne '*' && $at_subtype ne $subtype; my $specificness = 0; $specificness++ if $at_type ne '*'; $specificness++ if $at_subtype ne '*'; # Let's see if content-type parameters also match while (($pk, $pv) = each %param) { print "Check if $pk = $pv is true\n" if $DEBUG; next unless exists $accept{'type'}{$at}{$pk}; next ACCEPT_TYPE unless $accept{'type'}{$at}{$pk} eq $pv; print "yes it is!!\n" if $DEBUG; $specificness++; } print "Hurray, type match with specificness = $specificness\n" if $DEBUG; if (!defined($sel_q) || $sel_specificness < $specificness) { $sel_q = $accept{'type'}{$at}{'q'}; $sel_mbx = $accept{'type'}{$at}{'mbx'}; $sel_specificness = $specificness; } } $q = $sel_q || 0; $mbx = $sel_mbx; } my $Q; if (!defined($mbx) || $mbx >= $bs) { $Q = $qs * $qe * $qc * $ql * $q; } else { $Q = 0; print "Variant's size is too large ==> Q=0\n" if $DEBUG; } if ($DEBUG) { $mbx = "undef" unless defined $mbx; printf "Q=%.4f", $Q; print " (q=$q, mbx=$mbx, qe=$qe, qc=$qc, ql=$ql, qs=$qs)\n"; } push(@Q, [$id, $Q, $bs]); } @Q = sort { $b->[1] <=> $a->[1] || $a->[2] <=> $b->[2] } @Q; return @Q if wantarray; return undef unless @Q; return undef if $Q[0][1] == 0; $Q[0][0]; } 1; __END__ =head1 NAME HTTP::Negotiate - choose a variant to serve =head1 SYNOPSIS use HTTP::Negotiate qw(choose); # ID QS Content-Type Encoding Char-Set Lang Size $variants = [['var1', 1.000, 'text/html', undef, 'iso-8859-1', 'en', 3000], ['var2', 0.950, 'text/plain', 'gzip', 'us-ascii', 'no', 400], ['var3', 0.3, 'image/gif', undef, undef, undef, 43555], ]; @preferred = choose($variants, $request_headers); $the_one = choose($variants); =head1 DESCRIPTION This module provides a complete implementation of the HTTP content negotiation algorithm specified in F chapter 12. Content negotiation allows for the selection of a preferred content representation based upon attributes of the negotiable variants and the value of the various Accept* header fields in the request. The variants are ordered by preference by calling the function choose(). The first parameter is reference to an array of the variants to choose among. Each element in this array is an array with the values [$id, $qs, $content_type, $content_encoding, $charset, $content_language, $content_length] whose meanings are described below. The $content_encoding and $content_language can be either a single scalar value or an array reference if there are several values. The second optional parameter is either a HTTP::Headers or a HTTP::Request object which is searched for "Accept*" headers. If this parameter is missing, then the accept specification is initialized from the CGI environment variables HTTP_ACCEPT, HTTP_ACCEPT_CHARSET, HTTP_ACCEPT_ENCODING and HTTP_ACCEPT_LANGUAGE. In an array context, choose() returns a list of [variant identifier, calculated quality, size] tuples. The values are sorted by quality, highest quality first. If the calculated quality is the same for two variants, then they are sorted by size (smallest first). I: (['var1', 1, 2000], ['var2', 0.3, 512], ['var3', 0.3, 1024]); Note that also zero quality variants are included in the return list even if these should never be served to the client. In a scalar context, it returns the identifier of the variant with the highest score or C if none have non-zero quality. If the $HTTP::Negotiate::DEBUG variable is set to TRUE, then a lot of noise is generated on STDOUT during evaluation of choose(). =head1 VARIANTS A variant is described by a list of the following values. If the attribute does not make sense or is unknown for a variant, then use C instead. =over 3 =item identifier This is a string that you use as the name for the variant. This identifier for the preferred variants returned by choose(). =item qs This is a number between 0.000 and 1.000 that describes the "source quality". This is what F says about this value: Source quality is measured by the content provider as representing the amount of degradation from the original source. For example, a picture in JPEG form would have a lower qs when translated to the XBM format, and much lower qs when translated to an ASCII-art representation. Note, however, that this is a function of the source - an original piece of ASCII-art may degrade in quality if it is captured in JPEG form. The qs values should be assigned to each variant by the content provider; if no qs value has been assigned, the default is generally "qs=1". =item content-type This is the media type of the variant. The media type does not include a charset attribute, but might contain other parameters. Examples are: text/html text/html;version=2.0 text/plain image/gif image/jpg =item content-encoding This is one or more content encodings that has been applied to the variant. The content encoding is generally used as a modifier to the content media type. The most common content encodings are: gzip compress =item content-charset This is the character set used when the variant contains text. The charset value should generally be C or one of these: us-ascii iso-8859-1 ... iso-8859-9 iso-2022-jp iso-2022-jp-2 iso-2022-kr unicode-1-1 unicode-1-1-utf-7 unicode-1-1-utf-8 =item content-language This describes one or more languages that are used in the variant. Language is described like this in F: A language is in this context a natural language spoken, written, or otherwise conveyed by human beings for communication of information to other human beings. Computer languages are explicitly excluded. The language tags are defined by RFC 3066. Examples are: no Norwegian en International English en-US US English en-cockney =item content-length This is the number of bytes used to represent the content. =back =head1 ACCEPT HEADERS The following Accept* headers can be used for describing content preferences in a request (This description is an edited extract from F): =over 3 =item Accept This header can be used to indicate a list of media ranges which are acceptable as a response to the request. The "*" character is used to group media types into ranges, with "*/*" indicating all media types and "type/*" indicating all subtypes of that type. The parameter q is used to indicate the quality factor, which represents the user's preference for that range of media types. The parameter mbx gives the maximum acceptable size of the response content. The default values are: q=1 and mbx=infinity. If no Accept header is present, then the client accepts all media types with q=1. For example: Accept: audio/*;q=0.2;mbx=200000, audio/basic would mean: "I prefer audio/basic (of any size), but send me any audio type if it is the best available after an 80% mark-down in quality and its size is less than 200000 bytes" =item Accept-Charset Used to indicate what character sets are acceptable for the response. The "us-ascii" character set is assumed to be acceptable for all user agents. If no Accept-Charset field is given, the default is that any charset is acceptable. Example: Accept-Charset: iso-8859-1, unicode-1-1 =item Accept-Encoding Restricts the Content-Encoding values which are acceptable in the response. If no Accept-Encoding field is present, the server may assume that the client will accept any content encoding. An empty Accept-Encoding means that no content encoding is acceptable. Example: Accept-Encoding: compress, gzip =item Accept-Language This field is similar to Accept, but restricts the set of natural languages that are preferred in a response. Each language may be given an associated quality value which represents an estimate of the user's comprehension of that language. For example: Accept-Language: no, en-gb;q=0.8, de;q=0.55 would mean: "I prefer Norwegian, but will accept British English (with 80% comprehension) or German (with 55% comprehension). =back =head1 COPYRIGHT Copyright 1996,2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Gisle Aas =cut PK!PY!!perl5/Test/Needs.pmnu6$package Test::Needs; use strict; use warnings; no warnings 'once'; our $VERSION = '0.002010'; $VERSION =~ tr/_//d; BEGIN { *_WORK_AROUND_HINT_LEAKAGE = "$]" < 5.011 && !("$]" >= 5.009004 && "$]" < 5.010001) ? sub(){1} : sub(){0}; *_WORK_AROUND_BROKEN_MODULE_STATE = "$]" < 5.009 ? sub(){1} : sub(){0}; # this allows regexes to match wide characters in vstrings if ("$]" >= 5.006001 && "$]" <= 5.006002) { require utf8; utf8->import; } } our @EXPORT = qw(test_needs); our $Level = 0; sub _try_require { local %^H if _WORK_AROUND_HINT_LEAKAGE; my ($module) = @_; (my $file = "$module.pm") =~ s{::|'}{/}g; my $err; { local $@; eval { require $file } or $err = $@; } if (defined $err) { delete $INC{$file} if _WORK_AROUND_BROKEN_MODULE_STATE; die $err unless $err =~ /\ACan't locate \Q$file\E/; return !1; } !0; } sub _croak { my $message = join '', @_; my $i = 1; while (my ($p, $f, $l) = caller($i++)) { next if $p =~ /\ATest::Needs(?:::|\z)/; die "$message at $f line $l.\n"; } die $message; } sub _try_version { my ($module, $version) = @_; local $@; !!eval { $module->VERSION($version); 1 }; } sub _numify_version { for ($_[0]) { return !$_ ? 0 : /^[0-9]+(?:\.[0-9]+)?$/ ? sprintf('%.6f', $_) : /^v?([0-9]+(?:\.[0-9]+)*)$/ ? sprintf('%d.%03d%03d', ((split /\./, $1), 0, 0)[0..2]) : /^([\x05-\x07])(.*)$/s ? sprintf('%d.%03d%03d', ((map ord, /(.)/gs), 0, 0)[0..2]) : _croak qq{version "$_" does not look like a number}; } } sub _find_missing { my @bad = map { my ($module, $version) = @$_; $module eq 'perl' ? do { $version = _numify_version($version); "$]" < $version ? (sprintf "perl %s (have %.6f)", $version, $]) : () } : $module =~ /^\d|[^\w:]|:::|[^:]:[^:]|^:|:$/ ? _croak sprintf qq{"%s" does not look like a module name}, $module : _try_require($module) ? ( defined $version && !_try_version($module, $version) ? "$module $version (have ".(defined $module->VERSION ? $module->VERSION : 'undef').')' : () ) : $version ? "$module $version" : $module; } _pairs(@_); @bad ? "Need " . join(', ', @bad) : undef; } sub import { my $class = shift; my $target = caller; if (@_) { local $Level = $Level + 1; test_needs(@_); } no strict 'refs'; *{"${target}::$_"} = \&{"${class}::$_"} for @{"${class}::EXPORT"}; } sub test_needs { my $missing = _find_missing(@_); local $Level = $Level + 1; if ($missing) { if ($ENV{RELEASE_TESTING}) { _fail("$missing due to RELEASE_TESTING"); } else { _skip($missing); } } return 1; } sub _skip { local $Level = $Level + 1; _fail_or_skip($_[0], 0) } sub _fail { local $Level = $Level + 1; _fail_or_skip($_[0], 1) } sub _pairs { map +( ref eq 'HASH' ? do { my $arg = $_; map [ $_ => $arg->{$_} ], sort keys %$arg; } : ref eq 'ARRAY' ? do { my $arg = $_; map [ @{$arg}[$_*2,$_*2+1] ], 0 .. int($#$arg / 2); } : [ $_ ] ), @_; } sub _fail_or_skip { my ($message, $fail) = @_; if ($INC{'Test2/API.pm'}) { my $ctx = Test2::API::context(level => $Level); my $hub = $ctx->hub; if ($fail) { $ctx->ok(0, "Test::Needs modules available", [$message]); } else { my $plan = $hub->plan; my $tests = $hub->count; if ($plan || $tests) { my $skips = $plan && $plan ne 'NO PLAN' ? $plan - $tests : 1; $ctx->skip("Test::Needs modules not available") for 1 .. $skips; $ctx->note($message); } else { $ctx->plan(0, 'SKIP', $message); } } $ctx->done_testing; $ctx->release if $Test2::API::VERSION < 1.302053; $ctx->send_event('+'._t2_terminate_event()); } elsif ($INC{'Test/Builder.pm'}) { local $Test::Builder::Level = $Test::Builder::Level + $Level; my $tb = Test::Builder->new; my $has_plan = Test::Builder->can('has_plan') ? 'has_plan' : sub { $_[0]->expected_tests || eval { $_[0]->current_test($_[0]->current_test); 'no_plan' } }; my $tests = $tb->current_test; if ($fail) { $tb->plan(tests => 1) unless $tb->$has_plan; $tests++; $tb->ok(0, "Test::Needs modules available"); $tb->diag($message); } else { my $plan = $tb->$has_plan; if ($plan || $tests) { my $skips = $plan && $plan ne 'no_plan' ? $plan - $tests : 1; $tb->skip("Test::Needs modules not available") for 1 .. $skips; $tests += $skips; Test::Builder->can('note') ? $tb->note($message) : print "# $message\n"; } else { $tb->skip_all($message); } } $tb->done_testing($tests) if Test::Builder->can('done_testing'); die bless {} => 'Test::Builder::Exception' if Test::Builder->can('parent') && $tb->parent; } else { if ($fail) { print "1..1\n"; print "not ok 1 - Test::Needs modules available\n"; print STDERR "# $message\n"; exit 1; } else { print "1..0 # SKIP $message\n"; } } exit 0; } my $terminate_event; sub _t2_terminate_event () { return $terminate_event if $terminate_event; local $@; $terminate_event = eval sprintf <<'END_CODE', __LINE__+2, __FILE__ or die "$@"; #line %d "%s" package # hide Test::Needs::Event::Terminate; use Test2::Event (); our @ISA = qw(Test2::Event); sub no_display { 1 } sub terminate { 0 } __PACKAGE__; END_CODE (my $pm = "$terminate_event.pm") =~ s{::}{/}g; $INC{$pm} = __FILE__; $terminate_event; } 1; __END__ =pod =encoding utf-8 =head1 NAME Test::Needs - Skip tests when modules not available =head1 SYNOPSIS # need one module use Test::Needs 'Some::Module'; # need multiple modules use Test::Needs 'Some::Module', 'Some::Other::Module'; # need a given version of a module use Test::Needs { 'Some::Module' => '1.005', }; # check later use Test::Needs; test_needs 'Some::Module'; # skips remainder of subtest use Test::More; use Test::Needs; subtest 'my subtest' => sub { test_needs 'Some::Module'; ... }; # check perl version use Test::Needs { perl => 5.020 }; =head1 DESCRIPTION Skip test scripts if modules are not available. The requested modules will be loaded, and optionally have their versions checked. If the module is missing, the test script will be skipped. Modules that are found but fail to compile will exit with an error rather than skip. If used in a subtest, the remainder of the subtest will be skipped. Skipping will work even if some tests have already been run, or if a plan has been declared. Versions are checked via a C<< $module->VERSION($wanted_version) >> call. Versions must be provided in a format that will be accepted. No extra processing is done on them. If C is used as a module, the version is checked against the running perl version (L<$]|perlvar/$]>). The version can be specified as a number, dotted-decimal string, v-string, or version object. If the C environment variable is set, the tests will fail rather than skip. Subtests will be aborted, but the test script will continue running after that point. =head1 EXPORTS =head2 test_needs Has the same interface as when using Test::Needs in a C. =head1 SEE ALSO =over 4 =item L A similar module, with some important differences. L will act as a C statement (despite its name), calling the import sub. Under C, it will BAIL_OUT if a module fails to load rather than using a normal test fail. It also doesn't distinguish between missing modules and broken modules. =item L Part of the L ecosystem. Only supports running as a C command to skip an entire plan. =item L Part of the L ecosystem. Only supports running as a C command to skip an entire plan. Checks perl versions. =item L Acts as a C statement. Only supports running as a C command to skip an entire plan. Can skip based on subref results. =back =head1 AUTHORS haarg - Graham Knop (cpan:HAARG) =head1 CONTRIBUTORS None so far. =head1 COPYRIGHT AND LICENSE Copyright (c) 2016 the Test::Needs L and L as listed above. This library is free software and may be distributed under the same terms as perl itself. See L. =cut PK!_oAttperl5/Test/Alien.pmnu6$package Test::Alien; use strict; use warnings; use 5.008004; use Env qw( @PATH ); use File::Which 1.10 qw( which ); use Capture::Tiny qw( capture capture_merged ); use Alien::Build::Temp; use File::Copy qw( move ); use Text::ParseWords qw( shellwords ); use Test2::API qw( context run_subtest ); use Exporter qw( import ); use Path::Tiny qw( path ); use Alien::Build::Util qw( _dump ); use Config; our @EXPORT = qw( alien_ok run_ok xs_ok ffi_ok with_subtest synthetic helper_ok interpolate_template_is interpolate_run_ok plugin_ok ); # ABSTRACT: Testing tools for Alien modules our $VERSION = '2.80'; # VERSION our @aliens; sub alien_ok ($;$) { my($alien, $message) = @_; my $name = ref $alien ? ref($alien) . '[instance]' : $alien; $name = 'undef' unless defined $name; my @methods = qw( cflags libs dynamic_libs bin_dir ); $message ||= "$name responds to: @methods"; my $ok; my @diag; if(defined $alien) { my @missing = grep { ! $alien->can($_) } @methods; $ok = !@missing; push @diag, map { " missing method $_" } @missing; if($ok) { push @aliens, $alien; if($^O eq 'MSWin32' && $alien->isa('Alien::MSYS')) { unshift @PATH, Alien::MSYS::msys_path(); } else { unshift @PATH, $alien->bin_dir; } } if($alien->can('alien_helper')) { my($intr) = _interpolator(); my $help = eval { $alien->alien_helper }; if(my $error = $@) { $ok = 0; push @diag, " error getting helpers: $error"; } foreach my $name (keys %$help) { my $code = $help->{$name}; $intr->replace_helper($name, $code); } } } else { $ok = 0; push @diag, " undefined alien"; } my $ctx = context(); $ctx->ok($ok, $message); $ctx->diag($_) for @diag; $ctx->release; $ok; } sub synthetic { my($opt) = @_; $opt ||= {}; my %alien = %$opt; require Test::Alien::Synthetic; bless \%alien, 'Test::Alien::Synthetic', } sub run_ok { my($command, $message) = @_; my(@command) = ref $command ? @$command : (do { my $command = $command; # make a copy # Double the backslashes so that when they are unescaped by shellwords(), # they become a single backslash. This should be fine on Windows since # backslashes are not used to escape metacharacters in cmd.exe. $command =~ s/\\/\\\\/g if $^O eq 'MSWin32'; shellwords $command; }); $message ||= ref $command ? "run @command" : "run $command"; require Test::Alien::Run; my $run = bless { out => '', err => '', exit => 0, sig => 0, cmd => [@command], }, 'Test::Alien::Run'; my $ctx = context(); my $exe = which $command[0]; if(defined $exe) { if(ref $command) { shift @command; $run->{cmd} = [$exe, @command]; } else { $run->{cmd} = [$command]; } my @diag; my $ok = 1; my($exit, $errno); ($run->{out}, $run->{err}, $exit, $errno) = capture { if(ref $command) { system $exe, @command; } else { system $command; } ($?,$!); }; if($exit == -1) { $ok = 0; $run->{fail} = "failed to execute: $errno"; push @diag, " failed to execute: $errno"; } elsif($exit & 127) { $ok = 0; push @diag, " killed with signal: @{[ $exit & 127 ]}"; $run->{sig} = $exit & 127; } else { $run->{exit} = $exit >> 8; } $ctx->ok($ok, $message); $ok ? $ctx->note(" using $exe") : $ctx->diag(" using $exe"); $ctx->diag(@diag) for @diag; } else { $ctx->ok(0, $message); $ctx->diag(" command not found"); $run->{fail} = 'command not found'; } unless(@aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}) { $ctx->diag("run_ok called without any aliens, you may want to call alien_ok"); } $ctx->release; $run; } sub _flags { my($class, $method) = @_; my $static = "${method}_static"; $class->can($static) && $class->can('install_type') && $class->install_type eq 'share' && (!$class->can('xs_load')) ? $class->$static : $class->$method; } sub xs_ok { my $cb; $cb = pop if defined $_[-1] && ref $_[-1] eq 'CODE'; my($xs, $message) = @_; $message ||= 'xs'; $xs = { xs => $xs } unless ref $xs; # make sure this is a copy because we may # modify it. $xs->{xs} = "@{[ $xs->{xs} ]}"; $xs->{pxs} ||= {}; $xs->{cbuilder_check} ||= 'have_compiler'; $xs->{cbuilder_config} ||= {}; $xs->{cbuilder_compile} ||= {}; $xs->{cbuilder_link} ||= {}; require ExtUtils::CBuilder; my $skip = do { my $have_compiler = $xs->{cbuilder_check}; my %config = %{ $xs->{cbuilder_config} }; !ExtUtils::CBuilder->new( config => \%config )->$have_compiler; }; if($skip) { my $ctx = context(); $ctx->skip($message, 'test requires a compiler'); $ctx->skip("$message subtest", 'test requires a compiler') if $cb; $ctx->release; return; } if($xs->{cpp} || $xs->{'C++'}) { my $ctx = context(); $ctx->bail("The cpp and C++ options have been removed from xs_ok"); } else { $xs->{c_ext} ||= 'c'; } my $verbose = $xs->{verbose} || 0; my $ok = 1; my @diag; my $dir = Alien::Build::Temp->newdir( TEMPLATE => 'test-alien-XXXXXX', CLEANUP => $^O =~ /^(MSWin32|cygwin|msys)$/ ? 0 : 1, ); my $xs_filename = path($dir)->child('test.xs')->stringify; my $c_filename = path($dir)->child("test.@{[ $xs->{c_ext} ]}")->stringify; my $ctx = context(); my $module; if($ENV{TEST_ALIEN_ALWAYS_KEEP}) { $dir->unlink_on_destroy(0); $ctx->note("keeping XS temporary directory $dir at user request"); } if($xs->{xs} =~ /\bTA_MODULE\b/) { our $count; $count = 0 unless defined $count; my $name = sprintf "Test::Alien::XS::Mod%s%s", $count, chr(65 + $count % 26 ) x 4; $count++; my $code = $xs->{xs}; $code =~ s{\bTA_MODULE\b}{$name}g; $xs->{xs} = $code; } # this regex copied shamefully from ExtUtils::ParseXS # in part because we need the module name to do the bootstrap # and also because if this regex doesn't match then ParseXS # does an exit() which we don't want. if($xs->{xs} =~ /^MODULE\s*=\s*([\w:]+)(?:\s+PACKAGE\s*=\s*([\w:]+))?(?:\s+PREFIX\s*=\s*(\S+))?\s*$/m) { $module = $1; $ctx->note("detect module name $module") if $verbose; } else { $ok = 0; push @diag, ' XS does not have a module decleration that we could find'; } if($ok) { open my $fh, '>', $xs_filename; print $fh $xs->{xs}; close $fh; require ExtUtils::ParseXS; my $pxs = ExtUtils::ParseXS->new; my($out, $err) = capture_merged { eval { $pxs->process_file( filename => $xs_filename, output => $c_filename, versioncheck => 0, prototypes => 0, %{ $xs->{pxs} }, ); }; $@; }; $ctx->note("parse xs $xs_filename => $c_filename") if $verbose; $ctx->note($out) if $verbose; $ctx->note("error: $err") if $verbose && $err; unless($pxs->report_error_count == 0) { $ok = 0; push @diag, ' ExtUtils::ParseXS failed:'; push @diag, " $err" if $err; push @diag, " $_" for split /\r?\n/, $out; } } push @diag, "xs_ok called without any aliens, you may want to call alien_ok" unless @aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}; if($ok) { my $cb = ExtUtils::CBuilder->new( config => do { my %config = %{ $xs->{cbuilder_config} }; my $lddlflags = join(' ', grep !/^-l/, shellwords map { _flags $_, 'libs' } @aliens) . " $Config{lddlflags}"; $config{lddlflags} = defined $config{lddlflags} ? "$lddlflags $config{lddlflags}" : $lddlflags; \%config; }, ); my %compile_options = ( source => $c_filename, %{ $xs->{cbuilder_compile} }, ); if(defined $compile_options{extra_compiler_flags} && ref($compile_options{extra_compiler_flags}) eq '') { $compile_options{extra_compiler_flags} = [ shellwords $compile_options{extra_compiler_flags} ]; } push @{ $compile_options{extra_compiler_flags} }, shellwords map { _flags $_, 'cflags' } @aliens; my($out, $obj, $err) = capture_merged { my $obj = eval { $cb->compile(%compile_options); }; ($obj, $@); }; $ctx->note("compile $c_filename") if $verbose; $ctx->note($out) if $verbose; $ctx->note($err) if $verbose && $err; if($verbose > 1) { $ctx->note(_dump({ compile_options => \%compile_options })); } unless($obj) { $ok = 0; push @diag, ' ExtUtils::CBuilder->compile failed'; push @diag, " $err" if $err; push @diag, " $_" for split /\r?\n/, $out; } if($ok) { my %link_options = ( objects => [$obj], module_name => $module, %{ $xs->{cbuilder_link} }, ); if(defined $link_options{extra_linker_flags} && ref($link_options{extra_linker_flags}) eq '') { $link_options{extra_linker_flags} = [ shellwords $link_options{extra_linker_flags} ]; } unshift @{ $link_options{extra_linker_flags} }, grep /^-l/, shellwords map { _flags $_, 'libs' } @aliens; my($out, $lib, $err) = capture_merged { my $lib = eval { $cb->link(%link_options); }; ($lib, $@); }; $ctx->note("link $obj") if $verbose; $ctx->note($out) if $verbose; $ctx->note($err) if $verbose && $err; if($verbose > 1) { $ctx->note(_dump({ link_options => \%link_options })); } if($lib && -f $lib) { $ctx->note("created lib $lib") if $xs->{verbose}; } else { $ok = 0; push @diag, ' ExtUtils::CBuilder->link failed'; push @diag, " $err" if $err; push @diag, " $_" for split /\r?\n/, $out; } if($ok) { my @modparts = split(/::/,$module); my $dl_dlext = $Config{dlext}; my $modfname = $modparts[-1]; my $libpath = path($dir)->child('auto', @modparts, "$modfname.$dl_dlext"); $libpath->parent->mkpath; move($lib, "$libpath") || die "unable to copy $lib => $libpath $!"; pop @modparts; my $pmpath = path($dir)->child(@modparts, "$modfname.pm"); $pmpath->parent->mkpath; open my $fh, '>', "$pmpath"; my($alien_with_xs_load, @rest) = grep { $_->can('xs_load') } @aliens; if($alien_with_xs_load) { { no strict 'refs'; @{join '::', $module, 'rest'} = @rest; ${join '::', $module, 'alien_with_xs_load'} = $alien_with_xs_load; } print $fh '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{ package $module; use strict; use warnings; our \$VERSION = '0.01'; our \@rest; our \$alien_with_xs_load; \$alien_with_xs_load->xs_load('$module', \$VERSION, \@rest); 1; }; } else { print $fh '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{ package $module; use strict; use warnings; require XSLoader; our \$VERSION = '0.01'; XSLoader::load('$module',\$VERSION); 1; }; } close $fh; { local @INC = @INC; unshift @INC, "$dir"; ## no critic eval '# line '. __LINE__ . ' "' . __FILE__ . qq("\n) . qq{ use $module; }; ## use critic } if(my $error = $@) { $ok = 0; push @diag, ' XSLoader failed'; push @diag, " $error"; } } } } $ctx->ok($ok, $message); $ctx->diag($_) for @diag; $ctx->release; unless($ok || defined $ENV{TEST_ALIEN_ALWAYS_KEEP}) { $ctx->note("keeping XS temporary directory $dir due to failure"); $dir->unlink_on_destroy(0); } if($cb) { $cb = sub { my $ctx = context(); $ctx->plan(0, 'SKIP', "subtest requires xs success"); $ctx->release; } unless $ok; @_ = ("$message subtest", $cb, 1, $module); goto \&Test2::API::run_subtest; } $ok; } sub with_subtest (&) { my($code) = @_; # it may be possible to catch a segmentation fault, # but not with signal handlers apparently. See: # https://feepingcreature.github.io/handling.html return $code if $^O eq 'MSWin32'; # try to catch a segmentation fault and bail out # with a useful diagnostic. prove test to swallow # the diagnostic on such failures. sub { local $SIG{SEGV} = sub { my $ctx = context(); $ctx->bail("Segmentation fault"); }; $code->(@_); } } sub ffi_ok { my $cb; $cb = pop if defined $_[-1] && ref $_[-1] eq 'CODE'; my($opt, $message) = @_; $message ||= 'ffi'; my $ok = 1; my $skip; my $ffi; my @diag; { my $min = '0.12'; # the first CPAN release $min = '0.15' if $opt->{ignore_not_found}; $min = '0.18' if $opt->{lang}; $min = '0.99' if defined $opt->{api} && $opt->{api} > 0; unless(eval { require FFI::Platypus; FFI::Platypus->VERSION($min) }) { $ok = 0; $skip = "Test requires FFI::Platypus $min"; } } if($ok && $opt->{lang}) { my $class = "FFI::Platypus::Lang::@{[ $opt->{lang} ]}"; { my $pm = "$class.pm"; $pm =~ s/::/\//g; eval { require $pm }; } if($@) { $ok = 0; $skip = "Test requires FFI::Platypus::Lang::@{[ $opt->{lang} ]}"; } } unless(@aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}) { push @diag, 'ffi_ok called without any aliens, you may want to call alien_ok'; } if($ok) { $ffi = FFI::Platypus->new( do { my @args = ( lib => [map { $_->dynamic_libs } @aliens], ignore_not_found => $opt->{ignore_not_found}, lang => $opt->{lang}, ); push @args, api => $opt->{api} if defined $opt->{api}; @args; } ); foreach my $symbol (@{ $opt->{symbols} || [] }) { unless($ffi->find_symbol($symbol)) { $ok = 0; push @diag, " $symbol not found" } } } my $ctx = context(); if($skip) { $ctx->skip($message, $skip); } else { $ctx->ok($ok, $message); } $ctx->diag($_) for @diag; $ctx->release; if($cb) { $cb = sub { my $ctx = context(); $ctx->plan(0, 'SKIP', "subtest requires ffi success"); $ctx->release; } unless $ok; @_ = ("$message subtest", $cb, 1, $ffi); goto \&Test2::API::run_subtest; } $ok; } { my @ret; sub _interpolator { return @ret if @ret; require Alien::Build::Interpolate::Default; my $intr = Alien::Build::Interpolate::Default->new; require Alien::Build; my $build = Alien::Build->new; $build->meta->interpolator($intr); @ret = ($intr, $build); } } sub helper_ok { my($name, $message) = @_; $message ||= "helper $name exists"; my($intr) = _interpolator; my $code = $intr->has_helper($name); my $ok = defined $code; my $ctx = context(); $ctx->ok($ok, $message); $ctx->diag("helper_ok called without any aliens, you may want to call alien_ok") unless @aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}; $ctx->release; $ok; } sub plugin_ok { my($name, $message) = @_; my @args; ($name, @args) = @$name if ref $name; $message ||= "plugin $name"; my($intr, $build) = _interpolator; my $class = "Alien::Build::Plugin::$name"; my $pm = "$class.pm"; $pm =~ s/::/\//g; my $ctx = context(); my $plugin = eval { require $pm unless $class->can('new'); $class->new(@args); }; if(my $error = $@) { $ctx->ok(0, $message, ['unable to create $name plugin', $error]); $ctx->release; return 0; } eval { $plugin->init($build->meta); }; if($^O eq 'MSWin32' && ($plugin->isa('Alien::Build::Plugin::Build::MSYS') || $plugin->isa('Alien::Build::Plugin::Build::Autoconf'))) { require Alien::MSYS; unshift @PATH, Alien::MSYS::msys_path(); } if(my $error = $@) { $ctx->ok(0, $message, ['unable to initiate $name plugin', $error]); $ctx->release; return 0; } else { $ctx->ok(1, $message); $ctx->release; return 1; } } sub interpolate_template_is { my($template, $pattern, $message) = @_; $message ||= "template matches"; my($intr) = _interpolator; my $value = eval { $intr->interpolate($template) }; my $error = $@; my @diag; my $ok; if($error) { $ok = 0; push @diag, "error in evaluation:"; push @diag, " $error"; } elsif(ref($pattern) eq 'Regexp') { $ok = $value =~ $pattern; push @diag, "value '$value' does not match $pattern'" unless $ok; } else { $ok = $value eq "$pattern"; push @diag, "value '$value' does not equal '$pattern'" unless $ok; } my $ctx = context(); $ctx->ok($ok, $message, [@diag]); $ctx->diag('interpolate_template_is called without any aliens, you may want to call alien_ok') unless @aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}; $ctx->release; $ok; } sub interpolate_run_ok { my($template, $message) = @_; my(@template) = ref $template ? @$template : ($template); my($intr) = _interpolator; my $ok = 1; my @diag; my @command; foreach my $template (@template) { my $command = eval { $intr->interpolate($template) }; if(my $error = $@) { $ok = 0; push @diag, "error in evaluation:"; push @diag, " $error"; } else { push @command, $command; } } my $ctx = context(); if($ok) { my $command = ref $template ? \@command : $command[0]; $ok = run_ok($command, $message); } else { $message ||= "run @template"; $ctx->ok($ok, $message, [@diag]); $ctx->diag('interpolate_run_ok called without any aliens, you may want to call alien_ok') unless @aliens || $ENV{TEST_ALIEN_ALIENS_MISSING}; } $ctx->release; $ok; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien - Testing tools for Alien modules =head1 VERSION version 2.80 =head1 SYNOPSIS Test commands that come with your Alien: use Test2::V0; use Test::Alien; use Alien::patch; alien_ok 'Alien::patch'; run_ok([ 'patch', '--version' ]) ->success # we only accept the version written # by Larry ... ->out_like(qr{Larry Wall}); done_testing; Test that your library works with C: use Test2::V0; use Test::Alien; use Alien::Editline; alien_ok 'Alien::Editline'; my $xs = do { local $/; }; xs_ok $xs, with_subtest { my($module) = @_; ok $module->version; }; done_testing; __DATA__ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include const char * version(const char *class) { return rl_library_version; } MODULE = TA_MODULE PACKAGE = TA_MODULE const char *version(class); const char *class; Test that your library works with L: use Test2::V0; use Test::Alien; use Alien::LibYAML; alien_ok 'Alien::LibYAML'; ffi_ok { symbols => ['yaml_get_version'] }, with_subtest { my($ffi) = @_; my $get_version = $ffi->function(yaml_get_version => ['int*','int*','int*'] => 'void'); $get_version->call(\my $major, \my $minor, \my $patch); like $major, qr{[0-9]+}; like $minor, qr{[0-9]+}; like $patch, qr{[0-9]+}; }; done_testing; =head1 DESCRIPTION This module provides tools for testing L modules. It has hooks to work easily with L based modules, but can also be used via the synthetic interface to test non L based L modules. It has very modest prerequisites. Prior to this module the best way to test a L module was via L. The main downside to that module is that it is heavily influenced by and uses L, which is a tool for checking at install time various things about your compiler. It was also written before L became as stable as it is today. In particular, L does its testing by creating an executable and running it. Unfortunately Perl uses extensions by creating dynamic libraries and linking them into the Perl process, which is different in subtle and error prone ways. This module attempts to test the libraries in the way that they will actually be used, via either C or L. It also provides a mechanism for testing binaries that are provided by the various L modules (for example L and L). L modules can actually be useable without a compiler, or without L (for example, if the library is provided by the system, and you are using L, or if you are building from source and you are using C), so tests with missing prerequisites are automatically skipped. For example, L will automatically skip itself if a compiler is not found, and L will automatically skip itself if L is not installed. =head1 FUNCTIONS =head2 alien_ok alien_ok $alien, $message; alien_ok $alien; Load the given L instance or class. Checks that the instance or class conforms to the same interface as L. Will be used by subsequent tests. The C<$alien> module only needs to provide these methods in order to conform to the L interface: =over 4 =item cflags String containing the compiler flags =item libs String containing the linker and library flags =item dynamic_libs List of dynamic libraries. Returns empty list if the L module does not provide this. =item bin_dir Directory containing tool binaries. Returns empty list if the L module does not provide this. =back If your L module does not conform to this interface then you can create a synthetic L module using the L function. =head2 synthetic my $alien = synthetic \%config; Create a synthetic L module which can be passed into L. C<\%config> can contain these keys (all of which are optional): =over 4 =item cflags String containing the compiler flags. =item cflags_static String containing the static compiler flags (optional). =item libs String containing the linker and library flags. =item libs_static String containing the static linker flags (optional). =item dynamic_libs List reference containing the dynamic libraries. =item bin_dir Tool binary directory. =item runtime_prop Runtime properties. =back See L for more details. =head2 run_ok my $run = run_ok $command; my $run = run_ok $command, $message; Runs the given command, falling back on any C methods provided by L modules specified with L. C<$command> can be either a string or an array reference. Only fails if the command cannot be found, or if it is killed by a signal! Returns a L object, which you can use to test the exit status, output and standard error. Always returns an instance of L, even if the command could not be found. =head2 xs_ok xs_ok $xs; xs_ok $xs, $message; Compiles, links the given C code and attaches to Perl. If you use the special module name C in your C code, it will be replaced by an automatically generated package name. This can be useful if you want to pass the same C code to multiple calls to C without subsequent calls replacing previous ones. C<$xs> may be either a string containing the C code, or a hash reference with these keys: =over 4 =item xs The XS code. This is the only required element. =item pxs Extra L arguments passed in as a hash reference. =item cbuilder_check The compile check that should be done prior to attempting to build. Should be one of C or C. Defaults to C. =item cbuilder_config Hash to override values normally provided by C. =item cbuilder_compile Extra The L arguments passed in as a hash reference. =item cbuilder_link Extra The L arguments passed in as a hash reference. =item verbose Spew copious debug information via test note. =back You can use the C keyword to conditionally run a subtest if the C call succeeds. If C does not work, then the subtest will automatically be skipped. Example: xs_ok $xs, with_subtest { # skipped if $xs fails for some reason my($module) = @_; is $module->foo, 1; }; The module name detected during the XS parsing phase will be passed in to the subtest. This is helpful when you are using a generated module name. If you need to test XS C++ interfaces, see L. Caveats: C uses L, which may call C under certain error conditions. While this is not really good thing to happen in the middle of a test, it usually indicates a real failure condition, and it should return a failure condition so the test should still fail overall. [version 2.53] As of version 2.53, C will only remove temporary generated files if the test is successful by default. You can force either always or never removing the temporary generated files using the C environment variable (see L below). =head2 ffi_ok ffi_ok; ffi_ok \%opt; ffi_ok \%opt, $message; Test that L works. C<\%opt> is a hash reference with these keys (all optional): =over 4 =item symbols List references of symbols that must be found for the test to succeed. =item ignore_not_found Ignores symbols that aren't found. This affects functions accessed via L and L methods, and does not influence the C key above. =item lang Set the language. Used primarily for language specific native types. =item api Set the API. C requires FFI::Platypus 0.99 or later. This option was added with Test::Alien version 1.90, so your use line should include this version as a safeguard to make sure it works: use Test::Alien 1.90; ... ffi_ok ...; =back As with L above, you can use the C keyword to specify a subtest to be run if C succeeds (it will skip otherwise). The L instance is passed into the subtest as the first argument. For example: ffi_ok with_subtest { my($ffi) = @_; is $ffi->function(foo => [] => 'void')->call, 42; }; =head2 helper_ok helper_ok $name; helper_ok $name, $message; Tests that the given helper has been defined. =head2 plugin_ok [version 2.52] plugin_ok $plugin_name, $message; plugin_ok [$plugin_name, @args], $message; This applies an L to the interpolator used by L, L and L so that you can test with any helpers that plugin provides. Useful, for example for getting C<%{configure}> from L. =head2 interpolate_template_is interpolate_template_is $template, $string; interpolate_template_is $template, $string, $message; interpolate_template_is $template, $regex; interpolate_template_is $template, $regex, $message; Tests that the given template when evaluated with the appropriate helpers will match either the given string or regular expression. =head2 interpolate_run_ok [version 2.52] my $run = interpolate_run_ok $command; my $run = interpolate_run_ok $command, $message; This is the same as L except it runs the command through the interpolator first. =head1 ENVIRONMENT =over 4 =item C If this is defined then it will override the built in logic that decides if the temporary files generated by L should be kept when the test file terminates. If set to true the generated files will always be kept. If set to false, then they will always be removed. =item C By default, this module will warn you if some tools are used without first invoking L. This is usually a mistake, but if you really do want to use one of these tools with no aliens loaded, you can set this environment variable to false. =back =head1 SEE ALSO =over 4 =item L =item L =item L =item L =item L =item L =item L =item L =item L =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!`$y y perl5/Test/RequiresInternet.pmnu6$use strict; use warnings; package Test::RequiresInternet; $Test::RequiresInternet::VERSION = '0.05'; # ABSTRACT: Easily test network connectivity use Socket; sub import { skip_all("NO_NETWORK_TESTING") if env("NO_NETWORK_TESTING"); my $namespace = shift; my $argc = scalar @_; if ( $argc == 0 ) { push @_, 'www.google.com', 80; } elsif ( $argc % 2 != 0 ) { die "Must supply server and a port pairs. You supplied " . (join ", ", @_) . "\n"; } while ( @_ ) { my $host = shift; my $port = shift; local $@; eval {make_socket($host, $port)}; if ( $@ ) { skip_all("$@"); } } } sub make_socket { my ($host, $port) = @_; my $portnum; if ($port =~ /\D/) { $portnum = getservbyname($port, "tcp"); } else { $portnum = $port; } die "Could not find a port number for $port\n" if not $portnum; my $iaddr = inet_aton($host) or die "no host: $host\n"; my $paddr = sockaddr_in($portnum, $iaddr); my $proto = getprotobyname("tcp"); socket(my $sock, PF_INET, SOCK_STREAM, $proto) or die "socket: $!\n"; connect($sock, $paddr) or die "connect: $!\n"; close ($sock) or die "close: $!\n"; 1; } sub env { exists $ENV{$_[0]} && $ENV{$_[0]} eq '1' } sub skip_all { my $reason = shift; print "1..0 # Skipped: $reason"; exit 0; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::RequiresInternet - Easily test network connectivity =head1 VERSION version 0.05 =head1 SYNOPSIS use Test::More; use Test::RequiresInternet ('www.example.com' => 80, 'foobar.io' => 25); # if you reach here, sockets successfully connected to hosts/ports above plan tests => 1; ok(do_that_internet_thing()); =head1 OVERVIEW This module is intended to easily test network connectivity before functional tests begin to non-local Internet resources. It does not require any modules beyond those supplied in core Perl. If you do not specify a host/port pair, then the module defaults to using C on port C<80>. You may optionally specify the port by its name, as in C or C. If you do this, the test module will attempt to look up the port number using C. If you do specify a host and port, they must be specified in B. It is a fatal error to omit one or the other. If the environment variable C is set, then the tests will be skipped without attempting any socket connections. If the sockets cannot connect to the specified hosts and ports, the exception is caught, reported and the tests skipped. =head1 AUTHOR Mark Allen =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2014 by Mark Allen. 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 PK!lppperl5/Test/Alien/Synthetic.pmnu6$package Test::Alien::Synthetic; use strict; use warnings; use 5.008004; use Test2::API qw( context ); # ABSTRACT: A mock alien object for testing our $VERSION = '2.80'; # VERSION sub _def ($) { my($val) = @_; defined $val ? $val : '' } sub cflags { _def shift->{cflags} } sub libs { _def shift->{libs} } sub dynamic_libs { @{ shift->{dynamic_libs} || [] } } sub runtime_prop { my($self) = @_; defined $self->{runtime_prop} ? $self->{runtime_prop} : {}; } sub cflags_static { my($self) = @_; defined $self->{cflags_static} ? $self->{cflags_static} : $self->cflags; } sub libs_static { my($self) = @_; defined $self->{libs_static} ? $self->{libs_static} : $self->libs; } sub bin_dir { my $dir = shift->{bin_dir}; defined $dir && -d $dir ? ($dir) : (); } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::Synthetic - A mock alien object for testing =head1 VERSION version 2.80 =head1 SYNOPSIS use Test2::V0; use Test::Alien; my $alien = synthetic { cflags => '-I/foo/bar/include', libs => '-L/foo/bar/lib -lbaz', }; alien_ok $alien; done_testing; =head1 DESCRIPTION This class is used to model a synthetic L class that implements the minimum L interface needed by L. It can be useful if you have a non-L based L distribution that you need to test. B: The name of this class may move in the future, so do not refer to this class name directly. Instead create instances of this class using the L function. =head1 ATTRIBUTES =head2 cflags String containing the compiler flags =head2 cflags_static String containing the static compiler flags =head2 libs String containing the linker and library flags =head2 libs_static String containing the static linker and library flags =head2 dynamic_libs List reference containing the dynamic libraries. =head2 bin_dir Tool binary directory. =head2 runtime_prop Runtime properties. =head1 EXAMPLE Here is a complete example using L which is a non-L based L distribution. use strict; use warnings; use Test2::V0; use Test::Alien; use Alien::Libarchive; my $real = Alien::Libarchive->new; my $alien = synthetic { cflags => scalar $real->cflags, libs => scalar $real->libs, dynamic_libs => [$real->dlls], }; alien_ok $alien; xs_ok do { local $/; }, with_subtest { my($module) = @_; my $ptr = $module->archive_read_new; like $ptr, qr{^[0-9]+$}; $module->archive_read_free($ptr); }; ffi_ok { symbols => [qw( archive_read_new )] }, with_subtest { my($ffi) = @_; my $new = $ffi->function(archive_read_new => [] => 'opaque'); my $free = $ffi->function(archive_read_close => ['opaque'] => 'void'); my $ptr = $new->(); like $ptr, qr{^[0-9]+$}; $free->($ptr); }; done_testing; __DATA__ #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include MODULE = TA_MODULE PACKAGE = TA_MODULE void *archive_read_new(class); const char *class; CODE: RETVAL = (void*) archive_read_new(); OUTPUT: RETVAL void archive_read_free(class, ptr); const char *class; void *ptr; CODE: archive_read_free(ptr); =head1 SEE ALSO =over 4 =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!7yoddperl5/Test/Alien/Run.pmnu6$package Test::Alien::Run; use strict; use warnings; use 5.008004; use Test2::API qw( context ); # ABSTRACT: Run object our $VERSION = '2.80'; # VERSION sub out { shift->{out} } sub err { shift->{err} } sub exit { shift->{exit} } sub signal { shift->{sig} } sub success { my($self, $message) = @_; $message ||= 'command succeeded'; my $ok = $self->exit == 0 && $self->signal == 0; $ok = 0 if $self->{fail}; my $ctx = context(); $ctx->ok($ok, $message); unless($ok) { $ctx->diag(" command exited with @{[ $self->exit ]}") if $self->exit; $ctx->diag(" command killed with @{[ $self->signal ]}") if $self->signal; $ctx->diag(" @{[ $self->{fail} ]}") if $self->{fail}; } $ctx->release; $self; } sub exit_is { my($self, $exit, $message) = @_; $message ||= "command exited with value $exit"; my $ok = $self->exit == $exit; my $ctx = context(); $ctx->ok($ok, $message); $ctx->diag(" actual exit value was: @{[ $self->exit ]}") unless $ok; $ctx->release; $self; } sub exit_isnt { my($self, $exit, $message) = @_; $message ||= "command exited with value not $exit"; my $ok = $self->exit != $exit; my $ctx = context(); $ctx->ok($ok, $message); $ctx->diag(" actual exit value was: @{[ $self->exit ]}") unless $ok; $ctx->release; $self; } sub _like { my($self, $regex, $source, $not, $message) = @_; my $ok = $self->{$source} =~ $regex; $ok = !$ok if $not; my $ctx = context(); $ctx->ok($ok, $message); unless($ok) { $ctx->diag(" $source:"); $ctx->diag(" $_") for split /\r?\n/, $self->{$source}; $ctx->diag($not ? ' matches:' : ' does not match:'); $ctx->diag(" $regex"); } $ctx->release; $self; } sub out_like { my($self, $regex, $message) = @_; $message ||= "output matches $regex"; $self->_like($regex, 'out', 0, $message); } sub out_unlike { my($self, $regex, $message) = @_; $message ||= "output does not match $regex"; $self->_like($regex, 'out', 1, $message); } sub err_like { my($self, $regex, $message) = @_; $message ||= "standard error matches $regex"; $self->_like($regex, 'err', 0, $message); } sub err_unlike { my($self, $regex, $message) = @_; $message ||= "standard error does not match $regex"; $self->_like($regex, 'err', 1, $message); } sub note { my($self) = @_; my $ctx = context(); $ctx->note("[cmd]"); $ctx->note(" @{$self->{cmd}}"); if($self->out ne '') { $ctx->note("[out]"); $ctx->note(" $_") for split /\r?\n/, $self->out; } if($self->err ne '') { $ctx->note("[err]"); $ctx->note(" $_") for split /\r?\n/, $self->err; } $ctx->release; $self; } sub diag { my($self) = @_; my $ctx = context(); $ctx->diag("[cmd]"); $ctx->diag(" @{$self->{cmd}}"); if($self->out ne '') { $ctx->diag("[out]"); $ctx->diag(" $_") for split /\r?\n/, $self->out; } if($self->err ne '') { $ctx->diag("[err]"); $ctx->diag(" $_") for split /\r?\n/, $self->err; } $ctx->release; $self; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::Run - Run object =head1 VERSION version 2.80 =head1 SYNOPSIS use Test2::V0; use Test::Alien; run_ok([ $^X, -e => 'print "some output"; exit 22']) ->exit_is(22) ->out_like(qr{some}); =head1 DESCRIPTION This class stores information about a process run as performed by L. That function is the I way to create an instance of this class. =head1 ATTRIBUTES =head2 out my $str = $run->out; The standard output from the run. =head2 err my $str = $run->err; The standard error from the run. =head2 exit my $int = $run->exit; The exit value of the run. =head2 signal my $int = $run->signal; The signal that killed the run, or zero if the process was terminated normally. =head1 METHODS These methods return the run object itself, so they can be chained, as in the synopsis above. =head2 success $run->success; $run->success($message); Passes if the process terminated normally with an exit value of 0. =head2 exit_is $run->exit_is($exit); $run->exit_is($exit, $message); Passes if the process terminated with the given exit value. =head2 exit_isnt $run->exit_isnt($exit); $run->exit_isnt($exit, $message); Passes if the process terminated with an exit value of anything but the given value. =head2 out_like $run->out_like($regex); $run->out_like($regex, $message); Passes if the output of the run matches the given pattern. =head2 out_unlike $run->out_unlike($regex); $run->out_unlike($regex, $message); Passes if the output of the run does not match the given pattern. =head2 err_like $run->err_like($regex); $run->err_like($regex, $message); Passes if the standard error of the run matches the given pattern. =head2 err_unlike $run->err_unlike($regex); $run->err_unlike($regex, $message); Passes if the standard error of the run does not match the given pattern. =head2 note $run->note; Send the output and standard error as test note. =head2 diag $run->diag; Send the output and standard error as test diagnostic. =head1 SEE ALSO =over 4 =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!vkperl5/Test/Alien/CanCompile.pmnu6$package Test::Alien::CanCompile; use strict; use warnings; use 5.008004; use Test2::API qw( context ); # ABSTRACT: Skip a test file unless a C compiler is available our $VERSION = '2.80'; # VERSION sub skip { require ExtUtils::CBuilder; ExtUtils::CBuilder->new->have_compiler ? undef : 'This test requires a compiler.'; } sub import { my $skip = __PACKAGE__->skip; return unless defined $skip; my $ctx = context(); $ctx->plan(0, SKIP => $skip); $ctx->release; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::CanCompile - Skip a test file unless a C compiler is available =head1 VERSION version 2.80 =head1 SYNOPSIS use Test::Alien::CanCompile; =head1 DESCRIPTION This is just a L plugin that requires that a compiler be available. Otherwise the test will be skipped. =head1 SEE ALSO =over 4 =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!(c>EEperl5/Test/Alien/Build.pmnu6$package Test::Alien::Build; use strict; use warnings; use 5.008004; use Exporter qw( import ); use Path::Tiny qw( path ); use Carp qw( croak ); use Test2::API qw( context run_subtest ); use Capture::Tiny qw( capture_merged ); use Alien::Build::Util qw( _mirror ); use List::Util 1.33 qw( any ); use Alien::Build::Temp; our @EXPORT = qw( alienfile alienfile_ok alienfile_skip_if_missing_prereqs alien_download_ok alien_extract_ok alien_build_ok alien_build_clean alien_clean_install alien_install_type_is alien_checkpoint_ok alien_resume_ok alien_subtest alien_rc ); # ABSTRACT: Tools for testing Alien::Build + alienfile our $VERSION = '2.80'; # VERSION my $build; my $build_alienfile; my $build_root; my $build_targ; sub alienfile::targ { $build_targ; } sub alienfile { my($package, $filename, $line) = caller; ($package, $filename, $line) = caller(2) if $package eq __PACKAGE__; $filename = path($filename)->absolute; my %args = @_ == 0 ? (filename => 'alienfile') : @_ % 2 ? ( source => do { '# line '. $line . ' "' . path($filename)->absolute . qq("\n) . $_[0] }) : @_; require alienfile; push @alienfile::EXPORT, 'targ' unless any { /^targ$/ } @alienfile::EXPORT; my $temp = Alien::Build::Temp->newdir; my $get_temp_root = do{ my $root; # may be undef; sub { $root ||= Path::Tiny->new($temp); if(@_) { my $path = $root->child(@_); $path->mkpath; $path; } else { return $root; } }; }; if($args{source}) { my $file = $get_temp_root->()->child('alienfile'); $file->spew_utf8($args{source}); $args{filename} = $file->stringify; } else { unless(defined $args{filename}) { croak "You must specify at least one of filename or source"; } $args{filename} = path($args{filename})->absolute->stringify; } $args{stage} ||= $get_temp_root->('stage')->stringify; $args{prefix} ||= $get_temp_root->('prefix')->stringify; $args{root} ||= $get_temp_root->('root')->stringify; require Alien::Build; _alienfile_clear(); my $out = capture_merged { $build_targ = $args{targ}; $build = Alien::Build->load($args{filename}, root => $args{root}); $build->set_stage($args{stage}); $build->set_prefix($args{prefix}); }; my $ctx = context(); $ctx->note($out) if $out; $ctx->release; $build_alienfile = $args{filename}; $build_root = $temp; $build } sub _alienfile_clear { eval { defined $build_root && -d $build_root && path($build_root)->remove_tree }; undef $build; undef $build_alienfile; undef $build_root; undef $build_targ; } sub alienfile_ok { my $build; my $name; my $error; if(@_ == 1 && ! defined $_[0]) { $build = $_[0]; $error = 'no alienfile given'; $name = 'alienfile compiled'; } elsif(@_ == 1 && eval { $_[0]->isa('Alien::Build') }) { $build = $_[0]; $name = 'alienfile compiled'; } else { $build = eval { alienfile(@_) }; $error = $@; $name = 'alienfile compiles'; } my $ok = !! $build; my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag("error: $error") if $error; $ctx->release; $build; } sub alienfile_skip_if_missing_prereqs { my($phase) = @_; if($build) { eval { $build->load_requires('configure', 1) }; if(my $error = $@) { my $reason = "Missing configure prereq"; if($error =~ /Required (.*) (.*),/) { $reason .= ": $1 $2"; } my $ctx = context(); $ctx->plan(0, SKIP => $reason); $ctx->release; return; } $phase ||= $build->install_type; eval { $build->load_requires($phase, 1) }; if(my $error = $@) { my $reason = "Missing $phase prereq"; if($error =~ /Required (.*) (.*),/) { $reason .= ": $1 $2"; } my $ctx = context(); $ctx->plan(0, SKIP => $reason); $ctx->release; return; } } } sub alien_install_type_is { my($type, $name) = @_; croak "invalid install type" unless defined $type && $type =~ /^(system|share)$/; $name ||= "alien install type is $type"; my $ok = 0; my @diag; if($build) { my($out, $actual) = capture_merged { $build->load_requires('configure'); $build->install_type; }; if($type eq $actual) { $ok = 1; } else { push @diag, "expected install type of $type, but got $actual"; } } else { push @diag, 'no alienfile' } my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag($_) for @diag; $ctx->release; $ok; } sub alien_download_ok { my($name) = @_; $name ||= 'alien download'; my $ok; my $file; my @diag; my @note; if($build) { my($out, $error) = capture_merged { eval { $build->load_requires('configure'); $build->load_requires($build->install_type); $build->download; }; $@; }; if($error) { $ok = 0; push @diag, $out if defined $out; push @diag, "extract threw exception: $error"; } else { $file = $build->install_prop->{download}; if(-d $file || -f $file) { $ok = 1; push @note, $out if defined $out; } else { $ok = 0; push @diag, $out if defined $out; push @diag, 'no file or directory'; } } } else { $ok = 0; push @diag, 'no alienfile'; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->note($_) for @note; $ctx->diag($_) for @diag; $ctx->release; $file; } sub alien_extract_ok { my($archive, $name) = @_; $name ||= $archive ? "alien extraction of $archive" : 'alien extraction'; my $ok; my $dir; my @diag; my @note; if($build) { my($out, $error); ($out, $dir, $error) = capture_merged { my $dir = eval { $build->load_requires('configure'); $build->load_requires($build->install_type); $build->download; $build->extract($archive); }; ($dir, $@); }; if($error) { $ok = 0; push @diag, $out if defined $out; push @diag, "extract threw exception: $error"; } else { if(-d $dir) { $ok = 1; push @note, $out if defined $out; } else { $ok = 0; push @diag, $out if defined $out; push @diag, 'no directory'; } } } else { $ok = 0; push @diag, 'no alienfile'; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->note($_) for @note; $ctx->diag($_) for @diag; $ctx->release; $dir; } my $count = 1; sub alien_build_ok { my $opt = defined $_[0] && ref($_[0]) eq 'HASH' ? shift : { class => 'Alien::Base' }; my($name) = @_; $name ||= 'alien builds okay'; my $ok; my @diag; my @note; my $alien; if($build) { my($out,$error) = capture_merged { eval { $build->load_requires('configure'); $build->load_requires($build->install_type); $build->download; $build->build; }; $@; }; if($error) { $ok = 0; push @diag, $out if defined $out; push @diag, "build threw exception: $error"; } else { $ok = 1; push @note, $out if defined $out; require Alien::Base; my $prefix = $build->runtime_prop->{prefix}; my $stage = $build->install_prop->{stage}; my %prop = %{ $build->runtime_prop }; $prop{distdir} = $prefix; _mirror $stage, $prefix; my $dist_dir = sub { $prefix; }; my $runtime_prop = sub { \%prop; }; $alien = sprintf 'Test::Alien::Build::Faux%04d', $count++; { no strict 'refs'; @{ "${alien}::ISA" } = $opt->{class}; *{ "${alien}::dist_dir" } = $dist_dir; *{ "${alien}::runtime_prop" } = $runtime_prop; } } } else { $ok = 0; push @diag, 'no alienfile'; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag($_) for @diag; $ctx->note($_) for @note; $ctx->release; $alien; } sub alien_build_clean { my $ctx = context(); if($build_root) { foreach my $child (path($build_root)->children) { next if $child->basename eq 'prefix'; $ctx->note("clean: rm: $child"); $child->remove_tree; } } else { $ctx->note("no build to clean"); } $ctx->release; } sub alien_clean_install { my($name) = @_; $name ||= "run clean_install"; my $ok; my @diag; my @note; if($build) { my($out,$error) = capture_merged { eval { $build->clean_install; }; $@; }; if($error) { $ok = 0; push @diag, $out if defined $out && $out ne ''; push @diag, "build threw exception: $error"; } else { $ok = 1; push @note, $out if defined $out && $out ne ''; } } else { $ok = 0; push @diag, 'no alienfile'; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag($_) for @diag; $ctx->note($_) for @note; $ctx->release; } sub alien_checkpoint_ok { my($name) = @_; $name ||= "alien checkpoint ok"; my $ok; my @diag; if($build) { eval { $build->checkpoint }; if($@) { push @diag, "error in checkpoint: $@"; $ok = 0; } else { $ok = 1; } undef $build; } else { push @diag, "no build to checkpoint"; $ok = 0; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag($_) for @diag; $ctx->release; $ok; } sub alien_resume_ok { my($name) = @_; $name ||= "alien resume ok"; my $ok; my @diag; if($build_alienfile && $build_root && !defined $build) { $build = eval { Alien::Build->resume($build_alienfile, "$build_root/root") }; if($@) { push @diag, "error in resume: $@"; $ok = 0; } else { $ok = 1; } } else { if($build) { push @diag, "build has not been checkpointed"; } else { push @diag, "no build to resume"; } $ok = 0; } my $ctx = context(); $ctx->ok($ok, $name); $ctx->diag($_) for @diag; $ctx->release; ($ok && $build) || $ok; } my $alien_rc_root; sub alien_rc { my($code) = @_; croak "passed in undef rc" unless defined $code; croak "looks like you have already defined a rc.pl file" if $ENV{ALIEN_BUILD_RC} ne '-'; my(undef, $filename, $line) = caller; my $code2 = "use strict; use warnings;\n" . '# line ' . $line . ' "' . path($filename)->absolute . "\n$code"; $alien_rc_root ||= Alien::Build::Temp->newdir; my $rc = path($alien_rc_root)->child('rc.pl'); $rc->spew_utf8($code2); $ENV{ALIEN_BUILD_RC} = "$rc"; return 1; } sub alien_subtest { my($name, $code, @args) = @_; _alienfile_clear; my $ctx = context(); my $pass = run_subtest($name, $code, { buffered => 1 }, @args); $ctx->release; _alienfile_clear; $pass; } delete $ENV{$_} for qw( ALIEN_BUILD_LOG ALIEN_BUILD_PRELOAD ALIEN_BUILD_POSTLOAD ALIEN_INSTALL_TYPE PKG_CONFIG_PATH ALIEN_BUILD_PKG_CONFIG ); $ENV{ALIEN_BUILD_RC} = '-'; 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::Build - Tools for testing Alien::Build + alienfile =head1 VERSION version 2.80 =head1 SYNOPSIS use Test2::V0; use Test::Alien::Build; # returns an instance of Alien::Build. my $build = alienfile_ok q{ use alienfile; plugin 'My::Plugin' => ( foo => 1, bar => 'string', ... ); }; alien_build_ok 'builds okay.'; done_testing; =head1 DESCRIPTION This module provides some tools for testing L and L. Outside of L core development, It is probably most useful for L developers. This module also unsets a number of L specific environment variables, in order to make tests reproducible even when overrides are set in different environments. So if you want to test those variables in various states you should explicitly set them in your test script. These variables are unset if they defined: C C C. =head1 FUNCTIONS =head2 alienfile my $build = alienfile; my $build = alienfile q{ use alienfile ... }; my $build = alienfile filename => 'alienfile'; Create a Alien::Build instance from the given L. The first two forms are abbreviations. my $build = alienfile; # is the same as my $build = alienfile filename => 'alienfile'; and my $build = alienfile q{ use alienfile ... }; # is the same as my $build = alienfile source => q{ use alienfile ... }; Except for the second abbreviated form sets the line number before feeding the source into L so that you will get diagnostics with the correct line numbers. =over 4 =item source The source for the alienfile as a string. You must specify one of C or C. =item filename The filename for the alienfile. You must specify one of C or C. =item root The build root. =item stage The staging area for the build. =item prefix The install prefix for the build. =back =head2 alienfile_ok my $build = alienfile_ok; my $build = alienfile_ok q{ use alienfile ... }; my $build = alienfile_ok filename => 'alienfile'; my $build = alienfile_ok $build; Same as C above, except that it runs as a test, and will not throw an exception on failure (it will return undef instead). [version 1.49] As of version 1.49 you can also pass in an already formed instance of L. This allows you to do something like this: subtest 'a subtest' => sub { my $build = alienfile q{ use alienfile; ... }; alienfile_skip_if_missing_prereqs; # skip if alienfile prereqs are missing alienfile_ok $build; # delayed pass/fail for the compile of alienfile }; =head2 alienfile_skip_if_missing_prereqs alienfile_skip_if_missing_prereqs; alienfile_skip_if_missing_prereqs $phase; Skips the test or subtest if the prereqs for the alienfile are missing. If C<$phase> is not given, then either C or C will be detected. =head2 alien_install_type_is alien_install_type_is $type; alien_install_type_is $type, $name; Simple test to see if the install type is what you expect. C<$type> should be one of C or C. =head2 alien_download_ok my $file = alien_download_ok; my $file = alien_download_ok $name; Makes a download attempt and test that a file or directory results. Returns the file or directory if successful. Returns C otherwise. =head2 alien_extract_ok my $dir = alien_extract_ok; my $dir = alien_extract_ok $archive; my $dir = alien_extract_ok $archive, $name; my $dir = alien_extract_ok undef, $name; Makes an extraction attempt and test that a directory results. Returns the directory if successful. Returns C otherwise. =head2 alien_build_ok my $alien = alien_build_ok; my $alien = alien_build_ok $name; my $alien = alien_build_ok { class => $class }; my $alien = alien_build_ok { class => $class }, $name; Runs the download and build stages. Passes if the build succeeds. Returns an instance of L which can be passed into C from L. Returns C if the test fails. Options =over 4 =item class The base class to use for your alien. This is L by default. Should be a subclass of L, or at least adhere to its API. =back =head2 alien_build_clean alien_build_clean; Removes all files with the current build, except for the runtime prefix. This helps test that the final install won't depend on the build files. =head2 alien_clean_install alien_clean_install; Runs C<$build-Eclean_install>, and verifies it did not crash. =head2 alien_checkpoint_ok alien_checkpoint_ok; alien_checkpoint_ok $test_name; Test the checkpoint of a build. =head2 alien_resume_ok alien_resume_ok; alien_resume_ok $test_name; Test a resume a checkpointed build. =head2 alien_rc alien_rc $code; Creates C file in a temp directory and sets ALIEN_BUILD_RC. Useful for testing plugins that should be called from C<~/.alienbuild/rc.pl>. Note that because of the nature of how the C<~/.alienbuild/rc.pl> file works, you can only use this once! =head2 alien_subtest alien_subtest $test_name => sub { ... }; Clear the build object and clear the build object before and after the subtest. =head1 SEE ALSO =over 4 =item L =item L =item L =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!0sdperl5/Test/Alien/Diag.pmnu6$package Test::Alien::Diag; use strict; use warnings; use 5.008004; use Test2::API qw( context ); use Exporter qw( import ); our @EXPORT = qw( alien_diag ); our @EXPORT_OK = @EXPORT; # ABSTRACT: Print out standard diagnostic for Aliens in the test step. our $VERSION = '2.80'; # VERSION my @default_scalar_properties = qw( cflags cflags_static libs libs_static version install_type ); my @default_list_properties = qw( dynamic_libs bin_dir ); sub alien_diag ($@) { my $ctx = context(); my %options = defined $_[-1] && ref($_[-1]) eq 'HASH' ? %{ pop @_ } : (); my @extra_properties = @{ delete $options{properties} || [] }; my @extra_list_properties = @{ delete $options{list_properties} || [] }; my $max = 0; foreach my $alien (@_) { foreach my $name (@default_scalar_properties, @default_list_properties, @extra_properties, @extra_list_properties) { if(eval { $alien->can($name) }) { my $str = "$alien->$name"; if(length($str) > $max) { $max = length($str); } } } } $ctx->diag(''); if(%options) { my @extra = sort keys %options; $ctx->diag("warning: unknown option@{[ @extra > 1 ? 's' : '' ]} for alien_diag: @extra"); $ctx->diag("(you should check for typos or maybe upgrade to a newer version of Alien::Build)"); } foreach my $alien (@_) { $ctx->diag('') for 1..2; my $found = 0; foreach my $name (sort(@default_scalar_properties, @extra_properties)) { if(eval { $alien->can($name) }) { $found++; my $value = $alien->$name; $value = '[undef]' unless defined $value; $ctx->diag(sprintf "%-${max}s = %s", "$alien->$name", $value); } } foreach my $name (sort(@default_list_properties, @extra_list_properties)) { if(eval { $alien->can($name) }) { $found++; my @list = eval { $alien->$name }; next if $@; $ctx->diag(sprintf "%-${max}s = %s", "$alien->$name", $_) for @list; } } $ctx->diag("no diagnostics found for $alien") unless $found; } $ctx->diag('') for 1..2; $ctx->release; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::Diag - Print out standard diagnostic for Aliens in the test step. =head1 VERSION version 2.80 =head1 SYNOPSIS use Test2::V0; use Test::Alien::Diag qw( alien_diag ); =head1 DESCRIPTION This module provides an C method that prints out diagnostics useful for cpantesters and other bug reports that gives a quick summary of the important settings like C and C. =head1 FUNCTIONS =head2 alien_diag alien_diag @aliens; prints out diagnostics for each given alien. Each alien must be the class name of an alien. [version 2.68] alien_diag @aliens, \%options; Starting with L 2.68, you can provide an option hash to adjust the behavior of C. Valid options are: =over 4 =item properties Additional properties to display in the diagnostic. Useful when you have an L with custom properties defined in the subclass. =item list_properties Additional properties that are returned as a list to display in the diagnostic. Useful when you have an L with customer properties that return a list. =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!t}perl5/Test/Alien/CanPlatypus.pmnu6$package Test::Alien::CanPlatypus; use strict; use warnings; use 5.008004; use Test2::API qw( context ); # ABSTRACT: Skip a test file unless FFI::Platypus is available our $VERSION = '2.80'; # VERSION sub skip { eval { require FFI::Platypus; 1 } ? undef : 'This test requires FFI::Platypus.'; } sub import { my $skip = __PACKAGE__->skip; return unless defined $skip; my $ctx = context(); $ctx->plan(0, SKIP => $skip); $ctx->release; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Test::Alien::CanPlatypus - Skip a test file unless FFI::Platypus is available =head1 VERSION version 2.80 =head1 SYNOPSIS use Test::Alien::CanPlatypus; =head1 DESCRIPTION This is just a L plugin that requires that L be available. Otherwise the test will be skipped. =head1 SEE ALSO =over 4 =item L =item L =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK!Qperl5/App/Cpan.pmnu6$package App::Cpan; use strict; use warnings; use vars qw($VERSION); use if $] < 5.008 => 'IO::Scalar'; $VERSION = '1.678'; =head1 NAME App::Cpan - easily interact with CPAN from the command line =head1 SYNOPSIS # with arguments and no switches, installs specified modules cpan module_name [ module_name ... ] # with switches, installs modules with extra behavior cpan [-cfFimtTw] module_name [ module_name ... ] # use local::lib cpan -I module_name [ module_name ... ] # one time mirror override for faster mirrors cpan -p ... # with just the dot, install from the distribution in the # current directory cpan . # without arguments, starts CPAN.pm shell cpan # without arguments, but some switches cpan [-ahpruvACDLOPX] =head1 DESCRIPTION This script provides a command interface (not a shell) to CPAN. At the moment it uses CPAN.pm to do the work, but it is not a one-shot command runner for CPAN.pm. =head2 Options =over 4 =item -a Creates a CPAN.pm autobundle with CPAN::Shell->autobundle. =item -A module [ module ... ] Shows the primary maintainers for the specified modules. =item -c module Runs a `make clean` in the specified module's directories. =item -C module [ module ... ] Show the F files for the specified modules =item -D module [ module ... ] Show the module details. This prints one line for each out-of-date module (meaning, modules locally installed but have newer versions on CPAN). Each line has three columns: module name, local version, and CPAN version. =item -f Force the specified action, when it normally would have failed. Use this to install a module even if its tests fail. When you use this option, -i is not optional for installing a module when you need to force it: % cpan -f -i Module::Foo =item -F Turn off CPAN.pm's attempts to lock anything. You should be careful with this since you might end up with multiple scripts trying to muck in the same directory. This isn't so much of a concern if you're loading a special config with C<-j>, and that config sets up its own work directories. =item -g module [ module ... ] Downloads to the current directory the latest distribution of the module. =item -G module [ module ... ] UNIMPLEMENTED Download to the current directory the latest distribution of the modules, unpack each distribution, and create a git repository for each distribution. If you want this feature, check out Yanick Champoux's C distribution. =item -h Print a help message and exit. When you specify C<-h>, it ignores all of the other options and arguments. =item -i module [ module ... ] Install the specified modules. With no other switches, this switch is implied. =item -I Load C (think like C<-I> for loading lib paths). Too bad C<-l> was already taken. =item -j Config.pm Load the file that has the CPAN configuration data. This should have the same format as the standard F file, which defines C<$CPAN::Config> as an anonymous hash. If the file does not exist, C dies. =item -J Dump the configuration in the same format that CPAN.pm uses. This is useful for checking the configuration as well as using the dump as a starting point for a new, custom configuration. =item -l List all installed modules with their versions =item -L author [ author ... ] List the modules by the specified authors. =item -m Make the specified modules. =item -M mirror1,mirror2,... A comma-separated list of mirrors to use for just this run. The C<-P> option can find them for you automatically. =item -n Do a dry run, but don't actually install anything. (unimplemented) =item -O Show the out-of-date modules. =item -p Ping the configured mirrors and print a report =item -P Find the best mirrors you could be using and use them for the current session. =item -r Recompiles dynamically loaded modules with CPAN::Shell->recompile. =item -s Drop in the CPAN.pm shell. This command does this automatically if you don't specify any arguments. =item -t module [ module ... ] Run a `make test` on the specified modules. =item -T Do not test modules. Simply install them. =item -u Upgrade all installed modules. Blindly doing this can really break things, so keep a backup. =item -v Print the script version and CPAN.pm version then exit. =item -V Print detailed information about the cpan client. =item -w UNIMPLEMENTED Turn on cpan warnings. This checks various things, like directory permissions, and tells you about problems you might have. =item -x module [ module ... ] Find close matches to the named modules that you think you might have mistyped. This requires the optional installation of Text::Levenshtein or Text::Levenshtein::Damerau. =item -X Dump all the namespaces to standard output. =back =head2 Examples # print a help message cpan -h # print the version numbers cpan -v # create an autobundle cpan -a # recompile modules cpan -r # upgrade all installed modules cpan -u # install modules ( sole -i is optional ) cpan -i Netscape::Booksmarks Business::ISBN # force install modules ( must use -i ) cpan -fi CGI::Minimal URI # install modules but without testing them cpan -Ti CGI::Minimal URI =head2 Environment variables There are several components in CPAN.pm that use environment variables. The build tools, L and L use some, while others matter to the levels above them. Some of these are specified by the Perl Toolchain Gang: Lancaster Consensus: L Oslo Consensus: L =over 4 =item NONINTERACTIVE_TESTING Assume no one is paying attention and skips prompts for distributions that do that correctly. C sets this to C<1> unless it already has a value (even if that value is false). =item PERL_MM_USE_DEFAULT Use the default answer for a prompted questions. C sets this to C<1> unless it already has a value (even if that value is false). =item CPAN_OPTS As with C, a string of additional C options to add to those you specify on the command line. =item CPANSCRIPT_LOGLEVEL The log level to use, with either the embedded, minimal logger or L if it is installed. Possible values are the same as the C levels: C, C, C, C, C, and C. The default is C. =item GIT_COMMAND The path to the C binary to use for the Git features. The default is C. =back =head2 Methods =over 4 =cut use autouse Carp => qw(carp croak cluck); use CPAN 1.80 (); # needs no test use Config; use autouse Cwd => qw(cwd); use autouse 'Data::Dumper' => qw(Dumper); use File::Spec::Functions qw(catfile file_name_is_absolute rel2abs); use File::Basename; use Getopt::Std; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Internal constants use constant TRUE => 1; use constant FALSE => 0; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # The return values use constant HEY_IT_WORKED => 0; use constant I_DONT_KNOW_WHAT_HAPPENED => 1; # 0b0000_0001 use constant ITS_NOT_MY_FAULT => 2; use constant THE_PROGRAMMERS_AN_IDIOT => 4; use constant A_MODULE_FAILED_TO_INSTALL => 8; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # set up the order of options that we layer over CPAN::Shell BEGIN { # most of this should be in methods use vars qw( @META_OPTIONS $Default %CPAN_METHODS @CPAN_OPTIONS @option_order %Method_table %Method_table_index ); @META_OPTIONS = qw( h v V I g G M: C A D O l L a r p P j: J w x X ); $Default = 'default'; %CPAN_METHODS = ( # map switches to method names in CPAN::Shell $Default => 'install', 'c' => 'clean', 'f' => 'force', 'i' => 'install', 'm' => 'make', 't' => 'test', 'u' => 'upgrade', 'T' => 'notest', 's' => 'shell', ); @CPAN_OPTIONS = grep { $_ ne $Default } sort keys %CPAN_METHODS; @option_order = ( @META_OPTIONS, @CPAN_OPTIONS ); # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # map switches to the subroutines in this script, along with other information. # use this stuff instead of hard-coded indices and values sub NO_ARGS () { 0 } sub ARGS () { 1 } sub GOOD_EXIT () { 0 } %Method_table = ( # key => [ sub ref, takes args?, exit value, description ] # options that do their thing first, then exit h => [ \&_print_help, NO_ARGS, GOOD_EXIT, 'Printing help' ], v => [ \&_print_version, NO_ARGS, GOOD_EXIT, 'Printing version' ], V => [ \&_print_details, NO_ARGS, GOOD_EXIT, 'Printing detailed version' ], X => [ \&_list_all_namespaces, NO_ARGS, GOOD_EXIT, 'Listing all namespaces' ], # options that affect other options j => [ \&_load_config, ARGS, GOOD_EXIT, 'Use specified config file' ], J => [ \&_dump_config, NO_ARGS, GOOD_EXIT, 'Dump configuration to stdout' ], F => [ \&_lock_lobotomy, NO_ARGS, GOOD_EXIT, 'Turn off CPAN.pm lock files' ], I => [ \&_load_local_lib, NO_ARGS, GOOD_EXIT, 'Loading local::lib' ], M => [ \&_use_these_mirrors, ARGS, GOOD_EXIT, 'Setting per session mirrors' ], P => [ \&_find_good_mirrors, NO_ARGS, GOOD_EXIT, 'Finding good mirrors' ], w => [ \&_turn_on_warnings, NO_ARGS, GOOD_EXIT, 'Turning on warnings' ], # options that do their one thing g => [ \&_download, ARGS, GOOD_EXIT, 'Download the latest distro' ], G => [ \&_gitify, ARGS, GOOD_EXIT, 'Down and gitify the latest distro' ], C => [ \&_show_Changes, ARGS, GOOD_EXIT, 'Showing Changes file' ], A => [ \&_show_Author, ARGS, GOOD_EXIT, 'Showing Author' ], D => [ \&_show_Details, ARGS, GOOD_EXIT, 'Showing Details' ], O => [ \&_show_out_of_date, NO_ARGS, GOOD_EXIT, 'Showing Out of date' ], l => [ \&_list_all_mods, NO_ARGS, GOOD_EXIT, 'Listing all modules' ], L => [ \&_show_author_mods, ARGS, GOOD_EXIT, 'Showing author mods' ], a => [ \&_create_autobundle, NO_ARGS, GOOD_EXIT, 'Creating autobundle' ], p => [ \&_ping_mirrors, NO_ARGS, GOOD_EXIT, 'Pinging mirrors' ], r => [ \&_recompile, NO_ARGS, GOOD_EXIT, 'Recompiling' ], u => [ \&_upgrade, NO_ARGS, GOOD_EXIT, 'Running `make test`' ], 's' => [ \&_shell, NO_ARGS, GOOD_EXIT, 'Drop into the CPAN.pm shell' ], 'x' => [ \&_guess_namespace, ARGS, GOOD_EXIT, 'Guessing namespaces' ], c => [ \&_default, ARGS, GOOD_EXIT, 'Running `make clean`' ], f => [ \&_default, ARGS, GOOD_EXIT, 'Installing with force' ], i => [ \&_default, ARGS, GOOD_EXIT, 'Running `make install`' ], 'm' => [ \&_default, ARGS, GOOD_EXIT, 'Running `make`' ], t => [ \&_default, ARGS, GOOD_EXIT, 'Running `make test`' ], T => [ \&_default, ARGS, GOOD_EXIT, 'Installing with notest' ], ); %Method_table_index = ( code => 0, takes_args => 1, exit_value => 2, description => 3, ); } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # finally, do some argument processing sub _stupid_interface_hack_for_non_rtfmers { no warnings 'uninitialized'; shift @ARGV if( $ARGV[0] eq 'install' and @ARGV > 1 ) } sub _process_options { my %options; push @ARGV, grep $_, split /\s+/, $ENV{CPAN_OPTS} || ''; # if no arguments, just drop into the shell if( 0 == @ARGV ) { CPAN::shell(); exit 0 } elsif (Getopt::Std::getopts( join( '', @option_order ), \%options )) { \%options; } else { exit 1 } } sub _process_setup_options { my( $class, $options ) = @_; if( $options->{j} ) { $Method_table{j}[ $Method_table_index{code} ]->( $options->{j} ); delete $options->{j}; } elsif ( ! $options->{h} ) { # h "ignores all of the other options and arguments" # this is what CPAN.pm would do otherwise local $CPAN::Be_Silent = 1; CPAN::HandleConfig->load( # be_silent => 1, deprecated write_file => 0, ); } $class->_turn_off_testing if $options->{T}; foreach my $o ( qw(F I w P M) ) { next unless exists $options->{$o}; $Method_table{$o}[ $Method_table_index{code} ]->( $options->{$o} ); delete $options->{$o}; } if( $options->{o} ) { my @pairs = map { [ split /=/, $_, 2 ] } split /,/, $options->{o}; foreach my $pair ( @pairs ) { my( $setting, $value ) = @$pair; $CPAN::Config->{$setting} = $value; # $logger->debug( "Setting [$setting] to [$value]" ); } delete $options->{o}; } my $option_count = grep { $options->{$_} } @option_order; no warnings 'uninitialized'; # don't count options that imply installation foreach my $opt ( qw(f T) ) { # don't count force or notest $option_count -= $options->{$opt}; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # if there are no options, set -i (this line fixes RT ticket 16915) $options->{i}++ unless $option_count; } sub _setup_environment { # should we override or set defaults? If this were a true interactive # session, we'd be in the CPAN shell. # https://github.com/Perl-Toolchain-Gang/toolchain-site/blob/master/lancaster-consensus.md $ENV{NONINTERACTIVE_TESTING} = 1 unless defined $ENV{NONINTERACTIVE_TESTING}; $ENV{PERL_MM_USE_DEFAULT} = 1 unless defined $ENV{PERL_MM_USE_DEFAULT}; } =item run( ARGS ) Just do it. The C method returns 0 on success and a positive number on failure. See the section on EXIT CODES for details on the values. =cut my $logger; sub run { my( $class, @args ) = @_; local @ARGV = @args; my $return_value = HEY_IT_WORKED; # assume that things will work $logger = $class->_init_logger; $logger->debug( "Using logger from @{[ref $logger]}" ); $class->_hook_into_CPANpm_report; $logger->debug( "Hooked into output" ); $class->_stupid_interface_hack_for_non_rtfmers; $logger->debug( "Patched cargo culting" ); my $options = $class->_process_options; $logger->debug( "Options are @{[Dumper($options)]}" ); $class->_process_setup_options( $options ); $class->_setup_environment( $options ); OPTION: foreach my $option ( @option_order ) { next unless $options->{$option}; my( $sub, $takes_args, $description ) = map { $Method_table{$option}[ $Method_table_index{$_} ] } qw( code takes_args description ); unless( ref $sub eq ref sub {} ) { $return_value = THE_PROGRAMMERS_AN_IDIOT; last OPTION; } $logger->info( "[$option] $description -- ignoring other arguments" ) if( @ARGV && ! $takes_args ); $return_value = $sub->( \ @ARGV, $options ); last; } return $return_value; } my $LEVEL; { package Local::Null::Logger; # hide from PAUSE my @LOGLEVELS = qw(TRACE DEBUG INFO WARN ERROR FATAL); $LEVEL = uc($ENV{CPANSCRIPT_LOGLEVEL} || 'INFO'); my %LL = map { $LOGLEVELS[$_] => $_ } 0..$#LOGLEVELS; unless (defined $LL{$LEVEL}){ warn "Unsupported loglevel '$LEVEL', setting to INFO"; $LEVEL = 'INFO'; } sub new { bless \ my $x, $_[0] } sub AUTOLOAD { my $autoload = our $AUTOLOAD; $autoload =~ s/.*://; return if $LL{uc $autoload} < $LL{$LEVEL}; $CPAN::Frontend->mywarn(">($autoload): $_\n") for split /[\r\n]+/, $_[1]; } sub DESTROY { 1 } } # load a module without searching the default entry for the current # directory sub _safe_load_module { my $name = shift; local @INC = @INC; pop @INC if $INC[-1] eq '.'; eval "require $name; 1"; } sub _init_logger { my $log4perl_loaded = _safe_load_module("Log::Log4perl"); unless( $log4perl_loaded ) { print STDOUT "Loading internal logger. Log::Log4perl recommended for better logging\n"; $logger = Local::Null::Logger->new; return $logger; } Log::Log4perl::init( \ <<"HERE" ); log4perl.rootLogger=$LEVEL, A1 log4perl.appender.A1=Log::Log4perl::Appender::Screen log4perl.appender.A1.layout=PatternLayout log4perl.appender.A1.layout.ConversionPattern=%m%n HERE $logger = Log::Log4perl->get_logger( 'App::Cpan' ); } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub _default { my( $args, $options ) = @_; my $switch = ''; # choose the option that we're going to use # we'll deal with 'f' (force) later, so skip it foreach my $option ( @CPAN_OPTIONS ) { next if ( $option eq 'f' or $option eq 'T' ); next unless $options->{$option}; $switch = $option; last; } # 1. with no switches, but arguments, use the default switch (install) # 2. with no switches and no args, start the shell # 3. With a switch but no args, die! These switches need arguments. if( not $switch and @$args ) { $switch = $Default; } elsif( not $switch and not @$args ) { return CPAN::shell() } elsif( $switch and not @$args ) { die "Nothing to $CPAN_METHODS{$switch}!\n"; } # Get and check the method from CPAN::Shell my $method = $CPAN_METHODS{$switch}; die "CPAN.pm cannot $method!\n" unless CPAN::Shell->can( $method ); # call the CPAN::Shell method, with force or notest if specified my $action = do { if( $options->{f} ) { sub { CPAN::Shell->force( $method, @_ ) } } elsif( $options->{T} ) { sub { CPAN::Shell->notest( $method, @_ ) } } else { sub { CPAN::Shell->$method( @_ ) } } }; # How do I handle exit codes for multiple arguments? my @errors = (); $options->{x} or _disable_guessers(); foreach my $arg ( @$args ) { # check the argument and perhaps capture typos my $module = _expand_module( $arg ) or do { $logger->error( "Skipping $arg because I couldn't find a matching namespace." ); next; }; _clear_cpanpm_output(); $action->( $arg ); my $error = _cpanpm_output_indicates_failure(); push @errors, $error if $error; } return do { if( @errors ) { $errors[0] } else { HEY_IT_WORKED } }; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # =for comment CPAN.pm sends all the good stuff either to STDOUT, or to a temp file if $CPAN::Be_Silent is set. I have to intercept that output so I can find out what happened. =cut BEGIN { my $scalar = ''; sub _hook_into_CPANpm_report { no warnings 'redefine'; *CPAN::Shell::myprint = sub { my($self,$what) = @_; $scalar .= $what if defined $what; $self->print_ornamented($what, $CPAN::Config->{colorize_print}||'bold blue on_white', ); }; *CPAN::Shell::mywarn = sub { my($self,$what) = @_; $scalar .= $what if defined $what; $self->print_ornamented($what, $CPAN::Config->{colorize_warn}||'bold red on_white' ); }; } sub _clear_cpanpm_output { $scalar = '' } sub _get_cpanpm_output { $scalar } # These are lines I don't care about in CPAN.pm output. If I can # filter out the informational noise, I have a better chance to # catch the error signal my @skip_lines = ( qr/^\QWarning \(usually harmless\)/, qr/\bwill not store persistent state\b/, qr(//hint//), qr/^\s+reports\s+/, qr/^Try the command/, qr/^\s+$/, qr/^to find objects/, qr/^\s*Database was generated on/, qr/^Going to read/, qr|^\s+i\s+/|, # the i /Foo::Whatever/ line when it doesn't know ); sub _get_cpanpm_last_line { my $fh; if( $] < 5.008 ) { $fh = IO::Scalar->new( \ $scalar ); } else { eval q{ open $fh, '<', \\ $scalar; }; } my @lines = <$fh>; # This is a bit ugly. Once we examine a line, we have to # examine the line before it and go through all of the same # regexes. I could do something fancy, but this works. REGEXES: { foreach my $regex ( @skip_lines ) { if( $lines[-1] =~ m/$regex/ ) { pop @lines; redo REGEXES; # we have to go through all of them for every line! } } } $logger->debug( "Last interesting line of CPAN.pm output is:\n\t$lines[-1]" ); $lines[-1]; } } BEGIN { my $epic_fail_words = join '|', qw( Error stop(?:ping)? problems force not unsupported fail(?:ed)? Cannot\s+install ); sub _cpanpm_output_indicates_failure { my $last_line = _get_cpanpm_last_line(); my $result = $last_line =~ /\b(?:$epic_fail_words)\b/i; return A_MODULE_FAILED_TO_INSTALL if $last_line =~ /\b(?:Cannot\s+install)\b/i; $result || (); } } sub _cpanpm_output_indicates_success { my $last_line = _get_cpanpm_last_line(); my $result = $last_line =~ /\b(?:\s+-- OK|PASS)\b/; $result || (); } sub _cpanpm_output_is_vague { return FALSE if _cpanpm_output_indicates_failure() || _cpanpm_output_indicates_success(); return TRUE; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub _turn_on_warnings { carp "Warnings are implemented yet"; return HEY_IT_WORKED; } sub _turn_off_testing { $logger->debug( 'Trusting test report history' ); $CPAN::Config->{trust_test_report_history} = 1; return HEY_IT_WORKED; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub _print_help { $logger->info( "Use perldoc to read the documentation" ); my $HAVE_PERLDOC = eval { require Pod::Perldoc; 1; }; if ($HAVE_PERLDOC) { system qq{"$^X" -e "require Pod::Perldoc; Pod::Perldoc->run()" $0}; exit; } else { warn "Please install Pod::Perldoc, maybe try 'cpan -i Pod::Perldoc'\n"; return HEY_IT_WORKED; } } sub _print_version # -v { $logger->info( "$0 script version $VERSION, CPAN.pm version " . CPAN->VERSION ); return HEY_IT_WORKED; } sub _print_details # -V { _print_version(); _check_install_dirs(); $logger->info( '-' x 50 . "\nChecking configured mirrors..." ); foreach my $mirror ( @{ $CPAN::Config->{urllist} } ) { _print_ping_report( $mirror ); } $logger->info( '-' x 50 . "\nChecking for faster mirrors..." ); { require CPAN::Mirrors; if ( $CPAN::Config->{connect_to_internet_ok} ) { $CPAN::Frontend->myprint(qq{Trying to fetch a mirror list from the Internet\n}); eval { CPAN::FTP->localize('MIRRORED.BY',File::Spec->catfile($CPAN::Config->{keep_source_where},'MIRRORED.BY'),3,1) } or $CPAN::Frontend->mywarn(<<'HERE'); We failed to get a copy of the mirror list from the Internet. You will need to provide CPAN mirror URLs yourself. HERE $CPAN::Frontend->myprint("\n"); } my $mirrors = CPAN::Mirrors->new( _mirror_file() ); my @continents = $mirrors->find_best_continents; my @mirrors = $mirrors->get_mirrors_by_continents( $continents[0] ); my @timings = $mirrors->get_mirrors_timings( \@mirrors ); foreach my $timing ( @timings ) { $logger->info( sprintf "%s (%0.2f ms)", $timing->hostname, $timing->rtt ); } } return HEY_IT_WORKED; } sub _check_install_dirs { my $makepl_arg = $CPAN::Config->{makepl_arg}; my $mbuildpl_arg = $CPAN::Config->{mbuildpl_arg}; my @custom_dirs; # PERL_MM_OPT push @custom_dirs, $makepl_arg =~ m/INSTALL_BASE\s*=\s*(\S+)/g, $mbuildpl_arg =~ m/--install_base\s*=\s*(\S+)/g; if( @custom_dirs ) { foreach my $dir ( @custom_dirs ) { _print_inc_dir_report( $dir ); } } # XXX: also need to check makepl_args, etc my @checks = ( [ 'core', [ grep $_, @Config{qw(installprivlib installarchlib)} ] ], [ 'vendor', [ grep $_, @Config{qw(installvendorlib installvendorarch)} ] ], [ 'site', [ grep $_, @Config{qw(installsitelib installsitearch)} ] ], [ 'PERL5LIB', _split_paths( $ENV{PERL5LIB} ) ], [ 'PERLLIB', _split_paths( $ENV{PERLLIB} ) ], ); $logger->info( '-' x 50 . "\nChecking install dirs..." ); foreach my $tuple ( @checks ) { my( $label ) = $tuple->[0]; $logger->info( "Checking $label" ); $logger->info( "\tno directories for $label" ) unless @{ $tuple->[1] }; foreach my $dir ( @{ $tuple->[1] } ) { _print_inc_dir_report( $dir ); } } } sub _split_paths { [ map { _expand_filename( $_ ) } split /$Config{path_sep}/, $_[0] || '' ]; } =pod Stolen from File::Path::Expand =cut sub _expand_filename { my( $path ) = @_; no warnings 'uninitialized'; $logger->debug( "Expanding path $path\n" ); $path =~ s{\A~([^/]+)?}{ _home_of( $1 || $> ) || "~$1" }e; return $path; } sub _home_of { require User::pwent; my( $user ) = @_; my $ent = User::pwent::getpw($user) or return; return $ent->dir; } sub _get_default_inc { require Config; [ @Config::Config{ _vars() }, '.' ]; } sub _vars { qw( installarchlib installprivlib installsitearch installsitelib ); } sub _ping_mirrors { my $urls = $CPAN::Config->{urllist}; require URI; foreach my $url ( @$urls ) { my( $obj ) = URI->new( $url ); next unless _is_pingable_scheme( $obj ); my $host = $obj->host; _print_ping_report( $obj ); } } sub _is_pingable_scheme { my( $uri ) = @_; $uri->scheme eq 'file' } sub _mirror_file { my $file = do { my $file = 'MIRRORED.BY'; my $local_path = File::Spec->catfile( $CPAN::Config->{keep_source_where}, $file ); if( -e $local_path ) { $local_path } else { require CPAN::FTP; CPAN::FTP->localize( $file, $local_path, 3, 1 ); $local_path; } }; } sub _find_good_mirrors { require CPAN::Mirrors; my $mirrors = CPAN::Mirrors->new( _mirror_file() ); my @mirrors = $mirrors->best_mirrors( how_many => 5, verbose => 1, ); foreach my $mirror ( @mirrors ) { next unless eval { $mirror->can( 'http' ) }; _print_ping_report( $mirror->http ); } $CPAN::Config->{urllist} = [ map { $_->http } @mirrors ]; } sub _print_inc_dir_report { my( $dir ) = shift; my $writeable = -w $dir ? '+' : '!!! (not writeable)'; $logger->info( "\t$writeable $dir" ); return -w $dir; } sub _print_ping_report { my( $mirror ) = @_; my $rtt = eval { _get_ping_report( $mirror ) }; my $result = $rtt ? sprintf "+ (%4d ms)", $rtt * 1000 : '!'; $logger->info( sprintf "\t%s %s", $result, $mirror ); } sub _get_ping_report { require URI; my( $mirror ) = @_; my( $url ) = ref $mirror ? $mirror : URI->new( $mirror ); #XXX require Net::Ping; my $ping = Net::Ping->new( 'tcp', 1 ); if( $url->scheme eq 'file' ) { return -e $url->file; } my( $port ) = $url->port; 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( $url->host ) }; $alive ? $rtt : undef; } sub _load_local_lib # -I { $logger->debug( "Loading local::lib" ); my $rc = _safe_load_module("local::lib"); unless( $rc ) { $logger->logdie( "Could not load local::lib" ); } local::lib->import; return HEY_IT_WORKED; } sub _use_these_mirrors # -M { $logger->debug( "Setting per session mirrors" ); unless( $_[0] ) { $logger->logdie( "The -M switch requires a comma-separated list of mirrors" ); } $CPAN::Config->{urllist} = [ split /,/, $_[0] ]; $logger->debug( "Mirrors are @{$CPAN::Config->{urllist}}" ); } sub _create_autobundle { $logger->info( "Creating autobundle in $CPAN::Config->{cpan_home}/Bundle" ); CPAN::Shell->autobundle; return HEY_IT_WORKED; } sub _recompile { $logger->info( "Recompiling dynamically-loaded extensions" ); CPAN::Shell->recompile; return HEY_IT_WORKED; } sub _upgrade { $logger->info( "Upgrading all modules" ); CPAN::Shell->upgrade(); return HEY_IT_WORKED; } sub _shell { $logger->info( "Dropping into shell" ); CPAN::shell(); return HEY_IT_WORKED; } sub _load_config # -j { my $argument = shift; my $file = file_name_is_absolute( $argument ) ? $argument : rel2abs( $argument ); croak( "cpan config file [$file] for -j does not exist!\n" ) unless -e $file; # should I clear out any existing config here? $CPAN::Config = {}; delete $INC{'CPAN/Config.pm'}; my $rc = eval "require '$file'"; # CPAN::HandleConfig::require_myconfig_or_config looks for this $INC{'CPAN/MyConfig.pm'} = 'fake out!'; # CPAN::HandleConfig::load looks for this $CPAN::Config_loaded = 'fake out'; croak( "Could not load [$file]: $@\n") unless $rc; return HEY_IT_WORKED; } sub _dump_config # -J { my $args = shift; require Data::Dumper; my $fh = $args->[0] || \*STDOUT; local $Data::Dumper::Sortkeys = 1; my $dd = Data::Dumper->new( [$CPAN::Config], ['$CPAN::Config'] ); print $fh $dd->Dump, "\n1;\n__END__\n"; return HEY_IT_WORKED; } sub _lock_lobotomy # -F { no warnings 'redefine'; *CPAN::_flock = sub { 1 }; *CPAN::checklock = sub { 1 }; return HEY_IT_WORKED; } sub _download { my $args = shift; local $CPAN::DEBUG = 1; my %paths; foreach my $arg ( @$args ) { $logger->info( "Checking $arg" ); my $module = _expand_module( $arg ) or next; my $path = $module->cpan_file; $logger->debug( "Inst file would be $path\n" ); $paths{$module} = _get_file( _make_path( $path ) ); $logger->info( "Downloaded [$arg] to [$paths{$arg}]" ); } return \%paths; } sub _make_path { join "/", qw(authors id), $_[0] } sub _get_file { my $path = shift; my $loaded = _safe_load_module("LWP::Simple"); croak "You need LWP::Simple to use features that fetch files from CPAN\n" unless $loaded; my $file = substr $path, rindex( $path, '/' ) + 1; my $store_path = catfile( cwd(), $file ); $logger->debug( "Store path is $store_path" ); foreach my $site ( @{ $CPAN::Config->{urllist} } ) { my $fetch_path = join "/", $site, $path; $logger->debug( "Trying $fetch_path" ); my $status_code = LWP::Simple::getstore( $fetch_path, $store_path ); last if( 200 <= $status_code and $status_code <= 300 ); $logger->warn( "Could not get [$fetch_path]: Status code $status_code" ); } return $store_path; } sub _gitify { my $args = shift; my $loaded = _safe_load_module("Archive::Extract"); croak "You need Archive::Extract to use features that gitify distributions\n" unless $loaded; my $starting_dir = cwd(); foreach my $arg ( @$args ) { $logger->info( "Checking $arg" ); my $store_paths = _download( [ $arg ] ); $logger->debug( "gitify Store path is $store_paths->{$arg}" ); my $dirname = dirname( $store_paths->{$arg} ); my $ae = Archive::Extract->new( archive => $store_paths->{$arg} ); $ae->extract( to => $dirname ); chdir $ae->extract_path; my $git = $ENV{GIT_COMMAND} || '/usr/local/bin/git'; croak "Could not find $git" unless -e $git; croak "$git is not executable" unless -x $git; # can we do this in Pure Perl? system( $git, 'init' ); system( $git, qw( add . ) ); system( $git, qw( commit -a -m ), 'initial import' ); } chdir $starting_dir; return HEY_IT_WORKED; } sub _show_Changes { my $args = shift; foreach my $arg ( @$args ) { $logger->info( "Checking $arg\n" ); my $module = _expand_module( $arg ) or next; my $out = _get_cpanpm_output(); next unless eval { $module->inst_file }; #next if $module->uptodate; ( my $id = $module->id() ) =~ s/::/\-/; my $url = "http://search.cpan.org/~" . lc( $module->userid ) . "/" . $id . "-" . $module->cpan_version() . "/"; #print "URL: $url\n"; _get_changes_file($url); } return HEY_IT_WORKED; } sub _get_changes_file { croak "Reading Changes files requires LWP::Simple and URI\n" unless _safe_load_module("LWP::Simple") && _safe_load_module("URI"); my $url = shift; my $content = LWP::Simple::get( $url ); $logger->info( "Got $url ..." ) if defined $content; #print $content; my( $change_link ) = $content =~ m|Changes|gi; my $changes_url = URI->new_abs( $change_link, $url ); $logger->debug( "Change link is: $changes_url" ); my $changes = LWP::Simple::get( $changes_url ); print $changes; return HEY_IT_WORKED; } sub _show_Author { my $args = shift; foreach my $arg ( @$args ) { my $module = _expand_module( $arg ) or next; unless( $module ) { $logger->info( "Didn't find a $arg module, so no author!" ); next; } my $author = CPAN::Shell->expand( "Author", $module->userid ); next unless $module->userid; printf "%-25s %-8s %-25s %s\n", $arg, $module->userid, $author->email, $author->name; } return HEY_IT_WORKED; } sub _show_Details { my $args = shift; foreach my $arg ( @$args ) { my $module = _expand_module( $arg ) or next; my $author = CPAN::Shell->expand( "Author", $module->userid ); next unless $module->userid; print "$arg\n", "-" x 73, "\n\t"; print join "\n\t", $module->description ? $module->description : "(no description)", $module->cpan_file ? $module->cpan_file : "(no cpanfile)", $module->inst_file ? $module->inst_file :"(no installation file)" , 'Installed: ' . ($module->inst_version ? $module->inst_version : "not installed"), 'CPAN: ' . $module->cpan_version . ' ' . ($module->uptodate ? "" : "Not ") . "up to date", $author->fullname . " (" . $module->userid . ")", $author->email; print "\n\n"; } return HEY_IT_WORKED; } BEGIN { my $modules; sub _get_all_namespaces { return $modules if $modules; $modules = [ map { $_->id } CPAN::Shell->expand( "Module", "/./" ) ]; } } sub _show_out_of_date { my $modules = _get_all_namespaces(); printf "%-40s %6s %6s\n", "Module Name", "Local", "CPAN"; print "-" x 73, "\n"; foreach my $module ( @$modules ) { next unless $module = _expand_module($module); next unless $module->inst_file; next if $module->uptodate; printf "%-40s %.4f %.4f\n", $module->id, $module->inst_version ? $module->inst_version : '', $module->cpan_version; } return HEY_IT_WORKED; } sub _show_author_mods { my $args = shift; my %hash = map { lc $_, 1 } @$args; my $modules = _get_all_namespaces(); foreach my $module ( @$modules ) { next unless exists $hash{ lc $module->userid }; print $module->id, "\n"; } return HEY_IT_WORKED; } sub _list_all_mods # -l { require File::Find; my $args = shift; my $fh = \*STDOUT; INC: foreach my $inc ( @INC ) { my( $wanted, $reporter ) = _generator(); File::Find::find( { wanted => $wanted }, $inc ); my $count = 0; FILE: foreach my $file ( @{ $reporter->() } ) { my $version = _parse_version_safely( $file ); my $module_name = _path_to_module( $inc, $file ); next FILE unless defined $module_name; print $fh "$module_name\t$version\n"; #last if $count++ > 5; } } return HEY_IT_WORKED; } sub _generator { my @files = (); sub { push @files, File::Spec->canonpath( $File::Find::name ) if m/\A\w+\.pm\z/ }, sub { \@files }, } sub _parse_version_safely # stolen from PAUSE's mldistwatch, but refactored { my( $file ) = @_; local $/ = "\n"; local $_; # don't mess with the $_ in the map calling this return unless open FILE, "<$file"; my $in_pod = 0; my $version; while( ) { chomp; $in_pod = /^=(?!cut)/ ? 1 : /^=cut/ ? 0 : $in_pod; next if $in_pod || /^\s*#/; next unless /([\$*])(([\w\:\']*)\bVERSION)\b.*\=/; my( $sigil, $var ) = ( $1, $2 ); $version = _eval_version( $_, $sigil, $var ); last; } close FILE; return 'undef' unless defined $version; return $version; } sub _eval_version { my( $line, $sigil, $var ) = @_; # split package line to hide from PAUSE my $eval = qq{ package ExtUtils::MakeMaker::_version; local $sigil$var; \$$var=undef; do { $line }; \$$var }; my $version = do { local $^W = 0; no strict; eval( $eval ); }; return $version; } sub _path_to_module { my( $inc, $path ) = @_; return if length $path < length $inc; my $module_path = substr( $path, length $inc ); $module_path =~ s/\.pm\z//; # XXX: this is cheating and doesn't handle everything right my @dirs = grep { ! /\W/ } File::Spec->splitdir( $module_path ); shift @dirs; my $module_name = join "::", @dirs; return $module_name; } sub _expand_module { my( $module ) = @_; my $expanded = CPAN::Shell->expandany( $module ); return $expanded if $expanded; $expanded = CPAN::Shell->expand( "Module", $module ); unless( defined $expanded ) { $logger->error( "Could not expand [$module]. Check the module name." ); my $threshold = ( grep { int } sort { length $a <=> length $b } length($module)/4, 4 )[0]; my $guesses = _guess_at_module_name( $module, $threshold ); if( defined $guesses and @$guesses ) { $logger->info( "Perhaps you meant one of these:" ); foreach my $guess ( @$guesses ) { $logger->info( "\t$guess" ); } } return; } return $expanded; } my $guessers = [ [ qw( Text::Levenshtein::XS distance 7 1 ) ], [ qw( Text::Levenshtein::Damerau::XS xs_edistance 7 1 ) ], [ qw( Text::Levenshtein distance 7 1 ) ], [ qw( Text::Levenshtein::Damerau::PP pp_edistance 7 1 ) ], ]; sub _disable_guessers { $_->[-1] = 0 for @$guessers; } # for -x sub _guess_namespace { my $args = shift; foreach my $arg ( @$args ) { $logger->debug( "Checking $arg" ); my $guesses = _guess_at_module_name( $arg ); foreach my $guess ( @$guesses ) { print $guess, "\n"; } } return HEY_IT_WORKED; } sub _list_all_namespaces { my $modules = _get_all_namespaces(); foreach my $module ( @$modules ) { print $module, "\n"; } } BEGIN { my $distance; my $_threshold; my $can_guess; my $shown_help = 0; sub _guess_at_module_name { my( $target, $threshold ) = @_; unless( defined $distance ) { foreach my $try ( @$guessers ) { $can_guess = eval "require $try->[0]; 1" or next; $try->[-1] or next; # disabled no strict 'refs'; $distance = \&{ join "::", @$try[0,1] }; $threshold ||= $try->[2]; } } $_threshold ||= $threshold; unless( $distance ) { unless( $shown_help ) { my $modules = join ", ", map { $_->[0] } @$guessers; substr $modules, rindex( $modules, ',' ), 1, ', and'; # Should this be colorized? if( $can_guess ) { $logger->info( "I can suggest names if you provide the -x option on invocation." ); } else { $logger->info( "I can suggest names if you install one of $modules" ); $logger->info( "and you provide the -x option on invocation." ); } $shown_help++; } return; } my $modules = _get_all_namespaces(); $logger->info( "Checking " . @$modules . " namespaces for close match suggestions" ); my %guesses; foreach my $guess ( @$modules ) { my $distance = $distance->( $target, $guess ); next if $distance > $_threshold; $guesses{$guess} = $distance; } my @guesses = sort { $guesses{$a} <=> $guesses{$b} } keys %guesses; return [ grep { defined } @guesses[0..9] ]; } } 1; =back =head1 EXIT VALUES The script exits with zero if it thinks that everything worked, or a positive number if it thinks that something failed. Note, however, that in some cases it has to divine a failure by the output of things it does not control. For now, the exit codes are vague: 1 An unknown error 2 The was an external problem 4 There was an internal problem with the script 8 A module failed to install =head1 TO DO * There is initial support for Log4perl if it is available, but I haven't gone through everything to make the NullLogger work out correctly if Log4perl is not installed. * When I capture CPAN.pm output, I need to check for errors and report them to the user. * Warnings switch * Check then exit =head1 BUGS * none noted =head1 SEE ALSO L, L =head1 SOURCE AVAILABILITY This code is in Github in the CPAN.pm repository: https://github.com/andk/cpanpm The source used to be tracked separately in another GitHub repo, but the canonical source is now in the above repo. =head1 CREDITS Japheth Cleaver added the bits to allow a forced install (C<-f>). Jim Brandt suggested and provided the initial implementation for the up-to-date and Changes features. Adam Kennedy pointed out that C causes problems on Windows where this script ends up with a .bat extension David Golden helps integrate this into the C repos. Jim Keenan fixed up various issues with _download =head1 AUTHOR brian d foy, C<< >> =head1 COPYRIGHT Copyright (c) 2001-2021, brian d foy, All Rights Reserved. You may redistribute this under the same terms as Perl itself. =cut # Local Variables: # mode: cperl # indent-tabs-mode: t # cperl-indent-level: 8 # cperl-continued-statement-offset: 8 # End: PK!!Gssperl5/Capture/Tiny.pmnu6$use 5.006; use strict; use warnings; package Capture::Tiny; # ABSTRACT: Capture STDOUT and STDERR from Perl, XS or external programs our $VERSION = '0.48'; use Carp (); use Exporter (); use IO::Handle (); use File::Spec (); use File::Temp qw/tempfile tmpnam/; use Scalar::Util qw/reftype blessed/; # Get PerlIO or fake it BEGIN { local $@; eval { require PerlIO; PerlIO->can('get_layers') } or *PerlIO::get_layers = sub { return () }; } #--------------------------------------------------------------------------# # create API subroutines and export them # [do STDOUT flag, do STDERR flag, do merge flag, do tee flag] #--------------------------------------------------------------------------# my %api = ( capture => [1,1,0,0], capture_stdout => [1,0,0,0], capture_stderr => [0,1,0,0], capture_merged => [1,1,1,0], tee => [1,1,0,1], tee_stdout => [1,0,0,1], tee_stderr => [0,1,0,1], tee_merged => [1,1,1,1], ); for my $sub ( keys %api ) { my $args = join q{, }, @{$api{$sub}}; eval "sub $sub(&;@) {unshift \@_, $args; goto \\&_capture_tee;}"; ## no critic } our @ISA = qw/Exporter/; our @EXPORT_OK = keys %api; our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); #--------------------------------------------------------------------------# # constants and fixtures #--------------------------------------------------------------------------# my $IS_WIN32 = $^O eq 'MSWin32'; ##our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG}; ## ##my $DEBUGFH; ##open $DEBUGFH, "> DEBUG" if $DEBUG; ## ##*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0}; our $TIMEOUT = 30; #--------------------------------------------------------------------------# # command to tee output -- the argument is a filename that must # be opened to signal that the process is ready to receive input. # This is annoying, but seems to be the best that can be done # as a simple, portable IPC technique #--------------------------------------------------------------------------# my @cmd = ($^X, '-C0', '-e', <<'HERE'); use Fcntl; $SIG{HUP}=sub{exit}; if ( my $fn=shift ) { sysopen(my $fh, qq{$fn}, O_WRONLY|O_CREAT|O_EXCL) or die $!; print {$fh} $$; close $fh; } my $buf; while (sysread(STDIN, $buf, 2048)) { syswrite(STDOUT, $buf); syswrite(STDERR, $buf); } HERE #--------------------------------------------------------------------------# # filehandle manipulation #--------------------------------------------------------------------------# sub _relayer { my ($fh, $apply_layers) = @_; # _debug("# requested layers (@{$layers}) for @{[fileno $fh]}\n"); # eliminate pseudo-layers binmode( $fh, ":raw" ); # strip off real layers until only :unix is left while ( 1 < ( my $layers =()= PerlIO::get_layers( $fh, output => 1 ) ) ) { binmode( $fh, ":pop" ); } # apply other layers my @to_apply = @$apply_layers; shift @to_apply; # eliminate initial :unix # _debug("# applying layers (unix @to_apply) to @{[fileno $fh]}\n"); binmode($fh, ":" . join(":",@to_apply)); } sub _name { my $glob = shift; no strict 'refs'; ## no critic return *{$glob}{NAME}; } sub _open { open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!"; # _debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" ); } sub _close { # _debug( "# closing " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . " on " . fileno( $_[0] ) . "\n" ); close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!"; } my %dup; # cache this so STDIN stays fd0 my %proxy_count; sub _proxy_std { my %proxies; if ( ! defined fileno STDIN ) { $proxy_count{stdin}++; if (defined $dup{stdin}) { _open \*STDIN, "<&=" . fileno($dup{stdin}); # _debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); } else { _open \*STDIN, "<" . File::Spec->devnull; # _debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); _open $dup{stdin} = IO::Handle->new, "<&=STDIN"; } $proxies{stdin} = \*STDIN; binmode(STDIN, ':utf8') if $] >= 5.008; ## no critic } if ( ! defined fileno STDOUT ) { $proxy_count{stdout}++; if (defined $dup{stdout}) { _open \*STDOUT, ">&=" . fileno($dup{stdout}); # _debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); } else { _open \*STDOUT, ">" . File::Spec->devnull; # _debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); _open $dup{stdout} = IO::Handle->new, ">&=STDOUT"; } $proxies{stdout} = \*STDOUT; binmode(STDOUT, ':utf8') if $] >= 5.008; ## no critic } if ( ! defined fileno STDERR ) { $proxy_count{stderr}++; if (defined $dup{stderr}) { _open \*STDERR, ">&=" . fileno($dup{stderr}); # _debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); } else { _open \*STDERR, ">" . File::Spec->devnull; # _debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); _open $dup{stderr} = IO::Handle->new, ">&=STDERR"; } $proxies{stderr} = \*STDERR; binmode(STDERR, ':utf8') if $] >= 5.008; ## no critic } return %proxies; } sub _unproxy { my (%proxies) = @_; # _debug( "# unproxying: " . join(" ", keys %proxies) . "\n" ); for my $p ( keys %proxies ) { $proxy_count{$p}--; # _debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" ); if ( ! $proxy_count{$p} ) { _close $proxies{$p}; _close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup delete $dup{$p}; } } } sub _copy_std { my %handles; for my $h ( qw/stdout stderr stdin/ ) { next if $h eq 'stdin' && ! $IS_WIN32; # WIN32 hangs on tee without STDIN copied my $redir = $h eq 'stdin' ? "<&" : ">&"; _open $handles{$h} = IO::Handle->new(), $redir . uc($h); # ">&STDOUT" or "<&STDIN" } return \%handles; } # In some cases we open all (prior to forking) and in others we only open # the output handles (setting up redirection) sub _open_std { my ($handles) = @_; _open \*STDIN, "<&" . fileno $handles->{stdin} if defined $handles->{stdin}; _open \*STDOUT, ">&" . fileno $handles->{stdout} if defined $handles->{stdout}; _open \*STDERR, ">&" . fileno $handles->{stderr} if defined $handles->{stderr}; } #--------------------------------------------------------------------------# # private subs #--------------------------------------------------------------------------# sub _start_tee { my ($which, $stash) = @_; # $which is "stdout" or "stderr" # setup pipes $stash->{$_}{$which} = IO::Handle->new for qw/tee reader/; pipe $stash->{reader}{$which}, $stash->{tee}{$which}; # _debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " " . fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which}) . " " . fileno( $stash->{reader}{$which}) . "\n" ); select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush # setup desired redirection for parent and child $stash->{new}{$which} = $stash->{tee}{$which}; $stash->{child}{$which} = { stdin => $stash->{reader}{$which}, stdout => $stash->{old}{$which}, stderr => $stash->{capture}{$which}, }; # flag file is used to signal the child is ready $stash->{flag_files}{$which} = scalar( tmpnam() ) . $$; # execute @cmd as a separate process if ( $IS_WIN32 ) { my $old_eval_err=$@; undef $@; eval "use Win32API::File qw/GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ "; # _debug( "# Win32API::File loaded\n") unless $@; my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} ); # _debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE(); my $result = SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0); # _debug( $result ? "# set no-inherit flag on $which tee\n" : ("# can't disable tee handle flag inherit: " . fileLastError() . "\n")); _open_std( $stash->{child}{$which} ); $stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which}); # not restoring std here as it all gets redirected again shortly anyway $@=$old_eval_err; } else { # use fork _fork_exec( $which, $stash ); } } sub _fork_exec { my ($which, $stash) = @_; # $which is "stdout" or "stderr" my $pid = fork; if ( not defined $pid ) { Carp::confess "Couldn't fork(): $!"; } elsif ($pid == 0) { # child # _debug( "# in child process ...\n" ); untie *STDIN; untie *STDOUT; untie *STDERR; _close $stash->{tee}{$which}; # _debug( "# redirecting handles in child ...\n" ); _open_std( $stash->{child}{$which} ); # _debug( "# calling exec on command ...\n" ); exec @cmd, $stash->{flag_files}{$which}; } $stash->{pid}{$which} = $pid } my $have_usleep = eval "use Time::HiRes 'usleep'; 1"; sub _files_exist { return 1 if @_ == grep { -f } @_; Time::HiRes::usleep(1000) if $have_usleep; return 0; } sub _wait_for_tees { my ($stash) = @_; my $start = time; my @files = values %{$stash->{flag_files}}; my $timeout = defined $ENV{PERL_CAPTURE_TINY_TIMEOUT} ? $ENV{PERL_CAPTURE_TINY_TIMEOUT} : $TIMEOUT; 1 until _files_exist(@files) || ($timeout && (time - $start > $timeout)); Carp::confess "Timed out waiting for subprocesses to start" if ! _files_exist(@files); unlink $_ for @files; } sub _kill_tees { my ($stash) = @_; if ( $IS_WIN32 ) { # _debug( "# closing handles\n"); close($_) for values %{ $stash->{tee} }; # _debug( "# waiting for subprocesses to finish\n"); my $start = time; 1 until wait == -1 || (time - $start > 30); } else { _close $_ for values %{ $stash->{tee} }; waitpid $_, 0 for values %{ $stash->{pid} }; } } sub _slurp { my ($name, $stash) = @_; my ($fh, $pos) = map { $stash->{$_}{$name} } qw/capture pos/; # _debug( "# slurping captured $name from " . fileno($fh) . " at pos $pos with layers: @{[PerlIO::get_layers($fh)]}\n"); seek( $fh, $pos, 0 ) or die "Couldn't seek on capture handle for $name\n"; my $text = do { local $/; scalar readline $fh }; return defined($text) ? $text : ""; } #--------------------------------------------------------------------------# # _capture_tee() -- generic main sub for capturing or teeing #--------------------------------------------------------------------------# sub _capture_tee { # _debug( "# starting _capture_tee with (@_)...\n" ); my ($do_stdout, $do_stderr, $do_merge, $do_tee, $code, @opts) = @_; my %do = ($do_stdout ? (stdout => 1) : (), $do_stderr ? (stderr => 1) : ()); Carp::confess("Custom capture options must be given as key/value pairs\n") unless @opts % 2 == 0; my $stash = { capture => { @opts } }; for ( keys %{$stash->{capture}} ) { my $fh = $stash->{capture}{$_}; Carp::confess "Custom handle for $_ must be seekable\n" unless ref($fh) eq 'GLOB' || (blessed($fh) && $fh->isa("IO::Seekable")); } # save existing filehandles and setup captures local *CT_ORIG_STDIN = *STDIN ; local *CT_ORIG_STDOUT = *STDOUT; local *CT_ORIG_STDERR = *STDERR; # find initial layers my %layers = ( stdin => [PerlIO::get_layers(\*STDIN) ], stdout => [PerlIO::get_layers(\*STDOUT, output => 1)], stderr => [PerlIO::get_layers(\*STDERR, output => 1)], ); # _debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; # get layers from underlying glob of tied filehandles if we can # (this only works for things that work like Tie::StdHandle) $layers{stdout} = [PerlIO::get_layers(tied *STDOUT)] if tied(*STDOUT) && (reftype tied *STDOUT eq 'GLOB'); $layers{stderr} = [PerlIO::get_layers(tied *STDERR)] if tied(*STDERR) && (reftype tied *STDERR eq 'GLOB'); # _debug( "# tied object corrected layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; # bypass scalar filehandles and tied handles # localize scalar STDIN to get a proxy to pick up FD0, then restore later to CT_ORIG_STDIN my %localize; $localize{stdin}++, local(*STDIN) if grep { $_ eq 'scalar' } @{$layers{stdin}}; $localize{stdout}++, local(*STDOUT) if $do_stdout && grep { $_ eq 'scalar' } @{$layers{stdout}}; $localize{stderr}++, local(*STDERR) if ($do_stderr || $do_merge) && grep { $_ eq 'scalar' } @{$layers{stderr}}; $localize{stdin}++, local(*STDIN), _open( \*STDIN, "<&=0") if tied *STDIN && $] >= 5.008; $localize{stdout}++, local(*STDOUT), _open( \*STDOUT, ">&=1") if $do_stdout && tied *STDOUT && $] >= 5.008; $localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2") if ($do_stderr || $do_merge) && tied *STDERR && $] >= 5.008; # _debug( "# localized $_\n" ) for keys %localize; # proxy any closed/localized handles so we don't use fds 0, 1 or 2 my %proxy_std = _proxy_std(); # _debug( "# proxy std: @{ [%proxy_std] }\n" ); # update layers after any proxying $layers{stdout} = [PerlIO::get_layers(\*STDOUT, output => 1)] if $proxy_std{stdout}; $layers{stderr} = [PerlIO::get_layers(\*STDERR, output => 1)] if $proxy_std{stderr}; # _debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; # store old handles and setup handles for capture $stash->{old} = _copy_std(); $stash->{new} = { %{$stash->{old}} }; # default to originals for ( keys %do ) { $stash->{new}{$_} = ($stash->{capture}{$_} ||= File::Temp->new); seek( $stash->{capture}{$_}, 0, 2 ) or die "Could not seek on capture handle for $_\n"; $stash->{pos}{$_} = tell $stash->{capture}{$_}; # _debug("# will capture $_ on " . fileno($stash->{capture}{$_})."\n" ); _start_tee( $_ => $stash ) if $do_tee; # tees may change $stash->{new} } _wait_for_tees( $stash ) if $do_tee; # finalize redirection $stash->{new}{stderr} = $stash->{new}{stdout} if $do_merge; # _debug( "# redirecting in parent ...\n" ); _open_std( $stash->{new} ); # execute user provided code my ($exit_code, $inner_error, $outer_error, $orig_pid, @result); { $orig_pid = $$; local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN # _debug( "# finalizing layers ...\n" ); _relayer(\*STDOUT, $layers{stdout}) if $do_stdout; _relayer(\*STDERR, $layers{stderr}) if $do_stderr; # _debug( "# running code $code ...\n" ); my $old_eval_err=$@; undef $@; eval { @result = $code->(); $inner_error = $@ }; $exit_code = $?; # save this for later $outer_error = $@; # save this for later STDOUT->flush if $do_stdout; STDERR->flush if $do_stderr; $@ = $old_eval_err; } # restore prior filehandles and shut down tees # _debug( "# restoring filehandles ...\n" ); _open_std( $stash->{old} ); _close( $_ ) for values %{$stash->{old}}; # don't leak fds # shouldn't need relayering originals, but see rt.perl.org #114404 _relayer(\*STDOUT, $layers{stdout}) if $do_stdout; _relayer(\*STDERR, $layers{stderr}) if $do_stderr; _unproxy( %proxy_std ); # _debug( "# killing tee subprocesses ...\n" ) if $do_tee; _kill_tees( $stash ) if $do_tee; # return captured output, but shortcut in void context # unless we have to echo output to tied/scalar handles; my %got; if ( $orig_pid == $$ and ( defined wantarray or ($do_tee && keys %localize) ) ) { for ( keys %do ) { _relayer($stash->{capture}{$_}, $layers{$_}); $got{$_} = _slurp($_, $stash); # _debug("# slurped " . length($got{$_}) . " bytes from $_\n"); } print CT_ORIG_STDOUT $got{stdout} if $do_stdout && $do_tee && $localize{stdout}; print CT_ORIG_STDERR $got{stderr} if $do_stderr && $do_tee && $localize{stderr}; } $? = $exit_code; $@ = $inner_error if $inner_error; die $outer_error if $outer_error; # _debug( "# ending _capture_tee with (@_)...\n" ); return unless defined wantarray; my @return; push @return, $got{stdout} if $do_stdout; push @return, $got{stderr} if $do_stderr && ! $do_merge; push @return, @result; return wantarray ? @return : $return[0]; } 1; __END__ =pod =encoding UTF-8 =head1 NAME Capture::Tiny - Capture STDOUT and STDERR from Perl, XS or external programs =head1 VERSION version 0.48 =head1 SYNOPSIS use Capture::Tiny ':all'; # capture from external command ($stdout, $stderr, $exit) = capture { system( $cmd, @args ); }; # capture from arbitrary code (Perl or external) ($stdout, $stderr, @result) = capture { # your code here }; # capture partial or merged output $stdout = capture_stdout { ... }; $stderr = capture_stderr { ... }; $merged = capture_merged { ... }; # tee output ($stdout, $stderr) = tee { # your code here }; $stdout = tee_stdout { ... }; $stderr = tee_stderr { ... }; $merged = tee_merged { ... }; =head1 DESCRIPTION Capture::Tiny provides a simple, portable way to capture almost anything sent to STDOUT or STDERR, regardless of whether it comes from Perl, from XS code or from an external program. Optionally, output can be teed so that it is captured while being passed through to the original filehandles. Yes, it even works on Windows (usually). Stop guessing which of a dozen capturing modules to use in any particular situation and just use this one. =head1 USAGE The following functions are available. None are exported by default. =head2 capture ($stdout, $stderr, @result) = capture \&code; $stdout = capture \&code; The C function takes a code reference and returns what is sent to STDOUT and STDERR as well as any return values from the code reference. In scalar context, it returns only STDOUT. If no output was received for a filehandle, it returns an empty string for that filehandle. Regardless of calling context, all output is captured -- nothing is passed to the existing filehandles. It is prototyped to take a subroutine reference as an argument. Thus, it can be called in block form: ($stdout, $stderr) = capture { # your code here ... }; Note that the coderef is evaluated in list context. If you wish to force scalar context on the return value, you must use the C keyword. ($stdout, $stderr, $count) = capture { my @list = qw/one two three/; return scalar @list; # $count will be 3 }; Also note that within the coderef, the C<@_> variable will be empty. So don't use arguments from a surrounding subroutine without copying them to an array first: sub wont_work { my ($stdout, $stderr) = capture { do_stuff( @_ ) }; # WRONG ... } sub will_work { my @args = @_; my ($stdout, $stderr) = capture { do_stuff( @args ) }; # RIGHT ... } Captures are normally done to an anonymous temporary filehandle. To capture via a named file (e.g. to externally monitor a long-running capture), provide custom filehandles as a trailing list of option pairs: my $out_fh = IO::File->new("out.txt", "w+"); my $err_fh = IO::File->new("out.txt", "w+"); capture { ... } stdout => $out_fh, stderr => $err_fh; The filehandles must be read/write and seekable. Modifying the files or filehandles during a capture operation will give unpredictable results. Existing IO layers on them may be changed by the capture. When called in void context, C saves memory and time by not reading back from the capture handles. =head2 capture_stdout ($stdout, @result) = capture_stdout \&code; $stdout = capture_stdout \&code; The C function works just like C except only STDOUT is captured. STDERR is not captured. =head2 capture_stderr ($stderr, @result) = capture_stderr \&code; $stderr = capture_stderr \&code; The C function works just like C except only STDERR is captured. STDOUT is not captured. =head2 capture_merged ($merged, @result) = capture_merged \&code; $merged = capture_merged \&code; The C function works just like C except STDOUT and STDERR are merged. (Technically, STDERR is redirected to the same capturing handle as STDOUT before executing the function.) Caution: STDOUT and STDERR output in the merged result are not guaranteed to be properly ordered due to buffering. =head2 tee ($stdout, $stderr, @result) = tee \&code; $stdout = tee \&code; The C function works just like C, except that output is captured as well as passed on to the original STDOUT and STDERR. When called in void context, C saves memory and time by not reading back from the capture handles, except when the original STDOUT OR STDERR were tied or opened to a scalar handle. =head2 tee_stdout ($stdout, @result) = tee_stdout \&code; $stdout = tee_stdout \&code; The C function works just like C except only STDOUT is teed. STDERR is not teed (output goes to STDERR as usual). =head2 tee_stderr ($stderr, @result) = tee_stderr \&code; $stderr = tee_stderr \&code; The C function works just like C except only STDERR is teed. STDOUT is not teed (output goes to STDOUT as usual). =head2 tee_merged ($merged, @result) = tee_merged \&code; $merged = tee_merged \&code; The C function works just like C except that output is captured as well as passed on to STDOUT. Caution: STDOUT and STDERR output in the merged result are not guaranteed to be properly ordered due to buffering. =head1 LIMITATIONS =head2 Portability Portability is a goal, not a guarantee. C requires fork, except on Windows where C is used instead. Not tested on any particularly esoteric platforms yet. See the L for test result by platform. =head2 PerlIO layers Capture::Tiny does its best to preserve PerlIO layers such as ':utf8' or ':crlf' when capturing (only for Perl 5.8.1+) . Layers should be applied to STDOUT or STDERR I the call to C or C. This may not work for tied filehandles (see below). =head2 Modifying filehandles before capturing Generally speaking, you should do little or no manipulation of the standard IO filehandles prior to using Capture::Tiny. In particular, closing, reopening, localizing or tying standard filehandles prior to capture may cause a variety of unexpected, undesirable and/or unreliable behaviors, as described below. Capture::Tiny does its best to compensate for these situations, but the results may not be what you desire. =head3 Closed filehandles Capture::Tiny will work even if STDIN, STDOUT or STDERR have been previously closed. However, since they will be reopened to capture or tee output, any code within the captured block that depends on finding them closed will, of course, not find them to be closed. If they started closed, Capture::Tiny will close them again when the capture block finishes. Note that this reopening will happen even for STDIN or a filehandle not being captured to ensure that the filehandle used for capture is not opened to file descriptor 0, as this causes problems on various platforms. Prior to Perl 5.12, closed STDIN combined with PERL_UNICODE=D leaks filehandles and also breaks tee() for undiagnosed reasons. So don't do that. =head3 Localized filehandles If code localizes any of Perl's standard filehandles before capturing, the capture will affect the localized filehandles and not the original ones. External system calls are not affected by localizing a filehandle in Perl and will continue to send output to the original filehandles (which will thus not be captured). =head3 Scalar filehandles If STDOUT or STDERR are reopened to scalar filehandles prior to the call to C or C, then Capture::Tiny will override the output filehandle for the duration of the C or C call and then, for C, send captured output to the output filehandle after the capture is complete. (Requires Perl 5.8) Capture::Tiny attempts to preserve the semantics of STDIN opened to a scalar reference, but note that external processes will not be able to read from such a handle. Capture::Tiny tries to ensure that external processes will read from the null device instead, but this is not guaranteed. =head3 Tied output filehandles If STDOUT or STDERR are tied prior to the call to C or C, then Capture::Tiny will attempt to override the tie for the duration of the C or C call and then send captured output to the tied filehandle after the capture is complete. (Requires Perl 5.8) Capture::Tiny may not succeed resending UTF-8 encoded data to a tied STDOUT or STDERR filehandle. Characters may appear as bytes. If the tied filehandle is based on L, then Capture::Tiny will attempt to determine appropriate layers like C<:utf8> from the underlying filehandle and do the right thing. =head3 Tied input filehandle Capture::Tiny attempts to preserve the semantics of tied STDIN, but this requires Perl 5.8 and is not entirely predictable. External processes will not be able to read from such a handle. Unless having STDIN tied is crucial, it may be safest to localize STDIN when capturing: my ($out, $err) = do { local *STDIN; capture { ... } }; =head2 Modifying filehandles during a capture Attempting to modify STDIN, STDOUT or STDERR I C or C is almost certainly going to cause problems. Don't do that. =head3 Forking inside a capture Forks aren't portable. The behavior of filehandles during a fork is even less so. If Capture::Tiny detects that a fork has occurred within a capture, it will shortcut in the child process and return empty strings for captures. Other problems may occur in the child or parent, as well. Forking in a capture block is not recommended. =head3 Using threads Filehandles are global. Mixing up I/O and captures in different threads without coordination is going to cause problems. Besides, threads are officially discouraged. =head3 Dropping privileges during a capture If you drop privileges during a capture, temporary files created to facilitate the capture may not be cleaned up afterwards. =head2 No support for Perl 5.8.0 It's just too buggy when it comes to layers and UTF-8. Perl 5.8.1 or later is recommended. =head2 Limited support for Perl 5.6 Perl 5.6 predates PerlIO. UTF-8 data may not be captured correctly. =head1 ENVIRONMENT =head2 PERL_CAPTURE_TINY_TIMEOUT Capture::Tiny uses subprocesses internally for C. By default, Capture::Tiny will timeout with an error if such subprocesses are not ready to receive data within 30 seconds (or whatever is the value of C<$Capture::Tiny::TIMEOUT>). An alternate timeout may be specified by setting the C environment variable. Setting it to zero will disable timeouts. B, this does not timeout the code reference being captured -- this only prevents Capture::Tiny itself from hanging your process waiting for its child processes to be ready to proceed. =head1 SEE ALSO This module was inspired by L, which provides similar functionality without the ability to tee output and with more complicated code and API. L does not handle layers or most of the unusual cases described in the L section and I no longer recommend it. There are many other CPAN modules that provide some sort of output capture, albeit with various limitations that make them appropriate only in particular circumstances. I'm probably missing some. The long list is provided to show why I felt Capture::Tiny was necessary. =over 4 =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =item * L =back =for :stopwords cpan testmatrix url annocpan anno 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/dagolden/Capture-Tiny.git =head1 AUTHOR David Golden =head1 CONTRIBUTORS =for stopwords Dagfinn Ilmari Mannsåker David E. Wheeler fecundf Graham Knop Peter Rabbitson =over 4 =item * Dagfinn Ilmari Mannsåker =item * David E. Wheeler =item * fecundf =item * Graham Knop =item * Peter Rabbitson =back =head1 COPYRIGHT AND LICENSE This software is Copyright (c) 2009 by David Golden. This is free software, licensed under: The Apache License, Version 2.0, January 2004 =cut PK!CP>>perl5/POD2/PT_BR/local/lib.podnu6$=encoding utf8 =head1 NAME local::lib~[pt_br] - crie e use um diretório lib/ local para módulos perl com PERL5LIB =head1 SINOPSE No código - use local::lib; # configura um lib local em ~/perl5 use local::lib '~/foo'; # idem, mas ~/foo # Ou... use FindBin; use local::lib "$FindBin::Bin/../suporte"; # bibliotecas de suporte locais à aplicação Pela linha de comando (shell) - # Instala o LWP e suas dependências não encontradas no diretório '~/perl5' perl -MCPAN -Mlocal::lib -e 'CPAN::install(LWP)' # Apenas exibe alguns comandos úteis para a shell $ perl -Mlocal::lib export PERL_MB_OPT='--install_base /home/username/perl5' export PERL_MM_OPT='INSTALL_BASE=/home/username/perl5' export PERL5LIB='/home/username/perl5/lib/perl5/i386-linux:/home/username/perl5/lib/perl5' export PATH="/home/username/perl5/bin:$PATH" =head2 A técnica de 'bootstrapping' Uma forma comum de instalar o local::lib é usando o que é conhecido como técnica de "bootstrapping". É uma boa abordagem caso seu administrador de sistemas não tenha instalado o local::lib. Nesse caso, você precisará instalar o local::lib em seu diretório de usuário. Caso você tenha privilégios de administrador, ainda assim deverá configurar suas variáveis de ambiente, como discutido no passo 4, abaixo. Sem elas, você ainda instalará módulos no CPAN do sistema e seus scripts Perl não utilizarão o caminho para o lib/ que você definiu com o local::lib. Por padrão, o local::lib instala os módulos do CPAN e a si próprio em ~/perl5. Usuários do Windows devem ler L. 1. Baixe e descompacte o local::lib do CPAN (procure por "Download" na página do CPAN sobre o local::lib). Faça isso como um usuário comum, não como root ou administrador. Descompacte o arquivo em seu diretório de usuário ou em qualquer outro local conveniente. 2. Execute isso: perl Makefile.PL --bootstrap Caso o sistema pergunte se deve configurar tudo que puder automaticamente, você provavelmente deve responder que sim (yes). Para instalar o local::lib em um diretório que não o padrão, você precisará especificá-lo ao chamar o bootstrap, da seguinte forma: perl Makefile.PL --bootstrap=~/foo 3. Execute isso: (local::lib assume que você possui o comando 'make' instalado em seu sistema) make test && make install 4. Agora precisamos configurar as variáveis de ambiente apropriadas para que o Perl use nosso recém-criado diretório lib/. Caso esteja usando bash ou outra shell Bourne, você pode fazer isso adicionando a seguinte linha em seu script de inicialização da shell: echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc Caso esteja usando a shell C, pode fazer da seguinte forma: /bin/csh echo $SHELL /bin/csh perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc Caso tenha passado para o bootstrap um diretório que não o padrão, você precisará indicá-lo na chamada ao local::lib, dessa forma: echo 'eval $(perl -I$HOME/foo/lib/perl5 -Mlocal::lib=$HOME/foo)' >>~/.bashrc Após atualizar seu arquivo de configuração da shell, certifique-se de processá-lo novamente para obter as modificações em sua shell atual. Shells Bourne usam C<. ~/.bashrc> para isso, enquanto shells C usam C. Se estiver em uma máquina lenta ou operando com grandes limitações de espaço em disco, você pode desativar a geração automática de manpages a partir do POD ao instalar módulos. Para isso, basta passar o argumento C<--no-manpages> durante o bootstrap: perl Makefile.PL --bootstrap --no-manpages Para evitar ter que fazer vários bootstraps para vários ambientes de módulos Perl na mesma conta de usuário - por exemplo se você usa o local::lib para desenvolver diferentes aplicativos independentes - você pode utilizar uma única instalação bootstrap do local::lib para instalar módulos em diretórios diferentes da seguinte forma: cd ~/meudir1 perl -Mlocal::lib=./ eval $(perl -Mlocal::lib=./) ### Para configurar o ambiente apenas nessa shell printenv ### Veja que o ~/meudir1 está na PERL5LIB perl -MCPAN -e install ... ### Os módulos que quiser cd ../meudir2 ... REPITA ... Para múltiplos ambientes destinados a múltiplos aplicativos, você pode precisar incluir uma versão modificada das instruções de C<< use FindBin >> no exemplo "No código" acima. Caso tenha feito algo como o que foi descrito acima, terá um conjunto de módulos Perl em C<< ~/meudir1/lib >>. Caso tenha um script em C<< ~/meudir1/scripts/meuscript.pl >>, você precisará indicar a ele onde encontrar os módulos que instalou para ele em C<< ~/meudir1/lib >>. Em C<< ~/meudir1/scripts/meuscript.pl >>: use strict; use warnings; use local::lib "$FindBin::Bin/.."; ### aponta para ~/meudir1 e o local::lib acha o lib/ use lib "$FindBin::Bin/../lib"; ### aponta para ~/meudir1/lib Coloque isso antes de qualquer bloco BEGIN { ... } que precise dos módulos instalados. =head2 Diferenças ao usar esse módulo em Win32 Para configurar as variáveis de ambiente apropriadas para sua sessão atual do C, você pode fazer assim: C:\>perl -Mlocal::lib set PERL_MB_OPT=--install_base C:\DOCUME~1\ADMINI~1\perl5 set PERL_MM_OPT=INSTALL_BASE=C:\DOCUME~1\ADMINI~1\perl5 set PERL5LIB=C:\DOCUME~1\ADMINI~1\perl5\lib\perl5;C:\DOCUME~1\ADMINI~1\perl5\lib\perl5\MSWin32-x86-multi-thread set PATH=C:\DOCUME~1\ADMINI~1\perl5\bin;%PATH% ### Para configurar o ambiente apenas dessa shell C:\>perl -Mlocal::lib > %TEMP%\tmp.bat && %TEMP%\tmp.bat && del %TEMP%\temp.bat ### em vez de $(perl -Mlocal::lib=./) Caso queira que as configurações do ambiente persistam, você precisará adicioná-las em Painel de Controle -> Sistema, ou usar o L. O "~" é transformado no diretório do perfil do usuário (o diretório com o nome do usuário dentro de "Documents and Settings" (Windows XP ou anterior) ou "Usuários" (Windows Vista e mais recentes)) a menos que $ENV{HOME} exista. Após isso, o nome do diretório é encurtado e os subdiretórios são criados (o que significa que o diretório deve existir). =head1 MOTIVAÇÃO A versão de um pacote Perl na sua máquina nem sempre é a que você precisa. Obviamente, a melhor coisa a fazer seria atualizá-la para a versão desejada. No entanto, você pode estar em uma situação que o impede de fazer isso. Talvez você não tenha privilégios de administrador do sistema; ou talvez esteja usando um sistema de gerenciamento de pacotes como o do Debian, e ainda não exista um pacote disponível na versão desejada. local::lib resolve esse problema possibilitando a criação de seu próprio diretório de pacotes Perl obtidos do CPAN (em sistemas multi-usuário, isso normalmente fica dentro do diretório de seu usuário). A instalação do Perl no sistema permanece inalterada; você simplesmente chama o Perl com opções especiais para que ele use os pacotes em seu diretório local em vez dos pacotes do sistema. O local::lib organiza as coisas para que versões dos pacotes Perl instalados localmente tenham precedência sobre as do sistema. Caso esteja usando um sistema de gerenciamento de pacote (como em sistemas Debian), não precisará se preocupar com conflitos entre o Debian e o CPAN. Sua versão local dos pacotes será instalada em um diretório completamente diferente das versões instaladas pelo gerenciador de pacotes do sistema. =head1 DESCRIÇÃO Este módulo oferece uma forma rápida e conveniente para criar um repositório de módulos locais ao usuário, dentro do diretório do mesmo. Ele também monta e exibe para o usuário uma lista de variáveis de ambiente utilizando a sintaxe da shell atual do usuário (conforme especificado pela variável de ambiente C), pronta para ser adicionada diretamente no arquivo de configuração da shell. Generalizando, o local::lib permite a criação e uso de um diretório contendo módulos Perl fora do C<@INC> do Perl. Isso facilita a produção de aplicações com uma versão específica de determinado módulo, ou coleção de módulos. Também é útil quando o mantenedor de um módulo não aplicou determinado patch que você precisa para seu aplicativo. Durante o C, o local::lib define valores apropriados para as seguintes variáveis de ambiente: =over 4 =item PERL_MB_OPT =item PERL_MM_OPT =item PERL5LIB =item PATH valores serão anexados ao PATH, em vez de substituí-lo. =back Esses valores são então disponibilizados para referência por qualquer outro código após o C. =head1 CRIANDO UM CONJUNTO AUTO-CONTIDO DE MÓDULOS Veja L para uma maneira de fazer isso - mas note que há uma série de ressalvas na abordagem, e a melhor forma é sempre fazer o 'build' contra uma versão limpa do perl (i.e. com 'site' e 'vendor' o mais vazios possível). =head1 MÉTODOS =head2 ensure_dir_structure_for =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: Nenhum =back Tenta criar o caminho fornecido, e todos os diretórios superiores necessários. Gera uma exceção em caso de falha. =head2 print_environment_vars_for =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: Nenhum =back Exibe na saída padrão as variáveis listadas acima, devidamente ajustadas para utilizar o caminho fornecido como diretório base. =head2 build_environment_vars_for =over 4 =item Argumentos: $caminho_do_diretorio, $interpolar =item Valor de Retorno: %variaveis_de_ambiente =back Retorna hash contendo as variáveis de ambiente listadas acima, devidamente ajustadas para utilizar o caminho fornecido como diretório base. =head2 setup_env_hash_for =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: Nenhum =back Constrói as chaves no C<%ENV> para o caminho fornecido, chamando C. =head2 install_base_perl_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_base_de_instalacao =back Retorna um caminho de diretório indicando onde instalar os módulos Perl para essa instalação local de bibliotecas. Adiciona os diretórios C e C ao final do caminho fornecido. =head2 install_base_arch_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_base_de_instalacao_arch =back Retorna um caminho de diretório indicando onde instalar os módulos Perl de arquiteturas específicas para essa instalação local de bibliotecas. Baseia-se no valor de retorno do método L, adicionando o valor de C<$Config{archname}>. =head2 install_base_bin_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_base_de_instalacao_bin =back Retorna um caminho de diretório indicando onde instalar programas executáveis para essa instalação local de bibliotecas. Baseia-se no valor de retorno do método L, adicionando o diretório C. =head2 resolve_empty_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_base_de_instalacao =back Cria e retorna o caminho de diretório raiz em que a instalação local de módulos deve ser feita. O padrão é C<~/perl5>. =head2 resolve_home_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_para_home =back Procura pelo diretório padrão (home) do usuário. Gera uma exceção caso não encontre resultado definitivo. =head2 resolve_relative_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_absoluto =back Transforma o caminho fornecido em um caminho absoluto. =head2 resolve_path =over 4 =item Argumentos: $caminho_do_diretorio =item Valor de Retorno: $caminho_absoluto =back Invoca os seguintes métodos em sequência, passando o resultado do método anterior para o seguinte, na tentativa de descobrir onde configurar o ambiente para a instalação local de bibliotecas: L, L, L. Passa o caminho de diretório fornecido para L que retorna um resultado que é passado para L, que então tem seu resultado passado para L. O resultado dessa chamada final é então retornado pelo L. =head1 UM AVISO SOBRE UNINST=1 Tenha cuidado ao usar o local::lib em conjunto com "make install UNINST=1". A idéia dessa opção é desinstalar a versão anterior de um módulo antes de instalar a mais recente. No entanto ela não possui uma verificação de segurança de que a versão antiga e a nova referem-se ao mesmo diretório. Usada em combinação com o local::lib, você pode potencialmente apagar uma versão globalmente acessível de um módulo e instalar a versão mais nova no diretório local. Apenas utilize "make install UNINST=1" junto com o local::lib se você entende essas possíveis consequências. =head1 LIMITAÇÕES As ferramentas auxiliares do perl não conseguem lidar com nomes de diretórios contendo espaços, então não é possível fazer seu bootstrap do local::lib em um diretório com espaços. O que você pode fazer é mover seu local::lib para um diretório com espaços B ter instalado todos os módulos dentro dele. Mas esteja ciente que você não poderá atualizar ou instalar outros módulos do CPAN nesse diretório local após a mudança. A detecção da shell é relativamente básica. Neste momento, qualquer coisa com csh no nome será tratada como a C shell ou compatível, e todo o resto será tratado como Bourne, exceto em sistemas Win32. Caso a variável de ambiente C não esteja disponível, assumiremos tratar-se de uma shell compatível com a Bourne. A técnica de bootstrap é um hack e usará o CPAN.pm para o ExtUtils::MakeMaker mesmo que você tenha o CPANPLUS instalado. Destrói qualquer valor pré-existente nas variáveis de ambiente PERL5LIB, PERL_MM_OPT e PERL_MB_OPT. Provavelmente deveria auto-configurar o CPAN caso isso ainda não tenha sido feito. Correções (patches) são muito bem-vindos para quaisquer dos itens acima. Em sistemas Win32, não há uma forma de escrever no registro as variáveis de ambiente criadas, para que elas persistam a uma reinicialização. =head1 SOLUÇÃO DE PROBLEMAS Se você configurou o local::lib para instalar módulos do CPAN em algum lugar do seu 'home', e mais tarde tentou instalar um módulo fazendo C, mas ele falhou com um erro como: C e em algum lugar no seu log de instalação houver um erro dizendo C<'INSTALL_BASE' is not a known MakeMaker parameter name>, então você de alguma forma perdeu seu ExtUtils::MakeMaker atualizado. Para remediar a situação, execute novamente o procedimento de bootstrap descrito acima. Então, execute C Finalmente, execute novamente o C e ele deve instalar sem problemas. =head1 AMBIENTE =over 4 =item SHELL =item COMSPEC O local::lib procura pela variável de ambiente C do usuário ao processar e exibir os comandos a serem adicionados no arquivo de configuração da shell. Em sistemas Win32, C também será examinado. =back =head1 SUPORTE IRC: Acesse #local-lib em irc.perl.org. =head1 AUTOR DA TRADUÇÃO Breno G. de Oliveira, C<< >>, após ter perdido uma aposta para o L durante a Copa de 2010. =head1 COPYRIGHT Copyright (c) 2007 - 2010 L e L do local::lib como listados em L. =head1 LICENÇA Esta biblioteca é software livre e pode ser distribuída sob os mesmo termos do perl. PK!( @@perl5/POD2/DE/local/lib.podnu6$=encoding utf8 =head1 NAME local::lib~[de] - Erschaffen und benutzen von Perl Modulen in einem lokalen lib/ Verzeichnis mit PERL5LIB =head1 SYNOPSIS Im Code - use local::lib; # Benutzt das Verzeichnis ~/perl5 zum anlegen des lokalen lib/ Verzeichnisses use local::lib '~/foo'; # das selbe, aber mit ~/foo # Oder... use FindBin; use local::lib "$FindBin::Bin/../support"; # Applikationsspezifische Sammlung von Modulen Von der Shell - # Installiert LWP und alle notwendigen Abhängigkeiten in das '~/perl5' Verzeichnis perl -MCPAN -Mlocal::lib -e 'CPAN::install(LWP)' # Gibt die Shell Kommandos aus um die Umgebung vorzubereiten $ perl -Mlocal::lib export PERL_MB_OPT='--install_base /home/username/perl5' export PERL_MM_OPT='INSTALL_BASE=/home/username/perl5' export PERL5LIB='/home/username/perl5/lib/perl5/i386-linux:/home/username/perl5/lib/perl5' export PATH="/home/username/perl5/bin:$PATH" =head2 Die Bootstrapping Methode Ein typischer Weg um local::lib zu benutzen ist die sogenannte "Bootstrapping" Methode. Diese Methode wird benutzt wenn noch kein local::lib auf dem System installiert ist. In diesem Fall kannst du einfach local::lib direkt in deinem Home-Verzeichnis installieren. Selbst wenn du administrative Rechte hast, ist es wichtig das die Umgebungsvariablen von Schritt 4 in deinem Shell Startup Skript gesetzt werden. Ohne diesen Schritt werden die Module von CPAN weiterhin im System installiert und auch Perl Skripte die du startest würden das von local::lib erstellte lib/ Verzeichnis nicht nutzen. Standardmäßig installiert sich local::lib in ~/perl5. Windows Benutzern müssen ausserdem dies hier lesen: L. 1. Lade das Tar-Archiv von CPAN runter (Suche nach "Download" auf der CPAN Seite von local::lib) und entpacke es in einem beliebigem Verzeichnis. Um das obige Problem zu vermeiden, sollte man dies als normaler User tun und nicht als root oder Administrator. 2. Starte in dem entstandenen Verzeichnis folgenden Befehl: perl Makefile.PL --bootstrap Wenn das System dir vorschlägt gewisse Dinge eigenständig zu konfigurieren ist es in fast allen Fällen vollkommen in Ordnung einfach "yes" zu antworten. Falls du local::lib nicht in das Standard Verzeichnis installieren willst, musst du dieses Verzeichnis als Parameter angeben: perl Makefile.PL --bootstrap=~/foo 3. Danach folgenden Befehl starten: (local::lib erwartet make auf dem System) make test && make install 4. Nun müssen wir die benötigten Umgebungsvariablen, damit Perl unser neu generiertes lib/ Verzeichnis benutzt. Wenn du bash oder eine andere Bourne Shell benutzt, kannst du es über diesen Weg zu deinem Shell Startup Skript hinzufügen: echo 'eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)' >>~/.bashrc Wenn du C Shell benutzt, du kannst das gleiche hiermit erreichen: /bin/csh echo $SHELL /bin/csh perl -I$HOME/perl5/lib/perl5 -Mlocal::lib >> ~/.cshrc Wenn du beim bootstrappen ein anderes Verzeichnis benutzt als das Standardverzeichnis, dann musst du dieses Verzeichnis als Parameter beim Laden des Modules local::lib angeben: echo 'eval $(perl -I$HOME/foo/lib/perl5 -Mlocal::lib=$HOME/foo)' >>~/.bashrc Nachdem diese Änderungen in deinem Shell Startup Skript gemacht wurden, ist es nun wichtig das diese Umgebungsvariablen auch gesetzt sind in deiner aktuellen Umgebung. In Bourne Shells macht man dies z.B. mit C<. ~/.bashrc>, und in C Shell würde man es mit: C mit. Wenn du eine sehr langsames System hast, oder du unter drakonischen Regulierungen des Plattenplatz leben musst, kann man die automatische Generierung der manpages vom POD bei der Installation des Moduls deaktivieren beim bootstrapping mit dem C<--no-manpages> Parameter: perl Makefile.PL --bootstrap --no-manpages Um zu vermeiden das man mehrere bootstraps macht um z.B. für verschiedene Applikationen eigene local::lib Installationen zu nutzen, kann man eine dieser Umgebungen benutzen um einfach in beliebigen anderen Verzeichnis Module zu installieren und somit weitere eigenständige lib/ Umgebungen zu bekommen: cd ~/mydir1 perl -Mlocal::lib=./ eval $(perl -Mlocal::lib=./) ### Um die Umgebungsvariablen für die ### aktuelle Shell zusetzen printenv ### Hier kannst du sehen das ~/mydir1 ### in der PERL5LIB Umgebungsvariable ### steht perl -MCPAN -e install ... ### welche Module auch immer ... cd ../mydir2 ... WIEDERHOLEN ... Für mehrere Umgebungen in dieser Form brauch man eine Modifikation in der Benutzung von C<< use FindBin >> in dem "Im Code" Beispiel oben. Wenn du sowas machst, und du hast damit z.B. Perl Module nach C<< ~/mydir1/lib >> installiert und du hast ein Script in C<< ~/mydir1/scripts/myscript.pl >>, du musst dort angeben das die Module die es braucht im Verzeichnis C<< ~/mydir1/lib >> liegen. In C<< ~/mydir1/scripts/myscript.pl >> steht dann: use strict; use warnings; use local::lib "$FindBin::Bin/.."; ### zeigt auf ~/mydir1 und local::lib ### findet dort lib use lib "$FindBin::Bin/../lib"; ### zeigt auf ~/mydir1/lib Setze das vor jeden BEGIN { ... } Block der die Module braucht die du installiert hast. =head2 Unterschiede bei Benutzung dieses Module mit Win32 Um die nötigen Umgebungsvariablen für diese Variablen in der derzeitigen Sitzung mit C zu setzen, kann man folgendes kommando nutzen: C:\>perl -Mlocal::lib set PERL_MB_OPT=--install_base C:\DOCUME~1\ADMINI~1\perl5 set PERL_MM_OPT=INSTALL_BASE=C:\DOCUME~1\ADMINI~1\perl5 set PERL5LIB=C:\DOCUME~1\ADMINI~1\perl5\lib\perl5;C:\DOCUME~1\ADMINI~1\perl5\lib\perl5\MSWin32-x86-multi-thread set PATH=C:\DOCUME~1\ADMINI~1\perl5\bin;%PATH% ### Um die Umgebungsvariablen für diese Shell alleine zu setzen C:\>perl -Mlocal::lib > %TEMP%\tmp.bat && %TEMP%\tmp.bat && del %TEMP%\temp.bat ### anstelle von $(perl -Mlocal::lib=./) in bash. Wenn du willst das die Umgebungsvariablen dauerhaft gesetzt sind, musst du diese in Systemsteuerung / System dauerhaft selber eintragen oder L benutzen. Die "~" wird übersetzt zu dem Benutzer Profil Verzeichnis (das Verzeichnis was beim User als "Dokumente und Einstellungen" bekannt ist unter Windows XP und vorher oder das "Benutzer" Verzeichnis bei Windows Vista und später), solange $ENV{HOME} nicht gesetzt ist. Das Verzeichnis wird hierbei zu dem korrekten Kurznamen umgewandelt, und muss daher definitiv existieren, und wird um die nötigen Unterverzeichnise erweitert. =head1 GRUNDPRINZIP Die Version von den Perl Paketen die man benötigt für spezifische Aufgaben sind sehr häufig nicht die richtigen oder korrekten Versionen auf dem System vorinstalliert. Ein Updaten von diesen Modulen ist in vielen Fällen einfach nicht möglich weil die nötigen Rechte fehlen. Ausserdem ist es generell nicht gut eigenständig die Versionen der Module auf dem System auszutauschen, weil natürlich der Rest des Systems genau die Version erwartet die von der Systemverwaltung auch installiert wurde. local::lib löst dieses Problem, es erlaubt dir dein komplett eigenes Verzeichnis für deine CPAN Module zu haben und bist so nicht genötigt die Module vom System zu nutzen oder andersrum andere User nicht mit individuellen Modulwünschen zu Überarbeitung ihres Codes zu zwingen, weil bestimmte Module zentral für alle auf neuere Version upgedatet werden. Die Installation findet hierbei dann z.B. im Home Verzeichnis statt. Es werden nur Umgebungsvariablen gesetzt die das installierte Perl dazu bewegen die im Homeverzeichnis installierten Module zu benutzen, zusätzlich und vorgezogen zu denen auf dem System. Daher muss man sich wenn man ein Paket System benutzt, wie z.b. Debian, garnicht mehr Sorgen machen, irgendwas auf dem System zu verletzten nur durch die Installation von Perl Modulen. =head1 BESCHREIBUNG Dieses Modul bietet eine schnelle und legitime Art und Weise ein sogenanntes bootstrapping zu machen um in einem User Homeverzeichnis eine Sammlung von Modulen zu installieren. Es erstellt auch die nötigen Umgebungsvariablen die benötigt werden um diese Module zu nutzen, passend zu der Shell die der User in der Umgebungsvariable C angegeben hat, um dann direkt passend in die entsprechenden Konfigurationsdateien der Shell einfügt zu werden. Weitergehend ist local::lib in der Lage Module zu nutzen die nicht im standardmäßigen C<@INC> Pfad von Perl enthalten sind. Das macht es einfacher für bestimmte Applikationen ein bestimmtes Set von Modulen zu installieren ohne die anderen Module auf dem System in irgendeiner Art anzufassen. Damit es z.B. auch sicherer Module zu installieren die vom Maintainer noch nicht als Release verfügbar sind. Beim Import setzt local::lib die folgenden Umgebungsvariablen zu den nötigen Werten: =over 4 =item PERL_MB_OPT =item PERL_MM_OPT =item PERL5LIB =item PATH Am PATH wird natürlich angehangen, und nicht ersetzt. =back Diese Werte sind dann verfügbar für jeden Code der danach importiert wurde. =head1 ERSTELLEN EINES EIGENSTÄNDIGE SAMMLUNG VON MODULEN Mit L besteht eine Möglichkeit dieses zutun, aber beachte das hier eine Menge von Fallstricken und Problemen existieren, und man sollte immer darauf achten das man auf einem Perl aufbaut was sowenig wie möglich verändert wurde (d.h. site und vendor Verzeichnis so leer wie möglich). =head1 METHODEN =head2 ensure_dir_structure_for =over 4 =item Parameter: $path =item Rückgabewert: Keiner =back Versucht den angegebenen Pfad anzulegen, mit allen nötigen drüberliegenden Verzeichnissen. Im Fehlerfall wird eine Exception geworfen. =head2 print_environment_vars_for =over 4 =item Parameter: $pfad =item Rückgabewert: Keiner =back Gibt die Umgebungsvariablen aus, die benötigt werden um den angegebenen Pfad als Basis Verzeichnis zu nutzen. =head2 build_environment_vars_for =over 4 =item Parameter: $pfad, $interpolate =item Rückgabewert: \%umgebungs_variablen =back Gibt ein Hash zurück mit den Variablen die nötig sind in den Umgebungsvariablen um eine Installation in dem gegebenen Pfad zu benutzen. =head2 setup_env_hash_for =over 4 =item Parameter: $pfad =item Rückgabewert: Keiner =back Setzt die C<%ENV> Einträge basierend auf dem Aufruf von L. =head2 install_base_perl_path =over 4 =item Parameter: $pfad =item Rückgabewert: $module_installations_pfad =back Gibt den Pfad zurück der benutzt wird um Perl Module zu installieren bei dem gegebenen Pfad als Basis. Prinzipiell wird nur C und C als Pfadelemente angehangen. =head2 install_base_arch_path =over 4 =item Parameter: $pfad =item Rückgabewert: $architektur_module_installations_pfad =back Gibt den Pfad zurück der benutzt wird um die Architektur-abhängigen Perl Module zu installieren basirend auf dem angegebenen Pfad als Basis. Basierend auf dem was L zurückgibt, and appends the value of C<$Config{archname}>.asis. =head2 install_base_bin_path =over 4 =item Parameter: $pfad =item Rückgabewert: $ausfuehrbare_programme_installations_pfad =back Gibt den Pfad zurück, wo ausführbare Programme installiert werden, basierend auf der Basis des angegebenen Pfad. Basierend auf L Rückgabewert, hängt diese Methode noch C an. =head2 resolve_empty_path =over 4 =item Parameter: $pfad =item Rückgabewert: $basis_pfad =back Erstellt und gibt zurück den Pfad der benutzt wird als Basis zur Installation der Module. Standardmäßig dies ist C<~/perl5>. =head2 resolve_home_path( $path ) =over 4 =item Parameter: $pfad =item Rückgabewert: $home =back Versucht das Home Verzeichnis vom aktullen User zu finden. Es wird eine Exception geworfen, wenn kein Home Verzeichnis ermittelt werden konnte. =head2 resolve_relative_path =over 4 =item Parameter: $pfad =item Rückgabewert: $absoluter_pfad =back Macht aus dem angegebenen Pfad einen absoluten Pfad. =head2 resolve_path =over 4 =item Parameter: $pfad =item Rückgabewert: $absoluter_pfad =back Hierbei wird der Pfad durch die folgende Methoden gegeben, wobei der Rückgabewert der ersten an die nächste weitergeben wird, um die Umgebung zu konfigurieren für die lokale Bibliotheks Installation: L, L, L. Der daraus resultierende Pfad wird zu L übergeben, dessen Resultat dann weitergegeben wird an L, wessen Resultat dann weitergegeben wird an L. Dieses Resultat wird dann final an L übergeben, welches dann den Rückgabewert stellt. =head1 EINE WARNUNG VOR UNINST=1 Wenn man local::lib in Kombination mit "make install UNINST=1" benutzt, muss man vorsichtig sein über die Tatsache das der Prozess über die Neuinstallation eine nicht ausreichende Sicherheit hat bezüglich wo er nun installieren muss. Hierdurch mann es passieren das beim deinstallieren eines Modul u.U. das globale Modul deinstalliert wird (wenn die Rechte vorhanden sind) aber die neue Version nur in der lokalen Version installiert ist. Es ist hier also sehr wichtig das man "make install UNINST=1" und local::lib nur gleichzeitig benutzt wenn man sehr sicher darüber ist welche Konsequenzen einem entgegenkommen. =head1 EINSCHRÄNKUNGEN Die Werkzeuge von perl, die benutzt werden um die Pakete zu installieren (die sogenannte toolchain), sind leider nicht in der Lage sauber mit Verzeichnissen umzugehen die Leerzeichen enthalten und können daher local::lib nicht direkt in ein solches Verzeichnis installieren. Was du machen kannst ist B der Installation von local::lib und der Module die du in deiner local::lib haben willst, das gesamte Verzeichnis dahin zu bewegen. local::lib kann mit dem Verzeichnis mit Leerzeichen umgehen. Bitte aufpassen das natürlich eine weitere Installation oder ein Erneuern von Paketen mit dem CPAN Programm nicht mehr möglich ist. Die Shell Erkennung ist sehr primitiv. Derzeit ist es so das alles was "csh" im Namen hat auch als C Shell eingeordnet wird, und alles andere wird als Bourne Shell betrachet, ausser auf Win32 Systemen. Wenn die C Variable nicht gesetzt ist, eine Bourne Shell wird angenommen. Bootstrap ist leider ein Hack, und wird auf jedenfall CPAN.pm benutzen für ExtUtils::MakeMaker, auch wenn CPANPLUS installiert ist. Es setzt definitiv PERL5LIB, PERL_MM_OPT und PERL_MB_OPT neu und vernichtet jeden Wert der vorher gesetzt war. Es sollte vielleicht eine automatische Korrektur der CPAN Config machen, wenn das nicht schon gemacht wurde. "Patches Welcome" - Patches sind immer willkommen beim Autor oder den anderen Mitwirkenden. Auf Win32 Systemen werden die Umgebungsvariablen nicht direkt in die Registrierung geschrieben damit sie auch nach dem Neustarten erhalten bleiben. =head1 FEHLERANALYSE Wenn du local::lib konfiguriert hast CPAN Module in deinem Home Verzeichnis zu installieren, und du danach versuchst mit C ein Modul zu installieren, und dabei einen Fehler bekommst, wie: C und in der installationsausgabe steht irgendwo ein Fehler der sagt C<'INSTALL_BASE' is not a known MakeMaker parameter name>, dann hast du aus irgendeinem Grund dein neue Version von ExtUtils::MakeMaker verloren. Um dies zu korrigieren, einfach nochmal die bootstrapping Methode laufen lassen, wie oben beschrieben. Dann starte C Abschliessend dann nochmal mit C installieren und die Probleme sollten verschwunden sein. =head1 UMGEBUNGSVARIABLEN =over 4 =item SHELL =item COMSPEC local::lib schaut in die C Umgebungsvariable um die korrekten Kommandos zu der Shell Konfiguration hinzuzufügen. Auf Win32 Systemen, C wird auch analysiert. =back =head1 SUPPORT IRC: Wir sind im Channel #local-lib auf dem Server irc.perl.org. =head1 AUTOR DER ÜBERSETZUNG Torsten Raudssus http://www.raudssus.de/ =head1 URHEBERRECHT Copyright (c) 2007 - 2010 von den local::lib L und L aufgelistet in L. =head1 LIZENZ Diese Sammlung ist freie Software und kann unter der selben Lizenz verbreitet werden wie Perl selber. =cut 1; PK!ʛQQperl5/XML/Simple/FAQ.podnu6$ =head1 NAME XML::Simple::FAQ - Frequently Asked Questions about XML::Simple =head1 Basics =head2 What should I use XML::Simple for? Nothing! It's as simple as that. Choose a better module. See L for a gentle introduction to L with lots of examples. =head2 What was XML::Simple designed to be used for? XML::Simple is a Perl module that was originally developed as a tool for reading and writing configuration data in XML format. You could use it for other purposes that involve storing and retrieving structured data in XML but it's likely to be a frustrating experience. =head2 Why store configuration data in XML anyway? It seemed like a good idea at the time. Now, I use and recommend L which uses a format similar to that used by the Apache web server. This is easier to read than XML while still allowing advanced concepts such as nested sections. At the time XML::Simple was written, the advantages of using XML format for configuration data were thought to include: =over 4 =item * Using existing XML parsing tools requires less development time, is easier and more robust than developing your own config file parsing code =item * XML can represent relationships between pieces of data, such as nesting of sections to arbitrary levels (not easily done with .INI files for example) =item * XML is basically just text, so you can easily edit a config file (easier than editing a Win32 registry) =item * XML provides standard solutions for handling character sets and encoding beyond basic ASCII (important for internationalization) =item * If it becomes necessary to change your configuration file format, there are many tools available for performing transformations on XML files =item * XML is an open standard (the world does not need more proprietary binary file formats) =item * Taking the extra step of developing a DTD allows the format of configuration files to be validated before your program reads them (not directly supported by XML::Simple) =item * Combining a DTD with a good XML editor can give you a GUI config editor for minimal coding effort =back =head2 What isn't XML::Simple good for? The main limitation of XML::Simple is that it does not work with 'mixed content' (see the next question). If you consider your XML files contain marked up text rather than structured data, you should probably use another module. If your source XML documents change regularly, it's likely that you will experience intermittent failures. In particular, failure to properly use the ForceArray and KeyAttr options will produce code that works when you get a list of elements with the same name, but fails when there's only one item in the list. These types of problems can be avoided by not using XML::Simple in the first place. If you are working with very large XML files, XML::Simple's approach of representing the whole file in memory as a 'tree' data structure may not be suitable. =head2 What is mixed content? Consider this example XML: This is mixed content. This is said to be mixed content, because the EparaE element contains both character data (text content) and nested elements. Here's some more XML: Joe Bloggs 25-April-1969 This second example is not generally considered to be mixed content. The Efirst_nameE, Elast_nameE and EdobE elements contain only character data and the EpersonE element contains only nested elements. (Note: Strictly speaking, the whitespace between the nested elements is character data, but it is ignored by XML::Simple). =head2 Why doesn't XML::Simple handle mixed content? Because if it did, it would no longer be simple :-) Seriously though, there are plenty of excellent modules that allow you to work with mixed content in a variety of ways. Handling mixed content correctly is not easy and by ignoring these issues, XML::Simple is able to present an API without a steep learning curve. =head2 Which Perl modules do handle mixed content? Every one of them except XML::Simple :-) If you're looking for a recommendation, I'd suggest you look at the Perl-XML FAQ at: http://perl-xml.sourceforge.net/faq/ =head1 Installation =head2 How do I install XML::Simple? If you're running ActiveState Perl, or L you've probably already got XML::Simple and therefore do not need to install it at all. But you probably also have L, which is a much better module, so just use that. If you do need to install XML::Simple, you'll need to install an XML parser module first. Install either XML::Parser (which you may have already) or XML::SAX. If you install both, XML::SAX will be used by default. Once you have a parser installed ... On Unix systems, try: perl -MCPAN -e 'install XML::Simple' If that doesn't work, download the latest distribution from ftp://ftp.cpan.org/pub/CPAN/authors/id/G/GR/GRANTM , unpack it and run these commands: perl Makefile.PL make make test make install On Win32, if you have a recent build of ActiveState Perl (618 or better) try this command: ppm install XML::Simple If that doesn't work, you really only need the Simple.pm file, so extract it from the .tar.gz file (eg: using WinZIP) and save it in the \site\lib\XML directory under your Perl installation (typically C:\Perl). =head2 I'm trying to install XML::Simple and 'make test' fails Is the directory where you've unpacked XML::Simple mounted from a file server using NFS, SMB or some other network file sharing? If so, that may cause errors in the following test scripts: 3_Storable.t 4_MemShare.t 5_MemCopy.t The test suite is designed to exercise the boundary conditions of all XML::Simple's functionality and these three scripts exercise the caching functions. If XML::Simple is asked to parse a file for which it has a cached copy of a previous parse, then it compares the timestamp on the XML file with the timestamp on the cached copy. If the cached copy is *newer* then it will be used. If the cached copy is older or the same age then the file is re-parsed. The test scripts will get confused by networked filesystems if the workstation and server system clocks are not synchronised (to the second). If you get an error in one of these three test scripts but you don't plan to use the caching options (they're not enabled by default), then go right ahead and run 'make install'. If you do plan to use caching, then try unpacking the distribution on local disk and doing the build/test there. It's probably not a good idea to use the caching options with networked filesystems in production. If the file server's clock is ahead of the local clock, XML::Simple will re-parse files when it could have used the cached copy. However if the local clock is ahead of the file server clock and a file is changed immediately after it is cached, the old cached copy will be used. Is one of the three test scripts (above) failing but you're not running on a network filesystem? Are you running Win32? If so, you may be seeing a bug in Win32 where writes to a file do not affect its modification timestamp. If none of these scenarios match your situation, please confirm you're running the latest version of XML::Simple and then email the output of 'make test' to me at grantm@cpan.org =head2 Why is XML::Simple so slow? If you find that XML::Simple is very slow reading XML, the most likely reason is that you have XML::SAX installed but no additional SAX parser module. The XML::SAX distribution includes an XML parser written entirely in Perl. This is very portable but not very fast. For better performance install either XML::SAX::Expat or XML::LibXML. =head1 Usage =head2 How do I use XML::Simple? If you don't know how to use XML::Simple then the best approach is to L instead. Stop reading this document and use that one instead. If you are determined to use XML::Simple, it come with copious documentation, so L. =head2 There are so many options, which ones do I really need to know about? Although you can get by without using any options, you shouldn't even consider using XML::Simple in production until you know what these two options do: =over 4 =item * forcearray =item * keyattr =back The reason you really need to read about them is because the default values for these options will trip you up if you don't. Although everyone agrees that these defaults are not ideal, there is not wide agreement on what they should be changed to. The answer therefore is to read about them (see below) and select values which are right for you. =head2 What is the forcearray option all about? Consider this XML in a file called ./person.xml: Joe Bloggs bungy jumping sky diving knitting You could read it in with this line: my $person = XMLin('./person.xml'); Which would give you a data structure like this: $person = { 'first_name' => 'Joe', 'last_name' => 'Bloggs', 'hobbie' => [ 'bungy jumping', 'sky diving', 'knitting' ] }; The Efirst_nameE and Elast_nameE elements are represented as simple scalar values which you could refer to like this: print "$person->{first_name} $person->{last_name}\n"; The EhobbieE elements are represented as an array - since there is more than one. You could refer to the first one like this: print $person->{hobbie}->[0], "\n"; Or the whole lot like this: print join(', ', @{$person->{hobbie}} ), "\n"; The catch is, that these last two lines of code will only work for people who have more than one hobbie. If there is only one EhobbieE element, it will be represented as a simple scalar (just like Efirst_nameE and Elast_nameE). Which might lead you to write code like this: if(ref($person->{hobbie})) { print join(', ', @{$person->{hobbie}} ), "\n"; } else { print $person->{hobbie}, "\n"; } Don't do that. One alternative approach is to set the forcearray option to a true value: my $person = XMLin('./person.xml', forcearray => 1); Which will give you a data structure like this: $person = { 'first_name' => [ 'Joe' ], 'last_name' => [ 'Bloggs' ], 'hobbie' => [ 'bungy jumping', 'sky diving', 'knitting' ] }; Then you can use this line to refer to all the list of hobbies even if there was only one: print join(', ', @{$person->{hobbie}} ), "\n"; The downside of this approach is that the Efirst_nameE and Elast_nameE elements will also always be represented as arrays even though there will never be more than one: print "$person->{first_name}->[0] $person->{last_name}->[0]\n"; This might be OK if you change the XML to use attributes for things that will always be singular and nested elements for things that may be plural: motorcycle maintenance On the other hand, if you prefer not to use attributes, then you could specify that any EhobbieE elements should always be represented as arrays and all other nested elements should be simple scalar values unless there is more than one: my $person = XMLin('./person.xml', forcearray => [ 'hobbie' ]); The forcearray option accepts a list of element names which should always be forced to an array representation: forcearray => [ qw(hobbie qualification childs_name) ] See the XML::Simple manual page for more information. =head2 What is the keyattr option all about? Consider this sample XML: You could slurp it in with this code: my $catalog = XMLin('./catalog.xml'); Which would return a data structure like this: $catalog = { 'part' => [ { 'partnum' => '1842334', 'desc' => 'High pressure flange', 'price' => '24.50' }, { 'partnum' => '9344675', 'desc' => 'Threaded gasket', 'price' => '9.25' }, { 'partnum' => '5634896', 'desc' => 'Low voltage washer', 'price' => '12.00' } ] }; Then you could access the description of the first part in the catalog with this code: print $catalog->{part}->[0]->{desc}, "\n"; However, if you wanted to access the description of the part with the part number of "9344675" then you'd have to code a loop like this: foreach my $part (@{$catalog->{part}}) { if($part->{partnum} eq '9344675') { print $part->{desc}, "\n"; last; } } The knowledge that each EpartE element has a unique partnum attribute allows you to eliminate this search. You can pass this knowledge on to XML::Simple like this: my $catalog = XMLin($xml, keyattr => ['partnum']); Which will return a data structure like this: $catalog = { 'part' => { '5634896' => { 'desc' => 'Low voltage washer', 'price' => '12.00' }, '1842334' => { 'desc' => 'High pressure flange', 'price' => '24.50' }, '9344675' => { 'desc' => 'Threaded gasket', 'price' => '9.25' } } }; XML::Simple has been able to transform $catalog->{part} from an arrayref to a hashref (keyed on partnum). This transformation is called 'array folding'. Through the use of array folding, you can now index directly to the description of the part you want: print $catalog->{part}->{9344675}->{desc}, "\n"; The 'keyattr' option also enables array folding when the unique key is in a nested element rather than an attribute. eg: 1842334 High pressure flange 24.50 9344675 Threaded gasket 9.25 5634896 Low voltage washer 12.00 See the XML::Simple manual page for more information. =head2 So what's the catch with 'keyattr'? One thing to watch out for is that you might get array folding even if you don't supply the keyattr option. The default value for this option is: [ 'name', 'key', 'id'] Which means if your XML elements have a 'name', 'key' or 'id' attribute (or nested element) then they may get folded on those values. This means that you can take advantage of array folding simply through careful choice of attribute names. On the hand, if you really don't want array folding at all, you'll need to set 'key attr to an empty list: my $ref = XMLin($xml, keyattr => []); A second 'gotcha' is that array folding only works on arrays. That might seem obvious, but if there's only one record in your XML and you didn't set the 'forcearray' option then it won't be represented as an array and consequently won't get folded into a hash. The moral is that if you're using array folding, you should always turn on the forcearray option. You probably want to be as specific as you can be too. For instance, the safest way to parse the EcatalogE example above would be: my $catalog = XMLin($xml, keyattr => { part => 'partnum'}, forcearray => ['part']); By using the hashref for keyattr, you can specify that only EpartE elements should be folded on the 'partnum' attribute (and that the EpartE elements should not be folded on any other attribute). By supplying a list of element names for forcearray, you're ensuring that folding will work even if there's only one EpartE. You're also ensuring that if the 'partnum' unique key is supplied in a nested element then that element won't get forced to an array too. =head2 How do I know what my data structure should look like? The rules are fairly straightforward: =over 4 =item * each element gets represented as a hash =item * unless it contains only text, in which case it'll be a simple scalar value =item * or unless there's more than one element with the same name, in which case they'll be represented as an array =item * unless you've got array folding enabled, in which case they'll be folded into a hash =item * empty elements (no text contents B no attributes) will either be represented as an empty hash, an empty string or undef - depending on the value of the 'suppressempty' option. =back If you're in any doubt, use Data::Dumper, eg: use XML::Simple; use Data::Dumper; my $ref = XMLin($xml); print Dumper($ref); =head2 I'm getting 'Use of uninitialized value' warnings You're probably trying to index into a non-existant hash key - try Data::Dumper. =head2 I'm getting a 'Not an ARRAY reference' error Something that you expect to be an array is not. The two most likely causes are that you forgot to use 'forcearray' or that the array got folded into a hash - try Data::Dumper. =head2 I'm getting a 'No such array field' error Something that you expect to be a hash is actually an array. Perhaps array folding failed because one element was missing the key attribute - try Data::Dumper. =head2 I'm getting an 'Out of memory' error Something in the data structure is not as you expect and Perl may be trying unsuccessfully to autovivify things - try Data::Dumper. If you're already using Data::Dumper, try calling Dumper() immediately after XMLin() - ie: before you attempt to access anything in the data structure. =head2 My element order is getting jumbled up If you read an XML file with XMLin() and then write it back out with XMLout(), the order of the elements will likely be different. (However, if you read the file back in with XMLin() you'll get the same Perl data structure). The reordering happens because XML::Simple uses hashrefs to store your data and Perl hashes do not really have any order. It is possible that a future version of XML::Simple will use Tie::IxHash to store the data in hashrefs which do retain the order. However this will not fix all cases of element order being lost. If your application really is sensitive to element order, don't use XML::Simple (and don't put order-sensitive values in attributes). =head2 XML::Simple turns nested elements into attributes If you read an XML file with XMLin() and then write it back out with XMLout(), some data which was originally stored in nested elements may end up in attributes. (However, if you read the file back in with XMLin() you'll get the same Perl data structure). There are a number of ways you might handle this: =over 4 =item * use the 'forcearray' option with XMLin() =item * use the 'noattr' option with XMLout() =item * live with it =item * don't use XML::Simple =back =head2 Why does XMLout() insert EnameE elements (or attributes)? Try setting keyattr => []. When you call XMLin() to read XML, the 'keyattr' option controls whether arrays get 'folded' into hashes. Similarly, when you call XMLout(), the 'keyattr' option controls whether hashes get 'unfolded' into arrays. As described above, 'keyattr' is enabled by default. =head2 Why are empty elements represented as empty hashes? An element is always represented as a hash unless it contains only text, in which case it is represented as a scalar string. If you would prefer empty elements to be represented as empty strings or the undefined value, set the 'suppressempty' option to '' or undef respectively. =head2 Why is ParserOpts deprecated? The C option is a remnant of the time when XML::Simple only worked with the XML::Parser API. Its value is completely ignored if you're using a SAX parser, so writing code which relied on it would bar you from taking advantage of SAX. Even if you are using XML::Parser, it is seldom necessary to pass options to the parser object. A number of people have written to say they use this option to set XML::Parser's C option. Don't do that, it's wrong, Wrong, WRONG! Fix the XML document so that it's well-formed and you won't have a problem. Having said all of that, as long as XML::Simple continues to support the XML::Parser API, this option will not be removed. There are currently no plans to remove support for the XML::Parser API. =cut PK!W||perl5/XML/SAX/ParserDetails.ininu[[XML::SAX::PurePerl] http://xml.org/sax/features/namespaces = 1 [XML::SAX::Expat] http://xml.org/sax/features/namespaces = 1 http://xml.org/sax/features/external-general-entities = 1 http://xml.org/sax/features/external-parameter-entities = 1 [XML::LibXML::SAX::Parser] http://xml.org/sax/features/namespaces = 1 [XML::LibXML::SAX] http://xml.org/sax/features/namespaces = 1 PK!Q B B perl5/XML/SAX/DocumentLocator.pmnu6$# $Id$ package XML::SAX::DocumentLocator; use strict; sub new { my $class = shift; my %object; tie %object, $class, @_; return bless \%object, $class; } sub TIEHASH { my $class = shift; my ($pubmeth, $sysmeth, $linemeth, $colmeth, $encmeth, $xmlvmeth) = @_; return bless { pubmeth => $pubmeth, sysmeth => $sysmeth, linemeth => $linemeth, colmeth => $colmeth, encmeth => $encmeth, xmlvmeth => $xmlvmeth, }, $class; } sub FETCH { my ($self, $key) = @_; my $method; if ($key eq 'PublicId') { $method = $self->{pubmeth}; } elsif ($key eq 'SystemId') { $method = $self->{sysmeth}; } elsif ($key eq 'LineNumber') { $method = $self->{linemeth}; } elsif ($key eq 'ColumnNumber') { $method = $self->{colmeth}; } elsif ($key eq 'Encoding') { $method = $self->{encmeth}; } elsif ($key eq 'XMLVersion') { $method = $self->{xmlvmeth}; } if ($method) { my $value = $method->($key); return $value; } return undef; } sub EXISTS { my ($self, $key) = @_; if ($key =~ /^(PublicId|SystemId|LineNumber|ColumnNumber|Encoding|XMLVersion)$/) { return 1; } return 0; } sub STORE { my ($self, $key, $value) = @_; } sub DELETE { my ($self, $key) = @_; } sub CLEAR { my ($self) = @_; } sub FIRSTKEY { my ($self) = @_; # assignment resets. $self->{keys} = { PublicId => 1, SystemId => 1, LineNumber => 1, ColumnNumber => 1, Encoding => 1, XMLVersion => 1, }; return each %{$self->{keys}}; } sub NEXTKEY { my ($self, $lastkey) = @_; return each %{$self->{keys}}; } 1; __END__ =head1 NAME XML::SAX::DocumentLocator - Helper class for document locators =head1 SYNOPSIS my $locator = XML::SAX::DocumentLocator->new( sub { $object->get_public_id }, sub { $object->get_system_id }, sub { $reader->current_line }, sub { $reader->current_column }, sub { $reader->get_encoding }, sub { $reader->get_xml_version }, ); =head1 DESCRIPTION This module gives you a tied hash reference that calls the specified closures when asked for PublicId, SystemId, LineNumber and ColumnNumber. It is useful for writing SAX Parsers so that you don't have to constantly update the line numbers in a hash reference on the object you pass to set_document_locator(). See the source code for XML::SAX::PurePerl for a usage example. =head1 API There is only 1 method: C. Simply pass it a list of closures that when called will return the PublicId, the SystemId, the LineNumber, the ColumnNumber, the Encoding and the XMLVersion respectively. The closures are passed a single parameter, the key being requested. But you're free to ignore that. =cut PK!h= perl5/XML/SAX/ParserFactory.pmnu6$# $Id$ package XML::SAX::ParserFactory; use strict; use vars qw($VERSION); $VERSION = '1.02'; use Symbol qw(gensym); use XML::SAX; use XML::SAX::Exception; sub new { my $class = shift; my %params = @_; # TODO : Fix this in spec. my $self = bless \%params, $class; $self->{KnownParsers} = XML::SAX->parsers(); return $self; } sub parser { my $self = shift; my @parser_params = @_; if (!ref($self)) { $self = $self->new(); } my $parser_class = $self->_parser_class(); my $version = ''; if ($parser_class =~ s/\s*\(([\d\.]+)\)\s*$//) { $version = " $1"; } if (!$parser_class->can('new')) { eval "require $parser_class $version;"; die $@ if $@; } return $parser_class->new(@parser_params); } sub require_feature { my $self = shift; my ($feature) = @_; $self->{RequiredFeatures}{$feature}++; return $self; } sub _parser_class { my $self = shift; # First try ParserPackage if ($XML::SAX::ParserPackage) { return $XML::SAX::ParserPackage; } # Now check if required/preferred is there if ($self->{RequiredFeatures}) { my %required = %{$self->{RequiredFeatures}}; # note - we never go onto the next try (ParserDetails.ini), # because if we can't provide the requested feature # we need to throw an exception. PARSER: foreach my $parser (reverse @{$self->{KnownParsers}}) { foreach my $feature (keys %required) { if (!exists $parser->{Features}{$feature}) { next PARSER; } } # got here - all features must exist! return $parser->{Name}; } # TODO : should this be NotSupported() ? throw XML::SAX::Exception ( Message => "Unable to provide required features", ); } # Next try SAX.ini for my $dir (@INC) { my $fh = gensym(); if (open($fh, "$dir/SAX.ini")) { my $param_list = XML::SAX->_parse_ini_file($fh); my $params = $param_list->[0]->{Features}; if ($params->{ParserPackage}) { return $params->{ParserPackage}; } else { # we have required features (or nothing?) PARSER: foreach my $parser (reverse @{$self->{KnownParsers}}) { foreach my $feature (keys %$params) { if (!exists $parser->{Features}{$feature}) { next PARSER; } } return $parser->{Name}; } XML::SAX->do_warn("Unable to provide SAX.ini required features. Using fallback\n"); } last; # stop after first INI found } } if (@{$self->{KnownParsers}}) { return $self->{KnownParsers}[-1]{Name}; } else { return "XML::SAX::PurePerl"; # backup plan! } } 1; __END__ =head1 NAME XML::SAX::ParserFactory - Obtain a SAX parser =head1 SYNOPSIS use XML::SAX::ParserFactory; use XML::SAX::XYZHandler; my $handler = XML::SAX::XYZHandler->new(); my $p = XML::SAX::ParserFactory->parser(Handler => $handler); $p->parse_uri("foo.xml"); # or $p->parse_string("") or $p->parse_file($fh); =head1 DESCRIPTION XML::SAX::ParserFactory is a factory class for providing an application with a Perl SAX2 XML parser. It is akin to DBI - a front end for other parser classes. Each new SAX2 parser installed will register itself with XML::SAX, and then it will become available to all applications that use XML::SAX::ParserFactory to obtain a SAX parser. Unlike DBI however, XML/SAX parsers almost all work alike (especially if they subclass XML::SAX::Base, as they should), so rather than specifying the parser you want in the call to C, XML::SAX has several ways to automatically choose which parser to use: =over 4 =item * $XML::SAX::ParserPackage If this package variable is set, then this package is Cd and an instance of this package is returned by calling the C class method in that package. If it cannot be loaded or there is an error, an exception will be thrown. The variable can also contain a version number: $XML::SAX::ParserPackage = "XML::SAX::Expat (0.72)"; And the number will be treated as a minimum version number. =item * Required features It is possible to require features from the parsers. For example, you may wish for a parser that supports validation via a DTD. To do that, use the following code: use XML::SAX::ParserFactory; my $factory = XML::SAX::ParserFactory->new(); $factory->require_feature('http://xml.org/sax/features/validation'); my $parser = $factory->parser(...); Alternatively, specify the required features in the call to the ParserFactory constructor: my $factory = XML::SAX::ParserFactory->new( RequiredFeatures => { 'http://xml.org/sax/features/validation' => 1, } ); If the features you have asked for are unavailable (for example the user might not have a validating parser installed), then an exception will be thrown. The list of known parsers is searched in reverse order, so it will always return the last installed parser that supports all of your requested features (Note: this is subject to change if someone comes up with a better way of making this work). =item * SAX.ini ParserFactory will search @INC for a file called SAX.ini, which is in a simple format: # a comment looks like this, ; or like this, and are stripped anywhere in the file key = value # SAX.in contains key/value pairs. All whitespace is non-significant. This file can contain either a line: ParserPackage = MyParserModule (1.02) Where MyParserModule is the module to load and use for the parser, and the number in brackets is a minimum version to load. Or you can list required features: http://xml.org/sax/features/validation = 1 And each feature with a true value will be required. =item * Fallback If none of the above works, the last parser installed on the user's system will be used. The XML::SAX package ships with a pure perl XML parser, XML::SAX::PurePerl, so that there will always be a fallback parser. =back =head1 AUTHOR Matt Sergeant, matt@sergeant.org =head1 LICENSE This is free software, you may use it and distribute it under the same terms as Perl itself. =cut PK!Gperl5/XML/SAX/Base.pmnu6$package XML::SAX::Base; $XML::SAX::Base::VERSION = '1.09'; # version 0.10 - Kip Hampton # version 0.13 - Robin Berjon # version 0.15 - Kip Hampton # version 0.17 - Kip Hampton # version 0.19 - Kip Hampton # version 0.21 - Kip Hampton # version 0.22 - Robin Berjon # version 0.23 - Matt Sergeant # version 0.24 - Robin Berjon # version 0.25 - Kip Hampton # version 1.00 - Kip Hampton # version 1.01 - Kip Hampton # version 1.02 - Robin Berjon # version 1.03 - Matt Sergeant # version 1.04 - Kip Hampton # version 1.05 - Grant McLean # version 1.06 - Grant McLean # version 1.07 - Grant McLean # version 1.08 - Grant McLean #-----------------------------------------------------# # STOP!!!!! # # This file is generated by the 'BuildSAXBase.pl' file # that ships with the XML::SAX::Base distribution. # If you need to make changes, patch that file NOT # XML/SAX/Base.pm Better yet, fork the git repository # commit your changes and send a pull request: # https://github.com/grantm/XML-SAX-Base #-----------------------------------------------------# use strict; use XML::SAX::Exception qw(); sub end_entity { my $self = shift; if (defined $self->{Methods}->{'end_entity'}) { $self->{Methods}->{'end_entity'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('end_entity') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_entity') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->end_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_entity'} = sub { $handler->end_entity(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_entity'} = sub { $handler->end_entity(@_) }; } return $res; } else { $self->{Methods}->{'end_entity'} = sub { }; } } } sub set_document_locator { my $self = shift; if (defined $self->{Methods}->{'set_document_locator'}) { $self->{Methods}->{'set_document_locator'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('set_document_locator') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'set_document_locator'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('set_document_locator') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'set_document_locator'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('set_document_locator') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'set_document_locator'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->set_document_locator(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'set_document_locator'} = sub { $handler->set_document_locator(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->set_document_locator(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'set_document_locator'} = sub { $handler->set_document_locator(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->set_document_locator(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'set_document_locator'} = sub { $handler->set_document_locator(@_) }; } return $res; } else { $self->{Methods}->{'set_document_locator'} = sub { }; } } } sub notation_decl { my $self = shift; if (defined $self->{Methods}->{'notation_decl'}) { $self->{Methods}->{'notation_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('notation_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'notation_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('notation_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'notation_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->notation_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'notation_decl'} = sub { $handler->notation_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->notation_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'notation_decl'} = sub { $handler->notation_decl(@_) }; } return $res; } else { $self->{Methods}->{'notation_decl'} = sub { }; } } } sub attlist_decl { my $self = shift; if (defined $self->{Methods}->{'attlist_decl'}) { $self->{Methods}->{'attlist_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('attlist_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'attlist_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('attlist_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'attlist_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->attlist_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'attlist_decl'} = sub { $handler->attlist_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->attlist_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'attlist_decl'} = sub { $handler->attlist_decl(@_) }; } return $res; } else { $self->{Methods}->{'attlist_decl'} = sub { }; } } } sub fatal_error { my $self = shift; if (defined $self->{Methods}->{'fatal_error'}) { $self->{Methods}->{'fatal_error'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ErrorHandler'} and $method = $callbacks->{'ErrorHandler'}->can('fatal_error') ) { my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'fatal_error'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('fatal_error') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'fatal_error'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ErrorHandler'} and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ErrorHandler'}->fatal_error(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'fatal_error'} = sub { $handler->fatal_error(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->fatal_error(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'fatal_error'} = sub { $handler->fatal_error(@_) }; } return $res; } else { $self->{Methods}->{'fatal_error'} = sub { }; } } } sub start_document { my $self = shift; if (defined $self->{Methods}->{'start_document'}) { $self->{Methods}->{'start_document'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('start_document') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('start_document') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_document') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->start_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_document'} = sub { $handler->start_document(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->start_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_document'} = sub { $handler->start_document(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_document'} = sub { $handler->start_document(@_) }; } return $res; } else { $self->{Methods}->{'start_document'} = sub { }; } } } sub warning { my $self = shift; if (defined $self->{Methods}->{'warning'}) { $self->{Methods}->{'warning'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ErrorHandler'} and $method = $callbacks->{'ErrorHandler'}->can('warning') ) { my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'warning'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('warning') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'warning'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ErrorHandler'} and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ErrorHandler'}->warning(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'warning'} = sub { $handler->warning(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->warning(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'warning'} = sub { $handler->warning(@_) }; } return $res; } else { $self->{Methods}->{'warning'} = sub { }; } } } sub ignorable_whitespace { my $self = shift; if (defined $self->{Methods}->{'ignorable_whitespace'}) { $self->{Methods}->{'ignorable_whitespace'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('ignorable_whitespace') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('ignorable_whitespace') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('ignorable_whitespace') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->ignorable_whitespace(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $handler->ignorable_whitespace(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->ignorable_whitespace(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $handler->ignorable_whitespace(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->ignorable_whitespace(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'ignorable_whitespace'} = sub { $handler->ignorable_whitespace(@_) }; } return $res; } else { $self->{Methods}->{'ignorable_whitespace'} = sub { }; } } } sub resolve_entity { my $self = shift; if (defined $self->{Methods}->{'resolve_entity'}) { $self->{Methods}->{'resolve_entity'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'EntityResolver'} and $method = $callbacks->{'EntityResolver'}->can('resolve_entity') ) { my $handler = $callbacks->{'EntityResolver'}; $self->{Methods}->{'resolve_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('resolve_entity') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'resolve_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'EntityResolver'} and $callbacks->{'EntityResolver'}->can('AUTOLOAD') and $callbacks->{'EntityResolver'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'EntityResolver'}->resolve_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'EntityResolver'}; $self->{Methods}->{'resolve_entity'} = sub { $handler->resolve_entity(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->resolve_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'resolve_entity'} = sub { $handler->resolve_entity(@_) }; } return $res; } else { $self->{Methods}->{'resolve_entity'} = sub { }; } } } sub external_entity_decl { my $self = shift; if (defined $self->{Methods}->{'external_entity_decl'}) { $self->{Methods}->{'external_entity_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DeclHandler'} and $method = $callbacks->{'DeclHandler'}->can('external_entity_decl') ) { my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'external_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('external_entity_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'external_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DeclHandler'} and $callbacks->{'DeclHandler'}->can('AUTOLOAD') and $callbacks->{'DeclHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DeclHandler'}->external_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'external_entity_decl'} = sub { $handler->external_entity_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->external_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'external_entity_decl'} = sub { $handler->external_entity_decl(@_) }; } return $res; } else { $self->{Methods}->{'external_entity_decl'} = sub { }; } } } sub entity_reference { my $self = shift; if (defined $self->{Methods}->{'entity_reference'}) { $self->{Methods}->{'entity_reference'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('entity_reference') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'entity_reference'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('entity_reference') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'entity_reference'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->entity_reference(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'entity_reference'} = sub { $handler->entity_reference(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->entity_reference(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'entity_reference'} = sub { $handler->entity_reference(@_) }; } return $res; } else { $self->{Methods}->{'entity_reference'} = sub { }; } } } sub start_entity { my $self = shift; if (defined $self->{Methods}->{'start_entity'}) { $self->{Methods}->{'start_entity'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('start_entity') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_entity') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->start_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_entity'} = sub { $handler->start_entity(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_entity'} = sub { $handler->start_entity(@_) }; } return $res; } else { $self->{Methods}->{'start_entity'} = sub { }; } } } sub end_dtd { my $self = shift; if (defined $self->{Methods}->{'end_dtd'}) { $self->{Methods}->{'end_dtd'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('end_dtd') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_dtd'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_dtd') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_dtd'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->end_dtd(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_dtd'} = sub { $handler->end_dtd(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_dtd(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_dtd'} = sub { $handler->end_dtd(@_) }; } return $res; } else { $self->{Methods}->{'end_dtd'} = sub { }; } } } sub element_decl { my $self = shift; if (defined $self->{Methods}->{'element_decl'}) { $self->{Methods}->{'element_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DeclHandler'} and $method = $callbacks->{'DeclHandler'}->can('element_decl') ) { my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'element_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('element_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'element_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DeclHandler'} and $callbacks->{'DeclHandler'}->can('AUTOLOAD') and $callbacks->{'DeclHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DeclHandler'}->element_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'element_decl'} = sub { $handler->element_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->element_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'element_decl'} = sub { $handler->element_decl(@_) }; } return $res; } else { $self->{Methods}->{'element_decl'} = sub { }; } } } sub start_element { my $self = shift; if (defined $self->{Methods}->{'start_element'}) { $self->{Methods}->{'start_element'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('start_element') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('start_element') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_element') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->start_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_element'} = sub { $handler->start_element(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->start_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_element'} = sub { $handler->start_element(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_element'} = sub { $handler->start_element(@_) }; } return $res; } else { $self->{Methods}->{'start_element'} = sub { }; } } } sub error { my $self = shift; if (defined $self->{Methods}->{'error'}) { $self->{Methods}->{'error'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ErrorHandler'} and $method = $callbacks->{'ErrorHandler'}->can('error') ) { my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'error'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('error') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'error'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ErrorHandler'} and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') and $callbacks->{'ErrorHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ErrorHandler'}->error(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ErrorHandler'}; $self->{Methods}->{'error'} = sub { $handler->error(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->error(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'error'} = sub { $handler->error(@_) }; } return $res; } else { $self->{Methods}->{'error'} = sub { }; } } } sub xml_decl { my $self = shift; if (defined $self->{Methods}->{'xml_decl'}) { $self->{Methods}->{'xml_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('xml_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'xml_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('xml_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'xml_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->xml_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'xml_decl'} = sub { $handler->xml_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->xml_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'xml_decl'} = sub { $handler->xml_decl(@_) }; } return $res; } else { $self->{Methods}->{'xml_decl'} = sub { }; } } } sub end_document { my $self = shift; if (defined $self->{Methods}->{'end_document'}) { $self->{Methods}->{'end_document'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('end_document') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('end_document') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_document') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_document'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->end_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_document'} = sub { $handler->end_document(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->end_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_document'} = sub { $handler->end_document(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_document(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_document'} = sub { $handler->end_document(@_) }; } return $res; } else { $self->{Methods}->{'end_document'} = sub { }; } } } sub attribute_decl { my $self = shift; if (defined $self->{Methods}->{'attribute_decl'}) { $self->{Methods}->{'attribute_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DeclHandler'} and $method = $callbacks->{'DeclHandler'}->can('attribute_decl') ) { my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'attribute_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('attribute_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'attribute_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DeclHandler'} and $callbacks->{'DeclHandler'}->can('AUTOLOAD') and $callbacks->{'DeclHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DeclHandler'}->attribute_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'attribute_decl'} = sub { $handler->attribute_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->attribute_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'attribute_decl'} = sub { $handler->attribute_decl(@_) }; } return $res; } else { $self->{Methods}->{'attribute_decl'} = sub { }; } } } sub internal_entity_decl { my $self = shift; if (defined $self->{Methods}->{'internal_entity_decl'}) { $self->{Methods}->{'internal_entity_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DeclHandler'} and $method = $callbacks->{'DeclHandler'}->can('internal_entity_decl') ) { my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'internal_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('internal_entity_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'internal_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DeclHandler'} and $callbacks->{'DeclHandler'}->can('AUTOLOAD') and $callbacks->{'DeclHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DeclHandler'}->internal_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DeclHandler'}; $self->{Methods}->{'internal_entity_decl'} = sub { $handler->internal_entity_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->internal_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'internal_entity_decl'} = sub { $handler->internal_entity_decl(@_) }; } return $res; } else { $self->{Methods}->{'internal_entity_decl'} = sub { }; } } } sub doctype_decl { my $self = shift; if (defined $self->{Methods}->{'doctype_decl'}) { $self->{Methods}->{'doctype_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('doctype_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'doctype_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('doctype_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'doctype_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->doctype_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'doctype_decl'} = sub { $handler->doctype_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->doctype_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'doctype_decl'} = sub { $handler->doctype_decl(@_) }; } return $res; } else { $self->{Methods}->{'doctype_decl'} = sub { }; } } } sub unparsed_entity_decl { my $self = shift; if (defined $self->{Methods}->{'unparsed_entity_decl'}) { $self->{Methods}->{'unparsed_entity_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('unparsed_entity_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'unparsed_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('unparsed_entity_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'unparsed_entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->unparsed_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'unparsed_entity_decl'} = sub { $handler->unparsed_entity_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->unparsed_entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'unparsed_entity_decl'} = sub { $handler->unparsed_entity_decl(@_) }; } return $res; } else { $self->{Methods}->{'unparsed_entity_decl'} = sub { }; } } } sub skipped_entity { my $self = shift; if (defined $self->{Methods}->{'skipped_entity'}) { $self->{Methods}->{'skipped_entity'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('skipped_entity') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'skipped_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('skipped_entity') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'skipped_entity'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->skipped_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'skipped_entity'} = sub { $handler->skipped_entity(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->skipped_entity(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'skipped_entity'} = sub { $handler->skipped_entity(@_) }; } return $res; } else { $self->{Methods}->{'skipped_entity'} = sub { }; } } } sub end_prefix_mapping { my $self = shift; if (defined $self->{Methods}->{'end_prefix_mapping'}) { $self->{Methods}->{'end_prefix_mapping'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('end_prefix_mapping') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_prefix_mapping'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_prefix_mapping') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_prefix_mapping'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->end_prefix_mapping(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_prefix_mapping'} = sub { $handler->end_prefix_mapping(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_prefix_mapping(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_prefix_mapping'} = sub { $handler->end_prefix_mapping(@_) }; } return $res; } else { $self->{Methods}->{'end_prefix_mapping'} = sub { }; } } } sub characters { my $self = shift; if (defined $self->{Methods}->{'characters'}) { $self->{Methods}->{'characters'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('characters') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'characters'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('characters') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'characters'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('characters') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'characters'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->characters(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'characters'} = sub { $handler->characters(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->characters(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'characters'} = sub { $handler->characters(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->characters(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'characters'} = sub { $handler->characters(@_) }; } return $res; } else { $self->{Methods}->{'characters'} = sub { }; } } } sub comment { my $self = shift; if (defined $self->{Methods}->{'comment'}) { $self->{Methods}->{'comment'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('comment') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'comment'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('comment') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'comment'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('comment') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'comment'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->comment(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'comment'} = sub { $handler->comment(@_) }; } return $res; } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->comment(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'comment'} = sub { $handler->comment(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->comment(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'comment'} = sub { $handler->comment(@_) }; } return $res; } else { $self->{Methods}->{'comment'} = sub { }; } } } sub start_dtd { my $self = shift; if (defined $self->{Methods}->{'start_dtd'}) { $self->{Methods}->{'start_dtd'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('start_dtd') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_dtd'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_dtd') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_dtd'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->start_dtd(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_dtd'} = sub { $handler->start_dtd(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_dtd(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_dtd'} = sub { $handler->start_dtd(@_) }; } return $res; } else { $self->{Methods}->{'start_dtd'} = sub { }; } } } sub entity_decl { my $self = shift; if (defined $self->{Methods}->{'entity_decl'}) { $self->{Methods}->{'entity_decl'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DTDHandler'} and $method = $callbacks->{'DTDHandler'}->can('entity_decl') ) { my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('entity_decl') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'entity_decl'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DTDHandler'} and $callbacks->{'DTDHandler'}->can('AUTOLOAD') and $callbacks->{'DTDHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DTDHandler'}->entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DTDHandler'}; $self->{Methods}->{'entity_decl'} = sub { $handler->entity_decl(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->entity_decl(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'entity_decl'} = sub { $handler->entity_decl(@_) }; } return $res; } else { $self->{Methods}->{'entity_decl'} = sub { }; } } } sub start_prefix_mapping { my $self = shift; if (defined $self->{Methods}->{'start_prefix_mapping'}) { $self->{Methods}->{'start_prefix_mapping'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('start_prefix_mapping') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_prefix_mapping'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_prefix_mapping') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_prefix_mapping'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->start_prefix_mapping(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'start_prefix_mapping'} = sub { $handler->start_prefix_mapping(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_prefix_mapping(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_prefix_mapping'} = sub { $handler->start_prefix_mapping(@_) }; } return $res; } else { $self->{Methods}->{'start_prefix_mapping'} = sub { }; } } } sub end_cdata { my $self = shift; if (defined $self->{Methods}->{'end_cdata'}) { $self->{Methods}->{'end_cdata'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('end_cdata') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('end_cdata') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_cdata') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->end_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_cdata'} = sub { $handler->end_cdata(@_) }; } return $res; } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->end_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'end_cdata'} = sub { $handler->end_cdata(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_cdata'} = sub { $handler->end_cdata(@_) }; } return $res; } else { $self->{Methods}->{'end_cdata'} = sub { }; } } } sub processing_instruction { my $self = shift; if (defined $self->{Methods}->{'processing_instruction'}) { $self->{Methods}->{'processing_instruction'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('processing_instruction') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'processing_instruction'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('processing_instruction') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'processing_instruction'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('processing_instruction') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'processing_instruction'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->processing_instruction(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'processing_instruction'} = sub { $handler->processing_instruction(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->processing_instruction(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'processing_instruction'} = sub { $handler->processing_instruction(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->processing_instruction(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'processing_instruction'} = sub { $handler->processing_instruction(@_) }; } return $res; } else { $self->{Methods}->{'processing_instruction'} = sub { }; } } } sub end_element { my $self = shift; if (defined $self->{Methods}->{'end_element'}) { $self->{Methods}->{'end_element'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'ContentHandler'} and $method = $callbacks->{'ContentHandler'}->can('end_element') ) { my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('end_element') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('end_element') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_element'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'ContentHandler'} and $callbacks->{'ContentHandler'}->can('AUTOLOAD') and $callbacks->{'ContentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'ContentHandler'}->end_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'ContentHandler'}; $self->{Methods}->{'end_element'} = sub { $handler->end_element(@_) }; } return $res; } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->end_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'end_element'} = sub { $handler->end_element(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->end_element(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'end_element'} = sub { $handler->end_element(@_) }; } return $res; } else { $self->{Methods}->{'end_element'} = sub { }; } } } sub start_cdata { my $self = shift; if (defined $self->{Methods}->{'start_cdata'}) { $self->{Methods}->{'start_cdata'}->(@_); } else { my $method; my $callbacks; if (exists $self->{ParseOptions}) { $callbacks = $self->{ParseOptions}; } else { $callbacks = $self; } if (0) { # dummy to make elsif's below compile } elsif (defined $callbacks->{'DocumentHandler'} and $method = $callbacks->{'DocumentHandler'}->can('start_cdata') ) { my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'LexicalHandler'} and $method = $callbacks->{'LexicalHandler'}->can('start_cdata') ) { my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'Handler'} and $method = $callbacks->{'Handler'}->can('start_cdata') ) { my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_cdata'} = sub { $method->($handler, @_) }; return $method->($handler, @_); } elsif (defined $callbacks->{'DocumentHandler'} and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') and $callbacks->{'DocumentHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'DocumentHandler'}->start_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'DocumentHandler'}; $self->{Methods}->{'start_cdata'} = sub { $handler->start_cdata(@_) }; } return $res; } elsif (defined $callbacks->{'LexicalHandler'} and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') and $callbacks->{'LexicalHandler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'LexicalHandler'}->start_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'LexicalHandler'}; $self->{Methods}->{'start_cdata'} = sub { $handler->start_cdata(@_) }; } return $res; } elsif (defined $callbacks->{'Handler'} and $callbacks->{'Handler'}->can('AUTOLOAD') and $callbacks->{'Handler'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my $res = eval { $callbacks->{'Handler'}->start_cdata(@_) }; if ($@) { die $@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my $handler = $callbacks->{'Handler'}; $self->{Methods}->{'start_cdata'} = sub { $handler->start_cdata(@_) }; } return $res; } else { $self->{Methods}->{'start_cdata'} = sub { }; } } } #-------------------------------------------------------------------# # Class->new(%options) #-------------------------------------------------------------------# sub new { my $proto = shift; my $class = ref($proto) || $proto; my $options = ($#_ == 0) ? shift : { @_ }; unless ( defined( $options->{Handler} ) or defined( $options->{ContentHandler} ) or defined( $options->{DTDHandler} ) or defined( $options->{DocumentHandler} ) or defined( $options->{LexicalHandler} ) or defined( $options->{ErrorHandler} ) or defined( $options->{DeclHandler} ) ) { $options->{Handler} = XML::SAX::Base::NoHandler->new; } my $self = bless $options, $class; # turn NS processing on by default $self->set_feature('http://xml.org/sax/features/namespaces', 1); return $self; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse(%options) #-------------------------------------------------------------------# sub parse { my $self = shift; my $parse_options = $self->get_options(@_); local $self->{ParseOptions} = $parse_options; if ($self->{Parent}) { # calling parse on a filter for some reason return $self->{Parent}->parse($parse_options); } else { my $method; if (defined $parse_options->{Source}{CharacterStream} and $method = $self->can('_parse_characterstream')) { warn("parse charstream???\n"); return $method->($self, $parse_options->{Source}{CharacterStream}); } elsif (defined $parse_options->{Source}{ByteStream} and $method = $self->can('_parse_bytestream')) { return $method->($self, $parse_options->{Source}{ByteStream}); } elsif (defined $parse_options->{Source}{String} and $method = $self->can('_parse_string')) { return $method->($self, $parse_options->{Source}{String}); } elsif (defined $parse_options->{Source}{SystemId} and $method = $self->can('_parse_systemid')) { return $method->($self, $parse_options->{Source}{SystemId}); } else { die "No _parse_* routine defined on this driver (If it is a filter, remember to set the Parent property. If you call the parse() method, make sure to set a Source. You may want to call parse_uri, parse_string or parse_file instead.) [$self]"; } } } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_file(%options) #-------------------------------------------------------------------# sub parse_file { my $self = shift; my $file = shift; return $self->parse_uri($file, @_) if ref(\$file) eq 'SCALAR'; my $parse_options = $self->get_options(@_); $parse_options->{Source}{ByteStream} = $file; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_uri(%options) #-------------------------------------------------------------------# sub parse_uri { my $self = shift; my $file = shift; my $parse_options = $self->get_options(@_); $parse_options->{Source}{SystemId} = $file; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_string(%options) #-------------------------------------------------------------------# sub parse_string { my $self = shift; my $string = shift; my $parse_options = $self->get_options(@_); $parse_options->{Source}{String} = $string; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_options #-------------------------------------------------------------------# sub get_options { my $self = shift; if (@_ == 1) { return { %$self, %{$_[0]} }; } else { return { %$self, @_ }; } } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_features #-------------------------------------------------------------------# sub get_features { return ( 'http://xml.org/sax/features/external-general-entities' => undef, 'http://xml.org/sax/features/external-parameter-entities' => undef, 'http://xml.org/sax/features/is-standalone' => undef, 'http://xml.org/sax/features/lexical-handler' => undef, 'http://xml.org/sax/features/parameter-entities' => undef, 'http://xml.org/sax/features/namespaces' => 1, 'http://xml.org/sax/features/namespace-prefixes' => 0, 'http://xml.org/sax/features/string-interning' => undef, 'http://xml.org/sax/features/use-attributes2' => undef, 'http://xml.org/sax/features/use-locator2' => undef, 'http://xml.org/sax/features/validation' => undef, 'http://xml.org/sax/properties/dom-node' => undef, 'http://xml.org/sax/properties/xml-string' => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_feature #-------------------------------------------------------------------# sub get_feature { my $self = shift; my $feat = shift; # check %FEATURES to see if it's there, and return it if so # throw XML::SAX::Exception::NotRecognized if it's not there # throw XML::SAX::Exception::NotSupported if it's there but we # don't support it my %features = $self->get_features(); if (exists $features{$feat}) { my %supported = map { $_ => 1 } $self->supported_features(); if ($supported{$feat}) { return $self->{__PACKAGE__ . "::Features"}{$feat}; } throw XML::SAX::Exception::NotSupported( Message => "The feature '$feat' is not supported by " . ref($self), Exception => undef, ); } throw XML::SAX::Exception::NotRecognized( Message => "The feature '$feat' is not recognized by " . ref($self), Exception => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # set_feature #-------------------------------------------------------------------# sub set_feature { my $self = shift; my $feat = shift; my $value = shift; # check %FEATURES to see if it's there, and set it if so # throw XML::SAX::Exception::NotRecognized if it's not there # throw XML::SAX::Exception::NotSupported if it's there but we # don't support it my %features = $self->get_features(); if (exists $features{$feat}) { my %supported = map { $_ => 1 } $self->supported_features(); if ($supported{$feat}) { return $self->{__PACKAGE__ . "::Features"}{$feat} = $value; } throw XML::SAX::Exception::NotSupported( Message => "The feature '$feat' is not supported by " . ref($self), Exception => undef, ); } throw XML::SAX::Exception::NotRecognized( Message => "The feature '$feat' is not recognized by " . ref($self), Exception => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_handler and friends #-------------------------------------------------------------------# sub get_handler { my $self = shift; my $handler_type = shift; $handler_type ||= 'Handler'; return defined( $self->{$handler_type} ) ? $self->{$handler_type} : undef; } sub get_document_handler { my $self = shift; return $self->get_handler('DocumentHandler', @_); } sub get_content_handler { my $self = shift; return $self->get_handler('ContentHandler', @_); } sub get_dtd_handler { my $self = shift; return $self->get_handler('DTDHandler', @_); } sub get_lexical_handler { my $self = shift; return $self->get_handler('LexicalHandler', @_); } sub get_decl_handler { my $self = shift; return $self->get_handler('DeclHandler', @_); } sub get_error_handler { my $self = shift; return $self->get_handler('ErrorHandler', @_); } sub get_entity_resolver { my $self = shift; return $self->get_handler('EntityResolver', @_); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # set_handler and friends #-------------------------------------------------------------------# sub set_handler { my $self = shift; my ($new_handler, $handler_type) = reverse @_; $handler_type ||= 'Handler'; $self->{Methods} = {} if $self->{Methods}; $self->{$handler_type} = $new_handler; $self->{ParseOptions}->{$handler_type} = $new_handler; return 1; } sub set_document_handler { my $self = shift; return $self->set_handler('DocumentHandler', @_); } sub set_content_handler { my $self = shift; return $self->set_handler('ContentHandler', @_); } sub set_dtd_handler { my $self = shift; return $self->set_handler('DTDHandler', @_); } sub set_lexical_handler { my $self = shift; return $self->set_handler('LexicalHandler', @_); } sub set_decl_handler { my $self = shift; return $self->set_handler('DeclHandler', @_); } sub set_error_handler { my $self = shift; return $self->set_handler('ErrorHandler', @_); } sub set_entity_resolver { my $self = shift; return $self->set_handler('EntityResolver', @_); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # supported_features #-------------------------------------------------------------------# sub supported_features { my $self = shift; # Only namespaces are required by all parsers return ( 'http://xml.org/sax/features/namespaces', ); } #-------------------------------------------------------------------# sub no_op { # this space intentionally blank } package XML::SAX::Base::NoHandler; $XML::SAX::Base::NoHandler::VERSION = '1.09'; # we need a fake handler that doesn't implement anything, this # simplifies the code a lot (though given the recent changes, # it may be better to do without) sub new { #warn "no handler called\n"; return bless {}; } 1; __END__ =head1 NAME XML::SAX::Base - Base class SAX Drivers and Filters =head1 SYNOPSIS package MyFilter; use XML::SAX::Base; @ISA = ('XML::SAX::Base'); =head1 DESCRIPTION This module has a very simple task - to be a base class for PerlSAX drivers and filters. It's default behaviour is to pass the input directly to the output unchanged. It can be useful to use this module as a base class so you don't have to, for example, implement the characters() callback. The main advantages that it provides are easy dispatching of events the right way (ie it takes care for you of checking that the handler has implemented that method, or has defined an AUTOLOAD), and the guarantee that filters will pass along events that they aren't implementing to handlers downstream that might nevertheless be interested in them. =head1 WRITING SAX DRIVERS AND FILTERS The Perl Sax API Reference is at L. Writing SAX Filters is tremendously easy: all you need to do is inherit from this module, and define the events you want to handle. A more detailed explanation can be found at http://www.xml.com/pub/a/2001/10/10/sax-filters.html. Writing Drivers is equally simple. The one thing you need to pay attention to is B to call events yourself (this applies to Filters as well). For instance: package MyFilter; use base qw(XML::SAX::Base); sub start_element { my $self = shift; my $data = shift; # do something $self->{Handler}->start_element($data); # BAD } The above example works well as precisely that: an example. But it has several faults: 1) it doesn't test to see whether the handler defines start_element. Perhaps it doesn't want to see that event, in which case you shouldn't throw it (otherwise it'll die). 2) it doesn't check ContentHandler and then Handler (ie it doesn't look to see that the user hasn't requested events on a specific handler, and if not on the default one), 3) if it did check all that, not only would the code be cumbersome (see this module's source to get an idea) but it would also probably have to check for a DocumentHandler (in case this were SAX1) and for AUTOLOADs potentially defined in all these packages. As you can tell, that would be fairly painful. Instead of going through that, simply remember to use code similar to the following instead: package MyFilter; use base qw(XML::SAX::Base); sub start_element { my $self = shift; my $data = shift; # do something to filter $self->SUPER::start_element($data); # GOOD (and easy) ! } This way, once you've done your job you hand the ball back to XML::SAX::Base and it takes care of all those problems for you! Note that the above example doesn't apply to filters only, drivers will benefit from the exact same feature. =head1 METHODS A number of methods are defined within this class for the purpose of inheritance. Some probably don't need to be overridden (eg parse_file) but some clearly should be (eg parse). Options for these methods are described in the PerlSAX2 specification available from http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/~checkout~/perl-xml/libxml-perl/doc/sax-2.0.html?rev=HEAD&content-type=text/html. =over 4 =item * parse The parse method is the main entry point to parsing documents. Internally the parse method will detect what type of "thing" you are parsing, and call the appropriate method in your implementation class. Here is the mapping table of what is in the Source options (see the Perl SAX 2.0 specification for the meaning of these values): Source Contains parse() calls =============== ============= CharacterStream (*) _parse_characterstream($stream, $options) ByteStream _parse_bytestream($stream, $options) String _parse_string($string, $options) SystemId _parse_systemid($string, $options) However note that these methods may not be sensible if your driver class is not for parsing XML. An example might be a DBI driver that generates XML/SAX from a database table. If that is the case, you likely want to write your own parse() method. Also note that the Source may contain both a PublicId entry, and an Encoding entry. To get at these, examine $options->{Source} as passed to your method. (*) A CharacterStream is a filehandle that does not need any encoding translation done on it. This is implemented as a regular filehandle and only works under Perl 5.7.2 or higher using PerlIO. To get a single character, or number of characters from it, use the perl core read() function. To get a single byte from it (or number of bytes), you can use sysread(). The encoding of the stream should be in the Encoding entry for the Source. =item * parse_file, parse_uri, parse_string These are all convenience variations on parse(), and in fact simply set up the options before calling it. You probably don't need to override these. =item * get_options This is a convenience method to get options in SAX2 style, or more generically either as hashes or as hashrefs (it returns a hashref). You will probably want to use this method in your own implementations of parse() and of new(). =item * get_feature, set_feature These simply get and set features, and throw the appropriate exceptions defined in the specification if need be. If your subclass defines features not defined in this one, then you should override these methods in such a way that they check for your features first, and then call the base class's methods for features not defined by your class. An example would be: sub get_feature { my $self = shift; my $feat = shift; if (exists $MY_FEATURES{$feat}) { # handle the feature in various ways } else { return $self->SUPER::get_feature($feat); } } Currently this part is unimplemented. =item * set_handler This method takes a handler type (Handler, ContentHandler, etc.) and a handler object as arguments, and changes the current handler for that handler type, while taking care of resetting the internal state that needs to be reset. This allows one to change a handler during parse without running into problems (changing it on the parser object directly will most likely cause trouble). =item * set_document_handler, set_content_handler, set_dtd_handler, set_lexical_handler, set_decl_handler, set_error_handler, set_entity_resolver These are just simple wrappers around the former method, and take a handler object as their argument. Internally they simply call set_handler with the correct arguments. =item * get_handler The inverse of set_handler, this method takes a an optional string containing a handler type (DTDHandler, ContentHandler, etc. 'Handler' is used if no type is passed). It returns a reference to the object that implements that class, or undef if that handler type is not set for the current driver/filter. =item * get_document_handler, get_content_handler, get_dtd_handler, get_lexical_handler, get_decl_handler, get_error_handler, get_entity_resolver These are just simple wrappers around the get_handler() method, and take no arguments. Internally they simply call get_handler with the correct handler type name. =back It would be rather useless to describe all the methods that this module implements here. They are all the methods supported in SAX1 and SAX2. In case your memory is a little short, here is a list. The apparent duplicates are there so that both versions of SAX can be supported. =over 4 =item * start_document =item * end_document =item * start_element =item * start_document =item * end_document =item * start_element =item * end_element =item * characters =item * processing_instruction =item * ignorable_whitespace =item * set_document_locator =item * start_prefix_mapping =item * end_prefix_mapping =item * skipped_entity =item * start_cdata =item * end_cdata =item * comment =item * entity_reference =item * notation_decl =item * unparsed_entity_decl =item * element_decl =item * attlist_decl =item * doctype_decl =item * xml_decl =item * entity_decl =item * attribute_decl =item * internal_entity_decl =item * external_entity_decl =item * resolve_entity =item * start_dtd =item * end_dtd =item * start_entity =item * end_entity =item * warning =item * error =item * fatal_error =back =head1 TODO - more tests - conform to the "SAX Filters" and "Java and DOM compatibility" sections of the SAX2 document. =head1 AUTHOR Kip Hampton (khampton@totalcinema.com) did most of the work, after porting it from XML::Filter::Base. Robin Berjon (robin@knowscape.com) pitched in with patches to make it usable as a base for drivers as well as filters, along with other patches. Matt Sergeant (matt@sergeant.org) wrote the original XML::Filter::Base, and patched a few things here and there, and imported it into the XML::SAX distribution. =head1 SEE ALSO L =cut PK!~@ppperl5/XML/SAX/BuildSAXBase.plnu6$#!/usr/bin/perl # # This file is used to generate lib/XML/SAX/Base.pm. There is a pre-generated # Base.pm file included in the distribution so you don't need to run this # script unless you are attempting to modify the code. # # The code in this file was adapted from the Makefile.PL when XML::SAX::Base # was split back out into its own distribution. # # You can manually run this file: # # perl ./BuildSAXBase.pl # # or better yet it will be invoked by automatically Dist::Zilla when building # a release from the git repository. # # dzil build # package SAX::Base::Builder; use strict; use warnings; use File::Spec; write_xml_sax_base() unless caller(); sub build_xml_sax_base { my $code = <<'EOHEADER'; package XML::SAX::Base; # version 0.10 - Kip Hampton # version 0.13 - Robin Berjon # version 0.15 - Kip Hampton # version 0.17 - Kip Hampton # version 0.19 - Kip Hampton # version 0.21 - Kip Hampton # version 0.22 - Robin Berjon # version 0.23 - Matt Sergeant # version 0.24 - Robin Berjon # version 0.25 - Kip Hampton # version 1.00 - Kip Hampton # version 1.01 - Kip Hampton # version 1.02 - Robin Berjon # version 1.03 - Matt Sergeant # version 1.04 - Kip Hampton # version 1.05 - Grant McLean # version 1.06 - Grant McLean # version 1.07 - Grant McLean # version 1.08 - Grant McLean #-----------------------------------------------------# # STOP!!!!! # # This file is generated by the 'BuildSAXBase.pl' file # that ships with the XML::SAX::Base distribution. # If you need to make changes, patch that file NOT # XML/SAX/Base.pm Better yet, fork the git repository # commit your changes and send a pull request: # https://github.com/grantm/XML-SAX-Base #-----------------------------------------------------# use strict; use XML::SAX::Exception qw(); EOHEADER my %EVENT_SPEC = ( start_document => [qw(ContentHandler DocumentHandler Handler)], end_document => [qw(ContentHandler DocumentHandler Handler)], start_element => [qw(ContentHandler DocumentHandler Handler)], end_element => [qw(ContentHandler DocumentHandler Handler)], characters => [qw(ContentHandler DocumentHandler Handler)], processing_instruction => [qw(ContentHandler DocumentHandler Handler)], ignorable_whitespace => [qw(ContentHandler DocumentHandler Handler)], set_document_locator => [qw(ContentHandler DocumentHandler Handler)], start_prefix_mapping => [qw(ContentHandler Handler)], end_prefix_mapping => [qw(ContentHandler Handler)], skipped_entity => [qw(ContentHandler Handler)], start_cdata => [qw(DocumentHandler LexicalHandler Handler)], end_cdata => [qw(DocumentHandler LexicalHandler Handler)], comment => [qw(DocumentHandler LexicalHandler Handler)], entity_reference => [qw(DocumentHandler Handler)], notation_decl => [qw(DTDHandler Handler)], unparsed_entity_decl => [qw(DTDHandler Handler)], element_decl => [qw(DeclHandler Handler)], attlist_decl => [qw(DTDHandler Handler)], doctype_decl => [qw(DTDHandler Handler)], xml_decl => [qw(DTDHandler Handler)], entity_decl => [qw(DTDHandler Handler)], attribute_decl => [qw(DeclHandler Handler)], internal_entity_decl => [qw(DeclHandler Handler)], external_entity_decl => [qw(DeclHandler Handler)], resolve_entity => [qw(EntityResolver Handler)], start_dtd => [qw(LexicalHandler Handler)], end_dtd => [qw(LexicalHandler Handler)], start_entity => [qw(LexicalHandler Handler)], end_entity => [qw(LexicalHandler Handler)], warning => [qw(ErrorHandler Handler)], error => [qw(ErrorHandler Handler)], fatal_error => [qw(ErrorHandler Handler)], ); for my $ev (keys %EVENT_SPEC) { $code .= <<" EOTOPCODE"; sub $ev { my \$self = shift; if (defined \$self->{Methods}->{'$ev'}) { \$self->{Methods}->{'$ev'}->(\@_); } else { my \$method; my \$callbacks; if (exists \$self->{ParseOptions}) { \$callbacks = \$self->{ParseOptions}; } else { \$callbacks = \$self; } if (0) { # dummy to make elsif's below compile } EOTOPCODE my ($can_string, $aload_string); for my $h (@{$EVENT_SPEC{$ev}}) { $can_string .= <<" EOCANBLOCK"; elsif (defined \$callbacks->{'$h'} and \$method = \$callbacks->{'$h'}->can('$ev') ) { my \$handler = \$callbacks->{'$h'}; \$self->{Methods}->{'$ev'} = sub { \$method->(\$handler, \@_) }; return \$method->(\$handler, \@_); } EOCANBLOCK $aload_string .= <<" EOALOADBLOCK"; elsif (defined \$callbacks->{'$h'} and \$callbacks->{'$h'}->can('AUTOLOAD') and \$callbacks->{'$h'}->can('AUTOLOAD') ne (UNIVERSAL->can('AUTOLOAD') || '') ) { my \$res = eval { \$callbacks->{'$h'}->$ev(\@_) }; if (\$@) { die \$@; } else { # I think there's a buggette here... # if the first call throws an exception, we don't set it up right. # Not fatal, but we might want to address it. my \$handler = \$callbacks->{'$h'}; \$self->{Methods}->{'$ev'} = sub { \$handler->$ev(\@_) }; } return \$res; } EOALOADBLOCK } $code .= $can_string . $aload_string; $code .= <<" EOFALLTHROUGH"; else { \$self->{Methods}->{'$ev'} = sub { }; } } EOFALLTHROUGH $code .= "\n}\n\n"; } $code .= <<'BODY'; #-------------------------------------------------------------------# # Class->new(%options) #-------------------------------------------------------------------# sub new { my $proto = shift; my $class = ref($proto) || $proto; my $options = ($#_ == 0) ? shift : { @_ }; unless ( defined( $options->{Handler} ) or defined( $options->{ContentHandler} ) or defined( $options->{DTDHandler} ) or defined( $options->{DocumentHandler} ) or defined( $options->{LexicalHandler} ) or defined( $options->{ErrorHandler} ) or defined( $options->{DeclHandler} ) ) { $options->{Handler} = XML::SAX::Base::NoHandler->new; } my $self = bless $options, $class; # turn NS processing on by default $self->set_feature('http://xml.org/sax/features/namespaces', 1); return $self; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse(%options) #-------------------------------------------------------------------# sub parse { my $self = shift; my $parse_options = $self->get_options(@_); local $self->{ParseOptions} = $parse_options; if ($self->{Parent}) { # calling parse on a filter for some reason return $self->{Parent}->parse($parse_options); } else { my $method; if (defined $parse_options->{Source}{CharacterStream} and $method = $self->can('_parse_characterstream')) { warn("parse charstream???\n"); return $method->($self, $parse_options->{Source}{CharacterStream}); } elsif (defined $parse_options->{Source}{ByteStream} and $method = $self->can('_parse_bytestream')) { return $method->($self, $parse_options->{Source}{ByteStream}); } elsif (defined $parse_options->{Source}{String} and $method = $self->can('_parse_string')) { return $method->($self, $parse_options->{Source}{String}); } elsif (defined $parse_options->{Source}{SystemId} and $method = $self->can('_parse_systemid')) { return $method->($self, $parse_options->{Source}{SystemId}); } else { die "No _parse_* routine defined on this driver (If it is a filter, remember to set the Parent property. If you call the parse() method, make sure to set a Source. You may want to call parse_uri, parse_string or parse_file instead.) [$self]"; } } } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_file(%options) #-------------------------------------------------------------------# sub parse_file { my $self = shift; my $file = shift; return $self->parse_uri($file, @_) if ref(\$file) eq 'SCALAR'; my $parse_options = $self->get_options(@_); $parse_options->{Source}{ByteStream} = $file; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_uri(%options) #-------------------------------------------------------------------# sub parse_uri { my $self = shift; my $file = shift; my $parse_options = $self->get_options(@_); $parse_options->{Source}{SystemId} = $file; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->parse_string(%options) #-------------------------------------------------------------------# sub parse_string { my $self = shift; my $string = shift; my $parse_options = $self->get_options(@_); $parse_options->{Source}{String} = $string; return $self->parse($parse_options); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_options #-------------------------------------------------------------------# sub get_options { my $self = shift; if (@_ == 1) { return { %$self, %{$_[0]} }; } else { return { %$self, @_ }; } } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_features #-------------------------------------------------------------------# sub get_features { return ( 'http://xml.org/sax/features/external-general-entities' => undef, 'http://xml.org/sax/features/external-parameter-entities' => undef, 'http://xml.org/sax/features/is-standalone' => undef, 'http://xml.org/sax/features/lexical-handler' => undef, 'http://xml.org/sax/features/parameter-entities' => undef, 'http://xml.org/sax/features/namespaces' => 1, 'http://xml.org/sax/features/namespace-prefixes' => 0, 'http://xml.org/sax/features/string-interning' => undef, 'http://xml.org/sax/features/use-attributes2' => undef, 'http://xml.org/sax/features/use-locator2' => undef, 'http://xml.org/sax/features/validation' => undef, 'http://xml.org/sax/properties/dom-node' => undef, 'http://xml.org/sax/properties/xml-string' => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_feature #-------------------------------------------------------------------# sub get_feature { my $self = shift; my $feat = shift; # check %FEATURES to see if it's there, and return it if so # throw XML::SAX::Exception::NotRecognized if it's not there # throw XML::SAX::Exception::NotSupported if it's there but we # don't support it my %features = $self->get_features(); if (exists $features{$feat}) { my %supported = map { $_ => 1 } $self->supported_features(); if ($supported{$feat}) { return $self->{__PACKAGE__ . "::Features"}{$feat}; } throw XML::SAX::Exception::NotSupported( Message => "The feature '$feat' is not supported by " . ref($self), Exception => undef, ); } throw XML::SAX::Exception::NotRecognized( Message => "The feature '$feat' is not recognized by " . ref($self), Exception => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # set_feature #-------------------------------------------------------------------# sub set_feature { my $self = shift; my $feat = shift; my $value = shift; # check %FEATURES to see if it's there, and set it if so # throw XML::SAX::Exception::NotRecognized if it's not there # throw XML::SAX::Exception::NotSupported if it's there but we # don't support it my %features = $self->get_features(); if (exists $features{$feat}) { my %supported = map { $_ => 1 } $self->supported_features(); if ($supported{$feat}) { return $self->{__PACKAGE__ . "::Features"}{$feat} = $value; } throw XML::SAX::Exception::NotSupported( Message => "The feature '$feat' is not supported by " . ref($self), Exception => undef, ); } throw XML::SAX::Exception::NotRecognized( Message => "The feature '$feat' is not recognized by " . ref($self), Exception => undef, ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # get_handler and friends #-------------------------------------------------------------------# sub get_handler { my $self = shift; my $handler_type = shift; $handler_type ||= 'Handler'; return defined( $self->{$handler_type} ) ? $self->{$handler_type} : undef; } sub get_document_handler { my $self = shift; return $self->get_handler('DocumentHandler', @_); } sub get_content_handler { my $self = shift; return $self->get_handler('ContentHandler', @_); } sub get_dtd_handler { my $self = shift; return $self->get_handler('DTDHandler', @_); } sub get_lexical_handler { my $self = shift; return $self->get_handler('LexicalHandler', @_); } sub get_decl_handler { my $self = shift; return $self->get_handler('DeclHandler', @_); } sub get_error_handler { my $self = shift; return $self->get_handler('ErrorHandler', @_); } sub get_entity_resolver { my $self = shift; return $self->get_handler('EntityResolver', @_); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # set_handler and friends #-------------------------------------------------------------------# sub set_handler { my $self = shift; my ($new_handler, $handler_type) = reverse @_; $handler_type ||= 'Handler'; $self->{Methods} = {} if $self->{Methods}; $self->{$handler_type} = $new_handler; $self->{ParseOptions}->{$handler_type} = $new_handler; return 1; } sub set_document_handler { my $self = shift; return $self->set_handler('DocumentHandler', @_); } sub set_content_handler { my $self = shift; return $self->set_handler('ContentHandler', @_); } sub set_dtd_handler { my $self = shift; return $self->set_handler('DTDHandler', @_); } sub set_lexical_handler { my $self = shift; return $self->set_handler('LexicalHandler', @_); } sub set_decl_handler { my $self = shift; return $self->set_handler('DeclHandler', @_); } sub set_error_handler { my $self = shift; return $self->set_handler('ErrorHandler', @_); } sub set_entity_resolver { my $self = shift; return $self->set_handler('EntityResolver', @_); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # supported_features #-------------------------------------------------------------------# sub supported_features { my $self = shift; # Only namespaces are required by all parsers return ( 'http://xml.org/sax/features/namespaces', ); } #-------------------------------------------------------------------# sub no_op { # this space intentionally blank } package XML::SAX::Base::NoHandler; # we need a fake handler that doesn't implement anything, this # simplifies the code a lot (though given the recent changes, # it may be better to do without) sub new { #warn "no handler called\n"; return bless {}; } 1; BODY $code .= "__END__\n"; $code .= <<'FOOTER'; =head1 NAME XML::SAX::Base - Base class SAX Drivers and Filters =head1 SYNOPSIS package MyFilter; use XML::SAX::Base; @ISA = ('XML::SAX::Base'); =head1 DESCRIPTION This module has a very simple task - to be a base class for PerlSAX drivers and filters. It's default behaviour is to pass the input directly to the output unchanged. It can be useful to use this module as a base class so you don't have to, for example, implement the characters() callback. The main advantages that it provides are easy dispatching of events the right way (ie it takes care for you of checking that the handler has implemented that method, or has defined an AUTOLOAD), and the guarantee that filters will pass along events that they aren't implementing to handlers downstream that might nevertheless be interested in them. =head1 WRITING SAX DRIVERS AND FILTERS The Perl Sax API Reference is at L. Writing SAX Filters is tremendously easy: all you need to do is inherit from this module, and define the events you want to handle. A more detailed explanation can be found at http://www.xml.com/pub/a/2001/10/10/sax-filters.html. Writing Drivers is equally simple. The one thing you need to pay attention to is B to call events yourself (this applies to Filters as well). For instance: package MyFilter; use base qw(XML::SAX::Base); sub start_element { my $self = shift; my $data = shift; # do something $self->{Handler}->start_element($data); # BAD } The above example works well as precisely that: an example. But it has several faults: 1) it doesn't test to see whether the handler defines start_element. Perhaps it doesn't want to see that event, in which case you shouldn't throw it (otherwise it'll die). 2) it doesn't check ContentHandler and then Handler (ie it doesn't look to see that the user hasn't requested events on a specific handler, and if not on the default one), 3) if it did check all that, not only would the code be cumbersome (see this module's source to get an idea) but it would also probably have to check for a DocumentHandler (in case this were SAX1) and for AUTOLOADs potentially defined in all these packages. As you can tell, that would be fairly painful. Instead of going through that, simply remember to use code similar to the following instead: package MyFilter; use base qw(XML::SAX::Base); sub start_element { my $self = shift; my $data = shift; # do something to filter $self->SUPER::start_element($data); # GOOD (and easy) ! } This way, once you've done your job you hand the ball back to XML::SAX::Base and it takes care of all those problems for you! Note that the above example doesn't apply to filters only, drivers will benefit from the exact same feature. =head1 METHODS A number of methods are defined within this class for the purpose of inheritance. Some probably don't need to be overridden (eg parse_file) but some clearly should be (eg parse). Options for these methods are described in the PerlSAX2 specification available from http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/~checkout~/perl-xml/libxml-perl/doc/sax-2.0.html?rev=HEAD&content-type=text/html. =over 4 =item * parse The parse method is the main entry point to parsing documents. Internally the parse method will detect what type of "thing" you are parsing, and call the appropriate method in your implementation class. Here is the mapping table of what is in the Source options (see the Perl SAX 2.0 specification for the meaning of these values): Source Contains parse() calls =============== ============= CharacterStream (*) _parse_characterstream($stream, $options) ByteStream _parse_bytestream($stream, $options) String _parse_string($string, $options) SystemId _parse_systemid($string, $options) However note that these methods may not be sensible if your driver class is not for parsing XML. An example might be a DBI driver that generates XML/SAX from a database table. If that is the case, you likely want to write your own parse() method. Also note that the Source may contain both a PublicId entry, and an Encoding entry. To get at these, examine $options->{Source} as passed to your method. (*) A CharacterStream is a filehandle that does not need any encoding translation done on it. This is implemented as a regular filehandle and only works under Perl 5.7.2 or higher using PerlIO. To get a single character, or number of characters from it, use the perl core read() function. To get a single byte from it (or number of bytes), you can use sysread(). The encoding of the stream should be in the Encoding entry for the Source. =item * parse_file, parse_uri, parse_string These are all convenience variations on parse(), and in fact simply set up the options before calling it. You probably don't need to override these. =item * get_options This is a convenience method to get options in SAX2 style, or more generically either as hashes or as hashrefs (it returns a hashref). You will probably want to use this method in your own implementations of parse() and of new(). =item * get_feature, set_feature These simply get and set features, and throw the appropriate exceptions defined in the specification if need be. If your subclass defines features not defined in this one, then you should override these methods in such a way that they check for your features first, and then call the base class's methods for features not defined by your class. An example would be: sub get_feature { my $self = shift; my $feat = shift; if (exists $MY_FEATURES{$feat}) { # handle the feature in various ways } else { return $self->SUPER::get_feature($feat); } } Currently this part is unimplemented. =item * set_handler This method takes a handler type (Handler, ContentHandler, etc.) and a handler object as arguments, and changes the current handler for that handler type, while taking care of resetting the internal state that needs to be reset. This allows one to change a handler during parse without running into problems (changing it on the parser object directly will most likely cause trouble). =item * set_document_handler, set_content_handler, set_dtd_handler, set_lexical_handler, set_decl_handler, set_error_handler, set_entity_resolver These are just simple wrappers around the former method, and take a handler object as their argument. Internally they simply call set_handler with the correct arguments. =item * get_handler The inverse of set_handler, this method takes a an optional string containing a handler type (DTDHandler, ContentHandler, etc. 'Handler' is used if no type is passed). It returns a reference to the object that implements that class, or undef if that handler type is not set for the current driver/filter. =item * get_document_handler, get_content_handler, get_dtd_handler, get_lexical_handler, get_decl_handler, get_error_handler, get_entity_resolver These are just simple wrappers around the get_handler() method, and take no arguments. Internally they simply call get_handler with the correct handler type name. =back It would be rather useless to describe all the methods that this module implements here. They are all the methods supported in SAX1 and SAX2. In case your memory is a little short, here is a list. The apparent duplicates are there so that both versions of SAX can be supported. =over 4 =item * start_document =item * end_document =item * start_element =item * start_document =item * end_document =item * start_element =item * end_element =item * characters =item * processing_instruction =item * ignorable_whitespace =item * set_document_locator =item * start_prefix_mapping =item * end_prefix_mapping =item * skipped_entity =item * start_cdata =item * end_cdata =item * comment =item * entity_reference =item * notation_decl =item * unparsed_entity_decl =item * element_decl =item * attlist_decl =item * doctype_decl =item * xml_decl =item * entity_decl =item * attribute_decl =item * internal_entity_decl =item * external_entity_decl =item * resolve_entity =item * start_dtd =item * end_dtd =item * start_entity =item * end_entity =item * warning =item * error =item * fatal_error =back =head1 TODO - more tests - conform to the "SAX Filters" and "Java and DOM compatibility" sections of the SAX2 document. =head1 AUTHOR Kip Hampton (khampton@totalcinema.com) did most of the work, after porting it from XML::Filter::Base. Robin Berjon (robin@knowscape.com) pitched in with patches to make it usable as a base for drivers as well as filters, along with other patches. Matt Sergeant (matt@sergeant.org) wrote the original XML::Filter::Base, and patched a few things here and there, and imported it into the XML::SAX distribution. =head1 SEE ALSO L =cut FOOTER return $code; } sub write_xml_sax_base { confirm_forced_update(); my $path = File::Spec->catfile("lib", "XML", "SAX", "Base.pm"); save_original_xml_sax_base($path); my $code = build_xml_sax_base(); $code = add_version_stanzas($code); open my $fh, ">", $path or die "Cannot write $path: $!"; print $fh $code; close $fh or die "Error writing $path: $!"; print "Wrote $path\n"; } sub confirm_forced_update { return if grep { $_ eq '--force' } @ARGV; print <<'EOF'; *** WARNING *** The BuildSAXBase.pl script is used to generate the lib/XML/SAX/Base.pm file. However a pre-generated version of Base.pm is included in the distribution so you do not need to run this script unless you intend to modify the code. You must use the --force option to deliberately overwrite the distributed version of lib/XML/SAX/Base.pm EOF exit; } sub save_original_xml_sax_base { my($path) = @_; return unless -e $path; (my $save_path = $path) =~ s{Base}{Base-orig}; return if -e $save_path; print "Saving $path to $save_path\n"; rename($path, $save_path); } sub add_version_stanzas { my($code) = @_; my $version = get_xml_sax_base_version(); $code =~ s<^(package\s+(\w[:\w]+).*?\n)> <${1}BEGIN {\n \$${2}::VERSION = '$version';\n}\n>mg; return $code; } sub get_xml_sax_base_version { open my $fh, '<', 'dist.ini' or die "open() { m{^\s*version\s*=\s*(\S+)} && return $1; } die "Failed to find version in dist.ini"; } PK!LLperl5/XML/SAX/Expat.pmnu7m ### # XML::SAX::Expat - SAX2 Driver for Expat (XML::Parser) # Originally by Robin Berjon ### package XML::SAX::Expat; use strict; use base qw(XML::SAX::Base); use XML::NamespaceSupport qw(); use XML::Parser qw(); use vars qw($VERSION); $VERSION = '0.51'; #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #`,`, Variations on parse `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# #```````````````````````````````````````````````````````````````````# #-------------------------------------------------------------------# # CharacterStream #-------------------------------------------------------------------# sub _parse_characterstream { my $p = shift; my $xml = shift; my $opt = shift; my $expat = $p->_create_parser($opt); my $result = $expat->parse($xml); $p->_cleanup; return $result; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # ByteStream #-------------------------------------------------------------------# sub _parse_bytestream { my $p = shift; my $xml = shift; my $opt = shift; my $expat = $p->_create_parser($opt); my $result = $expat->parse($xml); $p->_cleanup; return $result; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # String #-------------------------------------------------------------------# sub _parse_string { my $p = shift; my $xml = shift; my $opt = shift; my $expat = $p->_create_parser($opt); my $result = $expat->parse($xml); $p->_cleanup; return $result; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # SystemId #-------------------------------------------------------------------# sub _parse_systemid { my $p = shift; my $xml = shift; my $opt = shift; my $expat = $p->_create_parser($opt); my $result = $expat->parsefile($xml); $p->_cleanup; return $result; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->_create_parser(\%options) #-------------------------------------------------------------------# sub _create_parser { my $self = shift; my $opt = shift; die "ParserReference: parser instance ($self) already parsing\n" if $self->{_InParse}; my $featUri = 'http://xml.org/sax/features/'; my $ppe = ($self->get_feature($featUri . 'external-general-entities') or $self->get_feature($featUri . 'external-parameter-entities') ) ? 1 : 0; my $expat = XML::Parser->new( ParseParamEnt => $ppe ); $expat->{__XSE} = $self; $expat->setHandlers( Init => \&_handle_init, Final => \&_handle_final, Start => \&_handle_start, End => \&_handle_end, Char => \&_handle_char, Comment => \&_handle_comment, Proc => \&_handle_proc, CdataStart => \&_handle_start_cdata, CdataEnd => \&_handle_end_cdata, Unparsed => \&_handle_unparsed_entity, Notation => \&_handle_notation_decl, #ExternEnt #ExternEntFin Entity => \&_handle_entity_decl, Element => \&_handle_element_decl, Attlist => \&_handle_attr_decl, Doctype => \&_handle_start_doctype, DoctypeFin => \&_handle_end_doctype, XMLDecl => \&_handle_xml_decl, ); $self->{_InParse} = 1; $self->{_NodeStack} = []; $self->{_NSStack} = []; $self->{_NSHelper} = XML::NamespaceSupport->new({xmlns => 1}); $self->{_started} = 0; return $expat; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # $p->_cleanup #-------------------------------------------------------------------# sub _cleanup { my $self = shift; $self->{_InParse} = 0; delete $self->{_NodeStack}; } #-------------------------------------------------------------------# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #`,`, Expat Handlers ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# #```````````````````````````````````````````````````````````````````# #-------------------------------------------------------------------# # _handle_init #-------------------------------------------------------------------# sub _handle_init { #my $self = shift()->{__XSE}; #my $document = {}; #push @{$self->{_NodeStack}}, $document; #$self->SUPER::start_document($document); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_final #-------------------------------------------------------------------# sub _handle_final { my $self = shift()->{__XSE}; #my $document = pop @{$self->{_NodeStack}}; return $self->SUPER::end_document({}); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_start #-------------------------------------------------------------------# sub _handle_start { my $self = shift()->{__XSE}; my $e_name = shift; my %attr = @_; # start_document data $self->_handle_start_document({}) unless $self->{_started}; # take care of namespaces my $nsh = $self->{_NSHelper}; $nsh->push_context; my @new_ns; for my $k (grep !index($_, 'xmlns'), keys %attr) { $k =~ m/^xmlns(:(.*))?$/; my $prefix = $2 || ''; $nsh->declare_prefix($prefix, $attr{$k}); my $ns = { Prefix => $prefix, NamespaceURI => $attr{$k}, }; push @new_ns, $ns; $self->SUPER::start_prefix_mapping($ns); } push @{$self->{_NSStack}}, \@new_ns; # create the attributes my %saxattr; map { my ($ns,$prefix,$lname) = $nsh->process_attribute_name($_); $saxattr{'{' . ($ns || '') . '}' . $lname} = { Name => $_, LocalName => $lname || '', Prefix => $prefix || '', Value => $attr{$_}, NamespaceURI => $ns || '', }; } keys %attr; # now the element my ($ns,$prefix,$lname) = $nsh->process_element_name($e_name); my $element = { Name => $e_name, LocalName => $lname || '', Prefix => $prefix || '', NamespaceURI => $ns || '', Attributes => \%saxattr, }; push @{$self->{_NodeStack}}, $element; $self->SUPER::start_element($element); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_end #-------------------------------------------------------------------# sub _handle_end { my $self = shift()->{__XSE}; my %element = %{pop @{$self->{_NodeStack}}}; delete $element{Attributes}; $self->SUPER::end_element(\%element); my $prev_ns = pop @{$self->{_NSStack}}; for my $ns (@$prev_ns) { $self->SUPER::end_prefix_mapping( { %$ns } ); } $self->{_NSHelper}->pop_context; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_char #-------------------------------------------------------------------# sub _handle_char { $_[0]->{__XSE}->_handle_start_document({}) unless $_[0]->{__XSE}->{_started}; $_[0]->{__XSE}->SUPER::characters({ Data => $_[1] }); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_comment #-------------------------------------------------------------------# sub _handle_comment { $_[0]->{__XSE}->_handle_start_document({}) unless $_[0]->{__XSE}->{_started}; $_[0]->{__XSE}->SUPER::comment({ Data => $_[1] }); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_proc #-------------------------------------------------------------------# sub _handle_proc { $_[0]->{__XSE}->_handle_start_document({}) unless $_[0]->{__XSE}->{_started}; $_[0]->{__XSE}->SUPER::processing_instruction({ Target => $_[1], Data => $_[2] }); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_start_cdata #-------------------------------------------------------------------# sub _handle_start_cdata { $_[0]->{__XSE}->SUPER::start_cdata( {} ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_end_cdata #-------------------------------------------------------------------# sub _handle_end_cdata { $_[0]->{__XSE}->SUPER::end_cdata( {} ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_xml_decl #-------------------------------------------------------------------# sub _handle_xml_decl { my $self = shift()->{__XSE}; my $version = shift; my $encoding = shift; my $standalone = shift; if (not defined $standalone) { $standalone = ''; } elsif ($standalone) { $standalone = 'yes'; } else { $standalone = 'no'; } my $xd = { Version => $version, Encoding => $encoding, Standalone => $standalone, }; #$self->SUPER::xml_decl($xd); $self->_handle_start_document($xd); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_notation_decl #-------------------------------------------------------------------# sub _handle_notation_decl { my $self = shift()->{__XSE}; my $notation = shift; shift; my $system = shift; my $public = shift; my $not = { Name => $notation, PublicId => $public, SystemId => $system, }; $self->SUPER::notation_decl($not); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_unparsed_entity #-------------------------------------------------------------------# sub _handle_unparsed_entity { my $self = shift()->{__XSE}; my $name = shift; my $system = shift; my $public = shift; my $notation = shift; my $ue = { Name => $name, PublicId => $public, SystemId => $system, Notation => $notation, }; $self->SUPER::unparsed_entity_decl($ue); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_element_decl #-------------------------------------------------------------------# sub _handle_element_decl { $_[0]->{__XSE}->SUPER::element_decl({ Name => $_[1], Model => "$_[2]" }); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_attr_decl #-------------------------------------------------------------------# sub _handle_attr_decl { my $self = shift()->{__XSE}; my $ename = shift; my $aname = shift; my $type = shift; my $default = shift; my $fixed = shift; my ($vd, $value); if ($fixed) { $vd = '#FIXED'; $default =~ s/^(?:"|')//; #" $default =~ s/(?:"|')$//; #" $value = $default; } else { if ($default =~ m/^#/) { $vd = $default; $value = ''; } else { $vd = ''; # maybe there's a default ? $default =~ s/^(?:"|')//; #" $default =~ s/(?:"|')$//; #" $value = $default; } } my $at = { eName => $ename, aName => $aname, Type => $type, ValueDefault => $vd, Value => $value, }; $self->SUPER::attribute_decl($at); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_entity_decl #-------------------------------------------------------------------# sub _handle_entity_decl { my $self = shift()->{__XSE}; my $name = shift; my $val = shift; my $sys = shift; my $pub = shift; my $ndata = shift; my $isprm = shift; # deal with param ents if ($isprm) { $name = '%' . $name; } # int vs ext if ($val) { my $ent = { Name => $name, Value => $val, }; $self->SUPER::internal_entity_decl($ent); } else { my $ent = { Name => $name, PublicId => $pub || '', SystemId => $sys, }; $self->SUPER::external_entity_decl($ent); } } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_start_doctype #-------------------------------------------------------------------# sub _handle_start_doctype { my $self = shift()->{__XSE}; my $name = shift; my $sys = shift; my $pub = shift; $self->_handle_start_document({}) unless $self->{_started}; my $dtd = { Name => $name, SystemId => $sys, PublicId => $pub, }; $self->SUPER::start_dtd($dtd); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_end_doctype #-------------------------------------------------------------------# sub _handle_end_doctype { $_[0]->{__XSE}->SUPER::end_dtd( {} ); } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # _handle_start_document #-------------------------------------------------------------------# sub _handle_start_document { $_[0]->SUPER::start_document($_[1]); $_[0]->{_started} = 1; } #-------------------------------------------------------------------# #-------------------------------------------------------------------# # supported_features #-------------------------------------------------------------------# sub supported_features { return ( $_[0]->SUPER::supported_features, 'http://xml.org/sax/features/external-general-entities', 'http://xml.org/sax/features/external-parameter-entities', ); } #-------------------------------------------------------------------# #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #`,`, Private Helpers `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# #```````````````````````````````````````````````````````````````````# #-------------------------------------------------------------------# # _create_node #-------------------------------------------------------------------# #sub _create_node { # shift; # # this may check for a factory later # return {@_}; #} #-------------------------------------------------------------------# 1; #,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,# #`,`, Documentation `,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,# #```````````````````````````````````````````````````````````````````# =pod =head1 NAME XML::SAX::Expat - SAX2 Driver for Expat (XML::Parser) =head1 SYNOPSIS use XML::SAX::Expat; use XML::SAX::MyFooHandler; my $h = XML::SAX::MyFooHandler->new; my $p = XML::SAX::Expat->new(Handler => $h); $p->parse_file('/path/to/foo.xml'); =head1 DESCRIPTION This is an implementation of a SAX2 driver sitting on top of Expat (XML::Parser) which Ken MacLeod posted to perl-xml and which I have updated. It is still incomplete, though most of the basic SAX2 events should be available. The SAX2 spec is currently available from L. A more friendly URL as well as a PODification of the spec are in the works. =head1 METHODS The methods defined in this class correspond to those listed in the PerlSAX2 specification, available above. =head1 FEATURES AND CAVEATS =over 2 =item supported_features Returns: * http://xml.org/sax/features/external-general-entities * http://xml.org/sax/features/external-parameter-entities * [ Features supported by ancestors ] Turning one of the first two on also turns the other on (this maps to the XML::Parser ParseParamEnts option). This may be fixed in the future, so don't rely on this behaviour. =back =head1 MISSING PARTS XML::Parser has no listed callbacks for the following events, which are therefore not presently generated (ways may be found in the future): * ignorable_whitespace * skipped_entity * start_entity / end_entity * resolve_entity Ways of signalling them are welcome. In addition to those, set_document_locator is not yet called. =head1 TODO - reuse Ken's tests and add more =head1 AUTHOR Robin Berjon; stolen from Ken Macleod, ken@bitsko.slc.ut.us, and with suggestions and feedback from perl-xml. Currently maintained by Bjoern Hoehrmann, L. =head1 COPYRIGHT AND LICENSE Copyright (c) 2001-2008 Robin Berjon. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO XML::Parser::PerlSAX =cut PK!u=99perl5/XML/SAX/Intro.podnu6$=head1 NAME XML::SAX::Intro - An Introduction to SAX Parsing with Perl =head1 Introduction XML::SAX is a new way to work with XML Parsers in Perl. In this article we'll discuss why you should be using SAX, why you should be using XML::SAX, and we'll see some of the finer implementation details. The text below assumes some familiarity with callback, or push based parsing, but if you are unfamiliar with these techniques then a good place to start is Kip Hampton's excellent series of articles on XML.com. =head1 Replacing XML::Parser The de-facto way of parsing XML under perl is to use Larry Wall and Clark Cooper's XML::Parser. This module is a Perl and XS wrapper around the expat XML parser library by James Clark. It has been a hugely successful project, but suffers from a couple of rather major flaws. Firstly it is a proprietary API, designed before the SAX API was conceived, which means that it is not easily replaceable by other streaming parsers. Secondly it's callbacks are subrefs. This doesn't sound like much of an issue, but unfortunately leads to code like: sub handle_start { my ($e, $el, %attrs) = @_; if ($el eq 'foo') { $e->{inside_foo}++; # BAD! $e is an XML::Parser::Expat object. } } As you can see, we're using the $e object to hold our state information, which is a bad idea because we don't own that object - we didn't create it. It's an internal object of XML::Parser, that happens to be a hashref. We could all too easily overwrite XML::Parser internal state variables by using this, or Clark could change it to an array ref (not that he would, because it would break so much code, but he could). The only way currently with XML::Parser to safely maintain state is to use a closure: my $state = MyState->new(); $parser->setHandlers(Start => sub { handle_start($state, @_) }); This closure traps the $state variable, which now gets passed as the first parameter to your callback. Unfortunately very few people use this technique, as it is not documented in the XML::Parser POD files. Another reason you might not want to use XML::Parser is because you need some feature that it doesn't provide (such as validation), or you might need to use a library that doesn't use expat, due to it not being installed on your system, or due to having a restrictive ISP. Using SAX allows you to work around these restrictions. =head1 Introducing SAX SAX stands for the Simple API for XML. And simple it really is. Constructing a SAX parser and passing events to handlers is done as simply as: use XML::SAX; use MySAXHandler; my $parser = XML::SAX::ParserFactory->parser( Handler => MySAXHandler->new ); $parser->parse_uri("foo.xml"); The important concept to grasp here is that SAX uses a factory class called XML::SAX::ParserFactory to create a new parser instance. The reason for this is so that you can support other underlying parser implementations for different feature sets. This is one thing that XML::Parser has always sorely lacked. In the code above we see the parse_uri method used, but we could have equally well called parse_file, parse_string, or parse(). Please see XML::SAX::Base for what these methods take as parameters, but don't be fooled into believing parse_file takes a filename. No, it takes a file handle, a glob, or a subclass of IO::Handle. Beware. SAX works very similarly to XML::Parser's default callback method, except it has one major difference: rather than setting individual callbacks, you create a new class in which to receive the callbacks. Each callback is called as a method call on an instance of that handler class. An example will best demonstrate this: package MySAXHandler; use base qw(XML::SAX::Base); sub start_document { my ($self, $doc) = @_; # process document start event } sub start_element { my ($self, $el) = @_; # process element start event } Now, when we instantiate this as above, and parse some XML with this as the handler, the methods start_document and start_element will be called as method calls, so this would be the equivalent of directly calling: $object->start_element($el); Notice how this is different to XML::Parser's calling style, which calls: start_element($e, $name, %attribs); It's the difference between function calling and method calling which allows you to subclass SAX handlers which contributes to SAX being a powerful solution. As you can see, unlike XML::Parser, we have to define a new package in which to do our processing (there are hacks you can do to make this uneccessary, but I'll leave figuring those out to the experts). The biggest benefit of this is that you maintain your own state variable ($self in the above example) thus freeing you of the concerns listed above. It is also an improvement in maintainability - you can place the code in a separate file if you wish to, and your callback methods are always called the same thing, rather than having to choose a suitable name for them as you had to with XML::Parser. This is an obvious win. SAX parsers are also very flexible in how you pass a handler to them. You can use a constructor parameter as we saw above, or we can pass the handler directly in the call to one of the parse methods: $parser->parse(Handler => $handler, Source => { SystemId => "foo.xml" }); # or... $parser->parse_file($fh, Handler => $handler); This flexibility allows for one parser to be used in many different scenarios throughout your script (though one shouldn't feel pressure to use this method, as parser construction is generally not a time consuming process). =head1 Callback Parameters The only other thing you need to know to understand basic SAX is the structure of the parameters passed to each of the callbacks. In XML::Parser, all parameters are passed as multiple options to the callbacks, so for example the Start callback would be called as my_start($e, $name, %attributes), and the PI callback would be called as my_processing_instruction($e, $target, $data). In SAX, every callback is passed a hash reference, containing entries that define our "node". The key callbacks and the structures they receive are: =head2 start_element The start_element handler is called whenever a parser sees an opening tag. It is passed an element structure consisting of: =over 4 =item LocalName The name of the element minus any namespace prefix it may have come with in the document. =item NamespaceURI The URI of the namespace associated with this element, or the empty string for none. =item Attributes A set of attributes as described below. =item Name The name of the element as it was seen in the document (i.e. including any prefix associated with it) =item Prefix The prefix used to qualify this element's namespace, or the empty string if none. =back The B are a hash reference, keyed by what we have called "James Clark" notation. This means that the attribute name has been expanded to include any associated namespace URI, and put together as {ns}name, where "ns" is the expanded namespace URI of the attribute if and only if the attribute had a prefix, and "name" is the LocalName of the attribute. The value of each entry in the attributes hash is another hash structure consisting of: =over 4 =item LocalName The name of the attribute minus any namespace prefix it may have come with in the document. =item NamespaceURI The URI of the namespace associated with this attribute. If the attribute had no prefix, then this consists of just the empty string. =item Name The attribute's name as it appeared in the document, including any namespace prefix. =item Prefix The prefix used to qualify this attribute's namepace, or the empty string if none. =item Value The value of the attribute. =back So a full example, as output by Data::Dumper might be: .... =head2 end_element The end_element handler is called either when a parser sees a closing tag, or after start_element has been called for an empty element (do note however that a parser may if it is so inclined call characters with an empty string when it sees an empty element. There is no simple way in SAX to determine if the parser in fact saw an empty element, a start and end element with no content.. The end_element handler receives exactly the same structure as start_element, minus the Attributes entry. One must note though that it should not be a reference to the same data as start_element receives, so you may change the values in start_element but this will not affect the values later seen by end_element. =head2 characters The characters callback may be called in serveral circumstances. The most obvious one is when seeing ordinary character data in the markup. But it is also called for text in a CDATA section, and is also called in other situations. A SAX parser has to make no guarantees whatsoever about how many times it may call characters for a stretch of text in an XML document - it may call once, or it may call once for every character in the text. In order to work around this it is often important for the SAX developer to use a bundling technique, where text is gathered up and processed in one of the other callbacks. This is not always necessary, but it is a worthwhile technique to learn, which we will cover in XML::SAX::Advanced (when I get around to writing it). The characters handler is called with a very simple structure - a hash reference consisting of just one entry: =over 4 =item Data The text data that was received. =back =head2 comment The comment callback is called for comment text. Unlike with C, the comment callback *must* be invoked just once for an entire comment string. It receives a single simple structure - a hash reference containing just one entry: =over 4 =item Data The text of the comment. =back =head2 processing_instruction The processing instruction handler is called for all processing instructions in the document. Note that these processing instructions may appear before the document root element, or after it, or anywhere where text and elements would normally appear within the document, according to the XML specification. The handler is passed a structure containing just two entries: =over 4 =item Target The target of the processing instrcution =item Data The text data in the processing instruction. Can be an empty string for a processing instruction that has no data element. For example E?wiggle?E is a perfectly valid processing instruction. =back =head1 Tip of the iceberg What we have discussed above is really the tip of the SAX iceberg. And so far it looks like there's not much of interest to SAX beyond what we have seen with XML::Parser. But it does go much further than that, I promise. People who hate Object Oriented code for the sake of it may be thinking here that creating a new package just to parse something is a waste when they've been parsing things just fine up to now using procedural code. But there's reason to all this madness. And that reason is SAX Filters. As you saw right at the very start, to let the parser know about our class, we pass it an instance of our class as the Handler to the parser. But now imagine what would happen if our class could also take a Handler option, and simply do some processing and pass on our data further down the line? That in a nutshell is how SAX filters work. It's Unix pipes for the 21st century! There are two downsides to this. Number 1 - writing SAX filters can be tricky. If you look into the future and read the advanced tutorial I'm writing, you'll see that Handler can come in several shapes and sizes. So making sure your filter does the right thing can be tricky. Secondly, constructing complex filter chains can be difficult, and simple thinking tells us that we only get one pass at our document, when often we'll need more than that. Luckily though, those downsides have been fixed by the release of two very cool modules. What's even better is that I didn't write either of them! The first module is XML::SAX::Base. This is a VITAL SAX module that acts as a base class for all SAX parsers and filters. It provides an abstraction away from calling the handler methods, that makes sure your filter or parser does the right thing, and it does it FAST. So, if you ever need to write a SAX filter, which if you're processing XML -> XML, or XML -> HTML, then you probably do, then you need to be writing it as a subclass of XML::SAX::Base. Really - this is advice not to ignore lightly. I will not go into the details of writing a SAX filter here. Kip Hampton, the author of XML::SAX::Base has covered this nicely in his article on XML.com here . To construct SAX pipelines, Barrie Slaymaker, a long time Perl hacker whose modules you will probably have heard of or used, wrote a very clever module called XML::SAX::Machines. This combines some really clever SAX filter-type modules, with a construction toolkit for filters that makes building pipelines easy. But before we see how it makes things easy, first lets see how tricky it looks to build complex SAX filter pipelines. use XML::SAX::ParserFactory; use XML::Filter::Filter1; use XML::Filter::Filter2; use XML::SAX::Writer; my $output_string; my $writer = XML::SAX::Writer->new(Output => \$output_string); my $filter2 = XML::SAX::Filter2->new(Handler => $writer); my $filter1 = XML::SAX::Filter1->new(Handler => $filter2); my $parser = XML::SAX::ParserFactory->parser(Handler => $filter1); $parser->parse_uri("foo.xml"); This is a lot easier with XML::SAX::Machines: use XML::SAX::Machines qw(Pipeline); my $output_string; my $parser = Pipeline( XML::SAX::Filter1 => XML::SAX::Filter2 => \$output_string ); $parser->parse_uri("foo.xml"); One of the main benefits of XML::SAX::Machines is that the pipelines are constructed in natural order, rather than the reverse order we saw with manual pipeline construction. XML::SAX::Machines takes care of all the internals of pipe construction, providing you at the end with just a parser you can use (and you can re-use the same parser as many times as you need to). Just a final tip. If you ever get stuck and are confused about what is being passed from one SAX filter or parser to the next, then Devel::TraceSAX will come to your rescue. This perl debugger plugin will allow you to dump the SAX stream of events as it goes by. Usage is really very simple just call your perl script that uses SAX as follows: $ perl -d:TraceSAX And preferably pipe the output to a pager of some sort, such as more or less. The output is extremely verbose, but should help clear some issues up. =head1 AUTHOR Matt Sergeant, matt@sergeant.org $Id$ =cut PK!9ms perl5/XML/SAX/Exception.pmnu6$package XML::SAX::Exception; $XML::SAX::Exception::VERSION = '1.09'; use strict; use overload '""' => "stringify", 'fallback' => 1; use vars qw($StackTrace); use Carp; $StackTrace = $ENV{XML_DEBUG} || 0; # Other exception classes: @XML::SAX::Exception::NotRecognized::ISA = ('XML::SAX::Exception'); @XML::SAX::Exception::NotSupported::ISA = ('XML::SAX::Exception'); @XML::SAX::Exception::Parse::ISA = ('XML::SAX::Exception'); sub throw { my $class = shift; if (ref($class)) { die $class; } die $class->new(@_); } sub new { my $class = shift; my %opts = @_; confess "Invalid options: " . join(', ', keys %opts) unless exists $opts{Message}; bless { ($StackTrace ? (StackTrace => stacktrace()) : ()), %opts }, $class; } sub stringify { my $self = shift; local $^W; my $error; if (exists $self->{LineNumber}) { $error = $self->{Message} . " [Ln: " . $self->{LineNumber} . ", Col: " . $self->{ColumnNumber} . "]"; } else { $error = $self->{Message}; } if ($StackTrace) { $error .= stackstring($self->{StackTrace}); } $error .= "\n"; return $error; } sub stacktrace { my $i = 2; my @fulltrace; while (my @trace = caller($i++)) { my %hash; @hash{qw(Package Filename Line)} = @trace[0..2]; push @fulltrace, \%hash; } return \@fulltrace; } sub stackstring { my $stacktrace = shift; my $string = "\nFrom:\n"; foreach my $current (@$stacktrace) { $string .= $current->{Filename} . " Line: " . $current->{Line} . "\n"; } return $string; } 1; __END__ =head1 NAME XML::SAX::Exception - Exception classes for XML::SAX =head1 SYNOPSIS throw XML::SAX::Exception::NotSupported( Message => "The foo feature is not supported", ); =head1 DESCRIPTION This module is the base class for all SAX Exceptions, those defined in the spec as well as those that one may create for one's own SAX errors. There are three subclasses included, corresponding to those of the SAX spec: XML::SAX::Exception::NotSupported XML::SAX::Exception::NotRecognized XML::SAX::Exception::Parse Use them wherever you want, and as much as possible when you encounter such errors. SAX is meant to use exceptions as much as possible to flag problems. =head1 CREATING NEW EXCEPTION CLASSES All you need to do to create a new exception class is: @XML::SAX::Exception::MyException::ISA = ('XML::SAX::Exception') The given package doesn't need to exist, it'll behave correctly this way. If your exception refines an existing exception class, then you may also inherit from that instead of from the base class. =head1 THROWING EXCEPTIONS This is as simple as exemplified in the SYNOPSIS. In fact, there's nothing more to know. All you have to do is: throw XML::SAX::Exception::MyException( Message => 'Something went wrong' ); and voila, you've thrown an exception which can be caught in an eval block. =cut PK!IWOkPkPperl5/XML/SAX/PurePerl.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; use vars qw/$VERSION/; $VERSION = '1.02'; use XML::SAX::PurePerl::Productions qw($NameChar $SingleChar); use XML::SAX::PurePerl::Reader; use XML::SAX::PurePerl::EncodingDetect (); use XML::SAX::Exception; use XML::SAX::PurePerl::DocType (); use XML::SAX::PurePerl::DTDDecls (); use XML::SAX::PurePerl::XMLDecl (); use XML::SAX::DocumentLocator (); use XML::SAX::Base (); use XML::SAX qw(Namespaces); use XML::NamespaceSupport (); use IO::File; if ($] < 5.006) { require XML::SAX::PurePerl::NoUnicodeExt; } else { require XML::SAX::PurePerl::UnicodeExt; } use vars qw(@ISA); @ISA = ('XML::SAX::Base'); my %int_ents = ( amp => '&', lt => '<', gt => '>', quot => '"', apos => "'", ); my $xmlns_ns = "http://www.w3.org/2000/xmlns/"; my $xml_ns = "http://www.w3.org/XML/1998/namespace"; use Carp; sub _parse_characterstream { my $self = shift; my ($fh) = @_; confess("CharacterStream is not yet correctly implemented"); my $reader = XML::SAX::PurePerl::Reader::Stream->new($fh); return $self->_parse($reader); } sub _parse_bytestream { my $self = shift; my ($fh) = @_; my $reader = XML::SAX::PurePerl::Reader::Stream->new($fh); return $self->_parse($reader); } sub _parse_string { my $self = shift; my ($str) = @_; my $reader = XML::SAX::PurePerl::Reader::String->new($str); return $self->_parse($reader); } sub _parse_systemid { my $self = shift; my ($uri) = @_; my $reader = XML::SAX::PurePerl::Reader::URI->new($uri); return $self->_parse($reader); } sub _parse { my ($self, $reader) = @_; $reader->public_id($self->{ParseOptions}{Source}{PublicId}); $reader->system_id($self->{ParseOptions}{Source}{SystemId}); $self->{NSHelper} = XML::NamespaceSupport->new({xmlns => 1}); $self->set_document_locator( XML::SAX::DocumentLocator->new( sub { $reader->public_id }, sub { $reader->system_id }, sub { $reader->line }, sub { $reader->column }, sub { $reader->get_encoding }, sub { $reader->get_xml_version }, ), ); $self->start_document({}); if (defined $self->{ParseOptions}{Source}{Encoding}) { $reader->set_encoding($self->{ParseOptions}{Source}{Encoding}); } else { $self->encoding_detect($reader); } # parse a document $self->document($reader); return $self->end_document({}); } sub parser_error { my $self = shift; my ($error, $reader) = @_; # warn("parser error: $error from ", $reader->line, " : ", $reader->column, "\n"); my $exception = XML::SAX::Exception::Parse->new( Message => $error, ColumnNumber => $reader->column, LineNumber => $reader->line, PublicId => $reader->public_id, SystemId => $reader->system_id, ); $self->fatal_error($exception); $exception->throw; } sub document { my ($self, $reader) = @_; # document ::= prolog element Misc* $self->prolog($reader); $self->element($reader) || $self->parser_error("Document requires an element", $reader); while(length($reader->data)) { $self->Misc($reader) || $self->parser_error("Only Comments, PIs and whitespace allowed at end of document", $reader); } } sub prolog { my ($self, $reader) = @_; $self->XMLDecl($reader); # consume all misc bits 1 while($self->Misc($reader)); if ($self->doctypedecl($reader)) { while (length($reader->data)) { $self->Misc($reader) || last; } } } sub element { my ($self, $reader) = @_; return 0 unless $reader->match('<'); my $name = $self->Name($reader) || $self->parser_error("Invalid element name", $reader); my %attribs; while( my ($k, $v) = $self->Attribute($reader) ) { $attribs{$k} = $v; } my $have_namespaces = $self->get_feature(Namespaces); # Namespace processing $self->{NSHelper}->push_context; my @new_ns; # my %attrs = @attribs; # while (my ($k,$v) = each %attrs) { if ($have_namespaces) { while ( my ($k, $v) = each %attribs ) { if ($k =~ m/^xmlns(:(.*))?$/) { my $prefix = $2 || ''; $self->{NSHelper}->declare_prefix($prefix, $v); my $ns = { Prefix => $prefix, NamespaceURI => $v, }; push @new_ns, $ns; $self->SUPER::start_prefix_mapping($ns); } } } # Create element object and fire event my %attrib_hash; while (my ($name, $value) = each %attribs ) { # TODO normalise value here my ($ns, $prefix, $lname); if ($have_namespaces) { ($ns, $prefix, $lname) = $self->{NSHelper}->process_attribute_name($name); } $ns ||= ''; $prefix ||= ''; $lname ||= ''; $attrib_hash{"{$ns}$lname"} = { Name => $name, LocalName => $lname, Prefix => $prefix, NamespaceURI => $ns, Value => $value, }; } %attribs = (); # lose the memory since we recurse deep my ($ns, $prefix, $lname); if ($self->get_feature(Namespaces)) { ($ns, $prefix, $lname) = $self->{NSHelper}->process_element_name($name); } else { $lname = $name; } $ns ||= ''; $prefix ||= ''; $lname ||= ''; # Process remainder of start_element $self->skip_whitespace($reader); my $have_content; my $data = $reader->data(2); if ($data =~ /^\/>/) { $reader->move_along(2); } else { $data =~ /^>/ or $self->parser_error("No close element tag", $reader); $reader->move_along(1); $have_content++; } my $el = { Name => $name, LocalName => $lname, Prefix => $prefix, NamespaceURI => $ns, Attributes => \%attrib_hash, }; $self->start_element($el); # warn("($name\n"); if ($have_content) { $self->content($reader); my $data = $reader->data(2); $data =~ /^<\// or $self->parser_error("No close tag marker", $reader); $reader->move_along(2); my $end_name = $self->Name($reader); $end_name eq $name || $self->parser_error("End tag mismatch ($end_name != $name)", $reader); $self->skip_whitespace($reader); $reader->match('>') or $self->parser_error("No close '>' on end tag", $reader); } my %end_el = %$el; delete $end_el{Attributes}; $self->end_element(\%end_el); for my $ns (@new_ns) { $self->end_prefix_mapping($ns); } $self->{NSHelper}->pop_context; return 1; } sub content { my ($self, $reader) = @_; while (1) { $self->CharData($reader); my $data = $reader->data(2); if ($data =~ /^<\//) { return 1; } elsif ($data =~ /^&/) { $self->Reference($reader) or $self->parser_error("bare & not allowed in content", $reader); next; } elsif ($data =~ /^CDSect($reader) or $self->Comment($reader)) and next; } elsif ($data =~ /^<\?/) { $self->PI($reader) and next; } elsif ($data =~ /^element($reader) and next; } last; } return 1; } sub CDSect { my ($self, $reader) = @_; my $data = $reader->data(9); return 0 unless $data =~ /^move_along(9); $self->start_cdata({}); $data = $reader->data; while (1) { $self->parser_error("EOF looking for CDATA section end", $reader) unless length($data); if ($data =~ /^(.*?)\]\]>/s) { my $chars = $1; $reader->move_along(length($chars) + 3); $self->characters({Data => $chars}); last; } else { $self->characters({Data => $data}); $reader->move_along(length($data)); $data = $reader->data; } } $self->end_cdata({}); return 1; } sub CharData { my ($self, $reader) = @_; my $data = $reader->data; while (1) { return unless length($data); if ($data =~ /^([^<&]*)[<&]/s) { my $chars = $1; $self->parser_error("String ']]>' not allowed in character data", $reader) if $chars =~ /\]\]>/; $reader->move_along(length($chars)); $self->characters({Data => $chars}) if length($chars); last; } else { $self->characters({Data => $data}); $reader->move_along(length($data)); $data = $reader->data; } } } sub Misc { my ($self, $reader) = @_; if ($self->Comment($reader)) { return 1; } elsif ($self->PI($reader)) { return 1; } elsif ($self->skip_whitespace($reader)) { return 1; } return 0; } sub Reference { my ($self, $reader) = @_; return 0 unless $reader->match('&'); my $data = $reader->data; # Fetch more data if we have an incomplete numeric reference if ($data =~ /^(#\d*|#x[0-9a-fA-F]*)$/) { $data = $reader->data(length($data) + 6); } if ($data =~ /^#x([0-9a-fA-F]+);/) { my $ref = $1; $reader->move_along(length($ref) + 3); my $char = chr_ref(hex($ref)); $self->parser_error("Character reference &#$ref; refers to an illegal XML character ($char)", $reader) unless $char =~ /$SingleChar/o; $self->characters({ Data => $char }); return 1; } elsif ($data =~ /^#([0-9]+);/) { my $ref = $1; $reader->move_along(length($ref) + 2); my $char = chr_ref($ref); $self->parser_error("Character reference &#$ref; refers to an illegal XML character ($char)", $reader) unless $char =~ /$SingleChar/o; $self->characters({ Data => $char }); return 1; } else { # EntityRef my $name = $self->Name($reader) || $self->parser_error("Invalid name in entity", $reader); $reader->match(';') or $self->parser_error("No semi-colon found after entity name", $reader); # warn("got entity: \&$name;\n"); # expand it if ($self->_is_entity($name)) { if ($self->_is_external($name)) { my $value = $self->_get_entity($name); my $ent_reader = XML::SAX::PurePerl::Reader::URI->new($value); $self->encoding_detect($ent_reader); $self->extParsedEnt($ent_reader); } else { my $value = $self->_stringify_entity($name); my $ent_reader = XML::SAX::PurePerl::Reader::String->new($value); $self->content($ent_reader); } return 1; } elsif ($name =~ /^(?:amp|gt|lt|quot|apos)$/) { $self->characters({ Data => $int_ents{$name} }); return 1; } else { $self->parser_error("Undeclared entity", $reader); } } } sub AttReference { my ($self, $name, $reader) = @_; if ($name =~ /^#x([0-9a-fA-F]+)$/) { my $chr = chr_ref(hex($1)); $chr =~ /$SingleChar/o or $self->parser_error("Character reference '&$name;' refers to an illegal XML character", $reader); return $chr; } elsif ($name =~ /^#([0-9]+)$/) { my $chr = chr_ref($1); $chr =~ /$SingleChar/o or $self->parser_error("Character reference '&$name;' refers to an illegal XML character", $reader); return $chr; } else { if ($self->_is_entity($name)) { if ($self->_is_external($name)) { $self->parser_error("No external entity references allowed in attribute values", $reader); } else { my $value = $self->_stringify_entity($name); return $value; } } elsif ($name =~ /^(?:amp|lt|gt|quot|apos)$/) { return $int_ents{$name}; } else { $self->parser_error("Undeclared entity '$name'", $reader); } } } sub extParsedEnt { my ($self, $reader) = @_; $self->TextDecl($reader); $self->content($reader); } sub _is_external { my ($self, $name) = @_; # TODO: Fix this to use $reader to store the entities perhaps. if ($self->{ParseOptions}{external_entities}{$name}) { return 1; } return ; } sub _is_entity { my ($self, $name) = @_; # TODO: ditto above if (exists $self->{ParseOptions}{entities}{$name}) { return 1; } return 0; } sub _stringify_entity { my ($self, $name) = @_; # TODO: ditto above if (exists $self->{ParseOptions}{expanded_entity}{$name}) { return $self->{ParseOptions}{expanded_entity}{$name}; } # expand my $reader = XML::SAX::PurePerl::Reader::URI->new($self->{ParseOptions}{entities}{$name}); my $ent = ''; while(1) { my $data = $reader->data; $ent .= $data; $reader->move_along(length($data)) or last; } return $self->{ParseOptions}{expanded_entity}{$name} = $ent; } sub _get_entity { my ($self, $name) = @_; # TODO: ditto above return $self->{ParseOptions}{entities}{$name}; } sub skip_whitespace { my ($self, $reader) = @_; my $data = $reader->data; my $found = 0; while ($data =~ s/^([\x20\x0A\x0D\x09]*)//) { last unless length($1); $found++; $reader->move_along(length($1)); $data = $reader->data; } return $found; } sub Attribute { my ($self, $reader) = @_; $self->skip_whitespace($reader) || return; my $data = $reader->data(2); return if $data =~ /^\/?>/; if (my $name = $self->Name($reader)) { $self->skip_whitespace($reader); $reader->match('=') or $self->parser_error("No '=' in Attribute", $reader); $self->skip_whitespace($reader); my $value = $self->AttValue($reader); if (!$self->cdata_attrib($name)) { $value =~ s/^\x20*//; # discard leading spaces $value =~ s/\x20*$//; # discard trailing spaces $value =~ s/ {1,}/ /g; # all >1 space to single space } return $name, $value; } return; } sub cdata_attrib { # TODO implement this! return 1; } sub AttValue { my ($self, $reader) = @_; my $quote = $self->quote($reader); my $value = ''; while (1) { my $data = $reader->data; $self->parser_error("EOF found while looking for the end of attribute value", $reader) unless length($data); if ($data =~ /^([^$quote]*)$quote/) { $reader->move_along(length($1) + 1); $value .= $1; last; } else { $value .= $data; $reader->move_along(length($data)); } } if ($value =~ /parser_error("< character not allowed in attribute values", $reader); } $value =~ s/[\x09\x0A\x0D]/\x20/g; $value =~ s/&(#(x[0-9a-fA-F]+)|#([0-9]+)|\w+);/$self->AttReference($1, $reader)/geo; return $value; } sub Comment { my ($self, $reader) = @_; my $data = $reader->data(4); if ($data =~ /^/s) { $comment_str .= $1; $self->parser_error("Invalid comment (dash)", $reader) if $comment_str =~ /-$/; $reader->move_along(length($1) + 3); last; } else { $comment_str .= $data; $reader->move_along(length($data)); } } $self->comment({ Data => $comment_str }); return 1; } return 0; } sub PI { my ($self, $reader) = @_; my $data = $reader->data(2); if ($data =~ /^<\?/) { $reader->move_along(2); my ($target); $target = $self->Name($reader) || $self->parser_error("PI has no target", $reader); my $pi_data = ''; if ($self->skip_whitespace($reader)) { while (1) { my $data = $reader->data; $self->parser_error("End of data seen while looking for close PI marker", $reader) unless length($data); if ($data =~ /^(.*?)\?>/s) { $pi_data .= $1; $reader->move_along(length($1) + 2); last; } else { $pi_data .= $data; $reader->move_along(length($data)); } } } else { my $data = $reader->data(2); $data =~ /^\?>/ or $self->parser_error("PI closing sequence not found", $reader); $reader->move_along(2); } $self->processing_instruction({ Target => $target, Data => $pi_data }); return 1; } return 0; } sub Name { my ($self, $reader) = @_; my $name = ''; while(1) { my $data = $reader->data; return unless length($data); $data =~ /^([^\s>\/&\?;=<\)\(\[\],\%\#\!\*\|]*)/ or return; $name .= $1; my $len = length($1); $reader->move_along($len); last if ($len != length($data)); } return unless length($name); $name =~ /$NameChar/o or $self->parser_error("Name <$name> does not match NameChar production", $reader); return $name; } sub quote { my ($self, $reader) = @_; my $data = $reader->data; $data =~ /^(['"])/ or $self->parser_error("Invalid quote token", $reader); $reader->move_along(1); return $1; } 1; __END__ =head1 NAME XML::SAX::PurePerl - Pure Perl XML Parser with SAX2 interface =head1 SYNOPSIS use XML::Handler::Foo; use XML::SAX::PurePerl; my $handler = XML::Handler::Foo->new(); my $parser = XML::SAX::PurePerl->new(Handler => $handler); $parser->parse_uri("myfile.xml"); =head1 DESCRIPTION This module implements an XML parser in pure perl. It is written around the upcoming perl 5.8's unicode support and support for multiple document encodings (using the PerlIO layer), however it has been ported to work with ASCII/UTF8 documents under lower perl versions. The SAX2 API is described in detail at http://sourceforge.net/projects/perl-xml/, in the CVS archive, under libxml-perl/docs. Hopefully those documents will be in a better location soon. Please refer to the SAX2 documentation for how to use this module - it is merely a front end to SAX2, and implements nothing that is not in that spec (or at least tries not to - please email me if you find errors in this implementation). =head1 BUGS XML::SAX::PurePerl is B. Very slow. I suggest you use something else in fact. However it is great as a fallback parser for XML::SAX, where the user might not be able to install an XS based parser or C library. Currently lots, probably. At the moment the weakest area is parsing DOCTYPE declarations, though the code is in place to start doing this. Also parsing parameter entity references is causing me much confusion, since it's not exactly what I would call trivial, or well documented in the XML grammar. XML documents with internal subsets are likely to fail. I am however trying to work towards full conformance using the Oasis test suite. =head1 AUTHOR Matt Sergeant, matt@sergeant.org. Copyright 2001. Please report all bugs to the Perl-XML mailing list at perl-xml@listserv.activestate.com. =head1 LICENSE This is free software. You may use it or redistribute it under the same terms as Perl 5.7.2 itself. =cut PK!'  'perl5/XML/SAX/PurePerl/Reader/Stream.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader::Stream; use strict; use vars qw(@ISA); use XML::SAX::PurePerl::Reader qw( EOF BUFFER LINE COLUMN ENCODING XML_VERSION ); use XML::SAX::Exception; @ISA = ('XML::SAX::PurePerl::Reader'); # subclassed by adding 1 to last element use constant FH => 8; use constant BUFFER_SIZE => 4096; sub new { my $class = shift; my $ioref = shift; XML::SAX::PurePerl::Reader::set_raw_stream($ioref); my @parts; @parts[FH, LINE, COLUMN, BUFFER, EOF, XML_VERSION] = ($ioref, 1, 0, '', 0, '1.0'); return bless \@parts, $class; } sub read_more { my $self = shift; my $buf; my $bytesread = read($self->[FH], $buf, BUFFER_SIZE); if ($bytesread) { $self->[BUFFER] .= $buf; return 1; } elsif (defined($bytesread)) { $self->[EOF]++; return 0; } else { throw XML::SAX::Exception::Parse( Message => "Error reading from filehandle: $!", ); } } sub move_along { my $self = shift; my $discarded = substr($self->[BUFFER], 0, $_[0], ''); # Wish I could skip this lot - tells us where we are in the file my $lines = $discarded =~ tr/\n//; $self->[LINE] += $lines; if ($lines) { $discarded =~ /\n([^\n]*)$/; $self->[COLUMN] = length($1); } else { $self->[COLUMN] += $_[0]; } } sub set_encoding { my $self = shift; my ($encoding) = @_; # warn("set encoding to: $encoding\n"); XML::SAX::PurePerl::Reader::switch_encoding_stream($self->[FH], $encoding); XML::SAX::PurePerl::Reader::switch_encoding_string($self->[BUFFER], $encoding); $self->[ENCODING] = $encoding; } sub bytepos { my $self = shift; tell($self->[FH]); } 1; PK!`$$perl5/XML/SAX/PurePerl/Reader/URI.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader::URI; use strict; use XML::SAX::PurePerl::Reader; use File::Temp qw(tempfile); use Symbol; ## NOTE: This is *not* a subclass of Reader. It just returns Stream or String ## Reader objects depending on what it's capabilities are. sub new { my $class = shift; my $uri = shift; # request the URI if (-e $uri && -f _) { my $fh = gensym; open($fh, $uri) || die "Cannot open file $uri : $!"; return XML::SAX::PurePerl::Reader::Stream->new($fh); } elsif ($uri =~ /^file:(.*)$/ && -e $1 && -f _) { my $file = $1; my $fh = gensym; open($fh, $file) || die "Cannot open file $file : $!"; return XML::SAX::PurePerl::Reader::Stream->new($fh); } else { # request URI, return String reader require LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->agent("Perl/XML/SAX/PurePerl/1.0 " . $ua->agent); my $req = HTTP::Request->new(GET => $uri); my $fh = tempfile(); my $callback = sub { my ($data, $response, $protocol) = @_; print $fh $data; }; my $res = $ua->request($req, $callback, 4096); if ($res->is_success) { seek($fh, 0, 0); return XML::SAX::PurePerl::Reader::Stream->new($fh); } else { die "LWP Request Failed"; } } } 1; PK! GKK-perl5/XML/SAX/PurePerl/Reader/NoUnicodeExt.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader; use strict; sub set_raw_stream { # no-op } sub switch_encoding_stream { my ($fh, $encoding) = @_; throw XML::SAX::Exception::Parse ( Message => "Only ASCII encoding allowed without perl 5.7.2 or higher. You tried: $encoding", ) if $encoding !~ /(ASCII|UTF\-?8)/i; } sub switch_encoding_string { my (undef, $encoding) = @_; throw XML::SAX::Exception::Parse ( Message => "Only ASCII encoding allowed without perl 5.7.2 or higher. You tried: $encoding", ) if $encoding !~ /(ASCII|UTF\-?8)/i; } 1; PK!LkFF+perl5/XML/SAX/PurePerl/Reader/UnicodeExt.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader; use strict; use Encode (); sub set_raw_stream { my ($fh) = @_; binmode($fh, ":bytes"); } sub switch_encoding_stream { my ($fh, $encoding) = @_; binmode($fh, ":encoding($encoding)"); } sub switch_encoding_string { $_[0] = Encode::decode($_[1], $_[0]); } 1; PK!'8'perl5/XML/SAX/PurePerl/Reader/String.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader::String; use strict; use vars qw(@ISA); use XML::SAX::PurePerl::Reader qw( LINE COLUMN BUFFER ENCODING EOF ); @ISA = ('XML::SAX::PurePerl::Reader'); use constant DISCARDED => 8; use constant STRING => 9; use constant USED => 10; use constant CHUNK_SIZE => 2048; sub new { my $class = shift; my $string = shift; my @parts; @parts[BUFFER, EOF, LINE, COLUMN, DISCARDED, STRING, USED] = ('', 0, 1, 0, 0, $string, 0); return bless \@parts, $class; } sub read_more () { my $self = shift; if ($self->[USED] >= length($self->[STRING])) { $self->[EOF]++; return 0; } my $bytes = CHUNK_SIZE; if ($bytes > (length($self->[STRING]) - $self->[USED])) { $bytes = (length($self->[STRING]) - $self->[USED]); } $self->[BUFFER] .= substr($self->[STRING], $self->[USED], $bytes); $self->[USED] += $bytes; return 1; } sub move_along { my($self, $bytes) = @_; my $discarded = substr($self->[BUFFER], 0, $bytes, ''); $self->[DISCARDED] += length($discarded); # Wish I could skip this lot - tells us where we are in the file my $lines = $discarded =~ tr/\n//; $self->[LINE] += $lines; if ($lines) { $discarded =~ /\n([^\n]*)$/; $self->[COLUMN] = length($1); } else { $self->[COLUMN] += $_[0]; } } sub set_encoding { my $self = shift; my ($encoding) = @_; XML::SAX::PurePerl::Reader::switch_encoding_string($self->[BUFFER], $encoding, "utf-8"); $self->[ENCODING] = $encoding; } sub bytepos { my $self = shift; $self->[DISCARDED]; } 1; PK!6:o`B`B"perl5/XML/SAX/PurePerl/DTDDecls.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; use XML::SAX::PurePerl::Productions qw($SingleChar); sub elementdecl { my ($self, $reader) = @_; my $data = $reader->data(9); return 0 unless $data =~ /^move_along(9); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after ELEMENT declaration", $reader); my $name = $self->Name($reader); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after ELEMENT's name", $reader); $self->contentspec($reader, $name); $self->skip_whitespace($reader); $reader->match('>') or $self->parser_error("Closing angle bracket not found on ELEMENT declaration", $reader); return 1; } sub contentspec { my ($self, $reader, $name) = @_; my $data = $reader->data(5); my $model; if ($data =~ /^EMPTY/) { $reader->move_along(5); $model = 'EMPTY'; } elsif ($data =~ /^ANY/) { $reader->move_along(3); $model = 'ANY'; } else { $model = $self->Mixed_or_children($reader); } if ($model) { # call SAX callback now. $self->element_decl({Name => $name, Model => $model}); return 1; } $self->parser_error("contentspec not found in ELEMENT declaration", $reader); } sub Mixed_or_children { my ($self, $reader) = @_; my $data = $reader->data(8); $data =~ /^\(/ or return; # $self->parser_error("No opening bracket in Mixed or children", $reader); if ($data =~ /^\(\s*\#PCDATA/) { $reader->match('('); $self->skip_whitespace($reader); $reader->move_along(7); my $model = $self->Mixed($reader); return $model; } # not matched - must be Children return $self->children($reader); } # Mixed ::= ( '(' S* PCDATA ( S* '|' S* QName )* S* ')' '*' ) # | ( '(' S* PCDATA S* ')' ) sub Mixed { my ($self, $reader) = @_; # Mixed_or_children already matched '(' S* '#PCDATA' my $model = '(#PCDATA'; $self->skip_whitespace($reader); my %seen; while (1) { last unless $reader->match('|'); $self->skip_whitespace($reader); my $name = $self->Name($reader) || $self->parser_error("No 'Name' after Mixed content '|'", $reader); if ($seen{$name}) { $self->parser_error("Element '$name' has already appeared in this group", $reader); } $seen{$name}++; $model .= "|$name"; $self->skip_whitespace($reader); } $reader->match(')') || $self->parser_error("no closing bracket on mixed content", $reader); $model .= ")"; if ($reader->match('*')) { $model .= "*"; } return $model; } # [[47]] Children ::= ChoiceOrSeq Cardinality? # [[48]] Cp ::= ( QName | ChoiceOrSeq ) Cardinality? # ChoiceOrSeq ::= '(' S* Cp ( Choice | Seq )? S* ')' # [[49]] Choice ::= ( S* '|' S* Cp )+ # [[50]] Seq ::= ( S* ',' S* Cp )+ # // Children ::= (Choice | Seq) Cardinality? # // Cp ::= ( QName | Choice | Seq) Cardinality? # // Choice ::= '(' S* Cp ( S* '|' S* Cp )+ S* ')' # // Seq ::= '(' S* Cp ( S* ',' S* Cp )* S* ')' # [[51]] Mixed ::= ( '(' S* PCDATA ( S* '|' S* QName )* S* ')' MixedCardinality ) # | ( '(' S* PCDATA S* ')' ) # Cardinality ::= '?' | '+' | '*' # MixedCardinality ::= '*' sub children { my ($self, $reader) = @_; return $self->ChoiceOrSeq($reader) . $self->Cardinality($reader); } sub ChoiceOrSeq { my ($self, $reader) = @_; $reader->match('(') or $self->parser_error("choice/seq contains no opening bracket", $reader); my $model = '('; $self->skip_whitespace($reader); $model .= $self->Cp($reader); if (my $choice = $self->Choice($reader)) { $model .= $choice; } else { $model .= $self->Seq($reader); } $self->skip_whitespace($reader); $reader->match(')') or $self->parser_error("choice/seq contains no closing bracket", $reader); $model .= ')'; return $model; } sub Cardinality { my ($self, $reader) = @_; # cardinality is always optional my $data = $reader->data; if ($data =~ /^([\?\+\*])/) { $reader->move_along(1); return $1; } return ''; } sub Cp { my ($self, $reader) = @_; my $model; my $name = eval { if (my $name = $self->Name($reader)) { return $name . $self->Cardinality($reader); } }; return $name if defined $name; return $self->ChoiceOrSeq($reader) . $self->Cardinality($reader); } sub Choice { my ($self, $reader) = @_; my $model = ''; $self->skip_whitespace($reader); while ($reader->match('|')) { $self->skip_whitespace($reader); $model .= '|'; $model .= $self->Cp($reader); $self->skip_whitespace($reader); } return $model; } sub Seq { my ($self, $reader) = @_; my $model = ''; $self->skip_whitespace($reader); while ($reader->match(',')) { $self->skip_whitespace($reader); my $cp = $self->Cp($reader); if ($cp) { $model .= ','; $model .= $cp; } $self->skip_whitespace($reader); } return $model; } sub AttlistDecl { my ($self, $reader) = @_; my $data = $reader->data(9); if ($data =~ /^move_along(9); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after ATTLIST declaration", $reader); my $name = $self->Name($reader); $self->AttDefList($reader, $name); $self->skip_whitespace($reader); $reader->match('>') or $self->parser_error("Closing angle bracket not found on ATTLIST declaration", $reader); return 1; } return 0; } sub AttDefList { my ($self, $reader, $name) = @_; 1 while $self->AttDef($reader, $name); } sub AttDef { my ($self, $reader, $el_name) = @_; $self->skip_whitespace($reader) || return 0; my $att_name = $self->Name($reader) || return 0; $self->skip_whitespace($reader) || $self->parser_error("No whitespace after Name in attribute definition", $reader); my $att_type = $self->AttType($reader); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after AttType in attribute definition", $reader); my ($mode, $value) = $self->DefaultDecl($reader); # fire SAX event here! $self->attribute_decl({ eName => $el_name, aName => $att_name, Type => $att_type, Mode => $mode, Value => $value, }); return 1; } sub AttType { my ($self, $reader) = @_; return $self->StringType($reader) || $self->TokenizedType($reader) || $self->EnumeratedType($reader) || $self->parser_error("Can't match AttType", $reader); } sub StringType { my ($self, $reader) = @_; my $data = $reader->data(5); return unless $data =~ /^CDATA/; $reader->move_along(5); return 'CDATA'; } sub TokenizedType { my ($self, $reader) = @_; my $data = $reader->data(8); if ($data =~ /^(IDREFS?|ID|ENTITIES|ENTITY|NMTOKENS?)/) { $reader->move_along(length($1)); return $1; } return; } sub EnumeratedType { my ($self, $reader) = @_; return $self->NotationType($reader) || $self->Enumeration($reader); } sub NotationType { my ($self, $reader) = @_; my $data = $reader->data(8); return unless $data =~ /^NOTATION/; $reader->move_along(8); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after NOTATION", $reader); $reader->match('(') or $self->parser_error("No opening bracket in notation section", $reader); $self->skip_whitespace($reader); my $model = 'NOTATION ('; my $name = $self->Name($reader) || $self->parser_error("No name in notation section", $reader); $model .= $name; $self->skip_whitespace($reader); $data = $reader->data; while ($data =~ /^\|/) { $reader->move_along(1); $model .= '|'; $self->skip_whitespace($reader); my $name = $self->Name($reader) || $self->parser_error("No name in notation section", $reader); $model .= $name; $self->skip_whitespace($reader); $data = $reader->data; } $data =~ /^\)/ or $self->parser_error("No closing bracket in notation section", $reader); $reader->move_along(1); $model .= ')'; return $model; } sub Enumeration { my ($self, $reader) = @_; return unless $reader->match('('); $self->skip_whitespace($reader); my $model = '('; my $nmtoken = $self->Nmtoken($reader) || $self->parser_error("No Nmtoken in enumerated declaration", $reader); $model .= $nmtoken; $self->skip_whitespace($reader); my $data = $reader->data; while ($data =~ /^\|/) { $model .= '|'; $reader->move_along(1); $self->skip_whitespace($reader); my $nmtoken = $self->Nmtoken($reader) || $self->parser_error("No Nmtoken in enumerated declaration", $reader); $model .= $nmtoken; $self->skip_whitespace($reader); $data = $reader->data; } $data =~ /^\)/ or $self->parser_error("No closing bracket in enumerated declaration", $reader); $reader->move_along(1); $model .= ')'; return $model; } sub Nmtoken { my ($self, $reader) = @_; return $self->Name($reader); } sub DefaultDecl { my ($self, $reader) = @_; my $data = $reader->data(9); if ($data =~ /^(\#REQUIRED|\#IMPLIED)/) { $reader->move_along(length($1)); return $1; } my $model = ''; if ($data =~ /^\#FIXED/) { $reader->move_along(6); $self->skip_whitespace($reader) || $self->parser_error( "no whitespace after FIXED specifier", $reader); my $value = $self->AttValue($reader); return "#FIXED", $value; } my $value = $self->AttValue($reader); return undef, $value; } sub EntityDecl { my ($self, $reader) = @_; my $data = $reader->data(8); return 0 unless $data =~ /^move_along(8); $self->skip_whitespace($reader) || $self->parser_error( "No whitespace after ENTITY declaration", $reader); $self->PEDecl($reader) || $self->GEDecl($reader); $self->skip_whitespace($reader); $reader->match('>') or $self->parser_error("No closing '>' in entity definition", $reader); return 1; } sub GEDecl { my ($self, $reader) = @_; my $name = $self->Name($reader) || $self->parser_error("No entity name given", $reader); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after entity name", $reader); # TODO: ExternalID calls lexhandler method. Wrong place for it. my $value; if ($value = $self->ExternalID($reader)) { $value .= $self->NDataDecl($reader); } else { $value = $self->EntityValue($reader); } if ($self->{ParseOptions}{entities}{$name}) { warn("entity $name already exists\n"); } else { $self->{ParseOptions}{entities}{$name} = 1; $self->{ParseOptions}{expanded_entity}{$name} = $value; # ??? } # do callback? return 1; } sub PEDecl { my ($self, $reader) = @_; return 0 unless $reader->match('%'); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after parameter entity marker", $reader); my $name = $self->Name($reader) || $self->parser_error("No parameter entity name given", $reader); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after parameter entity name", $reader); my $value = $self->ExternalID($reader) || $self->EntityValue($reader) || $self->parser_error("PE is not a value or an external resource", $reader); # do callback? return 1; } my $quotre = qr/[^%&\"]/; my $aposre = qr/[^%&\']/; sub EntityValue { my ($self, $reader) = @_; my $data = $reader->data; my $quote = '"'; my $re = $quotre; if ($data !~ /^"/) { $data =~ /^'/ or $self->parser_error("Not a quote character", $reader); $quote = "'"; $re = $aposre; } $reader->move_along(1); my $value = ''; while (1) { my $data = $reader->data; $self->parser_error("EOF found while reading entity value", $reader) unless length($data); if ($data =~ /^($re+)/) { my $match = $1; $value .= $match; $reader->move_along(length($match)); } elsif ($reader->match('&')) { # if it's a char ref, expand now: if ($reader->match('#')) { my $char; my $ref = ''; if ($reader->match('x')) { my $data = $reader->data; while (1) { $self->parser_error("EOF looking for reference end", $reader) unless length($data); if ($data !~ /^([0-9a-fA-F]*)/) { last; } $ref .= $1; $reader->move_along(length($1)); if (length($1) == length($data)) { $data = $reader->data; } else { last; } } $char = chr_ref(hex($ref)); $ref = "x$ref"; } else { my $data = $reader->data; while (1) { $self->parser_error("EOF looking for reference end", $reader) unless length($data); if ($data !~ /^([0-9]*)/) { last; } $ref .= $1; $reader->move_along(length($1)); if (length($1) == length($data)) { $data = $reader->data; } else { last; } } $char = chr($ref); } $reader->match(';') || $self->parser_error("No semi-colon found after character reference", $reader); if ($char !~ $SingleChar) { # match a single character $self->parser_error("Character reference '&#$ref;' refers to an illegal XML character ($char)", $reader); } $value .= $char; } else { # entity refs in entities get expanded later, so don't parse now. $value .= '&'; } } elsif ($reader->match('%')) { $value .= $self->PEReference($reader); } elsif ($reader->match($quote)) { # end of attrib last; } else { $self->parser_error("Invalid character in attribute value: " . substr($reader->data, 0, 1), $reader); } } return $value; } sub NDataDecl { my ($self, $reader) = @_; $self->skip_whitespace($reader) || return ''; my $data = $reader->data(5); return '' unless $data =~ /^NDATA/; $reader->move_along(5); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after NDATA declaration", $reader); my $name = $self->Name($reader) || $self->parser_error("NDATA declaration lacks a proper Name", $reader); return " NDATA $name"; } sub NotationDecl { my ($self, $reader) = @_; my $data = $reader->data(10); return 0 unless $data =~ /^move_along(10); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after NOTATION declaration", $reader); $data = $reader->data; my $value = ''; while(1) { $self->parser_error("EOF found while looking for end of NotationDecl", $reader) unless length($data); if ($data =~ /^([^>]*)>/) { $value .= $1; $reader->move_along(length($1) + 1); $self->notation_decl({Name => "FIXME", SystemId => "FIXME", PublicId => "FIXME" }); last; } else { $value .= $data; $reader->move_along(length($data)); $data = $reader->data; } } return 1; } 1; PK!.D D (perl5/XML/SAX/PurePerl/EncodingDetect.pmnu6$# $Id$ package XML::SAX::PurePerl; # NB, not ::EncodingDetect! use strict; sub encoding_detect { my ($parser, $reader) = @_; my $error = "Invalid byte sequence at start of file"; my $data = $reader->data; if ($data =~ /^\x00\x00\xFE\xFF/) { # BO-UCS4-be $reader->move_along(4); $reader->set_encoding('UCS-4BE'); return; } elsif ($data =~ /^\x00\x00\xFF\xFE/) { # BO-UCS-4-2143 $reader->move_along(4); $reader->set_encoding('UCS-4-2143'); return; } elsif ($data =~ /^\x00\x00\x00\x3C/) { $reader->set_encoding('UCS-4BE'); return; } elsif ($data =~ /^\x00\x00\x3C\x00/) { $reader->set_encoding('UCS-4-2143'); return; } elsif ($data =~ /^\x00\x3C\x00\x00/) { $reader->set_encoding('UCS-4-3412'); return; } elsif ($data =~ /^\x00\x3C\x00\x3F/) { $reader->set_encoding('UTF-16BE'); return; } elsif ($data =~ /^\xFF\xFE\x00\x00/) { # BO-UCS-4LE $reader->move_along(4); $reader->set_encoding('UCS-4LE'); return; } elsif ($data =~ /^\xFF\xFE/) { $reader->move_along(2); $reader->set_encoding('UTF-16LE'); return; } elsif ($data =~ /^\xFE\xFF\x00\x00/) { $reader->move_along(4); $reader->set_encoding('UCS-4-3412'); return; } elsif ($data =~ /^\xFE\xFF/) { $reader->move_along(2); $reader->set_encoding('UTF-16BE'); return; } elsif ($data =~ /^\xEF\xBB\xBF/) { # UTF-8 BOM $reader->move_along(3); $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^\x3C\x00\x00\x00/) { $reader->set_encoding('UCS-4LE'); return; } elsif ($data =~ /^\x3C\x00\x3F\x00/) { $reader->set_encoding('UTF-16LE'); return; } elsif ($data =~ /^\x3C\x3F\x78\x6D/) { # $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^\x3C\x3F\x78/) { # $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^\x3C\x3F/) { # $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^\x3C/) { # $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^[\x20\x09\x0A\x0D]+\x3C[^\x3F]/) { # $reader->set_encoding('UTF-8'); return; } elsif ($data =~ /^\x4C\x6F\xA7\x94/) { $reader->set_encoding('EBCDIC'); return; } warn("Unable to recognise encoding of this document"); return; } 1; PK!a3tt&perl5/XML/SAX/PurePerl/NoUnicodeExt.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; sub chr_ref { my $n = shift; if ($n < 0x80) { return chr ($n); } elsif ($n < 0x800) { return pack ("CC", (($n >> 6) | 0xc0), (($n & 0x3f) | 0x80)); } elsif ($n < 0x10000) { return pack ("CCC", (($n >> 12) | 0xe0), ((($n >> 6) & 0x3f) | 0x80), (($n & 0x3f) | 0x80)); } elsif ($n < 0x110000) { return pack ("CCCC", (($n >> 18) | 0xf0), ((($n >> 12) & 0x3f) | 0x80), ((($n >> 6) & 0x3f) | 0x80), (($n & 0x3f) | 0x80)); } else { return undef; } } 1; PK!k%perl5/XML/SAX/PurePerl/Productions.pmnu6$# $Id$ package XML::SAX::PurePerl::Productions; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = qw($S $Char $VersionNum $BaseChar $Ideographic $Extender $Digit $CombiningChar $EncNameStart $EncNameEnd $NameChar $CharMinusDash $PubidChar $Any $SingleChar); ### WARNING!!! All productions here must *only* match a *single* character!!! ### BEGIN { $S = qr/[\x20\x09\x0D\x0A]/; $CharMinusDash = qr/[^-]/x; $Any = qr/ . /xms; $VersionNum = qr/ [a-zA-Z0-9_.:-]+ /x; $EncNameStart = qr/ [A-Za-z] /x; $EncNameEnd = qr/ [A-Za-z0-9\._-] /x; $PubidChar = qr/ [\x20\x0D\x0Aa-zA-Z0-9'()\+,.\/:=\?;!*\#@\$_\%-] /x; if ($] < 5.006) { eval <<' PERL'; $Char = qr/^ [\x09\x0A\x0D\x20-\x7F]|([\xC0-\xFD][\x80-\xBF]+) $/x; $SingleChar = qr/^$Char$/; $BaseChar = qr/ [\x41-\x5A\x61-\x7A]|([\xC0-\xFD][\x80-\xBF]+) /x; $Extender = qr/ \xB7 /x; $Digit = qr/ [\x30-\x39] /x; # can't do this one without unicode # $CombiningChar = qr/^$/msx; $NameChar = qr/^ (?: $BaseChar | $Digit | [._:-] | $Extender )+ $/x; PERL die $@ if $@; } else { eval <<' PERL'; use utf8; # for 5.6 $Char = qr/^ [\x09\x0A\x0D\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}] $/x; $SingleChar = qr/^$Char$/; $BaseChar = qr/ [\x{0041}-\x{005A}\x{0061}-\x{007A}\x{00C0}-\x{00D6}\x{00D8}-\x{00F6}] | [\x{00F8}-\x{00FF}\x{0100}-\x{0131}\x{0134}-\x{013E}\x{0141}-\x{0148}] | [\x{014A}-\x{017E}\x{0180}-\x{01C3}\x{01CD}-\x{01F0}\x{01F4}-\x{01F5}] | [\x{01FA}-\x{0217}\x{0250}-\x{02A8}\x{02BB}-\x{02C1}\x{0386}\x{0388}-\x{038A}] | [\x{038C}\x{038E}-\x{03A1}\x{03A3}-\x{03CE}\x{03D0}-\x{03D6}\x{03DA}] | [\x{03DC}\x{03DE}\x{03E0}\x{03E2}-\x{03F3}\x{0401}-\x{040C}\x{040E}-\x{044F}] | [\x{0451}-\x{045C}\x{045E}-\x{0481}\x{0490}-\x{04C4}\x{04C7}-\x{04C8}] | [\x{04CB}-\x{04CC}\x{04D0}-\x{04EB}\x{04EE}-\x{04F5}\x{04F8}-\x{04F9}] | [\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0586}\x{05D0}-\x{05EA}\x{05F0}-\x{05F2}] | [\x{0621}-\x{063A}\x{0641}-\x{064A}\x{0671}-\x{06B7}\x{06BA}-\x{06BE}] | [\x{06C0}-\x{06CE}\x{06D0}-\x{06D3}\x{06D5}\x{06E5}-\x{06E6}\x{0905}-\x{0939}] | [\x{093D}\x{0958}-\x{0961}\x{0985}-\x{098C}\x{098F}-\x{0990}] | [\x{0993}-\x{09A8}\x{09AA}-\x{09B0}\x{09B2}\x{09B6}-\x{09B9}\x{09DC}-\x{09DD}] | [\x{09DF}-\x{09E1}\x{09F0}-\x{09F1}\x{0A05}-\x{0A0A}\x{0A0F}-\x{0A10}] | [\x{0A13}-\x{0A28}\x{0A2A}-\x{0A30}\x{0A32}-\x{0A33}\x{0A35}-\x{0A36}] | [\x{0A38}-\x{0A39}\x{0A59}-\x{0A5C}\x{0A5E}\x{0A72}-\x{0A74}\x{0A85}-\x{0A8B}] | [\x{0A8D}\x{0A8F}-\x{0A91}\x{0A93}-\x{0AA8}\x{0AAA}-\x{0AB0}] | [\x{0AB2}-\x{0AB3}\x{0AB5}-\x{0AB9}\x{0ABD}\x{0AE0}\x{0B05}-\x{0B0C}] | [\x{0B0F}-\x{0B10}\x{0B13}-\x{0B28}\x{0B2A}-\x{0B30}\x{0B32}-\x{0B33}] | [\x{0B36}-\x{0B39}\x{0B3D}\x{0B5C}-\x{0B5D}\x{0B5F}-\x{0B61}\x{0B85}-\x{0B8A}] | [\x{0B8E}-\x{0B90}\x{0B92}-\x{0B95}\x{0B99}-\x{0B9A}\x{0B9C}] | [\x{0B9E}-\x{0B9F}\x{0BA3}-\x{0BA4}\x{0BA8}-\x{0BAA}\x{0BAE}-\x{0BB5}] | [\x{0BB7}-\x{0BB9}\x{0C05}-\x{0C0C}\x{0C0E}-\x{0C10}\x{0C12}-\x{0C28}] | [\x{0C2A}-\x{0C33}\x{0C35}-\x{0C39}\x{0C60}-\x{0C61}\x{0C85}-\x{0C8C}] | [\x{0C8E}-\x{0C90}\x{0C92}-\x{0CA8}\x{0CAA}-\x{0CB3}\x{0CB5}-\x{0CB9}\x{0CDE}] | [\x{0CE0}-\x{0CE1}\x{0D05}-\x{0D0C}\x{0D0E}-\x{0D10}\x{0D12}-\x{0D28}] | [\x{0D2A}-\x{0D39}\x{0D60}-\x{0D61}\x{0E01}-\x{0E2E}\x{0E30}\x{0E32}-\x{0E33}] | [\x{0E40}-\x{0E45}\x{0E81}-\x{0E82}\x{0E84}\x{0E87}-\x{0E88}\x{0E8A}] | [\x{0E8D}\x{0E94}-\x{0E97}\x{0E99}-\x{0E9F}\x{0EA1}-\x{0EA3}\x{0EA5}\x{0EA7}] | [\x{0EAA}-\x{0EAB}\x{0EAD}-\x{0EAE}\x{0EB0}\x{0EB2}-\x{0EB3}\x{0EBD}] | [\x{0EC0}-\x{0EC4}\x{0F40}-\x{0F47}\x{0F49}-\x{0F69}\x{10A0}-\x{10C5}] | [\x{10D0}-\x{10F6}\x{1100}\x{1102}-\x{1103}\x{1105}-\x{1107}\x{1109}] | [\x{110B}-\x{110C}\x{110E}-\x{1112}\x{113C}\x{113E}\x{1140}\x{114C}\x{114E}] | [\x{1150}\x{1154}-\x{1155}\x{1159}\x{115F}-\x{1161}\x{1163}\x{1165}] | [\x{1167}\x{1169}\x{116D}-\x{116E}\x{1172}-\x{1173}\x{1175}\x{119E}\x{11A8}] | [\x{11AB}\x{11AE}-\x{11AF}\x{11B7}-\x{11B8}\x{11BA}\x{11BC}-\x{11C2}] | [\x{11EB}\x{11F0}\x{11F9}\x{1E00}-\x{1E9B}\x{1EA0}-\x{1EF9}\x{1F00}-\x{1F15}] | [\x{1F18}-\x{1F1D}\x{1F20}-\x{1F45}\x{1F48}-\x{1F4D}\x{1F50}-\x{1F57}] | [\x{1F59}\x{1F5B}\x{1F5D}\x{1F5F}-\x{1F7D}\x{1F80}-\x{1FB4}\x{1FB6}-\x{1FBC}] | [\x{1FBE}\x{1FC2}-\x{1FC4}\x{1FC6}-\x{1FCC}\x{1FD0}-\x{1FD3}] | [\x{1FD6}-\x{1FDB}\x{1FE0}-\x{1FEC}\x{1FF2}-\x{1FF4}\x{1FF6}-\x{1FFC}] | [\x{2126}\x{212A}-\x{212B}\x{212E}\x{2180}-\x{2182}\x{3041}-\x{3094}] | [\x{30A1}-\x{30FA}\x{3105}-\x{312C}\x{AC00}-\x{D7A3}] /x; $Extender = qr/ [\x{00B7}\x{02D0}\x{02D1}\x{0387}\x{0640}\x{0E46}\x{0EC6}\x{3005}\x{3031}-\x{3035}\x{309D}-\x{309E}\x{30FC}-\x{30FE}] /x; $Digit = qr/ [\x{0030}-\x{0039}\x{0660}-\x{0669}\x{06F0}-\x{06F9}\x{0966}-\x{096F}] | [\x{09E6}-\x{09EF}\x{0A66}-\x{0A6F}\x{0AE6}-\x{0AEF}\x{0B66}-\x{0B6F}] | [\x{0BE7}-\x{0BEF}\x{0C66}-\x{0C6F}\x{0CE6}-\x{0CEF}\x{0D66}-\x{0D6F}] | [\x{0E50}-\x{0E59}\x{0ED0}-\x{0ED9}\x{0F20}-\x{0F29}] /x; $CombiningChar = qr/ [\x{0300}-\x{0345}\x{0360}-\x{0361}\x{0483}-\x{0486}\x{0591}-\x{05A1}] | [\x{05A3}-\x{05B9}\x{05BB}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}] | [\x{064B}-\x{0652}\x{0670}\x{06D6}-\x{06DC}\x{06DD}-\x{06DF}\x{06E0}-\x{06E4}] | [\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{0901}-\x{0903}\x{093C}] | [\x{093E}-\x{094C}\x{094D}\x{0951}-\x{0954}\x{0962}-\x{0963}\x{0981}-\x{0983}] | [\x{09BC}\x{09BE}\x{09BF}\x{09C0}-\x{09C4}\x{09C7}-\x{09C8}] | [\x{09CB}-\x{09CD}\x{09D7}\x{09E2}-\x{09E3}\x{0A02}\x{0A3C}\x{0A3E}\x{0A3F}] | [\x{0A40}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A70}-\x{0A71}] | [\x{0A81}-\x{0A83}\x{0ABC}\x{0ABE}-\x{0AC5}\x{0AC7}-\x{0AC9}\x{0ACB}-\x{0ACD}] | [\x{0B01}-\x{0B03}\x{0B3C}\x{0B3E}-\x{0B43}\x{0B47}-\x{0B48}] | [\x{0B4B}-\x{0B4D}\x{0B56}-\x{0B57}\x{0B82}-\x{0B83}\x{0BBE}-\x{0BC2}] | [\x{0BC6}-\x{0BC8}\x{0BCA}-\x{0BCD}\x{0BD7}\x{0C01}-\x{0C03}\x{0C3E}-\x{0C44}] | [\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C82}-\x{0C83}] | [\x{0CBE}-\x{0CC4}\x{0CC6}-\x{0CC8}\x{0CCA}-\x{0CCD}\x{0CD5}-\x{0CD6}] | [\x{0D02}-\x{0D03}\x{0D3E}-\x{0D43}\x{0D46}-\x{0D48}\x{0D4A}-\x{0D4D}\x{0D57}] | [\x{0E31}\x{0E34}-\x{0E3A}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}] | [\x{0EBB}-\x{0EBC}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}] | [\x{0F3E}\x{0F3F}\x{0F71}-\x{0F84}\x{0F86}-\x{0F8B}\x{0F90}-\x{0F95}] | [\x{0F97}\x{0F99}-\x{0FAD}\x{0FB1}-\x{0FB7}\x{0FB9}\x{20D0}-\x{20DC}\x{20E1}] | [\x{302A}-\x{302F}\x{3099}\x{309A}] /x; $Ideographic = qr/ [\x{4E00}-\x{9FA5}\x{3007}\x{3021}-\x{3029}] /x; $NameChar = qr/^ (?: $BaseChar | $Ideographic | $Digit | [._:-] | $CombiningChar | $Extender )+ $/x; PERL die $@ if $@; } } 1; PK!*WW&perl5/XML/SAX/PurePerl/DebugHandler.pmnu6$# $Id$ package XML::SAX::PurePerl::DebugHandler; use strict; sub new { my $class = shift; my %opts = @_; return bless \%opts, $class; } # DocumentHandler sub set_document_locator { my $self = shift; print "set_document_locator\n" if $ENV{DEBUG_XML}; $self->{seen}{set_document_locator}++; } sub start_document { my $self = shift; print "start_document\n" if $ENV{DEBUG_XML}; $self->{seen}{start_document}++; } sub end_document { my $self = shift; print "end_document\n" if $ENV{DEBUG_XML}; $self->{seen}{end_document}++; } sub start_element { my $self = shift; print "start_element\n" if $ENV{DEBUG_XML}; $self->{seen}{start_element}++; } sub end_element { my $self = shift; print "end_element\n" if $ENV{DEBUG_XML}; $self->{seen}{end_element}++; } sub characters { my $self = shift; print "characters\n" if $ENV{DEBUG_XML}; # warn "Char: ", $_[0]->{Data}, "\n"; $self->{seen}{characters}++; } sub processing_instruction { my $self = shift; print "processing_instruction\n" if $ENV{DEBUG_XML}; $self->{seen}{processing_instruction}++; } sub ignorable_whitespace { my $self = shift; print "ignorable_whitespace\n" if $ENV{DEBUG_XML}; $self->{seen}{ignorable_whitespace}++; } # LexHandler sub comment { my $self = shift; print "comment\n" if $ENV{DEBUG_XML}; $self->{seen}{comment}++; } # DTDHandler sub notation_decl { my $self = shift; print "notation_decl\n" if $ENV{DEBUG_XML}; $self->{seen}{notation_decl}++; } sub unparsed_entity_decl { my $self = shift; print "unparsed_entity_decl\n" if $ENV{DEBUG_XML}; $self->{seen}{entity_decl}++; } # EntityResolver sub resolve_entity { my $self = shift; print "resolve_entity\n" if $ENV{DEBUG_XML}; $self->{seen}{resolve_entity}++; return ''; } 1; PK! perl5/XML/SAX/PurePerl/Reader.pmnu6$# $Id$ package XML::SAX::PurePerl::Reader; use strict; use XML::SAX::PurePerl::Reader::URI; use Exporter (); use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); @EXPORT_OK = qw( EOF BUFFER LINE COLUMN ENCODING XML_VERSION ); use constant EOF => 0; use constant BUFFER => 1; use constant LINE => 2; use constant COLUMN => 3; use constant ENCODING => 4; use constant SYSTEM_ID => 5; use constant PUBLIC_ID => 6; use constant XML_VERSION => 7; require XML::SAX::PurePerl::Reader::Stream; require XML::SAX::PurePerl::Reader::String; if ($] >= 5.007002) { require XML::SAX::PurePerl::Reader::UnicodeExt; } else { require XML::SAX::PurePerl::Reader::NoUnicodeExt; } sub new { my $class = shift; my $thing = shift; # try to figure if this $thing is a handle of some sort if (ref($thing) && UNIVERSAL::isa($thing, 'IO::Handle')) { return XML::SAX::PurePerl::Reader::Stream->new($thing)->init; } my $ioref; if (tied($thing)) { my $class = ref($thing); no strict 'refs'; $ioref = $thing if defined &{"${class}::TIEHANDLE"}; } else { eval { $ioref = *{$thing}{IO}; }; undef $@; } if ($ioref) { return XML::SAX::PurePerl::Reader::Stream->new($thing)->init; } if ($thing =~ /new($thing)->init; } # assume it is a uri return XML::SAX::PurePerl::Reader::URI->new($thing)->init; } sub init { my $self = shift; $self->[LINE] = 1; $self->[COLUMN] = 1; $self->read_more; return $self; } sub data { my ($self, $min_length) = (@_, 1); if (length($self->[BUFFER]) < $min_length) { $self->read_more; } return $self->[BUFFER]; } sub match { my ($self, $char) = @_; my $data = $self->data; if (substr($data, 0, 1) eq $char) { $self->move_along(1); return 1; } return 0; } sub public_id { my $self = shift; @_ and $self->[PUBLIC_ID] = shift; $self->[PUBLIC_ID]; } sub system_id { my $self = shift; @_ and $self->[SYSTEM_ID] = shift; $self->[SYSTEM_ID]; } sub line { shift->[LINE]; } sub column { shift->[COLUMN]; } sub get_encoding { my $self = shift; return $self->[ENCODING]; } sub get_xml_version { my $self = shift; return $self->[XML_VERSION]; } 1; __END__ =head1 NAME XML::Parser::PurePerl::Reader - Abstract Reader factory class =cut PK!&#perl5/XML/SAX/PurePerl/Exception.pmnu6$# $Id$ package XML::SAX::PurePerl::Exception; use strict; use overload '""' => "stringify"; use vars qw/$StackTrace/; $StackTrace = $ENV{XML_DEBUG} || 0; sub throw { my $class = shift; die $class->new(@_); } sub new { my $class = shift; my %opts = @_; die "Invalid options" unless exists $opts{Message}; if ($opts{reader}) { return bless { Message => $opts{Message}, Exception => undef, # not sure what this is for!!! ColumnNumber => $opts{reader}->column, LineNumber => $opts{reader}->line, PublicId => $opts{reader}->public_id, SystemId => $opts{reader}->system_id, $StackTrace ? (StackTrace => stacktrace()) : (), }, $class; } return bless { Message => $opts{Message}, Exception => undef, # not sure what this is for!!! }, $class; } sub stringify { my $self = shift; local $^W; return $self->{Message} . " [Ln: " . $self->{LineNumber} . ", Col: " . $self->{ColumnNumber} . "]" . ($StackTrace ? stackstring($self->{StackTrace}) : "") . "\n"; } sub stacktrace { my $i = 2; my @fulltrace; while (my @trace = caller($i++)) { my %hash; @hash{qw(Package Filename Line)} = @trace[0..2]; push @fulltrace, \%hash; } return \@fulltrace; } sub stackstring { my $stacktrace = shift; my $string = "\nFrom:\n"; foreach my $current (@$stacktrace) { $string .= $current->{Filename} . " Line: " . $current->{Line} . "\n"; } return $string; } 1; PK!5+22!perl5/XML/SAX/PurePerl/DocType.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; use XML::SAX::PurePerl::Productions qw($PubidChar); sub doctypedecl { my ($self, $reader) = @_; my $data = $reader->data(9); if ($data =~ /^move_along(9); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after doctype declaration", $reader); my $root_name = $self->Name($reader) || $self->parser_error("Doctype declaration has no root element name", $reader); if ($self->skip_whitespace($reader)) { # might be externalid... my %dtd = $self->ExternalID($reader); # TODO: Call SAX event } $self->skip_whitespace($reader); $self->InternalSubset($reader); $reader->match('>') or $self->parser_error("Doctype not closed", $reader); return 1; } return 0; } sub ExternalID { my ($self, $reader) = @_; my $data = $reader->data(6); if ($data =~ /^SYSTEM/) { $reader->move_along(6); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after SYSTEM identifier", $reader); return (SYSTEM => $self->SystemLiteral($reader)); } elsif ($data =~ /^PUBLIC/) { $reader->move_along(6); $self->skip_whitespace($reader) || $self->parser_error("No whitespace after PUBLIC identifier", $reader); my $quote = $self->quote($reader) || $self->parser_error("Not a quote character in PUBLIC identifier", $reader); my $data = $reader->data; my $pubid = ''; while(1) { $self->parser_error("EOF while looking for end of PUBLIC identifiier", $reader) unless length($data); if ($data =~ /^([^$quote]*)$quote/) { $pubid .= $1; $reader->move_along(length($1) + 1); last; } else { $pubid .= $data; $reader->move_along(length($data)); $data = $reader->data; } } if ($pubid !~ /^($PubidChar)+$/) { $self->parser_error("Invalid characters in PUBLIC identifier", $reader); } $self->skip_whitespace($reader) || $self->parser_error("Not whitespace after PUBLIC ID in DOCTYPE", $reader); return (PUBLIC => $pubid, SYSTEM => $self->SystemLiteral($reader)); } else { return; } return 1; } sub SystemLiteral { my ($self, $reader) = @_; my $quote = $self->quote($reader); my $data = $reader->data; my $systemid = ''; while (1) { $self->parser_error("EOF found while looking for end of System Literal", $reader) unless length($data); if ($data =~ /^([^$quote]*)$quote/) { $systemid .= $1; $reader->move_along(length($1) + 1); return $systemid; } else { $systemid .= $data; $reader->move_along(length($data)); $data = $reader->data; } } } sub InternalSubset { my ($self, $reader) = @_; return 0 unless $reader->match('['); 1 while $self->IntSubsetDecl($reader); $reader->match(']') or $self->parser_error("No close bracket on internal subset (found: " . $reader->data, $reader); $self->skip_whitespace($reader); return 1; } sub IntSubsetDecl { my ($self, $reader) = @_; return $self->DeclSep($reader) || $self->markupdecl($reader); } sub DeclSep { my ($self, $reader) = @_; if ($self->skip_whitespace($reader)) { return 1; } if ($self->PEReference($reader)) { return 1; } # if ($self->ParsedExtSubset($reader)) { # return 1; # } return 0; } sub PEReference { my ($self, $reader) = @_; return 0 unless $reader->match('%'); my $peref = $self->Name($reader) || $self->parser_error("PEReference did not find a Name", $reader); # TODO - load/parse the peref $reader->match(';') or $self->parser_error("Invalid token in PEReference", $reader); return 1; } sub markupdecl { my ($self, $reader) = @_; if ($self->elementdecl($reader) || $self->AttlistDecl($reader) || $self->EntityDecl($reader) || $self->NotationDecl($reader) || $self->PI($reader) || $self->Comment($reader)) { return 1; } return 0; } 1; PK!z]Xqq$perl5/XML/SAX/PurePerl/UnicodeExt.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; no warnings 'utf8'; sub chr_ref { return chr(shift); } if ($] >= 5.007002) { require Encode; Encode::define_alias( "UTF-16" => "UCS-2" ); Encode::define_alias( "UTF-16BE" => "UCS-2" ); Encode::define_alias( "UTF-16LE" => "ucs-2le" ); Encode::define_alias( "UTF16LE" => "ucs-2le" ); } 1; PK!N= = !perl5/XML/SAX/PurePerl/XMLDecl.pmnu6$# $Id$ package XML::SAX::PurePerl; use strict; use XML::SAX::PurePerl::Productions qw($S $VersionNum $EncNameStart $EncNameEnd); sub XMLDecl { my ($self, $reader) = @_; my $data = $reader->data(5); # warn("Looking for xmldecl in: $data"); if ($data =~ /^<\?xml$S/o) { $reader->move_along(5); $self->skip_whitespace($reader); # get version attribute $self->VersionInfo($reader) || $self->parser_error("XML Declaration lacks required version attribute, or version attribute does not match XML specification", $reader); if (!$self->skip_whitespace($reader)) { my $data = $reader->data(2); $data =~ /^\?>/ or $self->parser_error("Syntax error", $reader); $reader->move_along(2); return; } if ($self->EncodingDecl($reader)) { if (!$self->skip_whitespace($reader)) { my $data = $reader->data(2); $data =~ /^\?>/ or $self->parser_error("Syntax error", $reader); $reader->move_along(2); return; } } $self->SDDecl($reader); $self->skip_whitespace($reader); my $data = $reader->data(2); $data =~ /^\?>/ or $self->parser_error("Syntax error", $reader); $reader->move_along(2); } else { # warn("first 5 bytes: ", join(',', unpack("CCCCC", $data)), "\n"); # no xml decl if (!$reader->get_encoding) { $reader->set_encoding("UTF-8"); } } } sub VersionInfo { my ($self, $reader) = @_; my $data = $reader->data(11); # warn("Looking for version in $data"); $data =~ /^(version$S*=$S*(["'])($VersionNum)\2)/o or return 0; $reader->move_along(length($1)); my $vernum = $3; if ($vernum ne "1.0") { $self->parser_error("Only XML version 1.0 supported. Saw: '$vernum'", $reader); } return 1; } sub SDDecl { my ($self, $reader) = @_; my $data = $reader->data(15); $data =~ /^(standalone$S*=$S*(["'])(yes|no)\2)/o or return 0; $reader->move_along(length($1)); my $yesno = $3; if ($yesno eq 'yes') { $self->{standalone} = 1; } else { $self->{standalone} = 0; } return 1; } sub EncodingDecl { my ($self, $reader) = @_; my $data = $reader->data(12); $data =~ /^(encoding$S*=$S*(["'])($EncNameStart$EncNameEnd*)\2)/o or return 0; $reader->move_along(length($1)); my $encoding = $3; $reader->set_encoding($encoding); return 1; } sub TextDecl { my ($self, $reader) = @_; my $data = $reader->data(6); $data =~ /^<\?xml$S+/ or return; $reader->move_along(5); $self->skip_whitespace($reader); if ($self->VersionInfo($reader)) { $self->skip_whitespace($reader) || $self->parser_error("Lack of whitespace after version attribute in text declaration", $reader); } $self->EncodingDecl($reader) || $self->parser_error("Encoding declaration missing from external entity text declaration", $reader); $self->skip_whitespace($reader); $data = $reader->data(2); $data =~ /^\?>/ or $self->parser_error("Syntax error", $reader); return 1; } 1; PK!45$5$perl5/XML/SAX.pmnu6$# $Id$ package XML::SAX; use strict; use vars qw($VERSION @ISA @EXPORT_OK); $VERSION = '1.02'; use Exporter (); @ISA = ('Exporter'); @EXPORT_OK = qw(Namespaces Validation); use File::Basename qw(dirname); use File::Spec (); use Symbol qw(gensym); use XML::SAX::ParserFactory (); # loaded for simplicity use constant PARSER_DETAILS => "ParserDetails.ini"; use constant Namespaces => "http://xml.org/sax/features/namespaces"; use constant Validation => "http://xml.org/sax/features/validation"; my $known_parsers = undef; # load_parsers takes the ParserDetails.ini file out of the same directory # that XML::SAX is in, and looks at it. Format in POD below =begin EXAMPLE [XML::SAX::PurePerl] http://xml.org/sax/features/namespaces = 1 http://xml.org/sax/features/validation = 0 # a comment # blank lines ignored [XML::SAX::AnotherParser] http://xml.org/sax/features/namespaces = 0 http://xml.org/sax/features/validation = 1 =end EXAMPLE =cut sub load_parsers { my $class = shift; my $dir = shift; # reset parsers $known_parsers = []; # get directory from wherever XML::SAX is installed if (!$dir) { $dir = $INC{'XML/SAX.pm'}; $dir = dirname($dir); } my $fh = gensym(); if (!open($fh, File::Spec->catfile($dir, "SAX", PARSER_DETAILS))) { XML::SAX->do_warn("could not find " . PARSER_DETAILS . " in $dir/SAX\n"); return $class; } $known_parsers = $class->_parse_ini_file($fh); return $class; } sub _parse_ini_file { my $class = shift; my ($fh) = @_; my @config; my $lineno = 0; while (defined(my $line = <$fh>)) { $lineno++; my $original = $line; # strip whitespace $line =~ s/\s*$//m; $line =~ s/^\s*//m; # strip comments $line =~ s/[#;].*$//m; # ignore blanks next if $line =~ /^$/m; # heading if ($line =~ /^\[\s*(.*)\s*\]$/m) { push @config, { Name => $1 }; next; } # instruction elsif ($line =~ /^(.*?)\s*?=\s*(.*)$/) { unless(@config) { push @config, { Name => '' }; } $config[-1]{Features}{$1} = $2; } # not whitespace, comment, or instruction else { die "Invalid line in ini: $lineno\n>>> $original\n"; } } return \@config; } sub parsers { my $class = shift; if (!$known_parsers) { $class->load_parsers(); } return $known_parsers; } sub remove_parser { my $class = shift; my ($parser_module) = @_; if (!$known_parsers) { $class->load_parsers(); } @$known_parsers = grep { $_->{Name} ne $parser_module } @$known_parsers; return $class; } sub add_parser { my $class = shift; my ($parser_module) = @_; if (!$known_parsers) { $class->load_parsers(); } # first load module, then query features, then push onto known_parsers, my $parser_file = $parser_module; $parser_file =~ s/::/\//g; $parser_file .= ".pm"; require $parser_file; my @features = $parser_module->supported_features(); my $new = { Name => $parser_module }; foreach my $feature (@features) { $new->{Features}{$feature} = 1; } # If exists in list already, move to end. my $done = 0; my $pos = undef; for (my $i = 0; $i < @$known_parsers; $i++) { my $p = $known_parsers->[$i]; if ($p->{Name} eq $parser_module) { $pos = $i; } } if (defined $pos) { splice(@$known_parsers, $pos, 1); push @$known_parsers, $new; $done++; } # Otherwise (not in list), add at end of list. if (!$done) { push @$known_parsers, $new; } return $class; } sub save_parsers { my $class = shift; # get directory from wherever XML::SAX is installed my $dir = $INC{'XML/SAX.pm'}; $dir = dirname($dir); my $file = File::Spec->catfile($dir, "SAX", PARSER_DETAILS); chmod 0644, $file; unlink($file); my $fh = gensym(); open($fh, ">$file") || die "Cannot write to $file: $!"; foreach my $p (@$known_parsers) { print $fh "[$p->{Name}]\n"; foreach my $key (keys %{$p->{Features}}) { print $fh "$key = $p->{Features}{$key}\n"; } print $fh "\n"; } print $fh "\n"; close $fh; return $class; } sub do_warn { my $class = shift; # Don't output warnings if running under Test::Harness warn(@_) unless $ENV{HARNESS_ACTIVE}; } 1; __END__ =head1 NAME XML::SAX - Simple API for XML =head1 SYNOPSIS use XML::SAX; # get a list of known parsers my $parsers = XML::SAX->parsers(); # add/update a parser XML::SAX->add_parser(q(XML::SAX::PurePerl)); # remove parser XML::SAX->remove_parser(q(XML::SAX::Foodelberry)); # save parsers XML::SAX->save_parsers(); =head1 DESCRIPTION XML::SAX is a SAX parser access API for Perl. It includes classes and APIs required for implementing SAX drivers, along with a factory class for returning any SAX parser installed on the user's system. =head1 USING A SAX2 PARSER The factory class is XML::SAX::ParserFactory. Please see the documentation of that module for how to instantiate a SAX parser: L. However if you don't want to load up another manual page, here's a short synopsis: use XML::SAX::ParserFactory; use XML::SAX::XYZHandler; my $handler = XML::SAX::XYZHandler->new(); my $p = XML::SAX::ParserFactory->parser(Handler => $handler); $p->parse_uri("foo.xml"); # or $p->parse_string("") or $p->parse_file($fh); This will automatically load a SAX2 parser (defaulting to XML::SAX::PurePerl if no others are found) and return it to you. In order to learn how to use SAX to parse XML, you will need to read L and for reference, L. =head1 WRITING A SAX2 PARSER The first thing to remember in writing a SAX2 parser is to subclass XML::SAX::Base. This will make your life infinitely easier, by providing a number of methods automagically for you. See L for more details. When writing a SAX2 parser that is compatible with XML::SAX, you need to inform XML::SAX of the presence of that driver when you install it. In order to do that, XML::SAX contains methods for saving the fact that the parser exists on your system to a "INI" file, which is then loaded to determine which parsers are installed. The best way to do this is to follow these rules: =over 4 =item * Add XML::SAX as a prerequisite in Makefile.PL: WriteMakefile( ... PREREQ_PM => { 'XML::SAX' => 0 }, ... ); Alternatively you may wish to check for it in other ways that will cause more than just a warning. =item * Add the following code snippet to your Makefile.PL: sub MY::install { package MY; my $script = shift->SUPER::install(@_); if (ExtUtils::MakeMaker::prompt( "Do you want to modify ParserDetails.ini?", 'Y') =~ /^y/i) { $script =~ s/install :: (.*)$/install :: $1 install_sax_driver/m; $script .= <<"INSTALL"; install_sax_driver : \t\@\$(PERL) -MXML::SAX -e "XML::SAX->add_parser(q(\$(NAME)))->save_parsers()" INSTALL } return $script; } Note that you should check the output of this - \$(NAME) will use the name of your distribution, which may not be exactly what you want. For example XML::LibXML has a driver called XML::LibXML::SAX::Generator, which is used in place of \$(NAME) in the above. =item * Add an XML::SAX test: A test file should be added to your t/ directory containing something like the following: use Test; BEGIN { plan tests => 3 } use XML::SAX; use XML::SAX::PurePerl::DebugHandler; XML::SAX->add_parser(q(XML::SAX::MyDriver)); local $XML::SAX::ParserPackage = 'XML::SAX::MyDriver'; eval { my $handler = XML::SAX::PurePerl::DebugHandler->new(); ok($handler); my $parser = XML::SAX::ParserFactory->parser(Handler => $handler); ok($parser); ok($parser->isa('XML::SAX::MyDriver'); $parser->parse_string(""); ok($handler->{seen}{start_element}); }; =back =head1 EXPORTS By default, XML::SAX exports nothing into the caller's namespace. However you can request the symbols C and C which are the URIs for those features, allowing an easier way to request those features via ParserFactory: use XML::SAX qw(Namespaces Validation); my $factory = XML::SAX::ParserFactory->new(); $factory->require_feature(Namespaces); $factory->require_feature(Validation); my $parser = $factory->parser(); =head1 AUTHOR Current maintainer: Grant McLean, grantm@cpan.org Originally written by: Matt Sergeant, matt@sergeant.org Kip Hampton, khampton@totalcinema.com Robin Berjon, robin@knowscape.com =head1 LICENSE This is free software, you may use it and distribute it under the same terms as Perl itself. =head1 SEE ALSO L for writing SAX Filters and Parsers L for an XML parser written in 100% pure perl. L for details on exception handling =cut PK!qiperl5/XML/Simple.pmnu6$package XML::Simple; $XML::Simple::VERSION = '2.25'; =head1 NAME XML::Simple - An API for simple XML files =head1 SYNOPSIS PLEASE DO NOT USE THIS MODULE IN NEW CODE. If you ignore this warning and use it anyway, the C mode will save you a little pain. use XML::Simple qw(:strict); my $ref = XMLin([] [, ]); my $xml = XMLout($hashref [, ]); Or the object oriented way: require XML::Simple qw(:strict); my $xs = XML::Simple->new([]); my $ref = $xs->XMLin([] [, ]); my $xml = $xs->XMLout($hashref [, ]); (or see L<"SAX SUPPORT"> for 'the SAX way'). Note, in these examples, the square brackets are used to denote optional items not to imply items should be supplied in arrayrefs. =cut # See after __END__ for more POD documentation # Load essentials here, other modules loaded on demand later use strict; use warnings; use warnings::register; use Carp; use Scalar::Util qw(); require Exporter; ############################################################################## # Define some constants # use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $PREFERRED_PARSER); @ISA = qw(Exporter); @EXPORT = qw(XMLin XMLout); @EXPORT_OK = qw(xml_in xml_out); my %StrictMode = (); my @KnownOptIn = qw(keyattr keeproot forcecontent contentkey noattr searchpath forcearray cache suppressempty parseropts grouptags nsexpand datahandler varattr variables normalisespace normalizespace valueattr strictmode); my @KnownOptOut = qw(keyattr keeproot contentkey noattr rootname xmldecl outputfile noescape suppressempty grouptags nsexpand handler noindent attrindent nosort valueattr numericescape strictmode); my @DefKeyAttr = qw(name key id); my $DefRootName = qq(opt); my $DefContentKey = qq(content); my $DefXmlDecl = qq(); my $xmlns_ns = 'http://www.w3.org/2000/xmlns/'; my $bad_def_ns_jcn = '{' . $xmlns_ns . '}'; # LibXML::SAX workaround ############################################################################## # Globals for use by caching routines # my %MemShareCache = (); my %MemCopyCache = (); ############################################################################## # Wrapper for Exporter - handles ':strict' # sub import { # Handle the :strict tag my($calling_package) = caller(); _strict_mode_for_caller(1) if grep(/^:strict$/, @_); # Pass everything else to Exporter.pm @_ = grep(!/^:strict$/, @_); goto &Exporter::import; } ############################################################################## # Constructor for optional object interface. # sub new { my $class = shift; if(@_ % 2) { croak "Default options must be name=>value pairs (odd number supplied)"; } my %known_opt; @known_opt{@KnownOptIn, @KnownOptOut} = (); my %raw_opt = @_; $raw_opt{strictmode} = _strict_mode_for_caller() unless exists $raw_opt{strictmode}; my %def_opt; while(my($key, $val) = each %raw_opt) { my $lkey = lc($key); $lkey =~ s/_//g; croak "Unrecognised option: $key" unless(exists($known_opt{$lkey})); $def_opt{$lkey} = $val; } my $self = { def_opt => \%def_opt }; return(bless($self, $class)); } ############################################################################## # Sub: _strict_mode_for_caller() # # Gets or sets the XML::Simple :strict mode flag for the calling namespace. # Walks back through call stack to find the calling namespace and sets the # :strict mode flag for that namespace if an argument was supplied and returns # the flag value if not. # sub _strict_mode_for_caller { my $set_mode = @_; my $frame = 1; while(my($package) = caller($frame++)) { next if $package eq 'XML::Simple'; $StrictMode{$package} = 1 if $set_mode; return $StrictMode{$package}; } return(0); } ############################################################################## # Sub: _get_object() # # Helper routine called from XMLin() and XMLout() to create an object if none # was provided. Note, this routine does mess with the caller's @_ array. # sub _get_object { my $self; if($_[0] and UNIVERSAL::isa($_[0], 'XML::Simple')) { $self = shift; } else { $self = XML::Simple->new(); } return $self; } ############################################################################## # Sub/Method: XMLin() # # Exported routine for slurping XML into a hashref - see pod for info. # # May be called as object method or as a plain function. # # Expects one arg for the source XML, optionally followed by a number of # name => value option pairs. # sub XMLin { my $self = &_get_object; # note, @_ is passed implicitly my $target = shift; # Work out whether to parse a string, a file or a filehandle if(not defined $target) { return $self->parse_file(undef, @_); } elsif($target eq '-') { local($/) = undef; $target = ; return $self->parse_string(\$target, @_); } elsif(my $type = ref($target)) { if($type eq 'SCALAR') { return $self->parse_string($target, @_); } else { return $self->parse_fh($target, @_); } } elsif($target =~ m{<.*?>}s) { return $self->parse_string(\$target, @_); } else { return $self->parse_file($target, @_); } } ############################################################################## # Sub/Method: parse_file() # # Same as XMLin, but only parses from a named file. # sub parse_file { my $self = &_get_object; # note, @_ is passed implicitly my $filename = shift; $self->handle_options('in', @_); $filename = $self->default_config_file if not defined $filename; $filename = $self->find_xml_file($filename, @{$self->{opt}->{searchpath}}); # Check cache for previous parse if($self->{opt}->{cache}) { foreach my $scheme (@{$self->{opt}->{cache}}) { my $method = 'cache_read_' . $scheme; my $opt = $self->$method($filename); return($opt) if($opt); } } my $ref = $self->build_simple_tree($filename, undef); if($self->{opt}->{cache}) { my $method = 'cache_write_' . $self->{opt}->{cache}->[0]; $self->$method($ref, $filename); } return $ref; } ############################################################################## # Sub/Method: parse_fh() # # Same as XMLin, but only parses from a filehandle. # sub parse_fh { my $self = &_get_object; # note, @_ is passed implicitly my $fh = shift; croak "Can't use " . (defined $fh ? qq{string ("$fh")} : 'undef') . " as a filehandle" unless ref $fh; $self->handle_options('in', @_); return $self->build_simple_tree(undef, $fh); } ############################################################################## # Sub/Method: parse_string() # # Same as XMLin, but only parses from a string or a reference to a string. # sub parse_string { my $self = &_get_object; # note, @_ is passed implicitly my $string = shift; $self->handle_options('in', @_); return $self->build_simple_tree(undef, ref $string ? $string : \$string); } ############################################################################## # Method: default_config_file() # # Returns the name of the XML file to parse if no filename (or XML string) # was provided. # sub default_config_file { my $self = shift; require File::Basename; my($basename, $script_dir, $ext) = File::Basename::fileparse($0, '\.[^\.]+'); # Add script directory to searchpath if($script_dir) { unshift(@{$self->{opt}->{searchpath}}, $script_dir); } return $basename . '.xml'; } ############################################################################## # Method: build_simple_tree() # # Builds a 'tree' data structure as provided by XML::Parser and then # 'simplifies' it as specified by the various options in effect. # sub build_simple_tree { my $self = shift; my $tree = eval { $self->build_tree(@_); }; Carp::croak("$@XML::Simple called") if $@; return $self->{opt}->{keeproot} ? $self->collapse({}, @$tree) : $self->collapse(@{$tree->[1]}); } ############################################################################## # Method: build_tree() # # This routine will be called if there is no suitable pre-parsed tree in a # cache. It parses the XML and returns an XML::Parser 'Tree' style data # structure (summarised in the comments for the collapse() routine below). # # XML::Simple requires the services of another module that knows how to parse # XML. If XML::SAX is installed, the default SAX parser will be used, # otherwise XML::Parser will be used. # # This routine expects to be passed a filename as argument 1 or a 'string' as # argument 2. The 'string' might be a string of XML (passed by reference to # save memory) or it might be a reference to an IO::Handle. (This # non-intuitive mess results in part from the way XML::Parser works but that's # really no excuse). # sub build_tree { my $self = shift; my $filename = shift; my $string = shift; my $preferred_parser = $PREFERRED_PARSER; unless(defined($preferred_parser)) { $preferred_parser = $ENV{XML_SIMPLE_PREFERRED_PARSER} || ''; } if($preferred_parser eq 'XML::Parser') { return($self->build_tree_xml_parser($filename, $string)); } eval { require XML::SAX; }; # We didn't need it until now if($@) { # No XML::SAX - fall back to XML::Parser if($preferred_parser) { # unless a SAX parser was expressly requested croak "XMLin() could not load XML::SAX"; } return($self->build_tree_xml_parser($filename, $string)); } $XML::SAX::ParserPackage = $preferred_parser if($preferred_parser); my $sp = XML::SAX::ParserFactory->parser(Handler => $self); $self->{nocollapse} = 1; my($tree); if($filename) { $tree = $sp->parse_uri($filename); } else { if(ref($string) && ref($string) ne 'SCALAR') { $tree = $sp->parse_file($string); } else { $tree = $sp->parse_string($$string); } } return($tree); } ############################################################################## # Method: build_tree_xml_parser() # # This routine will be called if XML::SAX is not installed, or if XML::Parser # was specifically requested. It takes the same arguments as build_tree() and # returns the same data structure (XML::Parser 'Tree' style). # sub build_tree_xml_parser { my $self = shift; my $filename = shift; my $string = shift; eval { local($^W) = 0; # Suppress warning from Expat.pm re File::Spec::load() require XML::Parser; # We didn't need it until now }; if($@) { croak "XMLin() requires either XML::SAX or XML::Parser"; } if($self->{opt}->{nsexpand}) { carp "'nsexpand' option requires XML::SAX"; } my $xp = $self->new_xml_parser(); my($tree); if($filename) { # $tree = $xp->parsefile($filename); # Changed due to prob w/mod_perl open(my $xfh, '<', $filename) || croak qq($filename - $!); $tree = $xp->parse($xfh); } else { $tree = $xp->parse($$string); } return($tree); } ############################################################################## # Method: new_xml_parser() # # Simply calls the XML::Parser constructor. Override this method to customise # the behaviour of the parser. # sub new_xml_parser { my($self) = @_; my $xp = XML::Parser->new(Style => 'Tree', @{$self->{opt}->{parseropts}}); $xp->setHandlers(ExternEnt => sub {return $_[2]}); return $xp; } ############################################################################## # Method: cache_write_storable() # # Wrapper routine for invoking Storable::nstore() to cache a parsed data # structure. # sub cache_write_storable { my($self, $data, $filename) = @_; my $cachefile = $self->storable_filename($filename); require Storable; # We didn't need it until now if ('VMS' eq $^O) { Storable::nstore($data, $cachefile); } else { # If the following line fails for you, your Storable.pm is old - upgrade Storable::lock_nstore($data, $cachefile); } } ############################################################################## # Method: cache_read_storable() # # Wrapper routine for invoking Storable::retrieve() to read a cached parsed # data structure. Only returns cached data if the cache file exists and is # newer than the source XML file. # sub cache_read_storable { my($self, $filename) = @_; my $cachefile = $self->storable_filename($filename); return unless(-r $cachefile); return unless((stat($cachefile))[9] > (stat($filename))[9]); require Storable; # We didn't need it until now if ('VMS' eq $^O) { return(Storable::retrieve($cachefile)); } else { return(Storable::lock_retrieve($cachefile)); } } ############################################################################## # Method: storable_filename() # # Translates the supplied source XML filename into a filename for the storable # cached data. A '.stor' suffix is added after stripping an optional '.xml' # suffix. # sub storable_filename { my($self, $cachefile) = @_; $cachefile =~ s{(\.xml)?$}{.stor}; return $cachefile; } ############################################################################## # Method: cache_write_memshare() # # Takes the supplied data structure reference and stores it away in a global # hash structure. # sub cache_write_memshare { my($self, $data, $filename) = @_; $MemShareCache{$filename} = [time(), $data]; } ############################################################################## # Method: cache_read_memshare() # # Takes a filename and looks in a global hash for a cached parsed version. # sub cache_read_memshare { my($self, $filename) = @_; return unless($MemShareCache{$filename}); return unless($MemShareCache{$filename}->[0] > (stat($filename))[9]); return($MemShareCache{$filename}->[1]); } ############################################################################## # Method: cache_write_memcopy() # # Takes the supplied data structure and stores a copy of it in a global hash # structure. # sub cache_write_memcopy { my($self, $data, $filename) = @_; require Storable; # We didn't need it until now $MemCopyCache{$filename} = [time(), Storable::dclone($data)]; } ############################################################################## # Method: cache_read_memcopy() # # Takes a filename and looks in a global hash for a cached parsed version. # Returns a reference to a copy of that data structure. # sub cache_read_memcopy { my($self, $filename) = @_; return unless($MemCopyCache{$filename}); return unless($MemCopyCache{$filename}->[0] > (stat($filename))[9]); return(Storable::dclone($MemCopyCache{$filename}->[1])); } ############################################################################## # Sub/Method: XMLout() # # Exported routine for 'unslurping' a data structure out to XML. # # Expects a reference to a data structure and an optional list of option # name => value pairs. # sub XMLout { my $self = &_get_object; # note, @_ is passed implicitly croak "XMLout() requires at least one argument" unless(@_); my $ref = shift; $self->handle_options('out', @_); # If namespace expansion is set, XML::NamespaceSupport is required if($self->{opt}->{nsexpand}) { require XML::NamespaceSupport; $self->{nsup} = XML::NamespaceSupport->new(); $self->{ns_prefix} = 'aaa'; } # Wrap top level arrayref in a hash if(UNIVERSAL::isa($ref, 'ARRAY')) { $ref = { anon => $ref }; } # Extract rootname from top level hash if keeproot enabled if($self->{opt}->{keeproot}) { my(@keys) = keys(%$ref); if(@keys == 1) { $ref = $ref->{$keys[0]}; $self->{opt}->{rootname} = $keys[0]; } } # Ensure there are no top level attributes if we're not adding root elements elsif($self->{opt}->{rootname} eq '') { if(UNIVERSAL::isa($ref, 'HASH')) { my $refsave = $ref; $ref = {}; foreach (keys(%$refsave)) { if(ref($refsave->{$_})) { $ref->{$_} = $refsave->{$_}; } else { $ref->{$_} = [ $refsave->{$_} ]; } } } } # Encode the hashref and write to file if necessary $self->{_ancestors} = {}; my $xml = $self->value_to_xml($ref, $self->{opt}->{rootname}, ''); delete $self->{_ancestors}; if($self->{opt}->{xmldecl}) { $xml = $self->{opt}->{xmldecl} . "\n" . $xml; } if($self->{opt}->{outputfile}) { if(ref($self->{opt}->{outputfile})) { my $fh = $self->{opt}->{outputfile}; if(UNIVERSAL::isa($fh, 'GLOB') and !UNIVERSAL::can($fh, 'print')) { eval { require IO::Handle; }; croak $@ if $@; } return($fh->print($xml)); } else { open(my $out, '>', "$self->{opt}->{outputfile}") || croak "open($self->{opt}->{outputfile}): $!"; binmode($out, ':utf8') if($] >= 5.008); print $out $xml or croak "print: $!"; close $out or croak "close: $!"; } } elsif($self->{opt}->{handler}) { require XML::SAX; my $sp = XML::SAX::ParserFactory->parser( Handler => $self->{opt}->{handler} ); return($sp->parse_string($xml)); } else { return($xml); } } ############################################################################## # Method: handle_options() # # Helper routine for both XMLin() and XMLout(). Both routines handle their # first argument and assume all other args are options handled by this routine. # Saves a hash of options in $self->{opt}. # # If default options were passed to the constructor, they will be retrieved # here and merged with options supplied to the method call. # # First argument should be the string 'in' or the string 'out'. # # Remaining arguments should be name=>value pairs. Sets up default values # for options not supplied. Unrecognised options are a fatal error. # sub handle_options { my $self = shift; my $dirn = shift; # Determine valid options based on context my %known_opt; if($dirn eq 'in') { @known_opt{@KnownOptIn} = @KnownOptIn; } else { @known_opt{@KnownOptOut} = @KnownOptOut; } # Store supplied options in hashref and weed out invalid ones if(@_ % 2) { croak "Options must be name=>value pairs (odd number supplied)"; } my %raw_opt = @_; my $opt = {}; $self->{opt} = $opt; while(my($key, $val) = each %raw_opt) { my $lkey = lc($key); $lkey =~ s/_//g; croak "Unrecognised option: $key" unless($known_opt{$lkey}); $opt->{$lkey} = $val; } # Merge in options passed to constructor foreach (keys(%known_opt)) { unless(exists($opt->{$_})) { if(exists($self->{def_opt}->{$_})) { $opt->{$_} = $self->{def_opt}->{$_}; } } } # Set sensible defaults if not supplied if(exists($opt->{rootname})) { unless(defined($opt->{rootname})) { $opt->{rootname} = ''; } } else { $opt->{rootname} = $DefRootName; } if($opt->{xmldecl} and $opt->{xmldecl} eq '1') { $opt->{xmldecl} = $DefXmlDecl; } if(exists($opt->{contentkey})) { if($opt->{contentkey} =~ m{^-(.*)$}) { $opt->{contentkey} = $1; $opt->{collapseagain} = 1; } } else { $opt->{contentkey} = $DefContentKey; } unless(exists($opt->{normalisespace})) { $opt->{normalisespace} = $opt->{normalizespace}; } $opt->{normalisespace} = 0 unless(defined($opt->{normalisespace})); # Cleanups for values assumed to be arrays later if($opt->{searchpath}) { unless(ref($opt->{searchpath})) { $opt->{searchpath} = [ $opt->{searchpath} ]; } } else { $opt->{searchpath} = [ ]; } if($opt->{cache} and !ref($opt->{cache})) { $opt->{cache} = [ $opt->{cache} ]; } if($opt->{cache}) { $_ = lc($_) foreach (@{$opt->{cache}}); foreach my $scheme (@{$opt->{cache}}) { my $method = 'cache_read_' . $scheme; croak "Unsupported caching scheme: $scheme" unless($self->can($method)); } } if(exists($opt->{parseropts})) { if(warnings::enabled()) { carp "Warning: " . "'ParserOpts' is deprecated, contact the author if you need it"; } } else { $opt->{parseropts} = [ ]; } # Special cleanup for {forcearray} which could be regex, arrayref or boolean # or left to default to 0 if(exists($opt->{forcearray})) { if(ref($opt->{forcearray}) eq 'Regexp') { $opt->{forcearray} = [ $opt->{forcearray} ]; } if(ref($opt->{forcearray}) eq 'ARRAY') { my @force_list = @{$opt->{forcearray}}; if(@force_list) { $opt->{forcearray} = {}; foreach my $tag (@force_list) { if(ref($tag) eq 'Regexp') { push @{$opt->{forcearray}->{_regex}}, $tag; } else { $opt->{forcearray}->{$tag} = 1; } } } else { $opt->{forcearray} = 0; } } else { $opt->{forcearray} = ( $opt->{forcearray} ? 1 : 0 ); } } else { if($opt->{strictmode} and $dirn eq 'in') { croak "No value specified for 'ForceArray' option in call to XML$dirn()"; } $opt->{forcearray} = 0; } # Special cleanup for {keyattr} which could be arrayref or hashref or left # to default to arrayref if(exists($opt->{keyattr})) { if(ref($opt->{keyattr})) { if(ref($opt->{keyattr}) eq 'HASH') { # Make a copy so we can mess with it $opt->{keyattr} = { %{$opt->{keyattr}} }; # Convert keyattr => { elem => '+attr' } # to keyattr => { elem => [ 'attr', '+' ] } foreach my $el (keys(%{$opt->{keyattr}})) { if($opt->{keyattr}->{$el} =~ /^(\+|-)?(.*)$/) { $opt->{keyattr}->{$el} = [ $2, ($1 ? $1 : '') ]; if($opt->{strictmode} and $dirn eq 'in') { next if($opt->{forcearray} == 1); next if(ref($opt->{forcearray}) eq 'HASH' and $opt->{forcearray}->{$el}); croak "<$el> set in KeyAttr but not in ForceArray"; } } else { delete($opt->{keyattr}->{$el}); # Never reached (famous last words?) } } } else { if(@{$opt->{keyattr}} == 0) { delete($opt->{keyattr}); } } } else { $opt->{keyattr} = [ $opt->{keyattr} ]; } } else { if($opt->{strictmode}) { croak "No value specified for 'KeyAttr' option in call to XML$dirn()"; } $opt->{keyattr} = [ @DefKeyAttr ]; } # Special cleanup for {valueattr} which could be arrayref or hashref if(exists($opt->{valueattr})) { if(ref($opt->{valueattr}) eq 'ARRAY') { $opt->{valueattrlist} = {}; $opt->{valueattrlist}->{$_} = 1 foreach(@{ delete $opt->{valueattr} }); } } # make sure there's nothing weird in {grouptags} if($opt->{grouptags}) { croak "Illegal value for 'GroupTags' option - expected a hashref" unless UNIVERSAL::isa($opt->{grouptags}, 'HASH'); while(my($key, $val) = each %{$opt->{grouptags}}) { next if $key ne $val; croak "Bad value in GroupTags: '$key' => '$val'"; } } # Check the {variables} option is valid and initialise variables hash if($opt->{variables} and !UNIVERSAL::isa($opt->{variables}, 'HASH')) { croak "Illegal value for 'Variables' option - expected a hashref"; } if($opt->{variables}) { $self->{_var_values} = { %{$opt->{variables}} }; } elsif($opt->{varattr}) { $self->{_var_values} = {}; } } ############################################################################## # Method: find_xml_file() # # Helper routine for XMLin(). # Takes a filename, and a list of directories, attempts to locate the file in # the directories listed. # Returns a full pathname on success; croaks on failure. # sub find_xml_file { my $self = shift; my $file = shift; my @search_path = @_; require File::Basename; require File::Spec; my($filename, $filedir) = File::Basename::fileparse($file); if($filename ne $file) { # Ignore searchpath if dir component return($file) if(-e $file); } else { my($path); foreach $path (@search_path) { my $fullpath = File::Spec->catfile($path, $file); return($fullpath) if(-e $fullpath); } } # If user did not supply a search path, default to current directory if(!@search_path) { return($file) if(-e $file); croak "File does not exist: $file"; } croak "Could not find $file in ", join(':', @search_path); } ############################################################################## # Method: collapse() # # Helper routine for XMLin(). This routine really comprises the 'smarts' (or # value add) of this module. # # Takes the parse tree that XML::Parser produced from the supplied XML and # recurses through it 'collapsing' unnecessary levels of indirection (nested # arrays etc) to produce a data structure that is easier to work with. # # Elements in the original parser tree are represented as an element name # followed by an arrayref. The first element of the array is a hashref # containing the attributes. The rest of the array contains a list of any # nested elements as name+arrayref pairs: # # , [ { }, , [ ... ], ... ] # # The special element name '0' (zero) flags text content. # # This routine cuts down the noise by discarding any text content consisting of # only whitespace and then moves the nested elements into the attribute hash # using the name of the nested element as the hash key and the collapsed # version of the nested element as the value. Multiple nested elements with # the same name will initially be represented as an arrayref, but this may be # 'folded' into a hashref depending on the value of the keyattr option. # sub collapse { my $self = shift; # Start with the hash of attributes my $attr = shift; if($self->{opt}->{noattr}) { # Discard if 'noattr' set $attr = $self->new_hashref; } elsif($self->{opt}->{normalisespace} == 2) { while(my($key, $value) = each %$attr) { $attr->{$key} = $self->normalise_space($value) } } # Do variable substitutions if(my $var = $self->{_var_values}) { while(my($key, $val) = each(%$attr)) { $val =~ s^\$\{([\w.]+)\}^ $self->get_var($1) ^ge; $attr->{$key} = $val; } } # Roll up 'value' attributes (but only if no nested elements) if(!@_ and keys %$attr == 1) { my($k) = keys %$attr; if($self->{opt}->{valueattrlist} and $self->{opt}->{valueattrlist}->{$k}) { return $attr->{$k}; } } # Add any nested elements my($key, $val); while(@_) { $key = shift; $val = shift; $val = '' if not defined $val; if(ref($val)) { $val = $self->collapse(@$val); next if(!defined($val) and $self->{opt}->{suppressempty}); } elsif($key eq '0') { next if($val =~ m{^\s*$}s); # Skip all whitespace content $val = $self->normalise_space($val) if($self->{opt}->{normalisespace} == 2); # do variable substitutions if(my $var = $self->{_var_values}) { $val =~ s^\$\{(\w+)\}^ $self->get_var($1) ^ge; } # look for variable definitions if(my $var = $self->{opt}->{varattr}) { if(exists $attr->{$var}) { $self->set_var($attr->{$var}, $val); } } # Collapse text content in element with no attributes to a string if(!%$attr and !@_) { return($self->{opt}->{forcecontent} ? { $self->{opt}->{contentkey} => $val } : $val ); } $key = $self->{opt}->{contentkey}; } # Combine duplicate attributes into arrayref if required if(exists($attr->{$key})) { if(UNIVERSAL::isa($attr->{$key}, 'ARRAY')) { push(@{$attr->{$key}}, $val); } else { $attr->{$key} = [ $attr->{$key}, $val ]; } } elsif(defined($val) and UNIVERSAL::isa($val, 'ARRAY')) { $attr->{$key} = [ $val ]; } else { if( $key ne $self->{opt}->{contentkey} and ( ($self->{opt}->{forcearray} == 1) or ( (ref($self->{opt}->{forcearray}) eq 'HASH') and ( $self->{opt}->{forcearray}->{$key} or (grep $key =~ $_, @{$self->{opt}->{forcearray}->{_regex}}) ) ) ) ) { $attr->{$key} = [ $val ]; } else { $attr->{$key} = $val; } } } # Turn arrayrefs into hashrefs if key fields present if($self->{opt}->{keyattr}) { while(($key,$val) = each %$attr) { if(defined($val) and UNIVERSAL::isa($val, 'ARRAY')) { $attr->{$key} = $self->array_to_hash($key, $val); } } } # disintermediate grouped tags if($self->{opt}->{grouptags}) { while(my($key, $val) = each(%$attr)) { next unless(UNIVERSAL::isa($val, 'HASH') and (keys %$val == 1)); next unless(exists($self->{opt}->{grouptags}->{$key})); my($child_key, $child_val) = %$val; if($self->{opt}->{grouptags}->{$key} eq $child_key) { $attr->{$key}= $child_val; } } } # Fold hashes containing a single anonymous array up into just the array my $count = scalar keys %$attr; if($count == 1 and exists $attr->{anon} and UNIVERSAL::isa($attr->{anon}, 'ARRAY') ) { return($attr->{anon}); } # Do the right thing if hash is empty, otherwise just return it if(!%$attr and exists($self->{opt}->{suppressempty})) { if(defined($self->{opt}->{suppressempty}) and $self->{opt}->{suppressempty} eq '') { return(''); } return(undef); } # Roll up named elements with named nested 'value' attributes if($self->{opt}->{valueattr}) { while(my($key, $val) = each(%$attr)) { next unless($self->{opt}->{valueattr}->{$key}); next unless(UNIVERSAL::isa($val, 'HASH') and (keys %$val == 1)); my($k) = keys %$val; next unless($k eq $self->{opt}->{valueattr}->{$key}); $attr->{$key} = $val->{$k}; } } return($attr) } ############################################################################## # Method: set_var() # # Called when a variable definition is encountered in the XML. (A variable # definition looks like value where attrname # matches the varattr setting). # sub set_var { my($self, $name, $value) = @_; $self->{_var_values}->{$name} = $value; } ############################################################################## # Method: get_var() # # Called during variable substitution to get the value for the named variable. # sub get_var { my($self, $name) = @_; my $value = $self->{_var_values}->{$name}; return $value if(defined($value)); return '${' . $name . '}'; } ############################################################################## # Method: normalise_space() # # Strips leading and trailing whitespace and collapses sequences of whitespace # characters to a single space. # sub normalise_space { my($self, $text) = @_; $text =~ s/^\s+//s; $text =~ s/\s+$//s; $text =~ s/\s\s+/ /sg; return $text; } ############################################################################## # Method: array_to_hash() # # Helper routine for collapse(). # Attempts to 'fold' an array of hashes into an hash of hashes. Returns a # reference to the hash on success or the original array if folding is # not possible. Behaviour is controlled by 'keyattr' option. # sub array_to_hash { my $self = shift; my $name = shift; my $arrayref = shift; my $hashref = $self->new_hashref; my($i, $key, $val, $flag); # Handle keyattr => { .... } if(ref($self->{opt}->{keyattr}) eq 'HASH') { return($arrayref) unless(exists($self->{opt}->{keyattr}->{$name})); ($key, $flag) = @{$self->{opt}->{keyattr}->{$name}}; for($i = 0; $i < @$arrayref; $i++) { if(UNIVERSAL::isa($arrayref->[$i], 'HASH') and exists($arrayref->[$i]->{$key}) ) { $val = $arrayref->[$i]->{$key}; if(ref($val)) { $self->die_or_warn("<$name> element has non-scalar '$key' key attribute"); return($arrayref); } $val = $self->normalise_space($val) if($self->{opt}->{normalisespace} == 1); $self->die_or_warn("<$name> element has non-unique value in '$key' key attribute: $val") if(exists($hashref->{$val})); $hashref->{$val} = $self->new_hashref( %{$arrayref->[$i]} ); $hashref->{$val}->{"-$key"} = $hashref->{$val}->{$key} if($flag eq '-'); delete $hashref->{$val}->{$key} unless($flag eq '+'); } else { $self->die_or_warn("<$name> element has no '$key' key attribute"); return($arrayref); } } } # Or assume keyattr => [ .... ] else { my $default_keys = join(',', @DefKeyAttr) eq join(',', @{$self->{opt}->{keyattr}}); ELEMENT: for($i = 0; $i < @$arrayref; $i++) { return($arrayref) unless(UNIVERSAL::isa($arrayref->[$i], 'HASH')); foreach $key (@{$self->{opt}->{keyattr}}) { if(defined($arrayref->[$i]->{$key})) { $val = $arrayref->[$i]->{$key}; if(ref($val)) { $self->die_or_warn("<$name> element has non-scalar '$key' key attribute") if not $default_keys; return($arrayref); } $val = $self->normalise_space($val) if($self->{opt}->{normalisespace} == 1); $self->die_or_warn("<$name> element has non-unique value in '$key' key attribute: $val") if(exists($hashref->{$val})); $hashref->{$val} = $self->new_hashref( %{$arrayref->[$i]} ); delete $hashref->{$val}->{$key}; next ELEMENT; } } return($arrayref); # No keyfield matched } } # collapse any hashes which now only have a 'content' key if($self->{opt}->{collapseagain}) { $hashref = $self->collapse_content($hashref); } return($hashref); } ############################################################################## # Method: die_or_warn() # # Takes a diagnostic message and does one of three things: # 1. dies if strict mode is enabled # 2. warns if warnings are enabled but strict mode is not # 3. ignores message and returns silently if neither strict mode nor warnings # are enabled # sub die_or_warn { my $self = shift; my $msg = shift; croak $msg if($self->{opt}->{strictmode}); if(warnings::enabled()) { carp "Warning: $msg"; } } ############################################################################## # Method: new_hashref() # # This is a hook routine for overriding in a sub-class. Some people believe # that using Tie::IxHash here will solve order-loss problems. # sub new_hashref { my $self = shift; return { @_ }; } ############################################################################## # Method: collapse_content() # # Helper routine for array_to_hash # # Arguments expected are: # - an XML::Simple object # - a hashref # the hashref is a former array, turned into a hash by array_to_hash because # of the presence of key attributes # at this point collapse_content avoids over-complicated structures like # dir => { libexecdir => { content => '$exec_prefix/libexec' }, # localstatedir => { content => '$prefix' }, # } # into # dir => { libexecdir => '$exec_prefix/libexec', # localstatedir => '$prefix', # } sub collapse_content { my $self = shift; my $hashref = shift; my $contentkey = $self->{opt}->{contentkey}; # first go through the values,checking that they are fit to collapse foreach my $val (values %$hashref) { return $hashref unless ( (ref($val) eq 'HASH') and (keys %$val == 1) and (exists $val->{$contentkey}) ); } # now collapse them foreach my $key (keys %$hashref) { $hashref->{$key}= $hashref->{$key}->{$contentkey}; } return $hashref; } ############################################################################## # Method: value_to_xml() # # Helper routine for XMLout() - recurses through a data structure building up # and returning an XML representation of that structure as a string. # # Arguments expected are: # - the data structure to be encoded (usually a reference) # - the XML tag name to use for this item # - a string of spaces for use as the current indent level # sub value_to_xml { my $self = shift;; # Grab the other arguments my($ref, $name, $indent) = @_; my $named = (defined($name) and $name ne '' ? 1 : 0); my $nl = "\n"; my $is_root = $indent eq '' ? 1 : 0; # Warning, dirty hack! if($self->{opt}->{noindent}) { $indent = ''; $nl = ''; } # Convert to XML my $refaddr = Scalar::Util::refaddr($ref); if($refaddr) { croak "circular data structures not supported" if $self->{_ancestors}->{$refaddr}; $self->{_ancestors}->{$refaddr} = $ref; # keep ref alive until we delete it } else { if($named) { return(join('', $indent, '<', $name, '>', ($self->{opt}->{noescape} ? $ref : $self->escape_value($ref)), '", $nl )); } else { return("$ref$nl"); } } # Unfold hash to array if possible if(UNIVERSAL::isa($ref, 'HASH') # It is a hash and keys %$ref # and it's not empty and $self->{opt}->{keyattr} # and folding is enabled and !$is_root # and its not the root element ) { $ref = $self->hash_to_array($name, $ref); } my @result = (); my($key, $value); # Handle hashrefs if(UNIVERSAL::isa($ref, 'HASH')) { # Reintermediate grouped values if applicable if($self->{opt}->{grouptags}) { $ref = $self->copy_hash($ref); while(my($key, $val) = each %$ref) { if($self->{opt}->{grouptags}->{$key}) { $ref->{$key} = $self->new_hashref( $self->{opt}->{grouptags}->{$key} => $val ); } } } # Scan for namespace declaration attributes my $nsdecls = ''; my $default_ns_uri; if($self->{nsup}) { $ref = $self->copy_hash($ref); $self->{nsup}->push_context(); # Look for default namespace declaration first if(exists($ref->{xmlns})) { $self->{nsup}->declare_prefix('', $ref->{xmlns}); $nsdecls .= qq( xmlns="$ref->{xmlns}"); delete($ref->{xmlns}); } $default_ns_uri = $self->{nsup}->get_uri(''); # Then check all the other keys foreach my $qname (keys(%$ref)) { my($uri, $lname) = $self->{nsup}->parse_jclark_notation($qname); if($uri) { if($uri eq $xmlns_ns) { $self->{nsup}->declare_prefix($lname, $ref->{$qname}); $nsdecls .= qq( xmlns:$lname="$ref->{$qname}"); delete($ref->{$qname}); } } } # Translate any remaining Clarkian names foreach my $qname (keys(%$ref)) { my($uri, $lname) = $self->{nsup}->parse_jclark_notation($qname); if($uri) { if($default_ns_uri and $uri eq $default_ns_uri) { $ref->{$lname} = $ref->{$qname}; delete($ref->{$qname}); } else { my $prefix = $self->{nsup}->get_prefix($uri); unless($prefix) { # $self->{nsup}->declare_prefix(undef, $uri); # $prefix = $self->{nsup}->get_prefix($uri); $prefix = $self->{ns_prefix}++; $self->{nsup}->declare_prefix($prefix, $uri); $nsdecls .= qq( xmlns:$prefix="$uri"); } $ref->{"$prefix:$lname"} = $ref->{$qname}; delete($ref->{$qname}); } } } } my @nested = (); my $text_content = undef; if($named) { push @result, $indent, '<', $name, $nsdecls; } if(keys %$ref) { my $first_arg = 1; foreach my $key ($self->sorted_keys($name, $ref)) { my $value = $ref->{$key}; next if(substr($key, 0, 1) eq '-'); if(!defined($value)) { next if $self->{opt}->{suppressempty}; unless(exists($self->{opt}->{suppressempty}) and !defined($self->{opt}->{suppressempty}) ) { carp 'Use of uninitialized value' if warnings::enabled(); } if($key eq $self->{opt}->{contentkey}) { $text_content = ''; } else { $value = exists($self->{opt}->{suppressempty}) ? {} : ''; } } if(!ref($value) and $self->{opt}->{valueattr} and $self->{opt}->{valueattr}->{$key} ) { $value = $self->new_hashref( $self->{opt}->{valueattr}->{$key} => $value ); } if(ref($value) or $self->{opt}->{noattr}) { push @nested, $self->value_to_xml($value, $key, "$indent "); } else { if($key eq $self->{opt}->{contentkey}) { $value = $self->escape_value($value) unless($self->{opt}->{noescape}); $text_content = $value; } else { $value = $self->escape_attr($value) unless($self->{opt}->{noescape}); push @result, "\n$indent " . ' ' x length($name) if($self->{opt}->{attrindent} and !$first_arg); push @result, ' ', $key, '="', $value , '"'; $first_arg = 0; } } } } else { $text_content = ''; } if(@nested or defined($text_content)) { if($named) { push @result, ">"; if(defined($text_content)) { push @result, $text_content; $nested[0] =~ s/^\s+// if(@nested); } else { push @result, $nl; } if(@nested) { push @result, @nested, $indent; } push @result, '", $nl; } else { push @result, @nested; # Special case if no root elements } } else { push @result, " />", $nl; } $self->{nsup}->pop_context() if($self->{nsup}); } # Handle arrayrefs elsif(UNIVERSAL::isa($ref, 'ARRAY')) { foreach $value (@$ref) { next if !defined($value) and $self->{opt}->{suppressempty}; if(!ref($value)) { push @result, $indent, '<', $name, '>', ($self->{opt}->{noescape} ? $value : $self->escape_value($value)), '$nl"; } elsif(UNIVERSAL::isa($value, 'HASH')) { push @result, $self->value_to_xml($value, $name, $indent); } else { push @result, $indent, '<', $name, ">$nl", $self->value_to_xml($value, 'anon', "$indent "), $indent, '$nl"; } } } else { croak "Can't encode a value of type: " . ref($ref); } delete $self->{_ancestors}->{$refaddr}; return(join('', @result)); } ############################################################################## # Method: sorted_keys() # # Returns the keys of the referenced hash sorted into alphabetical order, but # with the 'key' key (as in KeyAttr) first, if there is one. # sub sorted_keys { my($self, $name, $ref) = @_; return keys %$ref if $self->{opt}->{nosort}; my %hash = %$ref; my $keyattr = $self->{opt}->{keyattr}; my @key; if(ref $keyattr eq 'HASH') { if(exists $keyattr->{$name} and exists $hash{$keyattr->{$name}->[0]}) { push @key, $keyattr->{$name}->[0]; delete $hash{$keyattr->{$name}->[0]}; } } elsif(ref $keyattr eq 'ARRAY') { foreach (@{$keyattr}) { if(exists $hash{$_}) { push @key, $_; delete $hash{$_}; last; } } } return(@key, sort keys %hash); } ############################################################################## # Method: escape_value() # # Helper routine for automatically escaping values for XMLout(). # Expects a scalar data value. Returns escaped version. # sub escape_value { my($self, $data) = @_; return '' unless(defined($data)); $data =~ s/&/&/sg; $data =~ s//>/sg; $data =~ s/"/"/sg; my $level = $self->{opt}->{numericescape} or return $data; return $self->numeric_escape($data, $level); } sub numeric_escape { my($self, $data, $level) = @_; if($self->{opt}->{numericescape} eq '2') { $data =~ s/([^\x00-\x7F])/'&#' . ord($1) . ';'/gse; } else { $data =~ s/([^\x00-\xFF])/'&#' . ord($1) . ';'/gse; } return $data; } ############################################################################## # Method: escape_attr() # # Helper routine for escaping attribute values. Defaults to escape_value(), # but may be overridden by a subclass to customise behaviour. # sub escape_attr { my $self = shift; return $self->escape_value(@_); } ############################################################################## # Method: hash_to_array() # # Helper routine for value_to_xml(). # Attempts to 'unfold' a hash of hashes into an array of hashes. Returns a # reference to the array on success or the original hash if unfolding is # not possible. # sub hash_to_array { my $self = shift; my $parent = shift; my $hashref = shift; my $arrayref = []; my($key, $value); my @keys = $self->{opt}->{nosort} ? keys %$hashref : sort keys %$hashref; foreach $key (@keys) { $value = $hashref->{$key}; return($hashref) unless(UNIVERSAL::isa($value, 'HASH')); if(ref($self->{opt}->{keyattr}) eq 'HASH') { return($hashref) unless(defined($self->{opt}->{keyattr}->{$parent})); push @$arrayref, $self->copy_hash( $value, $self->{opt}->{keyattr}->{$parent}->[0] => $key ); } else { push(@$arrayref, { $self->{opt}->{keyattr}->[0] => $key, %$value }); } } return($arrayref); } ############################################################################## # Method: copy_hash() # # Helper routine for hash_to_array(). When unfolding a hash of hashes into # an array of hashes, we need to copy the key from the outer hash into the # inner hash. This routine makes a copy of the original hash so we don't # destroy the original data structure. You might wish to override this # method if you're using tied hashes and don't want them to get untied. # sub copy_hash { my($self, $orig, @extra) = @_; return { @extra, %$orig }; } ############################################################################## # Methods required for building trees from SAX events ############################################################################## sub start_document { my $self = shift; $self->handle_options('in') unless($self->{opt}); $self->{lists} = []; $self->{curlist} = $self->{tree} = []; } sub start_element { my $self = shift; my $element = shift; my $name = $element->{Name}; if($self->{opt}->{nsexpand}) { $name = $element->{LocalName} || ''; if($element->{NamespaceURI}) { $name = '{' . $element->{NamespaceURI} . '}' . $name; } } my $attributes = {}; if($element->{Attributes}) { # Might be undef foreach my $attr (values %{$element->{Attributes}}) { if($self->{opt}->{nsexpand}) { my $name = $attr->{LocalName} || ''; if($attr->{NamespaceURI}) { $name = '{' . $attr->{NamespaceURI} . '}' . $name } $name = 'xmlns' if($name eq $bad_def_ns_jcn); $attributes->{$name} = $attr->{Value}; } else { $attributes->{$attr->{Name}} = $attr->{Value}; } } } my $newlist = [ $attributes ]; push @{ $self->{lists} }, $self->{curlist}; push @{ $self->{curlist} }, $name => $newlist; $self->{curlist} = $newlist; } sub characters { my $self = shift; my $chars = shift; my $text = $chars->{Data}; my $clist = $self->{curlist}; my $pos = $#$clist; if ($pos > 0 and $clist->[$pos - 1] eq '0') { $clist->[$pos] .= $text; } else { push @$clist, 0 => $text; } } sub end_element { my $self = shift; $self->{curlist} = pop @{ $self->{lists} }; } sub end_document { my $self = shift; delete($self->{curlist}); delete($self->{lists}); my $tree = $self->{tree}; delete($self->{tree}); # Return tree as-is to XMLin() return($tree) if($self->{nocollapse}); # Or collapse it before returning it to SAX parser class if($self->{opt}->{keeproot}) { $tree = $self->collapse({}, @$tree); } else { $tree = $self->collapse(@{$tree->[1]}); } if($self->{opt}->{datahandler}) { return($self->{opt}->{datahandler}->($self, $tree)); } return($tree); } *xml_in = \&XMLin; *xml_out = \&XMLout; 1; __END__ =head1 STATUS OF THIS MODULE The use of this module in new code is B. Other modules are available which provide more straightforward and consistent interfaces. In particular, L is highly recommended and you can refer to L for a tutorial introduction. L is another excellent alternative. The major problems with this module are the large number of options (some of which have unfortunate defaults) and the arbitrary ways in which these options interact - often producing unexpected results. Patches with bug fixes and documentation fixes are welcome, but new features are unlikely to be added. =head1 QUICK START Say you have a script called B and a file of configuration options called B containing the following:
10.0.0.101
10.0.1.101
10.0.0.102
10.0.0.103
10.0.1.103
The following lines of code in B: use XML::Simple qw(:strict); my $config = XMLin(undef, KeyAttr => { server => 'name' }, ForceArray => [ 'server', 'address' ]); will 'slurp' the configuration options into the hashref $config (because no filename or XML string was passed as the first argument to C the name and location of the XML file will be inferred from name and location of the script). You can dump out the contents of the hashref using Data::Dumper: use Data::Dumper; print Dumper($config); which will produce something like this (formatting has been adjusted for brevity): { 'logdir' => '/var/log/foo/', 'debugfile' => '/tmp/foo.debug', 'server' => { 'sahara' => { 'osversion' => '2.6', 'osname' => 'solaris', 'address' => [ '10.0.0.101', '10.0.1.101' ] }, 'gobi' => { 'osversion' => '6.5', 'osname' => 'irix', 'address' => [ '10.0.0.102' ] }, 'kalahari' => { 'osversion' => '2.0.34', 'osname' => 'linux', 'address' => [ '10.0.0.103', '10.0.1.103' ] } } } Your script could then access the name of the log directory like this: print $config->{logdir}; similarly, the second address on the server 'kalahari' could be referenced as: print $config->{server}->{kalahari}->{address}->[1]; Note: If the mapping between the output of Data::Dumper and the print statements above is not obvious to you, then please refer to the 'references' tutorial (AKA: "Mark's very short tutorial about references") at L. In this example, the C<< ForceArray >> option was used to list elements that might occur multiple times and should therefore be represented as arrayrefs (even when only one element is present). The C<< KeyAttr >> option was used to indicate that each C<< >> element has a unique identifier in the C<< name >> attribute. This allows you to index directly to a particular server record using the name as a hash key (as shown above). For simple requirements, that's really all there is to it. If you want to store your XML in a different directory or file, or pass it in as a string or even pass it in via some derivative of an IO::Handle, you'll need to check out L<"OPTIONS">. If you want to turn off or tweak the array folding feature (that neat little transformation that produced $config->{server}) you'll find options for that as well. If you want to generate XML (for example to write a modified version of $config back out as XML), check out C. If your needs are not so simple, this may not be the module for you. In that case, you might want to read L<"WHERE TO FROM HERE?">. =head1 DESCRIPTION The XML::Simple module provides a simple API layer on top of an underlying XML parsing module (either XML::Parser or one of the SAX2 parser modules). Two functions are exported: C and C. Note: you can explicitly request the lower case versions of the function names: C and C. The simplest approach is to call these two functions directly, but an optional object oriented interface (see L<"OPTIONAL OO INTERFACE"> below) allows them to be called as methods of an B object. The object interface can also be used at either end of a SAX pipeline. =head2 XMLin() Parses XML formatted data and returns a reference to a data structure which contains the same information in a more readily accessible form. (Skip down to L<"EXAMPLES"> below, for more sample code). C accepts an optional XML specifier followed by zero or more 'name => value' option pairs. The XML specifier can be one of the following: =over 4 =item A filename If the filename contains no directory components C will look for the file in each directory in the SearchPath (see L<"OPTIONS"> below) or in the current directory if the SearchPath option is not defined. eg: $ref = XMLin('/etc/params.xml'); Note, the filename '-' can be used to parse from STDIN. =item undef If there is no XML specifier, C will check the script directory and each of the SearchPath directories for a file with the same name as the script but with the extension '.xml'. Note: if you wish to specify options, you must specify the value 'undef'. eg: $ref = XMLin(undef, ForceArray => 1); =item A string of XML A string containing XML (recognised by the presence of '<' and '>' characters) will be parsed directly. eg: $ref = XMLin(''); =item An IO::Handle object An IO::Handle object will be read to EOF and its contents parsed. eg: $fh = IO::File->new('/etc/params.xml'); $ref = XMLin($fh); =back =head2 XMLout() Takes a data structure (generally a hashref) and returns an XML encoding of that structure. If the resulting XML is parsed using C, it should return a data structure equivalent to the original (see caveats below). The C function can also be used to output the XML as SAX events see the C option and L<"SAX SUPPORT"> for more details). When translating hashes to XML, hash keys which have a leading '-' will be silently skipped. This is the approved method for marking elements of a data structure which should be ignored by C. (Note: If these items were not skipped the key names would be emitted as element or attribute names with a leading '-' which would not be valid XML). =head2 Caveats Some care is required in creating data structures which will be passed to C. Hash keys from the data structure will be encoded as either XML element names or attribute names. Therefore, you should use hash key names which conform to the relatively strict XML naming rules: Names in XML must begin with a letter. The remaining characters may be letters, digits, hyphens (-), underscores (_) or full stops (.). It is also allowable to include one colon (:) in an element name but this should only be used when working with namespaces (B can only usefully work with namespaces when teamed with a SAX Parser). You can use other punctuation characters in hash values (just not in hash keys) however B does not support dumping binary data. If you break these rules, the current implementation of C will simply emit non-compliant XML which will be rejected if you try to read it back in. (A later version of B might take a more proactive approach). Note also that although you can nest hashes and arrays to arbitrary levels, circular data structures are not supported and will cause C to die. If you wish to 'round-trip' arbitrary data structures from Perl to XML and back to Perl, then you should probably disable array folding (using the KeyAttr option) both with C and with C. If you still don't get the expected results, you may prefer to use L which is designed for exactly that purpose. Refer to L<"WHERE TO FROM HERE?"> if C is too simple for your needs. =head1 OPTIONS B supports a number of options (in fact as each release of B adds more options, the module's claim to the name 'Simple' becomes increasingly tenuous). If you find yourself repeatedly having to specify the same options, you might like to investigate L<"OPTIONAL OO INTERFACE"> below. If you can't be bothered reading the documentation, refer to L<"STRICT MODE"> to automatically catch common mistakes. Because there are so many options, it's hard for new users to know which ones are important, so here are the two you really need to know about: =over 4 =item * check out C because you'll almost certainly want to turn it on =item * make sure you know what the C option does and what its default value is because it may surprise you otherwise (note in particular that 'KeyAttr' affects both C and C) =back The option name headings below have a trailing 'comment' - a hash followed by two pieces of metadata: =over 4 =item * Options are marked with 'I' if they are recognised by C and 'I' if they are recognised by C. =item * Each option is also flagged to indicate whether it is: 'important' - don't use the module until you understand this one 'handy' - you can skip this on the first time through 'advanced' - you can skip this on the second time through 'SAX only' - don't worry about this unless you're using SAX (or alternatively if you need this, you also need SAX) 'seldom used' - you'll probably never use this unless you were the person that requested the feature =back The options are listed alphabetically: Note: option names are no longer case sensitive so you can use the mixed case versions shown here; all lower case as required by versions 2.03 and earlier; or you can add underscores between the words (eg: key_attr). =head2 AttrIndent => 1 I<# out - handy> When you are using C, enable this option to have attributes printed one-per-line with sensible indentation rather than all on one line. =head2 Cache => [ cache schemes ] I<# in - advanced> Because loading the B module and parsing an XML file can consume a significant number of CPU cycles, it is often desirable to cache the output of C for later reuse. When parsing from a named file, B supports a number of caching schemes. The 'Cache' option may be used to specify one or more schemes (using an anonymous array). Each scheme will be tried in turn in the hope of finding a cached pre-parsed representation of the XML file. If no cached copy is found, the file will be parsed and the first cache scheme in the list will be used to save a copy of the results. The following cache schemes have been implemented: =over 4 =item storable Utilises B to read/write a cache file with the same name as the XML file but with the extension .stor =item memshare When a file is first parsed, a copy of the resulting data structure is retained in memory in the B module's namespace. Subsequent calls to parse the same file will return a reference to this structure. This cached version will persist only for the life of the Perl interpreter (which in the case of mod_perl for example, may be some significant time). Because each caller receives a reference to the same data structure, a change made by one caller will be visible to all. For this reason, the reference returned should be treated as read-only. =item memcopy This scheme works identically to 'memshare' (above) except that each caller receives a reference to a new data structure which is a copy of the cached version. Copying the data structure will add a little processing overhead, therefore this scheme should only be used where the caller intends to modify the data structure (or wishes to protect itself from others who might). This scheme uses B to perform the copy. =back Warning! The memory-based caching schemes compare the timestamp on the file to the time when it was last parsed. If the file is stored on an NFS filesystem (or other network share) and the clock on the file server is not exactly synchronised with the clock where your script is run, updates to the source XML file may appear to be ignored. =head2 ContentKey => 'keyname' I<# in+out - seldom used> When text content is parsed to a hash value, this option lets you specify a name for the hash key to override the default 'content'. So for example: XMLin('Text', ContentKey => 'text') will parse to: { 'one' => 1, 'text' => 'Text' } instead of: { 'one' => 1, 'content' => 'Text' } C will also honour the value of this option when converting a hashref to XML. You can also prefix your selected key name with a '-' character to have C try a little harder to eliminate unnecessary 'content' keys after array folding. For example: XMLin( 'FirstSecond', KeyAttr => {item => 'name'}, ForceArray => [ 'item' ], ContentKey => '-content' ) will parse to: { 'item' => { 'one' => 'First' 'two' => 'Second' } } rather than this (without the '-'): { 'item' => { 'one' => { 'content' => 'First' } 'two' => { 'content' => 'Second' } } } =head2 DataHandler => code_ref I<# in - SAX only> When you use an B object as a SAX handler, it will return a 'simple tree' data structure in the same format as C would return. If this option is set (to a subroutine reference), then when the tree is built the subroutine will be called and passed two arguments: a reference to the B object and a reference to the data tree. The return value from the subroutine will be returned to the SAX driver. (See L<"SAX SUPPORT"> for more details). =head2 ForceArray => 1 I<# in - important> This option should be set to '1' to force nested elements to be represented as arrays even when there is only one. Eg, with ForceArray enabled, this XML: value would parse to this: { 'name' => [ 'value' ] } instead of this (the default): { 'name' => 'value' } This option is especially useful if the data structure is likely to be written back out as XML and the default behaviour of rolling single nested elements up into attributes is not desirable. If you are using the array folding feature, you should almost certainly enable this option. If you do not, single nested elements will not be parsed to arrays and therefore will not be candidates for folding to a hash. (Given that the default value of 'KeyAttr' enables array folding, the default value of this option should probably also have been enabled too - sorry). =head2 ForceArray => [ names ] I<# in - important> This alternative (and preferred) form of the 'ForceArray' option allows you to specify a list of element names which should always be forced into an array representation, rather than the 'all or nothing' approach above. It is also possible (since version 2.05) to include compiled regular expressions in the list - any element names which match the pattern will be forced to arrays. If the list contains only a single regex, then it is not necessary to enclose it in an arrayref. Eg: ForceArray => qr/_list$/ =head2 ForceContent => 1 I<# in - seldom used> When C parses elements which have text content as well as attributes, the text content must be represented as a hash value rather than a simple scalar. This option allows you to force text content to always parse to a hash value even when there are no attributes. So for example: XMLin('text1text2', ForceContent => 1) will parse to: { 'x' => { 'content' => 'text1' }, 'y' => { 'a' => 2, 'content' => 'text2' } } instead of: { 'x' => 'text1', 'y' => { 'a' => 2, 'content' => 'text2' } } =head2 GroupTags => { grouping tag => grouped tag } I<# in+out - handy> You can use this option to eliminate extra levels of indirection in your Perl data structure. For example this XML: /usr/bin /usr/local/bin /usr/X11/bin Would normally be read into a structure like this: { searchpath => { dir => [ '/usr/bin', '/usr/local/bin', '/usr/X11/bin' ] } } But when read in with the appropriate value for 'GroupTags': my $opt = XMLin($xml, GroupTags => { searchpath => 'dir' }); It will return this simpler structure: { searchpath => [ '/usr/bin', '/usr/local/bin', '/usr/X11/bin' ] } The grouping element (C<< >> in the example) must not contain any attributes or elements other than the grouped element. You can specify multiple 'grouping element' to 'grouped element' mappings in the same hashref. If this option is combined with C, the array folding will occur first and then the grouped element names will be eliminated. C will also use the grouptag mappings to re-introduce the tags around the grouped elements. Beware though that this will occur in all places that the 'grouping tag' name occurs - you probably don't want to use the same name for elements as well as attributes. =head2 Handler => object_ref I<# out - SAX only> Use the 'Handler' option to have C generate SAX events rather than returning a string of XML. For more details see L<"SAX SUPPORT"> below. Note: the current implementation of this option generates a string of XML and uses a SAX parser to translate it into SAX events. The normal encoding rules apply here - your data must be UTF8 encoded unless you specify an alternative encoding via the 'XMLDecl' option; and by the time the data reaches the handler object, it will be in UTF8 form regardless of the encoding you supply. A future implementation of this option may generate the events directly. =head2 KeepRoot => 1 I<# in+out - handy> In its attempt to return a data structure free of superfluous detail and unnecessary levels of indirection, C normally discards the root element name. Setting the 'KeepRoot' option to '1' will cause the root element name to be retained. So after executing this code: $config = XMLin('', KeepRoot => 1) You'll be able to reference the tempdir as C<$config-E{config}-E{tempdir}> instead of the default C<$config-E{tempdir}>. Similarly, setting the 'KeepRoot' option to '1' will tell C that the data structure already contains a root element name and it is not necessary to add another. =head2 KeyAttr => [ list ] I<# in+out - important> This option controls the 'array folding' feature which translates nested elements from an array to a hash. It also controls the 'unfolding' of hashes to arrays. For example, this XML: would, by default, parse to this: { 'user' => [ { 'login' => 'grep', 'fullname' => 'Gary R Epstein' }, { 'login' => 'stty', 'fullname' => 'Simon T Tyson' } ] } If the option 'KeyAttr => "login"' were used to specify that the 'login' attribute is a key, the same XML would parse to: { 'user' => { 'stty' => { 'fullname' => 'Simon T Tyson' }, 'grep' => { 'fullname' => 'Gary R Epstein' } } } The key attribute names should be supplied in an arrayref if there is more than one. C will attempt to match attribute names in the order supplied. C will use the first attribute name supplied when 'unfolding' a hash into an array. Note 1: The default value for 'KeyAttr' is ['name', 'key', 'id']. If you do not want folding on input or unfolding on output you must set this option to an empty list to disable the feature. Note 2: If you wish to use this option, you should also enable the C option. Without 'ForceArray', a single nested element will be rolled up into a scalar rather than an array and therefore will not be folded (since only arrays get folded). =head2 KeyAttr => { list } I<# in+out - important> This alternative (and preferred) method of specifying the key attributes allows more fine grained control over which elements are folded and on which attributes. For example the option 'KeyAttr => { package => 'id' } will cause any package elements to be folded on the 'id' attribute. No other elements which have an 'id' attribute will be folded at all. Note: C will generate a warning (or a fatal error in L<"STRICT MODE">) if this syntax is used and an element which does not have the specified key attribute is encountered (eg: a 'package' element without an 'id' attribute, to use the example above). Warnings can be suppressed with the lexical C pragma or C. Two further variations are made possible by prefixing a '+' or a '-' character to the attribute name: The option 'KeyAttr => { user => "+login" }' will cause this XML: to parse to this data structure: { 'user' => { 'stty' => { 'fullname' => 'Simon T Tyson', 'login' => 'stty' }, 'grep' => { 'fullname' => 'Gary R Epstein', 'login' => 'grep' } } } The '+' indicates that the value of the key attribute should be copied rather than moved to the folded hash key. A '-' prefix would produce this result: { 'user' => { 'stty' => { 'fullname' => 'Simon T Tyson', '-login' => 'stty' }, 'grep' => { 'fullname' => 'Gary R Epstein', '-login' => 'grep' } } } As described earlier, C will ignore hash keys starting with a '-'. =head2 NoAttr => 1 I<# in+out - handy> When used with C, the generated XML will contain no attributes. All hash key/values will be represented as nested elements instead. When used with C, any attributes in the XML will be ignored. =head2 NoEscape => 1 I<# out - seldom used> By default, C will translate the characters 'E', 'E', '&' and '"' to '<', '>', '&' and '"' respectively. Use this option to suppress escaping (presumably because you've already escaped the data in some more sophisticated manner). =head2 NoIndent => 1 I<# out - seldom used> Set this option to 1 to disable C's default 'pretty printing' mode. With this option enabled, the XML output will all be on one line (unless there are newlines in the data) - this may be easier for downstream processing. =head2 NoSort => 1 I<# out - seldom used> Newer versions of XML::Simple sort elements and attributes alphabetically (*), by default. Enable this option to suppress the sorting - possibly for backwards compatibility. * Actually, sorting is alphabetical but 'key' attribute or element names (as in 'KeyAttr') sort first. Also, when a hash of hashes is 'unfolded', the elements are sorted alphabetically by the value of the key field. =head2 NormaliseSpace => 0 | 1 | 2 I<# in - handy> This option controls how whitespace in text content is handled. Recognised values for the option are: =over 4 =item * 0 = (default) whitespace is passed through unaltered (except of course for the normalisation of whitespace in attribute values which is mandated by the XML recommendation) =item * 1 = whitespace is normalised in any value used as a hash key (normalising means removing leading and trailing whitespace and collapsing sequences of whitespace characters to a single space) =item * 2 = whitespace is normalised in all text content =back Note: you can spell this option with a 'z' if that is more natural for you. =head2 NSExpand => 1 I<# in+out handy - SAX only> This option controls namespace expansion - the translation of element and attribute names of the form 'prefix:name' to '{uri}name'. For example the element name 'xsl:template' might be expanded to: '{http://www.w3.org/1999/XSL/Transform}template'. By default, C will return element names and attribute names exactly as they appear in the XML. Setting this option to 1 will cause all element and attribute names to be expanded to include their namespace prefix. I. This option also controls whether C performs the reverse translation from '{uri}name' back to 'prefix:name'. The default is no translation. If your data contains expanded names, you should set this option to 1 otherwise C will emit XML which is not well formed. I to translate URIs back to prefixes>. =head2 NumericEscape => 0 | 1 | 2 I<# out - handy> Use this option to have 'high' (non-ASCII) characters in your Perl data structure converted to numeric entities (eg: €) in the XML output. Three levels are possible: 0 - default: no numeric escaping (OK if you're writing out UTF8) 1 - only characters above 0xFF are escaped (ie: characters in the 0x80-FF range are not escaped), possibly useful with ISO8859-1 output 2 - all characters above 0x7F are escaped (good for plain ASCII output) =head2 OutputFile => I<# out - handy> The default behaviour of C is to return the XML as a string. If you wish to write the XML to a file, simply supply the filename using the 'OutputFile' option. This option also accepts an IO handle object - especially useful in Perl 5.8.0 and later for output using an encoding other than UTF-8, eg: open my $fh, '>:encoding(iso-8859-1)', $path or die "open($path): $!"; XMLout($ref, OutputFile => $fh); Note, XML::Simple does not require that the object you pass in to the OutputFile option inherits from L - it simply assumes the object supports a C method. =head2 ParserOpts => [ XML::Parser Options ] I<# in - don't use this> I. This option allows you to pass parameters to the constructor of the underlying XML::Parser object (which of course assumes you're not using SAX). =head2 RootName => 'string' I<# out - handy> By default, when C generates XML, the root element will be named 'opt'. This option allows you to specify an alternative name. Specifying either undef or the empty string for the RootName option will produce XML with no root elements. In most cases the resulting XML fragment will not be 'well formed' and therefore could not be read back in by C. Nevertheless, the option has been found to be useful in certain circumstances. =head2 SearchPath => [ list ] I<# in - handy> If you pass C a filename, but the filename include no directory component, you can use this option to specify which directories should be searched to locate the file. You might use this option to search first in the user's home directory, then in a global directory such as /etc. If a filename is provided to C but SearchPath is not defined, the file is assumed to be in the current directory. If the first parameter to C is undefined, the default SearchPath will contain only the directory in which the script itself is located. Otherwise the default SearchPath will be empty. =head2 StrictMode => 1 | 0 I<# in+out seldom used> This option allows you to turn L on or off for a particular call, regardless of whether it was enabled at the time XML::Simple was loaded. =head2 SuppressEmpty => 1 | '' | undef I<# in+out - handy> This option controls what C should do with empty elements (no attributes and no content). The default behaviour is to represent them as empty hashes. Setting this option to a true value (eg: 1) will cause empty elements to be skipped altogether. Setting the option to 'undef' or the empty string will cause empty elements to be represented as the undefined value or the empty string respectively. The latter two alternatives are a little easier to test for in your code than a hash with no keys. The option also controls what C does with undefined values. Setting the option to undef causes undefined values to be output as empty elements (rather than empty attributes), it also suppresses the generation of warnings about undefined values. Setting the option to a true value (eg: 1) causes undefined values to be skipped altogether on output. =head2 ValueAttr => [ names ] I<# in - handy> Use this option to deal elements which always have a single attribute and no content. Eg: Setting C<< ValueAttr => [ 'value' ] >> will cause the above XML to parse to: { colour => 'red', size => 'XXL' } instead of this (the default): { colour => { value => 'red' }, size => { value => 'XXL' } } Note: This form of the ValueAttr option is not compatible with C - since the attribute name is discarded at parse time, the original XML cannot be reconstructed. =head2 ValueAttr => { element => attribute, ... } I<# in+out - handy> This (preferred) form of the ValueAttr option requires you to specify both the element and the attribute names. This is not only safer, it also allows the original XML to be reconstructed by C. Note: You probably don't want to use this option and the NoAttr option at the same time. =head2 Variables => { name => value } I<# in - handy> This option allows variables in the XML to be expanded when the file is read. (there is no facility for putting the variable names back if you regenerate XML using C). A 'variable' is any text of the form C<${name}> which occurs in an attribute value or in the text content of an element. If 'name' matches a key in the supplied hashref, C<${name}> will be replaced with the corresponding value from the hashref. If no matching key is found, the variable will not be replaced. Names must match the regex: C<[\w.]+> (ie: only 'word' characters and dots are allowed). =head2 VarAttr => 'attr_name' I<# in - handy> In addition to the variables defined using C, this option allows variables to be defined in the XML. A variable definition consists of an element with an attribute called 'attr_name' (the value of the C option). The value of the attribute will be used as the variable name and the text content of the element will be used as the value. A variable defined in this way will override a variable defined using the C option. For example: XMLin( ' /usr/local/apache ${prefix} ${exec_prefix}/bin ', VarAttr => 'name', ContentKey => '-content' ); produces the following data structure: { dir => { prefix => '/usr/local/apache', exec_prefix => '/usr/local/apache', bindir => '/usr/local/apache/bin', } } =head2 XMLDecl => 1 or XMLDecl => 'string' I<# out - handy> If you want the output from C to start with the optional XML declaration, simply set the option to '1'. The default XML declaration is: If you want some other string (for example to declare an encoding value), set the value of this option to the complete string you require. =head1 OPTIONAL OO INTERFACE The procedural interface is both simple and convenient however there are a couple of reasons why you might prefer to use the object oriented (OO) interface: =over 4 =item * to define a set of default values which should be used on all subsequent calls to C or C =item * to override methods in B to provide customised behaviour =back The default values for the options described above are unlikely to suit everyone. The OO interface allows you to effectively override B's defaults with your preferred values. It works like this: First create an XML::Simple parser object with your preferred defaults: my $xs = XML::Simple->new(ForceArray => 1, KeepRoot => 1); then call C or C as a method of that object: my $ref = $xs->XMLin($xml); my $xml = $xs->XMLout($ref); You can also specify options when you make the method calls and these values will be merged with the values specified when the object was created. Values specified in a method call take precedence. Note: when called as methods, the C and C routines may be called as C or C. The method names are aliased so the only difference is the aesthetics. =head2 Parsing Methods You can explicitly call one of the following methods rather than rely on the C method automatically determining whether the target to be parsed is a string, a file or a filehandle: =over 4 =item parse_string(text) Works exactly like the C method but assumes the first argument is a string of XML (or a reference to a scalar containing a string of XML). =item parse_file(filename) Works exactly like the C method but assumes the first argument is the name of a file containing XML. =item parse_fh(file_handle) Works exactly like the C method but assumes the first argument is a filehandle which can be read to get XML. =back =head2 Hook Methods You can make your own class which inherits from XML::Simple and overrides certain behaviours. The following methods may provide useful 'hooks' upon which to hang your modified behaviour. You may find other undocumented methods by examining the source, but those may be subject to change in future releases. =over 4 =item new_xml_parser() This method will be called when a new XML::Parser object must be constructed (either because XML::SAX is not installed or XML::Parser is preferred). =item handle_options(direction, name => value ...) This method will be called when one of the parsing methods or the C method is called. The initial argument will be a string (either 'in' or 'out') and the remaining arguments will be name value pairs. =item default_config_file() Calculates and returns the name of the file which should be parsed if no filename is passed to C (default: C<$0.xml>). =item build_simple_tree(filename, string) Called from C or any of the parsing methods. Takes either a file name as the first argument or C followed by a 'string' as the second argument. Returns a simple tree data structure. You could override this method to apply your own transformations before the data structure is returned to the caller. =item new_hashref() When the 'simple tree' data structure is being built, this method will be called to create any required anonymous hashrefs. =item sorted_keys(name, hashref) Called when C is translating a hashref to XML. This routine returns a list of hash keys in the order that the corresponding attributes/elements should appear in the output. =item escape_value(string) Called from C, takes a string and returns a copy of the string with XML character escaping rules applied. =item escape_attr(string) Called from C, to handle attribute values. By default, just calls C, but you can override this method if you want attributes escaped differently than text content. =item numeric_escape(string) Called from C, to handle non-ASCII characters (depending on the value of the NumericEscape option). =item copy_hash(hashref, extra_key => value, ...) Called from C, when 'unfolding' a hash of hashes into an array of hashes. You might wish to override this method if you're using tied hashes and don't want them to get untied. =back =head2 Cache Methods XML::Simple implements three caching schemes ('storable', 'memshare' and 'memcopy'). You can implement a custom caching scheme by implementing two methods - one for reading from the cache and one for writing to it. For example, you might implement a new 'dbm' scheme that stores cached data structures using the L module. First, you would add a C method which accepted a filename for use as a lookup key and returned a data structure on success, or undef on failure. Then, you would implement a C method which accepted a data structure and a filename. You would use this caching scheme by specifying the option: Cache => [ 'dbm' ] =head1 STRICT MODE If you import the B routines like this: use XML::Simple qw(:strict); the following common mistakes will be detected and treated as fatal errors =over 4 =item * Failing to explicitly set the C option - if you can't be bothered reading about this option, turn it off with: KeyAttr => [ ] =item * Failing to explicitly set the C option - if you can't be bothered reading about this option, set it to the safest mode with: ForceArray => 1 =item * Setting ForceArray to an array, but failing to list all the elements from the KeyAttr hash. =item * Data error - KeyAttr is set to say { part => 'partnum' } but the XML contains one or more EpartE elements without a 'partnum' attribute (or nested element). Note: if strict mode is not set but C is in force, this condition triggers a warning. =item * Data error - as above, but non-unique values are present in the key attribute (eg: more than one EpartE element with the same partnum). This will also trigger a warning if strict mode is not enabled. =item * Data error - as above, but value of key attribute (eg: partnum) is not a scalar string (due to nested elements etc). This will also trigger a warning if strict mode is not enabled. =back =head1 SAX SUPPORT From version 1.08_01, B includes support for SAX (the Simple API for XML) - specifically SAX2. In a typical SAX application, an XML parser (or SAX 'driver') module generates SAX events (start of element, character data, end of element, etc) as it parses an XML document and a 'handler' module processes the events to extract the required data. This simple model allows for some interesting and powerful possibilities: =over 4 =item * Applications written to the SAX API can extract data from huge XML documents without the memory overheads of a DOM or tree API. =item * The SAX API allows for plug and play interchange of parser modules without having to change your code to fit a new module's API. A number of SAX parsers are available with capabilities ranging from extreme portability to blazing performance. =item * A SAX 'filter' module can implement both a handler interface for receiving data and a generator interface for passing modified data on to a downstream handler. Filters can be chained together in 'pipelines'. =item * One filter module might split a data stream to direct data to two or more downstream handlers. =item * Generating SAX events is not the exclusive preserve of XML parsing modules. For example, a module might extract data from a relational database using DBI and pass it on to a SAX pipeline for filtering and formatting. =back B can operate at either end of a SAX pipeline. For example, you can take a data structure in the form of a hashref and pass it into a SAX pipeline using the 'Handler' option on C: use XML::Simple; use Some::SAX::Filter; use XML::SAX::Writer; my $ref = { .... # your data here }; my $writer = XML::SAX::Writer->new(); my $filter = Some::SAX::Filter->new(Handler => $writer); my $simple = XML::Simple->new(Handler => $filter); $simple->XMLout($ref); You can also put B at the opposite end of the pipeline to take advantage of the simple 'tree' data structure once the relevant data has been isolated through filtering: use XML::SAX; use Some::SAX::Filter; use XML::Simple; my $simple = XML::Simple->new(ForceArray => 1, KeyAttr => ['partnum']); my $filter = Some::SAX::Filter->new(Handler => $simple); my $parser = XML::SAX::ParserFactory->parser(Handler => $filter); my $ref = $parser->parse_uri('some_huge_file.xml'); print $ref->{part}->{'555-1234'}; You can build a filter by using an XML::Simple object as a handler and setting its DataHandler option to point to a routine which takes the resulting tree, modifies it and sends it off as SAX events to a downstream handler: my $writer = XML::SAX::Writer->new(); my $filter = XML::Simple->new( DataHandler => sub { my $simple = shift; my $data = shift; # Modify $data here $simple->XMLout($data, Handler => $writer); } ); my $parser = XML::SAX::ParserFactory->parser(Handler => $filter); $parser->parse_uri($filename); I but it could also have been specified in the constructor>. =head1 ENVIRONMENT If you don't care which parser module B uses then skip this section entirely (it looks more complicated than it really is). B will default to using a B parser if one is available or B if SAX is not available. You can dictate which parser module is used by setting either the environment variable 'XML_SIMPLE_PREFERRED_PARSER' or the package variable $XML::Simple::PREFERRED_PARSER to contain the module name. The following rules are used: =over 4 =item * The package variable takes precedence over the environment variable if both are defined. To force B to ignore the environment settings and use its default rules, you can set the package variable to an empty string. =item * If the 'preferred parser' is set to the string 'XML::Parser', then L will be used (or C will die if L is not installed). =item * If the 'preferred parser' is set to some other value, then it is assumed to be the name of a SAX parser module and is passed to L. If L is not installed, or the requested parser module is not installed, then C will die. =item * If the 'preferred parser' is not defined at all (the normal default state), an attempt will be made to load L. If L is installed, then a parser module will be selected according to L's normal rules (which typically means the last SAX parser installed). =item * if the 'preferred parser' is not defined and B is not installed, then B will be used. C will die if L is not installed. =back Note: The B distribution includes an XML parser written entirely in Perl. It is very portable but it is not very fast. You should consider installing L or L if they are available for your platform. =head1 ERROR HANDLING The XML standard is very clear on the issue of non-compliant documents. An error in parsing any single element (for example a missing end tag) must cause the whole document to be rejected. B will die with an appropriate message if it encounters a parsing error. If dying is not appropriate for your application, you should arrange to call C in an eval block and look for errors in $@. eg: my $config = eval { XMLin() }; PopUpMessage($@) if($@); Note, there is a common misconception that use of B will significantly slow down a script. While that may be true when the code being eval'd is in a string, it is not true of code like the sample above. =head1 EXAMPLES When C reads the following very simple piece of XML: it returns the following data structure: { 'username' => 'testuser', 'password' => 'frodo' } The identical result could have been produced with this alternative XML: Or this (although see 'ForceArray' option for variations): testuser frodo Repeated nested elements are represented as anonymous arrays: joe@smith.com jsmith@yahoo.com bob@smith.com { 'person' => [ { 'email' => [ 'joe@smith.com', 'jsmith@yahoo.com' ], 'firstname' => 'Joe', 'lastname' => 'Smith' }, { 'email' => 'bob@smith.com', 'firstname' => 'Bob', 'lastname' => 'Smith' } ] } Nested elements with a recognised key attribute are transformed (folded) from an array into a hash keyed on the value of that attribute (see the C option): { 'person' => { 'jbloggs' => { 'firstname' => 'Joe', 'lastname' => 'Bloggs' }, 'tsmith' => { 'firstname' => 'Tom', 'lastname' => 'Smith' }, 'jsmith' => { 'firstname' => 'Joe', 'lastname' => 'Smith' } } } The tag can be used to form anonymous arrays: Col 1Col 2Col 3 R1C1R1C2R1C3 R2C1R2C2R2C3 R3C1R3C2R3C3 { 'head' => [ [ 'Col 1', 'Col 2', 'Col 3' ] ], 'data' => [ [ 'R1C1', 'R1C2', 'R1C3' ], [ 'R2C1', 'R2C2', 'R2C3' ], [ 'R3C1', 'R3C2', 'R3C3' ] ] } Anonymous arrays can be nested to arbitrary levels and as a special case, if the surrounding tags for an XML document contain only an anonymous array the arrayref will be returned directly rather than the usual hashref: Col 1Col 2 R1C1R1C2 R2C1R2C2 [ [ 'Col 1', 'Col 2' ], [ 'R1C1', 'R1C2' ], [ 'R2C1', 'R2C2' ] ] Elements which only contain text content will simply be represented as a scalar. Where an element has both attributes and text content, the element will be represented as a hashref with the text content in the 'content' key (see the C option): first second { 'one' => 'first', 'two' => { 'attr' => 'value', 'content' => 'second' } } Mixed content (elements which contain both text content and nested elements) will be not be represented in a useful way - element order and significant whitespace will be lost. If you need to work with mixed content, then XML::Simple is not the right tool for your job - check out the next section. =head1 WHERE TO FROM HERE? B is able to present a simple API because it makes some assumptions on your behalf. These include: =over 4 =item * You're not interested in text content consisting only of whitespace =item * You don't mind that when things get slurped into a hash the order is lost =item * You don't want fine-grained control of the formatting of generated XML =item * You would never use a hash key that was not a legal XML element name =item * You don't need help converting between different encodings =back In a serious XML project, you'll probably outgrow these assumptions fairly quickly. This section of the document used to offer some advice on choosing a more powerful option. That advice has now grown into the 'Perl-XML FAQ' document which you can find at: L The advice in the FAQ boils down to a quick explanation of tree versus event based parsers and then recommends: For event based parsing, use SAX (do not set out to write any new code for XML::Parser's handler API - it is obsolete). For tree-based parsing, you could choose between the 'Perlish' approach of L and more standards based DOM implementations - preferably one with XPath support such as L. =head1 SEE ALSO B requires either L or L. To generate documents with namespaces, L is required. The optional caching functions require L. Answers to Frequently Asked Questions about XML::Simple are bundled with this distribution as: L =head1 COPYRIGHT Copyright 1999-2004 Grant McLean Egrantm@cpan.orgE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!F|MMperl5/XML/NamespaceSupport.pmnu6$package XML::NamespaceSupport; use strict; our $VERSION = '1.12'; # VERSION # ABSTRACT: A simple generic namespace processor use constant FATALS => 0; # root object use constant NSMAP => 1; use constant UNKNOWN_PREF => 2; use constant AUTO_PREFIX => 3; use constant XMLNS_11 => 4; use constant DEFAULT => 0; # maps use constant PREFIX_MAP => 1; use constant DECLARATIONS => 2; use vars qw($NS_XMLNS $NS_XML); $NS_XMLNS = 'http://www.w3.org/2000/xmlns/'; $NS_XML = 'http://www.w3.org/XML/1998/namespace'; # add the ns stuff that baud wants based on Java's xml-writer #-------------------------------------------------------------------# # constructor #-------------------------------------------------------------------# sub new { my $class = ref($_[0]) ? ref(shift) : shift; my $options = shift; my $self = [ 1, # FATALS [[ # NSMAP undef, # DEFAULT { xml => $NS_XML }, # PREFIX_MAP undef, # DECLARATIONS ]], 'aaa', # UNKNOWN_PREF 0, # AUTO_PREFIX 1, # XML_11 ]; $self->[NSMAP]->[0]->[PREFIX_MAP]->{xmlns} = $NS_XMLNS if $options->{xmlns}; $self->[FATALS] = $options->{fatal_errors} if defined $options->{fatal_errors}; $self->[AUTO_PREFIX] = $options->{auto_prefix} if defined $options->{auto_prefix}; $self->[XMLNS_11] = $options->{xmlns_11} if defined $options->{xmlns_11}; return bless $self, $class; } #-------------------------------------------------------------------# # reset() - return to the original state (for reuse) #-------------------------------------------------------------------# sub reset { my $self = shift; $#{$self->[NSMAP]} = 0; } #-------------------------------------------------------------------# # push_context() - add a new empty context to the stack #-------------------------------------------------------------------# sub push_context { my $self = shift; push @{$self->[NSMAP]}, [ $self->[NSMAP]->[-1]->[DEFAULT], { %{$self->[NSMAP]->[-1]->[PREFIX_MAP]} }, [], ]; } #-------------------------------------------------------------------# # pop_context() - remove the topmost context from the stack #-------------------------------------------------------------------# sub pop_context { my $self = shift; die 'Trying to pop context without push context' unless @{$self->[NSMAP]} > 1; pop @{$self->[NSMAP]}; } #-------------------------------------------------------------------# # declare_prefix() - declare a prefix in the current scope #-------------------------------------------------------------------# sub declare_prefix { my $self = shift; my $prefix = shift; my $value = shift; warn <<' EOWARN' unless defined $prefix or $self->[AUTO_PREFIX]; Prefix was undefined. If you wish to set the default namespace, use the empty string ''. If you wish to autogenerate prefixes, set the auto_prefix option to a true value. EOWARN no warnings 'uninitialized'; if ($prefix eq 'xml' and $value ne $NS_XML) { die "The xml prefix can only be bound to the $NS_XML namespace." } elsif ($value eq $NS_XML and $prefix ne 'xml') { die "the $NS_XML namespace can only be bound to the xml prefix."; } elsif ($value eq $NS_XML and $prefix eq 'xml') { return 1; } return 0 if index(lc($prefix), 'xml') == 0; use warnings 'uninitialized'; if (defined $prefix and $prefix eq '') { $self->[NSMAP]->[-1]->[DEFAULT] = $value; } else { die "Cannot declare prefix $prefix" if $value eq '' and not $self->[XMLNS_11]; if (not defined $prefix and $self->[AUTO_PREFIX]) { while (1) { $prefix = $self->[UNKNOWN_PREF]++; last if not exists $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix}; } } elsif (not defined $prefix and not $self->[AUTO_PREFIX]) { return 0; } $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix} = $value; } push @{$self->[NSMAP]->[-1]->[DECLARATIONS]}, $prefix; return 1; } #-------------------------------------------------------------------# # declare_prefixes() - declare several prefixes in the current scope #-------------------------------------------------------------------# sub declare_prefixes { my $self = shift; my %prefixes = @_; while (my ($k,$v) = each %prefixes) { $self->declare_prefix($k,$v); } } #-------------------------------------------------------------------# # undeclare_prefix #-------------------------------------------------------------------# sub undeclare_prefix { my $self = shift; my $prefix = shift; return if not defined($prefix); return unless exists $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix}; my ( $tfix ) = grep { $_ eq $prefix } @{$self->[NSMAP]->[-1]->[DECLARATIONS]}; if ( not defined $tfix ) { die "prefix $prefix not declared in this context\n"; } @{$self->[NSMAP]->[-1]->[DECLARATIONS]} = grep { $_ ne $prefix } @{$self->[NSMAP]->[-1]->[DECLARATIONS]}; delete $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix}; } #-------------------------------------------------------------------# # get_prefix() - get a (random) prefix for a given URI #-------------------------------------------------------------------# sub get_prefix { my $self = shift; my $uri = shift; # we have to iterate over the whole hash here because if we don't # the iterator isn't reset and the next pass will fail my $pref; while (my ($k, $v) = each %{$self->[NSMAP]->[-1]->[PREFIX_MAP]}) { $pref = $k if $v eq $uri; } return $pref; } #-------------------------------------------------------------------# # get_prefixes() - get all the prefixes for a given URI #-------------------------------------------------------------------# sub get_prefixes { my $self = shift; my $uri = shift; return keys %{$self->[NSMAP]->[-1]->[PREFIX_MAP]} unless defined $uri; return grep { $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$_} eq $uri } keys %{$self->[NSMAP]->[-1]->[PREFIX_MAP]}; } #-------------------------------------------------------------------# # get_declared_prefixes() - get all prefixes declared in the last context #-------------------------------------------------------------------# sub get_declared_prefixes { my $declarations = $_[0]->[NSMAP]->[-1]->[DECLARATIONS]; die "At least one context must be pushed onto stack with push_context()\n", "before calling get_declared_prefixes()" if not defined $declarations; return @{$_[0]->[NSMAP]->[-1]->[DECLARATIONS]}; } #-------------------------------------------------------------------# # get_uri() - get a URI given a prefix #-------------------------------------------------------------------# sub get_uri { my $self = shift; my $prefix = shift; warn "Prefix must not be undef in get_uri(). The emtpy prefix must be ''" unless defined $prefix; return $self->[NSMAP]->[-1]->[DEFAULT] if $prefix eq ''; return $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix} if exists $self->[NSMAP]->[-1]->[PREFIX_MAP]->{$prefix}; return undef; } #-------------------------------------------------------------------# # process_name() - provide details on a name #-------------------------------------------------------------------# sub process_name { my $self = shift; my $qname = shift; my $aflag = shift; if ($self->[FATALS]) { return( ($self->_get_ns_details($qname, $aflag))[0,2], $qname ); } else { eval { return( ($self->_get_ns_details($qname, $aflag))[0,2], $qname ); } } } #-------------------------------------------------------------------# # process_element_name() - provide details on a element's name #-------------------------------------------------------------------# sub process_element_name { my $self = shift; my $qname = shift; if ($self->[FATALS]) { return $self->_get_ns_details($qname, 0); } else { eval { return $self->_get_ns_details($qname, 0); } } } #-------------------------------------------------------------------# # process_attribute_name() - provide details on a attribute's name #-------------------------------------------------------------------# sub process_attribute_name { my $self = shift; my $qname = shift; if ($self->[FATALS]) { return $self->_get_ns_details($qname, 1); } else { eval { return $self->_get_ns_details($qname, 1); } } } #-------------------------------------------------------------------# # ($ns, $prefix, $lname) = $self->_get_ns_details($qname, $f_attr) # returns ns, prefix, and lname for a given attribute name # >> the $f_attr flag, if set to one, will work for an attribute #-------------------------------------------------------------------# sub _get_ns_details { my $self = shift; my $qname = shift; my $aflag = shift; my ($ns, $prefix, $lname); (my ($tmp_prefix, $tmp_lname) = split /:/, $qname, 3) < 3 or die "Invalid QName: $qname"; # no prefix my $cur_map = $self->[NSMAP]->[-1]; if (not defined($tmp_lname)) { $prefix = undef; $lname = $qname; # attr don't have a default namespace $ns = ($aflag) ? undef : $cur_map->[DEFAULT]; } # prefix else { if (exists $cur_map->[PREFIX_MAP]->{$tmp_prefix}) { $prefix = $tmp_prefix; $lname = $tmp_lname; $ns = $cur_map->[PREFIX_MAP]->{$prefix} } else { # no ns -> lname == name, all rest undef die "Undeclared prefix: $tmp_prefix"; } } return ($ns, $prefix, $lname); } #-------------------------------------------------------------------# # parse_jclark_notation() - parse the Clarkian notation #-------------------------------------------------------------------# sub parse_jclark_notation { shift; my $jc = shift; $jc =~ m/^\{(.*)\}([^}]+)$/; return $1, $2; } #-------------------------------------------------------------------# # Java names mapping #-------------------------------------------------------------------# *XML::NamespaceSupport::pushContext = \&push_context; *XML::NamespaceSupport::popContext = \&pop_context; *XML::NamespaceSupport::declarePrefix = \&declare_prefix; *XML::NamespaceSupport::declarePrefixes = \&declare_prefixes; *XML::NamespaceSupport::getPrefix = \&get_prefix; *XML::NamespaceSupport::getPrefixes = \&get_prefixes; *XML::NamespaceSupport::getDeclaredPrefixes = \&get_declared_prefixes; *XML::NamespaceSupport::getURI = \&get_uri; *XML::NamespaceSupport::processName = \&process_name; *XML::NamespaceSupport::processElementName = \&process_element_name; *XML::NamespaceSupport::processAttributeName = \&process_attribute_name; *XML::NamespaceSupport::parseJClarkNotation = \&parse_jclark_notation; *XML::NamespaceSupport::undeclarePrefix = \&undeclare_prefix; 1; __END__ =pod =encoding UTF-8 =head1 NAME XML::NamespaceSupport - A simple generic namespace processor =head1 VERSION version 1.12 =head1 SYNOPSIS use XML::NamespaceSupport; my $nsup = XML::NamespaceSupport->new; # add a new empty context $nsup->push_context; # declare a few prefixes $nsup->declare_prefix($prefix1, $uri1); $nsup->declare_prefix($prefix2, $uri2); # the same shorter $nsup->declare_prefixes($prefix1 => $uri1, $prefix2 => $uri2); # get a single prefix for a URI (randomly) $prefix = $nsup->get_prefix($uri); # get all prefixes for a URI (probably better) @prefixes = $nsup->get_prefixes($uri); # get all prefixes in scope @prefixes = $nsup->get_prefixes(); # get all prefixes that were declared for the current scope @prefixes = $nsup->get_declared_prefixes; # get a URI for a given prefix $uri = $nsup->get_uri($prefix); # get info on a qname (java-ish way, it's a bit weird) ($ns_uri, $local_name, $qname) = $nsup->process_name($qname, $is_attr); # the same, more perlish ($ns_uri, $prefix, $local_name) = $nsup->process_element_name($qname); ($ns_uri, $prefix, $local_name) = $nsup->process_attribute_name($qname); # remove the current context $nsup->pop_context; # reset the object for reuse in another document $nsup->reset; # a simple helper to process Clarkian Notation my ($ns, $lname) = $nsup->parse_jclark_notation('{http://foo}bar'); # or (given that it doesn't care about the object my ($ns, $lname) = XML::NamespaceSupport->parse_jclark_notation('{http://foo}bar'); =head1 DESCRIPTION This module offers a simple to process namespaced XML names (unames) from within any application that may need them. It also helps maintain a prefix to namespace URI map, and provides a number of basic checks. The model for this module is SAX2's NamespaceSupport class, readable at http://www.saxproject.org/namespaces.html It adds a few perlisations where we thought it appropriate. =head1 NAME XML::NamespaceSupport - a simple generic namespace support class =head1 METHODS =over 4 =item * XML::NamespaceSupport->new(\%options) A simple constructor. The options are C, C, and C If C is turned on (it is off by default) the mapping from the xmlns prefix to the URI defined for it in DOM level 2 is added to the list of predefined mappings (which normally only contains the xml prefix mapping). If C is turned off (it is on by default) a number of validity errors will simply be flagged as failures, instead of die()ing. If C is turned on (it is off by default) when one provides a prefix of C to C it will generate a random prefix mapped to that namespace. Otherwise an undef prefix will trigger a warning (you should probably know what you're doing if you turn this option on). If C us turned off, it becomes illegal to undeclare namespace prefixes. It is on by default. This behaviour is compliant with Namespaces in XML 1.1, turning it off reverts you to version 1.0. =item * $nsup->push_context Adds a new empty context to the stack. You can then populate it with new prefixes defined at this level. =item * $nsup->pop_context Removes the topmost context in the stack and reverts to the previous one. It will die() if you try to pop more than you have pushed. =item * $nsup->declare_prefix($prefix, $uri) Declares a mapping of $prefix to $uri, at the current level. Note that with C turned on, if you declare a prefix mapping in which $prefix is undef(), you will get an automatic prefix selected for you. If it is off you will get a warning. This is useful when you deal with code that hasn't kept prefixes around and need to reserialize the nodes. It also means that if you want to set the default namespace (i.e. with an empty prefix) you must use the empty string instead of undef. This behaviour is consistent with the SAX 2.0 specification. =item * $nsup->declare_prefixes(%prefixes2uris) Declares a mapping of several prefixes to URIs, at the current level. =item * $nsup->get_prefix($uri) Returns a prefix given a URI. Note that as several prefixes may be mapped to the same URI, it returns an arbitrary one. It'll return undef on failure. =item * $nsup->get_prefixes($uri) Returns an array of prefixes given a URI. It'll return all the prefixes if the uri is undef. =item * $nsup->get_declared_prefixes Returns an array of all the prefixes that have been declared within this context, ie those that were declared on the last element, not those that were declared above and are simply in scope. Note that at least one context must be added to the stack via C before this method can be called. =item * $nsup->get_uri($prefix) Returns a URI for a given prefix. Returns undef on failure. =item * $nsup->process_name($qname, $is_attr) Given a qualified name and a boolean indicating whether this is an attribute or another type of name (those are differently affected by default namespaces), it returns a namespace URI, local name, qualified name tuple. I know that that is a rather abnormal list to return, but it is so for compatibility with the Java spec. See below for more Perlish alternatives. If the prefix is not declared, or if the name is not valid, it'll either die or return undef depending on the current setting of C. =item * $nsup->undeclare_prefix($prefix); Removes a namespace prefix from the current context. This function may be used in SAX's end_prefix_mapping when there is fear that a namespace declaration might be available outside their scope (which shouldn't normally happen, but you never know ;) ). This may be needed in order to properly support Namespace 1.1. =item * $nsup->process_element_name($qname) Given a qualified name, it returns a namespace URI, prefix, and local name tuple. This method applies to element names. If the prefix is not declared, or if the name is not valid, it'll either die or return undef depending on the current setting of C. =item * $nsup->process_attribute_name($qname) Given a qualified name, it returns a namespace URI, prefix, and local name tuple. This method applies to attribute names. If the prefix is not declared, or if the name is not valid, it'll either die or return undef depending on the current setting of C. =item * $nsup->reset Resets the object so that it can be reused on another document. =back All methods of the interface have an alias that is the name used in the original Java specification. You can use either name interchangeably. Here is the mapping: Java name Perl name --------------------------------------------------- pushContext push_context popContext pop_context declarePrefix declare_prefix declarePrefixes declare_prefixes getPrefix get_prefix getPrefixes get_prefixes getDeclaredPrefixes get_declared_prefixes getURI get_uri processName process_name processElementName process_element_name processAttributeName process_attribute_name parseJClarkNotation parse_jclark_notation undeclarePrefix undeclare_prefix =head1 VARIABLES Two global variables are made available to you. They used to be constants but simple scalars are easier to use in a number of contexts. They are not exported but can easily be accessed from any package, or copied into it. =over 4 =item * C<$NS_XMLNS> The namespace for xmlns prefixes, http://www.w3.org/2000/xmlns/. =item * C<$NS_XML> The namespace for xml prefixes, http://www.w3.org/XML/1998/namespace. =back =head1 TODO - add more tests - optimise here and there =head1 SEE ALSO XML::Parser::PerlSAX =head1 AUTHORS =over 4 =item * Robin Berjon =item * Chris Prather =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2015 by Robin Berjon. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =head1 CONTRIBUTORS =for stopwords Chris Prather David Steinbrunner Paul Cochrane Paulo Custodio =over 4 =item * Chris Prather =item * David Steinbrunner =item * Paul Cochrane =item * Paulo Custodio =back =cut PK!X6qRRperl5/Try/Tiny.pmnu6$package Try::Tiny; # git description: v0.30-11-g1b81d0a use 5.006; # ABSTRACT: Minimal try/catch with proper preservation of $@ our $VERSION = '0.31'; use strict; use warnings; use Exporter 5.57 'import'; our @EXPORT = our @EXPORT_OK = qw(try catch finally); use Carp; $Carp::Internal{+__PACKAGE__}++; BEGIN { my $su = $INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname; my $sn = $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) }; unless ($su || $sn) { $su = eval { require Sub::Util; } && defined &Sub::Util::set_subname; unless ($su) { $sn = eval { require Sub::Name; Sub::Name->VERSION(0.08) }; } } *_subname = $su ? \&Sub::Util::set_subname : $sn ? \&Sub::Name::subname : sub { $_[1] }; *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0}; } my %_finally_guards; # Need to prototype as @ not $$ because of the way Perl evaluates the prototype. # Keeping it at $$ means you only ever get 1 sub because we need to eval in a list # context & not a scalar one sub try (&;@) { my ( $try, @code_refs ) = @_; # we need to save this here, the eval block will be in scalar context due # to $failed my $wantarray = wantarray; # work around perl bug by explicitly initializing these, due to the likelyhood # this will be used in global destruction (perl rt#119311) my ( $catch, @finally ) = (); # find labeled blocks in the argument list. # catch and finally tag the blocks by blessing a scalar reference to them. foreach my $code_ref (@code_refs) { if ( ref($code_ref) eq 'Try::Tiny::Catch' ) { croak 'A try() may not be followed by multiple catch() blocks' if $catch; $catch = ${$code_ref}; } elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) { push @finally, ${$code_ref}; } else { croak( 'try() encountered an unexpected argument (' . ( defined $code_ref ? $code_ref : 'undef' ) . ') - perhaps a missing semi-colon before or' ); } } # FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's # not perfect, but we could provide a list of additional errors for # $catch->(); # name the blocks if we have Sub::Name installed _subname(caller().'::try {...} ' => $try) if _HAS_SUBNAME; # set up scope guards to invoke the finally blocks at the end. # this should really be a function scope lexical variable instead of # file scope + local but that causes issues with perls < 5.20 due to # perl rt#119311 local $_finally_guards{guards} = [ map Try::Tiny::ScopeGuard->_new($_), @finally ]; # save the value of $@ so we can set $@ back to it in the beginning of the eval # and restore $@ after the eval finishes my $prev_error = $@; my ( @ret, $error ); # failed will be true if the eval dies, because 1 will not be returned # from the eval body my $failed = not eval { $@ = $prev_error; # evaluate the try block in the correct context if ( $wantarray ) { @ret = $try->(); } elsif ( defined $wantarray ) { $ret[0] = $try->(); } else { $try->(); }; return 1; # properly set $failed to false }; # preserve the current error and reset the original value of $@ $error = $@; $@ = $prev_error; # at this point $failed contains a true value if the eval died, even if some # destructor overwrote $@ as the eval was unwinding. if ( $failed ) { # pass $error to the finally blocks push @$_, $error for @{$_finally_guards{guards}}; # if we got an error, invoke the catch block. if ( $catch ) { # This works like given($error), but is backwards compatible and # sets $_ in the dynamic scope for the body of C<$catch> for ($error) { return $catch->($error); } # in case when() was used without an explicit return, the C # loop will be aborted and there's no useful return value } return; } else { # no failure, $@ is back to what it was, everything is fine return $wantarray ? @ret : $ret[0]; } } sub catch (&;@) { my ( $block, @rest ) = @_; croak 'Useless bare catch()' unless wantarray; _subname(caller().'::catch {...} ' => $block) if _HAS_SUBNAME; return ( bless(\$block, 'Try::Tiny::Catch'), @rest, ); } sub finally (&;@) { my ( $block, @rest ) = @_; croak 'Useless bare finally()' unless wantarray; _subname(caller().'::finally {...} ' => $block) if _HAS_SUBNAME; return ( bless(\$block, 'Try::Tiny::Finally'), @rest, ); } { package # hide from PAUSE Try::Tiny::ScopeGuard; use constant UNSTABLE_DOLLARAT => ("$]" < '5.013002') ? 1 : 0; sub _new { shift; bless [ @_ ]; } sub DESTROY { my ($code, @args) = @{ $_[0] }; local $@ if UNSTABLE_DOLLARAT; eval { $code->(@args); 1; } or do { warn "Execution of finally() block $code resulted in an exception, which " . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. ' . 'Your program will continue as if this event never took place. ' . "Original exception text follows:\n\n" . (defined $@ ? $@ : '$@ left undefined...') . "\n" ; } } } __PACKAGE__ __END__ =pod =encoding UTF-8 =head1 NAME Try::Tiny - Minimal try/catch with proper preservation of $@ =head1 VERSION version 0.31 =head1 SYNOPSIS You can use Try::Tiny's C and C to expect and handle exceptional conditions, avoiding quirks in Perl and common mistakes: # handle errors with a catch handler try { die "foo"; } catch { warn "caught error: $_"; # not $@ }; You can also use it like a standalone C to catch and ignore any error conditions. Obviously, this is an extreme measure not to be undertaken lightly: # just silence errors try { die "foo"; }; =head1 DESCRIPTION This module provides bare bones C/C/C statements that are designed to minimize common mistakes with eval blocks, and NOTHING else. This is unlike L which provides a nice syntax and avoids adding another call stack layer, and supports calling C from the C block to return from the parent subroutine. These extra features come at a cost of a few dependencies, namely L and L which are occasionally problematic, and the additional catch filtering uses L type constraints which may not be desirable either. The main focus of this module is to provide simple and reliable error handling for those having a hard time installing L, but who still want to write correct C blocks without 5 lines of boilerplate each time. It's designed to work as correctly as possible in light of the various pathological edge cases (see L) and to be compatible with any style of error values (simple strings, references, objects, overloaded objects, etc). If the C block dies, it returns the value of the last statement executed in the C block, if there is one. Otherwise, it returns C in scalar context or the empty list in list context. The following examples all assign C<"bar"> to C<$x>: my $x = try { die "foo" } catch { "bar" }; my $x = try { die "foo" } || "bar"; my $x = (try { die "foo" }) // "bar"; my $x = eval { die "foo" } || "bar"; You can add C blocks, yielding the following: my $x; try { die 'foo' } finally { $x = 'bar' }; try { die 'foo' } catch { warn "Got a die: $_" } finally { $x = 'bar' }; C blocks are always executed making them suitable for cleanup code which cannot be handled using local. You can add as many C blocks to a given C block as you like. Note that adding a C block without a preceding C block suppresses any errors. This behaviour is consistent with using a standalone C, but it is not consistent with C/C patterns found in other programming languages, such as Java, Python, Javascript or C#. If you learned the C/C pattern from one of these languages, watch out for this. =head1 EXPORTS All functions are exported by default using L. If you need to rename the C, C or C keyword consider using L to get L's flexibility. =over 4 =item try (&;@) Takes one mandatory C subroutine, an optional C subroutine and C subroutine. The mandatory subroutine is evaluated in the context of an C block. If no error occurred the value from the first block is returned, preserving list/scalar context. If there was an error and the second subroutine was given it will be invoked with the error in C<$_> (localized) and as that block's first and only argument. C<$@> does B contain the error. Inside the C block it has the same value it had before the C block was executed. Note that the error may be false, but if that happens the C block will still be invoked. Once all execution is finished then the C block, if given, will execute. =item catch (&;@) Intended to be used in the second argument position of C. Returns a reference to the subroutine it was given but blessed as C which allows try to decode correctly what to do with this code reference. catch { ... } Inside the C block the caught error is stored in C<$_>, while previous value of C<$@> is still available for use. This value may or may not be meaningful depending on what happened before the C, but it might be a good idea to preserve it in an error stack. For code that captures C<$@> when throwing new errors (i.e. L), you'll need to do: local $@ = $_; =item finally (&;@) try { ... } catch { ... } finally { ... }; Or try { ... } finally { ... }; Or even try { ... } finally { ... } catch { ... }; Intended to be the second or third element of C. C blocks are always executed in the event of a successful C or if C is run. This allows you to locate cleanup code which cannot be done via C e.g. closing a file handle. When invoked, the C block is passed the error that was caught. If no error was caught, it is passed nothing. (Note that the C block does not localize C<$_> with the error, since unlike in a C block, there is no way to know if C<$_ == undef> implies that there were no errors.) In other words, the following code does just what you would expect: try { die_sometimes(); } catch { # ...code run in case of error } finally { if (@_) { print "The try block died with: @_\n"; } else { print "The try block ran without error.\n"; } }; B block>. C will not do anything about handling possible errors coming from code located in these blocks. Furthermore B blocks are not trappable and are unable to influence the execution of your program>. This is due to limitation of C-based scope guards, which C is implemented on top of. This may change in a future version of Try::Tiny. In the same way C blesses the code reference this subroutine does the same except it bless them as C. =back =head1 BACKGROUND There are a number of issues with C. =head2 Clobbering $@ When you run an C block and it succeeds, C<$@> will be cleared, potentially clobbering an error that is currently being caught. This causes action at a distance, clearing previous errors your caller may have not yet handled. C<$@> must be properly localized before invoking C in order to avoid this issue. More specifically, L C<$@> was clobbered at the beginning of the C, which also made it impossible to capture the previous error before you die (for instance when making exception objects with error stacks). For this reason C will actually set C<$@> to its previous value (the one available before entering the C block) in the beginning of the C block. =head2 Localizing $@ silently masks errors Inside an C block, C behaves sort of like: sub die { $@ = $_[0]; return_undef_from_eval(); } This means that if you were polite and localized C<$@> you can't die in that scope, or your error will be discarded (printing "Something's wrong" instead). The workaround is very ugly: my $error = do { local $@; eval { ... }; $@; }; ... die $error; =head2 $@ might not be a true value This code is wrong: if ( $@ ) { ... } because due to the previous caveats it may have been unset. C<$@> could also be an overloaded error object that evaluates to false, but that's asking for trouble anyway. The classic failure mode (fixed in L) is: sub Object::DESTROY { eval { ... } } eval { my $obj = Object->new; die "foo"; }; if ( $@ ) { } In this case since C is not localizing C<$@> but still uses C, it will set C<$@> to C<"">. The destructor is called when the stack is unwound, after C sets C<$@> to C<"foo at Foo.pm line 42\n">, so by the time C is evaluated it has been cleared by C in the destructor. The workaround for this is even uglier than the previous ones. Even though we can't save the value of C<$@> from code that doesn't localize, we can at least be sure the C was aborted due to an error: my $failed = not eval { ... return 1; }; This is because an C that caught a C will always return a false value. =head1 ALTERNATE SYNTAX Using Perl 5.10 you can use L (but please don't, because that syntax has since been deprecated because there was too much unexpected magical behaviour). =for stopwords topicalizer The C block is invoked in a topicalizer context (like a C block), but note that you can't return a useful value from C using the C blocks without an explicit C. This is somewhat similar to Perl 6's C blocks. You can use it to concisely match errors: try { require Foo; } catch { when (/^Can't locate .*?\.pm in \@INC/) { } # ignore default { die $_ } }; =head1 CAVEATS =over 4 =item * C<@_> is not available within the C block, so you need to copy your argument list. In case you want to work with argument values directly via C<@_> aliasing (i.e. allow C<$_[1] = "foo">), you need to pass C<@_> by reference: sub foo { my ( $self, @args ) = @_; try { $self->bar(@args) } } or sub bar_in_place { my $self = shift; my $args = \@_; try { $_ = $self->bar($_) for @$args } } =item * C returns from the C block, not from the parent sub (note that this is also how C works, but not how L works): sub parent_sub { try { die; } catch { return; }; say "this text WILL be displayed, even though an exception is thrown"; } Instead, you should capture the return value: sub parent_sub { my $success = try { die; 1; }; return unless $success; say "This text WILL NEVER appear!"; } # OR sub parent_sub_with_catch { my $success = try { die; 1; } catch { # do something with $_ return undef; #see note }; return unless $success; say "This text WILL NEVER appear!"; } Note that if you have a C block, it must return C for this to work, since if a C block exists, its return value is returned in place of C when an exception is thrown. =item * C introduces another caller stack frame. L is not used. L will not report this when using full stack traces, though, because C<%Carp::Internal> is used. This lack of magic is considered a feature. =for stopwords unhygienically =item * The value of C<$_> in the C block is not guaranteed to be the value of the exception thrown (C<$@>) in the C block. There is no safe way to ensure this, since C may be used unhygienically in destructors. The only guarantee is that the C will be called if an exception is thrown. =item * The return value of the C block is not ignored, so if testing the result of the expression for truth on success, be sure to return a false value from the C block: my $obj = try { MightFail->new; } catch { ... return; # avoid returning a true value; }; return unless $obj; =item * C<$SIG{__DIE__}> is still in effect. Though it can be argued that C<$SIG{__DIE__}> should be disabled inside of C blocks, since it isn't people have grown to rely on it. Therefore in the interests of compatibility, C does not disable C<$SIG{__DIE__}> for the scope of the error throwing code. =item * Lexical C<$_> may override the one set by C. For example Perl 5.10's C form uses a lexical C<$_>, creating some confusing behavior: given ($foo) { when (...) { try { ... } catch { warn $_; # will print $foo, not the error warn $_[0]; # instead, get the error like this } } } Note that this behavior was changed once again in L. However, since the entirety of lexical C<$_> is now L, it is unclear whether the new version 18 behavior is final. =back =head1 SEE ALSO =over 4 =item L Only available on perls >= 5.14, with a slightly different syntax (e.g. no trailing C<;> because it's actually a keyword, not a sub, but this means you can C and C within it). Use L to automatically switch to the native C syntax in newer perls (when available). See also L. =item L Much more feature complete, more convenient semantics, but at the cost of implementation complexity. =item L Automatic error throwing for builtin functions and more. Also designed to work well with C/C. =item L A lightweight role for rolling your own exception classes. =item L Exception object implementation with a C statement. Does not localize C<$@>. =item L Provides a C statement, but properly calling C is your responsibility. The C keyword pushes C<$@> onto an error stack, avoiding some of the issues with C<$@>, but you still need to localize to prevent clobbering. =back =head1 LIGHTNING TALK I gave a lightning talk about this module, you can see the slides (Firefox only): L Or read the source: L =head1 SUPPORT Bugs may be submitted through L (or L). =head1 AUTHORS =over 4 =item * יובל קוג'מן (Yuval Kogman) =item * Jesse Luehrs =back =head1 CONTRIBUTORS =for stopwords Karen Etheridge Peter Rabbitson Ricardo Signes Mark Fowler Graham Knop Aristotle Pagaltzis Dagfinn Ilmari Mannsåker Lukas Mai Alex anaxagoras Andrew Yates awalker chromatic cm-perl David Lowe Glenn Hans Dieter Pearcey Jens Berthold Jonathan Yu Marc Mims Stosberg Pali Paul Howarth Rudolf Leermakers =over 4 =item * Karen Etheridge =item * Peter Rabbitson =item * Ricardo Signes =item * Mark Fowler =item * Graham Knop =item * Aristotle Pagaltzis =item * Dagfinn Ilmari Mannsåker =item * Lukas Mai =item * Alex =item * anaxagoras =item * Andrew Yates =item * awalker =item * chromatic =item * cm-perl =item * David Lowe =item * Glenn Fowler =item * Hans Dieter Pearcey =item * Jens Berthold =item * Jonathan Yu =item * Marc Mims =item * Mark Stosberg =item * Pali =item * Paul Howarth =item * Rudolf Leermakers =back =head1 COPYRIGHT AND LICENCE This software is Copyright (c) 2009 by יובל קוג'מן (Yuval Kogman). This is free software, licensed under: The MIT (X11) License =cut PK!7Sperl5/CPAN/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; PK!((perl5/CPAN/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; PK!3yI I perl5/CPAN/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; PK!:perl5/CPAN/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; PK!ܑH*perl5/CPAN/Exception/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; PK!*^'perl5/CPAN/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; PK!1..+perl5/CPAN/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; PK!T*perl5/CPAN/Exception/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; PK!CvWvWperl5/CPAN/Module.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Module; use strict; @CPAN::Module::ISA = qw(CPAN::InfoObj); use vars qw( $VERSION ); $VERSION = "5.5003"; BEGIN { # alarm() is not implemented in perl 5.6.x and earlier under Windows *ALARM_IMPLEMENTED = sub () { $] >= 5.007 || $^O !~ /MSWin/ }; } # Accessors #-> sub CPAN::Module::userid sub userid { my $self = shift; my $ro = $self->ro; return unless $ro; return $ro->{userid} || $ro->{CPAN_USERID}; } #-> sub CPAN::Module::description sub description { my $self = shift; my $ro = $self->ro or return ""; $ro->{description} } #-> sub CPAN::Module::distribution sub distribution { my($self) = @_; CPAN::Shell->expand("Distribution",$self->cpan_file); } #-> sub CPAN::Module::_is_representative_module sub _is_representative_module { my($self) = @_; return $self->{_is_representative_module} if defined $self->{_is_representative_module}; my $pm = $self->cpan_file or return $self->{_is_representative_module} = 0; $pm =~ s|.+/||; $pm =~ s{\.(?:tar\.(bz2|gz|Z)|t(?:gz|bz)|zip)$}{}i; # see base_id $pm =~ s|-\d+\.\d+.+$||; $pm =~ s|-[\d\.]+$||; $pm =~ s/-/::/g; $self->{_is_representative_module} = $pm eq $self->{ID} ? 1 : 0; # warn "DEBUG: $pm eq $self->{ID} => $self->{_is_representative_module}"; $self->{_is_representative_module}; } #-> sub CPAN::Module::undelay sub undelay { my $self = shift; delete $self->{later}; if ( my $dist = CPAN::Shell->expand("Distribution", $self->cpan_file) ) { $dist->undelay; } } # mark as dirty/clean #-> sub CPAN::Module::color_cmd_tmps ; sub color_cmd_tmps { my($self) = shift; my($depth) = shift || 0; my($color) = shift || 0; my($ancestors) = shift || []; # a module needs to recurse to its cpan_file return if exists $self->{incommandcolor} && $color==1 && $self->{incommandcolor}==$color; return if $color==0 && !$self->{incommandcolor}; if ($color>=1) { if ( $self->uptodate ) { $self->{incommandcolor} = $color; return; } elsif (my $have_version = $self->available_version) { # maybe what we have is good enough if (@$ancestors) { my $who_asked_for_me = $ancestors->[-1]; my $obj = CPAN::Shell->expandany($who_asked_for_me); if (0) { } elsif ($obj->isa("CPAN::Bundle")) { # bundles cannot specify a minimum version return; } elsif ($obj->isa("CPAN::Distribution")) { if (my $prereq_pm = $obj->prereq_pm) { for my $k (keys %$prereq_pm) { if (my $want_version = $prereq_pm->{$k}{$self->id}) { if (CPAN::Version->vcmp($have_version,$want_version) >= 0) { $self->{incommandcolor} = $color; return; } } } } } } } } else { $self->{incommandcolor} = $color; # set me before recursion, # so we can break it } if ($depth>=$CPAN::MAX_RECURSION) { my $e = CPAN::Exception::RecursiveDependency->new($ancestors); if ($e->is_resolvable) { return $self->{incommandcolor}=2; } else { die $e; } } # warn "color_cmd_tmps $depth $color " . $self->id; # sleep 1; if ( my $dist = CPAN::Shell->expand("Distribution", $self->cpan_file) ) { $dist->color_cmd_tmps($depth+1,$color,[@$ancestors, $self->id]); } # unreached code? # if ($color==0) { # delete $self->{badtestcnt}; # } $self->{incommandcolor} = $color; } #-> sub CPAN::Module::as_glimpse ; sub as_glimpse { my($self) = @_; my(@m); my $class = ref($self); $class =~ s/^CPAN:://; my $color_on = ""; my $color_off = ""; if ( $CPAN::Shell::COLOR_REGISTERED && $CPAN::META->has_inst("Term::ANSIColor") && $self->description ) { $color_on = Term::ANSIColor::color("green"); $color_off = Term::ANSIColor::color("reset"); } my $uptodateness = " "; unless ($class eq "Bundle") { my $u = $self->uptodate; $uptodateness = $u ? "=" : "<" if defined $u; }; my $id = do { my $d = $self->distribution; $d ? $d -> pretty_id : $self->cpan_userid; }; push @m, sprintf("%-7s %1s %s%-22s%s (%s)\n", $class, $uptodateness, $color_on, $self->id, $color_off, $id, ); join "", @m; } #-> sub CPAN::Module::dslip_status sub dslip_status { my($self) = @_; my($stat); # development status @{$stat->{D}}{qw,i c a b R M S,} = qw,idea pre-alpha alpha beta released mature standard,; # support level @{$stat->{S}}{qw,m d u n a,} = qw,mailing-list developer comp.lang.perl.* none abandoned,; # language @{$stat->{L}}{qw,p c + o h,} = qw,perl C C++ other hybrid,; # interface @{$stat->{I}}{qw,f r O p h n,} = qw,functions references+ties object-oriented pragma hybrid none,; # public licence @{$stat->{P}}{qw,p g l b a 2 o d r n,} = qw,Standard-Perl GPL LGPL BSD Artistic Artistic_2 open-source distribution_allowed restricted_distribution no_licence,; for my $x (qw(d s l i p)) { $stat->{$x}{' '} = 'unknown'; $stat->{$x}{'?'} = 'unknown'; } my $ro = $self->ro; return +{} unless $ro && $ro->{statd}; return { D => $ro->{statd}, S => $ro->{stats}, L => $ro->{statl}, I => $ro->{stati}, P => $ro->{statp}, DV => $stat->{D}{$ro->{statd}}, SV => $stat->{S}{$ro->{stats}}, LV => $stat->{L}{$ro->{statl}}, IV => $stat->{I}{$ro->{stati}}, PV => $stat->{P}{$ro->{statp}}, }; } #-> sub CPAN::Module::as_string ; sub as_string { my($self) = @_; my(@m); CPAN->debug("$self entering as_string") if $CPAN::DEBUG; my $class = ref($self); $class =~ s/^CPAN:://; local($^W) = 0; push @m, $class, " id = $self->{ID}\n"; my $sprintf = " %-12s %s\n"; push @m, sprintf($sprintf, 'DESCRIPTION', $self->description) if $self->description; my $sprintf2 = " %-12s %s (%s)\n"; my($userid); $userid = $self->userid; if ( $userid ) { my $author; if ($author = CPAN::Shell->expand('Author',$userid)) { my $email = ""; my $m; # old perls if ($m = $author->email) { $email = " <$m>"; } push @m, sprintf( $sprintf2, 'CPAN_USERID', $userid, $author->fullname . $email ); } } push @m, sprintf($sprintf, 'CPAN_VERSION', $self->cpan_version) if $self->cpan_version; if (my $cpan_file = $self->cpan_file) { push @m, sprintf($sprintf, 'CPAN_FILE', $cpan_file); if (my $dist = CPAN::Shell->expand("Distribution",$cpan_file)) { my $upload_date = $dist->upload_date; if ($upload_date) { push @m, sprintf($sprintf, 'UPLOAD_DATE', $upload_date); } } } my $sprintf3 = " %-12s %1s%1s%1s%1s%1s (%s,%s,%s,%s,%s)\n"; my $dslip = $self->dslip_status; push @m, sprintf( $sprintf3, 'DSLIP_STATUS', @{$dslip}{qw(D S L I P DV SV LV IV PV)}, ) if $dslip->{D}; my $local_file = $self->inst_file; unless ($self->{MANPAGE}) { my $manpage; if ($local_file) { $manpage = $self->manpage_headline($local_file); } else { # If we have already untarred it, we should look there my $dist = $CPAN::META->instance('CPAN::Distribution', $self->cpan_file); # warn "dist[$dist]"; # mff=manifest file; mfh=manifest handle my($mff,$mfh); if ( $dist->{build_dir} and (-f ($mff = File::Spec->catfile($dist->{build_dir}, "MANIFEST"))) and $mfh = FileHandle->new($mff) ) { CPAN->debug("mff[$mff]") if $CPAN::DEBUG; my $lfre = $self->id; # local file RE $lfre =~ s/::/./g; $lfre .= "\\.pm\$"; my($lfl); # local file file local $/ = "\n"; my(@mflines) = <$mfh>; for (@mflines) { s/^\s+//; s/\s.*//s; } while (length($lfre)>5 and !$lfl) { ($lfl) = grep /$lfre/, @mflines; CPAN->debug("lfl[$lfl]lfre[$lfre]") if $CPAN::DEBUG; $lfre =~ s/.+?\.//; } $lfl =~ s/\s.*//; # remove comments $lfl =~ s/\s+//g; # chomp would maybe be too system-specific my $lfl_abs = File::Spec->catfile($dist->{build_dir},$lfl); # warn "lfl_abs[$lfl_abs]"; if (-f $lfl_abs) { $manpage = $self->manpage_headline($lfl_abs); } } } $self->{MANPAGE} = $manpage if $manpage; } my($item); for $item (qw/MANPAGE/) { push @m, sprintf($sprintf, $item, $self->{$item}) if exists $self->{$item}; } for $item (qw/CONTAINS/) { push @m, sprintf($sprintf, $item, join(" ",@{$self->{$item}})) if exists $self->{$item} && @{$self->{$item}}; } push @m, sprintf($sprintf, 'INST_FILE', $local_file || "(not installed)"); push @m, sprintf($sprintf, 'INST_VERSION', $self->inst_version) if $local_file; if (%{$CPAN::META->{is_tested}||{}}) { # XXX needs to be methodified somehow my $available_file = $self->available_file; if ($available_file && $available_file ne $local_file) { push @m, sprintf($sprintf, 'AVAILABLE_FILE', $available_file); push @m, sprintf($sprintf, 'AVAILABLE_VERSION', $self->available_version); } } join "", @m, "\n"; } #-> sub CPAN::Module::manpage_headline sub manpage_headline { my($self,$local_file) = @_; my(@local_file) = $local_file; $local_file =~ s/\.pm(?!\n)\Z/.pod/; push @local_file, $local_file; my(@result,$locf); for $locf (@local_file) { next unless -f $locf; my $fh = FileHandle->new($locf) or $Carp::Frontend->mydie("Couldn't open $locf: $!"); my $inpod = 0; local $/ = "\n"; while (<$fh>) { $inpod = m/^=(?!head1\s+NAME\s*$)/ ? 0 : m/^=head1\s+NAME\s*$/ ? 1 : $inpod; next unless $inpod; next if /^=/; next if /^\s+$/; chomp; push @result, $_; } close $fh; last if @result; } for (@result) { s/^\s+//; s/\s+$//; } join " ", @result; } #-> sub CPAN::Module::cpan_file ; # Note: also inherited by CPAN::Bundle sub cpan_file { my $self = shift; # CPAN->debug(sprintf "id[%s]", $self->id) if $CPAN::DEBUG; unless ($self->ro) { CPAN::Index->reload; } my $ro = $self->ro; if ($ro && defined $ro->{CPAN_FILE}) { return $ro->{CPAN_FILE}; } else { my $userid = $self->userid; if ( $userid ) { if ($CPAN::META->exists("CPAN::Author",$userid)) { my $author = $CPAN::META->instance("CPAN::Author", $userid); my $fullname = $author->fullname; my $email = $author->email; unless (defined $fullname && defined $email) { return sprintf("Contact Author %s", $userid, ); } return "Contact Author $fullname <$email>"; } else { return "Contact Author $userid (Email address not available)"; } } else { return "N/A"; } } } #-> sub CPAN::Module::cpan_version ; sub cpan_version { my $self = shift; my $ro = $self->ro; unless ($ro) { # Can happen with modules that are not on CPAN $ro = {}; } $ro->{CPAN_VERSION} = 'undef' unless defined $ro->{CPAN_VERSION}; $ro->{CPAN_VERSION}; } #-> sub CPAN::Module::force ; sub force { my($self) = @_; $self->{force_update} = 1; } #-> sub CPAN::Module::fforce ; sub fforce { my($self) = @_; $self->{force_update} = 2; } #-> sub CPAN::Module::notest ; sub notest { my($self) = @_; # $CPAN::Frontend->mywarn("XDEBUG: set notest for Module"); $self->{notest}++; } #-> sub CPAN::Module::rematein ; sub rematein { my($self,$meth) = @_; $CPAN::Frontend->myprint(sprintf("Running %s for module '%s'\n", $meth, $self->id)); my $cpan_file = $self->cpan_file; if ($cpan_file eq "N/A" || $cpan_file =~ /^Contact Author/) { $CPAN::Frontend->mywarn(sprintf qq{ The module %s isn\'t available on CPAN. Either the module has not yet been uploaded to CPAN, or it is temporary unavailable. Please contact the author to find out more about the status. Try 'i %s'. }, $self->id, $self->id, ); return; } my $pack = $CPAN::META->instance('CPAN::Distribution',$cpan_file); $pack->called_for($self->id); if (exists $self->{force_update}) { if ($self->{force_update} == 2) { $pack->fforce($meth); } else { $pack->force($meth); } } $pack->notest($meth) if exists $self->{notest} && $self->{notest}; $pack->{reqtype} ||= ""; CPAN->debug("dist-reqtype[$pack->{reqtype}]". "self-reqtype[$self->{reqtype}]") if $CPAN::DEBUG; if ($pack->{reqtype}) { if ($pack->{reqtype} eq "b" && $self->{reqtype} =~ /^[rc]$/) { $pack->{reqtype} = $self->{reqtype}; if ( exists $pack->{install} && ( UNIVERSAL::can($pack->{install},"failed") ? $pack->{install}->failed : $pack->{install} =~ /^NO/ ) ) { delete $pack->{install}; $CPAN::Frontend->mywarn ("Promoting $pack->{ID} from 'build_requires' to 'requires'"); } } } else { $pack->{reqtype} = $self->{reqtype}; } my $success = eval { $pack->$meth(); }; my $err = $@; $pack->unforce if $pack->can("unforce") && exists $self->{force_update}; $pack->unnotest if $pack->can("unnotest") && exists $self->{notest}; delete $self->{force_update}; delete $self->{notest}; if ($err) { die $err; } return $success; } #-> sub CPAN::Module::perldoc ; sub perldoc { shift->rematein('perldoc') } #-> sub CPAN::Module::readme ; sub readme { shift->rematein('readme') } #-> sub CPAN::Module::look ; sub look { shift->rematein('look') } #-> sub CPAN::Module::cvs_import ; sub cvs_import { shift->rematein('cvs_import') } #-> sub CPAN::Module::get ; sub get { shift->rematein('get',@_) } #-> sub CPAN::Module::make ; sub make { shift->rematein('make') } #-> sub CPAN::Module::test ; sub test { my $self = shift; # $self->{badtestcnt} ||= 0; $self->rematein('test',@_); } #-> sub CPAN::Module::deprecated_in_core ; sub deprecated_in_core { my ($self) = @_; return unless $CPAN::META->has_inst('Module::CoreList') && Module::CoreList->can('is_deprecated'); return Module::CoreList::is_deprecated($self->{ID}); } #-> sub CPAN::Module::inst_deprecated; # Indicates whether the *installed* version of the module is a deprecated *and* # installed as part of the Perl core library path sub inst_deprecated { my ($self) = @_; my $inst_file = $self->inst_file or return; return $self->deprecated_in_core && $self->_in_priv_or_arch($inst_file); } #-> sub CPAN::Module::uptodate ; sub uptodate { my ($self) = @_; local ($_); my $inst = $self->inst_version or return 0; my $cpan = $self->cpan_version; return 0 if CPAN::Version->vgt($cpan,$inst) || $self->inst_deprecated; CPAN->debug (join ("", "returning uptodate. ", "cpan[$cpan]inst[$inst]", )) if $CPAN::DEBUG; return 1; } # returns true if installed in privlib or archlib sub _in_priv_or_arch { my($self,$inst_file) = @_; foreach my $pair ( [qw(sitearchexp archlibexp)], [qw(sitelibexp privlibexp)] ) { my ($site, $priv) = @Config::Config{@$pair}; if ($^O eq 'VMS') { for my $d ($site, $priv) { $d = VMS::Filespec::unixify($d) }; } s!/*$!!g foreach $site, $priv; next if $site eq $priv; if ($priv eq substr($inst_file,0,length($priv))) { return 1; } } return 0; } #-> sub CPAN::Module::install ; sub install { my($self) = @_; my($doit) = 0; if ($self->uptodate && not exists $self->{force_update} ) { $CPAN::Frontend->myprint(sprintf("%s is up to date (%s).\n", $self->id, $self->inst_version, )); } else { $doit = 1; } my $ro = $self->ro; if ($ro && $ro->{stats} && $ro->{stats} eq "a") { $CPAN::Frontend->mywarn(qq{ \n\n\n ***WARNING*** The module $self->{ID} has no active maintainer (CPAN support level flag 'abandoned').\n\n\n }); $CPAN::Frontend->mysleep(5); } return $doit ? $self->rematein('install') : 1; } #-> sub CPAN::Module::clean ; sub clean { shift->rematein('clean') } #-> sub CPAN::Module::inst_file ; sub inst_file { my($self) = @_; $self->_file_in_path([@INC]); } #-> sub CPAN::Module::available_file ; sub available_file { my($self) = @_; my $sep = $Config::Config{path_sep}; my $perllib = $ENV{PERL5LIB}; $perllib = $ENV{PERLLIB} unless defined $perllib; my @perllib = split(/$sep/,$perllib) if defined $perllib; my @cpan_perl5inc; if ($CPAN::Perl5lib_tempfile) { my $yaml = CPAN->_yaml_loadfile($CPAN::Perl5lib_tempfile); @cpan_perl5inc = @{$yaml->[0]{inc} || []}; } $self->_file_in_path([@cpan_perl5inc,@perllib,@INC]); } #-> sub CPAN::Module::file_in_path ; sub _file_in_path { my($self,$path) = @_; my($dir,@packpath); @packpath = split /::/, $self->{ID}; $packpath[-1] .= ".pm"; if (@packpath == 1 && $packpath[0] eq "readline.pm") { unshift @packpath, "Term", "ReadLine"; # historical reasons } foreach $dir (@$path) { my $pmfile = File::Spec->catfile($dir,@packpath); if (-f $pmfile) { return $pmfile; } } return; } #-> sub CPAN::Module::xs_file ; sub xs_file { my($self) = @_; my($dir,@packpath); @packpath = split /::/, $self->{ID}; push @packpath, $packpath[-1]; $packpath[-1] .= "." . $Config::Config{'dlext'}; foreach $dir (@INC) { my $xsfile = File::Spec->catfile($dir,'auto',@packpath); if (-f $xsfile) { return $xsfile; } } return; } #-> sub CPAN::Module::inst_version ; sub inst_version { my($self) = @_; my $parsefile = $self->inst_file or return; my $have = $self->parse_version($parsefile); $have; } #-> sub CPAN::Module::inst_version ; sub available_version { my($self) = @_; my $parsefile = $self->available_file or return; my $have = $self->parse_version($parsefile); $have; } #-> sub CPAN::Module::parse_version ; sub parse_version { my($self,$parsefile) = @_; if (ALARM_IMPLEMENTED) { my $timeout = (exists($CPAN::Config{'version_timeout'})) ? $CPAN::Config{'version_timeout'} : 15; alarm($timeout); } my $have = eval { local $SIG{ALRM} = sub { die "alarm\n" }; MM->parse_version($parsefile); }; if ($@) { $CPAN::Frontend->mywarn("Error while parsing version number in file '$parsefile'\n"); } alarm(0) if ALARM_IMPLEMENTED; my $leastsanity = eval { defined $have && length $have; }; $have = "undef" unless $leastsanity; $have =~ s/^ //; # since the %vd hack these two lines here are needed $have =~ s/ $//; # trailing whitespace happens all the time $have = CPAN::Version->readable($have); $have =~ s/\s*//g; # stringify to float around floating point issues $have; # no stringify needed, \s* above matches always } #-> sub CPAN::Module::reports sub reports { my($self) = @_; $self->distribution->reports; } 1; PK!hz.ηperl5/CPAN/FTP.pmnu6$# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::FTP; use strict; use Errno (); use Fcntl qw(:flock); use File::Basename qw(dirname); use File::Path qw(mkpath); use CPAN::FTP::netrc; use vars qw($connect_to_internet_ok $Ua $Thesite $ThesiteURL $Themethod); @CPAN::FTP::ISA = qw(CPAN::Debug); use vars qw( $VERSION ); $VERSION = "5.5016"; sub _plus_append_open { my($fh, $file) = @_; my $parent_dir = dirname $file; mkpath $parent_dir; my($cnt); until (open $fh, "+>>$file") { next if exists &Errno::EAGAIN && $! == &Errno::EAGAIN; # don't increment on EAGAIN $CPAN::Frontend->mydie("Could not open '$file' after 10000 tries: $!") if ++$cnt > 100000; sleep 0.0001; mkpath $parent_dir; } } #-> sub CPAN::FTP::ftp_statistics # if they want to rewrite, they need to pass in a filehandle sub _ftp_statistics { my($self,$fh) = @_; my $ftpstats_size = $CPAN::Config->{ftpstats_size}; return if defined $ftpstats_size && $ftpstats_size <= 0; my $locktype = $fh ? LOCK_EX : LOCK_SH; # XXX On Windows flock() implements mandatory locking, so we can # XXX only use shared locking to still allow _yaml_loadfile() to # XXX read from the file using a different filehandle. $locktype = LOCK_SH if $^O eq "MSWin32"; $fh ||= FileHandle->new; my $file = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml"); _plus_append_open($fh,$file); my $sleep = 1; my $waitstart; while (!CPAN::_flock($fh, $locktype|LOCK_NB)) { $waitstart ||= localtime(); if ($sleep>3) { my $now = localtime(); $CPAN::Frontend->mywarn("$now: waiting for read lock on '$file' (since $waitstart)\n"); } sleep($sleep); # this sleep must not be overridden; # Frontend->mysleep with AUTOMATED_TESTING has # provoked complete lock contention on my NFS if ($sleep <= 6) { $sleep+=0.5; } else { # retry to get a fresh handle. If it is NFS and the handle is stale, we will never get an flock _plus_append_open($fh, $file); } } my $stats = eval { CPAN->_yaml_loadfile($file, {loadblessed => 1}); }; if ($@) { if (ref $@) { if (ref $@ eq "CPAN::Exception::yaml_not_installed") { chomp $@; $CPAN::Frontend->myprintonce("Warning (usually harmless): $@\n"); return; } elsif (ref $@ eq "CPAN::Exception::yaml_process_error") { my $time = time; my $to = "$file.$time"; $CPAN::Frontend->mywarn("Error reading '$file': $@ Trying to stash it away as '$to' to prevent further interruptions. You may want to remove that file later.\n"); # may fail because somebody else has moved it away in the meantime: rename $file, $to or $CPAN::Frontend->mywarn("Could not rename '$file' to '$to': $!\n"); return; } } else { $CPAN::Frontend->mydie($@); } } CPAN::_flock($fh, LOCK_UN); return $stats->[0]; } #-> sub CPAN::FTP::_mytime sub _mytime () { if (CPAN->has_inst("Time::HiRes")) { return Time::HiRes::time(); } else { return time; } } #-> sub CPAN::FTP::_new_stats sub _new_stats { my($self,$file) = @_; my $ret = { file => $file, attempts => [], start => _mytime, }; $ret; } #-> sub CPAN::FTP::_add_to_statistics sub _add_to_statistics { my($self,$stats) = @_; my $yaml_module = CPAN::_yaml_module(); $self->debug("yaml_module[$yaml_module]") if $CPAN::DEBUG; if ($CPAN::META->has_inst($yaml_module)) { $stats->{thesiteurl} = $ThesiteURL; $stats->{end} = CPAN::FTP::_mytime(); my $fh = FileHandle->new; my $time = time; my $sdebug = 0; my @debug; @debug = $time if $sdebug; my $fullstats = $self->_ftp_statistics($fh); close $fh if $fh && defined(fileno($fh)); $fullstats->{history} ||= []; push @debug, scalar @{$fullstats->{history}} if $sdebug; push @debug, time if $sdebug; push @{$fullstats->{history}}, $stats; # YAML.pm 0.62 is unacceptably slow with 999; # YAML::Syck 0.82 has no noticable performance problem with 999; my $ftpstats_size = $CPAN::Config->{ftpstats_size}; $ftpstats_size = 99 unless defined $ftpstats_size; my $ftpstats_period = $CPAN::Config->{ftpstats_period} || 14; while ( @{$fullstats->{history} || []} && ( @{$fullstats->{history}} > $ftpstats_size || $time - $fullstats->{history}[0]{start} > 86400*$ftpstats_period ) ) { shift @{$fullstats->{history}} } push @debug, scalar @{$fullstats->{history}} if $sdebug; push @debug, time if $sdebug; push @debug, scalar localtime($fullstats->{history}[0]{start}) if $sdebug; # need no eval because if this fails, it is serious my $sfile = File::Spec->catfile($CPAN::Config->{cpan_home},"FTPstats.yml"); CPAN->_yaml_dumpfile("$sfile.$$",$fullstats); if ( $sdebug ) { local $CPAN::DEBUG = 512; # FTP push @debug, time; CPAN->debug(sprintf("DEBUG history: before_read[%d]before[%d]at[%d]". "after[%d]at[%d]oldest[%s]dumped backat[%d]", @debug, )); } # Win32 cannot rename a file to an existing filename unlink($sfile) if ($^O eq 'MSWin32' or $^O eq 'os2'); _copy_stat($sfile, "$sfile.$$") if -e $sfile; rename "$sfile.$$", $sfile or $CPAN::Frontend->mywarn("Could not rename '$sfile.$$' to '$sfile': $!\nGiving up\n"); } } # Copy some stat information (owner, group, mode and) from one file to # another. # This is a utility function which might be moved to a utility repository. #-> sub CPAN::FTP::_copy_stat sub _copy_stat { my($src, $dest) = @_; my @stat = stat($src); if (!@stat) { $CPAN::Frontend->mywarn("Can't stat '$src': $!\n"); return; } eval { chmod $stat[2], $dest or $CPAN::Frontend->mywarn("Can't chmod '$dest' to " . sprintf("0%o", $stat[2]) . ": $!\n"); }; warn $@ if $@; eval { chown $stat[4], $stat[5], $dest or do { my $save_err = $!; # otherwise it's lost in the get... calls $CPAN::Frontend->mywarn("Can't chown '$dest' to " . (getpwuid($stat[4]))[0] . "/" . (getgrgid($stat[5]))[0] . ": $save_err\n" ); }; }; warn $@ if $@; } # if file is CHECKSUMS, suggest the place where we got the file to be # checked from, maybe only for young files? #-> sub CPAN::FTP::_recommend_url_for sub _recommend_url_for { my($self, $file, $urllist) = @_; if ($file =~ s|/CHECKSUMS(.gz)?$||) { my $fullstats = $self->_ftp_statistics(); my $history = $fullstats->{history} || []; while (my $last = pop @$history) { last if $last->{end} - time > 3600; # only young results are interesting next unless $last->{file}; # dirname of nothing dies! next unless $file eq dirname($last->{file}); return $last->{thesiteurl}; } } if ($CPAN::Config->{randomize_urllist} && rand(1) < $CPAN::Config->{randomize_urllist} ) { $urllist->[int rand scalar @$urllist]; } else { return (); } } #-> sub CPAN::FTP::_get_urllist sub _get_urllist { my($self, $with_defaults) = @_; $with_defaults ||= 0; CPAN->debug("with_defaults[$with_defaults]") if $CPAN::DEBUG; $CPAN::Config->{urllist} ||= []; unless (ref $CPAN::Config->{urllist} eq 'ARRAY') { $CPAN::Frontend->mywarn("Malformed urllist; ignoring. Configuration file corrupt?\n"); $CPAN::Config->{urllist} = []; } my @urllist = grep { defined $_ and length $_ } @{$CPAN::Config->{urllist}}; push @urllist, @CPAN::Defaultsites if $with_defaults; for my $u (@urllist) { CPAN->debug("u[$u]") if $CPAN::DEBUG; if (UNIVERSAL::can($u,"text")) { $u->{TEXT} .= "/" unless substr($u->{TEXT},-1) eq "/"; } else { $u .= "/" unless substr($u,-1) eq "/"; $u = CPAN::URL->new(TEXT => $u, FROM => "USER"); } } \@urllist; } #-> sub CPAN::FTP::ftp_get ; sub ftp_get { my($class,$host,$dir,$file,$target) = @_; $class->debug( qq[Going to fetch file [$file] from dir [$dir] on host [$host] as local [$target]\n] ) if $CPAN::DEBUG; my $ftp = Net::FTP->new($host); unless ($ftp) { $CPAN::Frontend->mywarn(" Could not connect to host '$host' with Net::FTP\n"); return; } return 0 unless defined $ftp; $ftp->debug(1) if $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG; $class->debug(qq[Going to login("anonymous","$Config::Config{cf_email}")]); unless ( $ftp->login("anonymous",$Config::Config{'cf_email'}) ) { my $msg = $ftp->message; $CPAN::Frontend->mywarn(" Couldn't login on $host: $msg\n"); return; } unless ( $ftp->cwd($dir) ) { my $msg = $ftp->message; $CPAN::Frontend->mywarn(" Couldn't cwd $dir: $msg\n"); return; } $ftp->binary; $class->debug(qq[Going to ->get("$file","$target")\n]) if $CPAN::DEBUG; unless ( $ftp->get($file,$target) ) { my $msg = $ftp->message; $CPAN::Frontend->mywarn(" Couldn't fetch $file from $host: $msg\n"); return; } $ftp->quit; # it's ok if this fails return 1; } # If more accuracy is wanted/needed, Chris Leach sent me this patch... # > *** /install/perl/live/lib/CPAN.pm- Wed Sep 24 13:08:48 1997 # > --- /tmp/cp Wed Sep 24 13:26:40 1997 # > *************** # > *** 1562,1567 **** # > --- 1562,1580 ---- # > return 1 if substr($url,0,4) eq "file"; # > return 1 unless $url =~ m|://([^/]+)|; # > my $host = $1; # > + my $proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'}; # > + if ($proxy) { # > + $proxy =~ m|://([^/:]+)|; # > + $proxy = $1; # > + my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'}; # > + if ($noproxy) { # > + if ($host !~ /$noproxy$/) { # > + $host = $proxy; # > + } # > + } else { # > + $host = $proxy; # > + } # > + } # > require Net::Ping; # > return 1 unless $Net::Ping::VERSION >= 2; # > my $p; #-> sub CPAN::FTP::localize ; sub localize { my($self,$file,$aslocal,$force,$with_defaults) = @_; $force ||= 0; Carp::croak( "Usage: ->localize(cpan_file,as_local_file[,\$force])" ) unless defined $aslocal; if ($CPAN::DEBUG){ require Carp; my $longmess = Carp::longmess(); $self->debug("file[$file] aslocal[$aslocal] force[$force] carplongmess[$longmess]"); } for ($CPAN::Config->{connect_to_internet_ok}) { $connect_to_internet_ok = $_ if not defined $connect_to_internet_ok and defined $_; } my $ph = $CPAN::Config->{pushy_https}; if (!defined $ph || $ph) { return $self->localize_2021($file,$aslocal,$force,$with_defaults); } else { return $self->localize_1995ff($file,$aslocal,$force,$with_defaults); } } sub have_promising_aslocal { my($self, $aslocal, $force) = @_; if (-f $aslocal && -r _ && !($force & 1)) { my $size; if ($size = -s $aslocal) { $self->debug("aslocal[$aslocal]size[$size]") if $CPAN::DEBUG; return 1; } else { # empty file from a previous unsuccessful attempt to download it unlink $aslocal or $CPAN::Frontend->mydie("Found a zero-length '$aslocal' that I ". "could not remove."); } } return; } #-> sub CPAN::FTP::localize ; sub localize_2021 { my($self,$file,$aslocal,$force,$with_defaults) = @_; return $aslocal if $self->have_promising_aslocal($aslocal, $force); my($aslocal_dir) = dirname($aslocal); my $ret; $self->mymkpath($aslocal_dir); my $aslocal_tempfile = $aslocal . ".tmp" . $$; my $base; if ( ($CPAN::META->has_usable('HTTP::Tiny') && $CPAN::META->has_usable('Net::SSLeay') && $CPAN::META->has_usable('IO::Socket::SSL') ) || $CPAN::Config->{curl} || $CPAN::Config->{wget} ) { for my $prx (qw(https_proxy no_proxy)) { $ENV{$prx} = $CPAN::Config->{$prx} if $CPAN::Config->{$prx}; } $base = "https://cpan.org/"; } else { my @missing_modules = grep { ! $CPAN::META->has_usable($_) } qw(HTTP::Tiny Net::SSLeay IO::Socket::SSL); my $miss = join ", ", map { "'$_'" } @missing_modules; my $modules = @missing_modules == 1 ? "module" : "modules"; $CPAN::Frontend->mywarn("Missing or unusable $modules $miss, and found neither curl nor wget installed.\n"); if ($CPAN::META->has_usable('HTTP::Tiny')) { $CPAN::Frontend->mywarn("Need to fall back to http.\n") } for my $prx (qw(http_proxy no_proxy)) { $ENV{$prx} = $CPAN::Config->{$prx} if $CPAN::Config->{$prx}; } $base = "http://www.cpan.org/"; } $ret = $self->hostdl_2021($base,$file,$aslocal_tempfile); if ($ret) { # c&p from below CPAN->debug("ret[$ret]aslocal[$aslocal]") if $CPAN::DEBUG; if ($ret eq $aslocal_tempfile) { # if we got it exactly as we asked for, only then we # want to rename rename $aslocal_tempfile, $aslocal or $CPAN::Frontend->mydie("Error while trying to rename ". "'$ret' to '$aslocal': $!"); $ret = $aslocal; } } else { unlink $aslocal_tempfile; return; } return $ret; } sub hostdl_2021 { my($self, $base, $file, $aslocal) = @_; # the $aslocal is $aslocal_tempfile in the caller (old convention) my $proxy_vars = $self->_proxy_vars($base); my($proto) = $base =~ /^(https?)/; my $url = "$base$file"; # hostdl_2021 may be called with either http or https urls if ( $CPAN::META->has_usable('HTTP::Tiny') && ( $proto eq "http" || ( $CPAN::META->has_usable('Net::SSLeay') && $CPAN::META->has_usable('IO::Socket::SSL') ) ) ){ # mostly c&p from below require CPAN::HTTP::Client; my $chc = CPAN::HTTP::Client->new( proxy => $CPAN::Config->{http_proxy} || $ENV{http_proxy}, no_proxy => $CPAN::Config->{no_proxy} || $ENV{no_proxy}, ); for my $try ( $url, ( $url !~ /\.gz(?!\n)\Z/ ? "$url.gz" : () ) ) { $CPAN::Frontend->myprint("Fetching with HTTP::Tiny:\n$try\n"); my $res = eval { $chc->mirror($try, $aslocal) }; if ( $res && $res->{success} ) { my $now = time; utime $now, $now, $aslocal; # download time is more # important than upload # time return $aslocal; } elsif ( $res && $res->{status} ne '599') { $CPAN::Frontend->myprint(sprintf( "HTTP::Tiny failed with code[%s] message[%s]\n", $res->{status}, $res->{reason}, ) ); } elsif ( $res && $res->{status} eq '599') { $CPAN::Frontend->myprint(sprintf( "HTTP::Tiny failed with an internal error: %s\n", $res->{content}, ) ); } else { my $err = $@ || 'Unknown error'; $CPAN::Frontend->myprint(sprintf( "Error downloading with HTTP::Tiny: %s\n", $err ) ); } } } elsif ($CPAN::Config->{curl} || $CPAN::Config->{wget}){ # c&p from further down my($src_switch, $stdout_redir); my($devnull) = $CPAN::Config->{devnull} || ""; DLPRG: for my $dlprg (qw(curl wget)) { my $dlprg_configured = $CPAN::Config->{$dlprg}; next unless defined $dlprg_configured && length $dlprg_configured; my $funkyftp = CPAN::HandleConfig->safe_quote($dlprg_configured); if ($dlprg eq "wget") { $src_switch = " -O \"$aslocal\""; $stdout_redir = ""; } elsif ($dlprg eq 'curl') { $src_switch = ' -L -f -s -S --netrc-optional'; $stdout_redir = " > \"$aslocal\""; if ($proxy_vars->{http_proxy}) { $src_switch .= qq{ -U "$proxy_vars->{proxy_user}:$proxy_vars->{proxy_pass}" -x "$proxy_vars->{http_proxy}"}; } } $CPAN::Frontend->myprint( qq[ Trying with $funkyftp$src_switch to get $url ]); my($system) = "$funkyftp$src_switch \"$url\" $devnull$stdout_redir"; $self->debug("system[$system]") if $CPAN::DEBUG; my($wstatus) = system($system); if ($wstatus == 0) { return $aslocal; } else { my $estatus = $wstatus >> 8; my $size = -f $aslocal ? ", left\n$aslocal with size ".-s _ : "\nWarning: expected file [$aslocal] doesn't exist"; $CPAN::Frontend->myprint(qq{ Function system("$system") returned status $estatus (wstat $wstatus)$size }); } } # DLPRG } # curl, wget return; } #-> sub CPAN::FTP::localize ; sub localize_1995ff { my($self,$file,$aslocal,$force,$with_defaults) = @_; if ($^O eq 'MacOS') { # Comment by AK on 2000-09-03: Uniq short filenames would be # available in CHECKSUMS file my($name, $path) = File::Basename::fileparse($aslocal, ''); if (length($name) > 31) { $name =~ s/( \.( readme(\.(gz|Z))? | (tar\.)?(gz|Z) | tgz | zip | pm\.(gz|Z) ) )$//x; my $suf = $1; my $size = 31 - length($suf); while (length($name) > $size) { chop $name; } $name .= $suf; $aslocal = File::Spec->catfile($path, $name); } } return $aslocal if $self->have_promising_aslocal($aslocal, $force); my($maybe_restore) = 0; if (-f $aslocal) { rename $aslocal, "$aslocal.bak$$"; $maybe_restore++; } my($aslocal_dir) = dirname($aslocal); # Inheritance is not easier to manage than a few if/else branches if ($CPAN::META->has_usable('LWP::UserAgent')) { unless ($Ua) { CPAN::LWP::UserAgent->config; eval {$Ua = CPAN::LWP::UserAgent->new;}; # Why is has_usable still not fit enough? if ($@) { $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n") if $CPAN::DEBUG; } else { my($var); $Ua->proxy('ftp', $var) if $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy}; $Ua->proxy('http', $var) if $var = $CPAN::Config->{http_proxy} || $ENV{http_proxy}; $Ua->no_proxy($var) if $var = $CPAN::Config->{no_proxy} || $ENV{no_proxy}; } } } for my $prx (qw(ftp_proxy http_proxy no_proxy)) { $ENV{$prx} = $CPAN::Config->{$prx} if $CPAN::Config->{$prx}; } # Try the list of urls for each single object. We keep a record # where we did get a file from my(@reordered,$last); my $ccurllist = $self->_get_urllist($with_defaults); $last = $#$ccurllist; if ($force & 2) { # local cpans probably out of date, don't reorder @reordered = (0..$last); } else { @reordered = sort { (substr($ccurllist->[$b],0,4) eq "file") <=> (substr($ccurllist->[$a],0,4) eq "file") or defined($ThesiteURL) and ($ccurllist->[$b] eq $ThesiteURL) <=> ($ccurllist->[$a] eq $ThesiteURL) } 0..$last; } my(@levels); $Themethod ||= ""; $self->debug("Themethod[$Themethod]reordered[@reordered]") if $CPAN::DEBUG; my @all_levels = ( ["dleasy", "file"], ["dleasy"], ["dlhard"], ["dlhardest"], ["dleasy", "http","defaultsites"], ["dlhard", "http","defaultsites"], ["dleasy", "ftp", "defaultsites"], ["dlhard", "ftp", "defaultsites"], ["dlhardest","", "defaultsites"], ); if ($Themethod) { @levels = grep {$_->[0] eq $Themethod} @all_levels; push @levels, grep {$_->[0] ne $Themethod} @all_levels; } else { @levels = @all_levels; } @levels = qw/dleasy/ if $^O eq 'MacOS'; my($levelno); local $ENV{FTP_PASSIVE} = exists $CPAN::Config->{ftp_passive} ? $CPAN::Config->{ftp_passive} : 1; my $ret; my $stats = $self->_new_stats($file); LEVEL: for $levelno (0..$#levels) { my $level_tuple = $levels[$levelno]; my($level,$scheme,$sitetag) = @$level_tuple; $self->mymkpath($aslocal_dir) unless $scheme && "file" eq $scheme; my $defaultsites = $sitetag && $sitetag eq "defaultsites" && !@$ccurllist; my @urllist; if ($defaultsites) { unless (defined $connect_to_internet_ok) { $CPAN::Frontend->myprint(sprintf qq{ I would like to connect to one of the following sites to get '%s': %s }, $file, join("",map { " ".$_->text."\n" } @CPAN::Defaultsites), ); my $answer = CPAN::Shell::colorable_makemaker_prompt("Is it OK to try to connect to the Internet?", "yes"); if ($answer =~ /^y/i) { $connect_to_internet_ok = 1; } else { $connect_to_internet_ok = 0; } } if ($connect_to_internet_ok) { @urllist = @CPAN::Defaultsites; } else { my $sleep = 2; # the tricky thing about dying here is that everybody # believes that calls to exists() or all_objects() are # safe. require CPAN::Exception::blocked_urllist; die CPAN::Exception::blocked_urllist->new; } } else { # ! $defaultsites my @host_seq = $level =~ /dleasy/ ? @reordered : 0..$last; # reordered has file and $Thesiteurl first @urllist = map { $ccurllist->[$_] } @host_seq; } $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG; my $aslocal_tempfile = $aslocal . ".tmp" . $$; if (my $recommend = $self->_recommend_url_for($file,\@urllist)) { @urllist = grep { $_ ne $recommend } @urllist; unshift @urllist, $recommend; } $self->debug("synth. urllist[@urllist]") if $CPAN::DEBUG; $ret = $self->hostdlxxx($level,$scheme,\@urllist,$file,$aslocal_tempfile,$stats); if ($ret) { CPAN->debug("ret[$ret]aslocal[$aslocal]") if $CPAN::DEBUG; if ($ret eq $aslocal_tempfile) { # if we got it exactly as we asked for, only then we # want to rename rename $aslocal_tempfile, $aslocal or $CPAN::Frontend->mydie("Error while trying to rename ". "'$ret' to '$aslocal': $!"); $ret = $aslocal; } elsif (-f $ret && $scheme eq 'file' ) { # it's a local file, so there's nothing left to do, we # let them read from where it is } $Themethod = $level; my $now = time; # utime $now, $now, $aslocal; # too bad, if we do that, we # might alter a local mirror $self->debug("level[$level]") if $CPAN::DEBUG; last LEVEL; } else { unlink $aslocal_tempfile; last if $CPAN::Signal; # need to cleanup } } if ($ret) { $stats->{filesize} = -s $ret; } $self->debug("before _add_to_statistics") if $CPAN::DEBUG; $self->_add_to_statistics($stats); $self->debug("after _add_to_statistics") if $CPAN::DEBUG; if ($ret) { unlink "$aslocal.bak$$"; return $ret; } unless ($CPAN::Signal) { my(@mess); local $" = " "; if (@{$CPAN::Config->{urllist}}) { push @mess, qq{Please check, if the URLs I found in your configuration file \(}. join(", ", @{$CPAN::Config->{urllist}}). qq{\) are valid.}; } else { push @mess, qq{Your urllist is empty!}; } push @mess, qq{The urllist can be edited.}, qq{E.g. with 'o conf urllist push ftp://myurl/'}; $CPAN::Frontend->mywarn(Text::Wrap::wrap("","","@mess"). "\n\n"); $CPAN::Frontend->mydie("Could not fetch $file\n"); } if ($maybe_restore) { rename "$aslocal.bak$$", $aslocal; $CPAN::Frontend->myprint("Trying to get away with old file:\n" . $self->ls($aslocal) . "\n"); return $aslocal; } return; } sub mymkpath { my($self, $aslocal_dir) = @_; mkpath($aslocal_dir); $CPAN::Frontend->mywarn(qq{Warning: You are not allowed to write into }. qq{directory "$aslocal_dir". I\'ll continue, but if you encounter problems, they may be due to insufficient permissions.\n}) unless -w $aslocal_dir; } sub hostdlxxx { my $self = shift; my $level = shift; my $scheme = shift; my $h = shift; $h = [ grep /^\Q$scheme\E:/, @$h ] if $scheme; my $method = "host$level"; $self->$method($h, @_); } sub _set_attempt { my($self,$stats,$method,$url) = @_; push @{$stats->{attempts}}, { method => $method, start => _mytime, url => $url, }; } # package CPAN::FTP; sub hostdleasy { #called from hostdlxxx my($self,$host_seq,$file,$aslocal,$stats) = @_; my($ro_url); HOSTEASY: for $ro_url (@$host_seq) { $self->_set_attempt($stats,"dleasy",$ro_url); my $url = "$ro_url$file"; $self->debug("localizing perlish[$url]") if $CPAN::DEBUG; if ($url =~ /^file:/) { my $l; if ($CPAN::META->has_inst('URI::URL')) { my $u = URI::URL->new($url); $l = $u->file; } else { # works only on Unix, is poorly constructed, but # hopefully better than nothing. # RFC 1738 says fileurl BNF is # fileurl = "file://" [ host | "localhost" ] "/" fpath # Thanks to "Mark D. Baushke" for # the code ($l = $url) =~ s|^file://[^/]*/|/|; # discard the host part $l =~ s|^file:||; # assume they # meant # file://localhost $l =~ s|^/||s if ! -f $l && $l =~ m|^/\w:|; # e.g. /P: } $self->debug("local file[$l]") if $CPAN::DEBUG; if ( -f $l && -r _) { $ThesiteURL = $ro_url; return $l; } # If request is for a compressed file and we can find the # uncompressed file also, return the path of the uncompressed file # otherwise, decompress it and return the resulting path if ($l =~ /(.+)\.gz$/) { my $ungz = $1; if ( -f $ungz && -r _) { $ThesiteURL = $ro_url; return $ungz; } elsif (-f $l && -r _) { eval { CPAN::Tarzip->new($l)->gunzip($aslocal) }; if ( -f $aslocal && -s _) { $ThesiteURL = $ro_url; return $aslocal; } elsif (! -s $aslocal) { unlink $aslocal; } elsif (-f $l) { $CPAN::Frontend->mywarn("Error decompressing '$l': $@\n") if $@; return; } } } # Otherwise, return the local file path if it exists elsif ( -f $l && -r _) { $ThesiteURL = $ro_url; return $l; } # If we can't find it, but there is a compressed version # of it, then decompress it elsif (-f "$l.gz") { $self->debug("found compressed $l.gz") if $CPAN::DEBUG; eval { CPAN::Tarzip->new("$l.gz")->gunzip($aslocal) }; if ( -f $aslocal) { $ThesiteURL = $ro_url; return $aslocal; } else { $CPAN::Frontend->mywarn("Error decompressing '$l': $@\n") if $@; return; } } $CPAN::Frontend->mywarn("Could not find '$l'\n"); } $self->debug("it was not a file URL") if $CPAN::DEBUG; if ($CPAN::META->has_usable('LWP')) { $CPAN::Frontend->myprint("Fetching with LWP:\n$url\n"); unless ($Ua) { CPAN::LWP::UserAgent->config; eval { $Ua = CPAN::LWP::UserAgent->new; }; if ($@) { $CPAN::Frontend->mywarn("CPAN::LWP::UserAgent->new dies with $@\n"); } } my $res = $Ua->mirror($url, $aslocal); if ($res->is_success) { $ThesiteURL = $ro_url; my $now = time; utime $now, $now, $aslocal; # download time is more # important than upload # time return $aslocal; } elsif ($url !~ /\.gz(?!\n)\Z/) { my $gzurl = "$url.gz"; $CPAN::Frontend->myprint("Fetching with LWP:\n$gzurl\n"); $res = $Ua->mirror($gzurl, "$aslocal.gz"); if ($res->is_success) { if (eval {CPAN::Tarzip->new("$aslocal.gz")->gunzip($aslocal)}) { $ThesiteURL = $ro_url; return $aslocal; } } } else { $CPAN::Frontend->myprint(sprintf( "LWP failed with code[%s] message[%s]\n", $res->code, $res->message, )); # Alan Burlison informed me that in firewall environments # Net::FTP can still succeed where LWP fails. So we do not # skip Net::FTP anymore when LWP is available. } } elsif ($url =~ /^http:/i && $CPAN::META->has_usable('HTTP::Tiny')) { require CPAN::HTTP::Client; my $chc = CPAN::HTTP::Client->new( proxy => $CPAN::Config->{http_proxy} || $ENV{http_proxy}, no_proxy => $CPAN::Config->{no_proxy} || $ENV{no_proxy}, ); for my $try ( $url, ( $url !~ /\.gz(?!\n)\Z/ ? "$url.gz" : () ) ) { $CPAN::Frontend->myprint("Fetching with HTTP::Tiny:\n$try\n"); my $res = eval { $chc->mirror($try, $aslocal) }; if ( $res && $res->{success} ) { $ThesiteURL = $ro_url; my $now = time; utime $now, $now, $aslocal; # download time is more # important than upload # time return $aslocal; } elsif ( $res && $res->{status} ne '599') { $CPAN::Frontend->myprint(sprintf( "HTTP::Tiny failed with code[%s] message[%s]\n", $res->{status}, $res->{reason}, ) ); } elsif ( $res && $res->{status} eq '599') { $CPAN::Frontend->myprint(sprintf( "HTTP::Tiny failed with an internal error: %s\n", $res->{content}, ) ); } else { my $err = $@ || 'Unknown error'; $CPAN::Frontend->myprint(sprintf( "Error downloading with HTTP::Tiny: %s\n", $err ) ); } } } return if $CPAN::Signal; if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { # that's the nice and easy way thanks to Graham $self->debug("recognized ftp") if $CPAN::DEBUG; my($host,$dir,$getfile) = ($1,$2,$3); if ($CPAN::META->has_usable('Net::FTP')) { $dir =~ s|/+|/|g; $CPAN::Frontend->myprint("Fetching with Net::FTP:\n$url\n"); $self->debug("getfile[$getfile]dir[$dir]host[$host]" . "aslocal[$aslocal]") if $CPAN::DEBUG; if (CPAN::FTP->ftp_get($host,$dir,$getfile,$aslocal)) { $ThesiteURL = $ro_url; return $aslocal; } if ($aslocal !~ /\.gz(?!\n)\Z/) { my $gz = "$aslocal.gz"; $CPAN::Frontend->myprint("Fetching with Net::FTP\n$url.gz\n"); if (CPAN::FTP->ftp_get($host, $dir, "$getfile.gz", $gz) && eval{CPAN::Tarzip->new($gz)->gunzip($aslocal)} ) { $ThesiteURL = $ro_url; return $aslocal; } } # next HOSTEASY; } else { CPAN->debug("Net::FTP does not count as usable atm") if $CPAN::DEBUG; } } if ( UNIVERSAL::can($ro_url,"text") and $ro_url->{FROM} eq "USER" ) { ##address #17973: default URLs should not try to override ##user-defined URLs just because LWP is not available my $ret = $self->hostdlhard([$ro_url],$file,$aslocal,$stats); return $ret if $ret; } return if $CPAN::Signal; } } # package CPAN::FTP; sub hostdlhard { my($self,$host_seq,$file,$aslocal,$stats) = @_; # Came back if Net::FTP couldn't establish connection (or # failed otherwise) Maybe they are behind a firewall, but they # gave us a socksified (or other) ftp program... my($ro_url); my($devnull) = $CPAN::Config->{devnull} || ""; # < /dev/null "; my($aslocal_dir) = dirname($aslocal); mkpath($aslocal_dir); my $some_dl_success = 0; my $any_attempt = 0; HOSTHARD: for $ro_url (@$host_seq) { $self->_set_attempt($stats,"dlhard",$ro_url); my $url = "$ro_url$file"; my($proto,$host,$dir,$getfile); # Courtesy Mark Conty mark_conty@cargill.com change from # if ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { # to if ($url =~ m|^([^:]+)://(.*?)/(.*)/(.*)|) { # proto not yet used ($proto,$host,$dir,$getfile) = ($1,$2,$3,$4); } else { next HOSTHARD; # who said, we could ftp anything except ftp? } next HOSTHARD if $proto eq "file"; # file URLs would have had # success above. Likely a bogus URL # making at least one attempt against a host $any_attempt++; $self->debug("localizing funkyftpwise[$url]") if $CPAN::DEBUG; # Try the most capable first and leave ncftp* for last as it only # does FTP. my $proxy_vars = $self->_proxy_vars($ro_url); DLPRG: for my $f (qw(curl wget lynx ncftpget ncftp)) { my $funkyftp = CPAN::HandleConfig->safe_quote($CPAN::Config->{$f}); next DLPRG unless defined $funkyftp; next DLPRG if $funkyftp =~ /^\s*$/; my($src_switch) = ""; my($chdir) = ""; my($stdout_redir) = " > \"$aslocal\""; if ($f eq "lynx") { $src_switch = " -source"; } elsif ($f eq "ncftp") { next DLPRG unless $url =~ m{\Aftp://}; $src_switch = " -c"; } elsif ($f eq "wget") { $src_switch = " -O \"$aslocal\""; $stdout_redir = ""; } elsif ($f eq 'curl') { $src_switch = ' -L -f -s -S --netrc-optional'; if ($proxy_vars->{http_proxy}) { $src_switch .= qq{ -U "$proxy_vars->{proxy_user}:$proxy_vars->{proxy_pass}" -x "$proxy_vars->{http_proxy}"}; } } elsif ($f eq "ncftpget") { next DLPRG unless $url =~ m{\Aftp://}; $chdir = "cd $aslocal_dir && "; $stdout_redir = ""; } $CPAN::Frontend->myprint( qq[ Trying with $funkyftp$src_switch to get $url ]); my($system) = "$chdir$funkyftp$src_switch \"$url\" $devnull$stdout_redir"; $self->debug("system[$system]") if $CPAN::DEBUG; my($wstatus) = system($system); if ($f eq "lynx") { # lynx returns 0 when it fails somewhere if (-s $aslocal) { my $content = do { local *FH; open FH, $aslocal or die; local $/; }; if ($content =~ /^<.*([45]|Error [45])/si) { $CPAN::Frontend->mywarn(qq{ No success, the file that lynx has downloaded looks like an error message: $content }); $CPAN::Frontend->mysleep(1); next DLPRG; } $some_dl_success++; } else { $CPAN::Frontend->myprint(qq{ No success, the file that lynx has downloaded is an empty file. }); next DLPRG; } } if ($wstatus == 0) { if (-s $aslocal) { # Looks good $some_dl_success++; } $ThesiteURL = $ro_url; return $aslocal; } else { my $estatus = $wstatus >> 8; my $size = -f $aslocal ? ", left\n$aslocal with size ".-s _ : "\nWarning: expected file [$aslocal] doesn't exist"; $CPAN::Frontend->myprint(qq{ Function system("$system") returned status $estatus (wstat $wstatus)$size }); } return if $CPAN::Signal; } # download/transfer programs (DLPRG) } # host return unless $any_attempt; if ($some_dl_success) { $CPAN::Frontend->mywarn("Warning: doesn't seem we had substantial success downloading '$aslocal'. Don't know how to proceed.\n"); } else { $CPAN::Frontend->mywarn("Warning: no success downloading '$aslocal'. Giving up on it.\n"); } return; } #-> CPAN::FTP::_proxy_vars sub _proxy_vars { my($self,$url) = @_; my $ret = +{}; my $http_proxy = $CPAN::Config->{'http_proxy'} || $ENV{'http_proxy'}; if ($http_proxy) { my($host) = $url =~ m|://([^/:]+)|; my $want_proxy = 1; my $noproxy = $CPAN::Config->{'no_proxy'} || $ENV{'no_proxy'} || ""; my @noproxy = split /\s*,\s*/, $noproxy; if ($host) { DOMAIN: for my $domain (@noproxy) { if ($host =~ /\Q$domain\E$/) { # cf. LWP::UserAgent $want_proxy = 0; last DOMAIN; } } } else { $CPAN::Frontend->mywarn(" Could not determine host from http_proxy '$http_proxy'\n"); } if ($want_proxy) { my($user, $pass) = CPAN::HTTP::Credentials->get_proxy_credentials(); $ret = { proxy_user => $user, proxy_pass => $pass, http_proxy => $http_proxy }; } } return $ret; } # package CPAN::FTP; sub hostdlhardest { my($self,$host_seq,$file,$aslocal,$stats) = @_; return unless @$host_seq; my($ro_url); my($aslocal_dir) = dirname($aslocal); mkpath($aslocal_dir); my $ftpbin = $CPAN::Config->{ftp}; unless ($ftpbin && length $ftpbin && MM->maybe_command($ftpbin)) { $CPAN::Frontend->myprint("No external ftp command available\n\n"); return; } $CPAN::Frontend->mywarn(qq{ As a last resort we now switch to the external ftp command '$ftpbin' to get '$aslocal'. Doing so often leads to problems that are hard to diagnose. If you're the victim of such problems, please consider unsetting the ftp config variable with o conf ftp "" o conf commit }); $CPAN::Frontend->mysleep(2); HOSTHARDEST: for $ro_url (@$host_seq) { $self->_set_attempt($stats,"dlhardest",$ro_url); my $url = "$ro_url$file"; $self->debug("localizing ftpwise[$url]") if $CPAN::DEBUG; unless ($url =~ m|^ftp://(.*?)/(.*)/(.*)|) { next; } my($host,$dir,$getfile) = ($1,$2,$3); my $timestamp = 0; my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime, $ctime,$blksize,$blocks) = stat($aslocal); $timestamp = $mtime ||= 0; my($netrc) = CPAN::FTP::netrc->new; my($netrcfile) = $netrc->netrc; my($verbose) = $CPAN::DEBUG{'FTP'} & $CPAN::DEBUG ? " -v" : ""; my $targetfile = File::Basename::basename($aslocal); my(@dialog); push( @dialog, "lcd $aslocal_dir", "cd /", map("cd $_", split /\//, $dir), # RFC 1738 "bin", "passive", "get $getfile $targetfile", "quit" ); if (! $netrcfile) { CPAN->debug("No ~/.netrc file found") if $CPAN::DEBUG; } elsif ($netrc->hasdefault || $netrc->contains($host)) { CPAN->debug(sprintf("hasdef[%d]cont($host)[%d]", $netrc->hasdefault, $netrc->contains($host))) if $CPAN::DEBUG; if ($netrc->protected) { my $dialog = join "", map { " $_\n" } @dialog; my $netrc_explain; if ($netrc->contains($host)) { $netrc_explain = "Relying that your .netrc entry for '$host' ". "manages the login"; } else { $netrc_explain = "Relying that your default .netrc entry ". "manages the login"; } $CPAN::Frontend->myprint(qq{ Trying with external ftp to get '$url' $netrc_explain Sending the dialog $dialog } ); $self->talk_ftp("$ftpbin$verbose $host", @dialog); ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal); $mtime ||= 0; if ($mtime > $timestamp) { $CPAN::Frontend->myprint("GOT $aslocal\n"); $ThesiteURL = $ro_url; return $aslocal; } else { $CPAN::Frontend->myprint("Hmm... Still failed!\n"); } return if $CPAN::Signal; } else { $CPAN::Frontend->mywarn(qq{Your $netrcfile is not }. qq{correctly protected.\n}); } } else { $CPAN::Frontend->mywarn("Your ~/.netrc neither contains $host nor does it have a default entry\n"); } # OK, they don't have a valid ~/.netrc. Use 'ftp -n' # then and login manually to host, using e-mail as # password. $CPAN::Frontend->myprint(qq{Issuing "$ftpbin$verbose -n"\n}); unshift( @dialog, "open $host", "user anonymous $Config::Config{'cf_email'}" ); my $dialog = join "", map { " $_\n" } @dialog; $CPAN::Frontend->myprint(qq{ Trying with external ftp to get $url Sending the dialog $dialog } ); $self->talk_ftp("$ftpbin$verbose -n", @dialog); ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($aslocal); $mtime ||= 0; if ($mtime > $timestamp) { $CPAN::Frontend->myprint("GOT $aslocal\n"); $ThesiteURL = $ro_url; return $aslocal; } else { $CPAN::Frontend->myprint("Bad luck... Still failed!\n"); } return if $CPAN::Signal; $CPAN::Frontend->mywarn("Can't access URL $url.\n\n"); $CPAN::Frontend->mysleep(2); } # host } # package CPAN::FTP; sub talk_ftp { my($self,$command,@dialog) = @_; my $fh = FileHandle->new; $fh->open("|$command") or die "Couldn't open ftp: $!"; foreach (@dialog) { $fh->print("$_\n") } $fh->close; # Wait for process to complete my $wstatus = $?; my $estatus = $wstatus >> 8; $CPAN::Frontend->myprint(qq{ Subprocess "|$command" returned status $estatus (wstat $wstatus) }) if $wstatus; } # find2perl needs modularization, too, all the following is stolen # from there # CPAN::FTP::ls sub ls { my($self,$name) = @_; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$sizemm, $atime,$mtime,$ctime,$blksize,$blocks) = lstat($name); my($perms,%user,%group); my $pname = $name; if ($blocks) { $blocks = int(($blocks + 1) / 2); } else { $blocks = int(($sizemm + 1023) / 1024); } if (-f _) { $perms = '-'; } elsif (-d _) { $perms = 'd'; } elsif (-c _) { $perms = 'c'; $sizemm = &sizemm; } elsif (-b _) { $perms = 'b'; $sizemm = &sizemm; } elsif (-p _) { $perms = 'p'; } elsif (-S _) { $perms = 's'; } else { $perms = 'l'; $pname .= ' -> ' . readlink($_); } my(@rwx) = ('---','--x','-w-','-wx','r--','r-x','rw-','rwx'); my(@moname) = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my $tmpmode = $mode; my $tmp = $rwx[$tmpmode & 7]; $tmpmode >>= 3; $tmp = $rwx[$tmpmode & 7] . $tmp; $tmpmode >>= 3; $tmp = $rwx[$tmpmode & 7] . $tmp; substr($tmp,2,1) =~ tr/-x/Ss/ if -u _; substr($tmp,5,1) =~ tr/-x/Ss/ if -g _; substr($tmp,8,1) =~ tr/-x/Tt/ if -k _; $perms .= $tmp; my $user = $user{$uid} || $uid; # too lazy to implement lookup my $group = $group{$gid} || $gid; my($sec,$min,$hour,$mday,$mon,$year) = localtime($mtime); my($timeyear); my($moname) = $moname[$mon]; if (-M _ > 365.25 / 2) { $timeyear = $year + 1900; } else { $timeyear = sprintf("%02d:%02d", $hour, $min); } sprintf "%5lu %4ld %-10s %2d %-8s %-8s %8s %s %2d %5s %s\n", $ino, $blocks, $perms, $nlink, $user, $group, $sizemm, $moname, $mday, $timeyear, $pname; } 1; PK�������!�C �� ����perl5/CPAN/Plugin.pmnu�6$��������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<CPAN/"Plugin support">. =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 <barney@cpan.org> =cut PK�������!�Ч������perl5/CPAN/InfoObj.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::InfoObj; use strict; use CPAN::Debug; @CPAN::InfoObj::ISA = qw(CPAN::Debug); use Cwd qw(chdir); use vars qw( $VERSION ); $VERSION = "5.5"; sub ro { my $self = shift; exists $self->{RO} and return $self->{RO}; } #-> sub CPAN::InfoObj::cpan_userid sub cpan_userid { my $self = shift; my $ro = $self->ro; if ($ro) { return $ro->{CPAN_USERID} || "N/A"; } else { $self->debug("ID[$self->{ID}]"); # N/A for bundles found locally return "N/A"; } } sub id { shift->{ID}; } #-> sub CPAN::InfoObj::new ; sub new { my $this = bless {}, shift; %$this = @_; $this } # The set method may only be used by code that reads index data or # otherwise "objective" data from the outside world. All session # related material may do anything else with instance variables but # must not touch the hash under the RO attribute. The reason is that # the RO hash gets written to Metadata file and is thus persistent. #-> sub CPAN::InfoObj::safe_chdir ; sub safe_chdir { my($self,$todir) = @_; # we die if we cannot chdir and we are debuggable Carp::confess("safe_chdir called without todir argument") unless defined $todir and length $todir; if (chdir $todir) { $self->debug(sprintf "changed directory to %s", CPAN::anycwd()) if $CPAN::DEBUG; } else { if (-e $todir) { unless (-x $todir) { unless (chmod 0755, $todir) { my $cwd = CPAN::anycwd(); $CPAN::Frontend->mywarn("I have neither the -x permission nor the ". "permission to change the permission; cannot ". "chdir to '$todir'\n"); $CPAN::Frontend->mysleep(5); $CPAN::Frontend->mydie(qq{Could not chdir from cwd[$cwd] }. qq{to todir[$todir]: $!}); } } } else { $CPAN::Frontend->mydie("Directory '$todir' has gone. Cannot continue.\n"); } if (chdir $todir) { $self->debug(sprintf "changed directory to %s", CPAN::anycwd()) if $CPAN::DEBUG; } else { my $cwd = CPAN::anycwd(); $CPAN::Frontend->mydie(qq{Could not chdir from cwd[$cwd] }. qq{to todir[$todir] (a chmod has been issued): $!}); } } } #-> sub CPAN::InfoObj::set ; sub set { my($self,%att) = @_; my $class = ref $self; # This must be ||=, not ||, because only if we write an empty # reference, only then the set method will write into the readonly # area. But for Distributions that spring into existence, maybe # because of a typo, we do not like it that they are written into # the readonly area and made permanent (at least for a while) and # that is why we do not "allow" other places to call ->set. unless ($self->id) { CPAN->debug("Bug? Empty ID, rejecting"); return; } my $ro = $self->{RO} = $CPAN::META->{readonly}{$class}{$self->id} ||= {}; while (my($k,$v) = each %att) { $ro->{$k} = $v; } } #-> sub CPAN::InfoObj::as_glimpse ; sub as_glimpse { my($self) = @_; my(@m); my $class = ref($self); $class =~ s/^CPAN:://; my $id = $self->can("pretty_id") ? $self->pretty_id : $self->{ID}; push @m, sprintf "%-15s %s\n", $class, $id; join "", @m; } #-> sub CPAN::InfoObj::as_string ; sub as_string { my($self) = @_; my(@m); my $class = ref($self); $class =~ s/^CPAN:://; push @m, $class, " id = $self->{ID}\n"; my $ro; unless ($ro = $self->ro) { if (substr($self->{ID},-1,1) eq ".") { # directory $ro = +{}; } else { $CPAN::Frontend->mywarn("Unknown object $self->{ID}\n"); $CPAN::Frontend->mysleep(5); return; } } for (sort keys %$ro) { # next if m/^(ID|RO)$/; my $extra = ""; if ($_ eq "CPAN_USERID") { $extra .= " ("; $extra .= $self->fullname; my $email; # old perls! if ($email = $CPAN::META->instance("CPAN::Author", $self->cpan_userid )->email) { $extra .= " <$email>"; } else { $extra .= " <no email>"; } $extra .= ")"; } elsif ($_ eq "FULLNAME") { # potential UTF-8 conversion push @m, sprintf " %-12s %s\n", $_, $self->fullname; next; } next unless defined $ro->{$_}; push @m, sprintf " %-12s %s%s\n", $_, $ro->{$_}, $extra; } KEY: for (sort keys %$self) { next if m/^(ID|RO)$/; unless (defined $self->{$_}) { delete $self->{$_}; next KEY; } if (ref($self->{$_}) eq "ARRAY") { push @m, sprintf " %-12s %s\n", $_, "@{$self->{$_}}"; } elsif (ref($self->{$_}) eq "HASH") { my $value; if (/^CONTAINSMODS$/) { $value = join(" ",sort keys %{$self->{$_}}); } elsif (/^prereq_pm$/) { my @value; my $v = $self->{$_}; for my $x (sort keys %$v) { my @svalue; for my $y (sort keys %{$v->{$x}}) { push @svalue, "$y=>$v->{$x}{$y}"; } push @value, "$x\:" . join ",", @svalue if @svalue; } $value = join ";", @value; } else { $value = $self->{$_}; } push @m, sprintf( " %-12s %s\n", $_, $value, ); } else { push @m, sprintf " %-12s %s\n", $_, $self->{$_}; } } join "", @m, "\n"; } #-> sub CPAN::InfoObj::fullname ; sub fullname { my($self) = @_; $CPAN::META->instance("CPAN::Author",$self->cpan_userid)->fullname; } #-> sub CPAN::InfoObj::dump ; sub dump { my($self, $what) = @_; unless ($CPAN::META->has_inst("Data::Dumper")) { $CPAN::Frontend->mydie("dump command requires Data::Dumper installed"); } local $Data::Dumper::Sortkeys; $Data::Dumper::Sortkeys = 1; my $out = Data::Dumper::Dumper($what ? eval $what : $self); if (length $out > 100000) { my $fh_pager = FileHandle->new; local($SIG{PIPE}) = "IGNORE"; my $pager = $CPAN::Config->{'pager'} || "cat"; $fh_pager->open("|$pager") or die "Could not open pager $pager\: $!"; $fh_pager->print($out); close $fh_pager; } else { $CPAN::Frontend->myprint($out); } } 1; PK�������!�Ʈޒw^��w^����perl5/CPAN/HandleConfig.pmnu�6$��������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 = <<EOF if $configpm =~ m{CPAN/Config\.pm}; # 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. EOF $msg ||= "\n"; my($fh) = FileHandle->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<commands_quote> to determine the correct quote. If C<commands_quote> 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); The system CPAN configuration file has provided some default values, but you need to complete the configuration dialog for CPAN.pm. Configuration will be written to <<$configpm>> END } else { $CPAN::Frontend->myprint(<<END); Sorry, we have to rerun the configuration dialog for CPAN.pm due to some missing parameters. Configuration will be written to <<$configpm>> 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(<<END); Old configuration file $configpm moved to $configpm_bak END } } my $fh = FileHandle->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: PK�������!�>+������perl5/CPAN/LWP/UserAgent.pmnu�6$��������# -*- 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; PK�������!�w)#9��9��"��perl5/CPAN/Kwalify/distroprefs.ymlnu�6$��������--- 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 PK�������!�< �� ��!��perl5/CPAN/Kwalify/distroprefs.ddnu�6$��������$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"}; PK�������!�o������perl5/CPAN/CacheMgr.pmnu�6$��������# -*- 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; PK�������!�z6��6����perl5/CPAN/Debug.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- package CPAN::Debug; use strict; use vars qw($VERSION); $VERSION = "5.5001"; # module is internal to CPAN.pm %CPAN::DEBUG = qw[ CPAN 1 Index 2 InfoObj 4 Author 8 Distribution 16 Bundle 32 Module 64 CacheMgr 128 Complete 256 FTP 512 Shell 1024 Eval 2048 HandleConfig 4096 Tarzip 8192 Version 16384 Queue 32768 FirstTime 65536 ]; $CPAN::DEBUG ||= 0; #-> sub CPAN::Debug::debug ; sub debug { my($self,$arg) = @_; my @caller; my $i = 0; while () { my(@c) = (caller($i))[0 .. ($i ? 3 : 2)]; last unless defined $c[0]; push @caller, \@c; for (0,3) { last if $_ > $#c; $c[$_] =~ s/.*:://; } for (1) { $c[$_] =~ s|.*/||; } last if ++$i>=3; } pop @caller; if ($CPAN::DEBUG{$caller[0][0]} & $CPAN::DEBUG) { if ($arg and ref $arg) { eval { require Data::Dumper }; if ($@) { $CPAN::Frontend->myprint("Debug(\n" . $arg->as_string . ")\n"); } else { $CPAN::Frontend->myprint("Debug(\n" . Data::Dumper::Dumper($arg) . ")\n"); } } else { my $outer = ""; local $" = ","; if (@caller>1) { $outer = ",[@{$caller[1]}]"; } $CPAN::Frontend->myprint("Debug(@{$caller[0]}$outer): $arg\n"); } } } 1; __END__ =head1 NAME CPAN::Debug - internal debugging 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 PK�������!�$d ��d ����perl5/CPAN/Kwalify.pmnu�6$��������=head1 NAME CPAN::Kwalify - Interface between CPAN.pm and Kwalify.pm =head1 SYNOPSIS use CPAN::Kwalify; validate($schema_name, $data, $file, $doc); =head1 DESCRIPTION =over =item _validate($schema_name, $data, $file, $doc) $schema_name is the name of a supported schema. Currently only C<distroprefs> is supported. $data is the data to be validated. $file is the absolute path to the file the data are coming from. $doc is the index of the document within $doc that is to be validated. The last two arguments are only there for better error reporting. Relies on being called from within CPAN.pm. Dies if something fails. Does not return anything useful. =item yaml($schema_name) Returns the YAML text of that schema. Dies if something fails. =back =head1 AUTHOR Andreas Koenig C<< <andk@cpan.org> >> =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<http://www.perl.com/perl/misc/Artistic.html> =cut use strict; package CPAN::Kwalify; use vars qw($VERSION $VAR1); $VERSION = "5.50"; use File::Spec (); my %vcache = (); my $schema_loaded = {}; sub _validate { my($schema_name,$data,$abs,$y) = @_; my $yaml_module = CPAN->_yaml_module; if ( $CPAN::META->has_inst($yaml_module) && $CPAN::META->has_inst("Kwalify") ) { my $load = UNIVERSAL::can($yaml_module,"Load"); unless ($schema_loaded->{$schema_name}) { eval { my $schema_yaml = yaml($schema_name); $schema_loaded->{$schema_name} = $load->($schema_yaml); }; if ($@) { # we know that YAML.pm 0.62 cannot parse the schema, # so we try a fallback my $content = do { my $path = __FILE__; $path =~ s/\.pm$//; $path = File::Spec->catfile($path, "$schema_name.dd"); local *FH; open FH, $path or die "Could not open '$path': $!"; local $/; <FH>; }; $VAR1 = undef; eval $content; if (my $err = $@) { die "parsing of '$schema_name.dd' failed: $err"; } $schema_loaded->{$schema_name} = $VAR1; } } } if (my $schema = $schema_loaded->{$schema_name}) { my $mtime = (stat $abs)[9]; for my $k (keys %{$vcache{$abs}}) { delete $vcache{$abs}{$k} unless $k eq $mtime; } return if $vcache{$abs}{$mtime}{$y}++; eval { Kwalify::validate($schema, $data) }; if (my $err = $@) { my $info = {}; yaml($schema_name, info => $info); die "validation of distropref '$abs'[$y] against schema '$info->{path}' failed: $err"; } } } sub _clear_cache { %vcache = (); } sub yaml { my($schema_name, %opt) = @_; my $content = do { my $path = __FILE__; $path =~ s/\.pm$//; $path = File::Spec->catfile($path, "$schema_name.yml"); if ($opt{info}) { $opt{info}{path} = $path; } local *FH; open FH, $path or die "Could not open '$path': $!"; local $/; <FH>; }; return $content; } 1; # Local Variables: # mode: cperl # cperl-indent-level: 4 # End: PK�������!�bJG��JG����perl5/CPAN/Mirrors.pmnu�6$��������# -*- 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<MIRRORED.BY>. =cut sub continents { my ($self) = @_; return sort keys %{$self->{geography} || {}}; } =item countries( [CONTINENTS] ) Return a list of countries based on those defined in F<MIRRORED.BY>. It only returns countries for the continents you specify (as defined in C<continents>). If you don't specify any continents, it returns all of the countries listed in F<MIRRORED.BY>. =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<MIRRORED.BY>. It only returns mirrors for the countries you specify (as defined in C<countries>). If you don't specify any countries, it returns all of the mirrors listed in F<MIRRORED.BY>. =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<best_mirrors> checks for the best mirrors based on the list of continents you pass, or, without that, all continents, as defined by C<CPAN::Mirrored::By>. It pings each mirror, up to the value of C<how_many>. In list context, it returns up to C<how_many> 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<best_mirrors> calls C<find_best_continents> to get the list of continents to check. If you don't have L<Net::Ping> v2.13 or later, needed for timings, this returns the default mirror. C<external_ping> should be set and then C<Net::Ping::External> 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<MIRROR_LIST> is an anonymous array of C<CPAN::Mirrored::By> objects to ping. The optional argument C<SEEN> is a hash reference used to track the mirrors you've already pinged. The optional argument C<CALLBACK> is a subroutine reference to call after each ping. It gets the C<CPAN::Mirrored::By> 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<find_best_continents> goes through each continent and pings C<N> 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<seen> hash has hostnames as keys and anonymous arrays as values. The anonymous array is a triplet of a C<CPAN::Mirrored::By> object, a ping time, and the epoch time for the measurement. The callback subroutine gets the C<CPAN::Mirrored::By> object, the ping time, and measurement time (the same things in the C<seen> hashref) as arguments. C<find_best_continents> doesn't care what the callback does and ignores the return value. With a low value for C<N>, 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<< <andk@cpan.org> >>, David Golden C<< <dagolden@cpan.org> >>, brian d foy C<< <bdfoy@cpan.org> >> =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<http://www.perl.com/perl/misc/Artistic.html> =cut PK�������!�somFU��FU����perl5/CPAN/Meta/Requirements.pmnu�6$��������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<META.yml> or F<META.json> files in CPAN distributions, #pod and as defined by L<CPAN::Meta::Spec>. #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<bad_version_hook> -- 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<exactly> 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<CPAN::Meta::Spec> 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</accepts_module> 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</accepts_module> 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<finalize> 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<if> 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<CPAN::Meta::Spec> 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<CPAN::Meta::Spec/Version Ranges>. 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<E<gt>=>). 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</new> #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<META.yml> or F<META.json> files in CPAN distributions, and as defined by L<CPAN::Meta::Spec>. 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<bad_version_hook> -- 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<exactly> 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<CPAN::Meta::Spec> 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</accepts_module> 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</accepts_module> 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<finalize> method called on them. =head2 finalize This method marks the requirements finalized. Subsequent attempts to change the requirements will be fatal, I<if> 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<CPAN::Meta::Spec> 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<CPAN::Meta::Spec/Version Ranges>. 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<E<gt>=>). 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</new> 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<https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements/issues>. 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<https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements> git clone https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements.git =head1 AUTHORS =over 4 =item * David Golden <dagolden@cpan.org> =item * Ricardo Signes <rjbs@cpan.org> =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 <mohawk2@users.noreply.github.com> =item * Graham Knop <haarg@haarg.org> =item * Karen Etheridge <ether@cpan.org> =item * Leon Timmermans <fawaka@gmail.com> =item * Paul Howarth <paul@city-fan.org> =item * Ricardo Signes <rjbs@semiotic.systems> =item * robario <webmaster@robario.com> =item * Tatsuhiko Miyagawa <miyagawa@bulknews.net> =item * Tatsuhiko Miyagawa <miyagawa@gmail.com> =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 PK�������!�< $l:M��:M��%��perl5/CPAN/Meta/Requirements/Range.pmnu�6$��������use v5.10; use strict; use warnings; package CPAN::Meta::Requirements::Range; # ABSTRACT: a set of version requirements for a CPAN dist our $VERSION = '2.143'; use Carp (); #pod =head1 SYNOPSIS #pod #pod use CPAN::Meta::Requirements::Range; #pod #pod my $range = CPAN::Meta::Requirements::Range->with_minimum(1); #pod #pod $range = $range->with_maximum('v2.2'); #pod #pod my $stringified = $range->as_string; #pod #pod =head1 DESCRIPTION #pod #pod A CPAN::Meta::Requirements::Range object models a set of version constraints like #pod those specified in the F<META.yml> or F<META.json> files in CPAN distributions, #pod and as defined by L<CPAN::Meta::Spec>; #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 (); package CPAN::Meta::Requirements::Range::_Base; # To help ExtUtils::MakeMaker bootstrap CPAN::Meta::Requirements on perls # before 5.10, we fall back to the EUMM bundled compatibility version module if # that's the only thing available. This shouldn't ever happen in a normal CPAN # install of CPAN::Meta::Requirements, as version.pm will be picked up from # prereqs and be available at runtime. BEGIN { eval "use version ()"; ## no critic if ( my $err = $@ ) { eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic } } # from version::vpp sub _find_magic_vstring { my $value = shift; my $tvalue = ''; require B; my $sv = B::svref_2object(\$value); my $magic = ref($sv) eq 'B::PVMG' ? $sv->MAGIC : undef; while ( $magic ) { if ( $magic->TYPE eq 'V' ) { $tvalue = $magic->PTR; $tvalue =~ s/^v?(.+)$/v$1/; last; } else { $magic = $magic->MOREMAGIC; } } return $tvalue; } # Perl 5.10.0 didn't have "is_qv" in version.pm *_is_qv = version->can('is_qv') ? sub { $_[0]->is_qv } : sub { exists $_[0]->{qv} }; # construct once, reuse many times my $V0 = version->new(0); # safe if given an unblessed reference sub _isa_version { UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) && $_[0]->isa('version') } sub _version_object { my ($self, $version, $module, $bad_version_hook) = @_; my ($vobj, $err); if (not defined $version or (!ref($version) && $version eq '0')) { return $V0; } elsif ( ref($version) eq 'version' || ( ref($version) && _isa_version($version) ) ) { $vobj = $version; } else { # hack around version::vpp not handling <3 character vstring literals if ( $INC{'version/vpp.pm'} || $INC{'ExtUtils/MakeMaker/version/vpp.pm'} ) { my $magic = _find_magic_vstring( $version ); $version = $magic if length $magic; } # pad to 3 characters if before 5.8.1 and appears to be a v-string if ( $] < 5.008001 && $version !~ /\A[0-9]/ && substr($version,0,1) ne 'v' && length($version) < 3 ) { $version .= "\0" x (3 - length($version)); } eval { local $SIG{__WARN__} = sub { die "Invalid version: $_[0]" }; # avoid specific segfault on some older version.pm versions die "Invalid version: $version" if $version eq 'version'; $vobj = version->new($version); }; if ( my $err = $@ ) { $vobj = eval { $bad_version_hook->($version, $module) } if ref $bad_version_hook eq 'CODE'; unless (eval { $vobj->isa("version") }) { $err =~ s{ at .* line \d+.*$}{}; die "Can't convert '$version': $err"; } } } # ensure no leading '.' if ( $vobj =~ m{\A\.} ) { $vobj = version->new("0$vobj"); } # ensure normal v-string form if ( _is_qv($vobj) ) { $vobj = version->new($vobj->normal); } return $vobj; } #pod =method with_string_requirement #pod #pod $req->with_string_requirement('>= 1.208, <= 2.206'); #pod $req->with_string_requirement(v1.208); #pod #pod This method parses the passed in string and adds the appropriate requirement. #pod A version can be a Perl "v-string". It understands version ranges as described #pod in the L<CPAN::Meta::Spec/Version Ranges>. For 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<E<gt>=>). Extra whitespace is allowed. #pod #pod =back #pod #pod =cut my %methods_for_op = ( '==' => [ qw(with_exact_version) ], '!=' => [ qw(with_exclusion) ], '>=' => [ qw(with_minimum) ], '<=' => [ qw(with_maximum) ], '>' => [ qw(with_minimum with_exclusion) ], '<' => [ qw(with_maximum with_exclusion) ], ); sub with_string_requirement { my ($self, $req, $module, $bad_version_hook) = @_; $module //= 'module'; unless ( defined $req && length $req ) { $req = 0; Carp::carp("Undefined requirement for $module treated as '0'"); } my $magic = _find_magic_vstring( $req ); if (length $magic) { return $self->with_minimum($magic, $module, $bad_version_hook); } my @parts = split qr{\s*,\s*}, $req; for my $part (@parts) { my ($op, $ver) = $part =~ m{\A\s*(==|>=|>|<=|<|!=)\s*(.*)\z}; if (! defined $op) { $self = $self->with_minimum($part, $module, $bad_version_hook); } else { Carp::croak("illegal requirement string: $req") unless my $methods = $methods_for_op{ $op }; $self = $self->$_($ver, $module, $bad_version_hook) for @$methods; } } return $self; } #pod =method with_range #pod #pod $range->with_range($other_range) #pod #pod This creates a new range object that is a merge two others. #pod #pod =cut sub with_range { my ($self, $other, $module, $bad_version_hook) = @_; for my $modifier($other->_as_modifiers) { my ($method, $arg) = @$modifier; $self = $self->$method($arg, $module, $bad_version_hook); } return $self; } package CPAN::Meta::Requirements::Range; our @ISA = 'CPAN::Meta::Requirements::Range::_Base'; sub _clone { return (bless { } => $_[0]) unless ref $_[0]; my ($s) = @_; my %guts = ( (exists $s->{minimum} ? (minimum => version->new($s->{minimum})) : ()), (exists $s->{maximum} ? (maximum => version->new($s->{maximum})) : ()), (exists $s->{exclusions} ? (exclusions => [ map { version->new($_) } @{ $s->{exclusions} } ]) : ()), ); bless \%guts => ref($s); } #pod =method with_exact_version #pod #pod $range->with_exact_version( $version ); #pod #pod This sets the version required to I<exactly> the given #pod version. No other version would be considered acceptable. #pod #pod This method returns the version range object. #pod #pod =cut sub with_exact_version { my ($self, $version, $module, $bad_version_hook) = @_; $module //= 'module'; $self = $self->_clone; $version = $self->_version_object($version, $module, $bad_version_hook); unless ($self->accepts($version)) { $self->_reject_requirements( $module, "exact specification $version outside of range " . $self->as_string ); } return CPAN::Meta::Requirements::Range::_Exact->_new($version); } sub _simplify { my ($self, $module) = @_; if (defined $self->{minimum} and defined $self->{maximum}) { if ($self->{minimum} == $self->{maximum}) { if (grep { $_ == $self->{minimum} } @{ $self->{exclusions} || [] }) { $self->_reject_requirements( $module, "minimum and maximum are both $self->{minimum}, which is excluded", ); } return CPAN::Meta::Requirements::Range::_Exact->_new($self->{minimum}); } if ($self->{minimum} > $self->{maximum}) { $self->_reject_requirements( $module, "minimum $self->{minimum} exceeds maximum $self->{maximum}", ); } } # eliminate irrelevant exclusions if ($self->{exclusions}) { my %seen; @{ $self->{exclusions} } = grep { (! defined $self->{minimum} or $_ >= $self->{minimum}) and (! defined $self->{maximum} or $_ <= $self->{maximum}) and ! $seen{$_}++ } @{ $self->{exclusions} }; } return $self; } #pod =method with_minimum #pod #pod $range->with_minimum( $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 version range object. #pod #pod =cut sub with_minimum { my ($self, $minimum, $module, $bad_version_hook) = @_; $module //= 'module'; $self = $self->_clone; $minimum = $self->_version_object( $minimum, $module, $bad_version_hook ); if (defined (my $old_min = $self->{minimum})) { $self->{minimum} = (sort { $b cmp $a } ($minimum, $old_min))[0]; } else { $self->{minimum} = $minimum; } return $self->_simplify($module); } #pod =method with_maximum #pod #pod $range->with_maximum( $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 version range object. #pod #pod =cut sub with_maximum { my ($self, $maximum, $module, $bad_version_hook) = @_; $module //= 'module'; $self = $self->_clone; $maximum = $self->_version_object( $maximum, $module, $bad_version_hook ); if (defined (my $old_max = $self->{maximum})) { $self->{maximum} = (sort { $a cmp $b } ($maximum, $old_max))[0]; } else { $self->{maximum} = $maximum; } return $self->_simplify($module); } #pod =method with_exclusion #pod #pod $range->with_exclusion( $version ); #pod #pod This adds a new excluded version. For example, you might use these three #pod method calls: #pod #pod $range->with_minimum( '1.00' ); #pod $range->with_maximum( '1.82' ); #pod #pod $range->with_exclusion( '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 =cut sub with_exclusion { my ($self, $exclusion, $module, $bad_version_hook) = @_; $module //= 'module'; $self = $self->_clone; $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook ); push @{ $self->{exclusions} ||= [] }, $exclusion; return $self->_simplify($module); } sub _as_modifiers { my ($self) = @_; my @mods; push @mods, [ with_minimum => $self->{minimum} ] if exists $self->{minimum}; push @mods, [ with_maximum => $self->{maximum} ] if exists $self->{maximum}; push @mods, map {; [ with_exclusion => $_ ] } @{$self->{exclusions} || []}; return @mods; } #pod =method as_struct #pod #pod $range->as_struct( $module ); #pod #pod This returns a data structure containing the version requirements. This should #pod not be used for version checks (see L</accepts_module> instead). #pod #pod =cut sub as_struct { my ($self) = @_; return 0 if ! keys %$self; my @exclusions = @{ $self->{exclusions} || [] }; my @parts; for my $tuple ( [ qw( >= > minimum ) ], [ qw( <= < maximum ) ], ) { my ($op, $e_op, $k) = @$tuple; if (exists $self->{$k}) { my @new_exclusions = grep { $_ != $self->{ $k } } @exclusions; if (@new_exclusions == @exclusions) { push @parts, [ $op, "$self->{ $k }" ]; } else { push @parts, [ $e_op, "$self->{ $k }" ]; @exclusions = @new_exclusions; } } } push @parts, map {; [ "!=", "$_" ] } @exclusions; return \@parts; } #pod =method as_string #pod #pod $range->as_string; #pod #pod This returns a string containing the version requirements in the format #pod described in L<CPAN::Meta::Spec>. This should only be used for informational #pod purposes such as error messages and should not be interpreted or used for #pod comparison (see L</accepts> instead). #pod #pod =cut sub as_string { my ($self) = @_; my @parts = @{ $self->as_struct }; return $parts[0][1] if @parts == 1 and $parts[0][0] eq '>='; return join q{, }, map {; join q{ }, @$_ } @parts; } sub _reject_requirements { my ($self, $module, $error) = @_; Carp::croak("illegal requirements for $module: $error") } #pod =method accepts #pod #pod my $bool = $range->accepts($version); #pod #pod Given a version, this method returns true if the version specification #pod accepts the provided version. In other words, given: #pod #pod '>= 1.00, < 2.00' #pod #pod We will accept 1.00 and 1.75 but not 0.50 or 2.00. #pod #pod =cut sub accepts { my ($self, $version) = @_; return if defined $self->{minimum} and $version < $self->{minimum}; return if defined $self->{maximum} and $version > $self->{maximum}; return if defined $self->{exclusions} and grep { $version == $_ } @{ $self->{exclusions} }; return 1; } #pod =method is_simple #pod #pod This method returns true if and only if the range is an inclusive minimum #pod -- that is, if their string expression is just the version number. #pod #pod =cut sub is_simple { my ($self) = @_; # XXX: This is a complete hack, but also entirely correct. return if $self->as_string =~ /\s/; return 1; } package CPAN::Meta::Requirements::Range::_Exact; our @ISA = 'CPAN::Meta::Requirements::Range::_Base'; our $VERSION = '2.141'; BEGIN { eval "use version ()"; ## no critic if ( my $err = $@ ) { eval "use ExtUtils::MakeMaker::version" or die $err; ## no critic } } sub _new { bless { version => $_[1] } => $_[0] } sub accepts { return $_[0]{version} == $_[1] } sub _reject_requirements { my ($self, $module, $error) = @_; Carp::croak("illegal requirements for $module: $error") } sub _clone { (ref $_[0])->_new( version->new( $_[0]{version} ) ) } sub with_exact_version { my ($self, $version, $module, $bad_version_hook) = @_; $module //= 'module'; $version = $self->_version_object($version, $module, $bad_version_hook); return $self->_clone if $self->accepts($version); $self->_reject_requirements( $module, "can't be exactly $version when exact requirement is already $self->{version}", ); } sub with_minimum { my ($self, $minimum, $module, $bad_version_hook) = @_; $module //= 'module'; $minimum = $self->_version_object( $minimum, $module, $bad_version_hook ); return $self->_clone if $self->{version} >= $minimum; $self->_reject_requirements( $module, "minimum $minimum exceeds exact specification $self->{version}", ); } sub with_maximum { my ($self, $maximum, $module, $bad_version_hook) = @_; $module //= 'module'; $maximum = $self->_version_object( $maximum, $module, $bad_version_hook ); return $self->_clone if $self->{version} <= $maximum; $self->_reject_requirements( $module, "maximum $maximum below exact specification $self->{version}", ); } sub with_exclusion { my ($self, $exclusion, $module, $bad_version_hook) = @_; $module //= 'module'; $exclusion = $self->_version_object( $exclusion, $module, $bad_version_hook ); return $self->_clone unless $exclusion == $self->{version}; $self->_reject_requirements( $module, "tried to exclude $exclusion, which is already exactly specified", ); } sub as_string { return "== $_[0]{version}" } sub as_struct { return [ [ '==', "$_[0]{version}" ] ] } sub _as_modifiers { return [ with_exact_version => $_[0]{version} ] } 1; # vim: ts=2 sts=2 sw=2 et: __END__ =pod =encoding UTF-8 =head1 NAME CPAN::Meta::Requirements::Range - a set of version requirements for a CPAN dist =head1 VERSION version 2.143 =head1 SYNOPSIS use CPAN::Meta::Requirements::Range; my $range = CPAN::Meta::Requirements::Range->with_minimum(1); $range = $range->with_maximum('v2.2'); my $stringified = $range->as_string; =head1 DESCRIPTION A CPAN::Meta::Requirements::Range object models a set of version constraints like those specified in the F<META.yml> or F<META.json> files in CPAN distributions, and as defined by L<CPAN::Meta::Spec>; 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 with_string_requirement $req->with_string_requirement('>= 1.208, <= 2.206'); $req->with_string_requirement(v1.208); This method parses the passed in string and adds the appropriate requirement. A version can be a Perl "v-string". It understands version ranges as described in the L<CPAN::Meta::Spec/Version Ranges>. 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<E<gt>=>). Extra whitespace is allowed. =back =head2 with_range $range->with_range($other_range) This creates a new range object that is a merge two others. =head2 with_exact_version $range->with_exact_version( $version ); This sets the version required to I<exactly> the given version. No other version would be considered acceptable. This method returns the version range object. =head2 with_minimum $range->with_minimum( $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 version range object. =head2 with_maximum $range->with_maximum( $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 version range object. =head2 with_exclusion $range->with_exclusion( $version ); This adds a new excluded version. For example, you might use these three method calls: $range->with_minimum( '1.00' ); $range->with_maximum( '1.82' ); $range->with_exclusion( '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 as_struct $range->as_struct( $module ); This returns a data structure containing the version requirements. This should not be used for version checks (see L</accepts_module> instead). =head2 as_string $range->as_string; This returns a string containing the version requirements in the format described in L<CPAN::Meta::Spec>. This should only be used for informational purposes such as error messages and should not be interpreted or used for comparison (see L</accepts> instead). =head2 accepts my $bool = $range->accepts($version); Given a version, this method returns true if the version specification accepts the provided version. In other words, given: '>= 1.00, < 2.00' We will accept 1.00 and 1.75 but not 0.50 or 2.00. =head2 is_simple This method returns true if and only if the range is an inclusive minimum -- that is, if their string expression is just the version number. =head1 AUTHORS =over 4 =item * David Golden <dagolden@cpan.org> =item * Ricardo Signes <rjbs@cpan.org> =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 PK�������!�2]������perl5/CPAN/Nox.pmnu�6$��������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<CPAN> =cut PK�������!�}l+��+����perl5/CPAN/API/HOWTO.podnu�6$��������=head1 NAME CPAN::API::HOWTO - a recipe book for programming with CPAN.pm =head1 RECIPES All of these recipes assume that you have put "use CPAN" at the top of your program. =head2 What distribution contains a particular module? my $distribution = CPAN::Shell->expand( "Module", "Data::UUID" )->distribution()->pretty_id(); This returns a string of the form "AUTHORID/TARBALL". If you want the full path and filename to this distribution on a CPAN mirror, then it is C<.../authors/id/A/AU/AUTHORID/TARBALL>. =head2 What modules does a particular distribution contain? CPAN::Index->reload(); my @modules = CPAN::Shell->expand( "Distribution", "JHI/Graph-0.83.tar.gz" )->containsmods(); You may also refer to a distribution in the form A/AU/AUTHORID/TARBALL. =head1 SEE ALSO the main CPAN.pm documentation =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<http://www.perl.com/perl/misc/Artistic.html> =head1 AUTHOR David Cantrell =cut PK�������!�Y3�A���A����perl5/CPAN/Tarzip.pmnu�6$��������# -*- 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 PK�������!�H������perl5/CPAN/FTP/netrc.pmnu�6$��������package CPAN::FTP::netrc; use strict; $CPAN::FTP::netrc::VERSION = $CPAN::FTP::netrc::VERSION = "1.01"; # package CPAN::FTP::netrc; sub new { my($class) = @_; my $file = File::Spec->catfile($ENV{HOME},".netrc"); my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($file); $mode ||= 0; my $protected = 0; my($fh,@machines,$hasdefault); $hasdefault = 0; $fh = FileHandle->new or die "Could not create a filehandle"; if($fh->open($file)) { $protected = ($mode & 077) == 0; local($/) = ""; NETRC: while (<$fh>) { my(@tokens) = split " ", $_; TOKEN: while (@tokens) { my($t) = shift @tokens; if ($t eq "default") { $hasdefault++; last NETRC; } last TOKEN if $t eq "macdef"; if ($t eq "machine") { push @machines, shift @tokens; } } } } else { $file = $hasdefault = $protected = ""; } bless { 'mach' => [@machines], 'netrc' => $file, 'hasdefault' => $hasdefault, 'protected' => $protected, }, $class; } # CPAN::FTP::netrc::hasdefault; sub hasdefault { shift->{'hasdefault'} } sub netrc { shift->{'netrc'} } sub protected { shift->{'protected'} } sub contains { my($self,$mach) = @_; for ( @{$self->{'mach'}} ) { return 1 if $_ eq $mach; } return 0; } 1; PK�������!�UV��V����perl5/CPAN/Index.pmnu�6$��������package CPAN::Index; use strict; use vars qw($LAST_TIME $DATE_OF_02 $DATE_OF_03 $HAVE_REANIMATED $VERSION); $VERSION = "2.29"; @CPAN::Index::ISA = qw(CPAN::Debug); $LAST_TIME ||= 0; $DATE_OF_03 ||= 0; # use constant PROTOCOL => "2.0"; # commented out to avoid warning on upgrade from 1.57 sub PROTOCOL { 2.0 } #-> sub CPAN::Index::force_reload ; sub force_reload { my($class) = @_; $CPAN::Index::LAST_TIME = 0; $class->reload(1); } my @indexbundle = ( { reader => "rd_authindex", dir => "authors", remotefile => '01mailrc.txt.gz', shortlocalfile => '01mailrc.gz', }, { reader => "rd_modpacks", dir => "modules", remotefile => '02packages.details.txt.gz', shortlocalfile => '02packag.gz', }, { reader => "rd_modlist", dir => "modules", remotefile => '03modlist.data.gz', shortlocalfile => '03mlist.gz', }, ); #-> sub CPAN::Index::reload ; sub reload { my($self,$force) = @_; my $time = time; # XXX check if a newer one is available. (We currently read it # from time to time) for ($CPAN::Config->{index_expire}) { $_ = 0.001 unless $_ && $_ > 0.001; } unless (1 || $CPAN::Have_warned->{readmetadatacache}++) { # debug here when CPAN doesn't seem to read the Metadata require Carp; Carp::cluck("META-PROTOCOL[$CPAN::META->{PROTOCOL}]"); } unless ($CPAN::META->{PROTOCOL}) { $self->read_metadata_cache; $CPAN::META->{PROTOCOL} ||= "1.0"; } if ( $CPAN::META->{PROTOCOL} < PROTOCOL ) { # warn "Setting last_time to 0"; $LAST_TIME = 0; # No warning necessary } if ($LAST_TIME + $CPAN::Config->{index_expire}*86400 > $time and ! $force) { # called too often # CPAN->debug("LAST_TIME[$LAST_TIME]index_expire[$CPAN::Config->{index_expire}]time[$time]"); } elsif (0) { # IFF we are developing, it helps to wipe out the memory # between reloads, otherwise it is not what a user expects. undef $CPAN::META; # Neue Gruendlichkeit since v1.52(r1.274) $CPAN::META = CPAN->new; } else { my($debug,$t2); local $LAST_TIME = $time; local $CPAN::META->{PROTOCOL} = PROTOCOL; my $needshort = $^O eq "dos"; INX: for my $indexbundle (@indexbundle) { my $reader = $indexbundle->{reader}; my $localfile = $needshort ? $indexbundle->{shortlocalfile} : $indexbundle->{remotefile}; my $localpath = File::Spec->catfile($indexbundle->{dir}, $localfile); my $remote = join "/", $indexbundle->{dir}, $indexbundle->{remotefile}; my $localized = $self->reload_x($remote, $localpath, $force); $self->$reader($localized); # may die but we let the shell catch it if ($CPAN::DEBUG){ $t2 = time; $debug = "timing reading 01[".($t2 - $time)."]"; $time = $t2; } return if $CPAN::Signal; # this is sometimes lengthy } $self->write_metadata_cache; if ($CPAN::DEBUG){ $t2 = time; $debug .= "03[".($t2 - $time)."]"; $time = $t2; } CPAN->debug($debug) if $CPAN::DEBUG; } if ($CPAN::Config->{build_dir_reuse}) { $self->reanimate_build_dir; } if (CPAN::_sqlite_running()) { $CPAN::SQLite->reload(time => $time, force => $force) if not $LAST_TIME; } $LAST_TIME = $time; $CPAN::META->{PROTOCOL} = PROTOCOL; } #-> sub CPAN::Index::reanimate_build_dir ; sub reanimate_build_dir { my($self) = @_; unless ($CPAN::META->has_inst($CPAN::Config->{yaml_module}||"YAML")) { return; } return if $HAVE_REANIMATED++; my $d = $CPAN::Config->{build_dir}; my $dh = DirHandle->new; opendir $dh, $d or return; # does not exist my $dirent; my $i = 0; my $painted = 0; my $restored = 0; my $start = CPAN::FTP::_mytime(); my @candidates = map { $_->[0] } sort { $b->[1] <=> $a->[1] } map { [ $_, -M File::Spec->catfile($d,$_) ] } grep {/(.+)\.yml$/ && -d File::Spec->catfile($d,$1)} readdir $dh; if ( @candidates ) { $CPAN::Frontend->myprint (sprintf("Reading %d yaml file%s from %s/\n", scalar @candidates, @candidates==1 ? "" : "s", $CPAN::Config->{build_dir} )); DISTRO: for $i (0..$#candidates) { my $dirent = $candidates[$i]; my $y = eval {CPAN->_yaml_loadfile(File::Spec->catfile($d,$dirent), {loadblessed => 1})}; if ($@) { warn "Error while parsing file '$dirent'; error: '$@'"; next DISTRO; } my $c = $y->[0]; if ($c && $c->{perl} && $c->{distribution} && CPAN->_perl_fingerprint($c->{perl})) { my $key = $c->{distribution}{ID}; for my $k (keys %{$c->{distribution}}) { if ($c->{distribution}{$k} && ref $c->{distribution}{$k} && UNIVERSAL::isa($c->{distribution}{$k},"CPAN::Distrostatus")) { $c->{distribution}{$k}{COMMANDID} = $i - @candidates; } } #we tried to restore only if element already #exists; but then we do not work with metadata #turned off. my $do = $CPAN::META->{readwrite}{'CPAN::Distribution'}{$key} = $c->{distribution}; for my $skipper (qw( badtestcnt configure_requires_later configure_requires_later_for force_update later later_for notest should_report sponsored_mods prefs negative_prefs_cache )) { delete $do->{$skipper}; } if ($do->can("tested_ok_but_not_installed")) { if ($do->tested_ok_but_not_installed) { $CPAN::META->is_tested($do->{build_dir},$do->{make_test}{TIME}); } else { next DISTRO; } } $restored++; } $i++; while (($painted/76) < ($i/@candidates)) { $CPAN::Frontend->myprint("."); $painted++; } } } else { $CPAN::Frontend->myprint("Build_dir empty, nothing to restore\n"); } my $took = CPAN::FTP::_mytime() - $start; $CPAN::Frontend->myprint(sprintf( "DONE\nRestored the state of %s (in %.4f secs)\n", $restored || "none", $took, )); } #-> sub CPAN::Index::reload_x ; sub reload_x { my($cl,$wanted,$localname,$force) = @_; $force |= 2; # means we're dealing with an index here CPAN::HandleConfig->load; # we should guarantee loading wherever # we rely on Config XXX $localname ||= $wanted; my $abs_wanted = File::Spec->catfile($CPAN::Config->{'keep_source_where'}, $localname); if ( -f $abs_wanted && -M $abs_wanted < $CPAN::Config->{'index_expire'} && !($force & 1) ) { my $s = $CPAN::Config->{'index_expire'} == 1 ? "" : "s"; $cl->debug(qq{$abs_wanted younger than $CPAN::Config->{'index_expire'} }. qq{day$s. I\'ll use that.}); return $abs_wanted; } else { $force |= 1; # means we're quite serious about it. } return CPAN::FTP->localize($wanted,$abs_wanted,$force); } #-> sub CPAN::Index::rd_authindex ; sub rd_authindex { my($cl, $index_target) = @_; return unless defined $index_target; return if CPAN::_sqlite_running(); my @lines; $CPAN::Frontend->myprint("Reading '$index_target'\n"); local(*FH); tie *FH, 'CPAN::Tarzip', $index_target; local($/) = "\n"; local($_); push @lines, split /\012/ while <FH>; my $i = 0; my $painted = 0; foreach (@lines) { my($userid,$fullname,$email) = m/alias\s+(\S+)\s+\"([^\"\<]*)\s+\<(.*)\>\"/; $fullname ||= $email; if ($userid && $fullname && $email) { my $userobj = $CPAN::META->instance('CPAN::Author',$userid); $userobj->set('FULLNAME' => $fullname, 'EMAIL' => $email); } else { CPAN->debug(sprintf "line[%s]", $_) if $CPAN::DEBUG; } $i++; while (($painted/76) < ($i/@lines)) { $CPAN::Frontend->myprint("."); $painted++; } return if $CPAN::Signal; } $CPAN::Frontend->myprint("DONE\n"); } sub userid { my($self,$dist) = @_; $dist = $self->{'id'} unless defined $dist; my($ret) = $dist =~ m|(?:\w/\w\w/)?([^/]+)/|; $ret; } #-> sub CPAN::Index::rd_modpacks ; sub rd_modpacks { my($self, $index_target) = @_; return unless defined $index_target; return if CPAN::_sqlite_running(); $CPAN::Frontend->myprint("Reading '$index_target'\n"); my $fh = CPAN::Tarzip->TIEHANDLE($index_target); local $_; CPAN->debug(sprintf "start[%d]", time) if $CPAN::DEBUG; my $slurp = ""; my $chunk; while (my $bytes = $fh->READ(\$chunk,8192)) { $slurp.=$chunk; } my @lines = split /\012/, $slurp; CPAN->debug(sprintf "end[%d]", time) if $CPAN::DEBUG; undef $fh; # read header my($line_count,$last_updated); while (@lines) { my $shift = shift(@lines); last if $shift =~ /^\s*$/; $shift =~ /^Line-Count:\s+(\d+)/ and $line_count = $1; $shift =~ /^Last-Updated:\s+(.+)/ and $last_updated = $1; } CPAN->debug("line_count[$line_count]last_updated[$last_updated]") if $CPAN::DEBUG; my $errors = 0; if (not defined $line_count) { $CPAN::Frontend->mywarn(qq{Warning: Your $index_target does not contain a Line-Count header. Please check the validity of the index file by comparing it to more than one CPAN mirror. I'll continue but problems seem likely to happen.\a }); $errors++; $CPAN::Frontend->mysleep(5); } elsif ($line_count != scalar @lines) { $CPAN::Frontend->mywarn(sprintf qq{Warning: Your %s contains a Line-Count header of %d but I see %d lines there. Please check the validity of the index file by comparing it to more than one CPAN mirror. I'll continue but problems seem likely to happen.\a\n}, $index_target, $line_count, scalar(@lines)); } if (not defined $last_updated) { $CPAN::Frontend->mywarn(qq{Warning: Your $index_target does not contain a Last-Updated header. Please check the validity of the index file by comparing it to more than one CPAN mirror. I'll continue but problems seem likely to happen.\a }); $errors++; $CPAN::Frontend->mysleep(5); } else { $CPAN::Frontend ->myprint(sprintf qq{ Database was generated on %s\n}, $last_updated); $DATE_OF_02 = $last_updated; my $age = time; if ($CPAN::META->has_inst('HTTP::Date')) { require HTTP::Date; $age -= HTTP::Date::str2time($last_updated); } else { $CPAN::Frontend->mywarn(" HTTP::Date not available\n"); require Time::Local; my(@d) = $last_updated =~ / (\d+) (\w+) (\d+) (\d+):(\d+):(\d+) /; $d[1] = index("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", $d[1])/4; $age -= $d[1]>=0 ? Time::Local::timegm(@d[5,4,3,0,1,2]) : 0; } $age /= 3600*24; if ($age > 30) { $CPAN::Frontend ->mywarn(sprintf qq{Warning: This index file is %d days old. Please check the host you chose as your CPAN mirror for staleness. I'll continue but problems seem likely to happen.\a\n}, $age); } elsif ($age < -1) { $CPAN::Frontend ->mywarn(sprintf qq{Warning: Your system date is %d days behind this index file! System time: %s Timestamp index file: %s Please fix your system time, problems with the make command expected.\n}, -$age, scalar gmtime, $DATE_OF_02, ); } } # A necessity since we have metadata_cache: delete what isn't # there anymore my $secondtime = $CPAN::META->exists("CPAN::Module","CPAN"); CPAN->debug("secondtime[$secondtime]") if $CPAN::DEBUG; my(%exists); my $i = 0; my $painted = 0; LINE: foreach (@lines) { # before 1.56 we split into 3 and discarded the rest. From # 1.57 we assign remaining text to $comment thus allowing to # influence isa_perl my($mod,$version,$dist,$comment) = split " ", $_, 4; unless ($mod && defined $version && $dist) { require Dumpvalue; my $dv = Dumpvalue->new(tick => '"'); $CPAN::Frontend->mywarn(sprintf "Could not split line[%s]\n", $dv->stringify($_)); if ($errors++ >= 5){ $CPAN::Frontend->mydie("Giving up parsing your $index_target, too many errors"); } next LINE; } my($bundle,$id,$userid); if ($mod eq 'CPAN' && ! ( CPAN::Queue->exists('Bundle::CPAN') || CPAN::Queue->exists('CPAN') ) ) { local($^W)= 0; if ($version > $CPAN::VERSION) { $CPAN::Frontend->mywarn(qq{ New CPAN.pm version (v$version) available. [Currently running version is v$CPAN::VERSION] You might want to try install CPAN reload cpan to both upgrade CPAN.pm and run the new version without leaving the current session. }); #}); $CPAN::Frontend->mysleep(2); $CPAN::Frontend->myprint(qq{\n}); } last if $CPAN::Signal; } elsif ($mod =~ /^Bundle::(.*)/) { $bundle = $1; } if ($bundle) { $id = $CPAN::META->instance('CPAN::Bundle',$mod); # Let's make it a module too, because bundles have so much # in common with modules. # Changed in 1.57_63: seems like memory bloat now without # any value, so commented out # $CPAN::META->instance('CPAN::Module',$mod); } else { # instantiate a module object $id = $CPAN::META->instance('CPAN::Module',$mod); } # Although CPAN prohibits same name with different version the # indexer may have changed the version for the same distro # since the last time ("Force Reindexing" feature) if ($id->cpan_file ne $dist || $id->cpan_version ne $version ) { $userid = $id->userid || $self->userid($dist); $id->set( 'CPAN_USERID' => $userid, 'CPAN_VERSION' => $version, 'CPAN_FILE' => $dist, ); } # instantiate a distribution object if ($CPAN::META->exists('CPAN::Distribution',$dist)) { # we do not need CONTAINSMODS unless we do something with # this dist, so we better produce it on demand. ## my $obj = $CPAN::META->instance( ## 'CPAN::Distribution' => $dist ## ); ## $obj->{CONTAINSMODS}{$mod} = undef; # experimental } else { $CPAN::META->instance( 'CPAN::Distribution' => $dist )->set( 'CPAN_USERID' => $userid, 'CPAN_COMMENT' => $comment, ); } if ($secondtime) { for my $name ($mod,$dist) { # $self->debug("exists name[$name]") if $CPAN::DEBUG; $exists{$name} = undef; } } $i++; while (($painted/76) < ($i/@lines)) { $CPAN::Frontend->myprint("."); $painted++; } return if $CPAN::Signal; } $CPAN::Frontend->myprint("DONE\n"); if ($secondtime) { for my $class (qw(CPAN::Module CPAN::Bundle CPAN::Distribution)) { for my $o ($CPAN::META->all_objects($class)) { next if exists $exists{$o->{ID}}; $CPAN::META->delete($class,$o->{ID}); # CPAN->debug("deleting ID[$o->{ID}] in class[$class]") # if $CPAN::DEBUG; } } } } #-> sub CPAN::Index::rd_modlist ; sub rd_modlist { my($cl,$index_target) = @_; return unless defined $index_target; return if CPAN::_sqlite_running(); $CPAN::Frontend->myprint("Reading '$index_target'\n"); my $fh = CPAN::Tarzip->TIEHANDLE($index_target); local $_; my $slurp = ""; my $chunk; while (my $bytes = $fh->READ(\$chunk,8192)) { $slurp.=$chunk; } my @eval2 = split /\012/, $slurp; while (@eval2) { my $shift = shift(@eval2); if ($shift =~ /^Date:\s+(.*)/) { if ($DATE_OF_03 eq $1) { $CPAN::Frontend->myprint("Unchanged.\n"); return; } ($DATE_OF_03) = $1; } last if $shift =~ /^\s*$/; } push @eval2, q{CPAN::Modulelist->data;}; local($^W) = 0; my($compmt) = Safe->new("CPAN::Safe1"); my($eval2) = join("\n", @eval2); CPAN->debug(sprintf "length of eval2[%d]", length $eval2) if $CPAN::DEBUG; my $ret = $compmt->reval($eval2); Carp::confess($@) if $@; return if $CPAN::Signal; my $i = 0; my $until = keys(%$ret); my $painted = 0; CPAN->debug(sprintf "until[%d]", $until) if $CPAN::DEBUG; for (sort keys %$ret) { my $obj = $CPAN::META->instance("CPAN::Module",$_); delete $ret->{$_}{modid}; # not needed here, maybe elsewhere $obj->set(%{$ret->{$_}}); $i++; while (($painted/76) < ($i/$until)) { $CPAN::Frontend->myprint("."); $painted++; } return if $CPAN::Signal; } $CPAN::Frontend->myprint("DONE\n"); } #-> sub CPAN::Index::write_metadata_cache ; sub write_metadata_cache { my($self) = @_; return unless $CPAN::Config->{'cache_metadata'}; return if CPAN::_sqlite_running(); return unless $CPAN::META->has_usable("Storable"); my $cache; foreach my $k (qw(CPAN::Bundle CPAN::Author CPAN::Module CPAN::Distribution)) { $cache->{$k} = $CPAN::META->{readonly}{$k}; # unsafe meta access, ok } my $metadata_file = File::Spec->catfile($CPAN::Config->{cpan_home},"Metadata"); $cache->{last_time} = $LAST_TIME; $cache->{DATE_OF_02} = $DATE_OF_02; $cache->{PROTOCOL} = PROTOCOL; $CPAN::Frontend->myprint("Writing $metadata_file\n"); eval { Storable::nstore($cache, $metadata_file) }; $CPAN::Frontend->mywarn($@) if $@; # ?? missing "\n" after $@ in mywarn ?? } #-> sub CPAN::Index::read_metadata_cache ; sub read_metadata_cache { my($self) = @_; return unless $CPAN::Config->{'cache_metadata'}; return if CPAN::_sqlite_running(); return unless $CPAN::META->has_usable("Storable"); my $metadata_file = File::Spec->catfile($CPAN::Config->{cpan_home},"Metadata"); return unless -r $metadata_file and -f $metadata_file; $CPAN::Frontend->myprint("Reading '$metadata_file'\n"); my $cache; eval { $cache = Storable::retrieve($metadata_file) }; $CPAN::Frontend->mywarn($@) if $@; # ?? missing "\n" after $@ in mywarn ?? if (!$cache || !UNIVERSAL::isa($cache, 'HASH')) { $LAST_TIME = 0; return; } if (exists $cache->{PROTOCOL}) { if (PROTOCOL > $cache->{PROTOCOL}) { $CPAN::Frontend->mywarn(sprintf("Ignoring Metadata cache written ". "with protocol v%s, requiring v%s\n", $cache->{PROTOCOL}, PROTOCOL) ); return; } } else { $CPAN::Frontend->mywarn("Ignoring Metadata cache written ". "with protocol v1.0\n"); return; } my $clcnt = 0; my $idcnt = 0; while(my($class,$v) = each %$cache) { next unless $class =~ /^CPAN::/; $CPAN::META->{readonly}{$class} = $v; # unsafe meta access, ok while (my($id,$ro) = each %$v) { $CPAN::META->{readwrite}{$class}{$id} ||= $class->new(ID=>$id, RO=>$ro); $idcnt++; } $clcnt++; } unless ($clcnt) { # sanity check $CPAN::Frontend->myprint("Warning: Found no data in $metadata_file\n"); return; } if ($idcnt < 1000) { $CPAN::Frontend->myprint("Warning: Found only $idcnt objects ". "in $metadata_file\n"); return; } $CPAN::META->{PROTOCOL} ||= $cache->{PROTOCOL}; # reading does not up or downgrade, but it # does initialize to some protocol $LAST_TIME = $cache->{last_time}; $DATE_OF_02 = $cache->{DATE_OF_02}; $CPAN::Frontend->myprint(" Database was generated on $DATE_OF_02\n") if defined $DATE_OF_02; # An old cache may not contain DATE_OF_02 return; } 1; PK�������!�4A'��'����perl5/CPAN/Bundle.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Bundle; use strict; use CPAN::Module; @CPAN::Bundle::ISA = qw(CPAN::Module); use vars qw( $VERSION ); $VERSION = "5.5005"; sub look { my $self = shift; $CPAN::Frontend->myprint($self->as_string); } #-> CPAN::Bundle::undelay sub undelay { my $self = shift; delete $self->{later}; for my $c ( $self->contains ) { my $obj = CPAN::Shell->expandany($c) or next; if ($obj->id eq $self->id){ my $id = $obj->id; $CPAN::Frontend->mywarn("$id seems to contain itself, skipping\n"); next; } $obj->undelay; } } # mark as dirty/clean #-> sub CPAN::Bundle::color_cmd_tmps ; sub color_cmd_tmps { my($self) = shift; my($depth) = shift || 0; my($color) = shift || 0; my($ancestors) = shift || []; # a module needs to recurse to its cpan_file, a distribution needs # to recurse into its prereq_pms, a bundle needs to recurse into its modules return if exists $self->{incommandcolor} && $color==1 && $self->{incommandcolor}==$color; if ($depth>=$CPAN::MAX_RECURSION) { my $e = CPAN::Exception::RecursiveDependency->new($ancestors); if ($e->is_resolvable) { return $self->{incommandcolor}=2; } else { die $e; } } # warn "color_cmd_tmps $depth $color " . $self->id; # sleep 1; for my $c ( $self->contains ) { my $obj = CPAN::Shell->expandany($c) or next; CPAN->debug("c[$c]obj[$obj]") if $CPAN::DEBUG; $obj->color_cmd_tmps($depth+1,$color,[@$ancestors, $self->id]); } # never reached code? #if ($color==0) { #delete $self->{badtestcnt}; #} $self->{incommandcolor} = $color; } #-> sub CPAN::Bundle::as_string ; sub as_string { my($self) = @_; $self->contains; # following line must be "=", not "||=" because we have a moving target $self->{INST_VERSION} = $self->inst_version; return $self->SUPER::as_string; } #-> sub CPAN::Bundle::contains ; sub contains { my($self) = @_; my($inst_file) = $self->inst_file || ""; my($id) = $self->id; $self->debug("inst_file[$inst_file]id[$id]") if $CPAN::DEBUG; if ($inst_file && CPAN::Version->vlt($self->inst_version,$self->cpan_version)) { undef $inst_file; } unless ($inst_file) { # Try to get at it in the cpan directory $self->debug("no inst_file") if $CPAN::DEBUG; my $cpan_file; $CPAN::Frontend->mydie("I don't know a bundle with ID '$id'\n") unless $cpan_file = $self->cpan_file; if ($cpan_file eq "N/A") { $CPAN::Frontend->mywarn("Bundle '$id' not found on disk and not on CPAN. Maybe stale symlink? Maybe removed during session?\n"); return; } my $dist = $CPAN::META->instance('CPAN::Distribution', $self->cpan_file); $self->debug("before get id[$dist->{ID}]") if $CPAN::DEBUG; $dist->get; $self->debug("after get id[$dist->{ID}]") if $CPAN::DEBUG; my($todir) = $CPAN::Config->{'cpan_home'}; my(@me,$from,$to,$me); @me = split /::/, $self->id; $me[-1] .= ".pm"; $me = File::Spec->catfile(@me); my $build_dir; unless ($build_dir = $dist->{build_dir}) { $CPAN::Frontend->mywarn("Warning: cannot determine bundle content without a build_dir.\n"); return; } $from = $self->find_bundle_file($build_dir,join('/',@me)); $to = File::Spec->catfile($todir,$me); File::Path::mkpath(File::Basename::dirname($to)); File::Copy::copy($from, $to) or Carp::confess("Couldn't copy $from to $to: $!"); $inst_file = $to; } my @result; my $fh = FileHandle->new; local $/ = "\n"; open($fh,$inst_file) or die "Could not open '$inst_file': $!"; my $in_cont = 0; $self->debug("inst_file[$inst_file]") if $CPAN::DEBUG; while (<$fh>) { $in_cont = m/^=(?!head1\s+(?i-xsm:CONTENTS))/ ? 0 : m/^=head1\s+(?i-xsm:CONTENTS)/ ? 1 : $in_cont; next unless $in_cont; next if /^=/; s/\#.*//; next if /^\s+$/; chomp; push @result, (split " ", $_, 2)[0]; } close $fh; delete $self->{STATUS}; $self->{CONTAINS} = \@result; $self->debug("CONTAINS[@result]") if $CPAN::DEBUG; unless (@result) { $CPAN::Frontend->mywarn(qq{ The bundle file "$inst_file" may be a broken bundlefile. It seems not to contain any bundle definition. Please check the file and if it is bogus, please delete it. Sorry for the inconvenience. }); } @result; } #-> sub CPAN::Bundle::find_bundle_file # $where is in local format, $what is in unix format sub find_bundle_file { my($self,$where,$what) = @_; $self->debug("where[$where]what[$what]") if $CPAN::DEBUG; ### The following two lines let CPAN.pm become Bundle/CPAN.pm :-( ### my $bu = File::Spec->catfile($where,$what); ### return $bu if -f $bu; my $manifest = File::Spec->catfile($where,"MANIFEST"); unless (-f $manifest) { require ExtUtils::Manifest; my $cwd = CPAN::anycwd(); $self->safe_chdir($where); ExtUtils::Manifest::mkmanifest(); $self->safe_chdir($cwd); } my $fh = FileHandle->new($manifest) or Carp::croak("Couldn't open $manifest: $!"); local($/) = "\n"; my $bundle_filename = $what; $bundle_filename =~ s|Bundle.*/||; my $bundle_unixpath; while (<$fh>) { next if /^\s*\#/; my($file) = /(\S+)/; if ($file =~ m|\Q$what\E$|) { $bundle_unixpath = $file; # return File::Spec->catfile($where,$bundle_unixpath); # bad last; } # retry if she managed to have no Bundle directory $bundle_unixpath = $file if $file =~ m|\Q$bundle_filename\E$|; } return File::Spec->catfile($where, split /\//, $bundle_unixpath) if $bundle_unixpath; Carp::croak("Couldn't find a Bundle file in $where"); } # needs to work quite differently from Module::inst_file because of # cpan_home/Bundle/ directory and the possibility that we have # shadowing effect. As it makes no sense to take the first in @INC for # Bundles, we parse them all for $VERSION and take the newest. #-> sub CPAN::Bundle::inst_file ; sub inst_file { my($self) = @_; my($inst_file); my(@me); @me = split /::/, $self->id; $me[-1] .= ".pm"; my($incdir,$bestv); foreach $incdir ($CPAN::Config->{'cpan_home'},@INC) { my $parsefile = File::Spec->catfile($incdir, @me); CPAN->debug("parsefile[$parsefile]") if $CPAN::DEBUG; next unless -f $parsefile; my $have = eval { MM->parse_version($parsefile); }; if ($@) { $CPAN::Frontend->mywarn("Error while parsing version number in file '$parsefile'\n"); } if (!$bestv || CPAN::Version->vgt($have,$bestv)) { $self->{INST_FILE} = $parsefile; $self->{INST_VERSION} = $bestv = $have; } } $self->{INST_FILE}; } #-> sub CPAN::Bundle::inst_version ; sub inst_version { my($self) = @_; $self->inst_file; # finds INST_VERSION as side effect $self->{INST_VERSION}; } #-> sub CPAN::Bundle::rematein ; sub rematein { my($self,$meth) = @_; $self->debug("self[$self] meth[$meth]") if $CPAN::DEBUG; my($id) = $self->id; Carp::croak( "Can't $meth $id, don't have an associated bundle file. :-(\n" ) unless $self->inst_file || $self->cpan_file; my($s,%fail); for $s ($self->contains) { my($type) = $s =~ m|/| ? 'CPAN::Distribution' : $s =~ m|^Bundle::| ? 'CPAN::Bundle' : 'CPAN::Module'; if ($type eq 'CPAN::Distribution') { $CPAN::Frontend->mywarn(qq{ The Bundle }.$self->id.qq{ contains explicitly a file '$s'. Going to $meth that. }); $CPAN::Frontend->mysleep(5); } # possibly noisy action: $self->debug("type[$type] s[$s]") if $CPAN::DEBUG; my $obj = $CPAN::META->instance($type,$s); $obj->{reqtype} = $self->{reqtype}; $obj->{viabundle} ||= { id => $id, reqtype => $self->{reqtype}, optional => !$self->{mandatory}}; # $obj->$meth(); # XXX should optional be based on whether bundle was optional? -- xdg, 2012-04-01 # A: Sure, what could demand otherwise? --andk, 2013-11-25 CPAN::Queue->queue_item(qmod => $obj->id, reqtype => $self->{reqtype}, optional => !$self->{mandatory}); } } # If a bundle contains another that contains an xs_file we have here, # we just don't bother I suppose #-> sub CPAN::Bundle::xs_file sub xs_file { return 0; } #-> sub CPAN::Bundle::force ; sub fforce { shift->rematein('fforce',@_); } #-> sub CPAN::Bundle::force ; sub force { shift->rematein('force',@_); } #-> sub CPAN::Bundle::notest ; sub notest { shift->rematein('notest',@_); } #-> sub CPAN::Bundle::get ; sub get { shift->rematein('get',@_); } #-> sub CPAN::Bundle::make ; sub make { shift->rematein('make',@_); } #-> sub CPAN::Bundle::test ; sub test { my $self = shift; # $self->{badtestcnt} ||= 0; $self->rematein('test',@_); } #-> sub CPAN::Bundle::install ; sub install { my $self = shift; $self->rematein('install',@_); } #-> sub CPAN::Bundle::clean ; sub clean { shift->rematein('clean',@_); } #-> sub CPAN::Bundle::uptodate ; sub uptodate { my($self) = @_; return 0 unless $self->SUPER::uptodate; # we must have the current Bundle def my $c; foreach $c ($self->contains) { my $obj = CPAN::Shell->expandany($c); return 0 unless $obj->uptodate; } return 1; } #-> sub CPAN::Bundle::readme ; sub readme { my($self) = @_; my($file) = $self->cpan_file or $CPAN::Frontend->myprint(qq{ No File found for bundle } . $self->id . qq{\n}), return; $self->debug("self[$self] file[$file]") if $CPAN::DEBUG; $CPAN::META->instance('CPAN::Distribution',$file)->readme; } 1; PK�������!�naX67��7����perl5/CPAN/Prompt.pmnu�6$��������# -*- 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; PK�������!�䍽��������perl5/CPAN/DeferredCode.pmnu�6$��������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; PK�������!�ך9L��L����perl5/CPAN/URL.pmnu�6$��������# -*- 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; PK�������!�!+����perl5/CPAN/Shell.pmnu�6$��������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; PK�������!�L����perl5/CPAN/Distribution.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::Distribution; use strict; use Cwd qw(chdir); use CPAN::Distroprefs; use CPAN::InfoObj; use File::Path (); use POSIX ":sys_wait_h"; @CPAN::Distribution::ISA = qw(CPAN::InfoObj); use vars qw($VERSION); $VERSION = "2.34"; my $run_allow_installing_within_test = 1; # boolean; either in test or in install, there is no third option # no prepare, because prepare is not a command on the shell command line # TODO: clear instance cache on reload my %instance; for my $method (qw(get make test install)) { no strict 'refs'; for my $prefix (qw(pre post)) { my $hookname = sprintf "%s_%s", $prefix, $method; *$hookname = sub { my($self) = @_; for my $plugin (@{$CPAN::Config->{plugin_list}}) { my($plugin_proper,$args) = split /=/, $plugin, 2; $args = "" unless defined $args; if ($CPAN::META->has_inst($plugin_proper)){ my @args = split /,/, $args; $instance{$plugin} ||= $plugin_proper->new(@args); if ($instance{$plugin}->can($hookname)) { $instance{$plugin}->$hookname($self); } } else { $CPAN::Frontend->mydie("Plugin '$plugin_proper' not found for hook '$hookname'"); } } }; } } # Accessors sub cpan_comment { my $self = shift; my $ro = $self->ro or return; $ro->{CPAN_COMMENT} } #-> CPAN::Distribution::undelay sub undelay { my $self = shift; for my $delayer ( "configure_requires_later", "configure_requires_later_for", "later", "later_for", ) { delete $self->{$delayer}; } } #-> CPAN::Distribution::is_dot_dist sub is_dot_dist { my($self) = @_; return substr($self->id,-1,1) eq "."; } # add the A/AN/ stuff #-> CPAN::Distribution::normalize sub normalize { my($self,$s) = @_; $s = $self->id unless defined $s; if (substr($s,-1,1) eq ".") { # using a global because we are sometimes called as static method if (!$CPAN::META->{LOCK} && !$CPAN::Have_warned->{"$s is unlocked"}++ ) { $CPAN::Frontend->mywarn("You are visiting the local directory '$s' without lock, take care that concurrent processes do not do likewise.\n"); $CPAN::Frontend->mysleep(1); } if ($s eq ".") { $s = "$CPAN::iCwd/."; } elsif (File::Spec->file_name_is_absolute($s)) { } elsif (File::Spec->can("rel2abs")) { $s = File::Spec->rel2abs($s); } else { $CPAN::Frontend->mydie("Your File::Spec is too old, please upgrade File::Spec"); } CPAN->debug("s[$s]") if $CPAN::DEBUG; unless ($CPAN::META->exists("CPAN::Distribution", $s)) { for ($CPAN::META->instance("CPAN::Distribution", $s)) { $_->{build_dir} = $s; $_->{archived} = "local_directory"; $_->{unwrapped} = CPAN::Distrostatus->new("YES -- local_directory"); } } } elsif ( $s =~ tr|/|| == 1 or $s !~ m|[A-Z]/[A-Z-0-9]{2}/[A-Z-0-9]{2,}/| ) { return $s if $s =~ m:^N/A|^Contact Author: ; $s =~ s|^(.)(.)([^/]*/)(.+)$|$1/$1$2/$1$2$3$4|; CPAN->debug("s[$s]") if $CPAN::DEBUG; } $s; } #-> sub CPAN::Distribution::author ; sub author { my($self) = @_; my($authorid); if (substr($self->id,-1,1) eq ".") { $authorid = "LOCAL"; } else { ($authorid) = $self->pretty_id =~ /^([\w\-]+)/; } CPAN::Shell->expand("Author",$authorid); } # tries to get the yaml from CPAN instead of the distro itself: # EXPERIMENTAL, UNDOCUMENTED AND UNTESTED, for Tels sub fast_yaml { my($self) = @_; my $meta = $self->pretty_id; $meta =~ s/\.(tar.gz|tgz|zip|tar.bz2)/.meta/; my(@ls) = CPAN::Shell->globls($meta); my $norm = $self->normalize($meta); my($local_file); my($local_wanted) = File::Spec->catfile( $CPAN::Config->{keep_source_where}, "authors", "id", split(/\//,$norm) ); $self->debug("Doing localize") if $CPAN::DEBUG; unless ($local_file = CPAN::FTP->localize("authors/id/$norm", $local_wanted)) { $CPAN::Frontend->mydie("Giving up on downloading yaml file '$local_wanted'\n"); } my $yaml = CPAN->_yaml_loadfile($local_file)->[0]; } #-> sub CPAN::Distribution::cpan_userid sub cpan_userid { my $self = shift; if ($self->{ID} =~ m{[A-Z]/[A-Z\-]{2}/([A-Z\-]+)/}) { return $1; } return $self->SUPER::cpan_userid; } #-> sub CPAN::Distribution::pretty_id sub pretty_id { my $self = shift; my $id = $self->id; return $id unless $id =~ m|^./../|; substr($id,5); } #-> sub CPAN::Distribution::base_id sub base_id { my $self = shift; my $id = $self->pretty_id(); my $base_id = File::Basename::basename($id); $base_id =~ s{\.(?:tar\.(bz2|gz|Z)|t(?:gz|bz)|zip)$}{}i; return $base_id; } #-> sub CPAN::Distribution::tested_ok_but_not_installed sub tested_ok_but_not_installed { my $self = shift; return ( $self->{make_test} && $self->{build_dir} && (UNIVERSAL::can($self->{make_test},"failed") ? ! $self->{make_test}->failed : $self->{make_test} =~ /^YES/ ) && ( !$self->{install} || $self->{install}->failed ) ); } # mark as dirty/clean for the sake of recursion detection. $color=1 # means "in use", $color=0 means "not in use anymore". $color=2 means # we have determined prereqs now and thus insist on passing this # through (at least) once again. #-> sub CPAN::Distribution::color_cmd_tmps ; sub color_cmd_tmps { my($self) = shift; my($depth) = shift || 0; my($color) = shift || 0; my($ancestors) = shift || []; # a distribution needs to recurse into its prereq_pms $self->debug("color_cmd_tmps[$depth,$color,@$ancestors]") if $CPAN::DEBUG; return if exists $self->{incommandcolor} && $color==1 && $self->{incommandcolor}==$color; $CPAN::MAX_RECURSION||=0; # silence 'once' warnings if ($depth>=$CPAN::MAX_RECURSION) { my $e = CPAN::Exception::RecursiveDependency->new($ancestors); if ($e->is_resolvable) { return $self->{incommandcolor}=2; } else { die $e; } } # warn "color_cmd_tmps $depth $color " . $self->id; # sleep 1; my $prereq_pm = $self->prereq_pm; if (defined $prereq_pm) { # XXX also optional_req & optional_breq? -- xdg, 2012-04-01 # A: no, optional deps may recurse -- ak, 2014-05-07 PREREQ: for my $pre (sort( keys %{$prereq_pm->{requires}||{}}, keys %{$prereq_pm->{build_requires}||{}}, )) { next PREREQ if $pre eq "perl"; my $premo; unless ($premo = CPAN::Shell->expand("Module",$pre)) { $CPAN::Frontend->mywarn("prerequisite module[$pre] not known\n"); $CPAN::Frontend->mysleep(0.2); next PREREQ; } $premo->color_cmd_tmps($depth+1,$color,[@$ancestors, $self->id]); } } if ($color==0) { delete $self->{sponsored_mods}; # as we are at the end of a command, we'll give up this # reminder of a broken test. Other commands may test this guy # again. Maybe 'badtestcnt' should be renamed to # 'make_test_failed_within_command'? delete $self->{badtestcnt}; } $self->{incommandcolor} = $color; } #-> sub CPAN::Distribution::as_string ; sub as_string { my $self = shift; $self->containsmods; $self->upload_date; $self->SUPER::as_string(@_); } #-> sub CPAN::Distribution::containsmods ; sub containsmods { my $self = shift; return sort keys %{$self->{CONTAINSMODS}} if exists $self->{CONTAINSMODS}; my $dist_id = $self->{ID}; for my $mod ($CPAN::META->all_objects("CPAN::Module")) { my $mod_file = $mod->cpan_file or next; my $mod_id = $mod->{ID} or next; # warn "mod_file[$mod_file] dist_id[$dist_id] mod_id[$mod_id]"; # sleep 1; if ($CPAN::Signal) { delete $self->{CONTAINSMODS}; return; } $self->{CONTAINSMODS}{$mod_id} = undef if $mod_file eq $dist_id; } sort keys %{$self->{CONTAINSMODS}||={}}; } #-> sub CPAN::Distribution::upload_date ; sub upload_date { my $self = shift; return $self->{UPLOAD_DATE} if exists $self->{UPLOAD_DATE}; my(@local_wanted) = split(/\//,$self->id); my $filename = pop @local_wanted; push @local_wanted, "CHECKSUMS"; my $author = CPAN::Shell->expand("Author",$self->cpan_userid); return unless $author; my @dl = $author->dir_listing(\@local_wanted,0,$CPAN::Config->{show_upload_date}); return unless @dl; my($dirent) = grep { $_->[2] eq $filename } @dl; # warn sprintf "dirent[%s]id[%s]", $dirent, $self->id; return unless $dirent->[1]; return $self->{UPLOAD_DATE} = $dirent->[1]; } #-> sub CPAN::Distribution::uptodate ; sub uptodate { my($self) = @_; my $c; foreach $c ($self->containsmods) { my $obj = CPAN::Shell->expandany($c); unless ($obj->uptodate) { my $id = $self->pretty_id; $self->debug("$id not uptodate due to $c") if $CPAN::DEBUG; return 0; } } return 1; } #-> sub CPAN::Distribution::called_for ; sub called_for { my($self,$id) = @_; $self->{CALLED_FOR} = $id if defined $id; return $self->{CALLED_FOR}; } #-> sub CPAN::Distribution::shortcut_get ; # return values: undef means don't shortcut; 0 means shortcut as fail; # and 1 means shortcut as success sub shortcut_get { my ($self) = @_; if (exists $self->{cleanup_after_install_done}) { if ($self->{force_update}) { delete $self->{cleanup_after_install_done}; } else { my $id = $self->{CALLED_FOR} || $self->pretty_id; return $self->success( "Has already been *installed and cleaned up in the staging area* within this session, will not work on it again; if you really want to start over, try something like `force get $id`" ); } } if (my $why = $self->check_disabled) { $self->{unwrapped} = CPAN::Distrostatus->new("NO $why"); # XXX why is this goodbye() instead of just print/warn? # Alternatively, should other print/warns here be goodbye()? # -- xdg, 2012-04-05 return $self->goodbye("[disabled] -- NA $why"); } $self->debug("checking already unwrapped[$self->{ID}]") if $CPAN::DEBUG; if (exists $self->{build_dir} && -d $self->{build_dir}) { # this deserves print, not warn: return $self->success("Has already been unwrapped into directory ". "$self->{build_dir}" ); } # XXX I'm not sure this should be here because it's not really # a test for whether get should continue or return; this is # a side effect -- xdg, 2012-04-05 $self->debug("checking missing build_dir[$self->{ID}]") if $CPAN::DEBUG; if (exists $self->{build_dir} && ! -d $self->{build_dir}){ # we have lost it. $self->fforce(""); # no method to reset all phases but not set force (dodge) return undef; # no shortcut } # although we talk about 'force' we shall not test on # force directly. New model of force tries to refrain from # direct checking of force. $self->debug("checking unwrapping error[$self->{ID}]") if $CPAN::DEBUG; if ( exists $self->{unwrapped} and ( UNIVERSAL::can($self->{unwrapped},"failed") ? $self->{unwrapped}->failed : $self->{unwrapped} =~ /^NO/ ) ) { return $self->goodbye("Unwrapping had some problem, won't try again without force"); } return undef; # no shortcut } #-> sub CPAN::Distribution::get ; sub get { my($self) = @_; $self->pre_get(); $self->debug("checking goto id[$self->{ID}]") if $CPAN::DEBUG; if (my $goto = $self->prefs->{goto}) { $self->post_get(); return $self->goto($goto); } if ( defined( my $sc = $self->shortcut_get) ) { $self->post_get(); return $sc; } local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; # local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # get $CPAN::META->set_perl5lib; local $ENV{MAKEFLAGS}; # protect us from outer make calls my $sub_wd = CPAN::anycwd(); # for cleaning up as good as possible my($local_file); # XXX I don't think this check needs to be here, as it # is already checked in shortcut_get() -- xdg, 2012-04-05 unless ($self->{build_dir} && -d $self->{build_dir}) { $self->get_file_onto_local_disk; if ($CPAN::Signal){ $self->post_get(); return; } $self->check_integrity; if ($CPAN::Signal){ $self->post_get(); return; } (my $packagedir,$local_file) = $self->run_preps_on_packagedir; # XXX why is this check here? -- xdg, 2012-04-08 if (exists $self->{writemakefile} && ref $self->{writemakefile} && $self->{writemakefile}->can("failed") && $self->{writemakefile}->failed) { # $self->post_get(); return; } $packagedir ||= $self->{build_dir}; $self->{build_dir} = $packagedir; } # XXX should this move up to after run_preps_on_packagedir? # Otherwise, failing writemakefile can return without # a $CPAN::Signal check -- xdg, 2012-04-05 if ($CPAN::Signal) { $self->safe_chdir($sub_wd); $self->post_get(); return; } unless ($self->patch){ $self->post_get(); return; } $self->store_persistent_state; $self->post_get(); return 1; # success } #-> CPAN::Distribution::get_file_onto_local_disk sub get_file_onto_local_disk { my($self) = @_; return if $self->is_dot_dist; my($local_file); my($local_wanted) = File::Spec->catfile( $CPAN::Config->{keep_source_where}, "authors", "id", split(/\//,$self->id) ); $self->debug("Doing localize") if $CPAN::DEBUG; unless ($local_file = CPAN::FTP->localize("authors/id/$self->{ID}", $local_wanted)) { my $note = ""; if ($CPAN::Index::DATE_OF_02) { $note = "Note: Current database in memory was generated ". "on $CPAN::Index::DATE_OF_02\n"; } $CPAN::Frontend->mydie("Giving up on '$local_wanted'\n$note"); } $self->debug("local_wanted[$local_wanted]local_file[$local_file]") if $CPAN::DEBUG; $self->{localfile} = $local_file; } #-> CPAN::Distribution::check_integrity sub check_integrity { my($self) = @_; return if $self->is_dot_dist; if ($CPAN::META->has_inst("Digest::SHA")) { $self->debug("Digest::SHA is installed, verifying"); $self->verifyCHECKSUM; } else { $self->debug("Digest::SHA is NOT installed"); } } #-> CPAN::Distribution::run_preps_on_packagedir sub run_preps_on_packagedir { my($self) = @_; return if $self->is_dot_dist; $CPAN::META->{cachemgr} ||= CPAN::CacheMgr->new(); # unsafe meta access, ok my $builddir = $CPAN::META->{cachemgr}->dir; # unsafe meta access, ok $self->safe_chdir($builddir); $self->debug("Removing tmp-$$") if $CPAN::DEBUG; File::Path::rmtree("tmp-$$"); unless (mkdir "tmp-$$", 0755) { $CPAN::Frontend->unrecoverable_error(<<EOF); Couldn't mkdir '$builddir/tmp-$$': $! Cannot continue: Please find the reason why I cannot make the directory $builddir/tmp-$$ and fix the problem, then retry. EOF } if ($CPAN::Signal) { return; } $self->safe_chdir("tmp-$$"); # # Unpack the goods # my $local_file = $self->{localfile}; my $ct = eval{CPAN::Tarzip->new($local_file)}; unless ($ct) { $self->{unwrapped} = CPAN::Distrostatus->new("NO"); delete $self->{build_dir}; return; } if ($local_file =~ /(\.tar\.(bz2|gz|Z)|\.tgz)(?!\n)\Z/i) { $self->{was_uncompressed}++ unless eval{$ct->gtest()}; $self->untar_me($ct); } elsif ( $local_file =~ /\.zip(?!\n)\Z/i ) { $self->unzip_me($ct); } else { $self->{was_uncompressed}++ unless $ct->gtest(); $local_file = $self->handle_singlefile($local_file); } # we are still in the tmp directory! # Let's check if the package has its own directory. my $dh = DirHandle->new(File::Spec->curdir) or Carp::croak("Couldn't opendir .: $!"); my @readdir = grep $_ !~ /^\.\.?(?!\n)\Z/s, $dh->read; ### MAC?? if (grep { $_ eq "pax_global_header" } @readdir) { $CPAN::Frontend->mywarn("Your (un)tar seems to have extracted a file named 'pax_global_header' from the tarball '$local_file'. This is almost certainly an error. Please upgrade your tar. I'll ignore this file for now. See also http://rt.cpan.org/Ticket/Display.html?id=38932\n"); $CPAN::Frontend->mysleep(5); @readdir = grep { $_ ne "pax_global_header" } @readdir; } $dh->close; my $tdir_base; my $from_dir; my @dirents; if (@readdir == 1 && -d $readdir[0]) { $tdir_base = $readdir[0]; $from_dir = File::Spec->catdir(File::Spec->curdir,$readdir[0]); my($mode) = (stat $from_dir)[2]; chmod $mode | 00755, $from_dir; # JONATHAN/Math-Calculus-TaylorSeries-0.1.tar.gz has 0644 my $dh2; unless ($dh2 = DirHandle->new($from_dir)) { my $why = sprintf ( "Couldn't opendir '%s', mode '%o': %s", $from_dir, $mode, $!, ); $CPAN::Frontend->mywarn("$why\n"); $self->{writemakefile} = CPAN::Distrostatus->new("NO -- $why"); return; } @dirents = grep $_ !~ /^\.\.?(?!\n)\Z/s, $dh2->read; ### MAC?? } else { my $userid = $self->cpan_userid; CPAN->debug("userid[$userid]"); if (!$userid or $userid eq "N/A") { $userid = "anon"; } $tdir_base = $userid; $from_dir = File::Spec->curdir; @dirents = @readdir; } my $packagedir; my $eexist = ($CPAN::META->has_usable("Errno") && defined &Errno::EEXIST) ? &Errno::EEXIST : undef; for(my $suffix = 0; ; $suffix++) { $packagedir = File::Spec->catdir($builddir, "$tdir_base-$suffix"); my $parent = $builddir; mkdir($packagedir, 0777) and last; if((defined($eexist) && $! != $eexist) || $suffix == 999) { $CPAN::Frontend->mydie("Cannot create directory $packagedir: $!\n"); } } my $f; for $f (@dirents) { # is already without "." and ".." my $from = File::Spec->catfile($from_dir,$f); my($mode) = (stat $from)[2]; chmod $mode | 00755, $from if -d $from; # OTTO/Pod-Trial-LinkImg-0.005.tgz my $to = File::Spec->catfile($packagedir,$f); unless (File::Copy::move($from,$to)) { my $err = $!; $from = File::Spec->rel2abs($from); $CPAN::Frontend->mydie( "Couldn't move $from to $to: $err; #82295? ". "CPAN::VERSION=$CPAN::VERSION; ". "File::Copy::VERSION=$File::Copy::VERSION; ". "$from " . (-e $from ? "exists; " : "does not exist; "). "$to " . (-e $to ? "exists; " : "does not exist; "). "cwd=" . CPAN::anycwd() . ";" ); } } $self->{build_dir} = $packagedir; $self->safe_chdir($builddir); File::Path::rmtree("tmp-$$"); $self->safe_chdir($packagedir); $self->_signature_business(); $self->safe_chdir($builddir); return($packagedir,$local_file); } #-> sub CPAN::Distribution::pick_meta_file ; sub pick_meta_file { my($self, $filter) = @_; $filter = '.' unless defined $filter; my $build_dir; unless ($build_dir = $self->{build_dir}) { # maybe permission on build_dir was missing $CPAN::Frontend->mywarn("Warning: cannot determine META.yml without a build_dir.\n"); return; } my $has_cm = $CPAN::META->has_usable("CPAN::Meta"); my $has_pcm = $CPAN::META->has_usable("Parse::CPAN::Meta"); my @choices; push @choices, 'MYMETA.json' if $has_cm; push @choices, 'MYMETA.yml' if $has_cm || $has_pcm; push @choices, 'META.json' if $has_cm; push @choices, 'META.yml' if $has_cm || $has_pcm; for my $file ( grep { /$filter/ } @choices ) { my $path = File::Spec->catfile( $build_dir, $file ); return $path if -f $path } return; } #-> sub CPAN::Distribution::parse_meta_yml ; sub parse_meta_yml { my($self, $yaml) = @_; $self->debug(sprintf("parse_meta_yml[%s]",$yaml||'undef')) if $CPAN::DEBUG; my $build_dir = $self->{build_dir} or die "PANIC: cannot parse yaml without a build_dir"; $yaml ||= File::Spec->catfile($build_dir,"META.yml"); $self->debug("meta[$yaml]") if $CPAN::DEBUG; return unless -f $yaml; my $early_yaml; eval { $CPAN::META->has_inst("Parse::CPAN::Meta") or die; die "Parse::CPAN::Meta yaml too old" unless $Parse::CPAN::Meta::VERSION >= "1.40"; # P::C::M returns last document in scalar context $early_yaml = Parse::CPAN::Meta::LoadFile($yaml); }; unless ($early_yaml) { eval { $early_yaml = CPAN->_yaml_loadfile($yaml)->[0]; }; } $self->debug(sprintf("yaml[%s]", $early_yaml || 'UNDEF')) if $CPAN::DEBUG; $self->debug($early_yaml) if $CPAN::DEBUG && $early_yaml; if (!ref $early_yaml or ref $early_yaml ne "HASH"){ # fix rt.cpan.org #95271 $CPAN::Frontend->mywarn("The content of '$yaml' is not a HASH reference. Cannot use it.\n"); return {}; } return $early_yaml || undef; } #-> sub CPAN::Distribution::satisfy_requires ; # return values: 1 means requirements are satisfied; # and 0 means not satisfied (and maybe queued) sub satisfy_requires { my ($self) = @_; $self->debug("Entering satisfy_requires") if $CPAN::DEBUG; if (my @prereq = $self->unsat_prereq("later")) { if ($CPAN::DEBUG){ require Data::Dumper; my $prereq = Data::Dumper->new(\@prereq)->Terse(1)->Indent(0)->Dump; $self->debug("unsatisfied[$prereq]"); } if ($prereq[0][0] eq "perl") { my $need = "requires perl '$prereq[0][1]'"; my $id = $self->pretty_id; $CPAN::Frontend->mywarn("$id $need; you have only $]; giving up\n"); $self->{make} = CPAN::Distrostatus->new("NO $need"); $self->store_persistent_state; die "[prereq] -- NOT OK\n"; } else { my $follow = eval { $self->follow_prereqs("later",@prereq); }; if (0) { } elsif ($follow) { return; # we need deps } elsif ($@ && ref $@ && $@->isa("CPAN::Exception::RecursiveDependency")) { $CPAN::Frontend->mywarn($@); die "[depend] -- NOT OK\n"; } } } return 1; } #-> sub CPAN::Distribution::satisfy_configure_requires ; # return values: 1 means configure_require is satisfied; # and 0 means not satisfied (and maybe queued) sub satisfy_configure_requires { my($self) = @_; $self->debug("Entering satisfy_configure_requires") if $CPAN::DEBUG; my $enable_configure_requires = 1; if (!$enable_configure_requires) { return 1; # if we return 1 here, everything is as before we introduced # configure_requires that means, things with # configure_requires simply fail, all others succeed } my @prereq = $self->unsat_prereq("configure_requires_later"); $self->debug(sprintf "configure_requires[%s]", join(",",map {join "/",@$_} @prereq)) if $CPAN::DEBUG; return 1 unless @prereq; $self->debug(\@prereq) if $CPAN::DEBUG; if ($self->{configure_requires_later}) { for my $k (sort keys %{$self->{configure_requires_later_for}||{}}) { if ($self->{configure_requires_later_for}{$k}>1) { my $type = ""; for my $p (@prereq) { if ($p->[0] eq $k) { $type = $p->[1]; } } $type = " $type" if $type; $CPAN::Frontend->mywarn("Warning: unmanageable(?) prerequisite $k$type"); sleep 1; } } } if ($prereq[0][0] eq "perl") { my $need = "requires perl '$prereq[0][1]'"; my $id = $self->pretty_id; $CPAN::Frontend->mywarn("$id $need; you have only $]; giving up\n"); $self->{make} = CPAN::Distrostatus->new("NO $need"); $self->store_persistent_state; return $self->goodbye("[prereq] -- NOT OK"); } else { my $follow = eval { $self->follow_prereqs("configure_requires_later", @prereq); }; if (0) { } elsif ($follow) { return; # we need deps } elsif ($@ && ref $@ && $@->isa("CPAN::Exception::RecursiveDependency")) { $CPAN::Frontend->mywarn($@); return $self->goodbye("[depend] -- NOT OK"); } else { return $self->goodbye("[configure_requires] -- NOT OK"); } } die "never reached"; } #-> sub CPAN::Distribution::choose_MM_or_MB ; sub choose_MM_or_MB { my($self) = @_; $self->satisfy_configure_requires() or return; my $local_file = $self->{localfile}; my($mpl) = File::Spec->catfile($self->{build_dir},"Makefile.PL"); my($mpl_exists) = -f $mpl; unless ($mpl_exists) { # NFS has been reported to have racing problems after the # renaming of a directory in some environments. # This trick helps. $CPAN::Frontend->mysleep(1); my $mpldh = DirHandle->new($self->{build_dir}) or Carp::croak("Couldn't opendir $self->{build_dir}: $!"); $mpl_exists = grep /^Makefile\.PL$/, $mpldh->read; $mpldh->close; } my $prefer_installer = "eumm"; # eumm|mb if (-f File::Spec->catfile($self->{build_dir},"Build.PL")) { if ($mpl_exists) { # they *can* choose if ($CPAN::META->has_inst("Module::Build")) { $prefer_installer = CPAN::HandleConfig->prefs_lookup( $self, q{prefer_installer} ); # M::B <= 0.35 left a DATA handle open that # causes problems upgrading M::B on Windows close *Module::Build::Version::DATA if fileno *Module::Build::Version::DATA; } } else { $prefer_installer = "mb"; } } if (lc($prefer_installer) eq "rand") { $prefer_installer = rand()<.5 ? "eumm" : "mb"; } if (lc($prefer_installer) eq "mb") { $self->{modulebuild} = 1; } elsif ($self->{archived} eq "patch") { # not an edge case, nothing to install for sure my $why = "A patch file cannot be installed"; $CPAN::Frontend->mywarn("Refusing to handle this file: $why\n"); $self->{writemakefile} = CPAN::Distrostatus->new("NO $why"); } elsif (! $mpl_exists) { $self->_edge_cases($mpl,$local_file); } if ($self->{build_dir} && $CPAN::Config->{build_dir_reuse} ) { $self->store_persistent_state; } return $self; } # see also reanimate_build_dir #-> CPAN::Distribution::store_persistent_state sub store_persistent_state { my($self) = @_; my $dir = $self->{build_dir}; unless (defined $dir && length $dir) { my $id = $self->id; $CPAN::Frontend->mywarnonce("build_dir of $id is not known, ". "will not store persistent state\n"); return; } # self-build-dir my $sbd = Cwd::realpath( File::Spec->catdir($dir, File::Spec->updir ()) ); # config-build-dir my $cbd = Cwd::realpath( # the catdir is a workaround for bug https://rt.cpan.org/Ticket/Display.html?id=101283 File::Spec->catdir($CPAN::Config->{build_dir}, File::Spec->curdir()) ); unless ($sbd eq $cbd) { $CPAN::Frontend->mywarnonce("Directory '$dir' not below $CPAN::Config->{build_dir}, ". "will not store persistent state\n"); return; } my $file = sprintf "%s.yml", $dir; my $yaml_module = CPAN::_yaml_module(); if ($CPAN::META->has_inst($yaml_module)) { CPAN->_yaml_dumpfile( $file, { time => time, perl => CPAN::_perl_fingerprint(), distribution => $self, } ); } else { $CPAN::Frontend->myprintonce("'$yaml_module' not installed, ". "will not store persistent state\n"); } } #-> CPAN::Distribution::try_download sub try_download { my($self,$patch) = @_; my $norm = $self->normalize($patch); my($local_wanted) = File::Spec->catfile( $CPAN::Config->{keep_source_where}, "authors", "id", split(/\//,$norm), ); $self->debug("Doing localize") if $CPAN::DEBUG; return CPAN::FTP->localize("authors/id/$norm", $local_wanted); } { my $stdpatchargs = ""; #-> CPAN::Distribution::patch sub patch { my($self) = @_; $self->debug("checking patches id[$self->{ID}]") if $CPAN::DEBUG; my $patches = $self->prefs->{patches}; $patches ||= ""; $self->debug("patches[$patches]") if $CPAN::DEBUG; if ($patches) { return unless @$patches; $self->safe_chdir($self->{build_dir}); CPAN->debug("patches[$patches]") if $CPAN::DEBUG; my $patchbin = $CPAN::Config->{patch}; unless ($patchbin && length $patchbin) { $CPAN::Frontend->mydie("No external patch command configured\n\n". "Please run 'o conf init /patch/'\n\n"); } unless (MM->maybe_command($patchbin)) { $CPAN::Frontend->mydie("No external patch command available\n\n". "Please run 'o conf init /patch/'\n\n"); } $patchbin = CPAN::HandleConfig->safe_quote($patchbin); local $ENV{PATCH_GET} = 0; # formerly known as -g0 unless ($stdpatchargs) { my $system = "$patchbin --version |"; local *FH; open FH, $system or die "Could not fork '$system': $!"; local $/ = "\n"; my $pversion; PARSEVERSION: while (<FH>) { if (/^patch\s+([\d\.]+)/) { $pversion = $1; last PARSEVERSION; } } if ($pversion) { $stdpatchargs = "-N --fuzz=3"; } else { $stdpatchargs = "-N"; } } my $countedpatches = @$patches == 1 ? "1 patch" : (scalar @$patches . " patches"); $CPAN::Frontend->myprint("Applying $countedpatches:\n"); my $patches_dir = $CPAN::Config->{patches_dir}; for my $patch (@$patches) { if ($patches_dir && !File::Spec->file_name_is_absolute($patch)) { my $f = File::Spec->catfile($patches_dir, $patch); $patch = $f if -f $f; } unless (-f $patch) { CPAN->debug("not on disk: patch[$patch]") if $CPAN::DEBUG; if (my $trydl = $self->try_download($patch)) { $patch = $trydl; } else { my $fail = "Could not find patch '$patch'"; $CPAN::Frontend->mywarn("$fail; cannot continue\n"); $self->{unwrapped} = CPAN::Distrostatus->new("NO -- $fail"); delete $self->{build_dir}; return; } } $CPAN::Frontend->myprint(" $patch\n"); my $readfh = CPAN::Tarzip->TIEHANDLE($patch); my $pcommand; my($ppp,$pfiles) = $self->_patch_p_parameter($readfh); if ($ppp eq "applypatch") { $pcommand = "$CPAN::Config->{applypatch} -verbose"; } else { my $thispatchargs = join " ", $stdpatchargs, $ppp; $pcommand = "$patchbin $thispatchargs"; require Config; # usually loaded from CPAN.pm if ($Config::Config{osname} eq "solaris") { # native solaris patch cannot patch readonly files for my $file (@{$pfiles||[]}) { my @stat = stat $file or next; chmod $stat[2] | 0600, $file; # may fail } } } $readfh = CPAN::Tarzip->TIEHANDLE($patch); # open again my $writefh = FileHandle->new; $CPAN::Frontend->myprint(" $pcommand\n"); unless (open $writefh, "|$pcommand") { my $fail = "Could not fork '$pcommand'"; $CPAN::Frontend->mywarn("$fail; cannot continue\n"); $self->{unwrapped} = CPAN::Distrostatus->new("NO -- $fail"); delete $self->{build_dir}; return; } binmode($writefh); while (my $x = $readfh->READLINE) { print $writefh $x; } unless (close $writefh) { my $fail = "Could not apply patch '$patch'"; $CPAN::Frontend->mywarn("$fail; cannot continue\n"); $self->{unwrapped} = CPAN::Distrostatus->new("NO -- $fail"); delete $self->{build_dir}; return; } } $self->{patched}++; } return 1; } } # may return # - "applypatch" # - ("-p0"|"-p1", $files) sub _patch_p_parameter { my($self,$fh) = @_; my $cnt_files = 0; my $cnt_p0files = 0; my @files; local($_); while ($_ = $fh->READLINE) { if ( $CPAN::Config->{applypatch} && /\#\#\#\# ApplyPatch data follows \#\#\#\#/ ) { return "applypatch" } next unless /^[\*\+]{3}\s(\S+)/; my $file = $1; push @files, $file; $cnt_files++; $cnt_p0files++ if -f $file; CPAN->debug("file[$file]cnt_files[$cnt_files]cnt_p0files[$cnt_p0files]") if $CPAN::DEBUG; } return "-p1" unless $cnt_files; my $opt_p = $cnt_files==$cnt_p0files ? "-p0" : "-p1"; return ($opt_p, \@files); } #-> sub CPAN::Distribution::_edge_cases # with "configure" or "Makefile" or single file scripts sub _edge_cases { my($self,$mpl,$local_file) = @_; $self->debug(sprintf("makefilepl[%s]anycwd[%s]", $mpl, CPAN::anycwd(), )) if $CPAN::DEBUG; my $build_dir = $self->{build_dir}; my($configure) = File::Spec->catfile($build_dir,"Configure"); if (-f $configure) { # do we have anything to do? $self->{configure} = $configure; } elsif (-f File::Spec->catfile($build_dir,"Makefile")) { $CPAN::Frontend->mywarn(qq{ Package comes with a Makefile and without a Makefile.PL. We\'ll try to build it with that Makefile then. }); $self->{writemakefile} = CPAN::Distrostatus->new("YES"); $CPAN::Frontend->mysleep(2); } else { my $cf = $self->called_for || "unknown"; if ($cf =~ m|/|) { $cf =~ s|.*/||; $cf =~ s|\W.*||; } $cf =~ s|[/\\:]||g; # risk of filesystem damage $cf = "unknown" unless length($cf); if (my $crud = $self->_contains_crud($build_dir)) { my $why = qq{Package contains $crud; not recognized as a perl package, giving up}; $CPAN::Frontend->mywarn("$why\n"); $self->{writemakefile} = CPAN::Distrostatus->new(qq{NO -- $why}); return; } $CPAN::Frontend->mywarn(qq{Package seems to come without Makefile.PL. (The test -f "$mpl" returned false.) Writing one on our own (setting NAME to $cf)\a\n}); $self->{had_no_makefile_pl}++; $CPAN::Frontend->mysleep(3); # Writing our own Makefile.PL my $exefile_stanza = ""; if ($self->{archived} eq "maybe_pl") { $exefile_stanza = $self->_exefile_stanza($build_dir,$local_file); } my $fh = FileHandle->new; $fh->open(">$mpl") or Carp::croak("Could not open >$mpl: $!"); $fh->print( qq{# This Makefile.PL has been autogenerated by the module CPAN.pm # because there was no Makefile.PL supplied. # Autogenerated on: }.scalar localtime().qq{ use ExtUtils::MakeMaker; WriteMakefile( NAME => q[$cf],$exefile_stanza ); }); $fh->close; } } #-> CPAN;:Distribution::_contains_crud sub _contains_crud { my($self,$dir) = @_; my(@dirs, $dh, @files); opendir $dh, $dir or return; my $dirent; for $dirent (readdir $dh) { next if $dirent =~ /^\.\.?$/; my $path = File::Spec->catdir($dir,$dirent); if (-d $path) { push @dirs, $dirent; } elsif (-f $path) { push @files, $dirent; } } if (@dirs && @files) { return "both files[@files] and directories[@dirs]"; } elsif (@files > 2) { return "several files[@files] but no Makefile.PL or Build.PL"; } return; } #-> CPAN;:Distribution::_exefile_stanza sub _exefile_stanza { my($self,$build_dir,$local_file) = @_; my $fh = FileHandle->new; my $script_file = File::Spec->catfile($build_dir,$local_file); $fh->open($script_file) or Carp::croak("Could not open script '$script_file': $!"); local $/ = "\n"; # parse name and prereq my($state) = "poddir"; my($name, $prereq) = ("", ""); while (<$fh>) { if ($state eq "poddir" && /^=head\d\s+(\S+)/) { if ($1 eq 'NAME') { $state = "name"; } elsif ($1 eq 'PREREQUISITES') { $state = "prereq"; } } elsif ($state =~ m{^(name|prereq)$}) { if (/^=/) { $state = "poddir"; } elsif (/^\s*$/) { # nop } elsif ($state eq "name") { if ($name eq "") { ($name) = /^(\S+)/; $state = "poddir"; } } elsif ($state eq "prereq") { $prereq .= $_; } } elsif (/^=cut\b/) { last; } } $fh->close; for ($name) { s{.*<}{}; # strip X<...> s{>.*}{}; } chomp $prereq; $prereq = join " ", split /\s+/, $prereq; my($PREREQ_PM) = join("\n", map { s{.*<}{}; # strip X<...> s{>.*}{}; if (/[\s\'\"]/) { # prose? } else { s/[^\w:]$//; # period? " "x28 . "'$_' => 0,"; } } split /\s*,\s*/, $prereq); if ($name) { my $to_file = File::Spec->catfile($build_dir, $name); rename $script_file, $to_file or die "Can't rename $script_file to $to_file: $!"; } return " EXE_FILES => ['$name'], PREREQ_PM => { $PREREQ_PM }, "; } #-> CPAN::Distribution::_signature_business sub _signature_business { my($self) = @_; my $check_sigs = CPAN::HandleConfig->prefs_lookup($self, q{check_sigs}); if ($check_sigs) { if ($CPAN::META->has_inst("Module::Signature")) { if (-f "SIGNATURE") { $self->debug("Module::Signature is installed, verifying") if $CPAN::DEBUG; my $rv = Module::Signature::verify(); if ($rv != Module::Signature::SIGNATURE_OK() and $rv != Module::Signature::SIGNATURE_MISSING()) { $CPAN::Frontend->mywarn( qq{\nSignature invalid for }. qq{distribution file. }. qq{Please investigate.\n\n} ); my $wrap = sprintf(qq{I'd recommend removing %s. Some error occurred }. qq{while checking its signature, so it could }. qq{be invalid. Maybe you have configured }. qq{your 'urllist' with a bad URL. Please check this }. qq{array with 'o conf urllist' and retry. Or }. qq{examine the distribution in a subshell. Try look %s and run cpansign -v }, $self->{localfile}, $self->pretty_id, ); $self->{signature_verify} = CPAN::Distrostatus->new("NO"); $CPAN::Frontend->mywarn(Text::Wrap::wrap("","",$wrap)); $CPAN::Frontend->mysleep(5) if $CPAN::Frontend->can("mysleep"); } else { $self->{signature_verify} = CPAN::Distrostatus->new("YES"); $self->debug("Module::Signature has verified") if $CPAN::DEBUG; } } else { $CPAN::Frontend->mywarn(qq{Package came without SIGNATURE\n\n}); } } else { $self->debug("Module::Signature is NOT installed") if $CPAN::DEBUG; } } } #-> CPAN::Distribution::untar_me ; sub untar_me { my($self,$ct) = @_; $self->{archived} = "tar"; my $result = eval { $ct->untar() }; if ($result) { $self->{unwrapped} = CPAN::Distrostatus->new("YES"); } else { # unfortunately we have no $@ here, Tarzip is using mydie which dies with "\n" $self->{unwrapped} = CPAN::Distrostatus->new("NO -- untar failed"); } } # CPAN::Distribution::unzip_me ; sub unzip_me { my($self,$ct) = @_; $self->{archived} = "zip"; if (eval { $ct->unzip() }) { $self->{unwrapped} = CPAN::Distrostatus->new("YES"); } else { $self->{unwrapped} = CPAN::Distrostatus->new("NO -- unzip failed during unzip"); } return; } sub handle_singlefile { my($self,$local_file) = @_; if ( $local_file =~ /\.pm(\.(gz|Z))?(?!\n)\Z/ ) { $self->{archived} = "pm"; } elsif ( $local_file =~ /\.patch(\.(gz|bz2))?(?!\n)\Z/ ) { $self->{archived} = "patch"; } else { $self->{archived} = "maybe_pl"; } my $to = File::Basename::basename($local_file); if ($to =~ s/\.(gz|Z)(?!\n)\Z//) { if (eval{CPAN::Tarzip->new($local_file)->gunzip($to)}) { $self->{unwrapped} = CPAN::Distrostatus->new("YES"); } else { $self->{unwrapped} = CPAN::Distrostatus->new("NO -- uncompressing failed"); } } else { if (File::Copy::cp($local_file,".")) { $self->{unwrapped} = CPAN::Distrostatus->new("YES"); } else { $self->{unwrapped} = CPAN::Distrostatus->new("NO -- copying failed"); } } return $to; } #-> sub CPAN::Distribution::new ; sub new { my($class,%att) = @_; # $CPAN::META->{cachemgr} ||= CPAN::CacheMgr->new(); my $this = { %att }; return bless $this, $class; } #-> sub CPAN::Distribution::look ; sub look { my($self) = @_; if ($^O eq 'MacOS') { $self->Mac::BuildTools::look; return; } if ( $CPAN::Config->{'shell'} ) { $CPAN::Frontend->myprint(qq{ Trying to open a subshell in the build directory... }); } else { $CPAN::Frontend->myprint(qq{ Your configuration does not define a value for subshells. Please define it with "o conf shell <your shell>" }); return; } my $dist = $self->id; my $dir; unless ($dir = $self->dir) { $self->get; } unless ($dir ||= $self->dir) { $CPAN::Frontend->mywarn(qq{ Could not determine which directory to use for looking at $dist. }); return; } my $pwd = CPAN::anycwd(); $self->safe_chdir($dir); $CPAN::Frontend->myprint(qq{Working directory is $dir\n}); { local $ENV{CPAN_SHELL_LEVEL} = $ENV{CPAN_SHELL_LEVEL}||0; $ENV{CPAN_SHELL_LEVEL} += 1; my $shell = CPAN::HandleConfig->safe_quote($CPAN::Config->{'shell'}); local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; # local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # look $CPAN::META->set_perl5lib; local $ENV{MAKEFLAGS}; # protect us from outer make calls unless (system($shell) == 0) { my $code = $? >> 8; $CPAN::Frontend->mywarn("Subprocess shell exit code $code\n"); } } $self->safe_chdir($pwd); } # CPAN::Distribution::cvs_import ; sub cvs_import { my($self) = @_; $self->get; my $dir = $self->dir; my $package = $self->called_for; my $module = $CPAN::META->instance('CPAN::Module', $package); my $version = $module->cpan_version; my $userid = $self->cpan_userid; my $cvs_dir = (split /\//, $dir)[-1]; $cvs_dir =~ s/-\d+[^-]+(?!\n)\Z//; my $cvs_root = $CPAN::Config->{cvsroot} || $ENV{CVSROOT}; my $cvs_site_perl = $CPAN::Config->{cvs_site_perl} || $ENV{CVS_SITE_PERL}; if ($cvs_site_perl) { $cvs_dir = "$cvs_site_perl/$cvs_dir"; } my $cvs_log = qq{"imported $package $version sources"}; $version =~ s/\./_/g; # XXX cvs: undocumented and unclear how it was meant to work my @cmd = ('cvs', '-d', $cvs_root, 'import', '-m', $cvs_log, "$cvs_dir", $userid, "v$version"); my $pwd = CPAN::anycwd(); chdir($dir) or $CPAN::Frontend->mydie(qq{Could not chdir to "$dir": $!}); $CPAN::Frontend->myprint(qq{Working directory is $dir\n}); $CPAN::Frontend->myprint(qq{@cmd\n}); system(@cmd) == 0 or # XXX cvs $CPAN::Frontend->mydie("cvs import failed"); chdir($pwd) or $CPAN::Frontend->mydie(qq{Could not chdir to "$pwd": $!}); } #-> sub CPAN::Distribution::readme ; sub readme { my($self) = @_; my($dist) = $self->id; my($sans,$suffix) = $dist =~ /(.+)\.(tgz|tar[\._-]gz|tar\.Z|zip)$/; $self->debug("sans[$sans] suffix[$suffix]\n") if $CPAN::DEBUG; my($local_file); my($local_wanted) = File::Spec->catfile( $CPAN::Config->{keep_source_where}, "authors", "id", split(/\//,"$sans.readme"), ); my $readme = "authors/id/$sans.readme"; $self->debug("Doing localize for '$readme'") if $CPAN::DEBUG; $local_file = CPAN::FTP->localize($readme, $local_wanted) or $CPAN::Frontend->mydie(qq{No $sans.readme found}); if ($^O eq 'MacOS') { Mac::BuildTools::launch_file($local_file); return; } my $fh_pager = FileHandle->new; local($SIG{PIPE}) = "IGNORE"; my $pager = $CPAN::Config->{'pager'} || "cat"; $fh_pager->open("|$pager") or die "Could not open pager $pager\: $!"; my $fh_readme = FileHandle->new; $fh_readme->open($local_file) or $CPAN::Frontend->mydie(qq{Could not open "$local_file": $!}); $CPAN::Frontend->myprint(qq{ Displaying file $local_file with pager "$pager" }); $fh_pager->print(<$fh_readme>); $fh_pager->close; } #-> sub CPAN::Distribution::verifyCHECKSUM ; sub verifyCHECKSUM { my($self) = @_; EXCUSE: { my @e; $self->{CHECKSUM_STATUS} ||= ""; $self->{CHECKSUM_STATUS} eq "OK" and push @e, "Checksum was ok"; $CPAN::Frontend->myprint(join "", map {" $_\n"} @e) and return if @e; } my($lc_want,$lc_file,@local,$basename); @local = split(/\//,$self->id); pop @local; push @local, "CHECKSUMS"; $lc_want = File::Spec->catfile($CPAN::Config->{keep_source_where}, "authors", "id", @local); local($") = "/"; if (my $size = -s $lc_want) { $self->debug("lc_want[$lc_want]size[$size]") if $CPAN::DEBUG; my @stat = stat $lc_want; my $epoch_starting_support_of_cpan_path = 1637471530; if ($stat[9] >= $epoch_starting_support_of_cpan_path) { if ($self->CHECKSUM_check_file($lc_want, 1)) { return $self->{CHECKSUM_STATUS} = "OK"; } } else { unlink $lc_want; } } $lc_file = CPAN::FTP->localize("authors/id/@local", $lc_want,1); unless ($lc_file) { $CPAN::Frontend->myprint("Trying $lc_want.gz\n"); $local[-1] .= ".gz"; $lc_file = CPAN::FTP->localize("authors/id/@local", "$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; } } if ($self->CHECKSUM_check_file($lc_file)) { return $self->{CHECKSUM_STATUS} = "OK"; } } #-> sub CPAN::Distribution::SIG_check_file ; sub SIG_check_file { my($self,$chk_file) = @_; my $rv = eval { Module::Signature::_verify($chk_file) }; if ($rv eq Module::Signature::CANNOT_VERIFY()) { $CPAN::Frontend->myprint(qq{\nSignature for }. qq{file $chk_file could not be verified for an unknown reason. }. $self->as_string. qq{Module::Signature verification returned value $rv\n\n} ); my $wrap = qq{The manual says for this case: Cannot verify the OpenPGP signature, maybe due to the lack of a network connection to the key server, or if neither gnupg nor Crypt::OpenPGP exists on the system. You probably want to analyse the situation and if you cannot fix it you will have to decide whether you want to stop this session or you want to turn off signature verification. The latter would be done with the command 'o conf init check_sigs'}; $CPAN::Frontend->mydie(Text::Wrap::wrap("","",$wrap)); } if ($rv == Module::Signature::SIGNATURE_OK()) { $CPAN::Frontend->myprint("Signature for $chk_file ok\n"); return $self->{SIG_STATUS} = "OK"; } else { $CPAN::Frontend->mywarn(qq{\nSignature invalid for }. qq{file $chk_file. }. qq{Please investigate.\n\n}. $self->as_string. qq{Module::Signature verification returned value $rv\n\n} ); my $wrap = qq{I\'d recommend removing $chk_file. Its signature is invalid. Maybe you have configured your 'urllist' with a bad URL. Please check this array with 'o conf urllist', and retry.}; $CPAN::Frontend->mydie(Text::Wrap::wrap("","",$wrap)); } } #-> sub CPAN::Distribution::CHECKSUM_check_file ; # sloppy is 1 when we have an old checksums file that maybe is good # enough sub CHECKSUM_check_file { my($self,$chk_file,$sloppy) = @_; my($cksum,$file,$basename); $sloppy ||= 0; $self->debug("chk_file[$chk_file]sloppy[$sloppy]") if $CPAN::DEBUG; my $check_sigs = CPAN::HandleConfig->prefs_lookup($self, q{check_sigs}); if ($check_sigs) { if ($CPAN::META->has_inst("Module::Signature")) { $self->debug("Module::Signature is installed, verifying") if $CPAN::DEBUG; $self->SIG_check_file($chk_file); } else { $self->debug("Module::Signature is NOT installed") if $CPAN::DEBUG; } } $file = $self->{localfile}; $basename = File::Basename::basename($file); my($signed_data); my $fh = FileHandle->new; if ($check_sigs) { my $tempdir; if ($CPAN::META->has_usable("File::Temp")) { $tempdir = File::Temp::tempdir("CHECKSUMS-XXXX", CLEANUP => 1, DIR => "/tmp" ); } else { $tempdir = File::Spec->catdir(File::Spec->tmpdir, "CHECKSUMS-$$"); File::Path::mkpath($tempdir); } my $tempfile = File::Spec->catfile($tempdir, "CHECKSUMS.$$"); unlink $tempfile; # ignore missing file my $devnull = File::Spec->devnull; my $gpg = $CPAN::Config->{gpg} or $CPAN::Frontend->mydie("Your configuration suggests that you do not have 'gpg' installed. This is needed to verify checksums with the config variable 'check_sigs' on. Please configure it with 'o conf init gpg'"); my $system = qq{"$gpg" --verify --batch --no-tty --output "$tempfile" "$chk_file" 2> "$devnull"}; 0 == system $system or $CPAN::Frontend->mydie("gpg run was failing, cannot continue: $system"); open $fh, $tempfile or $CPAN::Frontend->mydie("Could not open $tempfile: $!"); local $/; $signed_data = <$fh>; close $fh; File::Path::rmtree($tempdir); } else { my $fh = FileHandle->new; if (open $fh, $chk_file) { local($/); $signed_data = <$fh>; } else { $CPAN::Frontend->mydie("Could not open $chk_file for reading"); } close $fh; } $signed_data =~ s/\015?\012/\n/g; my($compmt) = Safe->new(); $cksum = $compmt->reval($signed_data); if ($@) { rename $chk_file, "$chk_file.bad"; Carp::confess($@) if $@; } if (! ref $cksum or ref $cksum ne "HASH") { $CPAN::Frontend->mywarn(qq{ Warning: checksum file '$chk_file' broken. When trying to read that file I expected to get a hash reference for further processing, but got garbage instead. }); my $answer = CPAN::Shell::colorable_makemaker_prompt("Proceed nonetheless?", "no"); $answer =~ /^\s*y/i or $CPAN::Frontend->mydie("Aborted.\n"); $self->{CHECKSUM_STATUS} = "NIL -- CHECKSUMS file broken"; return; } elsif (exists $cksum->{$basename} && ! exists $cksum->{$basename}{cpan_path}) { $CPAN::Frontend->mywarn(qq{ Warning: checksum file '$chk_file' not conforming. The cksum does not contain the key 'cpan_path' for '$basename'. }); my $answer = CPAN::Shell::colorable_makemaker_prompt("Proceed nonetheless?", "no"); $answer =~ /^\s*y/i or $CPAN::Frontend->mydie("Aborted.\n"); $self->{CHECKSUM_STATUS} = "NIL -- CHECKSUMS file without cpan_path"; return; } elsif (exists $cksum->{$basename} && substr($self->{ID},0,length($cksum->{$basename}{cpan_path})) ne $cksum->{$basename}{cpan_path}) { $CPAN::Frontend->mywarn(qq{ Warning: checksum file not matching path '$self->{ID}'. The cksum contain the key 'cpan_path=$cksum->{$basename}{cpan_path}' which does not match the ID of the distribution '$self->{ID}'. Something's suspicious might be going on here. Please investigate. }); my $answer = CPAN::Shell::colorable_makemaker_prompt("Proceed nonetheless?", "no"); $answer =~ /^\s*y/i or $CPAN::Frontend->mydie("Aborted.\n"); $self->{CHECKSUM_STATUS} = "NIL -- CHECKSUMS non-matching cpan_path vs. ID"; return; } elsif (exists $cksum->{$basename}{sha256}) { $self->debug("Found checksum for $basename:" . "$cksum->{$basename}{sha256}\n") if $CPAN::DEBUG; open($fh, $file); binmode $fh; my $eq = $self->eq_CHECKSUM($fh,$cksum->{$basename}{sha256}); $fh->close; $fh = CPAN::Tarzip->TIEHANDLE($file); unless ($eq) { my $dg = Digest::SHA->new(256); my($data,$ref); $ref = \$data; while ($fh->READ($ref, 4096) > 0) { $dg->add($data); } my $hexdigest = $dg->hexdigest; $eq += $hexdigest eq $cksum->{$basename}{'sha256-ungz'}; } if ($eq) { $CPAN::Frontend->myprint("Checksum for $file ok\n"); return $self->{CHECKSUM_STATUS} = "OK"; } else { $CPAN::Frontend->myprint(qq{\nChecksum mismatch for }. qq{distribution file. }. qq{Please investigate.\n\n}. $self->as_string, $CPAN::META->instance( 'CPAN::Author', $self->cpan_userid )->as_string); my $wrap = qq{I\'d recommend removing $file. Its checksum is incorrect. Maybe you have configured your 'urllist' with a bad URL. Please check this array with 'o conf urllist', and retry.}; $CPAN::Frontend->mydie(Text::Wrap::wrap("","",$wrap)); # former versions just returned here but this seems a # serious threat that deserves a die # $CPAN::Frontend->myprint("\n\n"); # sleep 3; # return; } # close $fh if fileno($fh); } else { return if $sloppy; unless ($self->{CHECKSUM_STATUS}) { $CPAN::Frontend->mywarn(qq{ Warning: No checksum for $basename in $chk_file. The cause for this may be that the file is very new and the checksum has not yet been calculated, but it may also be that something is going awry right now. }); my $answer = CPAN::Shell::colorable_makemaker_prompt("Proceed?", "yes"); $answer =~ /^\s*y/i or $CPAN::Frontend->mydie("Aborted.\n"); } $self->{CHECKSUM_STATUS} = "NIL -- distro not in CHECKSUMS file"; return; } } #-> sub CPAN::Distribution::eq_CHECKSUM ; sub eq_CHECKSUM { my($self,$fh,$expect) = @_; if ($CPAN::META->has_inst("Digest::SHA")) { my $dg = Digest::SHA->new(256); my($data); while (read($fh, $data, 4096)) { $dg->add($data); } my $hexdigest = $dg->hexdigest; # warn "fh[$fh] hex[$hexdigest] aexp[$expectMD5]"; return $hexdigest eq $expect; } return 1; } #-> sub CPAN::Distribution::force ; # Both CPAN::Modules and CPAN::Distributions know if "force" is in # effect by autoinspection, not by inspecting a global variable. One # of the reason why this was chosen to work that way was the treatment # of dependencies. They should not automatically inherit the force # status. But this has the downside that ^C and die() will return to # the prompt but will not be able to reset the force_update # attributes. We try to correct for it currently in the read_metadata # routine, and immediately before we check for a Signal. I hope this # works out in one of v1.57_53ff # "Force get forgets previous error conditions" #-> sub CPAN::Distribution::fforce ; sub fforce { my($self, $method) = @_; $self->force($method,1); } #-> sub CPAN::Distribution::force ; sub force { my($self, $method,$fforce) = @_; my %phase_map = ( get => [ "unwrapped", "build_dir", "archived", "localfile", "CHECKSUM_STATUS", "signature_verify", "prefs", "prefs_file", "prefs_file_doc", "cleanup_after_install_done", ], make => [ "writemakefile", "make", "modulebuild", "prereq_pm", "cleanup_after_install_done", ], test => [ "badtestcnt", "make_test", "cleanup_after_install_done", ], install => [ "install", "cleanup_after_install_done", ], unknown => [ "reqtype", "yaml_content", "cleanup_after_install_done", ], ); my $methodmatch = 0; my $ldebug = 0; PHASE: for my $phase (qw(unknown get make test install)) { # order matters $methodmatch = 1 if $fforce || ($method && $phase eq $method); next unless $methodmatch; ATTRIBUTE: for my $att (@{$phase_map{$phase}}) { if ($phase eq "get") { if (substr($self->id,-1,1) eq "." && $att =~ /(unwrapped|build_dir|archived)/ ) { # cannot be undone for local distros next ATTRIBUTE; } if ($att eq "build_dir" && $self->{build_dir} && $CPAN::META->{is_tested} ) { delete $CPAN::META->{is_tested}{$self->{build_dir}}; } } elsif ($phase eq "test") { if ($att eq "make_test" && $self->{make_test} && $self->{make_test}{COMMANDID} && $self->{make_test}{COMMANDID} == $CPAN::CurrentCommandId ) { # endless loop too likely next ATTRIBUTE; } } delete $self->{$att}; if ($ldebug || $CPAN::DEBUG) { # local $CPAN::DEBUG = 16; # Distribution CPAN->debug(sprintf "id[%s]phase[%s]att[%s]", $self->id, $phase, $att); } } } if ($method && $method =~ /make|test|install/) { $self->{force_update} = 1; # name should probably have been force_install } } #-> sub CPAN::Distribution::notest ; sub notest { my($self, $method) = @_; # $CPAN::Frontend->mywarn("XDEBUG: set notest for $self $method"); $self->{"notest"}++; # name should probably have been force_install } #-> sub CPAN::Distribution::unnotest ; sub unnotest { my($self) = @_; # warn "XDEBUG: deleting notest"; delete $self->{notest}; } #-> sub CPAN::Distribution::unforce ; sub unforce { my($self) = @_; delete $self->{force_update}; } #-> sub CPAN::Distribution::isa_perl ; sub isa_perl { my($self) = @_; my $file = File::Basename::basename($self->id); if ($file =~ m{ ^ perl ( -(5\.\d+\.\d+) | (5)[._-](00[0-5](?:_[0-4][0-9])?) ) \.tar[._-](?:gz|bz2) (?!\n)\Z }xs) { my $perl_version; if ($2) { $perl_version = $2; } else { $perl_version = "$3.$4"; } return $perl_version; } elsif ($self->cpan_comment && $self->cpan_comment =~ /isa_perl\(.+?\)/) { return $1; } } #-> sub CPAN::Distribution::perl ; sub perl { my ($self) = @_; if (! $self) { use Carp qw(carp); carp __PACKAGE__ . "::perl was called without parameters."; } return CPAN::HandleConfig->safe_quote($CPAN::Perl); } #-> sub CPAN::Distribution::shortcut_prepare ; # return values: undef means don't shortcut; 0 means shortcut as fail; # and 1 means shortcut as success sub shortcut_prepare { my ($self) = @_; $self->debug("checking archive type[$self->{ID}]") if $CPAN::DEBUG; if (!$self->{archived} || $self->{archived} eq "NO") { return $self->goodbye("Is neither a tar nor a zip archive."); } $self->debug("checking unwrapping[$self->{ID}]") if $CPAN::DEBUG; if (!$self->{unwrapped} || ( UNIVERSAL::can($self->{unwrapped},"failed") ? $self->{unwrapped}->failed : $self->{unwrapped} =~ /^NO/ )) { return $self->goodbye("Had problems unarchiving. Please build manually"); } $self->debug("checking signature[$self->{ID}]") if $CPAN::DEBUG; if ( ! $self->{force_update} && exists $self->{signature_verify} && ( UNIVERSAL::can($self->{signature_verify},"failed") ? $self->{signature_verify}->failed : $self->{signature_verify} =~ /^NO/ ) ) { return $self->goodbye("Did not pass the signature test."); } $self->debug("checking writemakefile[$self->{ID}]") if $CPAN::DEBUG; if ($self->{writemakefile}) { if ( UNIVERSAL::can($self->{writemakefile},"failed") ? $self->{writemakefile}->failed : $self->{writemakefile} =~ /^NO/ ) { # XXX maybe a retry would be in order? my $err = UNIVERSAL::can($self->{writemakefile},"text") ? $self->{writemakefile}->text : $self->{writemakefile}; $err =~ s/^NO\s*(--\s+)?//; $err ||= "Had some problem writing Makefile"; $err .= ", not re-running"; return $self->goodbye($err); } else { return $self->success("Has already been prepared"); } } $self->debug("checking configure_requires_later[$self->{ID}]") if $CPAN::DEBUG; if( my $later = $self->{configure_requires_later} ) { # see also undelay return $self->goodbye($later); } return undef; # no shortcut } sub prepare { my ($self) = @_; $self->get or return; if ( defined( my $sc = $self->shortcut_prepare) ) { return $sc; } local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} && defined $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # prepare $CPAN::META->set_perl5lib; local $ENV{MAKEFLAGS}; # protect us from outer make calls if ($CPAN::Signal) { delete $self->{force_update}; return; } my $builddir = $self->dir or $CPAN::Frontend->mydie("PANIC: Cannot determine build directory\n"); unless (chdir $builddir) { $CPAN::Frontend->mywarn("Couldn't chdir to '$builddir': $!"); return; } if ($CPAN::Signal) { delete $self->{force_update}; return; } $self->debug("Changed directory to $builddir") if $CPAN::DEBUG; local $ENV{PERL_AUTOINSTALL} = $ENV{PERL_AUTOINSTALL} || ''; local $ENV{PERL_EXTUTILS_AUTOINSTALL} = $ENV{PERL_EXTUTILS_AUTOINSTALL} || ''; $self->choose_MM_or_MB or return; my $configurator = $self->{configure} ? "Configure" : $self->{modulebuild} ? "Build.PL" : "Makefile.PL"; $CPAN::Frontend->myprint("Configuring ".$self->id." with $configurator\n"); if ($CPAN::Config->{prerequisites_policy} eq "follow") { $ENV{PERL_AUTOINSTALL} ||= "--defaultdeps"; $ENV{PERL_EXTUTILS_AUTOINSTALL} ||= "--defaultdeps"; } my $system; my $pl_commandline; if ($self->prefs->{pl}) { $pl_commandline = $self->prefs->{pl}{commandline}; } local $ENV{PERL} = defined $ENV{PERL}? $ENV{PERL} : $^X; local $ENV{PERL5_CPAN_IS_EXECUTING} = $ENV{PERL5_CPAN_IS_EXECUTING} || ''; local $ENV{PERL_MM_USE_DEFAULT} = 1 if $CPAN::Config->{use_prompt_default}; local $ENV{NONINTERACTIVE_TESTING} = 1 if $CPAN::Config->{use_prompt_default}; if ($pl_commandline) { $system = $pl_commandline; $ENV{PERL} = $^X; } elsif ($self->{'configure'}) { $system = $self->{'configure'}; } elsif ($self->{modulebuild}) { my($perl) = $self->perl or die "Couldn\'t find executable perl\n"; my $mbuildpl_arg = $self->_make_phase_arg("pl"); $system = sprintf("%s Build.PL%s", $perl, $mbuildpl_arg ? " $mbuildpl_arg" : "", ); } else { my($perl) = $self->perl or die "Couldn\'t find executable perl\n"; my $switch = ""; # This needs a handler that can be turned on or off: # $switch = "-MExtUtils::MakeMaker ". # "-Mops=:default,:filesys_read,:filesys_open,require,chdir" # if $] > 5.00310; my $makepl_arg = $self->_make_phase_arg("pl"); $ENV{PERL5_CPAN_IS_EXECUTING} = File::Spec->catfile($self->{build_dir}, "Makefile.PL"); $system = sprintf("%s%s Makefile.PL%s", $perl, $switch ? " $switch" : "", $makepl_arg ? " $makepl_arg" : "", ); } my $pl_env; if ($self->prefs->{pl}) { $pl_env = $self->prefs->{pl}{env}; } local @ENV{keys %$pl_env} = values %$pl_env if $pl_env; if (exists $self->{writemakefile}) { } else { local($SIG{ALRM}) = sub { die "inactivity_timeout reached\n" }; my($ret,$pid,$output); $@ = ""; my $go_via_alarm; if ($CPAN::Config->{inactivity_timeout}) { require Config; if ($Config::Config{d_alarm} && $Config::Config{d_alarm} eq "define" ) { $go_via_alarm++ } else { $CPAN::Frontend->mywarn("Warning: you have configured the config ". "variable 'inactivity_timeout' to ". "'$CPAN::Config->{inactivity_timeout}'. But ". "on this machine the system call 'alarm' ". "isn't available. This means that we cannot ". "provide the feature of intercepting long ". "waiting code and will turn this feature off.\n" ); $CPAN::Config->{inactivity_timeout} = 0; } } if ($go_via_alarm) { if ( $self->_should_report('pl') ) { ($output, $ret) = CPAN::Reporter::record_command( $system, $CPAN::Config->{inactivity_timeout}, ); CPAN::Reporter::grade_PL( $self, $system, $output, $ret ); } else { eval { alarm $CPAN::Config->{inactivity_timeout}; local $SIG{CHLD}; # = sub { wait }; if (defined($pid = fork)) { if ($pid) { #parent # wait; waitpid $pid, 0; } else { #child # note, this exec isn't necessary if # inactivity_timeout is 0. On the Mac I'd # suggest, we set it always to 0. exec $system; } } else { $CPAN::Frontend->myprint("Cannot fork: $!"); return; } }; alarm 0; if ($@) { kill 9, $pid; waitpid $pid, 0; my $err = "$@"; $CPAN::Frontend->myprint($err); $self->{writemakefile} = CPAN::Distrostatus->new("NO $err"); $@ = ""; $self->store_persistent_state; return $self->goodbye("$system -- TIMED OUT"); } } } else { if (my $expect_model = $self->_prefs_with_expect("pl")) { # XXX probably want to check _should_report here and warn # about not being able to use CPAN::Reporter with expect $ret = $self->_run_via_expect($system,'writemakefile',$expect_model); if (! defined $ret && $self->{writemakefile} && $self->{writemakefile}->failed) { # timeout return; } } elsif ( $self->_should_report('pl') ) { ($output, $ret) = eval { CPAN::Reporter::record_command($system) }; if (! defined $output or $@) { my $err = $@ || "Unknown error"; $CPAN::Frontend->mywarn("Error while running PL phase: $err\n"); $self->{writemakefile} = CPAN::Distrostatus ->new("NO '$system' returned status $ret and no output"); return $self->goodbye("$system -- NOT OK"); } CPAN::Reporter::grade_PL( $self, $system, $output, $ret ); } else { $ret = system($system); } if ($ret != 0) { $self->{writemakefile} = CPAN::Distrostatus ->new("NO '$system' returned status $ret"); $CPAN::Frontend->mywarn("Warning: No success on command[$system]\n"); $self->store_persistent_state; return $self->goodbye("$system -- NOT OK"); } } if (-f "Makefile" || -f "Build" || ($^O eq 'VMS' && (-f 'descrip.mms' || -f 'Build.com'))) { $self->{writemakefile} = CPAN::Distrostatus->new("YES"); delete $self->{make_clean}; # if cleaned before, enable next $self->store_persistent_state; return $self->success("$system -- OK"); } else { my $makefile = $self->{modulebuild} ? "Build" : "Makefile"; my $why = "No '$makefile' created"; $CPAN::Frontend->mywarn($why); $self->{writemakefile} = CPAN::Distrostatus ->new(qq{NO -- $why\n}); $self->store_persistent_state; return $self->goodbye("$system -- NOT OK"); } } $self->store_persistent_state; return 1; # success } #-> sub CPAN::Distribution::shortcut_make ; # return values: undef means don't shortcut; 0 means shortcut as fail; # and 1 means shortcut as success sub shortcut_make { my ($self) = @_; $self->debug("checking make/build results[$self->{ID}]") if $CPAN::DEBUG; if (defined $self->{make}) { if (UNIVERSAL::can($self->{make},"failed") ? $self->{make}->failed : $self->{make} =~ /^NO/ ) { if ($self->{force_update}) { # Trying an already failed 'make' (unless somebody else blocks) return undef; # no shortcut } else { # introduced for turning recursion detection into a distrostatus my $error = length $self->{make}>3 ? substr($self->{make},3) : "Unknown error"; $self->store_persistent_state; return $self->goodbye("Could not make: $error\n"); } } else { return $self->success("Has already been made") } } return undef; # no shortcut } #-> sub CPAN::Distribution::make ; sub make { my($self) = @_; $self->pre_make(); if (exists $self->{cleanup_after_install_done}) { $self->post_make(); return $self->get; } $self->debug("checking goto id[$self->{ID}]") if $CPAN::DEBUG; if (my $goto = $self->prefs->{goto}) { $self->post_make(); return $self->goto($goto); } # Emergency brake if they said install Pippi and get newest perl # XXX Would this make more sense in shortcut_prepare, since # that doesn't make sense on a perl dist either? Broader # question: what is the purpose of suggesting force install # on a perl distribution? That seems unlikely to result in # such a dependency being satisfied, even if the perl is # successfully installed. This situation is tantamount to # a prereq on a version of perl greater than the current one # so I think we should just abort. -- xdg, 2012-04-06 if ($self->isa_perl) { if ( $self->called_for ne $self->id && ! $self->{force_update} ) { # if we die here, we break bundles $CPAN::Frontend ->mywarn(sprintf( qq{The most recent version "%s" of the module "%s" is part of the perl-%s distribution. To install that, you need to run force install %s --or-- install %s }, $CPAN::META->instance( 'CPAN::Module', $self->called_for )->cpan_version, $self->called_for, $self->isa_perl, $self->called_for, $self->pretty_id, )); $self->{make} = CPAN::Distrostatus->new("NO isa perl"); $CPAN::Frontend->mysleep(1); $self->post_make(); return; } } unless ($self->prepare){ $self->post_make(); return; } if ( defined( my $sc = $self->shortcut_make) ) { $self->post_make(); return $sc; } if ($CPAN::Signal) { delete $self->{force_update}; $self->post_make(); return; } my $builddir = $self->dir or $CPAN::Frontend->mydie("PANIC: Cannot determine build directory\n"); unless (chdir $builddir) { $CPAN::Frontend->mywarn("Couldn't chdir to '$builddir': $!"); $self->post_make(); return; } my $make = $self->{modulebuild} ? "Build" : "make"; $CPAN::Frontend->myprint(sprintf "Running %s for %s\n", $make, $self->id); local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} && defined $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # make $CPAN::META->set_perl5lib; local $ENV{MAKEFLAGS}; # protect us from outer make calls if ($CPAN::Signal) { delete $self->{force_update}; $self->post_make(); return; } if ($^O eq 'MacOS') { Mac::BuildTools::make($self); $self->post_make(); return; } my %env; while (my($k,$v) = each %ENV) { next if defined $v; $env{$k} = ''; } local @ENV{keys %env} = values %env; my $satisfied = eval { $self->satisfy_requires }; if ($@) { return $self->goodbye($@); } unless ($satisfied){ $self->post_make(); return; } if ($CPAN::Signal) { delete $self->{force_update}; $self->post_make(); return; } # need to chdir again, because $self->satisfy_requires might change the directory unless (chdir $builddir) { $CPAN::Frontend->mywarn("Couldn't chdir to '$builddir': $!"); $self->post_make(); return; } my $system; my $make_commandline; if ($self->prefs->{make}) { $make_commandline = $self->prefs->{make}{commandline}; } local $ENV{PERL} = defined $ENV{PERL}? $ENV{PERL} : $^X; local $ENV{PERL_MM_USE_DEFAULT} = 1 if $CPAN::Config->{use_prompt_default}; local $ENV{NONINTERACTIVE_TESTING} = 1 if $CPAN::Config->{use_prompt_default}; if ($make_commandline) { $system = $make_commandline; $ENV{PERL} = CPAN::find_perl(); } else { if ($self->{modulebuild}) { unless (-f "Build" || ($^O eq 'VMS' && -f 'Build.com')) { my $cwd = CPAN::anycwd(); $CPAN::Frontend->mywarn("Alert: no Build file available for 'make $self->{id}'". " in cwd[$cwd]. Danger, Will Robinson!\n"); $CPAN::Frontend->mysleep(5); } $system = join " ", $self->_build_command(), $CPAN::Config->{mbuild_arg}; } else { $system = join " ", $self->_make_command(), $CPAN::Config->{make_arg}; } $system =~ s/\s+$//; my $make_arg = $self->_make_phase_arg("make"); $system = sprintf("%s%s", $system, $make_arg ? " $make_arg" : "", ); } my $make_env; if ($self->prefs->{make}) { $make_env = $self->prefs->{make}{env}; } local @ENV{keys %$make_env} = values %$make_env if $make_env; my $expect_model = $self->_prefs_with_expect("make"); my $want_expect = 0; if ( $expect_model && @{$expect_model->{talk}} ) { my $can_expect = $CPAN::META->has_inst("Expect"); if ($can_expect) { $want_expect = 1; } else { $CPAN::Frontend->mywarn("Expect not installed, falling back to ". "system()\n"); } } my ($system_ok, $system_err); if ($want_expect) { # XXX probably want to check _should_report here and # warn about not being able to use CPAN::Reporter with expect $system_ok = $self->_run_via_expect($system,'make',$expect_model) == 0; } elsif ( $self->_should_report('make') ) { my ($output, $ret) = CPAN::Reporter::record_command($system); CPAN::Reporter::grade_make( $self, $system, $output, $ret ); $system_ok = ! $ret; } else { my $rc = system($system); $system_ok = $rc == 0; $system_err = $! if $rc == -1; } $self->introduce_myself; if ( $system_ok ) { $CPAN::Frontend->myprint(" $system -- OK\n"); $self->{make} = CPAN::Distrostatus->new("YES"); } else { $self->{writemakefile} ||= CPAN::Distrostatus->new("YES"); $self->{make} = CPAN::Distrostatus->new("NO"); $CPAN::Frontend->mywarn(" $system -- NOT OK\n"); $CPAN::Frontend->mywarn(" $system_err\n") if defined $system_err; } $self->store_persistent_state; $self->post_make(); return !! $system_ok; } # CPAN::Distribution::goodbye ; sub goodbye { my($self,$goodbye) = @_; my $id = $self->pretty_id; $CPAN::Frontend->mywarn(" $id\n $goodbye\n"); return 0; # must be explicit false, not undef } sub success { my($self,$why) = @_; my $id = $self->pretty_id; $CPAN::Frontend->myprint(" $id\n $why\n"); return 1; } # CPAN::Distribution::_run_via_expect ; sub _run_via_expect { my($self,$system,$phase,$expect_model) = @_; CPAN->debug("system[$system]expect_model[$expect_model]") if $CPAN::DEBUG; if ($CPAN::META->has_inst("Expect")) { my $expo = Expect->new; # expo Expect object; $expo->spawn($system); $expect_model->{mode} ||= "deterministic"; if ($expect_model->{mode} eq "deterministic") { return $self->_run_via_expect_deterministic($expo,$phase,$expect_model); } elsif ($expect_model->{mode} eq "anyorder") { return $self->_run_via_expect_anyorder($expo,$phase,$expect_model); } else { die "Panic: Illegal expect mode: $expect_model->{mode}"; } } else { $CPAN::Frontend->mywarn("Expect not installed, falling back to system()\n"); return system($system); } } sub _run_via_expect_anyorder { my($self,$expo,$phase,$expect_model) = @_; my $timeout = $expect_model->{timeout} || 5; my $reuse = $expect_model->{reuse}; my @expectacopy = @{$expect_model->{talk}}; # we trash it! my $but = ""; my $timeout_start = time; EXPECT: while () { my($eof,$ran_into_timeout); # XXX not up to the full power of expect. one could certainly # wrap all of the talk pairs into a single expect call and on # success tweak it and step ahead to the next question. The # current implementation unnecessarily limits itself to a # single match. my @match = $expo->expect(1, [ eof => sub { $eof++; } ], [ timeout => sub { $ran_into_timeout++; } ], -re => eval"qr{.}", ); if ($match[2]) { $but .= $match[2]; } $but .= $expo->clear_accum; if ($eof) { $expo->soft_close; return $expo->exitstatus(); } elsif ($ran_into_timeout) { # warn "DEBUG: they are asking a question, but[$but]"; for (my $i = 0; $i <= $#expectacopy; $i+=2) { my($next,$send) = @expectacopy[$i,$i+1]; my $regex = eval "qr{$next}"; # warn "DEBUG: will compare with regex[$regex]."; if ($but =~ /$regex/) { # warn "DEBUG: will send send[$send]"; $expo->send($send); # never allow reusing an QA pair unless they told us splice @expectacopy, $i, 2 unless $reuse; $but =~ s/(?s:^.*?)$regex//; $timeout_start = time; next EXPECT; } } my $have_waited = time - $timeout_start; if ($have_waited < $timeout) { # warn "DEBUG: have_waited[$have_waited]timeout[$timeout]"; next EXPECT; } my $why = "could not answer a question during the dialog"; $CPAN::Frontend->mywarn("Failing: $why\n"); $self->{$phase} = CPAN::Distrostatus->new("NO $why"); return 0; } } } sub _run_via_expect_deterministic { my($self,$expo,$phase,$expect_model) = @_; my $ran_into_timeout; my $ran_into_eof; my $timeout = $expect_model->{timeout} || 15; # currently unsettable my $expecta = $expect_model->{talk}; EXPECT: for (my $i = 0; $i <= $#$expecta; $i+=2) { my($re,$send) = @$expecta[$i,$i+1]; CPAN->debug("timeout[$timeout]re[$re]") if $CPAN::DEBUG; my $regex = eval "qr{$re}"; $expo->expect($timeout, [ eof => sub { my $but = $expo->clear_accum; $CPAN::Frontend->mywarn("EOF (maybe harmless) expected[$regex]\nbut[$but]\n\n"); $ran_into_eof++; } ], [ timeout => sub { my $but = $expo->clear_accum; $CPAN::Frontend->mywarn("TIMEOUT expected[$regex]\nbut[$but]\n\n"); $ran_into_timeout++; } ], -re => $regex); if ($ran_into_timeout) { # note that the caller expects 0 for success $self->{$phase} = CPAN::Distrostatus->new("NO timeout during expect dialog"); return 0; } elsif ($ran_into_eof) { last EXPECT; } $expo->send($send); } $expo->soft_close; return $expo->exitstatus(); } #-> CPAN::Distribution::_validate_distropref sub _validate_distropref { my($self,@args) = @_; if ( $CPAN::META->has_inst("CPAN::Kwalify") && $CPAN::META->has_inst("Kwalify") ) { eval {CPAN::Kwalify::_validate("distroprefs",@args);}; if ($@) { $CPAN::Frontend->mywarn($@); } } else { CPAN->debug("not validating '@args'") if $CPAN::DEBUG; } } #-> CPAN::Distribution::_find_prefs sub _find_prefs { my($self) = @_; my $distroid = $self->pretty_id; #CPAN->debug("distroid[$distroid]") if $CPAN::DEBUG; my $prefs_dir = $CPAN::Config->{prefs_dir}; return if $prefs_dir =~ /^\s*$/; eval { File::Path::mkpath($prefs_dir); }; if ($@) { $CPAN::Frontend->mydie("Cannot create directory $prefs_dir"); } # shortcut if there are no distroprefs files { my $dh = DirHandle->new($prefs_dir) or $CPAN::Frontend->mydie("Couldn't open '$prefs_dir': $!"); my @files = map { /\.(yml|dd|st)\z/i } $dh->read; return unless @files; } my $yaml_module = CPAN::_yaml_module(); my $ext_map = {}; my @extensions; if ($CPAN::META->has_inst($yaml_module)) { $ext_map->{yml} = 'CPAN'; } else { my @fallbacks; if ($CPAN::META->has_inst("Data::Dumper")) { push @fallbacks, $ext_map->{dd} = 'Data::Dumper'; } if ($CPAN::META->has_inst("Storable")) { push @fallbacks, $ext_map->{st} = 'Storable'; } if (@fallbacks) { local $" = " and "; unless ($self->{have_complained_about_missing_yaml}++) { $CPAN::Frontend->mywarnonce("'$yaml_module' not installed, falling back ". "to @fallbacks to read prefs '$prefs_dir'\n"); } } else { unless ($self->{have_complained_about_missing_yaml}++) { $CPAN::Frontend->mywarnonce("'$yaml_module' not installed, cannot ". "read prefs '$prefs_dir'\n"); } } } my $finder = CPAN::Distroprefs->find($prefs_dir, $ext_map); DIRENT: while (my $result = $finder->next) { if ($result->is_warning) { $CPAN::Frontend->mywarn($result->as_string); $CPAN::Frontend->mysleep(1); next DIRENT; } elsif ($result->is_fatal) { $CPAN::Frontend->mydie($result->as_string); } my @prefs = @{ $result->prefs }; ELEMENT: for my $y (0..$#prefs) { my $pref = $prefs[$y]; $self->_validate_distropref($pref->data, $result->abs, $y); # I don't know why we silently skip when there's no match, but # complain if there's an empty match hashref, and there's no # comment explaining why -- hdp, 2008-03-18 unless ($pref->has_any_match) { next ELEMENT; } unless ($pref->has_valid_subkeys) { $CPAN::Frontend->mydie(sprintf "Nonconforming .%s file '%s': " . "missing match/* subattribute. " . "Please remove, cannot continue.", $result->ext, $result->abs, ); } my $arg = { env => \%ENV, distribution => $distroid, perl => \&CPAN::find_perl, perlconfig => \%Config::Config, module => sub { [ $self->containsmods ] }, }; if ($pref->matches($arg)) { return { prefs => $pref->data, prefs_file => $result->abs, prefs_file_doc => $y, }; } } } return; } # CPAN::Distribution::prefs sub prefs { my($self) = @_; if (exists $self->{negative_prefs_cache} && $self->{negative_prefs_cache} != $CPAN::CurrentCommandId ) { delete $self->{negative_prefs_cache}; delete $self->{prefs}; } if (exists $self->{prefs}) { return $self->{prefs}; # XXX comment out during debugging } if ($CPAN::Config->{prefs_dir}) { CPAN->debug("prefs_dir[$CPAN::Config->{prefs_dir}]") if $CPAN::DEBUG; my $prefs = $self->_find_prefs(); $prefs ||= ""; # avoid warning next line CPAN->debug("prefs[$prefs]") if $CPAN::DEBUG; if ($prefs) { for my $x (qw(prefs prefs_file prefs_file_doc)) { $self->{$x} = $prefs->{$x}; } my $bs = sprintf( "%s[%s]", File::Basename::basename($self->{prefs_file}), $self->{prefs_file_doc}, ); my $filler1 = "_" x 22; my $filler2 = int(66 - length($bs))/2; $filler2 = 0 if $filler2 < 0; $filler2 = " " x $filler2; $CPAN::Frontend->myprint(" $filler1 D i s t r o P r e f s $filler1 $filler2 $bs $filler2 "); $CPAN::Frontend->mysleep(1); return $self->{prefs}; } } $self->{negative_prefs_cache} = $CPAN::CurrentCommandId; return $self->{prefs} = +{}; } # CPAN::Distribution::_make_phase_arg sub _make_phase_arg { my($self, $phase) = @_; my $_make_phase_arg; my $prefs = $self->prefs; if ( $prefs && exists $prefs->{$phase} && exists $prefs->{$phase}{args} && $prefs->{$phase}{args} ) { $_make_phase_arg = join(" ", map {CPAN::HandleConfig ->safe_quote($_)} @{$prefs->{$phase}{args}}, ); } # cpan[2]> o conf make[TAB] # make make_install_make_command # make_arg makepl_arg # make_install_arg # cpan[2]> o conf mbuild[TAB] # mbuild_arg mbuild_install_build_command # mbuild_install_arg mbuildpl_arg my $mantra; # must switch make/mbuild here if ($self->{modulebuild}) { $mantra = "mbuild"; } else { $mantra = "make"; } my %map = ( pl => "pl_arg", make => "_arg", test => "_test_arg", # does not really exist but maybe # will some day and now protects # us from unini warnings install => "_install_arg", ); my $phase_underscore_meshup = $map{$phase}; my $what = sprintf "%s%s", $mantra, $phase_underscore_meshup; $_make_phase_arg ||= $CPAN::Config->{$what}; return $_make_phase_arg; } # CPAN::Distribution::_make_command sub _make_command { my ($self) = @_; if ($self) { return CPAN::HandleConfig ->safe_quote( CPAN::HandleConfig->prefs_lookup($self, q{make}) || $Config::Config{make} || 'make' ); } else { # Old style call, without object. Deprecated Carp::confess("CPAN::_make_command() used as function. Don't Do That."); return safe_quote(undef, CPAN::HandleConfig->prefs_lookup($self,q{make}) || $CPAN::Config->{make} || $Config::Config{make} || 'make'); } } sub _make_install_make_command { my ($self) = @_; my $mimc = CPAN::HandleConfig->prefs_lookup($self, q{make_install_make_command}); return $self->_make_command() unless $mimc; # Quote the "make install" make command on Windows, where it is commonly # found in, e.g., C:\Program Files\... and therefore needs quoting. We can't # do this in general because the command maybe "sudo make..." (i.e. a # program with arguments), but that is unlikely to be the case on Windows. $mimc = CPAN::HandleConfig->safe_quote($mimc) if $^O eq 'MSWin32'; return $mimc; } #-> sub CPAN::Distribution::is_locally_optional sub is_locally_optional { my($self, $prereq_pm, $prereq) = @_; $prereq_pm ||= $self->{prereq_pm}; my($nmo,$opt); for my $rt (qw(requires build_requires)) { if (exists $prereq_pm->{$rt}{$prereq}) { # rt 121914 $nmo ||= $CPAN::META->instance("CPAN::Module",$prereq); my $av = $nmo->available_version; return 0 if !$av || CPAN::Version->vlt($av,$prereq_pm->{$rt}{$prereq}); } if (exists $prereq_pm->{"opt_$rt"}{$prereq}) { $opt = 1; } } return $opt||0; } #-> sub CPAN::Distribution::follow_prereqs ; sub follow_prereqs { my($self) = shift; my($slot) = shift; my(@prereq_tuples) = grep {$_->[0] ne "perl"} @_; return unless @prereq_tuples; my(@good_prereq_tuples); for my $p (@prereq_tuples) { # e.g. $p = ['Devel::PartialDump', 'r', 1] # promote if possible if ($p->[1] =~ /^(r|c)$/) { push @good_prereq_tuples, $p; } elsif ($p->[1] =~ /^(b)$/) { my $reqtype = CPAN::Queue->reqtype_of($p->[0]); if ($reqtype =~ /^(r|c)$/) { push @good_prereq_tuples, [$p->[0], $reqtype, $p->[2]]; } else { push @good_prereq_tuples, $p; } } else { die "Panic: in follow_prereqs: reqtype[$p->[1]] seen, should never happen"; } } my $pretty_id = $self->pretty_id; my %map = ( b => "build_requires", r => "requires", c => "commandline", ); my($filler1,$filler2,$filler3,$filler4); my $unsat = "Unsatisfied dependencies detected during"; my $w = length($unsat) > length($pretty_id) ? length($unsat) : length($pretty_id); { my $r = int(($w - length($unsat))/2); my $l = $w - length($unsat) - $r; $filler1 = "-"x4 . " "x$l; $filler2 = " "x$r . "-"x4 . "\n"; } { my $r = int(($w - length($pretty_id))/2); my $l = $w - length($pretty_id) - $r; $filler3 = "-"x4 . " "x$l; $filler4 = " "x$r . "-"x4 . "\n"; } $CPAN::Frontend-> myprint("$filler1 $unsat $filler2". "$filler3 $pretty_id $filler4". join("", map {sprintf " %s \[%s%s]\n", $_->[0], $map{$_->[1]}, $self->is_locally_optional(undef,$_->[0]) ? ",optional" : ""} @good_prereq_tuples), ); my $follow = 0; if ($CPAN::Config->{prerequisites_policy} eq "follow") { $follow = 1; } elsif ($CPAN::Config->{prerequisites_policy} eq "ask") { my $answer = CPAN::Shell::colorable_makemaker_prompt( "Shall I follow them and prepend them to the queue of modules we are processing right now?", "yes"); $follow = $answer =~ /^\s*y/i; } else { my @prereq = map { $_->[0] } @good_prereq_tuples; local($") = ", "; $CPAN::Frontend-> myprint(" Ignoring dependencies on modules @prereq\n"); } if ($follow) { my $id = $self->id; my(@to_queue_mand,@to_queue_opt); for my $gp (@good_prereq_tuples) { my($prereq,$reqtype,$optional) = @$gp; my $qthing = +{qmod=>$prereq,reqtype=>$reqtype,optional=>$optional}; if ($optional && $self->is_locally_optional(undef,$prereq) ){ # Since we do not depend on this one, we do not need # this in a mandatory arrangement: push @to_queue_opt, $qthing; } else { my $any = CPAN::Shell->expandany($prereq); $self->{$slot . "_for"}{$any->id}++; if ($any) { unless ($optional) { # No recursion check in an optional area of the tree $any->color_cmd_tmps(0,2); } } else { $CPAN::Frontend->mywarn("Warning (maybe a bug): Cannot expand prereq '$prereq'\n"); $CPAN::Frontend->mysleep(2); } # order everything that is not locally_optional just # like mandatory items: this keeps leaves before # branches unshift @to_queue_mand, $qthing; } } if (@to_queue_mand) { unshift @to_queue_mand, {qmod => $id, reqtype => $self->{reqtype}, optional=> !$self->{mandatory}}; CPAN::Queue->jumpqueue(@to_queue_opt,@to_queue_mand); $self->{$slot} = "Delayed until after prerequisites"; return 1; # signal we need dependencies } elsif (@to_queue_opt) { CPAN::Queue->jumpqueue(@to_queue_opt); } } return; } sub _feature_depends { my($self) = @_; my $meta_yml = $self->parse_meta_yml(); my $optf = $meta_yml->{optional_features} or return; if (!ref $optf or ref $optf ne "HASH"){ $CPAN::Frontend->mywarn("The content of optional_features is not a HASH reference. Cannot use it.\n"); $optf = {}; } my $wantf = $self->prefs->{features} or return; if (!ref $wantf or ref $wantf ne "ARRAY"){ $CPAN::Frontend->mywarn("The content of 'features' is not an ARRAY reference. Cannot use it.\n"); $wantf = []; } my $dep = +{}; for my $wf (@$wantf) { if (my $f = $optf->{$wf}) { $CPAN::Frontend->myprint("Found the demanded feature '$wf' that ". "is accompanied by this description:\n". $f->{description}. "\n\n" ); # configure_requires currently not in the spec, unlikely to be useful anyway for my $reqtype (qw(configure_requires build_requires requires)) { my $reqhash = $f->{$reqtype} or next; while (my($k,$v) = each %$reqhash) { $dep->{$reqtype}{$k} = $v; } } } else { $CPAN::Frontend->mywarn("The demanded feature '$wf' was not ". "found in the META.yml file". "\n\n" ); } } $dep; } sub prereqs_for_slot { my($self,$slot) = @_; my($prereq_pm); unless ($CPAN::META->has_usable("CPAN::Meta::Requirements")) { my $whynot = "not available"; if (defined $CPAN::Meta::Requirements::VERSION) { $whynot = "version $CPAN::Meta::Requirements::VERSION not sufficient"; } $CPAN::Frontend->mywarn("CPAN::Meta::Requirements $whynot\n"); my $before = ""; if ($self->{CALLED_FOR}){ if ($self->{CALLED_FOR} =~ /^( CPAN::Meta::Requirements |CPAN::DistnameInfo |version |parent |ExtUtils::MakeMaker |Test::Harness )$/x) { $CPAN::Frontend->mywarn("Please install CPAN::Meta::Requirements ". "as soon as possible; it is needed for a reliable operation of ". "the cpan shell; setting requirements to nil for '$1' for now ". "to prevent deadlock during bootstrapping\n"); return; } $before = " before $self->{CALLED_FOR}"; } $CPAN::Frontend->mydie("Please install CPAN::Meta::Requirements manually$before"); } my $merged = CPAN::Meta::Requirements->new; my $prefs_depends = $self->prefs->{depends}||{}; my $feature_depends = $self->_feature_depends(); if ($slot eq "configure_requires_later") { for my $hash ( $self->configure_requires, $prefs_depends->{configure_requires}, $feature_depends->{configure_requires}, ) { $merged->add_requirements( CPAN::Meta::Requirements->from_string_hash($hash) ); } if (-f "Build.PL" && ! -f File::Spec->catfile($self->{build_dir},"Makefile.PL") && ! @{[ $merged->required_modules ]} && ! $CPAN::META->has_inst("Module::Build") ) { $CPAN::Frontend->mywarn( " Warning: CPAN.pm discovered Module::Build as undeclared prerequisite.\n". " Adding it now as such.\n" ); $CPAN::Frontend->mysleep(5); $merged->add_minimum( "Module::Build" => 0 ); delete $self->{writemakefile}; } $prereq_pm = {}; # configure_requires defined as "b" } elsif ($slot eq "later") { my $prereq_pm_0 = $self->prereq_pm || {}; for my $reqtype (qw(requires build_requires opt_requires opt_build_requires)) { $prereq_pm->{$reqtype} = {%{$prereq_pm_0->{$reqtype}||{}}}; # copy to not pollute it for my $dep ($prefs_depends,$feature_depends) { for my $k (keys %{$dep->{$reqtype}||{}}) { $prereq_pm->{$reqtype}{$k} = $dep->{$reqtype}{$k}; } } } # XXX what about optional_req|breq? -- xdg, 2012-04-01 for my $hash ( $prereq_pm->{requires}, $prereq_pm->{build_requires}, $prereq_pm->{opt_requires}, $prereq_pm->{opt_build_requires}, ) { $merged->add_requirements( CPAN::Meta::Requirements->from_string_hash($hash) ); } } else { die "Panic: illegal slot '$slot'"; } return ($merged->as_string_hash, $prereq_pm); } #-> sub CPAN::Distribution::unsat_prereq ; # return ([Foo,"r"],[Bar,"b"]) for normal modules # return ([perl=>5.008]) if we need a newer perl than we are running under # (sorry for the inconsistency, it was an accident) sub unsat_prereq { my($self,$slot) = @_; my($merged_hash,$prereq_pm) = $self->prereqs_for_slot($slot); my(@need); unless ($CPAN::META->has_usable("CPAN::Meta::Requirements")) { $CPAN::Frontend->mywarn("CPAN::Meta::Requirements not available, please install as soon as possible, trying to continue with severly limited capabilities\n"); return; } my $merged = CPAN::Meta::Requirements->from_string_hash($merged_hash); my @merged = sort $merged->required_modules; CPAN->debug("all merged_prereqs[@merged]") if $CPAN::DEBUG; NEED: for my $need_module ( @merged ) { my $need_version = $merged->requirements_for_module($need_module); my($available_version,$inst_file,$available_file,$nmo); if ($need_module eq "perl") { $available_version = $]; $available_file = CPAN::find_perl(); } else { if (CPAN::_sqlite_running()) { CPAN::Index->reload; $CPAN::SQLite->search("CPAN::Module",$need_module); } $nmo = $CPAN::META->instance("CPAN::Module",$need_module); $inst_file = $nmo->inst_file || ''; $available_file = $nmo->available_file || ''; $available_version = $nmo->available_version; if ($nmo->uptodate) { my $accepts = eval { $merged->accepts_module($need_module, $available_version); }; unless ($accepts) { my $rq = $merged->requirements_for_module( $need_module ); $CPAN::Frontend->mywarn( "Warning: Version '$available_version' of ". "'$need_module' is up to date but does not ". "fulfill requirements ($rq). I will continue, ". "but chances to succeed are low.\n"); } next NEED; } # if they have not specified a version, we accept any # installed one; in that case inst_file is always # sufficient and available_file is sufficient on # both build_requires and configure_requires my $sufficient = $inst_file || ( exists $prereq_pm->{requires}{$need_module} ? 0 : $available_file ); if ( $sufficient and ( # a few quick short circuits not defined $need_version or $need_version eq '0' # "==" would trigger warning when not numeric or $need_version eq "undef" )) { unless ($nmo->inst_deprecated) { next NEED; } } } # We only want to install prereqs if either they're not installed # or if the installed version is too old. We cannot omit this # check, because if 'force' is in effect, nobody else will check. # But we don't want to accept a deprecated module installed as part # of the Perl core, so we continue if the available file is the installed # one and is deprecated if ( $available_file ) { my $fulfills_all_version_rqs = $self->_fulfills_all_version_rqs ( $need_module, $available_file, $available_version, $need_version, ); if ( $inst_file && $available_file eq $inst_file && $nmo->inst_deprecated ) { # continue installing as a prereq. we really want that # because the deprecated module may spit out warnings # and third party did not know until today. Only one # exception is OK, because CPANPLUS is special after # all: if ( $fulfills_all_version_rqs and $nmo->id =~ /^CPANPLUS(?:::Dist::Build)$/ ) { # here we have an available version that is good # enough although deprecated (preventing circular # loop CPANPLUS => CPANPLUS::Dist::Build RT#83042) next NEED; } } elsif ( $self->{reqtype} # e.g. maybe we came via goto? && $self->{reqtype} =~ /^(r|c)$/ && ( exists $prereq_pm->{requires}{$need_module} || exists $prereq_pm->{opt_requires}{$need_module} ) && $nmo && !$inst_file ) { # continue installing as a prereq; this may be a # distro we already used when it was a build_requires # so we did not install it. But suddenly somebody # wants it as a requires my $need_distro = $nmo->distribution; if ($need_distro->{install} && $need_distro->{install}->failed && $need_distro->{install}->text =~ /is only/) { my $id = $need_distro->pretty_id; $CPAN::Frontend->myprint("Promoting $id from build_requires to requires due $need_module\n"); delete $need_distro->{install}; # promote to another installation attempt $need_distro->{reqtype} = "r"; $need_distro->install; next NEED; } } else { next NEED if $fulfills_all_version_rqs; } } if ($need_module eq "perl") { return ["perl", $need_version]; } $self->{sponsored_mods}{$need_module} ||= 0; CPAN->debug("need_module[$need_module]s/s/n[$self->{sponsored_mods}{$need_module}]") if $CPAN::DEBUG; if (my $sponsoring = $self->{sponsored_mods}{$need_module}++) { # We have already sponsored it and for some reason it's still # not available. So we do ... what?? # if we push it again, we have a potential infinite loop # The following "next" was a very problematic construct. # It helped a lot but broke some day and had to be # replaced. # We must be able to deal with modules that come again and # again as a prereq and have themselves prereqs and the # queue becomes long but finally we would find the correct # order. The RecursiveDependency check should trigger a # die when it's becoming too weird. Unfortunately removing # this next breaks many other things. # The bug that brought this up is described in Todo under # "5.8.9 cannot install Compress::Zlib" # next; # this is the next that had to go away # The following "next NEED" are fine and the error message # explains well what is going on. For example when the DBI # fails and consequently DBD::SQLite fails and now we are # processing CPAN::SQLite. Then we must have a "next" for # DBD::SQLite. How can we get it and how can we identify # all other cases we must identify? my $do = $nmo->distribution; next NEED unless $do; # not on CPAN if (CPAN::Version->vcmp($need_version, $nmo->ro->{CPAN_VERSION}) > 0){ $CPAN::Frontend->mywarn("Warning: Prerequisite ". "'$need_module => $need_version' ". "for '$self->{ID}' seems ". "not available according to the indices\n" ); next NEED; } NOSAYER: for my $nosayer ( "unwrapped", "writemakefile", "signature_verify", "make", "make_test", "install", "make_clean", ) { if ($do->{$nosayer}) { my $selfid = $self->pretty_id; my $did = $do->pretty_id; if (UNIVERSAL::can($do->{$nosayer},"failed") ? $do->{$nosayer}->failed : $do->{$nosayer} =~ /^NO/) { if ($nosayer eq "make_test" && $do->{make_test}{COMMANDID} != $CPAN::CurrentCommandId ) { next NOSAYER; } ### XXX don't complain about missing optional deps -- xdg, 2012-04-01 if ($self->is_locally_optional($prereq_pm, $need_module)) { # don't complain about failing optional prereqs } else { $CPAN::Frontend->mywarn("Warning: Prerequisite ". "'$need_module => $need_version' ". "for '$selfid' failed when ". "processing '$did' with ". "'$nosayer => $do->{$nosayer}'. Continuing, ". "but chances to succeed are limited.\n" ); $CPAN::Frontend->mysleep($sponsoring/10); } next NEED; } else { # the other guy succeeded if ($nosayer =~ /^(install|make_test)$/) { # we had this with # DMAKI/DateTime-Calendar-Chinese-0.05.tar.gz # in 2007-03 for 'make install' # and 2008-04: #30464 (for 'make test') # $CPAN::Frontend->mywarn("Warning: Prerequisite ". # "'$need_module => $need_version' ". # "for '$selfid' already built ". # "but the result looks suspicious. ". # "Skipping another build attempt, ". # "to prevent looping endlessly.\n" # ); next NEED; } } } } } my $needed_as; if (0) { } elsif (exists $prereq_pm->{requires}{$need_module} || exists $prereq_pm->{opt_requires}{$need_module} ) { $needed_as = "r"; } elsif ($slot eq "configure_requires_later") { # in ae872487d5 we said: C< we have not yet run the # {Build,Makefile}.PL, we must presume "r" >; but the # meta.yml standard says C< These dependencies are not # required after the distribution is installed. >; so now # we change it back to "b" and care for the proper # promotion later. $needed_as = "b"; } else { $needed_as = "b"; } # here need to flag as optional for recommends/suggests # -- xdg, 2012-04-01 $self->debug(sprintf "%s manadory?[%s]", $self->pretty_id, $self->{mandatory}) if $CPAN::DEBUG; my $optional = !$self->{mandatory} || $self->is_locally_optional($prereq_pm, $need_module); push @need, [$need_module,$needed_as,$optional]; } my @unfolded = map { "[".join(",",@$_)."]" } @need; CPAN->debug("returning from unsat_prereq[@unfolded]") if $CPAN::DEBUG; @need; } sub _fulfills_all_version_rqs { my($self,$need_module,$available_file,$available_version,$need_version) = @_; my(@all_requirements) = split /\s*,\s*/, $need_version; local($^W) = 0; my $ok = 0; RQ: for my $rq (@all_requirements) { if ($rq =~ s|>=\s*||) { } elsif ($rq =~ s|>\s*||) { # 2005-12: one user if (CPAN::Version->vgt($available_version,$rq)) { $ok++; } next RQ; } elsif ($rq =~ s|!=\s*||) { # 2005-12: no user if (CPAN::Version->vcmp($available_version,$rq)) { $ok++; next RQ; } else { $ok=0; last RQ; } } elsif ($rq =~ m|<=?\s*|) { # 2005-12: no user $CPAN::Frontend->mywarn("Downgrading not supported (rq[$rq])\n"); $ok++; next RQ; } elsif ($rq =~ s|==\s*||) { # 2009-07: ELLIOTJS/Perl-Critic-1.099_002.tar.gz if (CPAN::Version->vcmp($available_version,$rq)) { $ok=0; last RQ; } else { $ok++; next RQ; } } if (! CPAN::Version->vgt($rq, $available_version)) { $ok++; } CPAN->debug(sprintf("need_module[%s]available_file[%s]". "available_version[%s]rq[%s]ok[%d]", $need_module, $available_file, $available_version, CPAN::Version->readable($rq), $ok, )) if $CPAN::DEBUG; } my $ret = $ok == @all_requirements; CPAN->debug(sprintf("need_module[%s]ok[%s]all_requirements[%d]",$need_module, $ok, scalar @all_requirements)) if $CPAN::DEBUG; return $ret; } #-> sub CPAN::Distribution::read_meta # read any sort of meta files, return CPAN::Meta object if no errors sub read_meta { my($self) = @_; my $meta_file = $self->pick_meta_file or return; return unless $CPAN::META->has_usable("CPAN::Meta"); my $meta = eval { CPAN::Meta->load_file($meta_file)} or return; # Very old EU::MM could have wrong META if ($meta_file eq 'META.yml' && $meta->generated_by =~ /ExtUtils::MakeMaker version ([\d\._]+)/ ) { my $eummv = do { local $^W = 0; $1+0; }; return if $eummv < 6.2501; } return $meta; } #-> sub CPAN::Distribution::read_yaml ; # XXX This should be DEPRECATED -- dagolden, 2011-02-05 sub read_yaml { my($self) = @_; my $meta_file = $self->pick_meta_file('\.yml$'); $self->debug("meta_file[$meta_file]") if $CPAN::DEBUG; return unless $meta_file; my $yaml; eval { $yaml = $self->parse_meta_yml($meta_file) }; if ($@ or ! $yaml) { return undef; # if we die, then we cannot read YAML's own META.yml } # not "authoritative" if (defined $yaml && (! ref $yaml || ref $yaml ne "HASH")) { $CPAN::Frontend->mywarn("META.yml does not seem to be conforming, cannot use it.\n"); $yaml = undef; } $self->debug(sprintf "yaml[%s]", $yaml || "UNDEF") if $CPAN::DEBUG; $self->debug($yaml) if $CPAN::DEBUG && $yaml; # MYMETA.yml is static and authoritative by definition if ( $meta_file =~ /MYMETA\.yml/ ) { return $yaml; } # META.yml is authoritative only if dynamic_config is defined and false if ( defined $yaml->{dynamic_config} && ! $yaml->{dynamic_config} ) { return $yaml; } # otherwise, we can't use what we found return undef; } #-> sub CPAN::Distribution::configure_requires ; sub configure_requires { my($self) = @_; return unless my $meta_file = $self->pick_meta_file('^META'); if (my $meta_obj = $self->read_meta) { my $prereqs = $meta_obj->effective_prereqs; my $cr = $prereqs->requirements_for(qw/configure requires/); return $cr ? $cr->as_string_hash : undef; } else { my $yaml = eval { $self->parse_meta_yml($meta_file) }; return $yaml->{configure_requires}; } } #-> sub CPAN::Distribution::prereq_pm ; sub prereq_pm { my($self) = @_; return unless $self->{writemakefile} # no need to have succeeded # but we must have run it || $self->{modulebuild}; unless ($self->{build_dir}) { return; } # no Makefile/Build means configuration aborted, so don't look for prereqs my $makefile = File::Spec->catfile($self->{build_dir}, $^O eq 'VMS' ? 'descrip.mms' : 'Makefile'); my $buildfile = File::Spec->catfile($self->{build_dir}, $^O eq 'VMS' ? 'Build.com' : 'Build'); return unless -f $makefile || -f $buildfile; CPAN->debug(sprintf "writemakefile[%s]modulebuild[%s]", $self->{writemakefile}||"", $self->{modulebuild}||"", ) if $CPAN::DEBUG; my($req,$breq, $opt_req, $opt_breq); my $meta_obj = $self->read_meta; # META/MYMETA is only authoritative if dynamic_config is false if ($meta_obj && ! $meta_obj->dynamic_config) { my $prereqs = $meta_obj->effective_prereqs; my $requires = $prereqs->requirements_for(qw/runtime requires/); my $build_requires = $prereqs->requirements_for(qw/build requires/); my $test_requires = $prereqs->requirements_for(qw/test requires/); # XXX we don't yet distinguish build vs test, so merge them for now $build_requires->add_requirements($test_requires); $req = $requires->as_string_hash; $breq = $build_requires->as_string_hash; # XXX assemble optional_req && optional_breq from recommends/suggests # depending on corresponding policies -- xdg, 2012-04-01 CPAN->use_inst("CPAN::Meta::Requirements"); my $opt_runtime = CPAN::Meta::Requirements->new; my $opt_build = CPAN::Meta::Requirements->new; if ( $CPAN::Config->{recommends_policy} ) { $opt_runtime->add_requirements( $prereqs->requirements_for(qw/runtime recommends/)); $opt_build->add_requirements( $prereqs->requirements_for(qw/build recommends/)); $opt_build->add_requirements( $prereqs->requirements_for(qw/test recommends/)); } if ( $CPAN::Config->{suggests_policy} ) { $opt_runtime->add_requirements( $prereqs->requirements_for(qw/runtime suggests/)); $opt_build->add_requirements( $prereqs->requirements_for(qw/build suggests/)); $opt_build->add_requirements( $prereqs->requirements_for(qw/test suggests/)); } $opt_req = $opt_runtime->as_string_hash; $opt_breq = $opt_build->as_string_hash; } elsif (my $yaml = $self->read_yaml) { # often dynamic_config prevents a result here $req = $yaml->{requires} || {}; $breq = $yaml->{build_requires} || {}; if ( $CPAN::Config->{recommends_policy} ) { $opt_req = $yaml->{recommends} || {}; } undef $req unless ref $req eq "HASH" && %$req; if ($req) { if ($yaml->{generated_by} && $yaml->{generated_by} =~ /ExtUtils::MakeMaker version ([\d\._]+)/) { my $eummv = do { local $^W = 0; $1+0; }; if ($eummv < 6.2501) { # thanks to Slaven for digging that out: MM before # that could be wrong because it could reflect a # previous release undef $req; } } my $areq; my $do_replace; foreach my $k (sort keys %{$req||{}}) { my $v = $req->{$k}; next unless defined $v; if ($v =~ /\d/) { $areq->{$k} = $v; } elsif ($k =~ /[A-Za-z]/ && $v =~ /[A-Za-z]/ && $CPAN::META->exists("CPAN::Module",$v) ) { $CPAN::Frontend->mywarn("Suspicious key-value pair in META.yml's ". "requires hash: $k => $v; I'll take both ". "key and value as a module name\n"); $CPAN::Frontend->mysleep(1); $areq->{$k} = 0; $areq->{$v} = 0; $do_replace++; } } $req = $areq if $do_replace; } } else { $CPAN::Frontend->mywarnonce("Could not read metadata file. Falling back to other ". "methods to determine prerequisites\n"); } unless ($req || $breq) { my $build_dir; unless ( $build_dir = $self->{build_dir} ) { return; } my $makefile = File::Spec->catfile($build_dir,"Makefile"); my $fh; if (-f $makefile and $fh = FileHandle->new("<$makefile\0")) { CPAN->debug("Getting prereq from Makefile") if $CPAN::DEBUG; local($/) = "\n"; while (<$fh>) { last if /MakeMaker post_initialize section/; my($p) = m{^[\#] \s+PREREQ_PM\s+=>\s+(.+) }x; next unless $p; # warn "Found prereq expr[$p]"; # Regexp modified by A.Speer to remember actual version of file # PREREQ_PM hash key wants, then add to while ( $p =~ m/(?:\s)([\w\:]+)=>(q\[.*?\]|undef),?/g ) { my($m,$n) = ($1,$2); # When a prereq is mentioned twice: let the bigger # win; usual culprit is that they declared # build_requires separately from requires; see # rt.cpan.org #47774 my($prevn); if ( defined $req->{$m} ) { $prevn = $req->{$m}; } if ($n =~ /^q\[(.*?)\]$/) { $n = $1; } if (!$prevn || CPAN::Version->vlt($prevn, $n)){ $req->{$m} = $n; } } last; } } } unless ($req || $breq) { my $build_dir = $self->{build_dir} or die "Panic: no build_dir?"; my $buildfile = File::Spec->catfile($build_dir,"Build"); if (-f $buildfile) { CPAN->debug("Found '$buildfile'") if $CPAN::DEBUG; my $build_prereqs = File::Spec->catfile($build_dir,"_build","prereqs"); if (-f $build_prereqs) { CPAN->debug("Getting prerequisites from '$build_prereqs'") if $CPAN::DEBUG; my $content = do { local *FH; open FH, $build_prereqs or $CPAN::Frontend->mydie("Could not open ". "'$build_prereqs': $!"); local $/; <FH>; }; my $bphash = eval $content; if ($@) { } else { $req = $bphash->{requires} || +{}; $breq = $bphash->{build_requires} || +{}; } } } } # XXX needs to be adapted for optional_req & optional_breq -- xdg, 2012-04-01 if ($req || $breq || $opt_req || $opt_breq ) { return $self->{prereq_pm} = { requires => $req, build_requires => $breq, opt_requires => $opt_req, opt_build_requires => $opt_breq, }; } } #-> sub CPAN::Distribution::shortcut_test ; # return values: undef means don't shortcut; 0 means shortcut as fail; # and 1 means shortcut as success sub shortcut_test { my ($self) = @_; $self->debug("checking badtestcnt[$self->{ID}]") if $CPAN::DEBUG; $self->{badtestcnt} ||= 0; if ($self->{badtestcnt} > 0) { require Data::Dumper; CPAN->debug(sprintf "NOREPEAT[%s]", Data::Dumper::Dumper($self)) if $CPAN::DEBUG; return $self->goodbye("Won't repeat unsuccessful test during this command"); } for my $slot ( qw/later configure_requires_later/ ) { $self->debug("checking $slot slot[$self->{ID}]") if $CPAN::DEBUG; return $self->success($self->{$slot}) if $self->{$slot}; } $self->debug("checking if tests passed[$self->{ID}]") if $CPAN::DEBUG; if ( $self->{make_test} ) { if ( UNIVERSAL::can($self->{make_test},"failed") ? $self->{make_test}->failed : $self->{make_test} =~ /^NO/ ) { if ( UNIVERSAL::can($self->{make_test},"commandid") && $self->{make_test}->commandid == $CPAN::CurrentCommandId ) { return $self->goodbye("Has already been tested within this command"); } } else { # if global "is_tested" has been cleared, we need to mark this to # be added to PERL5LIB if not already installed if ($self->tested_ok_but_not_installed) { $CPAN::META->is_tested($self->{build_dir},$self->{make_test}{TIME}); } return $self->success("Has already been tested successfully"); } } if ($self->{notest}) { $self->{make_test} = CPAN::Distrostatus->new("YES"); return $self->success("Skipping test because of notest pragma"); } return undef; # no shortcut } #-> sub CPAN::Distribution::_exe_files ; sub _exe_files { my($self) = @_; return unless $self->{writemakefile} # no need to have succeeded # but we must have run it || $self->{modulebuild}; unless ($self->{build_dir}) { return; } CPAN->debug(sprintf "writemakefile[%s]modulebuild[%s]", $self->{writemakefile}||"", $self->{modulebuild}||"", ) if $CPAN::DEBUG; my $build_dir; unless ( $build_dir = $self->{build_dir} ) { return; } my $makefile = File::Spec->catfile($build_dir,"Makefile"); my $fh; my @exe_files; if (-f $makefile and $fh = FileHandle->new("<$makefile\0")) { CPAN->debug("Getting exefiles from Makefile") if $CPAN::DEBUG; local($/) = "\n"; while (<$fh>) { last if /MakeMaker post_initialize section/; my($p) = m{^[\#] \s+EXE_FILES\s+=>\s+\[(.+)\] }x; next unless $p; # warn "Found exefiles expr[$p]"; my @p = split /,\s*/, $p; for my $p2 (@p) { if ($p2 =~ /^q\[(.+)\]/) { push @exe_files, $1; } } } } return \@exe_files if @exe_files; my $buildparams = File::Spec->catfile($build_dir,"_build","build_params"); if (-f $buildparams) { CPAN->debug("Found '$buildparams'") if $CPAN::DEBUG; my $x = do $buildparams; for my $sf ($x->[2]{script_files}) { if (my $reftype = ref $sf) { if ($reftype eq "ARRAY") { push @exe_files, @$sf; } elsif ($reftype eq "HASH") { push @exe_files, keys %$sf; } else { $CPAN::Frontend->mywarn("Invalid reftype $reftype for Build.PL 'script_files'\n"); } } elsif (defined $sf) { push @exe_files, $sf; } } } return \@exe_files; } #-> sub CPAN::Distribution::test ; sub test { my($self) = @_; $self->pre_test(); if (exists $self->{cleanup_after_install_done}) { $self->post_test(); return $self->make; } $self->debug("checking goto id[$self->{ID}]") if $CPAN::DEBUG; if (my $goto = $self->prefs->{goto}) { $self->post_test(); return $self->goto($goto); } unless ($self->make){ $self->post_test(); return; } if ( defined( my $sc = $self->shortcut_test ) ) { $self->post_test(); return $sc; } if ($CPAN::Signal) { delete $self->{force_update}; $self->post_test(); return; } # warn "XDEBUG: checking for notest: $self->{notest} $self"; my $make = $self->{modulebuild} ? "Build" : "make"; local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} && defined $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # test $CPAN::META->set_perl5lib; local $ENV{MAKEFLAGS}; # protect us from outer make calls local $ENV{PERL_MM_USE_DEFAULT} = 1 if $CPAN::Config->{use_prompt_default}; local $ENV{NONINTERACTIVE_TESTING} = 1 if $CPAN::Config->{use_prompt_default}; if ($run_allow_installing_within_test) { my($allow_installing, $why) = $self->_allow_installing; if (! $allow_installing) { $CPAN::Frontend->mywarn("Testing/Installation stopped: $why\n"); $self->introduce_myself; $self->{make_test} = CPAN::Distrostatus->new("NO -- testing/installation stopped due $why"); $CPAN::Frontend->mywarn(" [testing] -- NOT OK\n"); delete $self->{force_update}; $self->post_test(); return; } } $CPAN::Frontend->myprint(sprintf "Running %s test for %s\n", $make, $self->pretty_id); my $builddir = $self->dir or $CPAN::Frontend->mydie("PANIC: Cannot determine build directory\n"); unless (chdir $builddir) { $CPAN::Frontend->mywarn("Couldn't chdir to '$builddir': $!"); $self->post_test(); return; } $self->debug("Changed directory to $self->{build_dir}") if $CPAN::DEBUG; if ($^O eq 'MacOS') { Mac::BuildTools::make_test($self); $self->post_test(); return; } if ($self->{modulebuild}) { my $thm = CPAN::Shell->expand("Module","Test::Harness"); my $v = $thm->inst_version; if (CPAN::Version->vlt($v,2.62)) { # XXX Eric Wilhelm reported this as a bug: klapperl: # Test::Harness 3.0 self-tests, so that should be 'unless # installing Test::Harness' unless ($self->id eq $thm->distribution->id) { $CPAN::Frontend->mywarn(qq{The version of your Test::Harness is only '$v', you need at least '2.62'. Please upgrade your Test::Harness.\n}); $self->{make_test} = CPAN::Distrostatus->new("NO Test::Harness too old"); $self->post_test(); return; } } } if ( ! $self->{force_update} ) { # bypass actual tests if "trust_test_report_history" and have a report my $have_tested_fcn; if ( $CPAN::Config->{trust_test_report_history} && $CPAN::META->has_inst("CPAN::Reporter::History") && ( $have_tested_fcn = CPAN::Reporter::History->can("have_tested" ))) { if ( my @reports = $have_tested_fcn->( dist => $self->base_id ) ) { # Do nothing if grade was DISCARD if ( $reports[-1]->{grade} =~ /^(?:PASS|UNKNOWN)$/ ) { $self->{make_test} = CPAN::Distrostatus->new("YES"); # if global "is_tested" has been cleared, we need to mark this to # be added to PERL5LIB if not already installed if ($self->tested_ok_but_not_installed) { $CPAN::META->is_tested($self->{build_dir},$self->{make_test}{TIME}); } $CPAN::Frontend->myprint("Found prior test report -- OK\n"); $self->post_test(); return; } elsif ( $reports[-1]->{grade} =~ /^(?:FAIL|NA)$/ ) { $self->{make_test} = CPAN::Distrostatus->new("NO"); $self->{badtestcnt}++; $CPAN::Frontend->mywarn("Found prior test report -- NOT OK\n"); $self->post_test(); return; } } } } my $system; my $prefs_test = $self->prefs->{test}; if (my $commandline = exists $prefs_test->{commandline} ? $prefs_test->{commandline} : "") { $system = $commandline; $ENV{PERL} = CPAN::find_perl(); } elsif ($self->{modulebuild}) { $system = sprintf "%s test", $self->_build_command(); unless (-e "Build" || ($^O eq 'VMS' && -e "Build.com")) { my $id = $self->pretty_id; $CPAN::Frontend->mywarn("Alert: no 'Build' file found while trying to test '$id'"); } } else { $system = join " ", $self->_make_command(), "test"; } my $make_test_arg = $self->_make_phase_arg("test"); $system = sprintf("%s%s", $system, $make_test_arg ? " $make_test_arg" : "", ); my($tests_ok); my $test_env; if ($self->prefs->{test}) { $test_env = $self->prefs->{test}{env}; } local @ENV{keys %$test_env} = values %$test_env if $test_env; my $expect_model = $self->_prefs_with_expect("test"); my $want_expect = 0; if ( $expect_model && @{$expect_model->{talk}} ) { my $can_expect = $CPAN::META->has_inst("Expect"); if ($can_expect) { $want_expect = 1; } else { $CPAN::Frontend->mywarn("Expect not installed, falling back to ". "testing without\n"); } } FORK: { my $pid = fork; if (! defined $pid) { # contention warn "Contention '$!', sleeping 2"; sleep 2; redo FORK; } elsif ($pid) { # parent if ($^O eq "MSWin32") { wait; } else { SUPERVISE: while (waitpid($pid, WNOHANG) <= 0) { if ($CPAN::Signal) { kill 9, -$pid; } sleep 1; } } $tests_ok = !$?; } else { # child POSIX::setsid() unless $^O eq "MSWin32"; my $c_ok; $|=1; if ($want_expect) { if ($self->_should_report('test')) { $CPAN::Frontend->mywarn("Reporting via CPAN::Reporter is currently ". "not supported when distroprefs specify ". "an interactive test\n"); } $c_ok = $self->_run_via_expect($system,'test',$expect_model) == 0; } elsif ( $self->_should_report('test') ) { $c_ok = CPAN::Reporter::test($self, $system); } else { $c_ok = system($system) == 0; } exit !$c_ok; } } # FORK $self->introduce_myself; my $but = $self->_make_test_illuminate_prereqs(); if ( $tests_ok ) { if ($but) { $CPAN::Frontend->mywarn("Tests succeeded but $but\n"); $self->{make_test} = CPAN::Distrostatus->new("NO $but"); $self->store_persistent_state; $self->post_test(); return $self->goodbye("[dependencies] -- NA"); } $CPAN::Frontend->myprint(" $system -- OK\n"); $self->{make_test} = CPAN::Distrostatus->new("YES"); $CPAN::META->is_tested($self->{build_dir},$self->{make_test}{TIME}); # probably impossible to need the next line because badtestcnt # has a lifespan of one command delete $self->{badtestcnt}; } else { if ($but) { $but .= "; additionally test harness failed"; $CPAN::Frontend->mywarn("$but\n"); $self->{make_test} = CPAN::Distrostatus->new("NO $but"); } elsif ( $self->{force_update} ) { $self->{make_test} = CPAN::Distrostatus->new( "NO but failure ignored because 'force' in effect" ); } elsif ($CPAN::Signal) { $self->{make_test} = CPAN::Distrostatus->new("NO -- Interrupted"); } else { $self->{make_test} = CPAN::Distrostatus->new("NO"); } $self->{badtestcnt}++; $CPAN::Frontend->mywarn(" $system -- NOT OK\n"); CPAN::Shell->optprint ("hint", sprintf ("//hint// to see the cpan-testers results for installing this module, try: reports %s\n", $self->pretty_id)); } $self->store_persistent_state; $self->post_test(); return $self->{force_update} ? 1 : !! $tests_ok; } sub _make_test_illuminate_prereqs { my($self) = @_; my @prereq; # local $CPAN::DEBUG = 16; # Distribution for my $m (sort keys %{$self->{sponsored_mods}}) { next unless $self->{sponsored_mods}{$m} > 0; my $m_obj = CPAN::Shell->expand("Module",$m) or next; # XXX we need available_version which reflects # $ENV{PERL5LIB} so that already tested but not yet # installed modules are counted. my $available_version = $m_obj->available_version; my $available_file = $m_obj->available_file; if ($available_version && !CPAN::Version->vlt($available_version,$self->{prereq_pm}{$m}) ) { CPAN->debug("m[$m] good enough available_version[$available_version]") if $CPAN::DEBUG; } elsif ($available_file && ( !$self->{prereq_pm}{$m} || $self->{prereq_pm}{$m} == 0 ) ) { # lex Class::Accessor::Chained::Fast which has no $VERSION CPAN->debug("m[$m] have available_file[$available_file]") if $CPAN::DEBUG; } else { push @prereq, $m unless $self->is_locally_optional(undef, $m); } } my $but; if (@prereq) { my $cnt = @prereq; my $which = join ",", @prereq; $but = $cnt == 1 ? "one dependency not OK ($which)" : "$cnt dependencies missing ($which)"; } $but; } sub _prefs_with_expect { my($self,$where) = @_; return unless my $prefs = $self->prefs; return unless my $where_prefs = $prefs->{$where}; if ($where_prefs->{expect}) { return { mode => "deterministic", timeout => 15, talk => $where_prefs->{expect}, }; } elsif ($where_prefs->{"eexpect"}) { return $where_prefs->{"eexpect"}; } return; } #-> sub CPAN::Distribution::clean ; sub clean { my($self) = @_; my $make = $self->{modulebuild} ? "Build" : "make"; $CPAN::Frontend->myprint(sprintf "Running %s clean for %s\n", $make, $self->pretty_id); unless (exists $self->{archived}) { $CPAN::Frontend->mywarn("Distribution seems to have never been unzipped". "/untarred, nothing done\n"); return 1; } unless (exists $self->{build_dir}) { $CPAN::Frontend->mywarn("Distribution has no own directory, nothing to do.\n"); return 1; } if (exists $self->{writemakefile} and $self->{writemakefile}->failed ) { $CPAN::Frontend->mywarn("No Makefile, don't know how to 'make clean'\n"); return 1; } EXCUSE: { my @e; exists $self->{make_clean} and $self->{make_clean} eq "YES" and push @e, "make clean already called once"; $CPAN::Frontend->myprint(join "", map {" $_\n"} @e) and return if @e; } chdir "$self->{build_dir}" or Carp::confess("Couldn't chdir to $self->{build_dir}: $!"); $self->debug("Changed directory to $self->{build_dir}") if $CPAN::DEBUG; if ($^O eq 'MacOS') { Mac::BuildTools::make_clean($self); return; } my $system; if ($self->{modulebuild}) { unless (-f "Build") { my $cwd = CPAN::anycwd(); $CPAN::Frontend->mywarn("Alert: no Build file available for 'clean $self->{id}". " in cwd[$cwd]. Danger, Will Robinson!"); $CPAN::Frontend->mysleep(5); } $system = sprintf "%s clean", $self->_build_command(); } else { $system = join " ", $self->_make_command(), "clean"; } my $system_ok = system($system) == 0; $self->introduce_myself; if ( $system_ok ) { $CPAN::Frontend->myprint(" $system -- OK\n"); # $self->force; # Jost Krieger pointed out that this "force" was wrong because # it has the effect that the next "install" on this distribution # will untar everything again. Instead we should bring the # object's state back to where it is after untarring. for my $k (qw( force_update install writemakefile make make_test )) { delete $self->{$k}; } $self->{make_clean} = CPAN::Distrostatus->new("YES"); } else { # Hmmm, what to do if make clean failed? $self->{make_clean} = CPAN::Distrostatus->new("NO"); $CPAN::Frontend->mywarn(qq{ $system -- NOT OK\n}); # 2006-02-27: seems silly to me to force a make now # $self->force("make"); # so that this directory won't be used again } $self->store_persistent_state; } #-> sub CPAN::Distribution::check_disabled ; sub check_disabled { my ($self) = @_; $self->debug("checking disabled id[$self->{ID}]") if $CPAN::DEBUG; if ($self->prefs->{disabled} && ! $self->{force_update}) { return sprintf( "Disabled via prefs file '%s' doc %d", $self->{prefs_file}, $self->{prefs_file_doc}, ); } return; } #-> sub CPAN::Distribution::goto ; sub goto { my($self,$goto) = @_; $goto = $self->normalize($goto); my $why = sprintf( "Goto '$goto' via prefs file '%s' doc %d", $self->{prefs_file}, $self->{prefs_file_doc}, ); $self->{unwrapped} = CPAN::Distrostatus->new("NO $why"); # 2007-07-16 akoenig : Better than NA would be if we could inherit # the status of the $goto distro but given the exceptional nature # of 'goto' I feel reluctant to implement it my $goodbye_message = "[goto] -- NA $why"; $self->goodbye($goodbye_message); # inject into the queue CPAN::Queue->delete($self->id); CPAN::Queue->jumpqueue({qmod => $goto, reqtype => $self->{reqtype}}); # and run where we left off my($method) = (caller(1))[3]; my $goto_do = CPAN->instance("CPAN::Distribution",$goto); $goto_do->called_for($self->called_for) unless $goto_do->called_for; $goto_do->{mandatory} ||= $self->{mandatory}; $goto_do->{reqtype} ||= $self->{reqtype}; $goto_do->{coming_from} = $self->pretty_id; $goto_do->$method(); CPAN::Queue->delete_first($goto); # XXX delete_first returns undef; is that what this should return # up the call stack, eg. return $sefl->goto($goto) -- xdg, 2012-04-04 } #-> sub CPAN::Distribution::shortcut_install ; # return values: undef means don't shortcut; 0 means shortcut as fail; # and 1 means shortcut as success sub shortcut_install { my ($self) = @_; $self->debug("checking previous install results[$self->{ID}]") if $CPAN::DEBUG; if (exists $self->{install}) { my $text = UNIVERSAL::can($self->{install},"text") ? $self->{install}->text : $self->{install}; if ($text =~ /^YES/) { $CPAN::META->is_installed($self->{build_dir}); return $self->success("Already done"); } elsif ($text =~ /is only/) { # e.g. 'is only build_requires': may be overruled later return $self->goodbye($text); } else { # comment in Todo on 2006-02-11; maybe retry? return $self->goodbye("Already tried without success"); } } for my $slot ( qw/later configure_requires_later/ ) { return $self->success($self->{$slot}) if $self->{$slot}; } return undef; } #-> sub CPAN::Distribution::is_being_sponsored ; # returns true if we find a distro object in the queue that has # sponsored this one sub is_being_sponsored { my($self) = @_; my $iterator = CPAN::Queue->iterator; QITEM: while (my $q = $iterator->()) { my $s = $q->as_string; my $obj = CPAN::Shell->expandany($s) or next QITEM; my $type = ref $obj; if ( $type eq 'CPAN::Distribution' ){ for my $module (sort keys %{$obj->{sponsored_mods} || {}}) { return 1 if grep { $_ eq $module } $self->containsmods; } } } return 0; } #-> sub CPAN::Distribution::install ; sub install { my($self) = @_; $self->pre_install(); if (exists $self->{cleanup_after_install_done}) { return $self->test; } $self->debug("checking goto id[$self->{ID}]") if $CPAN::DEBUG; if (my $goto = $self->prefs->{goto}) { $self->goto($goto); $self->post_install(); return; } unless ($self->test) { $self->post_install(); return; } if ( defined( my $sc = $self->shortcut_install ) ) { $self->post_install(); return $sc; } if ($CPAN::Signal) { delete $self->{force_update}; $self->post_install(); return; } my $builddir = $self->dir or $CPAN::Frontend->mydie("PANIC: Cannot determine build directory\n"); unless (chdir $builddir) { $CPAN::Frontend->mywarn("Couldn't chdir to '$builddir': $!"); $self->post_install(); return; } $self->debug("Changed directory to $self->{build_dir}") if $CPAN::DEBUG; my $make = $self->{modulebuild} ? "Build" : "make"; $CPAN::Frontend->myprint(sprintf "Running %s install for %s\n", $make, $self->pretty_id); if ($^O eq 'MacOS') { Mac::BuildTools::make_install($self); $self->post_install(); return; } my $system; if (my $commandline = $self->prefs->{install}{commandline}) { $system = $commandline; $ENV{PERL} = CPAN::find_perl(); } elsif ($self->{modulebuild}) { my($mbuild_install_build_command) = exists $CPAN::HandleConfig::keys{mbuild_install_build_command} && $CPAN::Config->{mbuild_install_build_command} ? $CPAN::Config->{mbuild_install_build_command} : $self->_build_command(); my $install_directive = $^O eq 'VMS' ? '"install"' : 'install'; $system = sprintf("%s %s %s", $mbuild_install_build_command, $install_directive, $CPAN::Config->{mbuild_install_arg}, ); } else { my($make_install_make_command) = $self->_make_install_make_command(); $system = sprintf("%s install %s", $make_install_make_command, $CPAN::Config->{make_install_arg}, ); } my($stderr) = $^O eq "MSWin32" || $^O eq 'VMS' ? "" : " 2>&1 "; my $brip = CPAN::HandleConfig->prefs_lookup($self, q{build_requires_install_policy}); $brip ||="ask/yes"; my $id = $self->id; my $reqtype = $self->{reqtype} ||= "c"; # in doubt it was a command my $want_install = "yes"; if ($reqtype eq "b") { if ($brip eq "no") { $want_install = "no"; } elsif ($brip =~ m|^ask/(.+)|) { my $default = $1; $default = "yes" unless $default =~ /^(y|n)/i; $want_install = CPAN::Shell::colorable_makemaker_prompt ("$id is just needed temporarily during building or testing. ". "Do you want to install it permanently?", $default); } } unless ($want_install =~ /^y/i) { my $is_only = "is only 'build_requires'"; $self->{install} = CPAN::Distrostatus->new("NO -- $is_only"); delete $self->{force_update}; $self->goodbye("Not installing because $is_only"); $self->post_install(); return; } local $ENV{PERL5LIB} = defined($ENV{PERL5LIB}) ? $ENV{PERL5LIB} : ($ENV{PERLLIB} || ""); local $ENV{PERL5OPT} = defined $ENV{PERL5OPT} ? $ENV{PERL5OPT} : ""; local $ENV{PERL_USE_UNSAFE_INC} = exists $ENV{PERL_USE_UNSAFE_INC} && defined $ENV{PERL_USE_UNSAFE_INC} ? $ENV{PERL_USE_UNSAFE_INC} : 1; # install $CPAN::META->set_perl5lib; local $ENV{PERL_MM_USE_DEFAULT} = 1 if $CPAN::Config->{use_prompt_default}; local $ENV{NONINTERACTIVE_TESTING} = 1 if $CPAN::Config->{use_prompt_default}; my $install_env; if ($self->prefs->{install}) { $install_env = $self->prefs->{install}{env}; } local @ENV{keys %$install_env} = values %$install_env if $install_env; if (! $run_allow_installing_within_test) { my($allow_installing, $why) = $self->_allow_installing; if (! $allow_installing) { $CPAN::Frontend->mywarn("Installation stopped: $why\n"); $self->introduce_myself; $self->{install} = CPAN::Distrostatus->new("NO -- installation stopped due $why"); $CPAN::Frontend->mywarn(" $system -- NOT OK\n"); delete $self->{force_update}; $self->post_install(); return; } } my($pipe) = FileHandle->new("$system $stderr |"); unless ($pipe) { $CPAN::Frontend->mywarn("Can't execute $system: $!"); $self->introduce_myself; $self->{install} = CPAN::Distrostatus->new("NO"); $CPAN::Frontend->mywarn(" $system -- NOT OK\n"); delete $self->{force_update}; $self->post_install(); return; } my($makeout) = ""; while (<$pipe>) { print $_; # intentionally NOT use Frontend->myprint because it # looks irritating when we markup in color what we # just pass through from an external program $makeout .= $_; } $pipe->close; my $close_ok = $? == 0; $self->introduce_myself; if ( $close_ok ) { $CPAN::Frontend->myprint(" $system -- OK\n"); $CPAN::META->is_installed($self->{build_dir}); $self->{install} = CPAN::Distrostatus->new("YES"); if ($CPAN::Config->{'cleanup_after_install'} && ! $self->is_dot_dist && ! $self->is_being_sponsored) { my $parent = File::Spec->catdir( $self->{build_dir}, File::Spec->updir ); chdir $parent or $CPAN::Frontend->mydie("Couldn't chdir to $parent: $!\n"); File::Path::rmtree($self->{build_dir}); my $yml = "$self->{build_dir}.yml"; if (-e $yml) { unlink $yml or $CPAN::Frontend->mydie("Couldn't unlink $yml: $!\n"); } $self->{cleanup_after_install_done}=1; } } else { $self->{install} = CPAN::Distrostatus->new("NO"); $CPAN::Frontend->mywarn(" $system -- NOT OK\n"); my $mimc = CPAN::HandleConfig->prefs_lookup($self, q{make_install_make_command}); if ( $makeout =~ /permission/s && $> > 0 && ( ! $mimc || $mimc eq (CPAN::HandleConfig->prefs_lookup($self, q{make})) ) ) { $CPAN::Frontend->myprint( qq{----\n}. qq{ You may have to su }. qq{to root to install the package\n}. qq{ (Or you may want to run something like\n}. qq{ o conf make_install_make_command 'sudo make'\n}. qq{ to raise your permissions.} ); } } delete $self->{force_update}; unless ($CPAN::Config->{'cleanup_after_install'}) { $self->store_persistent_state; } $self->post_install(); return !! $close_ok; } sub blib_pm_walk { my @queue = grep { -e $_ } File::Spec->catdir("blib","lib"), File::Spec->catdir("blib","arch"); return sub { LOOP: { if (@queue) { my $file = shift @queue; if (-d $file) { my $dh; opendir $dh, $file or next; my @newfiles = map { my @ret; my $maybedir = File::Spec->catdir($file, $_); if (-d $maybedir) { unless (File::Spec->catdir("blib","arch","auto") eq $maybedir) { # prune the blib/arch/auto directory, no pm files there @ret = $maybedir; } } elsif (/\.pm$/) { my $mustbefile = File::Spec->catfile($file, $_); if (-f $mustbefile) { @ret = $mustbefile; } } @ret; } grep { $_ ne "." && $_ ne ".." } readdir $dh; push @queue, @newfiles; redo LOOP; } else { return $file; } } else { return; } } }; } sub _allow_installing { my($self) = @_; my $id = my $pretty_id = $self->pretty_id; if ($self->{CALLED_FOR}) { $id .= " (called for $self->{CALLED_FOR})"; } my $allow_down = CPAN::HandleConfig->prefs_lookup($self,q{allow_installing_module_downgrades}); $allow_down ||= "ask/yes"; my $allow_outdd = CPAN::HandleConfig->prefs_lookup($self,q{allow_installing_outdated_dists}); $allow_outdd ||= "ask/yes"; return 1 if $allow_down eq "yes" && $allow_outdd eq "yes"; if (($allow_outdd ne "yes") && ! $CPAN::META->has_inst('CPAN::DistnameInfo')) { return 1 if grep { $_ eq 'CPAN::DistnameInfo'} $self->containsmods; if ($allow_outdd ne "yes") { $CPAN::Frontend->mywarn("The current configuration of allow_installing_outdated_dists is '$allow_outdd', but for this option we would need 'CPAN::DistnameInfo' installed. Please install 'CPAN::DistnameInfo' as soon as possible. As long as we are not equipped with 'CPAN::DistnameInfo' this option does not take effect\n"); $allow_outdd = "yes"; } } return 1 if $allow_down eq "yes" && $allow_outdd eq "yes"; my($dist_version, $dist_dist); if ($allow_outdd ne "yes"){ my $dni = CPAN::DistnameInfo->new($pretty_id); $dist_version = $dni->version; $dist_dist = $dni->dist; } my $iterator = blib_pm_walk(); my(@down,@outdd); while (my $file = $iterator->()) { my $version = CPAN::Module->parse_version($file); my($volume, $directories, $pmfile) = File::Spec->splitpath( $file ); my @dirs = File::Spec->splitdir( $directories ); my(@blib_plus1) = splice @dirs, 0, 2; my($pmpath) = File::Spec->catfile(grep { length($_) } @dirs, $pmfile); unless ($allow_down eq "yes") { if (my $inst_file = $self->_file_in_path($pmpath, \@INC)) { my $inst_version = CPAN::Module->parse_version($inst_file); my $cmp = CPAN::Version->vcmp($version, $inst_version); if ($cmp) { if ($cmp < 0) { push @down, { pmpath => $pmpath, version => $version, inst_version => $inst_version }; } } if (@down) { my $why = "allow_installing_module_downgrades: $id contains downgrading module(s) (e.g. '$down[0]{pmpath}' would downgrade installed '$down[0]{inst_version}' to '$down[0]{version}')"; if (my($default) = $allow_down =~ m|^ask/(.+)|) { $default = "yes" unless $default =~ /^(y|n)/i; my $answer = CPAN::Shell::colorable_makemaker_prompt ("$why. Do you want to allow installing it?", $default, "colorize_warn"); $allow_down = $answer =~ /^\s*y/i ? "yes" : "no"; } if ($allow_down eq "no") { return (0, $why); } } } } unless ($allow_outdd eq "yes") { my @pmpath = (@dirs, $pmfile); $pmpath[-1] =~ s/\.pm$//; my $mo = CPAN::Shell->expand("Module",join "::", grep { length($_) } @pmpath); if ($mo) { my $cpan_version = $mo->cpan_version; my $is_lower = CPAN::Version->vlt($version, $cpan_version); my $other_dist; if (my $mo_dist = $mo->distribution) { $other_dist = $mo_dist->pretty_id; my $dni = CPAN::DistnameInfo->new($other_dist); if ($dni->dist eq $dist_dist){ if (CPAN::Version->vgt($dni->version, $dist_version)) { push @outdd, { pmpath => $pmpath, cpan_path => $dni->pathname, dist_version => $dni->version, dist_dist => $dni->dist, }; } } } } if (@outdd && $allow_outdd ne "yes") { my $why = "allow_installing_outdated_dists: $id contains module(s) that are indexed on the CPAN with a different distro: (e.g. '$outdd[0]{pmpath}' is indexed with '$outdd[0]{cpan_path}')"; if ($outdd[0]{dist_dist} eq $dist_dist) { $why .= ", and this has a higher distribution-version, i.e. version '$outdd[0]{dist_version}' is higher than '$dist_version')"; } if (my($default) = $allow_outdd =~ m|^ask/(.+)|) { $default = "yes" unless $default =~ /^(y|n)/i; my $answer = CPAN::Shell::colorable_makemaker_prompt ("$why. Do you want to allow installing it?", $default, "colorize_warn"); $allow_outdd = $answer =~ /^\s*y/i ? "yes" : "no"; } if ($allow_outdd eq "no") { return (0, $why); } } } } return 1; } sub _file_in_path { # similar to CPAN::Module::_file_in_path my($self,$pmpath,$incpath) = @_; my($dir,@packpath); foreach $dir (@$incpath) { my $pmfile = File::Spec->catfile($dir,$pmpath); if (-f $pmfile) { return $pmfile; } } return; } sub introduce_myself { my($self) = @_; $CPAN::Frontend->myprint(sprintf(" %s\n",$self->pretty_id)); } #-> sub CPAN::Distribution::dir ; sub dir { shift->{build_dir}; } #-> sub CPAN::Distribution::perldoc ; sub perldoc { my($self) = @_; my($dist) = $self->id; my $package = $self->called_for; if ($CPAN::META->has_inst("Pod::Perldocs")) { my($perl) = $self->perl or $CPAN::Frontend->mydie("Couldn't find executable perl\n"); my @args = ($perl, q{-MPod::Perldocs}, q{-e}, q{Pod::Perldocs->run()}, $package); my($wstatus); unless ( ($wstatus = system(@args)) == 0 ) { my $estatus = $wstatus >> 8; $CPAN::Frontend->myprint(qq{ Function system("@args") returned status $estatus (wstat $wstatus) }); } } else { $self->_display_url( $CPAN::Defaultdocs . $package ); } } #-> sub CPAN::Distribution::_check_binary ; sub _check_binary { my ($dist,$shell,$binary) = @_; my ($pid,$out); $CPAN::Frontend->myprint(qq{ + _check_binary($binary)\n}) if $CPAN::DEBUG; if ($CPAN::META->has_inst("File::Which")) { return File::Which::which($binary); } else { local *README; $pid = open README, "which $binary|" or $CPAN::Frontend->mywarn(qq{Could not fork 'which $binary': $!\n}); return unless $pid; while (<README>) { $out .= $_; } close README or $CPAN::Frontend->mywarn("Could not run 'which $binary': $!\n") and return; } $CPAN::Frontend->myprint(qq{ + $out \n}) if $CPAN::DEBUG && $out; return $out; } #-> sub CPAN::Distribution::_display_url ; sub _display_url { my($self,$url) = @_; my($res,$saved_file,$pid,$out); $CPAN::Frontend->myprint(qq{ + _display_url($url)\n}) if $CPAN::DEBUG; # should we define it in the config instead? my $html_converter = "html2text.pl"; my $web_browser = $CPAN::Config->{'lynx'} || undef; my $web_browser_out = $web_browser ? CPAN::Distribution->_check_binary($self,$web_browser) : undef; if ($web_browser_out) { # web browser found, run the action my $browser = CPAN::HandleConfig->safe_quote($CPAN::Config->{'lynx'}); $CPAN::Frontend->myprint(qq{system[$browser $url]}) if $CPAN::DEBUG; $CPAN::Frontend->myprint(qq{ Displaying URL $url with browser $browser }); $CPAN::Frontend->mysleep(1); system("$browser $url"); if ($saved_file) { 1 while unlink($saved_file) } } else { # web browser not found, let's try text only my $html_converter_out = CPAN::Distribution->_check_binary($self,$html_converter); $html_converter_out = CPAN::HandleConfig->safe_quote($html_converter_out); if ($html_converter_out ) { # html2text found, run it $saved_file = CPAN::Distribution->_getsave_url( $self, $url ); $CPAN::Frontend->mydie(qq{ERROR: problems while getting $url\n}) unless defined($saved_file); local *README; $pid = open README, "$html_converter $saved_file |" or $CPAN::Frontend->mydie(qq{ Could not fork '$html_converter $saved_file': $!}); my($fh,$filename); if ($CPAN::META->has_usable("File::Temp")) { $fh = File::Temp->new( dir => File::Spec->tmpdir, template => 'cpan_htmlconvert_XXXX', suffix => '.txt', unlink => 0, ); $filename = $fh->filename; } else { $filename = "cpan_htmlconvert_$$.txt"; $fh = FileHandle->new(); open $fh, ">$filename" or die; } while (<README>) { $fh->print($_); } close README or $CPAN::Frontend->mydie(qq{Could not run '$html_converter $saved_file': $!}); my $tmpin = $fh->filename; $CPAN::Frontend->myprint(sprintf(qq{ Run '%s %s' and saved output to %s\n}, $html_converter, $saved_file, $tmpin, )) if $CPAN::DEBUG; close $fh; local *FH; open FH, $tmpin or $CPAN::Frontend->mydie(qq{Could not open "$tmpin": $!}); my $fh_pager = FileHandle->new; local($SIG{PIPE}) = "IGNORE"; my $pager = $CPAN::Config->{'pager'} || "cat"; $fh_pager->open("|$pager") or $CPAN::Frontend->mydie(qq{ Could not open pager '$pager': $!}); $CPAN::Frontend->myprint(qq{ Displaying URL $url with pager "$pager" }); $CPAN::Frontend->mysleep(1); $fh_pager->print(<FH>); $fh_pager->close; } else { # coldn't find the web browser or html converter $CPAN::Frontend->myprint(qq{ You need to install lynx or $html_converter to use this feature.}); } } } #-> sub CPAN::Distribution::_getsave_url ; sub _getsave_url { my($dist, $shell, $url) = @_; $CPAN::Frontend->myprint(qq{ + _getsave_url($url)\n}) if $CPAN::DEBUG; my($fh,$filename); if ($CPAN::META->has_usable("File::Temp")) { $fh = File::Temp->new( dir => File::Spec->tmpdir, template => "cpan_getsave_url_XXXX", suffix => ".html", unlink => 0, ); $filename = $fh->filename; } else { $fh = FileHandle->new; $filename = "cpan_getsave_url_$$.html"; } my $tmpin = $filename; if ($CPAN::META->has_usable('LWP')) { $CPAN::Frontend->myprint("Fetching with LWP: $url "); my $Ua; CPAN::LWP::UserAgent->config; eval { $Ua = CPAN::LWP::UserAgent->new; }; if ($@) { $CPAN::Frontend->mywarn("ERROR: CPAN::LWP::UserAgent->new dies with $@\n"); return; } else { my($var); $Ua->proxy('http', $var) if $var = $CPAN::Config->{http_proxy} || $ENV{http_proxy}; $Ua->no_proxy($var) if $var = $CPAN::Config->{no_proxy} || $ENV{no_proxy}; } my $req = HTTP::Request->new(GET => $url); $req->header('Accept' => 'text/html'); my $res = $Ua->request($req); if ($res->is_success) { $CPAN::Frontend->myprint(" + request successful.\n") if $CPAN::DEBUG; print $fh $res->content; close $fh; $CPAN::Frontend->myprint(qq{ + saved content to $tmpin \n}) if $CPAN::DEBUG; return $tmpin; } else { $CPAN::Frontend->myprint(sprintf( "LWP failed with code[%s], message[%s]\n", $res->code, $res->message, )); return; } } else { $CPAN::Frontend->mywarn(" LWP not available\n"); return; } } #-> sub CPAN::Distribution::_build_command sub _build_command { my($self) = @_; if ($^O eq "MSWin32") { # special code needed at least up to # Module::Build 0.2611 and 0.2706; a fix # in M:B has been promised 2006-01-30 my($perl) = $self->perl or $CPAN::Frontend->mydie("Couldn't find executable perl\n"); return "$perl ./Build"; } elsif ($^O eq 'VMS') { return "$^X Build.com"; } return "./Build"; } #-> sub CPAN::Distribution::_should_report sub _should_report { my($self, $phase) = @_; die "_should_report() requires a 'phase' argument" if ! defined $phase; return unless $CPAN::META->has_usable("CPAN::Reporter"); # configured my $test_report = CPAN::HandleConfig->prefs_lookup($self, q{test_report}); return unless $test_report; # don't repeat if we cached a result return $self->{should_report} if exists $self->{should_report}; # don't report if we generated a Makefile.PL if ( $self->{had_no_makefile_pl} ) { $CPAN::Frontend->mywarn( "Will not send CPAN Testers report with generated Makefile.PL.\n" ); return $self->{should_report} = 0; } # available if ( ! $CPAN::META->has_inst("CPAN::Reporter")) { $CPAN::Frontend->mywarnonce( "CPAN::Reporter not installed. No reports will be sent.\n" ); return $self->{should_report} = 0; } # capable my $crv = CPAN::Reporter->VERSION; if ( CPAN::Version->vlt( $crv, 0.99 ) ) { # don't cache $self->{should_report} -- need to check each phase if ( $phase eq 'test' ) { return 1; } else { $CPAN::Frontend->mywarn( "Reporting on the '$phase' phase requires CPAN::Reporter 0.99, but \n" . "you only have version $crv\. Only 'test' phase reports will be sent.\n" ); return; } } # appropriate if ($self->is_dot_dist) { $CPAN::Frontend->mywarn("Reporting via CPAN::Reporter is disabled ". "for local directories\n"); return $self->{should_report} = 0; } if ($self->prefs->{patches} && @{$self->prefs->{patches}} && $self->{patched} ) { $CPAN::Frontend->mywarn("Reporting via CPAN::Reporter is disabled ". "when the source has been patched\n"); return $self->{should_report} = 0; } # proceed and cache success return $self->{should_report} = 1; } #-> sub CPAN::Distribution::reports sub reports { my($self) = @_; my $pathname = $self->id; $CPAN::Frontend->myprint("Distribution: $pathname\n"); unless ($CPAN::META->has_inst("CPAN::DistnameInfo")) { $CPAN::Frontend->mydie("CPAN::DistnameInfo not installed; cannot continue"); } unless ($CPAN::META->has_usable("LWP")) { $CPAN::Frontend->mydie("LWP not installed; cannot continue"); } unless ($CPAN::META->has_usable("File::Temp")) { $CPAN::Frontend->mydie("File::Temp not installed; cannot continue"); } my $format; if ($CPAN::META->has_inst("YAML::XS") || $CPAN::META->has_inst("YAML::Syck")){ $format = 'yaml'; } elsif (!$format && $CPAN::META->has_inst("JSON::PP") ) { $format = 'json'; } else { $CPAN::Frontend->mydie("JSON::PP not installed, cannot continue"); } my $d = CPAN::DistnameInfo->new($pathname); my $dist = $d->dist; # "CPAN-DistnameInfo" my $version = $d->version; # "0.02" my $maturity = $d->maturity; # "released" my $filename = $d->filename; # "CPAN-DistnameInfo-0.02.tar.gz" my $cpanid = $d->cpanid; # "GBARR" my $distvname = $d->distvname; # "CPAN-DistnameInfo-0.02" my $url = sprintf "http://www.cpantesters.org/show/%s.%s", $dist, $format; CPAN::LWP::UserAgent->config; my $Ua; eval { $Ua = CPAN::LWP::UserAgent->new; }; if ($@) { $CPAN::Frontend->mydie("CPAN::LWP::UserAgent->new dies with $@\n"); } $CPAN::Frontend->myprint("Fetching '$url'..."); 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 $unserialized; if ( $format eq 'yaml' ) { my $yaml = $resp->content; # what a long way round! my $fh = File::Temp->new( dir => File::Spec->tmpdir, template => 'cpan_reports_XXXX', suffix => '.yaml', unlink => 0, ); my $tfilename = $fh->filename; print $fh $yaml; close $fh or $CPAN::Frontend->mydie("Could not close '$tfilename': $!"); $unserialized = CPAN->_yaml_loadfile($tfilename)->[0]; unlink $tfilename or $CPAN::Frontend->mydie("Could not unlink '$tfilename': $!"); } else { require JSON::PP; $unserialized = JSON::PP->new->utf8->decode($resp->content); } my %other_versions; my $this_version_seen; for my $rep (@$unserialized) { my $rversion = $rep->{version}; if ($rversion eq $version) { unless ($this_version_seen++) { $CPAN::Frontend->myprint ("$rep->{version}:\n"); } my $arch = $rep->{archname} || $rep->{platform} || '????'; my $grade = $rep->{action} || $rep->{status} || '????'; my $ostext = $rep->{ostext} || ucfirst($rep->{osname}) || '????'; $CPAN::Frontend->myprint (sprintf("%1s%1s%-4s %s on %s %s (%s)\n", $arch eq $Config::Config{archname}?"*":"", $grade eq "PASS"?"+":$grade eq"FAIL"?"-":"", $grade, $rep->{perl}, $ostext, $rep->{osvers}, $arch, )); } else { $other_versions{$rep->{version}}++; } } unless ($this_version_seen) { $CPAN::Frontend->myprint("No reports found for version '$version' Reports for other versions:\n"); for my $v (sort keys %other_versions) { $CPAN::Frontend->myprint(" $v\: $other_versions{$v}\n"); } } $url = substr($url,0,-4) . 'html'; $CPAN::Frontend->myprint("See $url for details\n"); } 1; PK�������!�z¤!��!����perl5/CPAN/Plugin/Specfile.pmnu�6$��������=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<WARNING:> 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<dir> as in o conf plugin_list push CPAN::Plugin::Specfile=dir,/tmp/specfiles-000042 The default directory for this is the C<plugins/CPAN::Plugin::Specfile> directory in the I<cpan_home> directory. =head1 AUTHOR Andreas Koenig <andk@cpan.org>, Branislav Zahradnik <barney@cpan.org> =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, <<EOF; %files %defattr(-,root,root,-) %doc @doc %{perl_$vendorlib}/* %{_mandir}/man3/*.3* $exe_stanza %changelog * $date <specfile\@specfile.cpan.org> - $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; PK�������!�M :.��.����perl5/CPAN/Distroprefs.pmnu�6$��������# -*- 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 = <FH>; 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<keys %$match> 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<Distroprefs|CPAN> 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<m/^[._]/> 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<undef>, indicating that no prefs files remain to be found =back =head1 RESULTS L<C<find()>|/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<success>, C<warning>, or C<fatal> =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<success> results from C<find()>. =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<CPAN>) currently: C<env perl perlconfig distribution module> =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<match_attributes> (above) =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!�n3y.��.����perl5/CPAN/Version.pmnu�6$��������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: PK�������!�7=$�$���perl5/CPAN/FirstTime.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: package CPAN::FirstTime; use strict; use ExtUtils::MakeMaker (); use FileHandle (); use File::Basename (); use File::Path (); use File::Spec (); use CPAN::Mirrors (); use CPAN::Version (); use vars qw($VERSION $auto_config); $VERSION = "5.5317"; =head1 NAME CPAN::FirstTime - Utility for CPAN::Config file Initialization =head1 SYNOPSIS CPAN::FirstTime::init() =head1 DESCRIPTION The init routine asks a few questions and writes a CPAN/Config.pm or CPAN/MyConfig.pm file (depending on what it is currently using). In the following all questions and explanations regarding config variables are collected. =cut # down until the next =back the manpage must be parsed by the program # because the text is used in the init dialogues. my @podpara = split /\n\n/, <<'=back'; =over 2 =item allow_installing_module_downgrades The CPAN shell can watch the C<blib/> directories that are built up before running C<make test> to determine whether the current distribution will end up with modules being overwritten with decreasing module version numbers. It can then let the build of this distro fail when it discovers a downgrade. Do you want to allow installing distros with decreasing module versions compared to what you have installed (yes, no, ask/yes, ask/no)? =item allow_installing_outdated_dists The CPAN shell can watch the C<blib/> directories that are built up before running C<make test> to determine whether the current distribution contains modules that are indexed with a distro with a higher distro-version number than the current one. It can then let the build of this distro fail when it would not represent the most up-to-date version of the distro. Note: choosing anything but 'yes' for this option will need CPAN::DistnameInfo being installed for taking effect. Do you want to allow installing distros that are not indexed as the highest distro-version for all contained modules (yes, no, ask/yes, ask/no)? =item auto_commit Normally CPAN.pm keeps config variables in memory and changes need to be saved in a separate 'o conf commit' command to make them permanent between sessions. If you set the 'auto_commit' option to true, changes to a config variable are always automatically committed to disk. Always commit changes to config variables to disk? =item build_cache CPAN.pm can limit the size of the disk area for keeping the build directories with all the intermediate files. Cache size for build directory (in MB)? =item build_dir Directory where the build process takes place? =item build_dir_reuse Until version 1.88 CPAN.pm never trusted the contents of the build_dir directory between sessions. Since 1.88_58 CPAN.pm has a YAML-based mechanism that makes it possible to share the contents of the build_dir/ directory between different sessions with the same version of perl. People who prefer to test things several days before installing will like this feature because it saves a lot of time. If you say yes to the following question, CPAN will try to store enough information about the build process so that it can pick up in future sessions at the same state of affairs as it left a previous session. Store and re-use state information about distributions between CPAN.pm sessions? =item build_requires_install_policy When a module declares another one as a 'build_requires' prerequisite this means that the other module is only needed for building or testing the module but need not be installed permanently. In this case you may wish to install that other module nonetheless or just keep it in the 'build_dir' directory to have it available only temporarily. Installing saves time on future installations but makes the perl installation bigger. You can choose if you want to always install (yes), never install (no) or be always asked. In the latter case you can set the default answer for the question to yes (ask/yes) or no (ask/no). Policy on installing 'build_requires' modules (yes, no, ask/yes, ask/no)? =item cache_metadata To considerably speed up the initial CPAN shell startup, it is possible to use Storable to create a cache of metadata. If Storable is not available, the normal index mechanism will be used. Note: this mechanism is not used when use_sqlite is on and SQLite is running. Cache metadata (yes/no)? =item check_sigs CPAN packages can be digitally signed by authors and thus verified with the security provided by strong cryptography. The exact mechanism is defined in the Module::Signature module. While this is generally considered a good thing, it is not always convenient to the end user to install modules that are signed incorrectly or where the key of the author is not available or where some prerequisite for Module::Signature has a bug and so on. With the check_sigs parameter you can turn signature checking on and off. The default is off for now because the whole tool chain for the functionality is not yet considered mature by some. The author of CPAN.pm would recommend setting it to true most of the time and turning it off only if it turns out to be annoying. Note that if you do not have Module::Signature installed, no signature checks will be performed at all. Always try to check and verify signatures if a SIGNATURE file is in the package and Module::Signature is installed (yes/no)? =item cleanup_after_install Users who install modules and do not intend to look back, can free occupied disk space quickly by letting CPAN.pm cleanup each build directory immediately after a successful install. Remove build directory after a successful install? (yes/no)? =item colorize_output When you have Term::ANSIColor installed, you can turn on colorized output to have some visual differences between normal CPAN.pm output, warnings, debugging output, and the output of the modules being installed. Set your favorite colors after some experimenting with the Term::ANSIColor module. Please note that on Windows platforms colorized output also requires the Win32::Console::ANSI module. Do you want to turn on colored output? =item colorize_print Color for normal output? =item colorize_warn Color for warnings? =item colorize_debug Color for debugging messages? =item commandnumber_in_prompt The prompt of the cpan shell can contain the current command number for easier tracking of the session or be a plain string. Do you want the command number in the prompt (yes/no)? =item connect_to_internet_ok If you have never defined your own C<urllist> in your configuration then C<CPAN.pm> will be hesitant to use the built in default sites for downloading. It will ask you once per session if a connection to the internet is OK and only if you say yes, it will try to connect. But to avoid this question, you can choose your favorite download sites once and get away with it. Or, if you have no favorite download sites answer yes to the following question. If no urllist has been chosen yet, would you prefer CPAN.pm to connect to the built-in default sites without asking? (yes/no)? =item ftp_passive Shall we always set the FTP_PASSIVE environment variable when dealing with ftp download (yes/no)? =item ftpstats_period Statistics about downloads are truncated by size and period simultaneously. How many days shall we keep statistics about downloads? =item ftpstats_size Statistics about downloads are truncated by size and period simultaneously. Setting this to zero or negative disables download statistics. How many items shall we keep in the statistics about downloads? =item getcwd CPAN.pm changes the current working directory often and needs to determine its own current working directory. Per default it uses Cwd::cwd but if this doesn't work on your system for some reason, alternatives can be configured according to the following table: cwd Cwd::cwd getcwd Cwd::getcwd fastcwd Cwd::fastcwd getdcwd Cwd::getdcwd backtickcwd external command cwd Preferred method for determining the current working directory? =item halt_on_failure Normally, CPAN.pm continues processing the full list of targets and dependencies, even if one of them fails. However, you can specify that CPAN should halt after the first failure. (Note that optional recommended or suggested modules that fail will not cause a halt.) Do you want to halt on failure (yes/no)? =item histfile If you have one of the readline packages (Term::ReadLine::Perl, Term::ReadLine::Gnu, possibly others) installed, the interactive CPAN shell will have history support. The next two questions deal with the filename of the history file and with its size. If you do not want to set this variable, please hit SPACE ENTER to the following question. File to save your history? =item histsize Number of lines to save? =item inactivity_timeout Sometimes you may wish to leave the processes run by CPAN alone without caring about them. Because the Makefile.PL or the Build.PL sometimes contains question you're expected to answer, you can set a timer that will kill a 'perl Makefile.PL' process after the specified time in seconds. If you set this value to 0, these processes will wait forever. This is the default and recommended setting. Timeout for inactivity during {Makefile,Build}.PL? =item index_expire The CPAN indexes are usually rebuilt once or twice per hour, but the typical CPAN mirror mirrors only once or twice per day. Depending on the quality of your mirror and your desire to be on the bleeding edge, you may want to set the following value to more or less than one day (which is the default). It determines after how many days CPAN.pm downloads new indexes. Let the index expire after how many days? =item inhibit_startup_message When the CPAN shell is started it normally displays a greeting message that contains the running version and the status of readline support. Do you want to turn this message off? =item keep_source_where Unless you are accessing the CPAN on your filesystem via a file: URL, CPAN.pm needs to keep the source files it downloads somewhere. Please supply a directory where the downloaded files are to be kept. Download target directory? =item load_module_verbosity When CPAN.pm loads a module it needs for some optional feature, it usually reports about module name and version. Choose 'v' to get this message, 'none' to suppress it. Verbosity level for loading modules (none or v)? =item makepl_arg Every Makefile.PL is run by perl in a separate process. Likewise we run 'make' and 'make install' in separate processes. If you have any parameters (e.g. PREFIX, UNINST or the like) you want to pass to the calls, please specify them here. If you don't understand this question, just press ENTER. Typical frequently used settings: PREFIX=~/perl # non-root users (please see manual for more hints) Parameters for the 'perl Makefile.PL' command? =item make_arg Parameters for the 'make' command? Typical frequently used setting: -j3 # dual processor system (on GNU make) Your choice: =item make_install_arg Parameters for the 'make install' command? Typical frequently used setting: UNINST=1 # to always uninstall potentially conflicting files # (but do NOT use with local::lib or INSTALL_BASE) Your choice: =item make_install_make_command Do you want to use a different make command for 'make install'? Cautious people will probably prefer: su root -c make or sudo make or /path1/to/sudo -u admin_account /path2/to/make or some such. Your choice: =item mbuildpl_arg A Build.PL is run by perl in a separate process. Likewise we run './Build' and './Build install' in separate processes. If you have any parameters you want to pass to the calls, please specify them here. Typical frequently used settings: --install_base /home/xxx # different installation directory Parameters for the 'perl Build.PL' command? =item mbuild_arg Parameters for the './Build' command? Setting might be: --extra_linker_flags -L/usr/foo/lib # non-standard library location Your choice: =item mbuild_install_arg Parameters for the './Build install' command? Typical frequently used setting: --uninst 1 # uninstall conflicting files # (but do NOT use with local::lib or INSTALL_BASE) Your choice: =item mbuild_install_build_command Do you want to use a different command for './Build install'? Sudo users will probably prefer: su root -c ./Build or sudo ./Build or /path1/to/sudo -u admin_account ./Build or some such. Your choice: =item pager What is your favorite pager program? =item prefer_installer When you have Module::Build installed and a module comes with both a Makefile.PL and a Build.PL, which shall have precedence? The main two standard installer modules are the old and well established ExtUtils::MakeMaker (for short: EUMM) which uses the Makefile.PL. And the next generation installer Module::Build (MB) which works with the Build.PL (and often comes with a Makefile.PL too). If a module comes only with one of the two we will use that one but if both are supplied then a decision must be made between EUMM and MB. See also http://rt.cpan.org/Ticket/Display.html?id=29235 for a discussion about the right default. Or, as a third option you can choose RAND which will make a random decision (something regular CPAN testers will enjoy). In case you can choose between running a Makefile.PL or a Build.PL, which installer would you prefer (EUMM or MB or RAND)? =item prefs_dir CPAN.pm can store customized build environments based on regular expressions for distribution names. These are YAML files where the default options for CPAN.pm and the environment can be overridden and dialog sequences can be stored that can later be executed by an Expect.pm object. The CPAN.pm distribution comes with some prefab YAML files that cover sample distributions that can be used as blueprints to store your own prefs. Please check out the distroprefs/ directory of the CPAN.pm distribution to get a quick start into the prefs system. Directory where to store default options/environment/dialogs for building modules that need some customization? =item prerequisites_policy The CPAN module can detect when a module which you are trying to build depends on prerequisites. If this happens, it can build the prerequisites for you automatically ('follow'), ask you for confirmation ('ask'), or just ignore them ('ignore'). Choosing 'follow' also sets PERL_AUTOINSTALL and PERL_EXTUTILS_AUTOINSTALL for "--defaultdeps" if not already set. Please set your policy to one of the three values. Policy on building prerequisites (follow, ask or ignore)? =item pushy_https Boolean. Defaults to true. If this option is true, the cpan shell will use https://cpan.org/ to download stuff from the CPAN. It will fall back to http://cpan.org/ if it can't handle https for some reason (missing modules, missing programs). Whenever it falls back to the http protocol, it will issue a warning. If this option is true, the option C<urllist> will be ignored. Consequently, if you want to work with local mirrors via your own configured list of URLs, you will have to choose no below. Do you want to turn the pushy_https behaviour on? =item randomize_urllist CPAN.pm can introduce some randomness when using hosts for download that are configured in the urllist parameter. Enter a numeric value between 0 and 1 to indicate how often you want to let CPAN.pm try a random host from the urllist. A value of one specifies to always use a random host as the first try. A value of zero means no randomness at all. Anything in between specifies how often, on average, a random host should be tried first. Randomize parameter =item recommends_policy (Experimental feature!) Some CPAN modules recommend additional, optional dependencies. These should generally be installed except in resource constrained environments. When this policy is true, recommended modules will be included with required modules. Include recommended modules? =item scan_cache By default, each time the CPAN module is started, cache scanning is performed to keep the cache size in sync ('atstart'). Alternatively, scanning and cleanup can happen when CPAN exits ('atexit'). To prevent any cache cleanup, answer 'never'. Perform cache scanning ('atstart', 'atexit' or 'never')? =item shell What is your favorite shell? =item show_unparsable_versions During the 'r' command CPAN.pm finds modules without version number. When the command finishes, it prints a report about this. If you want this report to be very verbose, say yes to the following variable. Show all individual modules that have no $VERSION? =item show_upload_date The 'd' and the 'm' command normally only show you information they have in their in-memory database and thus will never connect to the internet. If you set the 'show_upload_date' variable to true, 'm' and 'd' will additionally show you the upload date of the module or distribution. Per default this feature is off because it may require a net connection to get at the upload date. Always try to show upload date with 'd' and 'm' command (yes/no)? =item show_zero_versions During the 'r' command CPAN.pm finds modules with a version number of zero. When the command finishes, it prints a report about this. If you want this report to be very verbose, say yes to the following variable. Show all individual modules that have a $VERSION of zero? =item suggests_policy (Experimental feature!) Some CPAN modules suggest additional, optional dependencies. These 'suggest' dependencies provide enhanced operation. When this policy is true, suggested modules will be included with required modules. Include suggested modules? =item tar_verbosity When CPAN.pm uses the tar command, which switch for the verbosity shall be used? Choose 'none' for quiet operation, 'v' for file name listing, 'vv' for full listing. Tar command verbosity level (none or v or vv)? =item term_is_latin The next option deals with the charset (a.k.a. character set) your terminal supports. In general, CPAN is English speaking territory, so the charset does not matter much but some CPAN have names that are outside the ASCII range. If your terminal supports UTF-8, you should say no to the next question. If it expects ISO-8859-1 (also known as LATIN1) then you should say yes. If it supports neither, your answer does not matter because you will not be able to read the names of some authors anyway. If you answer no, names will be output in UTF-8. Your terminal expects ISO-8859-1 (yes/no)? =item term_ornaments When using Term::ReadLine, you can turn ornaments on so that your input stands out against the output from CPAN.pm. Do you want to turn ornaments on? =item test_report The goal of the CPAN Testers project (http://testers.cpan.org/) is to test as many CPAN packages as possible on as many platforms as possible. This provides valuable feedback to module authors and potential users to identify bugs or platform compatibility issues and improves the overall quality and value of CPAN. One way you can contribute is to send test results for each module that you install. If you install the CPAN::Reporter module, you have the option to automatically generate and deliver test reports to CPAN Testers whenever you run tests on a CPAN package. See the CPAN::Reporter documentation for additional details and configuration settings. If your firewall blocks outgoing traffic, you may need to configure CPAN::Reporter before sending reports. Generate test reports if CPAN::Reporter is installed (yes/no)? =item perl5lib_verbosity When CPAN.pm extends @INC via PERL5LIB, it prints a list of directories added (or a summary of how many directories are added). Choose 'v' to get this message, 'none' to suppress it. Verbosity level for PERL5LIB changes (none or v)? =item prefer_external_tar Per default all untar operations are done with the perl module Archive::Tar; by setting this variable to true the external tar command is used if available; on Unix this is usually preferred because they have a reliable and fast gnutar implementation. Use the external tar program instead of Archive::Tar? =item trust_test_report_history When a distribution has already been tested by CPAN::Reporter on this machine, CPAN can skip the test phase and just rely on the test report history instead. Note that this will not apply to distributions that failed tests because of missing dependencies. Also, tests can be run regardless of the history using "force". Do you want to rely on the test report history (yes/no)? =item urllist_ping_external When automatic selection of the nearest cpan mirrors is performed, turn on the use of the external ping via Net::Ping::External. This is recommended in the case the local network has a transparent proxy. Do you want to use the external ping command when autoselecting mirrors? =item urllist_ping_verbose When automatic selection of the nearest cpan mirrors is performed, this option can be used to turn on verbosity during the selection process. Do you want to see verbosity turned on when autoselecting mirrors? =item use_prompt_default When this is true, CPAN will set PERL_MM_USE_DEFAULT to a true value. This causes ExtUtils::MakeMaker (and compatible) prompts to use default values instead of stopping to prompt you to answer questions. It also sets NONINTERACTIVE_TESTING to a true value to signal more generally that distributions should not try to interact with you. Do you want to use prompt defaults (yes/no)? =item use_sqlite CPAN::SQLite is a layer between the index files that are downloaded from the CPAN and CPAN.pm that speeds up metadata queries and reduces memory consumption of CPAN.pm considerably. Use CPAN::SQLite if available? (yes/no)? =item version_timeout This timeout prevents CPAN from hanging when trying to parse a pathologically coded $VERSION from a module. The default is 15 seconds. If you set this value to 0, no timeout will occur, but this is not recommended. Timeout for parsing module versions? =item yaml_load_code Both YAML.pm and YAML::Syck are capable of deserialising code. As this requires a string eval, which might be a security risk, you can use this option to enable or disable the deserialisation of code via CPAN::DeferredCode. (Note: This does not work under perl 5.6) Do you want to enable code deserialisation (yes/no)? =item yaml_module At the time of this writing (2009-03) there are three YAML implementations working: YAML, YAML::Syck, and YAML::XS. The latter two are faster but need a C compiler installed on your system. There may be more alternative YAML conforming modules. When I tried two other players, YAML::Tiny and YAML::Perl, they seemed not powerful enough to work with CPAN.pm. This may have changed in the meantime. Which YAML implementation would you prefer? =back =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut use vars qw( %prompts ); { my @prompts = ( auto_config => qq{ CPAN.pm requires configuration, but most of it can be done automatically. If you answer 'no' below, you will enter an interactive dialog for each configuration option instead. Would you like to configure as much as possible automatically?}, auto_pick => qq{ Would you like me to automatically choose some CPAN mirror sites for you? (This means connecting to the Internet)}, config_intro => qq{ The following questions are intended to help you with the configuration. The CPAN module needs a directory of its own to cache important index files and maybe keep a temporary mirror of CPAN files. This may be a site-wide or a personal directory. }, # cpan_home => qq{ }, cpan_home_where => qq{ First of all, I'd like to create this directory. Where? }, external_progs => qq{ The CPAN module will need a few external programs to work properly. Please correct me, if I guess the wrong path for a program. Don't panic if you do not have some of them, just press ENTER for those. To disable the use of a program, you can type a space followed by ENTER. }, proxy_intro => qq{ If you're accessing the net via proxies, you can specify them in the CPAN configuration or via environment variables. The variable in the \$CPAN::Config takes precedence. }, proxy_user => qq{ If your proxy is an authenticating proxy, you can store your username permanently. If you do not want that, just press ENTER. You will then be asked for your username in every future session. }, proxy_pass => qq{ Your password for the authenticating proxy can also be stored permanently on disk. If this violates your security policy, just press ENTER. You will then be asked for the password in every future session. }, urls_intro => qq{ Now you need to choose your CPAN mirror sites. You can let me pick mirrors for you, you can select them from a list or you can enter them by hand. }, urls_picker_intro => qq{First, pick a nearby continent and country by typing in the number(s) in front of the item(s) you want to select. You can pick several of each, separated by spaces. Then, you will be presented with a list of URLs of CPAN mirrors in the countries you selected, along with previously selected URLs. Select some of those URLs, or just keep the old list. Finally, you will be prompted for any extra URLs -- file:, ftp:, or http: -- that host a CPAN mirror. You should select more than one (just in case the first isn't available). }, password_warn => qq{ Warning: Term::ReadKey seems not to be available, your password will be echoed to the terminal! }, install_help => qq{ Warning: You do not have write permission for Perl library directories. To install modules, you need to configure a local Perl library directory or escalate your privileges. CPAN can help you by bootstrapping the local::lib module or by configuring itself to use 'sudo' (if available). You may also resolve this problem manually if you need to customize your setup. What approach do you want? (Choose 'local::lib', 'sudo' or 'manual') }, local_lib_installed => qq{ local::lib is installed. You must now add the following environment variables to your shell configuration files (or registry, if you are on Windows) and then restart your command line shell and CPAN before installing modules: }, ); die "Coding error in \@prompts declaration. Odd number of elements, above" if (@prompts % 2); %prompts = @prompts; if (scalar(keys %prompts) != scalar(@prompts)/2) { my %already; for my $item (0..$#prompts) { next if $item % 2; die "$prompts[$item] is duplicated\n" if $already{$prompts[$item]}++; } } shift @podpara; while (@podpara) { warn "Alert: cannot parse my own manpage for init dialog" unless $podpara[0] =~ s/^=item\s+//; my $name = shift @podpara; my @para; while (@podpara && $podpara[0] !~ /^=item/) { push @para, shift @podpara; } $prompts{$name} = pop @para; if (@para) { $prompts{$name . "_intro"} = join "", map { "$_\n\n" } @para; } } } sub init { my($configpm, %args) = @_; use Config; # extra args after 'o conf init' my $matcher = $args{args} && @{$args{args}} ? $args{args}[0] : ''; if ($matcher =~ /^\/(.*)\/$/) { # case /regex/ => take the first, ignore the rest $matcher = $1; shift @{$args{args}}; if (@{$args{args}}) { local $" = " "; $CPAN::Frontend->mywarn("Ignoring excessive arguments '@{$args{args}}'"); $CPAN::Frontend->mysleep(2); } } elsif (0 == length $matcher) { } elsif (0 && $matcher eq "~") { # extremely buggy, but a nice idea my @unconfigured = sort grep { not exists $CPAN::Config->{$_} or not defined $CPAN::Config->{$_} or not length $CPAN::Config->{$_} } keys %$CPAN::Config; $matcher = "\\b(".join("|", @unconfigured).")\\b"; $CPAN::Frontend->mywarn("matcher[$matcher]"); } else { # case WORD... => all arguments must be valid for my $arg (@{$args{args}}) { unless (exists $CPAN::HandleConfig::keys{$arg}) { $CPAN::Frontend->mywarn("'$arg' is not a valid configuration variable\n"); return; } } $matcher = "\\b(".join("|",@{$args{args}}).")\\b"; } CPAN->debug("matcher[$matcher]") if $CPAN::DEBUG; unless ($CPAN::VERSION) { require CPAN::Nox; } require CPAN::HandleConfig; CPAN::HandleConfig::require_myconfig_or_config(); $CPAN::Config ||= {}; local($/) = "\n"; local($\) = ""; local($|) = 1; my($ans,$default); # why so half global? # #= Files, directories # local *_real_prompt; if ( $args{autoconfig} ) { $auto_config = 1; } elsif ($matcher) { $auto_config = 0; } else { my $_conf = prompt($prompts{auto_config}, "yes"); $auto_config = ($_conf and $_conf =~ /^y/i) ? 1 : 0; } CPAN->debug("auto_config[$auto_config]") if $CPAN::DEBUG; if ( $auto_config ) { local $^W = 0; # prototype should match that of &MakeMaker::prompt my $current_second = time; my $current_second_count = 0; my $i_am_mad = 0; # silent prompting -- just quietly use default *_real_prompt = sub { return $_[1] }; } # # bootstrap local::lib or sudo # unless ( $matcher || _can_write_to_libdirs() || _using_installbase() || _using_sudo() ) { local $auto_config = 0; # We *must* ask, even under autoconfig local *_real_prompt; # We *must* show prompt my_prompt_loop(install_help => 'local::lib', $matcher, 'local::lib|sudo|manual'); } $CPAN::Config->{install_help} ||= ''; # Temporary to suppress warnings if (!$matcher or q{ build_dir build_dir_reuse cpan_home keep_source_where prefs_dir } =~ /$matcher/) { $CPAN::Frontend->myprint($prompts{config_intro}) unless $auto_config; init_cpan_home($matcher); my_dflt_prompt("keep_source_where", File::Spec->catdir($CPAN::Config->{cpan_home},"sources"), $matcher, ); my_dflt_prompt("build_dir", File::Spec->catdir($CPAN::Config->{cpan_home},"build"), $matcher ); my_yn_prompt(build_dir_reuse => 0, $matcher); my_dflt_prompt("prefs_dir", File::Spec->catdir($CPAN::Config->{cpan_home},"prefs"), $matcher ); } # #= Config: auto_commit # my_yn_prompt(auto_commit => 0, $matcher); # #= Cache size, Index expire # my_dflt_prompt(build_cache => 100, $matcher); my_dflt_prompt(index_expire => 1, $matcher); my_prompt_loop(scan_cache => 'atstart', $matcher, 'atstart|atexit|never'); my_yn_prompt(cleanup_after_install => 0, $matcher); # #= cache_metadata # my_yn_prompt(cache_metadata => 1, $matcher); my_yn_prompt(use_sqlite => 0, $matcher); # #= Do we follow PREREQ_PM? # my_prompt_loop(prerequisites_policy => 'follow', $matcher, 'follow|ask|ignore'); my_prompt_loop(build_requires_install_policy => 'yes', $matcher, 'yes|no|ask/yes|ask/no'); my_yn_prompt(recommends_policy => 1, $matcher); my_yn_prompt(suggests_policy => 0, $matcher); # #= Module::Signature # my_yn_prompt(check_sigs => 0, $matcher); # #= CPAN::Reporter # if (!$matcher or 'test_report' =~ /$matcher/) { my_yn_prompt(test_report => 0, $matcher); if ( $matcher && $CPAN::Config->{test_report} && $CPAN::META->has_inst("CPAN::Reporter") && CPAN::Reporter->can('configure') ) { my $_conf = prompt("Would you like me configure CPAN::Reporter now?", "yes"); if ($_conf =~ /^y/i) { $CPAN::Frontend->myprint("\nProceeding to configure CPAN::Reporter.\n"); CPAN::Reporter::configure(); $CPAN::Frontend->myprint("\nReturning to CPAN configuration.\n"); } } } my_yn_prompt(trust_test_report_history => 0, $matcher); # #= YAML vs. YAML::Syck # if (!$matcher or "yaml_module" =~ /$matcher/) { my_dflt_prompt(yaml_module => "YAML", $matcher); my $old_v = $CPAN::Config->{load_module_verbosity}; $CPAN::Config->{load_module_verbosity} = q[none]; if (!$auto_config && !$CPAN::META->has_inst($CPAN::Config->{yaml_module})) { $CPAN::Frontend->mywarn ("Warning (maybe harmless): '$CPAN::Config->{yaml_module}' not installed.\n"); $CPAN::Frontend->mysleep(3); } $CPAN::Config->{load_module_verbosity} = $old_v; } # #= YAML code deserialisation # my_yn_prompt(yaml_load_code => 0, $matcher); # #= External programs # my(@path) = split /$Config{'path_sep'}/, $ENV{'PATH'}; $CPAN::Frontend->myprint($prompts{external_progs}) if !$matcher && !$auto_config; _init_external_progs($matcher, { path => \@path, progs => [ qw/make bzip2 gzip tar unzip gpg patch applypatch/ ], shortcut => 0 }); _init_external_progs($matcher, { path => \@path, progs => [ qw/wget curl lynx ncftpget ncftp ftp/ ], shortcut => 1 }); { my $path = $CPAN::Config->{'pager'} || $ENV{PAGER} || find_exe("less",\@path) || find_exe("more",\@path) || ($^O eq 'MacOS' ? $ENV{EDITOR} : 0 ) || "more"; my_dflt_prompt(pager => $path, $matcher); } { my $path = $CPAN::Config->{'shell'}; if ($path && File::Spec->file_name_is_absolute($path)) { $CPAN::Frontend->mywarn("Warning: configured $path does not exist\n") unless -e $path; $path = ""; } $path ||= $ENV{SHELL}; $path ||= $ENV{COMSPEC} if $^O eq "MSWin32"; if ($^O eq 'MacOS') { $CPAN::Config->{'shell'} = 'not_here'; } else { $path ||= 'sh', $path =~ s,\\,/,g if $^O eq 'os2'; # Cosmetic only my_dflt_prompt(shell => $path, $matcher); } } { my $tar = $CPAN::Config->{tar}; my $prefer_external_tar = $CPAN::Config->{prefer_external_tar}; # XXX not yet supported unless (defined $prefer_external_tar) { if ($^O =~ /(MSWin32|solaris)/) { # both have a record of broken tars $prefer_external_tar = 0; } elsif ($tar) { $prefer_external_tar = 1; } else { $prefer_external_tar = 0; } } my_yn_prompt(prefer_external_tar => $prefer_external_tar, $matcher); } # # verbosity # my_prompt_loop(tar_verbosity => 'none', $matcher, 'none|v|vv'); my_prompt_loop(load_module_verbosity => 'none', $matcher, 'none|v'); my_prompt_loop(perl5lib_verbosity => 'none', $matcher, 'none|v'); my_yn_prompt(inhibit_startup_message => 0, $matcher); # #= Installer, arguments to make etc. # my_prompt_loop(prefer_installer => 'MB', $matcher, 'MB|EUMM|RAND'); if (!$matcher or 'makepl_arg make_arg' =~ /$matcher/) { my_dflt_prompt(makepl_arg => "", $matcher); my_dflt_prompt(make_arg => "", $matcher); if ( $CPAN::Config->{makepl_arg} =~ /LIBS=|INC=/ ) { $CPAN::Frontend->mywarn( "Warning: Using LIBS or INC in makepl_arg will likely break distributions\n" . "that specify their own LIBS or INC options in Makefile.PL.\n" ); } } require CPAN::HandleConfig; if (exists $CPAN::HandleConfig::keys{make_install_make_command}) { # as long as Windows needs $self->_build_command, we cannot # support sudo on windows :-) my $default = $CPAN::Config->{make} || ""; if ( $default && $CPAN::Config->{install_help} eq 'sudo' ) { if ( find_exe('sudo') ) { $default = "sudo $default"; delete $CPAN::Config->{make_install_make_command} unless $CPAN::Config->{make_install_make_command} =~ /sudo/; } else { $CPAN::Frontend->mywarnonce("Could not find 'sudo' in PATH\n"); } } my_dflt_prompt(make_install_make_command => $default, $matcher); } my_dflt_prompt(make_install_arg => $CPAN::Config->{make_arg} || "", $matcher); my_dflt_prompt(mbuildpl_arg => "", $matcher); my_dflt_prompt(mbuild_arg => "", $matcher); if (exists $CPAN::HandleConfig::keys{mbuild_install_build_command} and $^O ne "MSWin32") { # as long as Windows needs $self->_build_command, we cannot # support sudo on windows :-) my $default = $^O eq 'VMS' ? '@Build.com' : "./Build"; if ( $CPAN::Config->{install_help} eq 'sudo' ) { if ( find_exe('sudo') ) { $default = "sudo $default"; delete $CPAN::Config->{mbuild_install_build_command} unless $CPAN::Config->{mbuild_install_build_command} =~ /sudo/; } else { $CPAN::Frontend->mywarnonce("Could not find 'sudo' in PATH\n"); } } my_dflt_prompt(mbuild_install_build_command => $default, $matcher); } my_dflt_prompt(mbuild_install_arg => "", $matcher); for my $o (qw( allow_installing_outdated_dists allow_installing_module_downgrades )) { my_prompt_loop($o => 'ask/no', $matcher, 'yes|no|ask/yes|ask/no'); } # #== use_prompt_default # my_yn_prompt(use_prompt_default => 0, $matcher); # #= Alarm period # my_dflt_prompt(inactivity_timeout => 0, $matcher); my_dflt_prompt(version_timeout => 15, $matcher); # #== halt_on_failure # my_yn_prompt(halt_on_failure => 0, $matcher); # #= Proxies # my @proxy_vars = qw/ftp_proxy http_proxy no_proxy/; my @proxy_user_vars = qw/proxy_user proxy_pass/; if (!$matcher or "@proxy_vars @proxy_user_vars" =~ /$matcher/) { $CPAN::Frontend->myprint($prompts{proxy_intro}) unless $auto_config; for (@proxy_vars) { $prompts{$_} = "Your $_?"; my_dflt_prompt($_ => $ENV{$_}||"", $matcher); } if ($CPAN::Config->{ftp_proxy} || $CPAN::Config->{http_proxy}) { $default = $CPAN::Config->{proxy_user} || $CPAN::LWP::UserAgent::USER || ""; $CPAN::Frontend->myprint($prompts{proxy_user}) unless $auto_config; if ($CPAN::Config->{proxy_user} = prompt("Your proxy user id?",$default)) { $CPAN::Frontend->myprint($prompts{proxy_pass}) unless $auto_config; if ($CPAN::META->has_inst("Term::ReadKey")) { Term::ReadKey::ReadMode("noecho"); } else { $CPAN::Frontend->myprint($prompts{password_warn}) unless $auto_config; } $CPAN::Config->{proxy_pass} = prompt_no_strip("Your proxy password?"); if ($CPAN::META->has_inst("Term::ReadKey")) { Term::ReadKey::ReadMode("restore"); } $CPAN::Frontend->myprint("\n\n") unless $auto_config; } } } # #= how plugins work # # XXX MISSING: my_array_prompt to be used with plugins. We did something like this near # git log -p fd68f8f5e33f4cecea4fdb7abc5ee19c12f138f0..test-notest-test-dependency # Need to do similar steps for plugin_list. As long as we do not support it here, people # must use the cpan shell prompt to write something like # o conf plugin_list push CPAN::Plugin::Specfile=dir,/tmp/foo-20141013,... # o conf commit # #= how FTP works # my_yn_prompt(ftp_passive => 1, $matcher); # #= how cwd works # my_prompt_loop(getcwd => 'cwd', $matcher, 'cwd|getcwd|fastcwd|getdcwd|backtickcwd'); # #= the CPAN shell itself (prompt, color) # my_yn_prompt(commandnumber_in_prompt => 1, $matcher); my_yn_prompt(term_ornaments => 1, $matcher); if ("colorize_output colorize_print colorize_warn colorize_debug" =~ $matcher) { my_yn_prompt(colorize_output => 0, $matcher); if ($CPAN::Config->{colorize_output}) { if ($CPAN::META->has_inst("Term::ANSIColor")) { my $T="gYw"; $CPAN::Frontend->myprint( " on_ on_y ". " on_ma on_\n") unless $auto_config; $CPAN::Frontend->myprint( " on_black on_red green ellow ". "on_blue genta on_cyan white\n") unless $auto_config; for my $FG ("", "bold", map {$_,"bold $_"} "black","red","green", "yellow","blue", "magenta", "cyan","white") { $CPAN::Frontend->myprint(sprintf( "%12s ", $FG)) unless $auto_config; for my $BG ("",map {"on_$_"} qw(black red green yellow blue magenta cyan white)) { $CPAN::Frontend->myprint( $FG||$BG ? Term::ANSIColor::colored(" $T ","$FG $BG") : " $T ") unless $auto_config; } $CPAN::Frontend->myprint( "\n" ) unless $auto_config; } $CPAN::Frontend->myprint( "\n" ) unless $auto_config; } for my $tuple ( ["colorize_print", "bold blue on_white"], ["colorize_warn", "bold red on_white"], ["colorize_debug", "black on_cyan"], ) { my_dflt_prompt($tuple->[0] => $tuple->[1], $matcher); if ($CPAN::META->has_inst("Term::ANSIColor")) { eval { Term::ANSIColor::color($CPAN::Config->{$tuple->[0]})}; if ($@) { $CPAN::Config->{$tuple->[0]} = $tuple->[1]; $CPAN::Frontend->mywarn($@."setting to default '$tuple->[1]'\n"); } } } } } # #== term_is_latin # my_yn_prompt(term_is_latin => 1, $matcher); # #== save history in file 'histfile' # if (!$matcher or 'histfile histsize' =~ /$matcher/) { $CPAN::Frontend->myprint($prompts{histfile_intro}) unless $auto_config; defined($default = $CPAN::Config->{histfile}) or $default = File::Spec->catfile($CPAN::Config->{cpan_home},"histfile"); my_dflt_prompt(histfile => $default, $matcher); if ($CPAN::Config->{histfile}) { defined($default = $CPAN::Config->{histsize}) or $default = 100; my_dflt_prompt(histsize => $default, $matcher); } } # #== do an ls on the m or the d command # my_yn_prompt(show_upload_date => 0, $matcher); # #== verbosity at the end of the r command # if (!$matcher or 'show_unparsable_versions' =~ /$matcher/ or 'show_zero_versions' =~ /$matcher/ ) { my_yn_prompt(show_unparsable_versions => 0, $matcher); my_yn_prompt(show_zero_versions => 0, $matcher); } # #= MIRRORED.BY and conf_sites() # # Let's assume they want to use the internet and make them turn it # off if they really don't. my_yn_prompt("connect_to_internet_ok" => 1, $matcher); my_yn_prompt("pushy_https" => 1, $matcher); # Allow matching but don't show during manual config if ($matcher) { if ("urllist_ping_external" =~ $matcher) { my_yn_prompt(urllist_ping_external => 0, $matcher); } if ("urllist_ping_verbose" =~ $matcher) { my_yn_prompt(urllist_ping_verbose => 0, $matcher); } if ("randomize_urllist" =~ $matcher) { my_dflt_prompt(randomize_urllist => 0, $matcher); } if ("ftpstats_size" =~ $matcher) { my_dflt_prompt(ftpstats_size => 99, $matcher); } if ("ftpstats_period" =~ $matcher) { my_dflt_prompt(ftpstats_period => 14, $matcher); } } $CPAN::Config->{urllist} ||= []; if ($auto_config) { if(@{ $CPAN::Config->{urllist} }) { $CPAN::Frontend->myprint( "Your 'urllist' is already configured. Type 'o conf init urllist' to change it.\n" ); } else { # Hint: as of 2021-11: to get http, use http://www.cpan.org/ $CPAN::Config->{urllist} = [ 'https://cpan.org/' ]; $CPAN::Frontend->myprint( "We initialized your 'urllist' to @{$CPAN::Config->{urllist}}. Type 'o conf init urllist' to change it.\n" ); } } elsif (!$matcher || "urllist" =~ $matcher) { _do_pick_mirrors(); } if ($auto_config) { $CPAN::Frontend->myprint( "\nAutoconfiguration complete.\n" ); $auto_config = 0; # reset } # bootstrap local::lib now if requested if ( $CPAN::Config->{install_help} eq 'local::lib' ) { if ( ! @{ $CPAN::Config->{urllist} } ) { $CPAN::Frontend->myprint( "\nALERT: Skipping local::lib bootstrap because 'urllist' is not configured.\n" ); } elsif (! $CPAN::Config->{make} ) { $CPAN::Frontend->mywarn( "\nALERT: Skipping local::lib bootstrap because 'make' is not configured.\n" ); _beg_for_make(); # repetitive, but we don't want users to miss it } else { $CPAN::Frontend->myprint("\nAttempting to bootstrap local::lib...\n"); $CPAN::Frontend->myprint("\nWriting $configpm for bootstrap...\n"); delete $CPAN::Config->{install_help}; # temporary only CPAN::HandleConfig->commit; my($dist, $locallib); $locallib = CPAN::Shell->expand('Module', 'local::lib'); if ( $locallib and $dist = $locallib->distribution ) { # this is a hack to force bootstrapping $dist->{prefs}{pl}{commandline} = "$^X Makefile.PL --bootstrap"; # Set @INC for this process so we find things as they bootstrap require lib; lib->import(_local_lib_inc_path()); eval { $dist->install }; } if ( ! $dist || (my $err = $@) ) { $err ||= 'Could not locate local::lib in the CPAN index'; $CPAN::Frontend->mywarn("Error bootstrapping local::lib: $@\n"); $CPAN::Frontend->myprint("From the CPAN Shell, you might try 'look local::lib' and \n" . "run 'perl Makefile --bootstrap' and see if that is successful. Then\n" . "restart your CPAN client\n" ); } else { _local_lib_config(); } } } # install_help is temporary for configuration and not saved delete $CPAN::Config->{install_help}; $CPAN::Frontend->myprint("\n"); if ($matcher && !$CPAN::Config->{auto_commit}) { $CPAN::Frontend->myprint("Please remember to call 'o conf commit' to ". "make the config permanent!\n"); } else { CPAN::HandleConfig->commit; } if (! $matcher) { $CPAN::Frontend->myprint( "\nYou can re-run configuration any time with 'o conf init' in the CPAN shell\n" ); } } sub _local_lib_config { # Set environment stuff for this process require local::lib; # Tell user about environment vars to set $CPAN::Frontend->myprint($prompts{local_lib_installed}); local $ENV{SHELL} = $CPAN::Config->{shell} || $ENV{SHELL}; my $shellvars = local::lib->environment_vars_string_for(_local_lib_path()); $CPAN::Frontend->myprint($shellvars); # Set %ENV after getting string above my %env = local::lib->build_environment_vars_for(_local_lib_path(), 1); while ( my ($k, $v) = each %env ) { $ENV{$k} = $v; } # Offer to mangle the shell config my $munged_rc; if ( my $rc = _find_shell_config() ) { local $auto_config = 0; # We *must* ask, even under autoconfig local *_real_prompt; # We *must* show prompt my $_conf = prompt( "\nWould you like me to append that to $rc now?", "yes" ); if ($_conf =~ /^y/i) { open my $fh, ">>", $rc; print {$fh} "\n$shellvars"; close $fh; $munged_rc++; } } # Warn at exit time if ($munged_rc) { push @{$CPAN::META->_exit_messages}, << "HERE"; *** Remember to restart your shell before running cpan again *** HERE } else { push @{$CPAN::META->_exit_messages}, << "HERE"; *** Remember to add these environment variables to your shell config and restart your shell before running cpan again *** $shellvars HERE } } { my %shell_rc_map = ( map { $_ => ".${_}rc" } qw/ bash tcsh csh /, map { $_ => ".profile" } qw/dash ash sh/, zsh => ".zshenv", ); sub _find_shell_config { my $shell = File::Basename::basename($CPAN::Config->{shell}); if ( my $rc = $shell_rc_map{$shell} ) { my $path = File::Spec->catfile($ENV{HOME}, $rc); return $path if -w $path; } } } sub _local_lib_inc_path { return File::Spec->catdir(_local_lib_path(), qw/lib perl5/); } sub _local_lib_path { return File::Spec->catdir(_local_lib_home(), 'perl5'); } # Adapted from resolve_home_path() in local::lib -- this is where # local::lib thinks the user's home is { my $local_lib_home; sub _local_lib_home { $local_lib_home ||= File::Spec->rel2abs( do { if ($CPAN::META->has_usable("File::HomeDir") && File::HomeDir->VERSION >= 0.65) { File::HomeDir->my_home; } elsif (defined $ENV{HOME}) { $ENV{HOME}; } else { (getpwuid $<)[7] || "~"; } }); } } sub _do_pick_mirrors { local *_real_prompt; *_real_prompt = \&CPAN::Shell::colorable_makemaker_prompt; $CPAN::Frontend->myprint($prompts{urls_intro}); # Only prompt for auto-pick if Net::Ping is new enough to do timings my $_conf = 'n'; if ( $CPAN::META->has_usable("Net::Ping") && CPAN::Version->vgt(Net::Ping->VERSION, '2.13')) { $_conf = prompt($prompts{auto_pick}, "yes"); } else { prompt("Autoselection disabled due to Net::Ping missing or insufficient. Please press ENTER"); } my @old_list = @{ $CPAN::Config->{urllist} }; if ( $_conf =~ /^y/i ) { conf_sites( auto_pick => 1 ) or bring_your_own(); } else { _print_urllist('Current') if @old_list; my $msg = scalar @old_list ? "\nWould you like to edit the urllist or pick new mirrors from a list?" : "\nWould you like to pick from the CPAN mirror list?" ; my $_conf = prompt($msg, "yes"); if ( $_conf =~ /^y/i ) { conf_sites(); } bring_your_own(); } _print_urllist('New'); } sub _init_external_progs { my($matcher,$args) = @_; my $PATH = $args->{path}; my @external_progs = @{ $args->{progs} }; my $shortcut = $args->{shortcut}; my $showed_make_warning; if (!$matcher or "@external_progs" =~ /$matcher/) { my $old_warn = $^W; local $^W if $^O eq 'MacOS'; local $^W = $old_warn; my $progname; for $progname (@external_progs) { next if $matcher && $progname !~ /$matcher/; if ($^O eq 'MacOS') { $CPAN::Config->{$progname} = 'not_here'; next; } my $progcall = $progname; unless ($matcher) { # we really don't need ncftp if we have ncftpget, but # if they chose this dialog via matcher, they shall have it next if $progname eq "ncftp" && $CPAN::Config->{ncftpget} gt " "; } my $path = $CPAN::Config->{$progname} || $Config::Config{$progname} || ""; if (File::Spec->file_name_is_absolute($path)) { # testing existence is not good enough, some have these exe # extensions # warn "Warning: configured $path does not exist\n" unless -e $path; # $path = ""; } elsif ($path =~ /^\s+$/) { # preserve disabled programs } else { $path = ''; } unless ($path) { # e.g. make -> nmake $progcall = $Config::Config{$progname} if $Config::Config{$progname}; } $path ||= find_exe($progcall,$PATH); unless ($path) { # not -e $path, because find_exe already checked that local $"=";"; $CPAN::Frontend->mywarn("Warning: $progcall not found in PATH[@$PATH]\n") unless $auto_config; _beg_for_make(), $showed_make_warning++ if $progname eq "make"; } $prompts{$progname} = "Where is your $progname program?"; $path = my_dflt_prompt($progname,$path,$matcher,1); # 1 => no strip spaces my $disabling = $path =~ m/^\s*$/; # don't let them disable or misconfigure make without warning if ( $progname eq "make" && ( $disabling || ! _check_found($path) ) ) { if ( $disabling && $showed_make_warning ) { next; } else { _beg_for_make() unless $showed_make_warning++; undef $CPAN::Config->{$progname}; $CPAN::Frontend->mywarn("Press SPACE and ENTER to disable make (NOT RECOMMENDED)\n"); redo; } } elsif ( $disabling ) { next; } elsif ( _check_found( $CPAN::Config->{$progname} ) ) { last if $shortcut && !$matcher; } else { undef $CPAN::Config->{$progname}; $CPAN::Frontend->mywarn("Press SPACE and ENTER to disable $progname\n"); redo; } } } } sub _check_found { my ($prog) = @_; if ( ! -f $prog ) { $CPAN::Frontend->mywarn("Warning: '$prog' does not exist\n") unless $auto_config; return; } elsif ( ! -x $prog ) { $CPAN::Frontend->mywarn("Warning: '$prog' is not executable\n") unless $auto_config; return; } return 1; } sub _beg_for_make { $CPAN::Frontend->mywarn(<<"HERE"); ALERT: 'make' is an essential tool for building perl Modules. Please make sure you have 'make' (or some equivalent) working. HERE if ($^O eq "MSWin32") { $CPAN::Frontend->mywarn(<<"HERE"); Windows users may want to follow this procedure when back in the CPAN shell: look YVES/scripts/alien_nmake.pl perl alien_nmake.pl This will install nmake on your system which can be used as a 'make' substitute. HERE } $CPAN::Frontend->mywarn(<<"HERE"); You can then retry the 'make' configuration step with o conf init make HERE } sub init_cpan_home { my($matcher) = @_; if (!$matcher or 'cpan_home' =~ /$matcher/) { my $cpan_home = $CPAN::Config->{cpan_home} || CPAN::HandleConfig::cpan_home(); if (-d $cpan_home) { $CPAN::Frontend->myprint( "\nI see you already have a directory\n" . "\n$cpan_home\n" . "Shall we use it as the general CPAN build and cache directory?\n\n" ) unless $auto_config; } else { # no cpan-home, must prompt and get one $CPAN::Frontend->myprint($prompts{cpan_home_where}) unless $auto_config; } my $default = $cpan_home; my $loop = 0; my($last_ans,$ans); $CPAN::Frontend->myprint(" <cpan_home>\n") unless $auto_config; PROMPT: while ($ans = prompt("CPAN build and cache directory?",$default)) { if (File::Spec->file_name_is_absolute($ans)) { my @cpan_home = split /[\/\\]/, $ans; DIR: for my $dir (@cpan_home) { if ($dir =~ /^~/ and (!$last_ans or $ans ne $last_ans)) { $CPAN::Frontend ->mywarn("Warning: a tilde in the path will be ". "taken as a literal tilde. Please ". "confirm again if you want to keep it\n"); $last_ans = $default = $ans; next PROMPT; } } } else { require Cwd; my $cwd = Cwd::cwd(); my $absans = File::Spec->catdir($cwd,$ans); $CPAN::Frontend->mywarn("The path '$ans' is not an ". "absolute path. Please specify ". "an absolute path\n"); $default = $absans; next PROMPT; } eval { File::Path::mkpath($ans); }; # dies if it can't if ($@) { $CPAN::Frontend->mywarn("Couldn't create directory $ans.\n". "Please retry.\n"); next PROMPT; } if (-d $ans && -w _) { last PROMPT; } else { $CPAN::Frontend->mywarn("Couldn't find directory $ans\n". "or directory is not writable. Please retry.\n"); if (++$loop > 5) { $CPAN::Frontend->mydie("Giving up"); } } } $CPAN::Config->{cpan_home} = $ans; } } sub my_dflt_prompt { my ($item, $dflt, $m, $no_strip) = @_; my $default = $CPAN::Config->{$item} || $dflt; if (!$auto_config && (!$m || $item =~ /$m/)) { if (my $intro = $prompts{$item . "_intro"}) { $CPAN::Frontend->myprint($intro); } $CPAN::Frontend->myprint(" <$item>\n"); $CPAN::Config->{$item} = $no_strip ? prompt_no_strip($prompts{$item}, $default) : prompt( $prompts{$item}, $default); } else { $CPAN::Config->{$item} = $default; } return $CPAN::Config->{$item}; } sub my_yn_prompt { my ($item, $dflt, $m) = @_; my $default; defined($default = $CPAN::Config->{$item}) or $default = $dflt; if (!$auto_config && (!$m || $item =~ /$m/)) { if (my $intro = $prompts{$item . "_intro"}) { $CPAN::Frontend->myprint($intro); } $CPAN::Frontend->myprint(" <$item>\n"); my $ans = prompt($prompts{$item}, $default ? 'yes' : 'no'); $CPAN::Config->{$item} = ($ans =~ /^[y1]/i ? 1 : 0); } else { $CPAN::Config->{$item} = $default; } } sub my_prompt_loop { my ($item, $dflt, $m, $ok) = @_; my $default = $CPAN::Config->{$item} || $dflt; my $ans; if (!$auto_config && (!$m || $item =~ /$m/)) { my $intro = $prompts{$item . "_intro"}; $CPAN::Frontend->myprint($intro) if defined $intro; $CPAN::Frontend->myprint(" <$item>\n"); do { $ans = prompt($prompts{$item}, $default); } until $ans =~ /$ok/; $CPAN::Config->{$item} = $ans; } else { $CPAN::Config->{$item} = $default; } } # Here's the logic about the MIRRORED.BY file. There are a number of scenarios: # (1) We have a cached MIRRORED.BY file # (1a) We're auto-picking # - Refresh it automatically if it's old # (1b) Otherwise, ask if using cached is ok. If old, default to no. # - If cached is not ok, get it from the Internet. If it succeeds we use # the new file. Otherwise, we use the old file. # (2) We don't have a copy at all # (2a) If we are allowed to connect, we try to get a new copy. If it succeeds, # we use it, otherwise, we warn about failure # (2b) If we aren't allowed to connect, sub conf_sites { my %args = @_; # auto pick implies using the internet $CPAN::Config->{connect_to_internet_ok} = 1 if $args{auto_pick}; my $m = 'MIRRORED.BY'; my $mby = File::Spec->catfile($CPAN::Config->{keep_source_where},$m); File::Path::mkpath(File::Basename::dirname($mby)); # Why are we using MIRRORED.BY from the current directory? # Is this for testing? -- dagolden, 2009-11-05 if (-f $mby && -f $m && -M $m < -M $mby) { require File::Copy; File::Copy::copy($m,$mby) or die "Could not update $mby: $!"; } local $^T = time; # if we have a cached copy is not older than 60 days, we either # use it or refresh it or fall back to it if the refresh failed. if ($mby && -f $mby && -s _ > 0 ) { my $very_old = (-M $mby > 60); my $mtime = localtime((stat _)[9]); # if auto_pick, refresh anything old automatically if ( $args{auto_pick} ) { if ( $very_old ) { $CPAN::Frontend->myprint(qq{Trying to refresh your mirror list\n}); eval { CPAN::FTP->localize($m,$mby,3,1) } or $CPAN::Frontend->myprint(qq{Refresh failed. Using the old cached copy instead.\n}); $CPAN::Frontend->myprint("\n"); } } else { my $prompt = qq{Found a cached mirror list as of $mtime If you'd like to just use the cached copy, answer 'yes', below. If you'd like an updated copy of the mirror list, answer 'no' and I'll get a fresh one from the Internet. Shall I use the cached mirror list?}; my $ans = prompt($prompt, $very_old ? "no" : "yes"); if ($ans =~ /^n/i) { $CPAN::Frontend->myprint(qq{Trying to refresh your mirror list\n}); # you asked for it from the Internet $CPAN::Config->{connect_to_internet_ok} = 1; eval { CPAN::FTP->localize($m,$mby,3,1) } or $CPAN::Frontend->myprint(qq{Refresh failed. Using the old cached copy instead.\n}); $CPAN::Frontend->myprint("\n"); } } } # else there is no cached copy and we must fetch or fail else { # If they haven't agree to connect to the internet, ask again if ( ! $CPAN::Config->{connect_to_internet_ok} ) { my $prompt = q{You are missing a copy of the CPAN mirror list. May I connect to the Internet to get it?}; my $ans = prompt($prompt, "yes"); if ($ans =~ /^y/i) { $CPAN::Config->{connect_to_internet_ok} = 1; } } # Now get it from the Internet or complain if ( $CPAN::Config->{connect_to_internet_ok} ) { $CPAN::Frontend->myprint(qq{Trying to fetch a mirror list from the Internet\n}); eval { CPAN::FTP->localize($m,$mby,3,1) } or $CPAN::Frontend->mywarn(<<'HERE'); We failed to get a copy of the mirror list from the Internet. You will need to provide CPAN mirror URLs yourself. HERE $CPAN::Frontend->myprint("\n"); } else { $CPAN::Frontend->mywarn(<<'HERE'); You will need to provide CPAN mirror URLs yourself or set 'o conf connect_to_internet_ok 1' and try again. HERE } } # if we finally have a good local MIRRORED.BY, get on with picking if (-f $mby && -s _ > 0){ $CPAN::Config->{urllist} = $args{auto_pick} ? auto_mirrored_by($mby) : choose_mirrored_by($mby); return 1; } return; } sub find_exe { my($exe,$path) = @_; $path ||= [split /$Config{'path_sep'}/, $ENV{'PATH'}]; my($dir); #warn "in find_exe exe[$exe] path[@$path]"; for $dir (@$path) { my $abs = File::Spec->catfile($dir,$exe); if (($abs = MM->maybe_command($abs))) { return $abs; } } } sub picklist { my($items,$prompt,$default,$require_nonempty,$empty_warning)=@_; CPAN->debug("picklist('$items','$prompt','$default','$require_nonempty',". "'$empty_warning')") if $CPAN::DEBUG; $default ||= ''; my $pos = 0; my @nums; SELECTION: while (1) { # display, at most, 15 items at a time my $limit = $#{ $items } - $pos; $limit = 15 if $limit > 15; # show the next $limit items, get the new position $pos = display_some($items, $limit, $pos, $default); $pos = 0 if $pos >= @$items; my $num = prompt($prompt,$default); @nums = split (' ', $num); { my %seen; @nums = grep { !$seen{$_}++ } @nums; } my $i = scalar @$items; unrangify(\@nums); if (0 == @nums) { # cannot allow nothing because nothing means paging! # return; } elsif (grep (/\D/ || $_ < 1 || $_ > $i, @nums)) { $CPAN::Frontend->mywarn("invalid items entered, try again\n"); if ("@nums" =~ /\D/) { $CPAN::Frontend->mywarn("(we are expecting only numbers between 1 and $i)\n"); } next SELECTION; } if ($require_nonempty && !@nums) { $CPAN::Frontend->mywarn("$empty_warning\n"); } # a blank line continues... unless (@nums){ $CPAN::Frontend->mysleep(0.1); # prevent hot spinning process on the next bug next SELECTION; } last; } for (@nums) { $_-- } @{$items}[@nums]; } sub unrangify ($) { my($nums) = $_[0]; my @nums2 = (); while (@{$nums||[]}) { my $n = shift @$nums; if ($n =~ /^(\d+)-(\d+)$/) { my @range = $1 .. $2; # warn "range[@range]"; push @nums2, @range; } else { push @nums2, $n; } } push @$nums, @nums2; } sub display_some { my ($items, $limit, $pos, $default) = @_; $pos ||= 0; my @displayable = @$items[$pos .. ($pos + $limit)]; for my $item (@displayable) { $CPAN::Frontend->myprint(sprintf "(%d) %s\n", ++$pos, $item); } my $hit_what = $default ? "SPACE ENTER" : "ENTER"; $CPAN::Frontend->myprint(sprintf("%d more items, hit %s to show them\n", (@$items - $pos), $hit_what, )) if $pos < @$items; return $pos; } sub auto_mirrored_by { my $local = shift or return; local $|=1; $CPAN::Frontend->myprint("Looking for CPAN mirrors near you (please be patient)\n"); my $mirrors = CPAN::Mirrors->new($local); my $cnt = 0; my $callback_was_active = 0; my @best = $mirrors->best_mirrors( how_many => 3, callback => sub { $callback_was_active++; $CPAN::Frontend->myprint("."); if ($cnt++>60) { $cnt=0; $CPAN::Frontend->myprint("\n"); } }, $CPAN::Config->{urllist_ping_external} ? (external_ping => 1) : (), $CPAN::Config->{urllist_ping_verbose} ? (verbose => 1) : (), ); my $urllist = [ map { $_->http } grep { $_ && ref $_ && $_->can('http') } @best ]; push @$urllist, grep { /^file:/ } @{$CPAN::Config->{urllist}}; $CPAN::Frontend->myprint(" done!\n\n") if $callback_was_active; return $urllist } sub choose_mirrored_by { my $local = shift or return; my ($default); my $mirrors = CPAN::Mirrors->new($local); my @previous_urls = @{$CPAN::Config->{urllist}}; $CPAN::Frontend->myprint($prompts{urls_picker_intro}); my (@cont, $cont, %cont, @countries, @urls, %seen); my $no_previous_warn = "Sorry! since you don't have any existing picks, you must make a\n" . "geographic selection."; my $offer_cont = [sort $mirrors->continents]; if (@previous_urls) { push @$offer_cont, "(edit previous picks)"; $default = @$offer_cont; } else { # cannot allow nothing because nothing means paging! # push @$offer_cont, "(none of the above)"; } @cont = picklist($offer_cont, "Select your continent (or several nearby continents)", $default, ! @previous_urls, $no_previous_warn); # cannot allow nothing because nothing means paging! # return unless @cont; foreach $cont (@cont) { my @c = sort $mirrors->countries($cont); @cont{@c} = map ($cont, 0..$#c); @c = map ("$_ ($cont)", @c) if @cont > 1; push (@countries, @c); } if (@previous_urls && @countries) { push @countries, "(edit previous picks)"; $default = @countries; } if (@countries) { @countries = picklist (\@countries, "Select your country (or several nearby countries)", $default, ! @previous_urls, $no_previous_warn); %seen = map (($_ => 1), @previous_urls); # hmmm, should take list of defaults from CPAN::Config->{'urllist'}... foreach my $country (@countries) { next if $country =~ /edit previous picks/; (my $bare_country = $country) =~ s/ \(.*\)//; my @u; for my $m ( $mirrors->mirrors($bare_country) ) { push @u, $m->ftp if $m->ftp; push @u, $m->http if $m->http; } @u = grep (! $seen{$_}, @u); @u = map ("$_ ($bare_country)", @u) if @countries > 1; push (@urls, sort @u); } } push (@urls, map ("$_ (previous pick)", @previous_urls)); my $prompt = "Select as many URLs as you like (by number), put them on one line, separated by blanks, hyphenated ranges allowed e.g. '1 4 5' or '7 1-4 8'"; if (@previous_urls) { $default = join (' ', ((scalar @urls) - (scalar @previous_urls) + 1) .. (scalar @urls)); $prompt .= "\n(or just hit ENTER to keep your previous picks)"; } @urls = picklist (\@urls, $prompt, $default); foreach (@urls) { s/ \(.*\)//; } return [ @urls ]; } sub bring_your_own { my $urllist = [ @{$CPAN::Config->{urllist}} ]; my %seen = map (($_ => 1), @$urllist); my($ans,@urls); my $eacnt = 0; # empty answers $CPAN::Frontend->myprint(<<'HERE'); Now you can enter your own CPAN URLs by hand. A local CPAN mirror can be listed using a 'file:' URL like 'file:///path/to/cpan/' HERE do { my $prompt = "Enter another URL or ENTER to quit:"; unless (%seen) { $prompt = qq{CPAN.pm needs at least one URL where it can fetch CPAN files from. Please enter your CPAN site:}; } $ans = prompt ($prompt, ""); if ($ans) { $ans =~ s|/?\z|/|; # has to end with one slash # XXX This manipulation is odd. Shouldn't we check that $ans is # a directory before converting to file:///? And we need /// below, # too, don't we? -- dagolden, 2009-11-05 $ans = "file:$ans" unless $ans =~ /:/; # without a scheme is a file: if ($ans =~ /^\w+:\/./) { push @urls, $ans unless $seen{$ans}++; } else { $CPAN::Frontend-> myprint(sprintf(qq{"%s" doesn\'t look like an URL at first sight. I\'ll ignore it for now. You can add it to your %s later if you\'re sure it\'s right.\n}, $ans, $INC{'CPAN/MyConfig.pm'} || $INC{'CPAN/Config.pm'} || "configuration file", )); } } else { if (++$eacnt >= 5) { $CPAN::Frontend-> mywarn("Giving up.\n"); $CPAN::Frontend->mysleep(5); return; } } } while $ans || !%seen; @$urllist = CPAN::_uniq(@$urllist, @urls); $CPAN::Config->{urllist} = $urllist; } sub _print_urllist { my ($which) = @_; $CPAN::Frontend->myprint("$which urllist\n"); for ( @{$CPAN::Config->{urllist} || []} ) { $CPAN::Frontend->myprint(" $_\n") }; } sub _can_write_to_libdirs { return -w $Config{installprivlib} && -w $Config{installarchlib} && -w $Config{installsitelib} && -w $Config{installsitearch} } sub _using_installbase { return 1 if $ENV{PERL_MM_OPT} && $ENV{PERL_MM_OPT} =~ /install_base/i; return 1 if grep { ($CPAN::Config->{$_}||q{}) =~ /install_base/i } qw(makepl_arg make_install_arg mbuildpl_arg mbuild_install_arg); return; } sub _using_sudo { return 1 if grep { ($CPAN::Config->{$_}||q{}) =~ /sudo/ } qw(make_install_make_command mbuild_install_build_command); return; } sub _strip_spaces { $_[0] =~ s/^\s+//; # no leading spaces $_[0] =~ s/\s+\z//; # no trailing spaces } sub prompt ($;$) { unless (defined &_real_prompt) { *_real_prompt = \&CPAN::Shell::colorable_makemaker_prompt; } my $ans = _real_prompt(@_); _strip_spaces($ans); $CPAN::Frontend->myprint("\n") unless $auto_config; return $ans; } sub prompt_no_strip ($;$) { unless (defined &_real_prompt) { *_real_prompt = \&CPAN::Shell::colorable_makemaker_prompt; } return _real_prompt(@_); } 1; PK�������!�B3������perl5/CPAN/Queue.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- use strict; package CPAN::Queue::Item; # CPAN::Queue::Item::new ; sub new { my($class,@attr) = @_; my $self = bless { @attr }, $class; return $self; } sub as_string { my($self) = @_; $self->{qmod}; } # r => requires, b => build_requires, c => commandline sub reqtype { my($self) = @_; $self->{reqtype}; } sub optional { my($self) = @_; $self->{optional}; } package CPAN::Queue; # One use of the queue is to determine if we should or shouldn't # announce the availability of a new CPAN module # Now we try to use it for dependency tracking. For that to happen # we need to draw a dependency tree and do the leaves first. This can # easily be reached by running CPAN.pm recursively, but we don't want # to waste memory and run into deep recursion. So what we can do is # this: # CPAN::Queue is the package where the queue is maintained. Dependencies # often have high priority and must be brought to the head of the queue, # possibly by jumping the queue if they are already there. My first code # attempt tried to be extremely correct. Whenever a module needed # immediate treatment, I either unshifted it to the front of the queue, # or, if it was already in the queue, I spliced and let it bypass the # others. This became a too correct model that made it impossible to put # an item more than once into the queue. Why would you need that? Well, # you need temporary duplicates as the manager of the queue is a loop # that # # (1) looks at the first item in the queue without shifting it off # # (2) cares for the item # # (3) removes the item from the queue, *even if its agenda failed and # even if the item isn't the first in the queue anymore* (that way # protecting against never ending queues) # # So if an item has prerequisites, the installation fails now, but we # want to retry later. That's easy if we have it twice in the queue. # # I also expect insane dependency situations where an item gets more # than two lives in the queue. Simplest example is triggered by 'install # Foo Foo Foo'. People make this kind of mistakes and I don't want to # get in the way. I wanted the queue manager to be a dumb servant, not # one that knows everything. # # Who would I tell in this model that the user wants to be asked before # processing? I can't attach that information to the module object, # because not modules are installed but distributions. So I'd have to # tell the distribution object that it should ask the user before # processing. Where would the question be triggered then? Most probably # in CPAN::Distribution::rematein. use vars qw{ @All $VERSION }; $VERSION = "5.5003"; # CPAN::Queue::queue_item ; sub queue_item { my($class,@attr) = @_; my $item = "$class\::Item"->new(@attr); $class->qpush($item); return 1; } # CPAN::Queue::qpush ; sub qpush { my($class,$obj) = @_; push @All, $obj; CPAN->debug(sprintf("in new All[%s]", join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @All), )) if $CPAN::DEBUG; } # CPAN::Queue::first ; sub first { my $obj = $All[0]; $obj; } # CPAN::Queue::delete_first ; sub delete_first { my($class,$what) = @_; my $i; for my $i (0..$#All) { if ( $All[$i]->{qmod} eq $what ) { splice @All, $i, 1; last; } } CPAN->debug(sprintf("after delete_first mod[%s] All[%s]", $what, join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @All) )) if $CPAN::DEBUG; } # CPAN::Queue::jumpqueue ; sub jumpqueue { my $class = shift; my @what = @_; CPAN->debug(sprintf("before jumpqueue All[%s] what[%s]", join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @All), join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @what), )) if $CPAN::DEBUG; unless (defined $what[0]{reqtype}) { # apparently it was not the Shell that sent us this enquiry, # treat it as commandline $what[0]{reqtype} = "c"; } my $inherit_reqtype = $what[0]{reqtype} =~ /^(c|r)$/ ? "r" : "b"; WHAT: for my $what_tuple (@what) { my($qmod,$reqtype,$optional) = @$what_tuple{qw(qmod reqtype optional)}; if ($reqtype eq "r" && $inherit_reqtype eq "b" ) { $reqtype = "b"; } my $jumped = 0; for (my $i=0; $i<$#All;$i++) { #prevent deep recursion if ($All[$i]{qmod} eq $qmod) { $jumped++; } } # high jumped values are normal for popular modules when # dealing with large bundles: XML::Simple, # namespace::autoclean, UNIVERSAL::require CPAN->debug("qmod[$qmod]jumped[$jumped]") if $CPAN::DEBUG; my $obj = "$class\::Item"->new( qmod => $qmod, reqtype => $reqtype, optional => !! $optional, ); unshift @All, $obj; } CPAN->debug(sprintf("after jumpqueue All[%s]", join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @All) )) if $CPAN::DEBUG; } # CPAN::Queue::exists ; sub exists { my($self,$what) = @_; my @all = map { $_->{qmod} } @All; my $exists = grep { $_->{qmod} eq $what } @All; # warn "in exists what[$what] all[@all] exists[$exists]"; $exists; } # CPAN::Queue::delete ; sub delete { my($self,$mod) = @_; @All = grep { $_->{qmod} ne $mod } @All; CPAN->debug(sprintf("after delete mod[%s] All[%s]", $mod, join("",map {sprintf " %s\[%s][%s]\n",$_->{qmod},$_->{reqtype},$_->{optional}} @All) )) if $CPAN::DEBUG; } # CPAN::Queue::nullify_queue ; sub nullify_queue { @All = (); } # CPAN::Queue::size ; sub size { return scalar @All; } sub reqtype_of { my($self,$mod) = @_; my $best = ""; for my $item (grep { $_->{qmod} eq $mod } @All) { my $c = $item->{reqtype}; if ($c eq "c") { $best = $c; last; } elsif ($c eq "r") { $best = $c; } elsif ($c eq "b") { if ($best eq "") { $best = $c; } } else { die "Panic: in reqtype_of: reqtype[$c] seen, should never happen"; } } return $best; } sub iterator { my $i = 0; return sub { until ($All[$i] || $i > $#All) { $i++; } return if $i > $#All; return $All[$i++] }; } 1; __END__ =head1 NAME CPAN::Queue - internal queue support 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 PK�������!�Bo��o����perl5/CPAN/Admin.pmnu�6$��������package CPAN::Admin; use base CPAN; use CPAN; # old base.pm did not load CPAN on previous line use strict; use vars qw(@EXPORT $VERSION); use constant PAUSE_IP => "pause.perl.org"; @EXPORT = qw(shell); $VERSION = "5.501"; push @CPAN::Complete::COMMANDS, qw(register modsearch); $CPAN::Shell::COLOR_REGISTERED = 1; sub shell { CPAN::shell($_[0]||"admin's cpan> ",$_[1]); } sub CPAN::Shell::register { my($self,$mod,@rest) = @_; unless ($mod) { print "register called without argument\n"; return; } if ($CPAN::META->has_inst("URI::Escape")) { require URI::Escape; } else { print "register requires URI::Escape installed, otherwise it cannot work\n"; return; } print "Got request for mod[$mod]\n"; if (@rest) { my $modline = join " ", $mod, @rest; print "Sending to PAUSE [$modline]\n"; my $emodline = URI::Escape::uri_escape($modline, '^\w '); $emodline =~ s/ /+/g; my $url = sprintf("https://%s/pause/authenquery?pause99_add_mod_modid=". "%s;SUBMIT_pause99_add_mod_hint=hint", PAUSE_IP, $emodline, ); print "url[$url]\n\n"; print ">>>>Trying to open a netscape window<<<<\n"; sleep 1; system("netscape","-remote","openURL($url)"); return; } my $m = CPAN::Shell->expand("Module",$mod); unless (ref $m) { print "Could not determine the object for $mod\n"; return; } my $id = $m->id; print "Found module id[$id] in database\n"; if (exists $m->{RO} && $m->{RO}{chapterid}) { print "$id is already registered\n"; return; } my(@namespace) = split /::/, $id; my $rootns = $namespace[0]; # Tk, XML and Apache need special treatment if ($rootns=~/^(Bundle)\b/) { print "Bundles are not yet ready for registering\n"; return; } # make a good suggestion for the chapter my(@simile) = CPAN::Shell->expand("Module","/^$rootns(:|\$)/"); print "Found within this namespace ", join(", ", map { $_->id } @simile), "\n"; my(%seench); for my $ch (map { exists $_->{RO} ? $_->{RO}{chapterid} : ""} @simile) { next unless $ch; $seench{$ch}=undef; } my(@seench) = sort grep {length($_)} keys %seench; my $reco_ch = ""; if (@seench>1) { print "Found rootnamespace[$rootns] in the chapters [", join(", ", @seench), "]\n"; $reco_ch = $seench[0]; print "Picking $reco_ch\n"; } elsif (@seench==1) { print "Found rootnamespace[$rootns] in the chapter[$seench[0]]\n"; $reco_ch = $seench[0]; } else { print "The new rootnamespace[$rootns] needs to be introduced. Oh well.\n"; } # Look closer at the dist my $d = CPAN::Shell->expand("Distribution", $m->cpan_file); printf "Module comes with dist[%s]\n", $d->id; for my $contm ($d->containsmods) { if ($CPAN::META->exists("CPAN::Module",$contm)) { my $contm_obj = CPAN::Shell->expand("Module",$contm) or next; my $is_reg = exists $contm_obj->{RO} && $contm_obj->{RO}{description}; printf(" in same dist: %s%s\n", $contm, $is_reg ? " already in modulelist" : "", ); } } # get it so that m is better and we can inspect for XS CPAN::Shell->get($id); CPAN::Shell->m($id); CPAN::Shell->d($d->id); my $has_xs = 0; { my($mani,@mani); local $/ = "\n"; open $mani, "$d->{build_dir}/MANIFEST" and @mani = <$mani>; my @xs = grep /\.xs\b/, @mani; if (@xs) { print "Found XS files: @xs"; $has_xs=1; } } my $emodid = URI::Escape::uri_escape($id, '\W'); my $ech = $reco_ch; $ech =~ s/ /+/g; my $description = $m->{MANPAGE} || ""; $description =~ s/[A-Z]<//; # POD markup (and maybe more) $description =~ s/^\s+//; # leading spaces $description =~ s/>//; # POD $description =~ s/^\Q$id\E//; # usually this line starts with the modid $description =~ s/^[ \-]+//; # leading spaces and dashes substr($description,44) = "" if length($description)>44; $description = ucfirst($description); my $edescription = URI::Escape::uri_escape($description, '^\w '); $edescription =~ s/ /+/g; my $url = sprintf("https://%s/pause/authenquery?pause99_add_mod_modid=". "%s;pause99_add_mod_chapterid=%s;pause99_add_mod_statd=%s;". "pause99_add_mod_stats=%s;pause99_add_mod_statl=%s;". "pause99_add_mod_stati=%s;pause99_add_mod_description=%s;". "pause99_add_mod_userid=%s;SUBMIT_pause99_add_mod_preview=preview", PAUSE_IP, $emodid, $ech, "R", "d", $has_xs ? "c" : "p", "O", $edescription, $m->{RO}{CPAN_USERID}, ); print "$url\n\n"; print ">>>>Trying to open a netscape window<<<<\n"; system("netscape","-remote","openURL($url)"); } sub CPAN::Shell::modsearch { my($self,@line) = @_; unless (@line) { print "modsearch called without argument\n"; return; } my $request = join " ", @line; print "Got request[$request]\n"; my $erequest = URI::Escape::uri_escape($request, '^\w '); $erequest =~ s/ /+/g; my $url = sprintf("http://www.xray.mpe.mpg.de/cgi-bin/w3glimpse/modules?query=%s". "&errors=0&case=on&maxfiles=100&maxlines=30", $erequest, ); print "$url\n\n"; print ">>>>Trying to open a netscape window<<<<\n"; system("netscape","-remote","openURL('$url')"); } 1; __END__ =head1 NAME CPAN::Admin - A CPAN Shell for CPAN admins =head1 SYNOPSIS perl -MCPAN::Admin -e shell =head1 STATUS Note: this module is currently not maintained. If you need it and fix it for your needs, please submit patches. =head1 DESCRIPTION CPAN::Admin is a subclass of CPAN that adds the commands C<register> and C<modsearch> to the CPAN shell. C<register> calls C<get> on the named module, assembles a couple of informations (description, language), and calls Netscape with the -remote argument so that a form is filled with all the assembled informations and the registration can be performed with a single click. If the command line has more than one argument, register does not run a C<get>, instead it interprets the rest of the line as DSLI status, description, and userid and sends them to netscape such that the form is again mostly filled and can be edited or confirmed with a single click. CPAN::Admin never performs the submission click for you, it is only intended to fill in the form on PAUSE and leave the confirmation to you. C<modsearch> simply passes the arguments to the search engine for the modules@perl.org mailing list at L<http://www.xray.mpe.mpg.de> where all registration requests are stored. It does so in the same way as register, namely with the C<netscape -remote> command. An experimental feature has also been added, namely to color already registered modules in listings. If you have L<Term::ANSIColor> installed, the u, r, and m commands will show already registered modules in green. =head1 PREREQUISITES L<URI::Escape>, a browser available in the path, the browser must understand the -remote switch (as far as I know, this is only available on UNIX); coloring of registered modules is only available if L<Term::ANSIColor> is installed. =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!�iۨD������perl5/CPAN/Distrostatus.pmnu�6$��������# -*- 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; PK�������!�~��~����perl5/AppConfig.pmnu�6$��������#============================================================================ # # AppConfig.pm # # Perl5 module for reading and parsing configuration files and command line # arguments. # # Written by Andy Wardley <abw@wardley.org> # # Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. #========================================================================== package AppConfig; use 5.006; use strict; use warnings; use base 'Exporter'; our $VERSION = '1.71'; # variable expansion constants use constant EXPAND_NONE => 0; use constant EXPAND_VAR => 1; use constant EXPAND_UID => 2; use constant EXPAND_ENV => 4; use constant EXPAND_ALL => EXPAND_VAR | EXPAND_UID | EXPAND_ENV; use constant EXPAND_WARN => 8; # argument count types use constant ARGCOUNT_NONE => 0; use constant ARGCOUNT_ONE => 1; use constant ARGCOUNT_LIST => 2; use constant ARGCOUNT_HASH => 3; # Exporter tagsets our @EXPAND = qw( EXPAND_NONE EXPAND_VAR EXPAND_UID EXPAND_ENV EXPAND_ALL EXPAND_WARN ); our @ARGCOUNT = qw( ARGCOUNT_NONE ARGCOUNT_ONE ARGCOUNT_LIST ARGCOUNT_HASH ); our @EXPORT_OK = ( @EXPAND, @ARGCOUNT ); our %EXPORT_TAGS = ( expand => [ @EXPAND ], argcount => [ @ARGCOUNT ], ); our $AUTOLOAD; require AppConfig::State; #------------------------------------------------------------------------ # new(\%config, @vars) # # Module constructor. All parameters passed are forwarded onto the # AppConfig::State constructor. Returns a reference to a newly created # AppConfig object. #------------------------------------------------------------------------ sub new { my $class = shift; bless { STATE => AppConfig::State->new(@_) }, $class; } #------------------------------------------------------------------------ # file(@files) # # The file() method is called to parse configuration files. An # AppConfig::File object is instantiated and stored internally for # use in subsequent calls to file(). #------------------------------------------------------------------------ sub file { my $self = shift; my $state = $self->{ STATE }; my $file; require AppConfig::File; # create an AppConfig::File object if one isn't defined $file = $self->{ FILE } ||= AppConfig::File->new($state); # call on the AppConfig::File object to process files. $file->parse(@_); } #------------------------------------------------------------------------ # args(\@args) # # The args() method is called to parse command line arguments. An # AppConfig::Args object is instantiated and then stored internally for # use in subsequent calls to args(). #------------------------------------------------------------------------ sub args { my $self = shift; my $state = $self->{ STATE }; my $args; require AppConfig::Args; # create an AppConfig::Args object if one isn't defined $args = $self->{ ARGS } ||= AppConfig::Args->new($state); # call on the AppConfig::Args object to process arguments. $args->parse(shift); } #------------------------------------------------------------------------ # getopt(@config, \@args) # # The getopt() method is called to parse command line arguments. The # AppConfig::Getopt module is require()'d and an AppConfig::Getopt object # is created to parse the arguments. #------------------------------------------------------------------------ sub getopt { my $self = shift; my $state = $self->{ STATE }; my $getopt; require AppConfig::Getopt; # create an AppConfig::Getopt object if one isn't defined $getopt = $self->{ GETOPT } ||= AppConfig::Getopt->new($state); # call on the AppConfig::Getopt object to process arguments. $getopt->parse(@_); } #------------------------------------------------------------------------ # cgi($query) # # The cgi() method is called to parse a CGI query string. An # AppConfig::CGI object is instantiated and then stored internally for # use in subsequent calls to args(). #------------------------------------------------------------------------ sub cgi { my $self = shift; my $state = $self->{ STATE }; my $cgi; require AppConfig::CGI; # create an AppConfig::CGI object if one isn't defined $cgi = $self->{ CGI } ||= AppConfig::CGI->new($state); # call on the AppConfig::CGI object to process a query. $cgi->parse(shift); } #------------------------------------------------------------------------ # AUTOLOAD # # Autoload function called whenever an unresolved object method is # called. All methods are delegated to the $self->{ STATE } # AppConfig::State object. # #------------------------------------------------------------------------ sub AUTOLOAD { my $self = shift; my $method; # splat the leading package name ($method = $AUTOLOAD) =~ s/.*:://; # ignore destructor $method eq 'DESTROY' && return; # delegate method call to AppConfig::State object in $self->{ STATE } $self->{ STATE }->$method(@_); } 1; __END__ =head1 NAME AppConfig - Perl5 module for reading configuration files and parsing command line arguments. =head1 SYNOPSIS use AppConfig; # create a new AppConfig object my $config = AppConfig->new( \%cfg ); # define a new variable $config->define( $varname => \%varopts ); # create/define combined my $config = AppConfig->new( \%cfg, $varname => \%varopts, $varname => \%varopts, ... ); # set/get the value $config->set( $varname, $value ); $config->get($varname); # shortcut form $config->varname($value); $config->varname; # read configuration file $config->file($file); # parse command line options $config->args(\@args); # default to \@ARGV # advanced command line options with Getopt::Long $config->getopt(\@args); # default to \@ARGV # parse CGI parameters (GET method) $config->cgi($query); # default to $ENV{ QUERY_STRING } =head1 OVERVIEW AppConfig is a Perl5 module for managing application configuration information. It maintains the state of any number of variables and provides methods for parsing configuration files, command line arguments and CGI script parameters. Variables values may be set via configuration files. Variables may be flags (On/Off), take a single value, or take multiple values stored as a list or hash. The number of arguments a variable expects is determined by its configuration when defined. # flags verbose nohelp debug = On # single value home = /home/abw/ # multiple list value file = /tmp/file1 file = /tmp/file2 # multiple hash value book camel = Programming Perl book llama = Learning Perl The '-' prefix can be used to reset a variable to its default value and the '+' prefix can be used to set it to 1 -verbose +debug Variable, environment variable and tilde (home directory) expansions can be applied (selectively, if necessary) to the values read from configuration files: home = ~ # home directory nntp = ${NNTPSERVER} # environment variable html = $home/html # internal variables img = $html/images Configuration files may be arranged in blocks as per the style of Win32 "INI" files. [file] site = kfs src = ~/websrc/docs/$site lib = ~/websrc/lib dest = ~/public_html/$site [page] header = $lib/header footer = $lib/footer You can also use Perl's "heredoc" syntax to define a large block of text in a configuration file. multiline = <<FOOBAR line 1 line 2 FOOBAR paths exe = "${PATH}:${HOME}/.bin" paths link = <<'FOO' ${LD_LIBARRAY_PATH}:${HOME}/lib FOO Variables may also be set by parsing command line arguments. myapp -verbose -site kfs -file f1 -file f2 AppConfig provides a simple method (args()) for parsing command line arguments. A second method (getopt()) allows more complex argument processing by delegation to Johan Vroman's Getopt::Long module. AppConfig also allows variables to be set by parameters passed to a CGI script via the URL (GET method). http://www.nowhere.com/cgi-bin/myapp?verbose&site=kfs =head1 PREREQUISITES AppConfig requires Perl 5.005 or later. The L<Getopt::Long> and L<Test::More> modules should be installed. If you are using a recent version of Perl (e.g. 5.8.0) then these should already be installed. =head1 OBTAINING AND INSTALLING THE AppConfig MODULE BUNDLE The AppConfig module bundle is available from CPAN. As the 'perlmod' manual page explains: CPAN stands for the Comprehensive Perl Archive Network. This is a globally replicated collection of all known Perl materials, including hundreds of unbundled modules. [...] For an up-to-date listing of CPAN sites, see http://www.perl.com/perl/ or ftp://ftp.perl.com/perl/ . Within the CPAN archive, AppConfig is in the category: 12) Option, Argument, Parameter and Configuration File Processing The module is available in the following directories: /modules/by-module/AppConfig/AppConfig-<version>.tar.gz /authors/id/ABW/AppConfig-<version>.tar.gz AppConfig is distributed as a single gzipped tar archive file: AppConfig-<version>.tar.gz Note that "<version>" represents the current AppConfig version number, of the form "n.nn", e.g. "3.14". See the REVISION section below to determine the current version number for AppConfig. Unpack the archive to create a AppConfig installation directory: gunzip AppConfig-<version>.tar.gz tar xvf AppConfig-<version>.tar 'cd' into that directory, make, test and install the modules: cd AppConfig-<version> perl Makefile.PL make make test make install The 't' sub-directory contains a number of test scripts that are run when a 'make test' is run. The 'make install' will install the module on your system. You may need administrator privileges to perform this task. If you install the module in a local directory (for example, by executing "perl Makefile.PL LIB=~/lib" in the above - see C<perldoc MakeMaker> for full details), you will need to ensure that the PERL5LIB environment variable is set to include the location, or add a line to your scripts explicitly naming the library location: use lib '/local/path/to/lib'; The 'examples' sub-directory contains some simple examples of using the AppConfig modules. =head1 DESCRIPTION =head2 USING THE AppConfig MODULE To import and use the L<AppConfig> module the following line should appear in your Perl script: use AppConfig; To import constants defined by the AppConfig module, specify the name of one or more of the constant or tag sets as parameters to C<use>: use AppConfig qw(:expand :argcount); See L<CONSTANT DEFINITIONS> below for more information on the constant tagsets defined by AppConfig. AppConfig is implemented using object-oriented methods. A new AppConfig object is created and initialized using the new() method. This returns a reference to a new AppConfig object. my $config = AppConfig->new(); This will create and return a reference to a new AppConfig object. In doing so, the AppConfig object also creates an internal reference to an AppConfig::State object in which to store variable state. All arguments passed into the AppConfig constructor are passed directly to the AppConfig::State constructor. The first (optional) parameter may be a reference to a hash array containing configuration information. my $config = AppConfig->new( { CASE => 1, ERROR => \&my_error, GLOBAL => { DEFAULT => "<unset>", ARGCOUNT => ARGCOUNT_ONE, }, } ); See L<AppConfig::State> for full details of the configuration options available. These are, in brief: =over 4 =item CASE Used to set case sensitivity for variable names (default: off). =item CREATE Used to indicate that undefined variables should be created automatically (default: off). =item GLOBAL Reference to a hash array of global values used by default when defining variables. Valid global values are DEFAULT, ARGCOUNT, EXPAND, VALIDATE and ACTION. =item PEDANTIC Used to indicate that command line and configuration file parsing routines should return immediately on encountering an error. =item ERROR Used to provide a error handling routine. Arguments as per printf(). =back Subsequent parameters may be variable definitions. These are passed to the define() method, described below in L<DEFINING VARIABLES>. my $config = AppConfig->new("foo", "bar", "baz"); my $config = AppConfig->new( { CASE => 1 }, qw(foo bar baz) ); Note that any unresolved method calls to AppConfig are automatically delegated to the AppConfig::State object. In practice, it means that it is possible to treat the AppConfig object as if it were an AppConfig::State object: # create AppConfig my $config = AppConfig->new('foo', 'bar'); # methods get passed through to internal AppConfig::State $config->foo(100); $config->set('bar', 200); $config->define('baz'); $config->baz(300); =head2 DEFINING VARIABLES The C<define()> method (delegated to AppConfig::State) is used to pre-declare a variable and specify its configuration. $config->define("foo"); Variables may also be defined directly from the AppConfig new() constructor. my $config = AppConfig->new("foo"); In both simple examples above, a new variable called "foo" is defined. A reference to a hash array may also be passed to specify configuration information for the variable: $config->define("foo", { DEFAULT => 99, ALIAS => 'metavar1', }); Configuration items specified in the GLOBAL option to the module constructor are applied by default when variables are created. e.g. my $config = AppConfig->new( { GLOBAL => { DEFAULT => "<undef>", ARGCOUNT => ARGCOUNT_ONE, } } ); $config->define("foo"); $config->define("bar", { ARGCOUNT => ARGCOUNT_NONE } ); is equivalent to: my $config = AppConfig->new(); $config->define( "foo", { DEFAULT => "<undef>", ARGCOUNT => ARGCOUNT_ONE, } ); $config->define( "bar", DEFAULT => "<undef>", ARGCOUNT => ARGCOUNT_NONE, } ); Multiple variables may be defined in the same call to define(). Configuration hashes for variables can be omitted. $config->define("foo", "bar" => { ALIAS = "boozer" }, "baz"); See L<AppConfig::State> for full details of the configuration options available when defining variables. These are, in brief: =over =item DEFAULT The default value for the variable (default: undef). =item ALIAS One or more (list reference or "list|like|this") alternative names for the variable. =item ARGCOUNT Specifies the number and type of arguments that the variable expects. Constants in C<:expand> tag set define ARGCOUNT_NONE - simple on/off flag (default), ARGCOUNT_ONE - single value, ARGCOUNT_LIST - multiple values accessed via list reference, ARGCOUNT_HASH - hash table, "key=value", accessed via hash reference. =item ARGS Used to provide an argument specification string to pass to Getopt::Long via AppConfig::Getopt. E.g. "=i", ":s", "=s@". This can also be used to implicitly set the ARGCOUNT value (C</^!/> = ARGCOUNT_NONE, C</@/> = ARGCOUNT_LIST, C</%/> = ARGCOUNT_HASH, C</[=:].*/> = ARGCOUNT_ONE) =item EXPAND Specifies which variable expansion policies should be used when parsing configuration files. Constants in C<:expand> tag set define: EXPAND_NONE - no expansion (default) EXPAND_VAR - expand C<$var> or C<$(var)> as other variables EXPAND_UID - expand C<~> and C<~uid> as user's home directory EXPAND_ENV - expand C<${var}> as environment variable EXPAND_ALL - do all expansions. =item VALIDATE Regex which the intended variable value should match or code reference which returns 1 to indicate successful validation (variable may now be set). =item ACTION Code reference to be called whenever variable value changes. =back =head2 COMPACT FORMAT DEFINITION Variables can be specified using a compact format. This is identical to the specification format of Getopt::Long and is of the form: "name|alias|alias<argopts>" The first element indicates the variable name and subsequent ALIAS values may be added, each separated by a vertical bar '|'. The E<lt>argoptsE<gt> element indicates the ARGCOUNT value and may be one of the following; ! ARGCOUNT_NONE =s ARGCOUNT_ONE =s@ ARGCOUNT_LIST =s% ARGCOUNT_HASH Additional constructs supported by Getopt::Long may be specified instead of the "=s" element (e.g. "=f"). The entire E<lt>argoptsE<gt> element is stored in the ARGS parameter for the variable and is passed intact to Getopt::Long when the getopt() method is called. The following examples demonstrate use of the compact format, with their equivalent full specifications: $config->define("foo|bar|baz!"); $config->define( "foo" => { ALIAS => "bar|baz", ARGCOUNT => ARGCOUNT_NONE, }); $config->define("name=s"); $config->define( "name" => { ARGCOUNT => ARGCOUNT_ONE, }); $config->define("file|filelist|f=s@"); $config->define( "file" => { ALIAS => "filelist|f", ARGCOUNT => ARGCOUNT_LIST, }); $config->define("user|u=s%"); $config->define( "user" => { ALIAS => "u", ARGCOUNT => ARGCOUNT_HASH, }); Additional configuration options may be specified by hash reference, as per normal. The compact definition format will override any configuration values provided for ARGS and ARGCOUNT. $config->define("file|filelist|f=s@", { VALIDATE => \&check_file } ); =head2 READING AND MODIFYING VARIABLE VALUES AppConfig defines two methods (via AppConfig::State) to manipulate variable values set($variable, $value); get($variable); Once defined, variables may be accessed directly as object methods where the method name is the same as the variable name. i.e. $config->set("verbose", 1); is equivalent to $config->verbose(1); Note that AppConfig defines the following methods: new(); file(); args(); getopt(); And also, through delegation to AppConfig::State: define() get() set() varlist() If you define a variable with one of the above names, you will not be able to access it directly as an object method. i.e. $config->file(); This will call the file() method, instead of returning the value of the 'file' variable. You can work around this by explicitly calling get() and set() on a variable whose name conflicts: $config->get('file'); or by defining a "safe" alias by which the variable can be accessed: $config->define("file", { ALIAS => "fileopt" }); or $config->define("file|fileopt"); ... $config->fileopt(); Without parameters, the current value of the variable is returned. If a parameter is specified, the variable is set to that value and the result of the set() operation is returned. $config->age(29); # sets 'age' to 29, returns 1 (ok) print $config->age(); # prints "29" The varlist() method can be used to extract a number of variables into a hash array. The first parameter should be a regular expression used for matching against the variable names. my %vars = $config->varlist("^file"); # all "file*" variables A second parameter may be specified (any true value) to indicate that the part of the variable name matching the regex should be removed when copied to the target hash. $config->file_name("/tmp/file"); $config->file_path("/foo:/bar:/baz"); my %vars = $config->varlist("^file_", 1); # %vars: # name => /tmp/file # path => "/foo:/bar:/baz" =head2 READING CONFIGURATION FILES The AppConfig module provides a streamlined interface for reading configuration files with the AppConfig::File module. The file() method automatically loads the AppConfig::File module and creates an object to process the configuration file or files. Variables stored in the internal AppConfig::State are automatically updated with values specified in the configuration file. $config->file($filename); Multiple files may be passed to file() and should indicate the file name or be a reference to an open file handle or glob. $config->file($filename, $filehandle, \*STDIN, ...); The file may contain blank lines and comments (prefixed by '#') which are ignored. Continutation lines may be marked by ending the line with a '\'. # this is a comment callsign = alpha bravo camel delta echo foxtrot golf hipowls \ india juliet kilo llama mike november oscar papa \ quebec romeo sierra tango umbrella victor whiskey \ x-ray yankee zebra Variables that are simple flags and do not expect an argument (ARGCOUNT = ARGCOUNT_NONE) can be specified without any value. They will be set with the value 1, with any value explicitly specified (except "0" and "off") being ignored. The variable may also be specified with a "no" prefix to implicitly set the variable to 0. verbose # on (1) verbose = 1 # on (1) verbose = 0 # off (0) verbose off # off (0) verbose on # on (1) verbose mumble # on (1) noverbose # off (0) Variables that expect an argument (ARGCOUNT = ARGCOUNT_ONE) will be set to whatever follows the variable name, up to the end of the current line (including any continuation lines). An optional equals sign may be inserted between the variable and value for clarity. room = /home/kitchen room /home/bedroom Each subsequent re-definition of the variable value overwrites the previous value. print $config->room(); # prints "/home/bedroom" Variables may be defined to accept multiple values (ARGCOUNT = ARGCOUNT_LIST). Each subsequent definition of the variable adds the value to the list of previously set values for the variable. drink = coffee drink = tea A reference to a list of values is returned when the variable is requested. my $beverages = $config->drink(); print join(", ", @$beverages); # prints "coffee, tea" Variables may also be defined as hash lists (ARGCOUNT = ARGCOUNT_HASH). Each subsequent definition creates a new key and value in the hash array. alias l="ls -CF" alias e="emacs" A reference to the hash is returned when the variable is requested. my $aliases = $config->alias(); foreach my $k (keys %$aliases) { print "$k => $aliases->{ $k }\n"; } The '-' prefix can be used to reset a variable to its default value and the '+' prefix can be used to set it to 1 -verbose +debug =head2 VARIABLE EXPANSION Variable values may contain references to other AppConfig variables, environment variables and/or users' home directories. These will be expanded depending on the EXPAND value for each variable or the GLOBAL EXPAND value. Three different expansion types may be applied: bin = ~/bin # expand '~' to home dir if EXPAND_UID tmp = ~abw/tmp # as above, but home dir for user 'abw' perl = $bin/perl # expand value of 'bin' variable if EXPAND_VAR ripl = $(bin)/ripl # as above with explicit parens home = ${HOME} # expand HOME environment var if EXPAND_ENV See L<AppConfig::State> for more information on expanding variable values. The configuration files may have variables arranged in blocks. A block header, consisting of the block name in square brackets, introduces a configuration block. The block name and an underscore are then prefixed to the names of all variables subsequently referenced in that block. The block continues until the next block definition or to the end of the current file. [block1] foo = 10 # block1_foo = 10 [block2] foo = 20 # block2_foo = 20 =head2 PARSING COMMAND LINE OPTIONS There are two methods for processing command line options. The first, args(), is a small and efficient implementation which offers basic functionality. The second, getopt(), offers a more powerful and complete facility by delegating the task to Johan Vroman's Getopt::Long module. The trade-off between args() and getopt() is essentially one of speed/size against flexibility. Use as appropriate. Both implement on-demand loading of modules and incur no overhead until used. The args() method is used to parse simple command line options. It automatically loads the AppConfig::Args module and creates an object to process the command line arguments. Variables stored in the internal AppConfig::State are automatically updated with values specified in the arguments. The method should be passed a reference to a list of arguments to parse. The @ARGV array is used if args() is called without parameters. $config->args(\@myargs); $config->args(); # uses @ARGV Arguments are read and shifted from the array until the first is encountered that is not prefixed by '-' or '--'. At that point, the method returns 1 to indicate success, leaving any unprocessed arguments remaining in the list. Each argument should be the name or alias of a variable prefixed by '-' or '--'. Arguments that are not prefixed as such (and are not an additional parameter to a previous argument) will cause a warning to be raised. If the PEDANTIC option is set, the method will return 0 immediately. With PEDANTIC unset (default), the method will continue to parse the rest of the arguments, returning 0 when done. If the variable is a simple flag (ARGCOUNT = ARGCOUNT_NONE) then it is set to the value 1. The variable may be prefixed by "no" to set its value to 0. myprog -verbose --debug -notaste # $config->verbose(1) # $config->debug(1) # $config->taste(0) Variables that expect an additional argument (ARGCOUNT != 0) will be set to the value of the argument following it. myprog -f /tmp/myfile # $config->file('/tmp/file'); Variables that expect multiple values (ARGCOUNT = ARGCOUNT_LIST or ARGCOUNT_HASH) will have successive values added each time the option is encountered. myprog -file /tmp/foo -file /tmp/bar # $config->file('/tmp/foo') # $config->file('/tmp/bar') # file => [ '/tmp/foo', '/tmp/bar' ] myprog -door "jim=Jim Morrison" -door "ray=Ray Manzarek" # $config->door("jim=Jim Morrison"); # $config->door("ray=Ray Manzarek"); # door => { 'jim' => 'Jim Morrison', 'ray' => 'Ray Manzarek' } See L<AppConfig::Args> for further details on parsing command line arguments. The getopt() method provides a way to use the power and flexibility of the Getopt::Long module to parse command line arguments and have the internal values of the AppConfig object updates automatically. The first (non-list reference) parameters may contain a number of configuration string to pass to Getopt::Long::Configure. A reference to a list of arguments may additionally be passed or @ARGV is used by default. $config->getopt(); # uses @ARGV $config->getopt(\@myargs); $config->getopt(qw(auto_abbrev debug)); # uses @ARGV $config->getopt(qw(debug), \@myargs); See Getopt::Long for details of the configuration options available. The getopt() method constructs a specification string for each internal variable and then initializes Getopt::Long with these values. The specification string is constructed from the name, any aliases (delimited by a vertical bar '|') and the value of the ARGS parameter. $config->define("foo", { ARGS => "=i", ALIAS => "bar|baz", }); # Getopt::Long specification: "foo|bar|baz=i" Errors and warning generated by the Getopt::Long module are trapped and handled by the AppConfig error handler. This may be a user-defined routine installed with the ERROR configuration option. Please note that the AppConfig::Getopt interface is still experimental and may not be 100% operational. This is almost undoubtedly due to problems in AppConfig::Getopt rather than Getopt::Long. =head2 PARSING CGI PARAMETERS The cgi() method provides an interface to the AppConfig::CGI module for updating variable values based on the parameters appended to the URL for a CGI script. This is commonly known as the CGI "GET" method. The CGI "POST" method is currently not supported. Parameter definitions are separated from the CGI script name by a question mark and from each other by ampersands. Where variables have specific values, these are appended to the variable with an equals sign: http://www.here.com/cgi-bin/myscript?foo=bar&baz=qux&verbose # $config->foo('bar'); # $config->baz('qux'); # $config->verbose(1); Certain values specified in a URL must be escaped in the appropriate manner (see CGI specifications at http://www.w3c.org/ for full details). The AppConfig::CGI module automatically unescapes the CGI query string to restore the parameters to their intended values. http://where.com/mycgi?title=%22The+Wrong+Trousers%22 # $config->title('"The Wrong Trousers"'); Please be considerate of the security implications of providing writable access to script variables via CGI. http://rebel.alliance.com/cgi-bin/... .../send_report?file=%2Fetc%2Fpasswd&email=darth%40empire.com To avoid any accidental or malicious changing of "private" variables, define only the "public" variables before calling the cgi() (or any other) method. Further variables can subsequently be defined which can not be influenced by the CGI parameters. $config->define('verbose', 'debug') $config->cgi(); # can only set verbose and debug $config->define('email', 'file'); $config->file($cfgfile); # can set verbose, debug, email + file =head1 CONSTANT DEFINITIONS A number of constants are defined by the AppConfig module. These may be accessed directly (e.g. AppConfig::EXPAND_VARS) or by first importing them into the caller's package. Constants are imported by specifying their names as arguments to C<use AppConfig> or by importing a set of constants identified by its "tag set" name. use AppConfig qw(ARGCOUNT_NONE ARGCOUNT_ONE); use AppConfig qw(:argcount); The following tag sets are defined: =over 4 =item :expand The ':expand' tagset defines the following constants: EXPAND_NONE EXPAND_VAR EXPAND_UID EXPAND_ENV EXPAND_ALL # EXPAND_VAR | EXPAND_UID | EXPAND_ENV EXPAND_WARN See AppConfig::File for full details of the use of these constants. =item :argcount The ':argcount' tagset defines the following constants: ARGCOUNT_NONE ARGCOUNT_ONE ARGCOUNT_LIST ARGCOUNT_HASH See AppConfig::State for full details of the use of these constants. =back =head1 REPOSITORY L<https://github.com/neilbowers/AppConfig> =head1 AUTHOR Andy Wardley, E<lt>abw@wardley.orgE<gt> With contributions from Dave Viner, Ijon Tichy, Axel Gerstmair and many others whose names have been lost to the sands of time (reminders welcome). =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L<AppConfig::State>, L<AppConfig::File>, L<AppConfig::Args>, L<AppConfig::Getopt>, L<AppConfig::CGI>, L<Getopt::Long> =cut PK�������!�"h �� ��%��perl5/Date/Language/Russian_cp1251.pmnu�7m��������## ## Russian cp1251 ## package Date::Language::Russian_cp1251; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @DoW = qw( ); @MoY = qw( ); @DoWs = qw( ); #@DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = ('e') x 31; #@Dsuf[11,12,13] = qw( ); #@Dsuf[30,31] = qw( ); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2de",$_[0]->[3]) } 1; PK�������!�{K �� ��$��perl5/Date/Language/Russian_koi8r.pmnu�7m��������## ## Russian koi8r ## package Date::Language::Russian_koi8r; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @DoW = qw( ); @MoY = qw( ); @DoWs = qw( ); #@DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = ('e') x 31; #@Dsuf[11,12,13] = qw( ); #@Dsuf[30,31] = qw( ); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2de",$_[0]->[3]) } 1; PK�������!�?Ob��b����perl5/Date/Language/Swedish.pmnu�6$��������## ## Swedish tables ## Contributed by Matthew Musgrove <muskrat@mindless.com> ## Corrected by dempa ## package Date::Language::Swedish; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(januari februari mars april maj juni juli augusti september oktober november december); @MoYs = map { substr($_,0,3) } @MoY; @DoW = map($_ . "dagen", qw(sn mn tis ons tors fre lr)); @DoWs = map { substr($_,0,2) } @DoW; # the ordinals are not typically used in modern times @Dsuf = ('a' x 2, 'e' x 29); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2de",$_[0]->[3]) } 1; PK�������!� �� �� ��perl5/Date/Language/Bulgarian.pmnu�6$��������## ## Bulgarian tables contributed by Krasimir Berov ## package Date::Language::Bulgarian; use strict; use warnings; use utf8; use base qw(Date::Language); our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW, $VERSION); $VERSION = "1.01"; @DoW = qw(неделя понеделник вторник сряда четвъртък петък събота); @MoY = qw(януари февруари март април май юни юли август септември октомври ноември декември); @DoWs = qw(нд пн вт ср чт пт сб); @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = (qw(ти ви ри ти ти ти ти ми ми ти)) x 3; @Dsuf[11,12,13] = qw(ти ти ти); @Dsuf[30,31] = qw(ти ви); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { ($_[0]->[3]<10?' ':'').$_[0]->[3].$Dsuf[$_[0]->[3]] } 1; __END__ =encoding utf8 =head1 NAME Date::Language::Bulgarian - localization for Date::Format =head1 DESCRIPTION This is Bulgarian localization for Date::Format. It is important to note that this module source code is in utf8. All strings which it outputs are in utf8, so it is safe to use it currently only with English. You are left alone to try and convert the output when using different Date::Language::* in the same application. This should be addresed in the future. =head1 SYNOPSIS use strict; use warnings; use Date::Language; local $\=$/; my $template ='%a %b %e %T %Y (%Y-%m-%d %H:%M:%S)'; my $time=1290883821; #or just use time(); my @lt = localtime($time); my %languages = qw(English GMT German EEST Bulgarian EET); binmode(select,':utf8'); foreach my $l(keys %languages){ my $lang = Date::Language->new($l); my $zone = $languages{$l}; print $/. "$l $zone"; print $lang->time2str($template, $time); print $lang->time2str($template, $time, $zone); print $lang->strftime($template, \@lt); } =head1 AUTHOR Krasimir Berov (berov@cpan.org) =head1 COPYRIGHT Copyright (c) 2010 Krasimir Berov. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!�Ŵ-������perl5/Date/Language/Oromo.pmnu�6$��������## ## Oromo tables ## package Date::Language::Oromo; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "0.99"; @DoW = qw(Dilbata Wiixata Qibxata Roobii Kamiisa Jimaata Sanbata); @MoY = qw(Amajjii Guraandhala Bitooteessa Elba Caamsa Waxabajjii Adooleessa Hagayya Fuulbana Onkololeessa Sadaasa Muddee); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(WD WB); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�������perl5/Date/Language/English.pmnu�6$��������## ## English tables ## package Date::Language::English; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @DoW = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday); @MoY = qw(January February March April May June July August September October November December); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�po����(��perl5/Date/Language/TigrinyaEthiopian.pmnu�6$��������## ## Tigrinya-Ethiopian tables ## package Date::Language::TigrinyaEthiopian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; if ( $] >= 5.006 ) { @DoW = ( "\x{1230}\x{1295}\x{1260}\x{1275}", "\x{1230}\x{1291}\x{12ed}", "\x{1230}\x{1209}\x{1235}", "\x{1228}\x{1261}\x{12d5}", "\x{1213}\x{1219}\x{1235}", "\x{12d3}\x{122d}\x{1262}", "\x{1240}\x{12f3}\x{121d}" ); @MoY = ( "\x{1303}\x{1295}\x{12e9}\x{12c8}\x{122a}", "\x{134c}\x{1265}\x{1229}\x{12c8}\x{122a}", "\x{121b}\x{122d}\x{127d}", "\x{12a4}\x{1355}\x{1228}\x{120d}", "\x{121c}\x{12ed}", "\x{1301}\x{1295}", "\x{1301}\x{120b}\x{12ed}", "\x{12a6}\x{1308}\x{1235}\x{1275}", "\x{1234}\x{1355}\x{1274}\x{121d}\x{1260}\x{122d}", "\x{12a6}\x{12ad}\x{1270}\x{12cd}\x{1260}\x{122d}", "\x{1296}\x{126c}\x{121d}\x{1260}\x{122d}", "\x{12f2}\x{1234}\x{121d}\x{1260}\x{122d}" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ( "\x{1295}/\x{1230}", "\x{12F5}/\x{1230}" ); @Dsuf = ("\x{12ed}" x 31); } else { @DoW = ( "ሰንበት", "ሰኑይ", "ሰሉስ", "ረቡዕ", "ሓሙስ", "ዓርቢ", "ቀዳም" ); @MoY = ( "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕረል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክተውበር", "ኖቬምበር", "ዲሴምበር" ); @DoWs = map { substr($_,0,9) } @DoW; @MoYs = map { substr($_,0,9) } @MoY; @AMPM = ( "ን/ሰ", "ድ/ሰ" ); @Dsuf = ("ይ" x 31); } @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�_b������perl5/Date/Language/Turkish.pmnu�7m��������#----------------------------------------------------# # # Turkish tables # Burak Grsoy <burak@cpan.org> # Last modified: Sat Nov 15 20:28:32 2003 # # use Date::Language; # my $turkish = Date::Language->new('Turkish'); # print $turkish->time2str("%e %b %Y, %a %T\n", time); # print $turkish->str2time("25 Haz 1996 21:09:55 +0100"); #----------------------------------------------------# package Date::Language::Turkish; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION %DsufMAP); @ISA = qw(Date::Language); $VERSION = "1.0"; @DoW = qw(Pazar Pazartesi Sal aramba Perembe Cuma Cumartesi); @MoY = qw(Ocak ubat Mart Nisan Mays Haziran Temmuz Austos Eyll Ekim Kasm Aralk); @DoWs = map { substr($_,0,3) } @DoW; $DoWs[1] = 'Pzt'; # Since we'll get two 'Paz' s $DoWs[-1] = 'Cmt'; # Since we'll get two 'Cum' s @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ('',''); # no am-pm thingy # not easy as in english... maybe we can just use a dot "." ? :) %DsufMAP = ( (map {$_ => 'inci', $_+10 => 'inci', $_+20 => 'inci' } 1,2,5,8 ), (map {$_ => 'nci', $_+10 => 'nci', $_+20 => 'nci' } 7 ), (map {$_ => 'nci', $_+10 => 'nci', $_+20 => 'nci' } 2 ), (map {$_ => 'nc', $_+10 => 'nc', $_+20 => 'nc' } 3,4 ), (map {$_ => 'uncu', $_+10 => 'uncu', $_+20 => 'uncu' } 9 ), (map {$_ => 'nc', $_+10 => 'nc', $_+20 => 'nc' } 6 ), (map {$_ => 'uncu', } 10,30 ), 20 => 'nci', 31 => 'inci', ); @Dsuf = map{ $DsufMAP{$_} } sort {$a <=> $b} keys %DsufMAP; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[ $_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[ $_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { '' } # disable sub format_P { '' } # disable sub format_o { sprintf("%2d%s",$_[0]->[3],$Dsuf[$_[0]->[3]-1]) } 1; __END__ PK�������!�'`=������perl5/Date/Language/Occitan.pmnu�6$��������## ## Occitan tables, contributed by Quentn PAGÈS ## package Date::Language::Occitan; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.04"; @DoW = qw(dimenge diluns dimars dimècres dijòus divendres dissabte); @MoY = qw(genièr febrièr mars abrial mai junh julhet agost octòbre novembre decembre); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; $MoYs[6] = 'jul'; @AMPM = qw(AM PM); @Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er'); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�������perl5/Date/Language/Chinese.pmnu�6$��������## ## English tables ## package Date::Language::Chinese; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; @DoW = qw(星期日 星期一 星期二 星期三 星期四 星期五 星期六); @MoY = qw(一月 二月 三月 四月 五月 六月 七月 八月 九月 十月 十一月 十二月); @DoWs = map { $_ } @DoW; @MoYs = map { $_ } @MoY; @AMPM = qw(上午 下午); @Dsuf = (qw(日 日 日 日 日 日 日 日 日 日)) x 3; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2d%s",$_[0]->[3],"日") } 1; PK�������!� 9 |��|����perl5/Date/Language/Romanian.pmnu�6$��������## ## Italian tables ## package Date::Language::Romanian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(ianuarie februarie martie aprilie mai iunie iulie august septembrie octombrie noembrie decembrie); @DoW = qw(duminica luni marti miercuri joi vineri sambata); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = ('') x 31; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�H������perl5/Date/Language/Sidama.pmnu�6$��������## ## Sidama tables ## package Date::Language::Sidama; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "0.99"; @DoW = qw(Sambata Sanyo Maakisanyo Roowe Hamuse Arbe Qidaame); @MoY = qw(January February March April May June July August September October November December); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(soodo hawwaro); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�Nzz������perl5/Date/Language/Afar.pmnu�6$��������## ## Afar tables ## package Date::Language::Afar; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "0.99"; @DoW = qw(Acaada Etleeni Talaata Arbaqa Kamiisi Gumqata Sabti); @MoY = ( "Qunxa Garablu", "Kudo", "Ciggilta Kudo", "Agda Baxis", "Caxah Alsa", "Qasa Dirri", "Qado Dirri", "Liiqen", "Waysu", "Diteli", "Ximoli", "Kaxxa Garablu" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(saaku carra); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�1UU��U�� ��perl5/Date/Language/Hungarian.pmnu�6$��������## ## Hungarian tables based on English ## # # This is a just-because-I-stumbled-across-it # -and-my-wife-is-Hungarian release: if Graham or # someone adds to docs to Date::Format, I'd be # glad to correct bugs and extend as neeed. # package Date::Language::Hungarian; =head1 NAME Date::Language::Hungarian - Magyar format for Date::Format =head1 SYNOPSIS my $lang = Date::Language->new('Hungarian'); print $lang->time2str("%a %b %e %T %Y", time); @lt = localtime(time); print $lang->time2str($template, time); print $lang->strftime($template, @lt); print $lang->time2str($template, time, $zone); print $lang->strftime($template, @lt, $zone); print $lang->ctime(time); print $lang->asctime(@lt); print $lang->ctime(time, $zone); print $lang->asctime(@lt, $zone); See L<Date::Format>. =head1 AUTHOR Paula Goddard (paula -at- paulacska -dot- com) =head1 LICENCE Made available under the same terms as Perl itself. =cut use strict; use warnings; use base "Date::Language"; use vars qw( @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); $VERSION = "1.01"; @DoW = qw(Vasrnap Htf Kedd Szerda Cstrtk Pntek Szombat); @MoY = qw(Janur Februr Mrcius prilis Mjus Jnius Jlius Augusztus Szeptember Oktber November December); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(DE. DU.); # There is no 'th or 'nd in Hungarian, just a dot @Dsuf = (".") x 31; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_P { lc($_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0]) } sub format_o { $_[0]->[3].'.' } sub format_D { &format_y . "." . &format_m . "." . &format_d } sub format_y { sprintf("%02d",$_[0]->[5] % 100) } sub format_d { sprintf("%02d",$_[0]->[3]) } sub format_m { sprintf("%02d",$_[0]->[4] + 1) } 1; PK�������!�p½\������perl5/Date/Language/French.pmnu�6$��������## ## French tables, contributed by Emmanuel Bataille (bem@residents.frmug.org) ## package Date::Language::French; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.04"; @DoW = qw(dimanche lundi mardi mercredi jeudi vendredi samedi); @MoY = qw(janvier fvrier mars avril mai juin juillet aot septembre octobre novembre dcembre); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; $MoYs[6] = 'jul'; @AMPM = qw(AM PM); @Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er'); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { $_[0]->[3] } 1; PK�������!�|p0������perl5/Date/Language/Tigrinya.pmnu�6$��������## ## Tigrinya tables ## package Date::Language::Tigrinya; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; @DoW = ( "\x{1230}\x{1295}\x{1260}\x{1275}", "\x{1230}\x{1291}\x{12ed}", "\x{1230}\x{1209}\x{1235}", "\x{1228}\x{1261}\x{12d5}", "\x{1213}\x{1219}\x{1235}", "\x{12d3}\x{122d}\x{1262}", "\x{1240}\x{12f3}\x{121d}" ); @MoY = ( "\x{1303}\x{1295}\x{12e9}\x{12c8}\x{122a}", "\x{134c}\x{1265}\x{1229}\x{12c8}\x{122a}", "\x{121b}\x{122d}\x{127d}", "\x{12a4}\x{1355}\x{1228}\x{120d}", "\x{121c}\x{12ed}", "\x{1301}\x{1295}", "\x{1301}\x{120b}\x{12ed}", "\x{12a6}\x{1308}\x{1235}\x{1275}", "\x{1234}\x{1355}\x{1274}\x{121d}\x{1260}\x{122d}", "\x{12a6}\x{12ad}\x{1270}\x{12cd}\x{1260}\x{122d}", "\x{1296}\x{126c}\x{121d}\x{1260}\x{122d}", "\x{12f2}\x{1234}\x{121d}\x{1260}\x{122d}" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ( "\x{1295}/\x{1230}", "\x{12F5}/\x{1230}" ); @Dsuf = ("\x{12ed}" x 31); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�J������perl5/Date/Language/Amharic.pmnu�6$��������## ## Amharic tables ## package Date::Language::Amharic; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; if ( $] >= 5.006 ) { @DoW = ( "\x{12a5}\x{1211}\x{12f5}", "\x{1230}\x{129e}", "\x{121b}\x{12ad}\x{1230}\x{129e}", "\x{1228}\x{1261}\x{12d5}", "\x{1210}\x{1219}\x{1235}", "\x{12d3}\x{122d}\x{1265}", "\x{1245}\x{12f3}\x{121c}" ); @MoY = ( "\x{1303}\x{1295}\x{12e9}\x{12c8}\x{122a}", "\x{134c}\x{1265}\x{1229}\x{12c8}\x{122a}", "\x{121b}\x{122d}\x{127d}", "\x{12a4}\x{1355}\x{1228}\x{120d}", "\x{121c}\x{12ed}", "\x{1301}\x{1295}", "\x{1301}\x{120b}\x{12ed}", "\x{12a6}\x{1308}\x{1235}\x{1275}", "\x{1234}\x{1355}\x{1274}\x{121d}\x{1260}\x{122d}", "\x{12a6}\x{12ad}\x{1270}\x{12cd}\x{1260}\x{122d}", "\x{1296}\x{126c}\x{121d}\x{1260}\x{122d}", "\x{12f2}\x{1234}\x{121d}\x{1260}\x{122d}" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ( "\x{1320}\x{12cb}\x{1275}", "\x{12a8}\x{1230}\x{12d3}\x{1275}" ); @Dsuf = ("\x{129b}" x 31); } else { @DoW = ( "እሑድ", "ሰኞ", "ማክሰኞ", "ረቡዕ", "ሐሙስ", "ዓርብ", "ቅዳሜ" ); @MoY = ( "ጃንዩወሪ", "ፌብሩወሪ", "ማርች", "ኤፕረል", "ሜይ", "ጁን", "ጁላይ", "ኦገስት", "ሴፕቴምበር", "ኦክተውበር", "ኖቬምበር", "ዲሴምበር" ); @DoWs = map { substr($_,0,9) } @DoW; @MoYs = map { substr($_,0,9) } @MoY; @AMPM = ( "ጠዋት", "ከሰዓት" ); @Dsuf = ("ኛ" x 31); } @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�73����!��perl5/Date/Language/Chinese_GB.pmnu�6$��������## ## English tables ## package Date::Language::Chinese_GB; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @DoW = qw( һ ڶ ); @MoY = qw(һ ʮ ʮһ ʮ); @DoWs = map { $_ } @DoW; @MoYs = map { $_ } @MoY; @AMPM = qw( ); @Dsuf = (qw( )) x 3; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2d%s",$_[0]->[3],"") } 1; PK�������!�C �� �� ��perl5/Date/Language/Icelandic.pmnu�6$��������## ## Icelandic tables ## package Date::Language::Icelandic; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(Janar Febrar Mars Aprl Ma Jni Jli gst September Oktber Nvember Desember); @MoYs = qw(Jan Feb Mar Apr Ma Jn Jl g Sep Okt Nv Des); @DoW = qw(Sunnudagur Mnudagur rijudagur Mivikudagur Fimmtudagur Fstudagur Laugardagur Sunnudagur); @DoWs = qw(Sun Mn ri Mi Fim Fs Lau Sun); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�<������perl5/Date/Language/Italian.pmnu�6$��������## ## Italian tables ## package Date::Language::Italian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(Gennaio Febbraio Marzo Aprile Maggio Giugno Luglio Agosto Settembre Ottobre Novembre Dicembre); @MoYs = qw(Gen Feb Mar Apr Mag Giu Lug Ago Set Ott Nov Dic); @DoW = qw(Domenica Lunedi Martedi Mercoledi Giovedi Venerdi Sabato); @DoWs = qw(Dom Lun Mar Mer Gio Ven Sab); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�tqh��h����perl5/Date/Language/Russian.pmnu�6$��������## ## Russian tables ## ## Contributed by Danil Pismenny <dapi@mail.ru> package Date::Language::Russian; use vars qw(@ISA @DoW @DoWs @MoY @MoYs @MoY2 @AMPM %MoY %DoW $VERSION); @ISA = qw(Date::Language Date::Format::Generic); $VERSION = "1.01"; @MoY = qw( ); @MoY2 = qw( ); @MoYs = qw( ); @DoW = qw( ); @DoWs = qw( ); @DoWs2 = qw( ); @AMPM = qw( ); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_d { $_[0]->[3] } sub format_m { $_[0]->[4] + 1 } sub format_o { $_[0]->[3] . '.' } sub format_Q { $MoY2[$_[0]->[4]] } sub str2time { my ($self,$value) = @_; map {$value=~s/(\s|^)$DoWs2[$_](\s)/$DoWs[$_]$2/ig} (0..6); $value=~s/(\s+|^)(\s+)/$1$2/; return $self->SUPER::str2time($value); } 1; PK�������!�f0���� ��perl5/Date/Language/Norwegian.pmnu�6$��������## ## Norwegian tables ## package Date::Language::Norwegian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(Januar Februar Mars April Mai Juni Juli August September Oktober November Desember); @MoYs = qw(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Des); @DoW = qw(Sndag Mandag Tirsdag Onsdag Torsdag Fredag Lrdag Sndag); @DoWs = qw(Sn Man Tir Ons Tor Fre Lr Sn); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�d������perl5/Date/Language/Czech.pmnu�6$��������## ## Czech tables ## ## Contributed by Honza Pazdziora package Date::Language::Czech; use vars qw(@ISA @DoW @DoWs @MoY @MoYs @MoY2 @AMPM %MoY %DoW $VERSION); @ISA = qw(Date::Language Date::Format::Generic); $VERSION = "1.01"; @MoY = qw(leden nor bezen duben kvten erven ervenec srpen z jen listopad prosinec); @MoYs = qw(led nor be dub kv vn ec srp z j lis pro); @MoY2 = @MoY; for (@MoY2) { s!en$!na! or s!ec$!ce! or s!ad$!adu! or s!or$!ora!; } @DoW = qw(nedle pondl ter steda tvrtek ptek sobota); @DoWs = qw(Ne Po t St t P So); @AMPM = qw(dop. odp.); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_d { $_[0]->[3] } sub format_m { $_[0]->[4] + 1 } sub format_o { $_[0]->[3] . '.' } sub format_Q { $MoY2[$_[0]->[4]] } sub time2str { my $ref = shift; my @a = @_; $a[0] =~ s/(%[do]\.?\s?)%B/$1%Q/; $ref->SUPER::time2str(@a); } sub strftime { my $ref = shift; my @a = @_; $a[0] =~ s/(%[do]\.?\s?)%B/$1%Q/; $ref->SUPER::time2str(@a); } 1; PK�������!��(!p �� ����perl5/Date/Language/Greek.pmnu�6$��������## ## Greek tables ## ## Traditional date format is: DoW DD{eta} MoY Year (%A %o %B %Y) ## ## Matthew Musgrove <muskrat@mindless.com> ## Translations gratiously provided by Menelaos Stamatelos <men@kwsn.net> ## This module returns unicode (utf8) encoded characters. You will need to ## take the necessary steps for this to display correctly. ## package Date::Language::Greek; use utf8; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; @DoW = ( "\x{039a}\x{03c5}\x{03c1}\x{03b9}\x{03b1}\x{03ba}\x{03ae}", "\x{0394}\x{03b5}\x{03c5}\x{03c4}\x{03ad}\x{03c1}\x{03b1}", "\x{03a4}\x{03c1}\x{03af}\x{03c4}\x{03b7}", "\x{03a4}\x{03b5}\x{03c4}\x{03ac}\x{03c1}\x{03c4}\x{03b7}", "\x{03a0}\x{03ad}\x{03bc}\x{03c0}\x{03c4}\x{03b7}", "\x{03a0}\x{03b1}\x{03c1}\x{03b1}\x{03c3}\x{03ba}\x{03b5}\x{03c5}\x{03ae}", "\x{03a3}\x{03ac}\x{03b2}\x{03b2}\x{03b1}\x{03c4}\x{03bf}", ); @MoY = ( "\x{0399}\x{03b1}\x{03bd}\x{03bf}\x{03c5}\x{03b1}\x{03c1}\x{03af}\x{03bf}\x{03c5}", "\x{03a6}\x{03b5}\x{03b2}\x{03c1}\x{03bf}\x{03c5}\x{03b1}\x{03c1}\x{03af}\x{03bf}\x{03c5}", "\x{039c}\x{03b1}\x{03c1}\x{03c4}\x{03af}\x{03bf}\x{03c5}", "\x{0391}\x{03c0}\x{03c1}\x{03b9}\x{03bb}\x{03af}\x{03c5}", "\x{039c}\x{03b1}\x{0390}\x{03bf}\x{03c5}", "\x{0399}\x{03bf}\x{03c5}\x{03bd}\x{03af}\x{03bf}\x{03c5}", "\x{0399}\x{03bf}\x{03c5}\x{03bb}\x{03af}\x{03bf}\x{03c5}", "\x{0391}\x{03c5}\x{03b3}\x{03bf}\x{03cd}\x{03c3}\x{03c4}\x{03bf}\x{03c5}", "\x{03a3}\x{03b5}\x{03c0}\x{03c4}\x{03b5}\x{03bc}\x{03c4}\x{03bf}\x{03c5}", "\x{039f}\x{03ba}\x{03c4}\x{03c9}\x{03b2}\x{03c1}\x{03af}\x{03bf}\x{03c5}", "\x{039d}\x{03bf}\x{03b5}\x{03bc}\x{03b2}\x{03c1}\x{03af}\x{03bf}\x{03c5}", "\x{0394}\x{03b5}\x{03ba}\x{03b5}\x{03bc}\x{03b2}\x{03c1}\x{03bf}\x{03c5}", ); @DoWs = ( "\x{039a}\x{03c5}", "\x{0394}\x{03b5}", "\x{03a4}\x{03c1}", "\x{03a4}\x{03b5}", "\x{03a0}\x{03b5}", "\x{03a0}\x{03b1}", "\x{03a3}\x{03b1}", ); @MoYs = ( "\x{0399}\x{03b1}\x{03bd}", "\x{03a6}\x{03b5}", "\x{039c}\x{03b1}\x{03c1}", "\x{0391}\x{03c0}\x{03c1}", "\x{039c}\x{03b1}", "\x{0399}\x{03bf}\x{03c5}\x{03bd}", "\x{0399}\x{03bf}\x{03c5}\x{03bb}", "\x{0391}\x{03c5}\x{03b3}", "\x{03a3}\x{03b5}\x{03c0}", "\x{039f}\x{03ba}", "\x{039d}\x{03bf}", "\x{0394}\x{03b5}", ); @AMPM = ("\x{03c0}\x{03bc}", "\x{03bc}\x{03bc}"); @Dsuf = ("\x{03b7}" x 31); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_o { sprintf("%2d%s",$_[0]->[3],"\x{03b7}") } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�7m������perl5/Date/Language/Spanish.pmnu�6$��������## ## Spanish tables ## package Date::Language::Spanish; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; @DoW = qw(domingo lunes martes mircoles jueves viernes sbado); @MoY = qw(enero febrero marzo abril mayo junio julio agosto septiembre octubre noviembre diciembre); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = ((qw(ro do ro to to to mo vo no mo)) x 3, 'ro'); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�<6p��p��'��perl5/Date/Language/TigrinyaEritrean.pmnu�6$��������## ## Tigrinya-Eritrean tables ## package Date::Language::TigrinyaEritrean; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.00"; if ( $] >= 5.006 ) { @DoW = ( "\x{1230}\x{1295}\x{1260}\x{1275}", "\x{1230}\x{1291}\x{12ed}", "\x{1230}\x{1209}\x{1235}", "\x{1228}\x{1261}\x{12d5}", "\x{1213}\x{1219}\x{1235}", "\x{12d3}\x{122d}\x{1262}", "\x{1240}\x{12f3}\x{121d}" ); @MoY = ( "\x{1303}\x{1295}\x{12e9}\x{12c8}\x{122a}", "\x{134c}\x{1265}\x{1229}\x{12c8}\x{122a}", "\x{121b}\x{122d}\x{127d}", "\x{12a4}\x{1355}\x{1228}\x{120d}", "\x{121c}\x{12ed}", "\x{1301}\x{1295}", "\x{1301}\x{120b}\x{12ed}", "\x{12a6}\x{1308}\x{1235}\x{1275}", "\x{1234}\x{1355}\x{1274}\x{121d}\x{1260}\x{122d}", "\x{12a6}\x{12ad}\x{1270}\x{12cd}\x{1260}\x{122d}", "\x{1296}\x{126c}\x{121d}\x{1260}\x{122d}", "\x{12f2}\x{1234}\x{121d}\x{1260}\x{122d}" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ( "\x{1295}/\x{1230}", "\x{12F5}/\x{1230}" ); @Dsuf = ("\x{12ed}" x 31); } else { @DoW = ( "ሰንበት", "ሰኑይ", "ሰሉስ", "ረቡዕ", "ሓሙስ", "ዓርቢ", "ቀዳም" ); @MoY = ( "ጥሪ", "ለካቲት", "መጋቢት", "ሚያዝያ", "ግንቦት", "ሰነ", "ሓምለ", "ነሓሰ", "መስከረም", "ጥቅምቲ", "ሕዳር", "ታሕሳስ" ); @DoWs = map { substr($_,0,9) } @DoW; @MoYs = map { substr($_,0,9) } @MoY; @AMPM = ( "ን/ሰ", "ድ/ሰ" ); @Dsuf = ("ይ" x 31); } @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�4E&S���� ��perl5/Date/Language/Brazilian.pmnu�6$��������## ## Brazilian tables, contributed by Christian Tosta (tosta@cce.ufmg.br) ## package Date::Language::Brazilian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @DoW = qw(Domingo Segunda Tera Quarta Quinta Sexta Sbado); @MoY = qw(Janeiro Fevereiro Maro Abril Maio Junho Julho Agosto Setembro Outubro Novembro Dezembro); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = (qw(mo ro do ro to to to mo vo no)) x 3; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�D`������perl5/Date/Language/Finnish.pmnu�6$��������## ## Finnish tables ## Contributed by Matthew Musgrove <muskrat@mindless.com> ## Corrected by roke ## package Date::Language::Finnish; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; # In Finnish, the names of the months and days are only capitalized at the beginning of sentences. @MoY = map($_ . "kuu", qw(tammi helmi maalis huhti touko kes hein elo syys loka marras joulu)); @DoW = qw(sunnuntai maanantai tiistai keskiviikko torstai perjantai lauantai); # it is not customary to use abbreviated names of months or days # per Graham's suggestion: @MoYs = @MoY; @DoWs = @DoW; # the short form of ordinals @Dsuf = ('.') x 31; # doesn't look like this is normally used... @AMPM = qw(ap ip); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2de",$_[0]->[3]) } 1;PK�������!�kEY4(��(����perl5/Date/Language/Gedeo.pmnu�6$��������## ## Gedeo tables ## package Date::Language::Gedeo; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "0.99"; @DoW = qw( Sanbbattaa Sanno Masano Roobe Hamusse Arbe Qiddamme); @MoY = ( "Oritto", "Birre'a", "Onkkollessa", "Saddasa", "Arrasa", "Qammo", "Ella", "Waacibajje", "Canissa", "Addolessa", "Bittitotessa", "Hegeya" ); @DoWs = map { substr($_,0,3) } @DoW; $DoWs[0] = "Snb"; $DoWs[1] = "Sno"; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(gorsa warreti-udumma); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�� �� ����perl5/Date/Language/Danish.pmnu�6$��������## ## Danish tables ## package Date::Language::Danish; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(Januar Februar Marts April Maj Juni Juli August September Oktober November December); @MoYs = qw(Jan Feb Mar Apr Maj Jun Jul Aug Sep Okt Nov Dec); @DoW = qw(Sndag Mandag Tirsdag Onsdag Torsdag Fredag Lrdag Sndag); @DoWs = qw(Sn Man Tir Ons Tor Fre Lr Sn); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�ϣ������perl5/Date/Language/German.pmnu�6$��������## ## German tables ## package Date::Language::German; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.02"; @MoY = qw(Januar Februar Mrz April Mai Juni Juli August September Oktober November Dezember); @MoYs = qw(Jan Feb Mr Apr Mai Jun Jul Aug Sep Okt Nov Dez); @DoW = qw(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag); @DoWs = qw(Son Mon Die Mit Don Fre Sam); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2d.",$_[0]->[3]) } 1; PK�������!�U!>��>����perl5/Date/Language/Dutch.pmnu�6$��������## ## Dutch tables ## Contributed by Johannes la Poutre <jlpoutre@corp.nl.home.com> ## package Date::Language::Dutch; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.02"; @MoY = qw(januari februari maart april mei juni juli augustus september oktober november december); @MoYs = map(substr($_, 0, 3), @MoY); $MoYs[2] = 'mrt'; # mrt is more common (Frank Maas) @DoW = map($_ . "dag", qw(zon maan dins woens donder vrij zater)); @DoWs = map(substr($_, 0, 2), @DoW); # these aren't normally used... @AMPM = qw(VM NM); @Dsuf = ('e') x 31; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_o { sprintf("%2de",$_[0]->[3]) } 1; PK�������!�i[Wj������perl5/Date/Language/Somali.pmnu�6$��������## ## Somali tables ## package Date::Language::Somali; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "0.99"; @DoW = qw(Axad Isniin Salaaso Arbaco Khamiis Jimco Sabti); @MoY = ( "Bisha Koobaad", "Bisha Labaad", "Bisha Saddexaad", "Bisha Afraad", "Bisha Shanaad", "Bisha Lixaad", "Bisha Todobaad", "Bisha Sideedaad", "Bisha Sagaalaad", "Bisha Tobnaad", "Bisha Kow iyo Tobnaad", "Bisha Laba iyo Tobnaad" ); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = ( "Kob", "Lab", "Sad", "Afr", "Sha", "Lix", "Tod", "Sid", "Sag", "Tob", "KIT", "LIT" ); @AMPM = qw(SN GN); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�҅������perl5/Date/Language/Austrian.pmnu�6$��������## ## Austrian tables ## package Date::Language::Austrian; use Date::Language (); use vars qw(@ISA @DoW @DoWs @MoY @MoYs @AMPM @Dsuf %MoY %DoW $VERSION); @ISA = qw(Date::Language); $VERSION = "1.01"; @MoY = qw(Jnner Feber Mrz April Mai Juni Juli August September Oktober November Dezember); @MoYs = qw(Jn Feb Mr Apr Mai Jun Jul Aug Sep Oct Nov Dez); @DoW = qw(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag); @DoWs = qw(Son Mon Die Mit Don Fre Sam); use Date::Language::English (); @AMPM = @{Date::Language::English::AMPM}; @Dsuf = @{Date::Language::English::Dsuf}; @MoY{@MoY} = (0 .. scalar(@MoY)); @MoY{@MoYs} = (0 .. scalar(@MoYs)); @DoW{@DoW} = (0 .. scalar(@DoW)); @DoW{@DoWs} = (0 .. scalar(@DoWs)); # Formatting routines sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } 1; PK�������!�UN#��#����perl5/Date/Parse.pmnu�6$��������# Copyright (c) 1995-2009 Graham Barr. This program is free # software; you can redistribute it and/or modify it under the same terms # as Perl itself. package Date::Parse; require 5.000; use strict; use vars qw($VERSION @ISA @EXPORT); use Time::Local; use Carp; use Time::Zone; use Exporter; @ISA = qw(Exporter); @EXPORT = qw(&strtotime &str2time &strptime); $VERSION = "2.33"; my %month = ( january => 0, february => 1, march => 2, april => 3, may => 4, june => 5, july => 6, august => 7, september => 8, sept => 8, october => 9, november => 10, december => 11, ); my %day = ( sunday => 0, monday => 1, tuesday => 2, tues => 2, wednesday => 3, wednes => 3, thursday => 4, thur => 4, thurs => 4, friday => 5, saturday => 6, ); my @suf = (qw(th st nd rd th th th th th th)) x 3; @suf[11,12,13] = qw(th th th); #Abbreviations map { $month{substr($_,0,3)} = $month{$_} } keys %month; map { $day{substr($_,0,3)} = $day{$_} } keys %day; my $strptime = <<'ESQ'; my %month = map { lc $_ } %$mon_ref; my $daypat = join("|", map { lc $_ } reverse sort keys %$day_ref); my $monpat = join("|", reverse sort keys %month); my $sufpat = join("|", reverse sort map { lc $_ } @$suf_ref); my %ampm = ( 'a' => 0, # AM 'p' => 12, # PM ); my($AM, $PM) = (0,12); sub { my $dtstr = lc shift; my $merid = 24; my($century,$year,$month,$day,$hh,$mm,$ss,$zone,$dst,$frac); $zone = tz_offset(shift) if @_; 1 while $dtstr =~ s#\([^\(\)]*\)# #o; $dtstr =~ s#(\A|\n|\Z)# #sog; # ignore day names $dtstr =~ s#([\d\w\s])[\.\,]\s#$1 #sog; $dtstr =~ s/,/ /g; $dtstr =~ s#($daypat)\s*(den\s)?\b# #o; # Time: 12:00 or 12:00:00 with optional am/pm return unless $dtstr =~ /\S/; if ($dtstr =~ s/\s(\d{4})([-:]?)(\d\d?)\2(\d\d?)(?:[-Tt ](\d\d?)(?:([-:]?)(\d\d?)(?:\6(\d\d?)(?:[.,](\d+))?)?)?)?(?=\D)/ /) { ($year,$month,$day,$hh,$mm,$ss,$frac) = ($1,$3-1,$4,$5,$7,$8,$9); } unless (defined $hh) { if ($dtstr =~ s#[:\s](\d\d?):(\d\d?)(:(\d\d?)(?:\.\d+)?)?(z)?\s*(?:([ap])\.?m?\.?)?\s# #o) { ($hh,$mm,$ss) = ($1,$2,$4); $zone = 0 if $5; $merid = $ampm{$6} if $6; } # Time: 12 am elsif ($dtstr =~ s#\s(\d\d?)\s*([ap])\.?m?\.?\s# #o) { ($hh,$mm,$ss) = ($1,0,0); $merid = $ampm{$2}; } } if (defined $hh and $hh <= 12 and $dtstr =~ s# ([ap])\.?m?\.?\s# #o) { $merid = $ampm{$1}; } unless (defined $year) { # Date: 12-June-96 (using - . or /) if ($dtstr =~ s#\s(\d\d?)([\-\./])($monpat)(\2(\d\d+))?\s# #o) { ($month,$day) = ($month{$3},$1); $year = $5 if $5; } # Date: 12-12-96 (using '-', '.' or '/' ) elsif ($dtstr =~ s#\s(\d+)([\-\./])(\d\d?)(\2(\d+))?\s# #o) { ($month,$day) = ($1 - 1,$3); if ($5) { $year = $5; # Possible match for 1995-01-24 (short mainframe date format); ($year,$month,$day) = ($1, $3 - 1, $5) if $month > 12; return if length($year) > 2 and $year < 1901; } } elsif ($dtstr =~ s#\s(\d+)\s*($sufpat)?\s*($monpat)# #o) { ($month,$day) = ($month{$3},$1); } elsif ($dtstr =~ s#($monpat)\s*(\d+)\s*($sufpat)?\s# #o) { ($month,$day) = ($month{$1},$2); } elsif ($dtstr =~ s#($monpat)([\/-])(\d+)[\/-]# #o) { ($month,$day) = ($month{$1},$3); } # Date: 961212 elsif ($dtstr =~ s#\s(\d\d)(\d\d)(\d\d)\s# #o) { ($year,$month,$day) = ($1,$2-1,$3); } $year = $1 if !defined($year) and $dtstr =~ s#\s(\d{2}(\d{2})?)[\s\.,]# #o; } # Zone $dst = 1 if $dtstr =~ s#\bdst\b##o; if ($dtstr =~ s#\s"?([a-z]{3,4})(dst|\d+[a-z]*|_[a-z]+)?"?\s# #o) { $dst = 1 if $2 and $2 eq 'dst'; $zone = tz_offset($1); return unless defined $zone; } elsif ($dtstr =~ s#\s([a-z]{3,4})?([\-\+]?)-?(\d\d?):?(\d\d)?(00)?\s# #o) { my $m = defined($4) ? "$2$4" : 0; my $h = "$2$3"; $zone = defined($1) ? tz_offset($1) : 0; return unless defined $zone; $zone += 60 * ($m + (60 * $h)); } if ($dtstr =~ /\S/) { # now for some dumb dates if ($dtstr =~ s/^\s*(ut?|z)\s*$//) { $zone = 0; } elsif ($dtstr =~ s#\s([a-z]{3,4})?([\-\+]?)-?(\d\d?)(\d\d)?(00)?\s# #o) { my $m = defined($4) ? "$2$4" : 0; my $h = "$2$3"; $zone = defined($1) ? tz_offset($1) : 0; return unless defined $zone; $zone += 60 * ($m + (60 * $h)); } return if $dtstr =~ /\S/o; } if (defined $hh) { if ($hh == 12) { $hh = 0 if $merid == $AM; } elsif ($merid == $PM) { $hh += 12; } } if (defined $year && $year > 1900) { $century = int($year / 100); $year -= 1900; } $zone += 3600 if defined $zone && $dst; $ss += "0.$frac" if $frac; return ($ss,$mm,$hh,$day,$month,$year,$zone,$century); } ESQ use vars qw($day_ref $mon_ref $suf_ref $obj); sub gen_parser { local($day_ref,$mon_ref,$suf_ref,$obj) = @_; if($obj) { my $obj_strptime = $strptime; substr($obj_strptime,index($strptime,"sub")+6,0) = <<'ESQ'; shift; # package ESQ my $sub = eval "$obj_strptime" or die $@; return $sub; } eval "$strptime" or die $@; } *strptime = gen_parser(\%day,\%month,\@suf); sub str2time { my @t = strptime(@_); return undef unless @t; my($ss,$mm,$hh,$day,$month,$year,$zone, $century) = @t; my @lt = localtime(time); $hh ||= 0; $mm ||= 0; $ss ||= 0; my $frac = $ss - int($ss); $ss = int $ss; $month = $lt[4] unless(defined $month); $day = $lt[3] unless(defined $day); $year = ($month > $lt[4]) ? ($lt[5] - 1) : $lt[5] unless(defined $year); # we were given a 4 digit year, so let's keep using those $year += 1900 if defined $century; return undef unless($month <= 11 && $day >= 1 && $day <= 31 && $hh <= 23 && $mm <= 59 && $ss <= 59); my $result; if (defined $zone) { $result = eval { local $SIG{__DIE__} = sub {}; # Ick! timegm($ss,$mm,$hh,$day,$month,$year); }; return undef if !defined $result or $result == -1 && join("",$ss,$mm,$hh,$day,$month,$year) ne "595923311169"; $result -= $zone; } else { $result = eval { local $SIG{__DIE__} = sub {}; # Ick! timelocal($ss,$mm,$hh,$day,$month,$year); }; return undef if !defined $result or $result == -1 && join("",$ss,$mm,$hh,$day,$month,$year) ne join("",(localtime(-1))[0..5]); } return $result + $frac; } 1; __END__ =head1 NAME Date::Parse - Parse date strings into time values =head1 SYNOPSIS use Date::Parse; $time = str2time($date); ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date); =head1 DESCRIPTION C<Date::Parse> provides two routines for parsing date strings into time values. =over 4 =item str2time(DATE [, ZONE]) C<str2time> parses C<DATE> and returns a unix time value, or undef upon failure. C<ZONE>, if given, specifies the timezone to assume when parsing if the date string does not specify a timezone. =item strptime(DATE [, ZONE]) C<strptime> takes the same arguments as str2time but returns an array of values C<($ss,$mm,$hh,$day,$month,$year,$zone,$century)>. Elements are only defined if they could be extracted from the date string. The C<$zone> element is the timezone offset in seconds from GMT. An empty array is returned upon failure. =back =head1 MULTI-LANGUAGE SUPPORT Date::Parse is capable of parsing dates in several languages, these include English, French, German and Italian. $lang = Date::Language->new('German'); $lang->str2time("25 Jun 1996 21:09:55 +0100"); =head1 EXAMPLE DATES Below is a sample list of dates that are known to be parsable with Date::Parse 1995:01:24T09:08:17.1823213 ISO-8601 1995-01-24T09:08:17.1823213 Wed, 16 Jun 94 07:29:35 CST Comma and day name are optional Thu, 13 Oct 94 10:13:13 -0700 Wed, 9 Nov 1994 09:50:32 -0500 (EST) Text in ()'s will be ignored. 21 dec 17:05 Will be parsed in the current time zone 21-dec 17:05 21/dec 17:05 21/dec/93 17:05 1999 10:02:18 "GMT" 16 Nov 94 22:28:20 PST =head1 LIMITATION Date::Parse uses L<Time::Local> internally, so is limited to only parsing dates which result in valid values for Time::Local::timelocal. This generally means dates between 1901-12-17 00:00:00 GMT and 2038-01-16 23:59:59 GMT =head1 BUGS When both the month and the date are specified in the date as numbers they are always parsed assuming that the month number comes before the date. This is the usual format used in American dates. The reason why it is like this and not dynamic is that it must be deterministic. Several people have suggested using the current locale, but this will not work as the date being parsed may not be in the format of the current locale. My plans to address this, which will be in a future release, is to allow the programmer to state what order they want these values parsed in. =head1 AUTHOR Graham Barr <gbarr@pobox.com> =head1 COPYRIGHT Copyright (c) 1995-2009 Graham Barr. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!� Wl ��l ����perl5/Date/Language.pmnu�6$�������� package Date::Language; use strict; use Time::Local; use Carp; use vars qw($VERSION @ISA); require Date::Format; $VERSION = "1.10"; @ISA = qw(Date::Format::Generic); sub new { my $self = shift; my $type = shift || $self; $type =~ s/^(\w+)$/Date::Language::$1/; croak "Bad language" unless $type =~ /^[\w:]+$/; eval "require $type" or croak $@; bless [], $type; } # Stop AUTOLOAD being called ;-) sub DESTROY {} sub AUTOLOAD { use vars qw($AUTOLOAD); if($AUTOLOAD =~ /::strptime\Z/o) { my $self = $_[0]; my $type = ref($self) || $self; require Date::Parse; no strict 'refs'; *{"${type}::strptime"} = Date::Parse::gen_parser( \%{"${type}::DoW"}, \%{"${type}::MoY"}, \@{"${type}::Dsuf"}, 1); goto &{"${type}::strptime"}; } croak "Undefined method &$AUTOLOAD called"; } sub str2time { my $me = shift; my @t = $me->strptime(@_); return undef unless @t; my($ss,$mm,$hh,$day,$month,$year,$zone) = @t; my @lt = localtime(time); $hh ||= 0; $mm ||= 0; $ss ||= 0; $month = $lt[4] unless(defined $month); $day = $lt[3] unless(defined $day); $year = ($month > $lt[4]) ? ($lt[5] - 1) : $lt[5] unless(defined $year); return defined $zone ? timegm($ss,$mm,$hh,$day,$month,$year) - $zone : timelocal($ss,$mm,$hh,$day,$month,$year); } 1; __END__ =head1 NAME Date::Language - Language specific date formating and parsing =head1 SYNOPSIS use Date::Language; my $lang = Date::Language->new('German'); $lang->time2str("%a %b %e %T %Y\n", time); =head1 DESCRIPTION L<Date::Language> provides objects to parse and format dates for specific languages. Available languages are Afar French Russian_cp1251 Amharic Gedeo Russian_koi8r Austrian German Sidama Brazilian Greek Somali Chinese Hungarian Spanish Chinese_GB Icelandic Swedish Czech Italian Tigrinya Danish Norwegian TigrinyaEritrean Dutch Oromo TigrinyaEthiopian English Romanian Turkish Finnish Russian Bulgarian Occitan =head1 METHODS =over =item time2str See L<Date::Format/time2str> =item strftime See L<Date::Format/strftime> =item ctime See L<Date::Format/ctime> =item asctime See L<Date::Format/asctime> =item str2time See L<Date::Parse/str2time> =item strptime See L<Date::Parse/strptime> =back PK�������!�Y%��%����perl5/Date/Format.pmnu�6$��������# Copyright (c) 1995-2009 Graham Barr. This program is free # software; you can redistribute it and/or modify it under the same terms # as Perl itself. package Date::Format; use strict; use vars qw(@EXPORT @ISA $VERSION); require Exporter; $VERSION = "2.24"; @ISA = qw(Exporter); @EXPORT = qw(time2str strftime ctime asctime); sub time2str ($;$$) { Date::Format::Generic->time2str(@_); } sub strftime ($\@;$) { Date::Format::Generic->strftime(@_); } sub ctime ($;$) { my($t,$tz) = @_; Date::Format::Generic->time2str("%a %b %e %T %Y\n", $t, $tz); } sub asctime (\@;$) { my($t,$tz) = @_; Date::Format::Generic->strftime("%a %b %e %T %Y\n", $t, $tz); } ## ## ## package Date::Format::Generic; use vars qw($epoch $tzname); use Time::Zone; use Time::Local; sub ctime { my($me,$t,$tz) = @_; $me->time2str("%a %b %e %T %Y\n", $t, $tz); } sub asctime { my($me,$t,$tz) = @_; $me->strftime("%a %b %e %T %Y\n", $t, $tz); } sub _subs { my $fn; $_[1] =~ s/ %(O?[%a-zA-Z]) / ($_[0]->can("format_$1") || sub { $1 })->($_[0]); /sgeox; $_[1]; } sub strftime { my($pkg,$fmt,$time); ($pkg,$fmt,$time,$tzname) = @_; my $me = ref($pkg) ? $pkg : bless []; if(defined $tzname) { $tzname = uc $tzname; $tzname = sprintf("%+05d",$tzname) unless($tzname =~ /\D/); $epoch = timegm(@{$time}[0..5]); @$me = gmtime($epoch + tz_offset($tzname) - tz_offset()); } else { @$me = @$time; undef $epoch; } _subs($me,$fmt); } sub time2str { my($pkg,$fmt,$time); ($pkg,$fmt,$time,$tzname) = @_; my $me = ref($pkg) ? $pkg : bless [], $pkg; $epoch = $time; if(defined $tzname) { $tzname = uc $tzname; $tzname = sprintf("%+05d",$tzname) unless($tzname =~ /\D/); $time += tz_offset($tzname); @$me = gmtime($time); } else { @$me = localtime($time); } $me->[9] = $time; _subs($me,$fmt); } my(@DoW,@MoY,@DoWs,@MoYs,@AMPM,%format,@Dsuf); @DoW = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday); @MoY = qw(January February March April May June July August September October November December); @DoWs = map { substr($_,0,3) } @DoW; @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); @Dsuf = (qw(th st nd rd th th th th th th)) x 3; @Dsuf[11,12,13] = qw(th th th); @Dsuf[30,31] = qw(th st); %format = ('x' => "%m/%d/%y", 'C' => "%a %b %e %T %Z %Y", 'X' => "%H:%M:%S", ); my @locale; my $locale = "/usr/share/lib/locale/LC_TIME/default"; local *LOCALE; if(open(LOCALE,"$locale")) { chop(@locale = <LOCALE>); close(LOCALE); @MoYs = @locale[0 .. 11]; @MoY = @locale[12 .. 23]; @DoWs = @locale[24 .. 30]; @DoW = @locale[31 .. 37]; @format{"X","x","C"} = @locale[38 .. 40]; @AMPM = @locale[41 .. 42]; } sub wkyr { my($wstart, $wday, $yday) = @_; $wday = ($wday + 7 - $wstart) % 7; return int(($yday - $wday + 13) / 7 - 1); } ## ## these 6 formatting routins need to be *copied* into the language ## specific packages ## my @roman = ('',qw(I II III IV V VI VII VIII IX)); sub roman { my $n = shift; $n =~ s/(\d)$//; my $r = $roman[ $1 ]; if($n =~ s/(\d)$//) { (my $t = $roman[$1]) =~ tr/IVX/XLC/; $r = $t . $r; } if($n =~ s/(\d)$//) { (my $t = $roman[$1]) =~ tr/IVX/CDM/; $r = $t . $r; } if($n =~ s/(\d)$//) { (my $t = $roman[$1]) =~ tr/IVX/M../; $r = $t . $r; } $r; } sub format_a { $DoWs[$_[0]->[6]] } sub format_A { $DoW[$_[0]->[6]] } sub format_b { $MoYs[$_[0]->[4]] } sub format_B { $MoY[$_[0]->[4]] } sub format_h { $MoYs[$_[0]->[4]] } sub format_p { $_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0] } sub format_P { lc($_[0]->[2] >= 12 ? $AMPM[1] : $AMPM[0]) } sub format_d { sprintf("%02d",$_[0]->[3]) } sub format_e { sprintf("%2d",$_[0]->[3]) } sub format_H { sprintf("%02d",$_[0]->[2]) } sub format_I { sprintf("%02d",$_[0]->[2] % 12 || 12)} sub format_j { sprintf("%03d",$_[0]->[7] + 1) } sub format_k { sprintf("%2d",$_[0]->[2]) } sub format_l { sprintf("%2d",$_[0]->[2] % 12 || 12)} sub format_L { $_[0]->[4] + 1 } sub format_m { sprintf("%02d",$_[0]->[4] + 1) } sub format_M { sprintf("%02d",$_[0]->[1]) } sub format_q { sprintf("%01d",int($_[0]->[4] / 3) + 1) } sub format_s { $epoch = timelocal(@{$_[0]}[0..5]) unless defined $epoch; sprintf("%d",$epoch) } sub format_S { sprintf("%02d",$_[0]->[0]) } sub format_U { wkyr(0, $_[0]->[6], $_[0]->[7]) } sub format_w { $_[0]->[6] } sub format_W { wkyr(1, $_[0]->[6], $_[0]->[7]) } sub format_y { sprintf("%02d",$_[0]->[5] % 100) } sub format_Y { sprintf("%04d",$_[0]->[5] + 1900) } sub format_Z { my $o = tz_local_offset(timelocal(@{$_[0]}[0..5])); defined $tzname ? $tzname : uc tz_name($o, $_[0]->[8]); } sub format_z { my $t = timelocal(@{$_[0]}[0..5]); my $o = defined $tzname ? tz_offset($tzname, $t) : tz_offset(undef,$t); sprintf("%+03d%02d", int($o / 3600), int(abs($o) % 3600) / 60); } sub format_c { &format_x . " " . &format_X } sub format_D { &format_m . "/" . &format_d . "/" . &format_y } sub format_r { &format_I . ":" . &format_M . ":" . &format_S . " " . &format_p } sub format_R { &format_H . ":" . &format_M } sub format_T { &format_H . ":" . &format_M . ":" . &format_S } sub format_t { "\t" } sub format_n { "\n" } sub format_o { sprintf("%2d%s",$_[0]->[3],$Dsuf[$_[0]->[3]]) } sub format_x { my $f = $format{'x'}; _subs($_[0],$f); } sub format_X { my $f = $format{'X'}; _subs($_[0],$f); } sub format_C { my $f = $format{'C'}; _subs($_[0],$f); } sub format_Od { roman(format_d(@_)) } sub format_Oe { roman(format_e(@_)) } sub format_OH { roman(format_H(@_)) } sub format_OI { roman(format_I(@_)) } sub format_Oj { roman(format_j(@_)) } sub format_Ok { roman(format_k(@_)) } sub format_Ol { roman(format_l(@_)) } sub format_Om { roman(format_m(@_)) } sub format_OM { roman(format_M(@_)) } sub format_Oq { roman(format_q(@_)) } sub format_Oy { roman(format_y(@_)) } sub format_OY { roman(format_Y(@_)) } sub format_G { int(($_[0]->[9] - 315993600) / 604800) } 1; __END__ =head1 NAME Date::Format - Date formating subroutines =head1 SYNOPSIS use Date::Format; @lt = localtime(time); print time2str($template, time); print strftime($template, @lt); print time2str($template, time, $zone); print strftime($template, @lt, $zone); print ctime(time); print asctime(@lt); print ctime(time, $zone); print asctime(@lt, $zone); =head1 DESCRIPTION This module provides routines to format dates into ASCII strings. They correspond to the C library routines C<strftime> and C<ctime>. =over 4 =item time2str(TEMPLATE, TIME [, ZONE]) C<time2str> converts C<TIME> into an ASCII string using the conversion specification given in C<TEMPLATE>. C<ZONE> if given specifies the zone which the output is required to be in, C<ZONE> defaults to your current zone. =item strftime(TEMPLATE, TIME [, ZONE]) C<strftime> is similar to C<time2str> with the exception that the time is passed as an array, such as the array returned by C<localtime>. =item ctime(TIME [, ZONE]) C<ctime> calls C<time2str> with the given arguments using the conversion specification C<"%a %b %e %T %Y\n"> =item asctime(TIME [, ZONE]) C<asctime> calls C<time2str> with the given arguments using the conversion specification C<"%a %b %e %T %Y\n"> =back =head1 MULTI-LANGUAGE SUPPORT Date::Format is capable of formating into several languages by creating a language specific object and calling methods, see L<Date::Language> my $lang = Date::Language->new('German'); $lang->time2str("%a %b %e %T %Y\n", time); I am open to suggestions on this. =head1 CONVERSION SPECIFICATION Each conversion specification is replaced by appropriate characters as described in the following list. The appropriate characters are determined by the LC_TIME category of the program's locale. %% PERCENT %a day of the week abbr %A day of the week %b month abbr %B month %c MM/DD/YY HH:MM:SS %C ctime format: Sat Nov 19 21:05:57 1994 %d numeric day of the month, with leading zeros (eg 01..31) %e like %d, but a leading zero is replaced by a space (eg 1..32) %D MM/DD/YY %G GPS week number (weeks since January 6, 1980) %h month abbr %H hour, 24 hour clock, leading 0's) %I hour, 12 hour clock, leading 0's) %j day of the year %k hour %l hour, 12 hour clock %L month number, starting with 1 %m month number, starting with 01 %M minute, leading 0's %n NEWLINE %o ornate day of month -- "1st", "2nd", "25th", etc. %p AM or PM %P am or pm (Yes %p and %P are backwards :) %q Quarter number, starting with 1 %r time format: 09:05:57 PM %R time format: 21:05 %s seconds since the Epoch, UCT %S seconds, leading 0's %t TAB %T time format: 21:05:57 %U week number, Sunday as first day of week %w day of the week, numerically, Sunday == 0 %W week number, Monday as first day of week %x date format: 11/19/94 %X time format: 21:05:57 %y year (2 digits) %Y year (4 digits) %Z timezone in ascii. eg: PST %z timezone in format -/+0000 C<%d>, C<%e>, C<%H>, C<%I>, C<%j>, C<%k>, C<%l>, C<%m>, C<%M>, C<%q>, C<%y> and C<%Y> can be output in Roman numerals by prefixing the letter with C<O>, e.g. C<%OY> will output the year as roman numerals. =head1 LIMITATION The functions in this module are limited to the time range that can be represented by the time_t data type, i.e. 1901-12-13 20:45:53 GMT to 2038-01-19 03:14:07 GMT. =head1 AUTHOR Graham Barr <gbarr@pobox.com> =head1 COPYRIGHT Copyright (c) 1995-2009 Graham Barr. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!�@iVD��VD����perl5/Net/HTTP/Methods.pmnu�6$��������package Net::HTTP::Methods; our $VERSION = '6.23'; use strict; use warnings; use URI; my $CRLF = "\015\012"; # "\r\n" is not portable *_bytes = defined(&utf8::downgrade) ? sub { unless (utf8::downgrade($_[0], 1)) { require Carp; Carp::croak("Wide character in HTTP request (bytes required)"); } return $_[0]; } : sub { return $_[0]; }; sub new { my $class = shift; unshift(@_, "Host") if @_ == 1; my %cnf = @_; require Symbol; my $self = bless Symbol::gensym(), $class; return $self->http_configure(\%cnf); } sub http_configure { my($self, $cnf) = @_; die "Listen option not allowed" if $cnf->{Listen}; my $explicit_host = (exists $cnf->{Host}); my $host = delete $cnf->{Host}; # All this because $cnf->{PeerAddr} = 0 is actually valid. my $peer; for my $key (qw{PeerAddr PeerHost}) { next if !defined($cnf->{$key}) || q{} eq $cnf->{$key}; $peer = $cnf->{$key}; last; } if (!defined $peer) { die "No Host option provided" unless $host; $cnf->{PeerAddr} = $peer = $host; } # CONNECTIONS # PREFER: port number from PeerAddr, then PeerPort, then http_default_port my $peer_uri = URI->new("http://$peer"); $cnf->{"PeerPort"} = $peer_uri->_port || $cnf->{PeerPort} || $self->http_default_port; $cnf->{"PeerAddr"} = $peer_uri->host; # HOST header: # If specified but blank, ignore. # If specified with a value, add the port number # If not specified, set to PeerAddr and port number # ALWAYS: If IPv6 address, use [brackets] (thanks to the URI package) # ALWAYS: omit port number if http_default_port if (($host) || (! $explicit_host)) { my $uri = ($explicit_host) ? URI->new("http://$host") : $peer_uri->clone; if (!$uri->_port) { # Always use *our* $self->http_default_port instead of URI's (Covers HTTP, HTTPS) $uri->port( $cnf->{PeerPort} || $self->http_default_port); } my $host_port = $uri->host_port; # Returns host:port or [ipv6]:port my $remove = ":" . $self->http_default_port; # we want to remove the default port number if (substr($host_port,0-length($remove)) eq $remove) { substr($host_port,0-length($remove)) = ""; } $host = $host_port; } $cnf->{Proto} = 'tcp'; my $keep_alive = delete $cnf->{KeepAlive}; my $http_version = delete $cnf->{HTTPVersion}; $http_version = "1.1" unless defined $http_version; my $peer_http_version = delete $cnf->{PeerHTTPVersion}; $peer_http_version = "1.0" unless defined $peer_http_version; my $send_te = delete $cnf->{SendTE}; my $max_line_length = delete $cnf->{MaxLineLength}; $max_line_length = 8*1024 unless defined $max_line_length; my $max_header_lines = delete $cnf->{MaxHeaderLines}; $max_header_lines = 128 unless defined $max_header_lines; return undef unless $self->http_connect($cnf); $self->host($host); $self->keep_alive($keep_alive); $self->send_te($send_te); $self->http_version($http_version); $self->peer_http_version($peer_http_version); $self->max_line_length($max_line_length); $self->max_header_lines($max_header_lines); ${*$self}{'http_buf'} = ""; return $self; } sub http_default_port { 80; } # set up property accessors for my $method (qw(host keep_alive send_te max_line_length max_header_lines peer_http_version)) { my $prop_name = "http_" . $method; no strict 'refs'; *$method = sub { my $self = shift; my $old = ${*$self}{$prop_name}; ${*$self}{$prop_name} = shift if @_; return $old; }; } # we want this one to be a bit smarter sub http_version { my $self = shift; my $old = ${*$self}{'http_version'}; if (@_) { my $v = shift; $v = "1.0" if $v eq "1"; # float unless ($v eq "1.0" or $v eq "1.1") { require Carp; Carp::croak("Unsupported HTTP version '$v'"); } ${*$self}{'http_version'} = $v; } $old; } sub format_request { my $self = shift; my $method = shift; my $uri = shift; my $content = (@_ % 2) ? pop : ""; for ($method, $uri) { require Carp; Carp::croak("Bad method or uri") if /\s/ || !length; } push(@{${*$self}{'http_request_method'}}, $method); my $ver = ${*$self}{'http_version'}; my $peer_ver = ${*$self}{'http_peer_http_version'} || "1.0"; my @h; my @connection; my %given = (host => 0, "content-length" => 0, "te" => 0); while (@_) { my($k, $v) = splice(@_, 0, 2); my $lc_k = lc($k); if ($lc_k eq "connection") { $v =~ s/^\s+//; $v =~ s/\s+$//; push(@connection, split(/\s*,\s*/, $v)); next; } if (exists $given{$lc_k}) { $given{$lc_k}++; } push(@h, "$k: $v"); } if (length($content) && !$given{'content-length'}) { push(@h, "Content-Length: " . length($content)); } my @h2; if ($given{te}) { push(@connection, "TE") unless grep lc($_) eq "te", @connection; } elsif ($self->send_te && gunzip_ok()) { # gzip is less wanted since the IO::Uncompress::Gunzip interface for # it does not really allow chunked decoding to take place easily. push(@h2, "TE: deflate,gzip;q=0.3"); push(@connection, "TE"); } unless (grep lc($_) eq "close", @connection) { if ($self->keep_alive) { if ($peer_ver eq "1.0") { # from looking at Netscape's headers push(@h2, "Keep-Alive: 300"); unshift(@connection, "Keep-Alive"); } } else { push(@connection, "close") if $ver ge "1.1"; } } push(@h2, "Connection: " . join(", ", @connection)) if @connection; unless ($given{host}) { my $h = ${*$self}{'http_host'}; push(@h2, "Host: $h") if $h; } return _bytes(join($CRLF, "$method $uri HTTP/$ver", @h2, @h, "", $content)); } sub write_request { my $self = shift; $self->print($self->format_request(@_)); } sub format_chunk { my $self = shift; return $_[0] unless defined($_[0]) && length($_[0]); return _bytes(sprintf("%x", length($_[0])) . $CRLF . $_[0] . $CRLF); } sub write_chunk { my $self = shift; return 1 unless defined($_[0]) && length($_[0]); $self->print(_bytes(sprintf("%x", length($_[0])) . $CRLF . $_[0] . $CRLF)); } sub format_chunk_eof { my $self = shift; my @h; while (@_) { push(@h, sprintf "%s: %s$CRLF", splice(@_, 0, 2)); } return _bytes(join("", "0$CRLF", @h, $CRLF)); } sub write_chunk_eof { my $self = shift; $self->print($self->format_chunk_eof(@_)); } sub my_read { die if @_ > 3; my $self = shift; my $len = $_[1]; for (${*$self}{'http_buf'}) { if (length) { $_[0] = substr($_, 0, $len, ""); return length($_[0]); } else { die "read timeout" unless $self->can_read; return $self->sysread($_[0], $len); } } } sub my_readline { my $self = shift; my $what = shift; for (${*$self}{'http_buf'}) { my $max_line_length = ${*$self}{'http_max_line_length'}; my $pos; while (1) { # find line ending $pos = index($_, "\012"); last if $pos >= 0; die "$what line too long (limit is $max_line_length)" if $max_line_length && length($_) > $max_line_length; # need to read more data to find a line ending my $new_bytes = 0; READ: { # wait until bytes start arriving $self->can_read or die "read timeout"; # consume all incoming bytes my $bytes_read = $self->sysread($_, 1024, length); if(defined $bytes_read) { $new_bytes += $bytes_read; } elsif($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) { redo READ; } else { # if we have already accumulated some data let's at # least return that as a line length or die "$what read failed: $!"; } # no line-ending, no new bytes return length($_) ? substr($_, 0, length($_), "") : undef if $new_bytes==0; } } die "$what line too long ($pos; limit is $max_line_length)" if $max_line_length && $pos > $max_line_length; my $line = substr($_, 0, $pos+1, ""); $line =~ s/(\015?\012)\z// || die "Assert"; return wantarray ? ($line, $1) : $line; } } sub can_read { my $self = shift; return 1 unless defined(fileno($self)); return 1 if $self->isa('IO::Socket::SSL') && $self->pending; return 1 if $self->isa('Net::SSL') && $self->can('pending') && $self->pending; # With no timeout, wait forever. An explicit timeout of 0 can be # used to just check if the socket is readable without waiting. my $timeout = @_ ? shift : (${*$self}{io_socket_timeout} || undef); my $fbits = ''; vec($fbits, fileno($self), 1) = 1; SELECT: { my $before; $before = time if $timeout; my $nfound = select($fbits, undef, undef, $timeout); if ($nfound < 0) { if ($!{EINTR} || $!{EAGAIN} || $!{EWOULDBLOCK}) { # don't really think EAGAIN/EWOULDBLOCK can happen here if ($timeout) { $timeout -= time - $before; $timeout = 0 if $timeout < 0; } redo SELECT; } die "select failed: $!"; } return $nfound > 0; } } sub _rbuf { my $self = shift; if (@_) { for (${*$self}{'http_buf'}) { my $old; $old = $_ if defined wantarray; $_ = shift; return $old; } } else { return ${*$self}{'http_buf'}; } } sub _rbuf_length { my $self = shift; return length ${*$self}{'http_buf'}; } sub _read_header_lines { my $self = shift; my $junk_out = shift; my @headers; my $line_count = 0; my $max_header_lines = ${*$self}{'http_max_header_lines'}; while (my $line = my_readline($self, 'Header')) { if ($line =~ /^(\S+?)\s*:\s*(.*)/s) { push(@headers, $1, $2); } elsif (@headers && $line =~ s/^\s+//) { $headers[-1] .= " " . $line; } elsif ($junk_out) { push(@$junk_out, $line); } else { die "Bad header: '$line'\n"; } if ($max_header_lines) { $line_count++; if ($line_count >= $max_header_lines) { die "Too many header lines (limit is $max_header_lines)"; } } } return @headers; } sub read_response_headers { my($self, %opt) = @_; my $laxed = $opt{laxed}; my($status, $eol) = my_readline($self, 'Status'); unless (defined $status) { die "Server closed connection without sending any data back"; } my($peer_ver, $code, $message) = split(/\s+/, $status, 3); if (!$peer_ver || $peer_ver !~ s,^HTTP/,, || $code !~ /^[1-5]\d\d$/) { die "Bad response status line: '$status'" unless $laxed; # assume HTTP/0.9 ${*$self}{'http_peer_http_version'} = "0.9"; ${*$self}{'http_status'} = "200"; substr(${*$self}{'http_buf'}, 0, 0) = $status . ($eol || ""); return 200 unless wantarray; return (200, "Assumed OK"); }; ${*$self}{'http_peer_http_version'} = $peer_ver; ${*$self}{'http_status'} = $code; my $junk_out; if ($laxed) { $junk_out = $opt{junk_out} || []; } my @headers = $self->_read_header_lines($junk_out); # pick out headers that read_entity_body might need my @te; my $content_length; for (my $i = 0; $i < @headers; $i += 2) { my $h = lc($headers[$i]); if ($h eq 'transfer-encoding') { my $te = $headers[$i+1]; $te =~ s/^\s+//; $te =~ s/\s+$//; push(@te, $te) if length($te); } elsif ($h eq 'content-length') { # ignore bogus and overflow values if ($headers[$i+1] =~ /^\s*(\d{1,15})(?:\s|$)/) { $content_length = $1; } } } ${*$self}{'http_te'} = join(",", @te); ${*$self}{'http_content_length'} = $content_length; ${*$self}{'http_first_body'}++; delete ${*$self}{'http_trailers'}; return $code unless wantarray; return ($code, $message, @headers); } sub read_entity_body { my $self = shift; my $buf_ref = \$_[0]; my $size = $_[1]; die "Offset not supported yet" if $_[2]; my $chunked; my $bytes; if (${*$self}{'http_first_body'}) { ${*$self}{'http_first_body'} = 0; delete ${*$self}{'http_chunked'}; delete ${*$self}{'http_bytes'}; my $method = shift(@{${*$self}{'http_request_method'}}); my $status = ${*$self}{'http_status'}; if ($method eq "HEAD") { # this response is always empty regardless of other headers $bytes = 0; } elsif (my $te = ${*$self}{'http_te'}) { my @te = split(/\s*,\s*/, lc($te)); die "Chunked must be last Transfer-Encoding '$te'" unless pop(@te) eq "chunked"; pop(@te) while @te && $te[-1] eq "chunked"; # ignore repeated chunked spec for (@te) { if ($_ eq "deflate" && inflate_ok()) { #require Compress::Raw::Zlib; my ($i, $status) = Compress::Raw::Zlib::Inflate->new(); die "Can't make inflator: $status" unless $i; $_ = sub { my $out; $i->inflate($_[0], \$out); $out } } elsif ($_ eq "gzip" && gunzip_ok()) { #require IO::Uncompress::Gunzip; my @buf; $_ = sub { push(@buf, $_[0]); return "" unless $_[1]; my $input = join("", @buf); my $output; IO::Uncompress::Gunzip::gunzip(\$input, \$output, Transparent => 0) or die "Can't gunzip content: $IO::Uncompress::Gunzip::GunzipError"; return \$output; }; } elsif ($_ eq "identity") { $_ = sub { $_[0] }; } else { die "Can't handle transfer encoding '$te'"; } } @te = reverse(@te); ${*$self}{'http_te2'} = @te ? \@te : ""; $chunked = -1; } elsif (defined(my $content_length = ${*$self}{'http_content_length'})) { $bytes = $content_length; } elsif ($status =~ /^(?:1|[23]04)/) { # RFC 2616 says that these responses should always be empty # but that does not appear to be true in practice [RT#17907] $bytes = 0; } else { # XXX Multi-Part types are self delimiting, but RFC 2616 says we # only has to deal with 'multipart/byteranges' # Read until EOF } } else { $chunked = ${*$self}{'http_chunked'}; $bytes = ${*$self}{'http_bytes'}; } if (defined $chunked) { # The state encoded in $chunked is: # $chunked == 0: read CRLF after chunk, then chunk header # $chunked == -1: read chunk header # $chunked > 0: bytes left in current chunk to read if ($chunked <= 0) { my $line = my_readline($self, 'Entity body'); if ($chunked == 0) { die "Missing newline after chunk data: '$line'" if !defined($line) || $line ne ""; $line = my_readline($self, 'Entity body'); } die "EOF when chunk header expected" unless defined($line); my $chunk_len = $line; $chunk_len =~ s/;.*//; # ignore potential chunk parameters unless ($chunk_len =~ /^([\da-fA-F]+)\s*$/) { die "Bad chunk-size in HTTP response: $line"; } $chunked = hex($1); ${*$self}{'http_chunked'} = $chunked; if ($chunked == 0) { ${*$self}{'http_trailers'} = [$self->_read_header_lines]; $$buf_ref = ""; my $n = 0; if (my $transforms = delete ${*$self}{'http_te2'}) { for (@$transforms) { $$buf_ref = &$_($$buf_ref, 1); } $n = length($$buf_ref); } # in case somebody tries to read more, make sure we continue # to return EOF delete ${*$self}{'http_chunked'}; ${*$self}{'http_bytes'} = 0; return $n; } } my $n = $chunked; $n = $size if $size && $size < $n; $n = my_read($self, $$buf_ref, $n); return undef unless defined $n; ${*$self}{'http_chunked'} = $chunked - $n; if ($n > 0) { if (my $transforms = ${*$self}{'http_te2'}) { for (@$transforms) { $$buf_ref = &$_($$buf_ref, 0); } $n = length($$buf_ref); $n = -1 if $n == 0; } } return $n; } elsif (defined $bytes) { unless ($bytes) { $$buf_ref = ""; return 0; } my $n = $bytes; $n = $size if $size && $size < $n; $n = my_read($self, $$buf_ref, $n); ${*$self}{'http_bytes'} = defined $n ? $bytes - $n : $bytes; return $n; } else { # read until eof $size ||= 8*1024; return my_read($self, $$buf_ref, $size); } } sub get_trailers { my $self = shift; @{${*$self}{'http_trailers'} || []}; } BEGIN { my $gunzip_ok; my $inflate_ok; sub gunzip_ok { return $gunzip_ok if defined $gunzip_ok; # Try to load IO::Uncompress::Gunzip. local $@; local $SIG{__DIE__}; $gunzip_ok = 0; eval { require IO::Uncompress::Gunzip; $gunzip_ok++; }; return $gunzip_ok; } sub inflate_ok { return $inflate_ok if defined $inflate_ok; # Try to load Compress::Raw::Zlib. local $@; local $SIG{__DIE__}; $inflate_ok = 0; eval { require Compress::Raw::Zlib; $inflate_ok++; }; return $inflate_ok; } } # BEGIN 1; =pod =encoding UTF-8 =head1 NAME Net::HTTP::Methods - Methods shared by Net::HTTP and Net::HTTPS =head1 VERSION version 6.23 =head1 AUTHOR Gisle Aas <gisle@activestate.com> =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2001 by Gisle Aas. 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: Methods shared by Net::HTTP and Net::HTTPS PK�������!�DO �� ����perl5/Net/HTTP/NB.pmnu�6$��������package Net::HTTP::NB; our $VERSION = '6.23'; use strict; use warnings; use base 'Net::HTTP'; sub can_read { return 1; } sub sysread { my $self = $_[0]; if (${*$self}{'httpnb_read_count'}++) { ${*$self}{'http_buf'} = ${*$self}{'httpnb_save'}; die "Multi-read\n"; } my $buf; my $offset = $_[3] || 0; my $n = sysread($self, $_[1], $_[2], $offset); ${*$self}{'httpnb_save'} .= substr($_[1], $offset); return $n; } sub read_response_headers { my $self = shift; ${*$self}{'httpnb_read_count'} = 0; ${*$self}{'httpnb_save'} = ${*$self}{'http_buf'}; my @h = eval { $self->SUPER::read_response_headers(@_) }; if ($@) { return if $@ eq "Multi-read\n"; die; } return @h; } sub read_entity_body { my $self = shift; ${*$self}{'httpnb_read_count'} = 0; ${*$self}{'httpnb_save'} = ${*$self}{'http_buf'}; # XXX I'm not so sure this does the correct thing in case of # transfer-encoding transforms my $n = eval { $self->SUPER::read_entity_body(@_); }; if ($@) { $_[0] = ""; return -1; } return $n; } 1; =pod =encoding UTF-8 =head1 NAME Net::HTTP::NB - Non-blocking HTTP client =head1 VERSION version 6.23 =head1 SYNOPSIS use Net::HTTP::NB; my $s = Net::HTTP::NB->new(Host => "www.perl.com") || die $@; $s->write_request(GET => "/"); use IO::Select; my $sel = IO::Select->new($s); READ_HEADER: { die "Header timeout" unless $sel->can_read(10); my($code, $mess, %h) = $s->read_response_headers; redo READ_HEADER unless $code; } while (1) { die "Body timeout" unless $sel->can_read(10); my $buf; my $n = $s->read_entity_body($buf, 1024); last unless $n; print $buf; } =head1 DESCRIPTION Same interface as C<Net::HTTP> but it will never try multiple reads when the read_response_headers() or read_entity_body() methods are invoked. This make it possible to multiplex multiple Net::HTTP::NB using select without risk blocking. If read_response_headers() did not see enough data to complete the headers an empty list is returned. If read_entity_body() did not see new entity data in its read the value -1 is returned. =head1 SEE ALSO L<Net::HTTP> =head1 AUTHOR Gisle Aas <gisle@activestate.com> =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2001 by Gisle Aas. 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: Non-blocking HTTP client PK�������!� �� ����perl5/Net/HTTPS.pmnu�6$��������package Net::HTTPS; our $VERSION = '6.23'; use strict; use warnings; # Figure out which SSL implementation to use our $SSL_SOCKET_CLASS; if ($SSL_SOCKET_CLASS) { # somebody already set it } elsif ($SSL_SOCKET_CLASS = $ENV{PERL_NET_HTTPS_SSL_SOCKET_CLASS}) { unless ($SSL_SOCKET_CLASS =~ /^(IO::Socket::SSL|Net::SSL)\z/) { die "Bad socket class [$SSL_SOCKET_CLASS]"; } eval "require $SSL_SOCKET_CLASS"; die $@ if $@; } elsif ($IO::Socket::SSL::VERSION) { $SSL_SOCKET_CLASS = "IO::Socket::SSL"; # it was already loaded } elsif ($Net::SSL::VERSION) { $SSL_SOCKET_CLASS = "Net::SSL"; } else { eval { require IO::Socket::SSL; }; if ($@) { my $old_errsv = $@; eval { require Net::SSL; # from Crypt-SSLeay }; if ($@) { $old_errsv =~ s/\s\(\@INC contains:.*\)/)/g; die $old_errsv . $@; } $SSL_SOCKET_CLASS = "Net::SSL"; } else { $SSL_SOCKET_CLASS = "IO::Socket::SSL"; } } require Net::HTTP::Methods; our @ISA=($SSL_SOCKET_CLASS, 'Net::HTTP::Methods'); sub configure { my($self, $cnf) = @_; $self->http_configure($cnf); } sub http_connect { my($self, $cnf) = @_; if ($self->isa("Net::SSL")) { if ($cnf->{SSL_verify_mode}) { if (my $f = $cnf->{SSL_ca_file}) { $ENV{HTTPS_CA_FILE} = $f; } if (my $f = $cnf->{SSL_ca_path}) { $ENV{HTTPS_CA_DIR} = $f; } } if ($cnf->{SSL_verifycn_scheme}) { $@ = "Net::SSL from Crypt-SSLeay can't verify hostnames; either install IO::Socket::SSL or turn off verification by setting the PERL_LWP_SSL_VERIFY_HOSTNAME environment variable to 0"; return undef; } } $self->SUPER::configure($cnf); } sub http_default_port { 443; } if ($SSL_SOCKET_CLASS eq "Net::SSL") { # The underlying SSLeay classes fails to work if the socket is # placed in non-blocking mode. This override of the blocking # method makes sure it stays the way it was created. *blocking = sub { }; } 1; =pod =encoding UTF-8 =head1 NAME Net::HTTPS - Low-level HTTP over SSL/TLS connection (client) =head1 VERSION version 6.23 =head1 DESCRIPTION The C<Net::HTTPS> is a low-level HTTP over SSL/TLS client. The interface is the same as the interface for C<Net::HTTP>, but the constructor takes additional parameters as accepted by L<IO::Socket::SSL>. The C<Net::HTTPS> object is an C<IO::Socket::SSL> too, which makes it inherit additional methods from that base class. For historical reasons this module also supports using C<Net::SSL> (from the Crypt-SSLeay distribution) as its SSL driver and base class. This base is automatically selected if available and C<IO::Socket::SSL> isn't. You might also force which implementation to use by setting $Net::HTTPS::SSL_SOCKET_CLASS before loading this module. If not set this variable is initialized from the C<PERL_NET_HTTPS_SSL_SOCKET_CLASS> environment variable. =head1 ENVIRONMENT You might set the C<PERL_NET_HTTPS_SSL_SOCKET_CLASS> environment variable to the name of the base SSL implementation (and Net::HTTPS base class) to use. The default is C<IO::Socket::SSL>. Currently the only other supported value is C<Net::SSL>. =head1 SEE ALSO L<Net::HTTP>, L<IO::Socket::SSL> =head1 AUTHOR Gisle Aas <gisle@activestate.com> =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2001 by Gisle Aas. 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: Low-level HTTP over SSL/TLS connection (client) PK�������!�K(��(����perl5/Net/HTTP.pmnu�6$��������package Net::HTTP; our $VERSION = '6.23'; use strict; use warnings; our $SOCKET_CLASS; unless ($SOCKET_CLASS) { # Try several, in order of capability and preference if (eval { require IO::Socket::IP }) { $SOCKET_CLASS = "IO::Socket::IP"; # IPv4+IPv6 } elsif (eval { require IO::Socket::INET6 }) { $SOCKET_CLASS = "IO::Socket::INET6"; # IPv4+IPv6 } elsif (eval { require IO::Socket::INET }) { $SOCKET_CLASS = "IO::Socket::INET"; # IPv4 only } else { require IO::Socket; $SOCKET_CLASS = "IO::Socket::INET"; } } require Net::HTTP::Methods; require Carp; our @ISA = ($SOCKET_CLASS, 'Net::HTTP::Methods'); sub new { my $class = shift; Carp::croak("No Host option provided") unless @_; $class->SUPER::new(@_); } sub configure { my($self, $cnf) = @_; $self->http_configure($cnf); } sub http_connect { my($self, $cnf) = @_; $self->SUPER::configure($cnf); } 1; =pod =encoding UTF-8 =head1 NAME Net::HTTP - Low-level HTTP connection (client) =head1 VERSION version 6.23 =head1 SYNOPSIS use Net::HTTP; my $s = Net::HTTP->new(Host => "www.perl.com") || die $@; $s->write_request(GET => "/", 'User-Agent' => "Mozilla/5.0"); my($code, $mess, %h) = $s->read_response_headers; while (1) { my $buf; my $n = $s->read_entity_body($buf, 1024); die "read failed: $!" unless defined $n; last unless $n; print $buf; } =head1 DESCRIPTION The C<Net::HTTP> class is a low-level HTTP client. An instance of the C<Net::HTTP> class represents a connection to an HTTP server. The HTTP protocol is described in RFC 2616. The C<Net::HTTP> class supports C<HTTP/1.0> and C<HTTP/1.1>. C<Net::HTTP> is a sub-class of one of C<IO::Socket::IP> (IPv6+IPv4), C<IO::Socket::INET6> (IPv6+IPv4), or C<IO::Socket::INET> (IPv4 only). You can mix the methods described below with reading and writing from the socket directly. This is not necessary a good idea, unless you know what you are doing. The following methods are provided (in addition to those of C<IO::Socket::INET>): =over =item C<< $s = Net::HTTP->new( %options ) >> The C<Net::HTTP> constructor method takes the same options as C<IO::Socket::INET>'s as well as these: Host: Initial host attribute value KeepAlive: Initial keep_alive attribute value SendTE: Initial send_te attribute_value HTTPVersion: Initial http_version attribute value PeerHTTPVersion: Initial peer_http_version attribute value MaxLineLength: Initial max_line_length attribute value MaxHeaderLines: Initial max_header_lines attribute value The C<Host> option is also the default for C<IO::Socket::INET>'s C<PeerAddr>. The C<PeerPort> defaults to 80 if not provided. The C<PeerPort> specification can also be embedded in the C<PeerAddr> by preceding it with a ":", and closing the IPv6 address on brackets "[]" if necessary: "192.0.2.1:80","[2001:db8::1]:80","any.example.com:80". The C<Listen> option provided by C<IO::Socket::INET>'s constructor method is not allowed. If unable to connect to the given HTTP server then the constructor returns C<undef> and $@ contains the reason. After a successful connect, a C<Net:HTTP> object is returned. =item C<< $s->host >> Get/set the default value of the C<Host> header to send. The $host must not be set to an empty string (or C<undef>) for HTTP/1.1. =item C<< $s->keep_alive >> Get/set the I<keep-alive> value. If this value is TRUE then the request will be sent with headers indicating that the server should try to keep the connection open so that multiple requests can be sent. The actual headers set will depend on the value of the C<http_version> and C<peer_http_version> attributes. =item C<< $s->send_te >> Get/set the a value indicating if the request will be sent with a "TE" header to indicate the transfer encodings that the server can choose to use. The list of encodings announced as accepted by this client depends on availability of the following modules: C<Compress::Raw::Zlib> for I<deflate>, and C<IO::Compress::Gunzip> for I<gzip>. =item C<< $s->http_version >> Get/set the HTTP version number that this client should announce. This value can only be set to "1.0" or "1.1". The default is "1.1". =item C<< $s->peer_http_version >> Get/set the protocol version number of our peer. This value will initially be "1.0", but will be updated by a successful read_response_headers() method call. =item C<< $s->max_line_length >> Get/set a limit on the length of response line and response header lines. The default is 8192. A value of 0 means no limit. =item C<< $s->max_header_length >> Get/set a limit on the number of header lines that a response can have. The default is 128. A value of 0 means no limit. =item C<< $s->format_request($method, $uri, %headers, [$content]) >> Format a request message and return it as a string. If the headers do not include a C<Host> header, then a header is inserted with the value of the C<host> attribute. Headers like C<Connection> and C<Keep-Alive> might also be added depending on the status of the C<keep_alive> attribute. If $content is given (and it is non-empty), then a C<Content-Length> header is automatically added unless it was already present. =item C<< $s->write_request($method, $uri, %headers, [$content]) >> Format and send a request message. Arguments are the same as for format_request(). Returns true if successful. =item C<< $s->format_chunk( $data ) >> Returns the string to be written for the given chunk of data. =item C<< $s->write_chunk($data) >> Will write a new chunk of request entity body data. This method should only be used if the C<Transfer-Encoding> header with a value of C<chunked> was sent in the request. Note, writing zero-length data is a no-op. Use the write_chunk_eof() method to signal end of entity body data. Returns true if successful. =item C<< $s->format_chunk_eof( %trailers ) >> Returns the string to be written for signaling EOF when a C<Transfer-Encoding> of C<chunked> is used. =item C<< $s->write_chunk_eof( %trailers ) >> Will write eof marker for chunked data and optional trailers. Note that trailers should not really be used unless is was signaled with a C<Trailer> header. Returns true if successful. =item C<< ($code, $mess, %headers) = $s->read_response_headers( %opts ) >> Read response headers from server and return it. The $code is the 3 digit HTTP status code (see L<HTTP::Status>) and $mess is the textual message that came with it. Headers are then returned as key/value pairs. Since key letter casing is not normalized and the same key can even occur multiple times, assigning these values directly to a hash is not wise. Only the $code is returned if this method is called in scalar context. As a side effect this method updates the 'peer_http_version' attribute. Options might be passed in as key/value pairs. There are currently only two options supported; C<laxed> and C<junk_out>. The C<laxed> option will make read_response_headers() more forgiving towards servers that have not learned how to speak HTTP properly. The C<laxed> option is a boolean flag, and is enabled by passing in a TRUE value. The C<junk_out> option can be used to capture bad header lines when C<laxed> is enabled. The value should be an array reference. Bad header lines will be pushed onto the array. The C<laxed> option must be specified in order to communicate with pre-HTTP/1.0 servers that don't describe the response outcome or the data they send back with a header block. For these servers peer_http_version is set to "0.9" and this method returns (200, "Assumed OK"). The method will raise an exception (die) if the server does not speak proper HTTP or if the C<max_line_length> or C<max_header_length> limits are reached. If the C<laxed> option is turned on and C<max_line_length> and C<max_header_length> checks are turned off, then no exception will be raised and this method will always return a response code. =item C<< $n = $s->read_entity_body($buf, $size); >> Reads chunks of the entity body content. Basically the same interface as for read() and sysread(), but the buffer offset argument is not supported yet. This method should only be called after a successful read_response_headers() call. The return value will be C<undef> on read errors, 0 on EOF, -1 if no data could be returned this time, otherwise the number of bytes assigned to $buf. The $buf is set to "" when the return value is -1. You normally want to retry this call if this function returns either -1 or C<undef> with C<$!> as EINTR or EAGAIN (see L<Errno>). EINTR can happen if the application catches signals and EAGAIN can happen if you made the socket non-blocking. This method will raise exceptions (die) if the server does not speak proper HTTP. This can only happen when reading chunked data. =item C<< %headers = $s->get_trailers >> After read_entity_body() has returned 0 to indicate end of the entity body, you might call this method to pick up any trailers. =item C<< $s->_rbuf >> Get/set the read buffer content. The read_response_headers() and read_entity_body() methods use an internal buffer which they will look for data before they actually sysread more from the socket itself. If they read too much, the remaining data will be left in this buffer. =item C<< $s->_rbuf_length >> Returns the number of bytes in the read buffer. This should always be the same as: length($s->_rbuf) but might be more efficient. =back =head1 SUBCLASSING The read_response_headers() and read_entity_body() will invoke the sysread() method when they need more data. Subclasses might want to override this method to control how reading takes place. The object itself is a glob. Subclasses should avoid using hash key names prefixed with C<http_> and C<io_>. =head1 SEE ALSO L<LWP>, L<IO::Socket::INET>, L<Net::HTTP::NB> =head1 AUTHOR Gisle Aas <gisle@activestate.com> =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2001 by Gisle Aas. 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: Low-level HTTP connection (client) PK�������!�H�A�A� ��perl5/CPAN.pmnu�6$��������# -*- Mode: cperl; coding: utf-8; cperl-indent-level: 4 -*- # vim: ts=4 sts=4 sw=4: use strict; package CPAN; $CPAN::VERSION = '2.36'; $CPAN::VERSION =~ s/_//; # we need to run chdir all over and we would get at wrong libraries # there use File::Spec (); BEGIN { if (File::Spec->can("rel2abs")) { for my $inc (@INC) { $inc = File::Spec->rel2abs($inc) unless ref $inc; } } $SIG{WINCH} = 'IGNORE' if exists $SIG{WINCH}; } use CPAN::Author; use CPAN::HandleConfig; use CPAN::Version; use CPAN::Bundle; use CPAN::CacheMgr; use CPAN::Complete; use CPAN::Debug; use CPAN::Distribution; use CPAN::Distrostatus; use CPAN::FTP; use CPAN::Index 1.93; # https://rt.cpan.org/Ticket/Display.html?id=43349 use CPAN::InfoObj; use CPAN::Module; use CPAN::Prompt; use CPAN::URL; use CPAN::Queue; use CPAN::Tarzip; use CPAN::DeferredCode; use CPAN::Shell; use CPAN::LWP::UserAgent; use CPAN::Exception::RecursiveDependency; use CPAN::Exception::yaml_not_installed; use CPAN::Exception::yaml_process_error; use Carp (); use Config (); use Cwd qw(chdir); use DirHandle (); use Exporter (); use ExtUtils::MakeMaker qw(prompt); # for some unknown reason, # 5.005_04 does not work without # this use File::Basename (); use File::Copy (); use File::Find; use File::Path (); use FileHandle (); use Fcntl qw(:flock); use Safe (); use Sys::Hostname qw(hostname); use Text::ParseWords (); use Text::Wrap (); # protect against "called too early" sub find_perl (); sub anycwd (); sub _uniq; no lib "."; require Mac::BuildTools if $^O eq 'MacOS'; if ($ENV{PERL5_CPAN_IS_RUNNING} && $$ != $ENV{PERL5_CPAN_IS_RUNNING}) { $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION} ||= $ENV{PERL5_CPAN_IS_RUNNING}; my @rec = _uniq split(/,/, $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION}), $$; $ENV{PERL5_CPAN_IS_RUNNING_IN_RECURSION} = join ",", @rec; # warn "# Note: Recursive call of CPAN.pm detected\n"; my $w = sprintf "# Note: CPAN.pm is running in process %d now", pop @rec; my %sleep = ( 5 => 30, 6 => 60, 7 => 120, ); my $sleep = @rec > 7 ? 300 : ($sleep{scalar @rec}||0); my $verbose = @rec >= 4; while (@rec) { $w .= sprintf " which has been called by process %d", pop @rec; } if ($sleep) { $w .= ".\n\n# Sleeping $sleep seconds to protect other processes\n"; } if ($verbose) { warn $w; } local $| = 1; my $have_been_sleeping = 0; while ($sleep > 0) { printf "\r#%5d", --$sleep; sleep 1; ++$have_been_sleeping; } print "\n" if $have_been_sleeping; } $ENV{PERL5_CPAN_IS_RUNNING}=$$; $ENV{PERL5_CPANPLUS_IS_RUNNING}=$$; # https://rt.cpan.org/Ticket/Display.html?id=23735 END { $CPAN::End++; &cleanup; } $CPAN::Signal ||= 0; $CPAN::Frontend ||= "CPAN::Shell"; unless (@CPAN::Defaultsites) { @CPAN::Defaultsites = map { CPAN::URL->new(TEXT => $_, FROM => "DEF") } "http://www.perl.org/CPAN/", "ftp://ftp.perl.org/pub/CPAN/"; } # $CPAN::iCwd (i for initial) $CPAN::iCwd ||= CPAN::anycwd(); $CPAN::Perl ||= CPAN::find_perl(); $CPAN::Defaultdocs ||= "http://search.cpan.org/perldoc?"; $CPAN::Defaultrecent ||= "http://search.cpan.org/uploads.rdf"; $CPAN::Defaultrecent ||= "http://cpan.uwinnipeg.ca/htdocs/cpan.xml"; # our globals are getting a mess use vars qw( $AUTOLOAD $Be_Silent $CONFIG_DIRTY $Defaultdocs $Echo_readline $Frontend $GOTOSHELL $HAS_USABLE $Have_warned $MAX_RECURSION $META $RUN_DEGRADED $Signal $SQLite $Suppress_readline $VERSION $autoload_recursion $term @Defaultsites @EXPORT ); $MAX_RECURSION = 32; @CPAN::ISA = qw(CPAN::Debug Exporter); # note that these functions live in CPAN::Shell and get executed via # AUTOLOAD when called directly @EXPORT = qw( autobundle bundle clean cvs_import expand force fforce get install install_tested is_tested make mkmyconfig notest perldoc readme recent recompile report shell smoke test upgrade ); sub soft_chdir_with_alternatives ($); { $autoload_recursion ||= 0; #-> sub CPAN::AUTOLOAD ; sub AUTOLOAD { ## no critic $autoload_recursion++; my($l) = $AUTOLOAD; $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; } my(%export); @export{@EXPORT} = ''; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; if (exists $export{$l}) { CPAN::Shell->$l(@_); } else { die(qq{Unknown CPAN command "$AUTOLOAD". }. qq{Type ? for help.\n}); } $autoload_recursion--; } } { my $x = *SAVEOUT; # avoid warning open($x,">&STDOUT") or die "dup failed"; my $redir = 0; sub _redirect(@) { #die if $redir; local $_; push(@_,undef); while(defined($_=shift)) { if (s/^\s*>//){ my ($m) = s/^>// ? ">" : ""; s/\s+//; $_=shift unless length; die "no dest" unless defined; open(STDOUT,">$m$_") or die "open:$_:$!\n"; $redir=1; } elsif ( s/^\s*\|\s*// ) { my $pipe="| $_"; while(defined($_[0])){ $pipe .= ' ' . shift; } open(STDOUT,$pipe) or die "open:$pipe:$!\n"; $redir=1; } else { push(@_,$_); } } return @_; } sub _unredirect { return unless $redir; $redir = 0; ## redirect: unredirect and propagate errors. explicit close to wait for pipe. close(STDOUT); open(STDOUT,">&SAVEOUT"); die "$@" if "$@"; ## redirect: done } } sub _uniq { my(@list) = @_; my %seen; return grep { !$seen{$_}++ } @list; } #-> sub CPAN::shell ; sub shell { my($self) = @_; $Suppress_readline = ! -t STDIN unless defined $Suppress_readline; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; my $oprompt = shift || CPAN::Prompt->new; my $prompt = $oprompt; my $commandline = shift || ""; $CPAN::CurrentCommandId ||= 1; local($^W) = 1; unless ($Suppress_readline) { require Term::ReadLine; if (! $term or $term->ReadLine eq "Term::ReadLine::Stub" ) { $term = Term::ReadLine->new('CPAN Monitor'); } if ($term->ReadLine eq "Term::ReadLine::Gnu") { my $attribs = $term->Attribs; $attribs->{attempted_completion_function} = sub { &CPAN::Complete::gnu_cpl; } } else { $readline::rl_completion_function = $readline::rl_completion_function = 'CPAN::Complete::cpl'; } if (my $histfile = $CPAN::Config->{'histfile'}) {{ unless ($term->can("AddHistory")) { $CPAN::Frontend->mywarn("Terminal does not support AddHistory.\n"); unless ($CPAN::META->has_inst('Term::ReadLine::Perl')) { $CPAN::Frontend->mywarn("\nTo fix that, maybe try> install Term::ReadLine::Perl\n\n"); } last; } $META->readhist($term,$histfile); }} for ($CPAN::Config->{term_ornaments}) { # alias local $Term::ReadLine::termcap_nowarn = 1; $term->ornaments($_) if defined; } # $term->OUT is autoflushed anyway my $odef = select STDERR; $| = 1; select STDOUT; $| = 1; select $odef; } $META->checklock(); my @cwd = grep { defined $_ and length $_ } CPAN::anycwd(), File::Spec->can("tmpdir") ? File::Spec->tmpdir() : (), File::Spec->rootdir(); my $try_detect_readline; $try_detect_readline = $term->ReadLine eq "Term::ReadLine::Stub" if $term; unless ($CPAN::Config->{inhibit_startup_message}) { my $rl_avail = $Suppress_readline ? "suppressed" : ($term->ReadLine ne "Term::ReadLine::Stub") ? "enabled" : "available (maybe install Bundle::CPAN or Bundle::CPANxxl?)"; $CPAN::Frontend->myprint( sprintf qq{ cpan shell -- CPAN exploration and modules installation (v%s) Enter 'h' for help. }, $CPAN::VERSION, ) } my($continuation) = ""; my $last_term_ornaments; SHELLCOMMAND: while () { if ($Suppress_readline) { if ($Echo_readline) { $|=1; } print $prompt; last SHELLCOMMAND unless defined ($_ = <> ); if ($Echo_readline) { # backdoor: I could not find a way to record sessions print $_; } chomp; } else { last SHELLCOMMAND unless defined ($_ = $term->readline($prompt, $commandline)); } $_ = "$continuation$_" if $continuation; s/^\s+//; next SHELLCOMMAND if /^$/; s/^\s*\?\s*/help /; if (/^(?:q(?:uit)?|bye|exit)\s*$/i) { last SHELLCOMMAND; } elsif (s/\\$//s) { chomp; $continuation = $_; $prompt = " > "; } elsif (/^\!/) { s/^\!//; my($eval) = $_; package CPAN::Eval; # hide from the indexer use strict; use vars qw($import_done); CPAN->import(':DEFAULT') unless $import_done++; CPAN->debug("eval[$eval]") if $CPAN::DEBUG; eval($eval); warn $@ if $@; $continuation = ""; $prompt = $oprompt; } elsif (/./) { my(@line); eval { @line = Text::ParseWords::shellwords($_) }; warn($@), next SHELLCOMMAND if $@; warn("Text::Parsewords could not parse the line [$_]"), next SHELLCOMMAND unless @line; $CPAN::META->debug("line[".join("|",@line)."]") if $CPAN::DEBUG; my $command = shift @line; eval { local (*STDOUT)=*STDOUT; @line = _redirect(@line); CPAN::Shell->$command(@line) }; my $command_error = $@; _unredirect; my $reported_error; if ($command_error) { my $err = $command_error; if (ref $err and $err->isa('CPAN::Exception::blocked_urllist')) { $CPAN::Frontend->mywarn("Client not fully configured, please proceed with configuring.$err"); $reported_error = ref $err; } else { # I'd prefer never to arrive here and make all errors exception objects if ($err =~ /\S/) { require Carp; require Dumpvalue; my $dv = Dumpvalue->new(tick => '"'); Carp::cluck(sprintf "Catching error: %s", $dv->stringify($err)); } } } if ($command =~ /^( # classic commands make |test |install |clean # pragmas for classic commands |ff?orce |notest # compounds |report |smoke |upgrade )$/x) { # only commands that tell us something about failed distros # eval necessary for people without an urllist eval {CPAN::Shell->failed($CPAN::CurrentCommandId,1);}; if (my $err = $@) { unless (ref $err and $reported_error eq ref $err) { die $@; } } } soft_chdir_with_alternatives(\@cwd); $CPAN::Frontend->myprint("\n"); $continuation = ""; $CPAN::CurrentCommandId++; $prompt = $oprompt; } } continue { $commandline = ""; # I do want to be able to pass a default to # shell, but on the second command I see no # use in that $Signal=0; CPAN::Queue->nullify_queue; if ($try_detect_readline) { if ($CPAN::META->has_inst("Term::ReadLine::Gnu") || $CPAN::META->has_inst("Term::ReadLine::Perl") ) { delete $INC{"Term/ReadLine.pm"}; my $redef = 0; local($SIG{__WARN__}) = CPAN::Shell::paintdots_onreload(\$redef); require Term::ReadLine; $CPAN::Frontend->myprint("\n$redef subroutines in ". "Term::ReadLine redefined\n"); $GOTOSHELL = 1; } } if ($term and $term->can("ornaments")) { for ($CPAN::Config->{term_ornaments}) { # alias if (defined $_) { if (not defined $last_term_ornaments or $_ != $last_term_ornaments ) { local $Term::ReadLine::termcap_nowarn = 1; $term->ornaments($_); $last_term_ornaments = $_; } } else { undef $last_term_ornaments; } } } for my $class (qw(Module Distribution)) { # again unsafe meta access? for my $dm (sort keys %{$CPAN::META->{readwrite}{"CPAN::$class"}}) { next unless $CPAN::META->{readwrite}{"CPAN::$class"}{$dm}{incommandcolor}; CPAN->debug("BUG: $class '$dm' was in command state, resetting"); delete $CPAN::META->{readwrite}{"CPAN::$class"}{$dm}{incommandcolor}; } } if ($GOTOSHELL) { $GOTOSHELL = 0; # not too often $META->savehist if $CPAN::term && $CPAN::term->can("GetHistory"); @_ = ($oprompt,""); goto &shell; } } soft_chdir_with_alternatives(\@cwd); } #-> CPAN::soft_chdir_with_alternatives ; sub soft_chdir_with_alternatives ($) { my($cwd) = @_; unless (@$cwd) { my $root = File::Spec->rootdir(); $CPAN::Frontend->mywarn(qq{Warning: no good directory to chdir to! Trying '$root' as temporary haven. }); push @$cwd, $root; } while () { if (chdir "$cwd->[0]") { return; } else { if (@$cwd>1) { $CPAN::Frontend->mywarn(qq{Could not chdir to "$cwd->[0]": $! Trying to chdir to "$cwd->[1]" instead. }); shift @$cwd; } else { $CPAN::Frontend->mydie(qq{Could not chdir to "$cwd->[0]": $!}); } } } } sub _flock { my($fh,$mode) = @_; if ( $Config::Config{d_flock} || $Config::Config{d_fcntl_can_lock} ) { return flock $fh, $mode; } elsif (!$Have_warned->{"d_flock"}++) { $CPAN::Frontend->mywarn("Your OS does not seem to support locking; continuing and ignoring all locking issues\n"); $CPAN::Frontend->mysleep(5); return 1; } else { return 1; } } sub _yaml_module () { my $yaml_module = $CPAN::Config->{yaml_module} || "YAML"; if ( $yaml_module ne "YAML" && !$CPAN::META->has_inst($yaml_module) ) { # $CPAN::Frontend->mywarn("'$yaml_module' not installed, falling back to 'YAML'\n"); $yaml_module = "YAML"; } if ($yaml_module eq "YAML" && $CPAN::META->has_inst($yaml_module) && $YAML::VERSION < 0.60 && !$Have_warned->{"YAML"}++ ) { $CPAN::Frontend->mywarn("Warning: YAML version '$YAML::VERSION' is too low, please upgrade!\n". "I'll continue but problems are *very* likely to happen.\n" ); $CPAN::Frontend->mysleep(5); } return $yaml_module; } # CPAN::_yaml_loadfile sub _yaml_loadfile { my($self,$local_file,$opt) = @_; return +[] unless -s $local_file; my $opt_loadblessed = $opt->{loadblessed} || $CPAN::Config->{yaml_load_code} || 0; my $yaml_module = _yaml_module; if ($CPAN::META->has_inst($yaml_module)) { # temporarily enable yaml code deserialisation no strict 'refs'; # 5.6.2 could not do the local() with the reference # so we do it manually instead my $old_loadcode = ${"$yaml_module\::LoadCode"}; my $old_loadblessed = ${"$yaml_module\::LoadBlessed"}; ${ "$yaml_module\::LoadCode" } = $CPAN::Config->{yaml_load_code} || 0; ${ "$yaml_module\::LoadBlessed" } = $opt_loadblessed ? 1 : 0; my ($code, @yaml); if ($code = UNIVERSAL::can($yaml_module, "LoadFile")) { eval { @yaml = $code->($local_file); }; if ($@) { # this shall not be done by the frontend die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"parse",$@); } } elsif ($code = UNIVERSAL::can($yaml_module, "Load")) { local *FH; if (open FH, $local_file) { local $/; my $ystream = <FH>; eval { @yaml = $code->($ystream); }; if ($@) { # this shall not be done by the frontend die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"parse",$@); } } else { $CPAN::Frontend->mywarn("Could not open '$local_file': $!"); } } ${"$yaml_module\::LoadCode"} = $old_loadcode; ${"$yaml_module\::LoadBlessed"} = $old_loadblessed; return \@yaml; } else { # this shall not be done by the frontend die CPAN::Exception::yaml_not_installed->new($yaml_module, $local_file, "parse"); } return +[]; } # CPAN::_yaml_dumpfile sub _yaml_dumpfile { my($self,$local_file,@what) = @_; my $yaml_module = _yaml_module; if ($CPAN::META->has_inst($yaml_module)) { my $code; if (UNIVERSAL::isa($local_file, "FileHandle")) { $code = UNIVERSAL::can($yaml_module, "Dump"); eval { print $local_file $code->(@what) }; } elsif ($code = UNIVERSAL::can($yaml_module, "DumpFile")) { eval { $code->($local_file,@what); }; } elsif ($code = UNIVERSAL::can($yaml_module, "Dump")) { local *FH; open FH, ">$local_file" or die "Could not open '$local_file': $!"; print FH $code->(@what); } if ($@) { die CPAN::Exception::yaml_process_error->new($yaml_module,$local_file,"dump",$@); } } else { if (UNIVERSAL::isa($local_file, "FileHandle")) { # I think this case does not justify a warning at all } else { die CPAN::Exception::yaml_not_installed->new($yaml_module, $local_file, "dump"); } } } sub _init_sqlite () { unless ($CPAN::META->has_inst("CPAN::SQLite")) { $CPAN::Frontend->mywarn(qq{CPAN::SQLite not installed, trying to work without\n}) unless $Have_warned->{"CPAN::SQLite"}++; return; } require CPAN::SQLite::META; # not needed since CVS version of 2006-12-17 $CPAN::SQLite ||= CPAN::SQLite::META->new($CPAN::META); } { my $negative_cache = {}; sub _sqlite_running { if ($negative_cache->{time} && time < $negative_cache->{time} + 60) { # need to cache the result, otherwise too slow return $negative_cache->{fact}; } else { $negative_cache = {}; # reset } my $ret = $CPAN::Config->{use_sqlite} && ($CPAN::SQLite || _init_sqlite()); return $ret if $ret; # fast anyway $negative_cache->{time} = time; return $negative_cache->{fact} = $ret; } } $META ||= CPAN->new; # In case we re-eval ourselves we need the || # from here on only subs. ################################################################################ sub _perl_fingerprint { my($self,$other_fingerprint) = @_; my $dll = eval {OS2::DLLname()}; my $mtime_dll = 0; if (defined $dll) { $mtime_dll = (-f $dll ? (stat(_))[9] : '-1'); } my $mtime_perl = (-f CPAN::find_perl ? (stat(_))[9] : '-1'); my $this_fingerprint = { '$^X' => CPAN::find_perl, sitearchexp => $Config::Config{sitearchexp}, 'mtime_$^X' => $mtime_perl, 'mtime_dll' => $mtime_dll, }; if ($other_fingerprint) { if (exists $other_fingerprint->{'stat($^X)'}) { # repair fp from rev. 1.88_57 $other_fingerprint->{'mtime_$^X'} = $other_fingerprint->{'stat($^X)'}[9]; } # mandatory keys since 1.88_57 for my $key (qw($^X sitearchexp mtime_dll mtime_$^X)) { return unless $other_fingerprint->{$key} eq $this_fingerprint->{$key}; } return 1; } else { return $this_fingerprint; } } sub suggest_myconfig () { SUGGEST_MYCONFIG: if(!$INC{'CPAN/MyConfig.pm'}) { $CPAN::Frontend->myprint("You don't seem to have a user ". "configuration (MyConfig.pm) yet.\n"); my $new = CPAN::Shell::colorable_makemaker_prompt("Do you want to create a ". "user configuration now? (Y/n)", "yes"); if($new =~ m{^y}i) { CPAN::Shell->mkmyconfig(); return &checklock; } else { $CPAN::Frontend->mydie("OK, giving up."); } } } #-> sub CPAN::all_objects ; sub all_objects { my($mgr,$class) = @_; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; CPAN->debug("mgr[$mgr] class[$class]") if $CPAN::DEBUG; CPAN::Index->reload; values %{ $META->{readwrite}{$class} }; # unsafe meta access, ok } # Called by shell, not in batch mode. In batch mode I see no risk in # having many processes updating something as installations are # continually checked at runtime. In shell mode I suspect it is # unintentional to open more than one shell at a time #-> sub CPAN::checklock ; sub checklock { my($self) = @_; my $lockfile = File::Spec->catfile($CPAN::Config->{cpan_home},".lock"); if (-f $lockfile && -M _ > 0) { my $fh = FileHandle->new($lockfile) or $CPAN::Frontend->mydie("Could not open lockfile '$lockfile': $!"); my $otherpid = <$fh>; my $otherhost = <$fh>; $fh->close; if (defined $otherpid && length $otherpid) { chomp $otherpid; } if (defined $otherhost && length $otherhost) { chomp $otherhost; } my $thishost = hostname(); my $ask_if_degraded_wanted = 0; if (defined $otherhost && defined $thishost && $otherhost ne '' && $thishost ne '' && $otherhost ne $thishost) { $CPAN::Frontend->mydie(sprintf("CPAN.pm panic: Lockfile '$lockfile'\n". "reports other host $otherhost and other ". "process $otherpid.\n". "Cannot proceed.\n")); } elsif ($RUN_DEGRADED) { $CPAN::Frontend->mywarn("Running in downgraded mode (experimental)\n"); } elsif (defined $otherpid && $otherpid) { return if $$ == $otherpid; # should never happen $CPAN::Frontend->mywarn( qq{ There seems to be running another CPAN process (pid $otherpid). Contacting... }); if (kill 0, $otherpid or $!{EPERM}) { $CPAN::Frontend->mywarn(qq{Other job is running.\n}); $ask_if_degraded_wanted = 1; } elsif (-w $lockfile) { my($ans) = CPAN::Shell::colorable_makemaker_prompt (qq{Other job not responding. Shall I overwrite }. qq{the lockfile '$lockfile'? (Y/n)},"y"); $CPAN::Frontend->myexit("Ok, bye\n") unless $ans =~ /^y/i; } else { Carp::croak( qq{Lockfile '$lockfile' not writable by you. }. qq{Cannot proceed.\n}. qq{ On UNIX try:\n}. qq{ rm '$lockfile'\n}. qq{ and then rerun us.\n} ); } } elsif ($^O eq "MSWin32") { $CPAN::Frontend->mywarn( qq{ There seems to be running another CPAN process according to '$lockfile'. }); $ask_if_degraded_wanted = 1; } else { $CPAN::Frontend->mydie(sprintf("CPAN.pm panic: Found invalid lockfile ". "'$lockfile', please remove. Cannot proceed.\n")); } if ($ask_if_degraded_wanted) { my($ans) = CPAN::Shell::colorable_makemaker_prompt (qq{Shall I try to run in downgraded }. qq{mode? (Y/n)},"y"); if ($ans =~ /^y/i) { $CPAN::Frontend->mywarn("Running in downgraded mode (experimental). Please report if something unexpected happens\n"); $RUN_DEGRADED = 1; for ($CPAN::Config) { # XXX # $_->{build_dir_reuse} = 0; # 2006-11-17 akoenig Why was that? $_->{commandnumber_in_prompt} = 0; # visibility $_->{histfile} = ""; # who should win otherwise? $_->{cache_metadata} = 0; # better would be a lock? $_->{use_sqlite} = 0; # better would be a write lock! $_->{auto_commit} = 0; # we are violent, do not persist $_->{test_report} = 0; # Oliver Paukstadt had sent wrong reports in degraded mode } } else { my $msg = "You may want to kill the other job and delete the lockfile."; if (defined $otherpid) { $msg .= " Something like: kill $otherpid rm $lockfile "; } $CPAN::Frontend->mydie("\n$msg"); } } } my $dotcpan = $CPAN::Config->{cpan_home}; eval { File::Path::mkpath($dotcpan);}; if ($@) { # A special case at least for Jarkko. my $firsterror = $@; my $seconderror; my $symlinkcpan; if (-l $dotcpan) { $symlinkcpan = readlink $dotcpan; die "readlink $dotcpan failed: $!" unless defined $symlinkcpan; eval { File::Path::mkpath($symlinkcpan); }; if ($@) { $seconderror = $@; } else { $CPAN::Frontend->mywarn(qq{ Working directory $symlinkcpan created. }); } } unless (-d $dotcpan) { my $mess = qq{ Your configuration suggests "$dotcpan" as your CPAN.pm working directory. I could not create this directory due to this error: $firsterror\n}; $mess .= qq{ As "$dotcpan" is a symlink to "$symlinkcpan", I tried to create that, but I failed with this error: $seconderror } if $seconderror; $mess .= qq{ Please make sure the directory exists and is writable. }; $CPAN::Frontend->mywarn($mess); return suggest_myconfig; } } # $@ after eval mkpath $dotcpan if (0) { # to test what happens when a race condition occurs for (reverse 1..10) { print $_, "\n"; sleep 1; } } # locking if (!$RUN_DEGRADED && !$self->{LOCKFH}) { my $fh; unless ($fh = FileHandle->new("+>>$lockfile")) { $CPAN::Frontend->mywarn(qq{ Your configuration suggests that CPAN.pm should use a working directory of $CPAN::Config->{cpan_home} Unfortunately we could not create the lock file $lockfile due to '$!'. Please make sure that the configuration variable \$CPAN::Config->{cpan_home} points to a directory where you can write a .lock file. You can set this variable in either a CPAN/MyConfig.pm or a CPAN/Config.pm in your \@INC path; }); return suggest_myconfig; } my $sleep = 1; while (!CPAN::_flock($fh, LOCK_EX|LOCK_NB)) { my $err = $! || "unknown error"; if ($sleep>3) { $CPAN::Frontend->mydie("Could not lock '$lockfile' with flock: $err; giving up\n"); } $CPAN::Frontend->mysleep($sleep+=0.1); $CPAN::Frontend->mywarn("Could not lock '$lockfile' with flock: $err; retrying\n"); } seek $fh, 0, 0; truncate $fh, 0; $fh->autoflush(1); $fh->print($$, "\n"); $fh->print(hostname(), "\n"); $self->{LOCK} = $lockfile; $self->{LOCKFH} = $fh; } $SIG{TERM} = sub { my $sig = shift; &cleanup; $CPAN::Frontend->mydie("Got SIG$sig, leaving"); }; $SIG{INT} = sub { # no blocks!!! my $sig = shift; &cleanup if $Signal; die "Got yet another signal" if $Signal > 1; $CPAN::Frontend->mydie("Got another SIG$sig") if $Signal; $CPAN::Frontend->mywarn("Caught SIG$sig, trying to continue\n"); $Signal++; }; # From: Larry Wall <larry@wall.org> # Subject: Re: deprecating SIGDIE # To: perl5-porters@perl.org # Date: Thu, 30 Sep 1999 14:58:40 -0700 (PDT) # # The original intent of __DIE__ was only to allow you to substitute one # kind of death for another on an application-wide basis without respect # to whether you were in an eval or not. As a global backstop, it should # not be used any more lightly (or any more heavily :-) than class # UNIVERSAL. Any attempt to build a general exception model on it should # be politely squashed. Any bug that causes every eval {} to have to be # modified should be not so politely squashed. # # Those are my current opinions. It is also my opinion that polite # arguments degenerate to personal arguments far too frequently, and that # when they do, it's because both people wanted it to, or at least didn't # sufficiently want it not to. # # Larry # global backstop to cleanup if we should really die $SIG{__DIE__} = \&cleanup; $self->debug("Signal handler set.") if $CPAN::DEBUG; } #-> sub CPAN::DESTROY ; sub DESTROY { &cleanup; # need an eval? } #-> sub CPAN::anycwd ; sub anycwd () { my $getcwd; $getcwd = $CPAN::Config->{'getcwd'} || 'cwd'; CPAN->$getcwd(); } #-> sub CPAN::cwd ; sub cwd {Cwd::cwd();} #-> sub CPAN::getcwd ; sub getcwd {Cwd::getcwd();} #-> sub CPAN::fastcwd ; sub fastcwd {Cwd::fastcwd();} #-> sub CPAN::getdcwd ; sub getdcwd {Cwd::getdcwd();} #-> sub CPAN::backtickcwd ; sub backtickcwd {my $cwd = `cwd`; chomp $cwd; $cwd} # Adapted from Probe::Perl #-> sub CPAN::_perl_is_same sub _perl_is_same { my ($perl) = @_; return MM->maybe_command($perl) && `$perl -MConfig=myconfig -e print -e myconfig` eq Config->myconfig; } # Adapted in part from Probe::Perl #-> sub CPAN::find_perl ; sub find_perl () { if ( File::Spec->file_name_is_absolute($^X) ) { return $^X; } else { my $exe = $Config::Config{exe_ext}; my @candidates = ( File::Spec->catfile($CPAN::iCwd,$^X), $Config::Config{'perlpath'}, ); for my $perl_name ($^X, 'perl', 'perl5', "perl$]") { for my $path (File::Spec->path(), $Config::Config{'binexp'}) { if ( defined($path) && length $path && -d $path ) { my $perl = File::Spec->catfile($path,$perl_name); push @candidates, $perl; # try with extension if not provided already if ($^O eq 'VMS') { # VMS might have a file version at the end push @candidates, $perl . $exe unless $perl =~ m/$exe(;\d+)?$/i; } elsif (defined $exe && length $exe) { push @candidates, $perl . $exe unless $perl =~ m/$exe$/i; } } } } for my $perl ( @candidates ) { if (MM->maybe_command($perl) && _perl_is_same($perl)) { $^X = $perl; return $perl; } } } return $^X; # default fall back } #-> sub CPAN::exists ; sub exists { my($mgr,$class,$id) = @_; CPAN::HandleConfig->load unless $CPAN::Config_loaded++; CPAN::Index->reload; ### Carp::croak "exists called without class argument" unless $class; $id ||= ""; $id =~ s/:+/::/g if $class eq "CPAN::Module"; my $exists; if (CPAN::_sqlite_running) { $exists = (exists $META->{readonly}{$class}{$id} or $CPAN::SQLite->set($class, $id)); } else { $exists = exists $META->{readonly}{$class}{$id}; } $exists ||= exists $META->{readwrite}{$class}{$id}; # unsafe meta access, ok } #-> sub CPAN::delete ; sub delete { my($mgr,$class,$id) = @_; delete $META->{readonly}{$class}{$id}; # unsafe meta access, ok delete $META->{readwrite}{$class}{$id}; # unsafe meta access, ok } #-> sub CPAN::has_usable # has_inst is sometimes too optimistic, we should replace it with this # has_usable whenever a case is given sub has_usable { my($self,$mod,$message) = @_; return 1 if $HAS_USABLE->{$mod}; my $has_inst = $self->has_inst($mod,$message); return unless $has_inst; my $usable; $usable = { # # most of these subroutines warn on the frontend, then # die if the installed version is unusable for some # reason; has_usable() then returns false when it caught # an exception, otherwise returns true and caches that; # 'CPAN::Meta' => [ sub { require CPAN::Meta; unless (CPAN::Version->vge(CPAN::Meta->VERSION, 2.110350)) { for ("Will not use CPAN::Meta, need version 2.110350\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'CPAN::Meta::Requirements' => [ sub { if (defined $CPAN::Meta::Requirements::VERSION && CPAN::Version->vlt($CPAN::Meta::Requirements::VERSION, "2.120920") ) { delete $INC{"CPAN/Meta/Requirements.pm"}; } require CPAN::Meta::Requirements; unless (CPAN::Version->vge(CPAN::Meta::Requirements->VERSION, 2.120920)) { for ("Will not use CPAN::Meta::Requirements, need version 2.120920\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'CPAN::Reporter' => [ sub { if (defined $CPAN::Reporter::VERSION && CPAN::Version->vlt($CPAN::Reporter::VERSION, "1.2011") ) { delete $INC{"CPAN/Reporter.pm"}; } require CPAN::Reporter; unless (CPAN::Version->vge(CPAN::Reporter->VERSION, "1.2011")) { for ("Will not use CPAN::Reporter, need version 1.2011\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], LWP => [ # we frequently had "Can't locate object # method "new" via package "LWP::UserAgent" at # (eval 69) line 2006 sub {require LWP}, sub {require LWP::UserAgent}, sub {require HTTP::Request}, sub {require URI::URL; unless (CPAN::Version->vge(URI::URL::->VERSION,0.08)) { for ("Will not use URI::URL, need 0.08\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'Net::FTP' => [ sub { my $var = $CPAN::Config->{ftp_proxy} || $ENV{ftp_proxy}; if ($var and $var =~ /^http:/i) { # rt #110833 for ("Net::FTP cannot handle http proxy") { $CPAN::Frontend->mywarn($_); die $_; } } }, sub {require Net::FTP}, sub {require Net::Config}, ], 'IO::Socket::SSL' => [ sub { require IO::Socket::SSL; unless (CPAN::Version->vge(IO::Socket::SSL::->VERSION,1.56)) { for ("Will not use IO::Socket::SSL, need 1.56\n") { $CPAN::Frontend->mywarn($_); die $_; } } } ], 'Net::SSLeay' => [ sub { require Net::SSLeay; unless (CPAN::Version->vge(Net::SSLeay::->VERSION,1.49)) { for ("Will not use Net::SSLeay, need 1.49\n") { $CPAN::Frontend->mywarn($_); die $_; } } } ], 'HTTP::Tiny' => [ sub { require HTTP::Tiny; unless (CPAN::Version->vge(HTTP::Tiny->VERSION, 0.005)) { for ("Will not use HTTP::Tiny, need version 0.005\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'File::HomeDir' => [ sub {require File::HomeDir; unless (CPAN::Version->vge(File::HomeDir::->VERSION, 0.52)) { for ("Will not use File::HomeDir, need 0.52\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ], 'Archive::Tar' => [ sub {require Archive::Tar; my $demand = "1.50"; unless (CPAN::Version->vge(Archive::Tar::->VERSION, $demand)) { my $atv = Archive::Tar->VERSION; for ("You have Archive::Tar $atv, but $demand or later is recommended. Please upgrade.\n") { $CPAN::Frontend->mywarn($_); # don't die, because we may need # Archive::Tar to upgrade } } }, ], 'File::Temp' => [ # XXX we should probably delete from # %INC too so we can load after we # installed a new enough version -- # I'm not sure. sub {require File::Temp; unless (CPAN::Version->vge(File::Temp::->VERSION,0.16)) { for ("Will not use File::Temp, need 0.16\n") { $CPAN::Frontend->mywarn($_); die $_; } } }, ] }; if ($usable->{$mod}) { local @INC = @INC; pop @INC if $INC[-1] eq '.'; for my $c (0..$#{$usable->{$mod}}) { my $code = $usable->{$mod}[$c]; my $ret = eval { &$code() }; $ret = "" unless defined $ret; if ($@) { # warn "DEBUG: c[$c]\$\@[$@]ret[$ret]"; return; } } } return $HAS_USABLE->{$mod} = 1; } sub frontend { shift; $CPAN::Frontend = shift if @_; $CPAN::Frontend; } sub use_inst { my ($self, $module) = @_; unless ($self->has_inst($module)) { $self->frontend->mydie("$module not installed, cannot continue"); } } #-> sub CPAN::has_inst sub has_inst { my($self,$mod,$message) = @_; Carp::croak("CPAN->has_inst() called without an argument") unless defined $mod; my %dont = map { $_ => 1 } keys %{$CPAN::META->{dontload_hash}||{}}, keys %{$CPAN::Config->{dontload_hash}||{}}, @{$CPAN::Config->{dontload_list}||[]}; if (defined $message && $message eq "no" # as far as I remember only used by Nox || $dont{$mod} ) { $CPAN::META->{dontload_hash}{$mod}||=1; # unsafe meta access, ok return 0; } local @INC = @INC; pop @INC if $INC[-1] eq '.'; my $file = $mod; my $obj; $file =~ s|::|/|g; $file .= ".pm"; if ($INC{$file}) { # checking %INC is wrong, because $INC{LWP} may be true # although $INC{"URI/URL.pm"} may have failed. But as # I really want to say "blah loaded OK", I have to somehow # cache results. ### warn "$file in %INC"; #debug return 1; } elsif (eval { require $file }) { # eval is good: if we haven't yet read the database it's # perfect and if we have installed the module in the meantime, # it tries again. The second require is only a NOOP returning # 1 if we had success, otherwise it's retrying my $mtime = (stat $INC{$file})[9]; # privileged files loaded by has_inst; Note: we use $mtime # as a proxy for a checksum. $CPAN::Shell::reload->{$file} = $mtime; my $v = eval "\$$mod\::VERSION"; $v = $v ? " (v$v)" : ""; CPAN::Shell->optprint("load_module","CPAN: $mod loaded ok$v\n"); if ($mod eq "CPAN::WAIT") { push @CPAN::Shell::ISA, 'CPAN::WAIT'; } return 1; } elsif ($mod eq "Net::FTP") { $CPAN::Frontend->mywarn(qq{ Please, install Net::FTP as soon as possible. CPAN.pm installs it for you if you just type install Bundle::libnet }) unless $Have_warned->{"Net::FTP"}++; $CPAN::Frontend->mysleep(3); } elsif ($mod eq "Digest::SHA") { if ($Have_warned->{"Digest::SHA"}++) { $CPAN::Frontend->mywarn(qq{CPAN: checksum security checks disabled }. qq{because Digest::SHA not installed.\n}); } else { $CPAN::Frontend->mywarn(qq{ CPAN: checksum security checks disabled because Digest::SHA not installed. Please consider installing the Digest::SHA module. }); $CPAN::Frontend->mysleep(2); } } elsif ($mod eq "Module::Signature") { # NOT prefs_lookup, we are not a distro my $check_sigs = $CPAN::Config->{check_sigs}; if (not $check_sigs) { # they do not want us:-( } elsif (not $Have_warned->{"Module::Signature"}++) { # No point in complaining unless the user can # reasonably install and use it. if (eval { require Crypt::OpenPGP; 1 } || ( defined $CPAN::Config->{'gpg'} && $CPAN::Config->{'gpg'} =~ /\S/ ) ) { $CPAN::Frontend->mywarn(qq{ CPAN: Module::Signature security checks disabled because Module::Signature not installed. Please consider installing the Module::Signature module. You may also need to be able to connect over the Internet to the public key servers like pool.sks-keyservers.net or pgp.mit.edu. }); $CPAN::Frontend->mysleep(2); } } } else { delete $INC{$file}; # if it inc'd LWP but failed during, say, URI } return 0; } #-> sub CPAN::instance ; sub instance { my($mgr,$class,$id) = @_; CPAN::Index->reload; $id ||= ""; # unsafe meta access, ok? return $META->{readwrite}{$class}{$id} if exists $META->{readwrite}{$class}{$id}; $META->{readwrite}{$class}{$id} ||= $class->new(ID => $id); } #-> sub CPAN::new ; sub new { bless {}, shift; } #-> sub CPAN::_exit_messages ; sub _exit_messages { my ($self) = @_; $self->{exit_messages} ||= []; } #-> sub CPAN::cleanup ; sub cleanup { # warn "cleanup called with arg[@_] End[$CPAN::End] Signal[$Signal]"; local $SIG{__DIE__} = ''; my($message) = @_; my $i = 0; my $ineval = 0; my($subroutine); while ((undef,undef,undef,$subroutine) = caller(++$i)) { $ineval = 1, last if $subroutine eq '(eval)'; } return if $ineval && !$CPAN::End; return unless defined $META->{LOCK}; return unless -f $META->{LOCK}; $META->savehist; $META->{cachemgr} ||= CPAN::CacheMgr->new('atexit'); close $META->{LOCKFH}; unlink $META->{LOCK}; # require Carp; # Carp::cluck("DEBUGGING"); if ( $CPAN::CONFIG_DIRTY ) { $CPAN::Frontend->mywarn("Warning: Configuration not saved.\n"); } $CPAN::Frontend->myprint("Lockfile removed.\n"); for my $msg ( @{ $META->_exit_messages } ) { $CPAN::Frontend->myprint($msg); } } #-> sub CPAN::readhist sub readhist { my($self,$term,$histfile) = @_; my $histsize = $CPAN::Config->{'histsize'} || 100; $term->Attribs->{'MaxHistorySize'} = $histsize if (defined($term->Attribs->{'MaxHistorySize'})); my($fh) = FileHandle->new; open $fh, "<$histfile" or return; local $/ = "\n"; while (<$fh>) { chomp; $term->AddHistory($_); } close $fh; } #-> sub CPAN::savehist sub savehist { my($self) = @_; my($histfile,$histsize); unless ($histfile = $CPAN::Config->{'histfile'}) { $CPAN::Frontend->mywarn("No history written (no histfile specified).\n"); return; } $histsize = $CPAN::Config->{'histsize'} || 100; if ($CPAN::term) { unless ($CPAN::term->can("GetHistory")) { $CPAN::Frontend->mywarn("Terminal does not support GetHistory.\n"); return; } } else { return; } my @h = $CPAN::term->GetHistory; splice @h, 0, @h-$histsize if @h>$histsize; my($fh) = FileHandle->new; open $fh, ">$histfile" or $CPAN::Frontend->mydie("Couldn't open >$histfile: $!"); local $\ = local $, = "\n"; print $fh @h; close $fh; } #-> sub CPAN::is_tested sub is_tested { my($self,$what,$when) = @_; unless ($what) { Carp::cluck("DEBUG: empty what"); return; } $self->{is_tested}{$what} = $when; } #-> sub CPAN::reset_tested # forget all distributions tested -- resets what gets included in PERL5LIB sub reset_tested { my ($self) = @_; $self->{is_tested} = {}; } #-> sub CPAN::is_installed # unsets the is_tested flag: as soon as the thing is installed, it is # not needed in set_perl5lib anymore sub is_installed { my($self,$what) = @_; delete $self->{is_tested}{$what}; } sub _list_sorted_descending_is_tested { my($self) = @_; my $foul = 0; my @sorted = sort { ($self->{is_tested}{$b}||0) <=> ($self->{is_tested}{$a}||0) } grep { if ($foul){ 0 } elsif (-e) { 1 } else { $foul = $_; 0 } } keys %{$self->{is_tested}}; if ($foul) { $CPAN::Frontend->mywarn("Lost build_dir detected ($foul), giving up all cached test results of currently running session.\n"); for my $dbd (sort keys %{$self->{is_tested}}) { # distro-build-dir SEARCH: for my $d (sort { $a->id cmp $b->id } $CPAN::META->all_objects("CPAN::Distribution")) { if ($d->{build_dir} && $d->{build_dir} eq $dbd) { $CPAN::Frontend->mywarn(sprintf "Flushing cache for %s\n", $d->pretty_id); $d->fforce(""); last SEARCH; } } delete $self->{is_tested}{$dbd}; } return (); } else { return @sorted; } } #-> sub CPAN::set_perl5lib # Notes on max environment variable length: # - Win32 : XP or later, 8191; Win2000 or NT4, 2047 { my $fh; sub set_perl5lib { my($self,$for) = @_; unless ($for) { (undef,undef,undef,$for) = caller(1); $for =~ s/.*://; } $self->{is_tested} ||= {}; return unless %{$self->{is_tested}}; my $env = $ENV{PERL5LIB}; $env = $ENV{PERLLIB} unless defined $env; my @env; push @env, split /\Q$Config::Config{path_sep}\E/, $env if defined $env and length $env; #my @dirs = map {("$_/blib/arch", "$_/blib/lib")} keys %{$self->{is_tested}}; #$CPAN::Frontend->myprint("Prepending @dirs to PERL5LIB.\n"); my @dirs = map {("$_/blib/arch", "$_/blib/lib")} $self->_list_sorted_descending_is_tested; return if !@dirs; if (@dirs < 12) { $CPAN::Frontend->optprint('perl5lib', "Prepending @dirs to PERL5LIB for '$for'\n"); $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; } elsif (@dirs < 24 ) { my @d = map {my $cp = $_; $cp =~ s/^\Q$CPAN::Config->{build_dir}\E/%BUILDDIR%/; $cp } @dirs; $CPAN::Frontend->optprint('perl5lib', "Prepending @d to PERL5LIB; ". "%BUILDDIR%=$CPAN::Config->{build_dir} ". "for '$for'\n" ); $ENV{PERL5LIB} = join $Config::Config{path_sep}, @dirs, @env; } else { my $cnt = keys %{$self->{is_tested}}; my $newenv = join $Config::Config{path_sep}, @dirs, @env; $CPAN::Frontend->optprint('perl5lib', sprintf ("Prepending blib/arch and blib/lib of ". "%d build dirs to PERL5LIB, reaching size %d; ". "for '%s'\n", $cnt, length($newenv), $for) ); $ENV{PERL5LIB} = $newenv; } }} 1; __END__ =head1 NAME CPAN - query, download and build perl modules from CPAN sites =head1 SYNOPSIS Interactive mode: perl -MCPAN -e shell --or-- cpan Basic commands: # Modules: cpan> install Acme::Meta # in the shell CPAN::Shell->install("Acme::Meta"); # in perl # Distributions: cpan> install NWCLARK/Acme-Meta-0.02.tar.gz # in the shell CPAN::Shell-> install("NWCLARK/Acme-Meta-0.02.tar.gz"); # in perl # module objects: $mo = CPAN::Shell->expandany($mod); $mo = CPAN::Shell->expand("Module",$mod); # same thing # distribution objects: $do = CPAN::Shell->expand("Module",$mod)->distribution; $do = CPAN::Shell->expandany($distro); # same thing $do = CPAN::Shell->expand("Distribution", $distro); # same thing =head1 DESCRIPTION The CPAN module automates or at least simplifies the make and install of perl modules and extensions. It includes some primitive searching capabilities and knows how to use LWP, HTTP::Tiny, Net::FTP and certain external download clients to fetch distributions from the net. These are fetched from one or more mirrored CPAN (Comprehensive Perl Archive Network) sites and unpacked in a dedicated directory. The CPAN module also supports named and versioned I<bundles> of modules. Bundles simplify handling of sets of related modules. See Bundles below. The package contains a session manager and a cache manager. The session manager keeps track of what has been fetched, built, and installed in the current session. The cache manager keeps track of the disk space occupied by the make processes and deletes excess space using a simple FIFO mechanism. All methods provided are accessible in a programmer style and in an interactive shell style. =head2 CPAN::shell([$prompt, $command]) Starting Interactive Mode Enter interactive mode by running perl -MCPAN -e shell or cpan which puts you into a readline interface. If C<Term::ReadKey> and either of C<Term::ReadLine::Perl> or C<Term::ReadLine::Gnu> are installed, history and command completion are supported. Once at the command line, type C<h> for one-page help screen; the rest should be self-explanatory. The function call C<shell> takes two optional arguments: one the prompt, the second the default initial command line (the latter only works if a real ReadLine interface module is installed). The most common uses of the interactive modes are =over 2 =item Searching for authors, bundles, distribution files and modules There are corresponding one-letter commands C<a>, C<b>, C<d>, and C<m> for each of the four categories and another, C<i> for any of the mentioned four. Each of the four entities is implemented as a class with slightly differing methods for displaying an object. Arguments to these commands are either strings exactly matching the identification string of an object, or regular expressions matched case-insensitively against various attributes of the objects. The parser only recognizes a regular expression when you enclose it with slashes. The principle is that the number of objects found influences how an item is displayed. If the search finds one item, the result is displayed with the rather verbose method C<as_string>, but if more than one is found, each object is displayed with the terse method C<as_glimpse>. Examples: cpan> m Acme::MetaSyntactic Module id = Acme::MetaSyntactic CPAN_USERID BOOK (Philippe Bruhat (BooK) <[...]>) CPAN_VERSION 0.99 CPAN_FILE B/BO/BOOK/Acme-MetaSyntactic-0.99.tar.gz UPLOAD_DATE 2006-11-06 MANPAGE Acme::MetaSyntactic - Themed metasyntactic variables names INST_FILE /usr/local/lib/perl/5.10.0/Acme/MetaSyntactic.pm INST_VERSION 0.99 cpan> a BOOK Author id = BOOK EMAIL [...] FULLNAME Philippe Bruhat (BooK) cpan> d BOOK/Acme-MetaSyntactic-0.99.tar.gz Distribution id = B/BO/BOOK/Acme-MetaSyntactic-0.99.tar.gz CPAN_USERID BOOK (Philippe Bruhat (BooK) <[...]>) CONTAINSMODS Acme::MetaSyntactic Acme::MetaSyntactic::Alias [...] UPLOAD_DATE 2006-11-06 cpan> m /lorem/ Module = Acme::MetaSyntactic::loremipsum (BOOK/Acme-MetaSyntactic-0.99.tar.gz) Module Text::Lorem (ADEOLA/Text-Lorem-0.3.tar.gz) Module Text::Lorem::More (RKRIMEN/Text-Lorem-More-0.12.tar.gz) Module Text::Lorem::More::Source (RKRIMEN/Text-Lorem-More-0.12.tar.gz) cpan> i /berlin/ Distribution BEATNIK/Filter-NumberLines-0.02.tar.gz Module = DateTime::TimeZone::Europe::Berlin (DROLSKY/DateTime-TimeZone-0.7904.tar.gz) Module Filter::NumberLines (BEATNIK/Filter-NumberLines-0.02.tar.gz) Author [...] The examples illustrate several aspects: the first three queries target modules, authors, or distros directly and yield exactly one result. The last two use regular expressions and yield several results. The last one targets all of bundles, modules, authors, and distros simultaneously. When more than one result is available, they are printed in one-line format. =item C<get>, C<make>, C<test>, C<install>, C<clean> modules or distributions These commands take any number of arguments and investigate what is necessary to perform the action. Argument processing is as follows: known module name in format Foo/Bar.pm module other embedded slash distribution - with trailing slash dot directory enclosing slashes regexp known module name in format Foo::Bar module If the argument is a distribution file name (recognized by embedded slashes), it is processed. If it is a module, CPAN determines the distribution file in which this module is included and processes that, following any dependencies named in the module's META.yml or Makefile.PL (this behavior is controlled by the configuration parameter C<prerequisites_policy>). If an argument is enclosed in slashes it is treated as a regular expression: it is expanded and if the result is a single object (distribution, bundle or module), this object is processed. Example: install Dummy::Perl # installs the module install AUXXX/Dummy-Perl-3.14.tar.gz # installs that distribution install /Dummy-Perl-3.14/ # same if the regexp is unambiguous C<get> downloads a distribution file and untars or unzips it, C<make> builds it, C<test> runs the test suite, and C<install> installs it. Any C<make> or C<test> is run unconditionally. An install <distribution_file> is also run unconditionally. But for install <module> CPAN checks whether an install is needed and prints I<module up to date> if the distribution file containing the module doesn't need updating. CPAN also keeps track of what it has done within the current session and doesn't try to build a package a second time regardless of whether it succeeded or not. It does not repeat a test run if the test has been run successfully before. Same for install runs. The C<force> pragma may precede another command (currently: C<get>, C<make>, C<test>, or C<install>) to execute the command from scratch and attempt to continue past certain errors. See the section below on the C<force> and the C<fforce> pragma. The C<notest> pragma skips the test part in the build process. Example: cpan> notest install Tk A C<clean> command results in a make clean being executed within the distribution file's working directory. =item C<readme>, C<perldoc>, C<look> module or distribution C<readme> displays the README file of the associated distribution. C<Look> gets and untars (if not yet done) the distribution file, changes to the appropriate directory and opens a subshell process in that directory. C<perldoc> displays the module's pod documentation in html or plain text format. =item C<ls> author =item C<ls> globbing_expression The first form lists all distribution files in and below an author's CPAN directory as stored in the CHECKSUMS files distributed on CPAN. The listing recurses into subdirectories. The second form limits or expands the output with shell globbing as in the following examples: ls JV/make* ls GSAR/*make* ls */*make* The last example is very slow and outputs extra progress indicators that break the alignment of the result. Note that globbing only lists directories explicitly asked for, for example FOO/* will not list FOO/bar/Acme-Sthg-n.nn.tar.gz. This may be regarded as a bug that may be changed in some future version. =item C<failed> The C<failed> command reports all distributions that failed on one of C<make>, C<test> or C<install> for some reason in the currently running shell session. =item Persistence between sessions If the C<YAML> or the C<YAML::Syck> module is installed a record of the internal state of all modules is written to disk after each step. The files contain a signature of the currently running perl version for later perusal. If the configurations variable C<build_dir_reuse> is set to a true value, then CPAN.pm reads the collected YAML files. If the stored signature matches the currently running perl, the stored state is loaded into memory such that persistence between sessions is effectively established. =item The C<force> and the C<fforce> pragma To speed things up in complex installation scenarios, CPAN.pm keeps track of what it has already done and refuses to do some things a second time. A C<get>, a C<make>, and an C<install> are not repeated. A C<test> is repeated only if the previous test was unsuccessful. The diagnostic message when CPAN.pm refuses to do something a second time is one of I<Has already been >C<unwrapped|made|tested successfully> or something similar. Another situation where CPAN refuses to act is an C<install> if the corresponding C<test> was not successful. In all these cases, the user can override this stubborn behaviour by prepending the command with the word force, for example: cpan> force get Foo cpan> force make AUTHOR/Bar-3.14.tar.gz cpan> force test Baz cpan> force install Acme::Meta Each I<forced> command is executed with the corresponding part of its memory erased. The C<fforce> pragma is a variant that emulates a C<force get> which erases the entire memory followed by the action specified, effectively restarting the whole get/make/test/install procedure from scratch. =item Lockfile Interactive sessions maintain a lockfile, by default C<~/.cpan/.lock>. Batch jobs can run without a lockfile and not disturb each other. The shell offers to run in I<downgraded mode> when another process is holding the lockfile. This is an experimental feature that is not yet tested very well. This second shell then does not write the history file, does not use the metadata file, and has a different prompt. =item Signals CPAN.pm installs signal handlers for SIGINT and SIGTERM. While you are in the cpan-shell, it is intended that you can press C<^C> anytime and return to the cpan-shell prompt. A SIGTERM will cause the cpan-shell to clean up and leave the shell loop. You can emulate the effect of a SIGTERM by sending two consecutive SIGINTs, which usually means by pressing C<^C> twice. CPAN.pm ignores SIGPIPE. If the user sets C<inactivity_timeout>, a SIGALRM is used during the run of the C<perl Makefile.PL> or C<perl Build.PL> subprocess. A SIGALRM is also used during module version parsing, and is controlled by C<version_timeout>. =back =head2 CPAN::Shell The commands available in the shell interface are methods in the package CPAN::Shell. If you enter the shell command, your input is split by the Text::ParseWords::shellwords() routine, which acts like most shells do. The first word is interpreted as the method to be invoked, and the rest of the words are treated as the method's arguments. Continuation lines are supported by ending a line with a literal backslash. =head2 autobundle C<autobundle> writes a bundle file into the C<$CPAN::Config-E<gt>{cpan_home}/Bundle> directory. The file contains a list of all modules that are both available from CPAN and currently installed within @INC. Duplicates of each distribution are suppressed. The name of the bundle file is based on the current date and a counter, e.g. F<Bundle/Snapshot_2012_05_21_00.pm>. This is installed again by running C<cpan Bundle::Snapshot_2012_05_21_00>, or installing C<Bundle::Snapshot_2012_05_21_00> from the CPAN shell. Return value: path to the written file. =head2 hosts Note: this feature is still in alpha state and may change in future versions of CPAN.pm This commands provides a statistical overview over recent download activities. The data for this is collected in the YAML file C<FTPstats.yml> in your C<cpan_home> directory. If no YAML module is configured or YAML not installed, no stats are provided. =over =item install_tested Install all distributions that have been tested successfully but have not yet been installed. See also C<is_tested>. =item is_tested List all build directories of distributions that have been tested successfully but have not yet been installed. See also C<install_tested>. =back =head2 mkmyconfig mkmyconfig() writes your own CPAN::MyConfig file into your C<~/.cpan/> directory so that you can save your own preferences instead of the system-wide ones. =head2 r [Module|/Regexp/]... scans current perl installation for modules that have a newer version available on CPAN and provides a list of them. If called without argument, all potential upgrades are listed; if called with arguments the list is filtered to the modules and regexps given as arguments. The listing looks something like this: Package namespace installed latest in CPAN file CPAN 1.94_64 1.9600 ANDK/CPAN-1.9600.tar.gz CPAN::Reporter 1.1801 1.1902 DAGOLDEN/CPAN-Reporter-1.1902.tar.gz YAML 0.70 0.73 INGY/YAML-0.73.tar.gz YAML::Syck 1.14 1.17 AVAR/YAML-Syck-1.17.tar.gz YAML::Tiny 1.44 1.50 ADAMK/YAML-Tiny-1.50.tar.gz CGI 3.43 3.55 MARKSTOS/CGI.pm-3.55.tar.gz Module::Build::YAML 1.40 1.41 DAGOLDEN/Module-Build-0.3800.tar.gz TAP::Parser::Result::YAML 3.22 3.23 ANDYA/Test-Harness-3.23.tar.gz YAML::XS 0.34 0.35 INGY/YAML-LibYAML-0.35.tar.gz It suppresses duplicates in the column C<in CPAN file> such that distributions with many upgradeable modules are listed only once. Note that the list is not sorted. =head2 recent ***EXPERIMENTAL COMMAND*** The C<recent> command downloads a list of recent uploads to CPAN and displays them I<slowly>. While the command is running, a $SIG{INT} exits the loop after displaying the current item. B<Note>: This command requires XML::LibXML installed. B<Note>: This whole command currently is just a hack and will probably change in future versions of CPAN.pm, but the general approach will likely remain. B<Note>: See also L<smoke> =head2 recompile recompile() is a special command that takes no argument and runs the make/test/install cycle with brute force over all installed dynamically loadable extensions (a.k.a. XS modules) with 'force' in effect. The primary purpose of this command is to finish a network installation. Imagine you have a common source tree for two different architectures. You decide to do a completely independent fresh installation. You start on one architecture with the help of a Bundle file produced earlier. CPAN installs the whole Bundle for you, but when you try to repeat the job on the second architecture, CPAN responds with a C<"Foo up to date"> message for all modules. So you invoke CPAN's recompile on the second architecture and you're done. Another popular use for C<recompile> is to act as a rescue in case your perl breaks binary compatibility. If one of the modules that CPAN uses is in turn depending on binary compatibility (so you cannot run CPAN commands), then you should try the CPAN::Nox module for recovery. =head2 report Bundle|Distribution|Module The C<report> command temporarily turns on the C<test_report> config variable, then runs the C<force test> command with the given arguments. The C<force> pragma reruns the tests and repeats every step that might have failed before. =head2 smoke ***EXPERIMENTAL COMMAND*** B<*** WARNING: this command downloads and executes software from CPAN to your computer of completely unknown status. You should never do this with your normal account and better have a dedicated well separated and secured machine to do this. ***> The C<smoke> command takes the list of recent uploads to CPAN as provided by the C<recent> command and tests them all. While the command is running $SIG{INT} is defined to mean that the current item shall be skipped. B<Note>: This whole command currently is just a hack and will probably change in future versions of CPAN.pm, but the general approach will likely remain. B<Note>: See also L<recent> =head2 upgrade [Module|/Regexp/]... The C<upgrade> command first runs an C<r> command with the given arguments and then installs the newest versions of all modules that were listed by that. =head2 The four C<CPAN::*> Classes: Author, Bundle, Module, Distribution Although it may be considered internal, the class hierarchy does matter for both users and programmer. CPAN.pm deals with the four classes mentioned above, and those classes all share a set of methods. Classical single polymorphism is in effect. A metaclass object registers all objects of all kinds and indexes them with a string. The strings referencing objects have a separated namespace (well, not completely separated): Namespace Class words containing a "/" (slash) Distribution words starting with Bundle:: Bundle everything else Module or Author Modules know their associated Distribution objects. They always refer to the most recent official release. Developers may mark their releases as unstable development versions (by inserting an underscore into the module version number which will also be reflected in the distribution name when you run 'make dist'), so the really hottest and newest distribution is not always the default. If a module Foo circulates on CPAN in both version 1.23 and 1.23_90, CPAN.pm offers a convenient way to install version 1.23 by saying install Foo This would install the complete distribution file (say BAR/Foo-1.23.tar.gz) with all accompanying material. But if you would like to install version 1.23_90, you need to know where the distribution file resides on CPAN relative to the authors/id/ directory. If the author is BAR, this might be BAR/Foo-1.23_90.tar.gz; so you would have to say install BAR/Foo-1.23_90.tar.gz The first example will be driven by an object of the class CPAN::Module, the second by an object of class CPAN::Distribution. =head2 Integrating local directories Note: this feature is still in alpha state and may change in future versions of CPAN.pm Distribution objects are normally distributions from the CPAN, but there is a slightly degenerate case for Distribution objects, too, of projects held on the local disk. These distribution objects have the same name as the local directory and end with a dot. A dot by itself is also allowed for the current directory at the time CPAN.pm was used. All actions such as C<make>, C<test>, and C<install> are applied directly to that directory. This gives the command C<cpan .> an interesting touch: while the normal mantra of installing a CPAN module without CPAN.pm is one of perl Makefile.PL perl Build.PL ( go and get prerequisites ) make ./Build make test ./Build test make install ./Build install the command C<cpan .> does all of this at once. It figures out which of the two mantras is appropriate, fetches and installs all prerequisites, takes care of them recursively, and finally finishes the installation of the module in the current directory, be it a CPAN module or not. The typical usage case is for private modules or working copies of projects from remote repositories on the local disk. =head2 Redirection The usual shell redirection symbols C< | > and C<< > >> are recognized by the cpan shell B<only when surrounded by whitespace>. So piping to pager or redirecting output into a file works somewhat as in a normal shell, with the stipulation that you must type extra spaces. =head2 Plugin support ***EXPERIMENTAL*** Plugins are objects that implement any of currently eight methods: pre_get post_get pre_make post_make pre_test post_test pre_install post_install The C<plugin_list> configuration parameter holds a list of strings of the form Modulename=arg0,arg1,arg2,arg3,... eg: CPAN::Plugin::Flurb=dir,/opt/pkgs/flurb/raw,verbose,1 At run time, each listed plugin is instantiated as a singleton object by running the equivalent of this pseudo code: my $plugin = <string representation from config>; <generate Modulename and arguments from $plugin>; my $p = $instance{$plugin} ||= Modulename->new($arg0,$arg1,...); The generated singletons are kept around from instantiation until the end of the shell session. <plugin_list> can be reconfigured at any time at run time. While the cpan shell is running, it checks all activated plugins at each of the 8 reference points listed above and runs the respective method if it is implemented for that object. The method is called with the active CPAN::Distribution object passed in as an argument. =head1 CONFIGURATION When the CPAN module is used for the first time, a configuration dialogue tries to determine a couple of site specific options. The result of the dialog is stored in a hash reference C< $CPAN::Config > in a file CPAN/Config.pm. Default values defined in the CPAN/Config.pm file can be overridden in a user specific file: CPAN/MyConfig.pm. Such a file is best placed in C<$HOME/.cpan/CPAN/MyConfig.pm>, because C<$HOME/.cpan> is added to the search path of the CPAN module before the use() or require() statements. The mkmyconfig command writes this file for you. The C<o conf> command has various bells and whistles: =over =item completion support If you have a ReadLine module installed, you can hit TAB at any point of the commandline and C<o conf> will offer you completion for the built-in subcommands and/or config variable names. =item displaying some help: o conf help Displays a short help =item displaying current values: o conf [KEY] Displays the current value(s) for this config variable. Without KEY, displays all subcommands and config variables. Example: o conf shell If KEY starts and ends with a slash, the string in between is treated as a regular expression and only keys matching this regexp are displayed Example: o conf /color/ =item changing of scalar values: o conf KEY VALUE Sets the config variable KEY to VALUE. The empty string can be specified as usual in shells, with C<''> or C<""> Example: o conf wget /usr/bin/wget =item changing of list values: o conf KEY SHIFT|UNSHIFT|PUSH|POP|SPLICE|LIST If a config variable name ends with C<list>, it is a list. C<o conf KEY shift> removes the first element of the list, C<o conf KEY pop> removes the last element of the list. C<o conf KEYS unshift LIST> prepends a list of values to the list, C<o conf KEYS push LIST> appends a list of valued to the list. Likewise, C<o conf KEY splice LIST> passes the LIST to the corresponding splice command. Finally, any other list of arguments is taken as a new list value for the KEY variable discarding the previous value. Examples: o conf urllist unshift http://cpan.dev.local/CPAN o conf urllist splice 3 1 o conf urllist http://cpan1.local http://cpan2.local ftp://ftp.perl.org =item reverting to saved: o conf defaults Reverts all config variables to the state in the saved config file. =item saving the config: o conf commit Saves all config variables to the current config file (CPAN/Config.pm or CPAN/MyConfig.pm that was loaded at start). =back The configuration dialog can be started any time later again by issuing the command C< o conf init > in the CPAN shell. A subset of the configuration dialog can be run by issuing C<o conf init WORD> where WORD is any valid config variable or a regular expression. =head2 Config Variables The following keys in the hash reference $CPAN::Config are currently defined: allow_installing_module_downgrades allow or disallow installing module downgrades allow_installing_outdated_dists allow or disallow installing modules that are indexed in the cpan index pointing to a distro with a higher distro-version number applypatch path to external prg auto_commit commit all changes to config variables to disk build_cache size of cache for directories to build modules build_dir locally accessible directory to build modules build_dir_reuse boolean if distros in build_dir are persistent build_requires_install_policy to install or not to install when a module is only needed for building. yes|no|ask/yes|ask/no bzip2 path to external prg cache_metadata use serializer to cache metadata check_sigs if signatures should be verified cleanup_after_install remove build directory immediately after a successful install and remember that for the duration of the session colorize_debug Term::ANSIColor attributes for debugging output colorize_output boolean if Term::ANSIColor should colorize output colorize_print Term::ANSIColor attributes for normal output colorize_warn Term::ANSIColor attributes for warnings commandnumber_in_prompt boolean if you want to see current command number commands_quote preferred character to use for quoting external commands when running them. Defaults to double quote on Windows, single tick everywhere else; can be set to space to disable quoting connect_to_internet_ok whether to ask if opening a connection is ok before urllist is specified cpan_home local directory reserved for this package curl path to external prg dontload_hash DEPRECATED dontload_list arrayref: modules in the list will not be loaded by the CPAN::has_inst() routine ftp path to external prg ftp_passive if set, the environment variable FTP_PASSIVE is set for downloads ftp_proxy proxy host for ftp requests ftpstats_period max number of days to keep download statistics ftpstats_size max number of items to keep in the download statistics getcwd see below gpg path to external prg gzip location of external program gzip halt_on_failure stop processing after the first failure of queued items or dependencies histfile file to maintain history between sessions histsize maximum number of lines to keep in histfile http_proxy proxy host for http requests inactivity_timeout breaks interactive Makefile.PLs or Build.PLs after this many seconds inactivity. Set to 0 to disable timeouts. index_expire refetch index files after this many days inhibit_startup_message if true, suppress the startup message keep_source_where directory in which to keep the source (if we do) load_module_verbosity report loading of optional modules used by CPAN.pm lynx path to external prg make location of external make program make_arg arguments that should always be passed to 'make' make_install_make_command the make command for running 'make install', for example 'sudo make' make_install_arg same as make_arg for 'make install' makepl_arg arguments passed to 'perl Makefile.PL' mbuild_arg arguments passed to './Build' mbuild_install_arg arguments passed to './Build install' mbuild_install_build_command command to use instead of './Build' when we are in the install stage, for example 'sudo ./Build' mbuildpl_arg arguments passed to 'perl Build.PL' ncftp path to external prg ncftpget path to external prg no_proxy don't proxy to these hosts/domains (comma separated list) pager location of external program more (or any pager) password your password if you CPAN server wants one patch path to external prg patches_dir local directory containing patch files perl5lib_verbosity verbosity level for PERL5LIB additions plugin_list list of active hooks (see Plugin support above and the CPAN::Plugin module) prefer_external_tar per default all untar operations are done with Archive::Tar; by setting this variable to true the external tar command is used if available prefer_installer legal values are MB and EUMM: if a module comes with both a Makefile.PL and a Build.PL, use the former (EUMM) or the latter (MB); if the module comes with only one of the two, that one will be used no matter the setting prerequisites_policy what to do if you are missing module prerequisites ('follow' automatically, 'ask' me, or 'ignore') For 'follow', also sets PERL_AUTOINSTALL and PERL_EXTUTILS_AUTOINSTALL for "--defaultdeps" if not already set prefs_dir local directory to store per-distro build options proxy_user username for accessing an authenticating proxy proxy_pass password for accessing an authenticating proxy pushy_https use https to cpan.org when possible, otherwise use http to cpan.org and issue a warning randomize_urllist add some randomness to the sequence of the urllist recommends_policy whether recommended prerequisites should be included scan_cache controls scanning of cache ('atstart', 'atexit' or 'never') shell your favorite shell show_unparsable_versions boolean if r command tells which modules are versionless show_upload_date boolean if commands should try to determine upload date show_zero_versions boolean if r command tells for which modules $version==0 suggests_policy whether suggested prerequisites should be included tar location of external program tar tar_verbosity verbosity level for the tar command term_is_latin deprecated: if true Unicode is translated to ISO-8859-1 (and nonsense for characters outside latin range) term_ornaments boolean to turn ReadLine ornamenting on/off test_report email test reports (if CPAN::Reporter is installed) trust_test_report_history skip testing when previously tested ok (according to CPAN::Reporter history) unzip location of external program unzip urllist arrayref to nearby CPAN sites (or equivalent locations) urllist_ping_external use external ping command when autoselecting mirrors urllist_ping_verbose increase verbosity when autoselecting mirrors use_prompt_default set PERL_MM_USE_DEFAULT for configure/make/test/install use_sqlite use CPAN::SQLite for metadata storage (fast and lean) username your username if you CPAN server wants one version_timeout stops version parsing after this many seconds. Default is 15 secs. Set to 0 to disable. wait_list arrayref to a wait server to try (See CPAN::WAIT) wget path to external prg yaml_load_code enable YAML code deserialisation via CPAN::DeferredCode yaml_module which module to use to read/write YAML files You can set and query each of these options interactively in the cpan shell with the C<o conf> or the C<o conf init> command as specified below. =over 2 =item C<o conf E<lt>scalar optionE<gt>> prints the current value of the I<scalar option> =item C<o conf E<lt>scalar optionE<gt> E<lt>valueE<gt>> Sets the value of the I<scalar option> to I<value> =item C<o conf E<lt>list optionE<gt>> prints the current value of the I<list option> in MakeMaker's neatvalue format. =item C<o conf E<lt>list optionE<gt> [shift|pop]> shifts or pops the array in the I<list option> variable =item C<o conf E<lt>list optionE<gt> [unshift|push|splice] E<lt>listE<gt>> works like the corresponding perl commands. =item interactive editing: o conf init [MATCH|LIST] Runs an interactive configuration dialog for matching variables. Without argument runs the dialog over all supported config variables. To specify a MATCH the argument must be enclosed by slashes. Examples: o conf init ftp_passive ftp_proxy o conf init /color/ Note: this method of setting config variables often provides more explanation about the functioning of a variable than the manpage. =back =head2 CPAN::anycwd($path): Note on config variable getcwd CPAN.pm changes the current working directory often and needs to determine its own current working directory. By default it uses Cwd::cwd, but if for some reason this doesn't work on your system, configure alternatives according to the following table: =over 4 =item cwd Calls Cwd::cwd =item getcwd Calls Cwd::getcwd =item fastcwd Calls Cwd::fastcwd =item getdcwd Calls Cwd::getdcwd =item backtickcwd Calls the external command cwd. =back =head2 Note on the format of the urllist parameter urllist parameters are URLs according to RFC 1738. We do a little guessing if your URL is not compliant, but if you have problems with C<file> URLs, please try the correct format. Either: file://localhost/whatever/ftp/pub/CPAN/ or file:///home/ftp/pub/CPAN/ =head2 The urllist parameter has CD-ROM support The C<urllist> parameter of the configuration table contains a list of URLs used for downloading. If the list contains any C<file> URLs, CPAN always tries there first. This feature is disabled for index files. So the recommendation for the owner of a CD-ROM with CPAN contents is: include your local, possibly outdated CD-ROM as a C<file> URL at the end of urllist, e.g. o conf urllist push file://localhost/CDROM/CPAN CPAN.pm will then fetch the index files from one of the CPAN sites that come at the beginning of urllist. It will later check for each module to see whether there is a local copy of the most recent version. Another peculiarity of urllist is that the site that we could successfully fetch the last file from automatically gets a preference token and is tried as the first site for the next request. So if you add a new site at runtime it may happen that the previously preferred site will be tried another time. This means that if you want to disallow a site for the next transfer, it must be explicitly removed from urllist. =head2 Maintaining the urllist parameter If you have YAML.pm (or some other YAML module configured in C<yaml_module>) installed, CPAN.pm collects a few statistical data about recent downloads. You can view the statistics with the C<hosts> command or inspect them directly by looking into the C<FTPstats.yml> file in your C<cpan_home> directory. To get some interesting statistics, it is recommended that C<randomize_urllist> be set; this introduces some amount of randomness into the URL selection. =head2 The C<requires> and C<build_requires> dependency declarations Since CPAN.pm version 1.88_51 modules declared as C<build_requires> by a distribution are treated differently depending on the config variable C<build_requires_install_policy>. By setting C<build_requires_install_policy> to C<no>, such a module is not installed. It is only built and tested, and then kept in the list of tested but uninstalled modules. As such, it is available during the build of the dependent module by integrating the path to the C<blib/arch> and C<blib/lib> directories in the environment variable PERL5LIB. If C<build_requires_install_policy> is set to C<yes>, then both modules declared as C<requires> and those declared as C<build_requires> are treated alike. By setting to C<ask/yes> or C<ask/no>, CPAN.pm asks the user and sets the default accordingly. =head2 Configuration of the allow_installing_* parameters The C<allow_installing_*> parameters are evaluated during the C<make> phase. If set to C<yes>, they allow the testing and the installation of the current distro and otherwise have no effect. If set to C<no>, they may abort the build (preventing testing and installing), depending on the contents of the C<blib/> directory. The C<blib/> directory is the directory that holds all the files that would usually be installed in the C<install> phase. C<allow_installing_outdated_dists> compares the C<blib/> directory with the CPAN index. If it finds something there that belongs, according to the index, to a different dist, it aborts the current build. C<allow_installing_module_downgrades> compares the C<blib/> directory with already installed modules, actually their version numbers, as determined by ExtUtils::MakeMaker or equivalent. If a to-be-installed module would downgrade an already installed module, the current build is aborted. An interesting twist occurs when a distroprefs document demands the installation of an outdated dist via goto while C<allow_installing_outdated_dists> forbids it. Without additional provisions, this would let the C<allow_installing_outdated_dists> win and the distroprefs lose. So the proper arrangement in such a case is to write a second distroprefs document for the distro that C<goto> points to and overrule the C<cpanconfig> there. E.g.: --- match: distribution: "^MAUKE/Keyword-Simple-0.04.tar.gz" goto: "MAUKE/Keyword-Simple-0.03.tar.gz" --- match: distribution: "^MAUKE/Keyword-Simple-0.03.tar.gz" cpanconfig: allow_installing_outdated_dists: yes =head2 Configuration for individual distributions (I<Distroprefs>) (B<Note:> This feature has been introduced in CPAN.pm 1.8854) Distributions on CPAN usually behave according to what we call the CPAN mantra. Or since the advent of Module::Build we should talk about two mantras: perl Makefile.PL perl Build.PL make ./Build make test ./Build test make install ./Build install But some modules cannot be built with this mantra. They try to get some extra data from the user via the environment, extra arguments, or interactively--thus disturbing the installation of large bundles like Phalanx100 or modules with many dependencies like Plagger. The distroprefs system of C<CPAN.pm> addresses this problem by allowing the user to specify extra informations and recipes in YAML files to either =over =item pass additional arguments to one of the four commands, =item set environment variables =item instantiate an Expect object that reads from the console, waits for some regular expressions and enters some answers =item temporarily override assorted C<CPAN.pm> configuration variables =item specify dependencies the original maintainer forgot =item disable the installation of an object altogether =back See the YAML and Data::Dumper files that come with the C<CPAN.pm> distribution in the C<distroprefs/> directory for examples. =head2 Filenames The YAML files themselves must have the C<.yml> extension; all other files are ignored (for two exceptions see I<Fallback Data::Dumper and Storable> below). The containing directory can be specified in C<CPAN.pm> in the C<prefs_dir> config variable. Try C<o conf init prefs_dir> in the CPAN shell to set and activate the distroprefs system. Every YAML file may contain arbitrary documents according to the YAML specification, and every document is treated as an entity that can specify the treatment of a single distribution. Filenames can be picked arbitrarily; C<CPAN.pm> always reads all files (in alphabetical order) and takes the key C<match> (see below in I<Language Specs>) as a hashref containing match criteria that determine if the current distribution matches the YAML document or not. =head2 Fallback Data::Dumper and Storable If neither your configured C<yaml_module> nor YAML.pm is installed, CPAN.pm falls back to using Data::Dumper and Storable and looks for files with the extensions C<.dd> or C<.st> in the C<prefs_dir> directory. These files are expected to contain one or more hashrefs. For Data::Dumper generated files, this is expected to be done with by defining C<$VAR1>, C<$VAR2>, etc. The YAML shell would produce these with the command ysh < somefile.yml > somefile.dd For Storable files the rule is that they must be constructed such that C<Storable::retrieve(file)> returns an array reference and the array elements represent one distropref object each. The conversion from YAML would look like so: perl -MYAML=LoadFile -MStorable=nstore -e ' @y=LoadFile(shift); nstore(\@y, shift)' somefile.yml somefile.st In bootstrapping situations it is usually sufficient to translate only a few YAML files to Data::Dumper for crucial modules like C<YAML::Syck>, C<YAML.pm> and C<Expect.pm>. If you prefer Storable over Data::Dumper, remember to pull out a Storable version that writes an older format than all the other Storable versions that will need to read them. =head2 Blueprint The following example contains all supported keywords and structures with the exception of C<eexpect> which can be used instead of C<expect>. --- comment: "Demo" match: module: "Dancing::Queen" distribution: "^CHACHACHA/Dancing-" not_distribution: "\.zip$" perl: "/usr/local/cariba-perl/bin/perl" perlconfig: archname: "freebsd" not_cc: "gcc" env: DANCING_FLOOR: "Shubiduh" disabled: 1 cpanconfig: make: gmake pl: args: - "--somearg=specialcase" env: {} expect: - "Which is your favorite fruit" - "apple\n" make: args: - all - extra-all env: {} expect: [] commandline: "echo SKIPPING make" test: args: [] env: {} expect: [] install: args: [] env: WANT_TO_INSTALL: YES expect: - "Do you really want to install" - "y\n" patches: - "ABCDE/Fedcba-3.14-ABCDE-01.patch" depends: configure_requires: LWP: 5.8 build_requires: Test::Exception: 0.25 requires: Spiffy: 0.30 =head2 Language Specs Every YAML document represents a single hash reference. The valid keys in this hash are as follows: =over =item comment [scalar] A comment =item cpanconfig [hash] Temporarily override assorted C<CPAN.pm> configuration variables. Supported are: C<build_requires_install_policy>, C<check_sigs>, C<make>, C<make_install_make_command>, C<prefer_installer>, C<test_report>. Please report as a bug when you need another one supported. =item depends [hash] *** EXPERIMENTAL FEATURE *** All three types, namely C<configure_requires>, C<build_requires>, and C<requires> are supported in the way specified in the META.yml specification. The current implementation I<merges> the specified dependencies with those declared by the package maintainer. In a future implementation this may be changed to override the original declaration. =item disabled [boolean] Specifies that this distribution shall not be processed at all. =item features [array] *** EXPERIMENTAL FEATURE *** Experimental implementation to deal with optional_features from META.yml. Still needs coordination with installer software and currently works only for META.yml declaring C<dynamic_config=0>. Use with caution. =item goto [string] The canonical name of a delegate distribution to install instead. Useful when a new version, although it tests OK itself, breaks something else or a developer release or a fork is already uploaded that is better than the last released version. =item install [hash] Processing instructions for the C<make install> or C<./Build install> phase of the CPAN mantra. See below under I<Processing Instructions>. =item make [hash] Processing instructions for the C<make> or C<./Build> phase of the CPAN mantra. See below under I<Processing Instructions>. =item match [hash] A hashref with one or more of the keys C<distribution>, C<module>, C<perl>, C<perlconfig>, and C<env> that specify whether a document is targeted at a specific CPAN distribution or installation. Keys prefixed with C<not_> negates the corresponding match. The corresponding values are interpreted as regular expressions. The C<distribution> related one will be matched against the canonical distribution name, e.g. "AUTHOR/Foo-Bar-3.14.tar.gz". The C<module> related one will be matched against I<all> modules contained in the distribution until one module matches. The C<perl> related one will be matched against C<$^X> (but with the absolute path). The value associated with C<perlconfig> is itself a hashref that is matched against corresponding values in the C<%Config::Config> hash living in the C<Config.pm> module. Keys prefixed with C<not_> negates the corresponding match. The value associated with C<env> is itself a hashref that is matched against corresponding values in the C<%ENV> hash. Keys prefixed with C<not_> negates the corresponding match. If more than one restriction of C<module>, C<distribution>, etc. is specified, the results of the separately computed match values must all match. If so, the hashref represented by the YAML document is returned as the preference structure for the current distribution. =item patches [array] An array of patches on CPAN or on the local disk to be applied in order via an external patch program. If the value for the C<-p> parameter is C<0> or C<1> is determined by reading the patch beforehand. The path to each patch is either an absolute path on the local filesystem or relative to a patch directory specified in the C<patches_dir> configuration variable or in the format of a canonical distro name. For examples please consult the distroprefs/ directory in the CPAN.pm distribution (these examples are not installed by default). Note: if the C<applypatch> program is installed and C<CPAN::Config> knows about it B<and> a patch is written by the C<makepatch> program, then C<CPAN.pm> lets C<applypatch> apply the patch. Both C<makepatch> and C<applypatch> are available from CPAN in the C<JV/makepatch-*> distribution. =item pl [hash] Processing instructions for the C<perl Makefile.PL> or C<perl Build.PL> phase of the CPAN mantra. See below under I<Processing Instructions>. =item test [hash] Processing instructions for the C<make test> or C<./Build test> phase of the CPAN mantra. See below under I<Processing Instructions>. =back =head2 Processing Instructions =over =item args [array] Arguments to be added to the command line =item commandline A full commandline to run via C<system()>. During execution, the environment variable PERL is set to $^X (but with an absolute path). If C<commandline> is specified, C<args> is not used. =item eexpect [hash] Extended C<expect>. This is a hash reference with four allowed keys, C<mode>, C<timeout>, C<reuse>, and C<talk>. You must install the C<Expect> module to use C<eexpect>. CPAN.pm does not install it for you. C<mode> may have the values C<deterministic> for the case where all questions come in the order written down and C<anyorder> for the case where the questions may come in any order. The default mode is C<deterministic>. C<timeout> denotes a timeout in seconds. Floating-point timeouts are OK. With C<mode=deterministic>, the timeout denotes the timeout per question; with C<mode=anyorder> it denotes the timeout per byte received from the stream or questions. C<talk> is a reference to an array that contains alternating questions and answers. Questions are regular expressions and answers are literal strings. The Expect module watches the stream from the execution of the external program (C<perl Makefile.PL>, C<perl Build.PL>, C<make>, etc.). For C<mode=deterministic>, the CPAN.pm injects the corresponding answer as soon as the stream matches the regular expression. For C<mode=anyorder> CPAN.pm answers a question as soon as the timeout is reached for the next byte in the input stream. In this mode you can use the C<reuse> parameter to decide what will happen with a question-answer pair after it has been used. In the default case (reuse=0) it is removed from the array, avoiding being used again accidentally. If you want to answer the question C<Do you really want to do that> several times, then it must be included in the array at least as often as you want this answer to be given. Setting the parameter C<reuse> to 1 makes this repetition unnecessary. =item env [hash] Environment variables to be set during the command =item expect [array] You must install the C<Expect> module to use C<expect>. CPAN.pm does not install it for you. C<< expect: <array> >> is a short notation for this C<eexpect>: eexpect: mode: deterministic timeout: 15 talk: <array> =back =head2 Schema verification with C<Kwalify> If you have the C<Kwalify> module installed (which is part of the Bundle::CPANxxl), then all your distroprefs files are checked for syntactic correctness. =head2 Example Distroprefs Files C<CPAN.pm> comes with a collection of example YAML files. Note that these are really just examples and should not be used without care because they cannot fit everybody's purpose. After all, the authors of the packages that ask questions had a need to ask, so you should watch their questions and adjust the examples to your environment and your needs. You have been warned:-) =head1 PROGRAMMER'S INTERFACE If you do not enter the shell, shell commands are available both as methods (C<CPAN::Shell-E<gt>install(...)>) and as functions in the calling package (C<install(...)>). Before calling low-level commands, it makes sense to initialize components of CPAN you need, e.g.: CPAN::HandleConfig->load; CPAN::Shell::setup_output; CPAN::Index->reload; High-level commands do such initializations automatically. There's currently only one class that has a stable interface - CPAN::Shell. All commands that are available in the CPAN shell are methods of the class CPAN::Shell. The arguments on the commandline are passed as arguments to the method. So if you take for example the shell command notest install A B C the actually executed command is CPAN::Shell->notest("install","A","B","C"); Each of the commands that produce listings of modules (C<r>, C<autobundle>, C<u>) also return a list of the IDs of all modules within the list. =over 2 =item expand($type,@things) The IDs of all objects available within a program are strings that can be expanded to the corresponding real objects with the C<CPAN::Shell-E<gt>expand("Module",@things)> method. Expand returns a list of CPAN::Module objects according to the C<@things> arguments given. In scalar context, it returns only the first element of the list. =item expandany(@things) Like expand, but returns objects of the appropriate type, i.e. CPAN::Bundle objects for bundles, CPAN::Module objects for modules, and CPAN::Distribution objects for distributions. Note: it does not expand to CPAN::Author objects. =item Programming Examples This enables the programmer to do operations that combine functionalities that are available in the shell. # install everything that is outdated on my disk: perl -MCPAN -e 'CPAN::Shell->install(CPAN::Shell->r)' # install my favorite programs if necessary: for $mod (qw(Net::FTP Digest::SHA Data::Dumper)) { CPAN::Shell->install($mod); } # list all modules on my disk that have no VERSION number for $mod (CPAN::Shell->expand("Module","/./")) { next unless $mod->inst_file; # MakeMaker convention for undefined $VERSION: next unless $mod->inst_version eq "undef"; print "No VERSION in ", $mod->id, "\n"; } # find out which distribution on CPAN contains a module: print CPAN::Shell->expand("Module","Apache::Constants")->cpan_file Or if you want to schedule a I<cron> job to watch CPAN, you could list all modules that need updating. First a quick and dirty way: perl -e 'use CPAN; CPAN::Shell->r;' If you don't want any output should all modules be up to date, parse the output of above command for the regular expression C</modules are up to date/> and decide to mail the output only if it doesn't match. If you prefer to do it more in a programmerish style in one single process, something like this may better suit you: # list all modules on my disk that have newer versions on CPAN for $mod (CPAN::Shell->expand("Module","/./")) { next unless $mod->inst_file; next if $mod->uptodate; printf "Module %s is installed as %s, could be updated to %s from CPAN\n", $mod->id, $mod->inst_version, $mod->cpan_version; } If that gives too much output every day, you may want to watch only for three modules. You can write for $mod (CPAN::Shell->expand("Module","/Apache|LWP|CGI/")) { as the first line instead. Or you can combine some of the above tricks: # watch only for a new mod_perl module $mod = CPAN::Shell->expand("Module","mod_perl"); exit if $mod->uptodate; # new mod_perl arrived, let me know all update recommendations CPAN::Shell->r; =back =head2 Methods in the other Classes =over 4 =item CPAN::Author::as_glimpse() Returns a one-line description of the author =item CPAN::Author::as_string() Returns a multi-line description of the author =item CPAN::Author::email() Returns the author's email address =item CPAN::Author::fullname() Returns the author's name =item CPAN::Author::name() An alias for fullname =item CPAN::Bundle::as_glimpse() Returns a one-line description of the bundle =item CPAN::Bundle::as_string() Returns a multi-line description of the bundle =item CPAN::Bundle::clean() Recursively runs the C<clean> method on all items contained in the bundle. =item CPAN::Bundle::contains() Returns a list of objects' IDs contained in a bundle. The associated objects may be bundles, modules or distributions. =item CPAN::Bundle::force($method,@args) Forces CPAN to perform a task that it normally would have refused to do. Force takes as arguments a method name to be called and any number of additional arguments that should be passed to the called method. The internals of the object get the needed changes so that CPAN.pm does not refuse to take the action. The C<force> is passed recursively to all contained objects. See also the section above on the C<force> and the C<fforce> pragma. =item CPAN::Bundle::get() Recursively runs the C<get> method on all items contained in the bundle =item CPAN::Bundle::inst_file() Returns the highest installed version of the bundle in either @INC or C<< $CPAN::Config->{cpan_home} >>. Note that this is different from CPAN::Module::inst_file. =item CPAN::Bundle::inst_version() Like CPAN::Bundle::inst_file, but returns the $VERSION =item CPAN::Bundle::uptodate() Returns 1 if the bundle itself and all its members are up-to-date. =item CPAN::Bundle::install() Recursively runs the C<install> method on all items contained in the bundle =item CPAN::Bundle::make() Recursively runs the C<make> method on all items contained in the bundle =item CPAN::Bundle::readme() Recursively runs the C<readme> method on all items contained in the bundle =item CPAN::Bundle::test() Recursively runs the C<test> method on all items contained in the bundle =item CPAN::Distribution::as_glimpse() Returns a one-line description of the distribution =item CPAN::Distribution::as_string() Returns a multi-line description of the distribution =item CPAN::Distribution::author Returns the CPAN::Author object of the maintainer who uploaded this distribution =item CPAN::Distribution::pretty_id() Returns a string of the form "AUTHORID/TARBALL", where AUTHORID is the author's PAUSE ID and TARBALL is the distribution filename. =item CPAN::Distribution::base_id() Returns the distribution filename without any archive suffix. E.g "Foo-Bar-0.01" =item CPAN::Distribution::clean() Changes to the directory where the distribution has been unpacked and runs C<make clean> there. =item CPAN::Distribution::containsmods() Returns a list of IDs of modules contained in a distribution file. Works only for distributions listed in the 02packages.details.txt.gz file. This typically means that just most recent version of a distribution is covered. =item CPAN::Distribution::cvs_import() Changes to the directory where the distribution has been unpacked and runs something like cvs -d $cvs_root import -m $cvs_log $cvs_dir $userid v$version there. =item CPAN::Distribution::dir() Returns the directory into which this distribution has been unpacked. =item CPAN::Distribution::force($method,@args) Forces CPAN to perform a task that it normally would have refused to do. Force takes as arguments a method name to be called and any number of additional arguments that should be passed to the called method. The internals of the object get the needed changes so that CPAN.pm does not refuse to take the action. See also the section above on the C<force> and the C<fforce> pragma. =item CPAN::Distribution::get() Downloads the distribution from CPAN and unpacks it. Does nothing if the distribution has already been downloaded and unpacked within the current session. =item CPAN::Distribution::install() Changes to the directory where the distribution has been unpacked and runs the external command C<make install> there. If C<make> has not yet been run, it will be run first. A C<make test> is issued in any case and if this fails, the install is cancelled. The cancellation can be avoided by letting C<force> run the C<install> for you. This install method only has the power to install the distribution if there are no dependencies in the way. To install an object along with all its dependencies, use CPAN::Shell->install. Note that install() gives no meaningful return value. See uptodate(). =item CPAN::Distribution::isa_perl() Returns 1 if this distribution file seems to be a perl distribution. Normally this is derived from the file name only, but the index from CPAN can contain a hint to achieve a return value of true for other filenames too. =item CPAN::Distribution::look() Changes to the directory where the distribution has been unpacked and opens a subshell there. Exiting the subshell returns. =item CPAN::Distribution::make() First runs the C<get> method to make sure the distribution is downloaded and unpacked. Changes to the directory where the distribution has been unpacked and runs the external commands C<perl Makefile.PL> or C<perl Build.PL> and C<make> there. =item CPAN::Distribution::perldoc() Downloads the pod documentation of the file associated with a distribution (in HTML format) and runs it through the external command I<lynx> specified in C<< $CPAN::Config->{lynx} >>. If I<lynx> isn't available, it converts it to plain text with the external command I<html2text> and runs it through the pager specified in C<< $CPAN::Config->{pager} >>. =item CPAN::Distribution::prefs() Returns the hash reference from the first matching YAML file that the user has deposited in the C<prefs_dir/> directory. The first succeeding match wins. The files in the C<prefs_dir/> are processed alphabetically, and the canonical distro name (e.g. AUTHOR/Foo-Bar-3.14.tar.gz) is matched against the regular expressions stored in the $root->{match}{distribution} attribute value. Additionally all module names contained in a distribution are matched against the regular expressions in the $root->{match}{module} attribute value. The two match values are ANDed together. Each of the two attributes are optional. =item CPAN::Distribution::prereq_pm() Returns the hash reference that has been announced by a distribution as the C<requires> and C<build_requires> elements. These can be declared either by the C<META.yml> (if authoritative) or can be deposited after the run of C<Build.PL> in the file C<./_build/prereqs> or after the run of C<Makfile.PL> written as the C<PREREQ_PM> hash in a comment in the produced C<Makefile>. I<Note>: this method only works after an attempt has been made to C<make> the distribution. Returns undef otherwise. =item CPAN::Distribution::readme() Downloads the README file associated with a distribution and runs it through the pager specified in C<< $CPAN::Config->{pager} >>. =item CPAN::Distribution::reports() Downloads report data for this distribution from www.cpantesters.org and displays a subset of them. =item CPAN::Distribution::read_yaml() Returns the content of the META.yml of this distro as a hashref. Note: works only after an attempt has been made to C<make> the distribution. Returns undef otherwise. Also returns undef if the content of META.yml is not authoritative. (The rules about what exactly makes the content authoritative are still in flux.) =item CPAN::Distribution::test() Changes to the directory where the distribution has been unpacked and runs C<make test> there. =item CPAN::Distribution::uptodate() Returns 1 if all the modules contained in the distribution are up-to-date. Relies on containsmods. =item CPAN::Index::force_reload() Forces a reload of all indices. =item CPAN::Index::reload() Reloads all indices if they have not been read for more than C<< $CPAN::Config->{index_expire} >> days. =item CPAN::InfoObj::dump() CPAN::Author, CPAN::Bundle, CPAN::Module, and CPAN::Distribution inherit this method. It prints the data structure associated with an object. Useful for debugging. Note: the data structure is considered internal and thus subject to change without notice. =item CPAN::Module::as_glimpse() Returns a one-line description of the module in four columns: The first column contains the word C<Module>, the second column consists of one character: an equals sign if this module is already installed and up-to-date, a less-than sign if this module is installed but can be upgraded, and a space if the module is not installed. The third column is the name of the module and the fourth column gives maintainer or distribution information. =item CPAN::Module::as_string() Returns a multi-line description of the module =item CPAN::Module::clean() Runs a clean on the distribution associated with this module. =item CPAN::Module::cpan_file() Returns the filename on CPAN that is associated with the module. =item CPAN::Module::cpan_version() Returns the latest version of this module available on CPAN. =item CPAN::Module::cvs_import() Runs a cvs_import on the distribution associated with this module. =item CPAN::Module::description() Returns a 44 character description of this module. Only available for modules listed in The Module List (CPAN/modules/00modlist.long.html or 00modlist.long.txt.gz) =item CPAN::Module::distribution() Returns the CPAN::Distribution object that contains the current version of this module. =item CPAN::Module::dslip_status() Returns a hash reference. The keys of the hash are the letters C<D>, C<S>, C<L>, C<I>, and <P>, for development status, support level, language, interface and public licence respectively. The data for the DSLIP status are collected by pause.perl.org when authors register their namespaces. The values of the 5 hash elements are one-character words whose meaning is described in the table below. There are also 5 hash elements C<DV>, C<SV>, C<LV>, C<IV>, and <PV> that carry a more verbose value of the 5 status variables. Where the 'DSLIP' characters have the following meanings: D - Development Stage (Note: *NO IMPLIED TIMESCALES*): i - Idea, listed to gain consensus or as a placeholder c - under construction but pre-alpha (not yet released) a/b - Alpha/Beta testing R - Released M - Mature (no rigorous definition) S - Standard, supplied with Perl 5 S - Support Level: m - Mailing-list d - Developer u - Usenet newsgroup comp.lang.perl.modules n - None known, try comp.lang.perl.modules a - abandoned; volunteers welcome to take over maintenance L - Language Used: p - Perl-only, no compiler needed, should be platform independent c - C and perl, a C compiler will be needed h - Hybrid, written in perl with optional C code, no compiler needed + - C++ and perl, a C++ compiler will be needed o - perl and another language other than C or C++ I - Interface Style f - plain Functions, no references used h - hybrid, object and function interfaces available n - no interface at all (huh?) r - some use of unblessed References or ties O - Object oriented using blessed references and/or inheritance P - Public License p - Standard-Perl: user may choose between GPL and Artistic g - GPL: GNU General Public License l - LGPL: "GNU Lesser General Public License" (previously known as "GNU Library General Public License") b - BSD: The BSD License a - Artistic license alone 2 - Artistic license 2.0 or later o - open source: approved by www.opensource.org d - allows distribution without restrictions r - restricted distribution n - no license at all =item CPAN::Module::force($method,@args) Forces CPAN to perform a task it would normally refuse to do. Force takes as arguments a method name to be invoked and any number of additional arguments to pass that method. The internals of the object get the needed changes so that CPAN.pm does not refuse to take the action. See also the section above on the C<force> and the C<fforce> pragma. =item CPAN::Module::get() Runs a get on the distribution associated with this module. =item CPAN::Module::inst_file() Returns the filename of the module found in @INC. The first file found is reported, just as perl itself stops searching @INC once it finds a module. =item CPAN::Module::available_file() Returns the filename of the module found in PERL5LIB or @INC. The first file found is reported. The advantage of this method over C<inst_file> is that modules that have been tested but not yet installed are included because PERL5LIB keeps track of tested modules. =item CPAN::Module::inst_version() Returns the version number of the installed module in readable format. =item CPAN::Module::available_version() Returns the version number of the available module in readable format. =item CPAN::Module::install() Runs an C<install> on the distribution associated with this module. =item CPAN::Module::look() Changes to the directory where the distribution associated with this module has been unpacked and opens a subshell there. Exiting the subshell returns. =item CPAN::Module::make() Runs a C<make> on the distribution associated with this module. =item CPAN::Module::manpage_headline() If module is installed, peeks into the module's manpage, reads the headline, and returns it. Moreover, if the module has been downloaded within this session, does the equivalent on the downloaded module even if it hasn't been installed yet. =item CPAN::Module::perldoc() Runs a C<perldoc> on this module. =item CPAN::Module::readme() Runs a C<readme> on the distribution associated with this module. =item CPAN::Module::reports() Calls the reports() method on the associated distribution object. =item CPAN::Module::test() Runs a C<test> on the distribution associated with this module. =item CPAN::Module::uptodate() Returns 1 if the module is installed and up-to-date. =item CPAN::Module::userid() Returns the author's ID of the module. =back =head2 Cache Manager Currently the cache manager only keeps track of the build directory ($CPAN::Config->{build_dir}). It is a simple FIFO mechanism that deletes complete directories below C<build_dir> as soon as the size of all directories there gets bigger than $CPAN::Config->{build_cache} (in MB). The contents of this cache may be used for later re-installations that you intend to do manually, but will never be trusted by CPAN itself. This is due to the fact that the user might use these directories for building modules on different architectures. There is another directory ($CPAN::Config->{keep_source_where}) where the original distribution files are kept. This directory is not covered by the cache manager and must be controlled by the user. If you choose to have the same directory as build_dir and as keep_source_where directory, then your sources will be deleted with the same fifo mechanism. =head2 Bundles A bundle is just a perl module in the namespace Bundle:: that does not define any functions or methods. It usually only contains documentation. It starts like a perl module with a package declaration and a $VERSION variable. After that the pod section looks like any other pod with the only difference being that I<one special pod section> exists starting with (verbatim): =head1 CONTENTS In this pod section each line obeys the format Module_Name [Version_String] [- optional text] The only required part is the first field, the name of a module (e.g. Foo::Bar, i.e. I<not> the name of the distribution file). The rest of the line is optional. The comment part is delimited by a dash just as in the man page header. The distribution of a bundle should follow the same convention as other distributions. Bundles are treated specially in the CPAN package. If you say 'install Bundle::Tkkit' (assuming such a bundle exists), CPAN will install all the modules in the CONTENTS section of the pod. You can install your own Bundles locally by placing a conformant Bundle file somewhere into your @INC path. The autobundle() command which is available in the shell interface does that for you by including all currently installed modules in a snapshot bundle file. =head1 PREREQUISITES The CPAN program is trying to depend on as little as possible so the user can use it in hostile environment. It works better the more goodies the environment provides. For example if you try in the CPAN shell install Bundle::CPAN or install Bundle::CPANxxl you will find the shell more convenient than the bare shell before. If you have a local mirror of CPAN and can access all files with "file:" URLs, then you only need a perl later than perl5.003 to run this module. Otherwise Net::FTP is strongly recommended. LWP may be required for non-UNIX systems, or if your nearest CPAN site is associated with a URL that is not C<ftp:>. If you have neither Net::FTP nor LWP, there is a fallback mechanism implemented for an external ftp command or for an external lynx command. =head1 UTILITIES =head2 Finding packages and VERSION This module presumes that all packages on CPAN =over 2 =item * declare their $VERSION variable in an easy to parse manner. This prerequisite can hardly be relaxed because it consumes far too much memory to load all packages into the running program just to determine the $VERSION variable. Currently all programs that are dealing with version use something like this perl -MExtUtils::MakeMaker -le \ 'print MM->parse_version(shift)' filename If you are author of a package and wonder if your $VERSION can be parsed, please try the above method. =item * come as compressed or gzipped tarfiles or as zip files and contain a C<Makefile.PL> or C<Build.PL> (well, we try to handle a bit more, but with little enthusiasm). =back =head2 Debugging Debugging this module is more than a bit complex due to interference from the software producing the indices on CPAN, the mirroring process on CPAN, packaging, configuration, synchronicity, and even (gasp!) due to bugs within the CPAN.pm module itself. For debugging the code of CPAN.pm itself in interactive mode, some debugging aid can be turned on for most packages within CPAN.pm with one of =over 2 =item o debug package... sets debug mode for packages. =item o debug -package... unsets debug mode for packages. =item o debug all turns debugging on for all packages. =item o debug number =back which sets the debugging packages directly. Note that C<o debug 0> turns debugging off. What seems a successful strategy is the combination of C<reload cpan> and the debugging switches. Add a new debug statement while running in the shell and then issue a C<reload cpan> and see the new debugging messages immediately without losing the current context. C<o debug> without an argument lists the valid package names and the current set of packages in debugging mode. C<o debug> has built-in completion support. For debugging of CPAN data there is the C<dump> command which takes the same arguments as make/test/install and outputs each object's Data::Dumper dump. If an argument looks like a perl variable and contains one of C<$>, C<@> or C<%>, it is eval()ed and fed to Data::Dumper directly. =head2 Floppy, Zip, Offline Mode CPAN.pm works nicely without network access, too. If you maintain machines that are not networked at all, you should consider working with C<file:> URLs. You'll have to collect your modules somewhere first. So you might use CPAN.pm to put together all you need on a networked machine. Then copy the $CPAN::Config->{keep_source_where} (but not $CPAN::Config->{build_dir}) directory on a floppy. This floppy is kind of a personal CPAN. CPAN.pm on the non-networked machines works nicely with this floppy. See also below the paragraph about CD-ROM support. =head2 Basic Utilities for Programmers =over 2 =item has_inst($module) Returns true if the module is installed. Used to load all modules into the running CPAN.pm that are considered optional. The config variable C<dontload_list> intercepts the C<has_inst()> call such that an optional module is not loaded despite being available. For example, the following command will prevent C<YAML.pm> from being loaded: cpan> o conf dontload_list push YAML See the source for details. =item use_inst($module) Similary to L<has_inst()> tries to load optional library but also dies if library is not available =item has_usable($module) Returns true if the module is installed and in a usable state. Only useful for a handful of modules that are used internally. See the source for details. =item instance($module) The constructor for all the singletons used to represent modules, distributions, authors, and bundles. If the object already exists, this method returns the object; otherwise, it calls the constructor. =item frontend() =item frontend($new_frontend) Getter/setter for frontend object. Method just allows to subclass CPAN.pm. =back =head1 SECURITY There's no strong security layer in CPAN.pm. CPAN.pm helps you to install foreign, unmasked, unsigned code on your machine. We compare to a checksum that comes from the net just as the distribution file itself. But we try to make it easy to add security on demand: =head2 Cryptographically signed modules Since release 1.77, CPAN.pm has been able to verify cryptographically signed module distributions using Module::Signature. The CPAN modules can be signed by their authors, thus giving more security. The simple unsigned MD5 checksums that were used before by CPAN protect mainly against accidental file corruption. You will need to have Module::Signature installed, which in turn requires that you have at least one of Crypt::OpenPGP module or the command-line F<gpg> tool installed. You will also need to be able to connect over the Internet to the public key servers, like pgp.mit.edu, and their port 11731 (the HKP protocol). The configuration parameter check_sigs is there to turn signature checking on or off. =head1 EXPORT Most functions in package CPAN are exported by default. The reason for this is that the primary use is intended for the cpan shell or for one-liners. =head1 ENVIRONMENT When the CPAN shell enters a subshell via the look command, it sets the environment CPAN_SHELL_LEVEL to 1, or increments that variable if it is already set. When CPAN runs, it sets the environment variable PERL5_CPAN_IS_RUNNING to the ID of the running process. It also sets PERL5_CPANPLUS_IS_RUNNING to prevent runaway processes which could happen with older versions of Module::Install. When running C<perl Makefile.PL>, the environment variable C<PERL5_CPAN_IS_EXECUTING> is set to the full path of the C<Makefile.PL> that is being executed. This prevents runaway processes with newer versions of Module::Install. When the config variable ftp_passive is set, all downloads will be run with the environment variable FTP_PASSIVE set to this value. This is in general a good idea as it influences both Net::FTP and LWP based connections. The same effect can be achieved by starting the cpan shell with this environment variable set. For Net::FTP alone, one can also always set passive mode by running libnetcfg. =head1 POPULATE AN INSTALLATION WITH LOTS OF MODULES Populating a freshly installed perl with one's favorite modules is pretty easy if you maintain a private bundle definition file. To get a useful blueprint of a bundle definition file, the command autobundle can be used on the CPAN shell command line. This command writes a bundle definition file for all modules installed for the current perl interpreter. It's recommended to run this command once only, and from then on maintain the file manually under a private name, say Bundle/my_bundle.pm. With a clever bundle file you can then simply say cpan> install Bundle::my_bundle then answer a few questions and go out for coffee (possibly even in a different city). Maintaining a bundle definition file means keeping track of two things: dependencies and interactivity. CPAN.pm sometimes fails on calculating dependencies because not all modules define all MakeMaker attributes correctly, so a bundle definition file should specify prerequisites as early as possible. On the other hand, it's annoying that so many distributions need some interactive configuring. So what you can try to accomplish in your private bundle file is to have the packages that need to be configured early in the file and the gentle ones later, so you can go out for coffee after a few minutes and leave CPAN.pm to churn away unattended. =head1 WORKING WITH CPAN.pm BEHIND FIREWALLS Thanks to Graham Barr for contributing the following paragraphs about the interaction between perl, and various firewall configurations. For further information on firewalls, it is recommended to consult the documentation that comes with the I<ncftp> program. If you are unable to go through the firewall with a simple Perl setup, it is likely that you can configure I<ncftp> so that it works through your firewall. =head2 Three basic types of firewalls Firewalls can be categorized into three basic types. =over 4 =item http firewall This is when the firewall machine runs a web server, and to access the outside world, you must do so via that web server. If you set environment variables like http_proxy or ftp_proxy to values beginning with http://, or in your web browser you've proxy information set, then you know you are running behind an http firewall. To access servers outside these types of firewalls with perl (even for ftp), you need LWP or HTTP::Tiny. =item ftp firewall This where the firewall machine runs an ftp server. This kind of firewall will only let you access ftp servers outside the firewall. This is usually done by connecting to the firewall with ftp, then entering a username like "user@outside.host.com". To access servers outside these type of firewalls with perl, you need Net::FTP. =item One-way visibility One-way visibility means these firewalls try to make themselves invisible to users inside the firewall. An FTP data connection is normally created by sending your IP address to the remote server and then listening for the return connection. But the remote server will not be able to connect to you because of the firewall. For these types of firewall, FTP connections need to be done in a passive mode. There are two that I can think off. =over 4 =item SOCKS If you are using a SOCKS firewall, you will need to compile perl and link it with the SOCKS library. This is what is normally called a 'socksified' perl. With this executable you will be able to connect to servers outside the firewall as if it were not there. =item IP Masquerade This is when the firewall implemented in the kernel (via NAT, or networking address translation), it allows you to hide a complete network behind one IP address. With this firewall no special compiling is needed as you can access hosts directly. For accessing ftp servers behind such firewalls you usually need to set the environment variable C<FTP_PASSIVE> or the config variable ftp_passive to a true value. =back =back =head2 Configuring lynx or ncftp for going through a firewall If you can go through your firewall with e.g. lynx, presumably with a command such as /usr/local/bin/lynx -pscott:tiger then you would configure CPAN.pm with the command o conf lynx "/usr/local/bin/lynx -pscott:tiger" That's all. Similarly for ncftp or ftp, you would configure something like o conf ncftp "/usr/bin/ncftp -f /home/scott/ncftplogin.cfg" Your mileage may vary... =head1 FAQ =over 4 =item 1) I installed a new version of module X but CPAN keeps saying, I have the old version installed Probably you B<do> have the old version installed. This can happen if a module installs itself into a different directory in the @INC path than it was previously installed. This is not really a CPAN.pm problem, you would have the same problem when installing the module manually. The easiest way to prevent this behaviour is to add the argument C<UNINST=1> to the C<make install> call, and that is why many people add this argument permanently by configuring o conf make_install_arg UNINST=1 =item 2) So why is UNINST=1 not the default? Because there are people who have their precise expectations about who may install where in the @INC path and who uses which @INC array. In fine tuned environments C<UNINST=1> can cause damage. =item 3) I want to clean up my mess, and install a new perl along with all modules I have. How do I go about it? Run the autobundle command for your old perl and optionally rename the resulting bundle file (e.g. Bundle/mybundle.pm), install the new perl with the Configure option prefix, e.g. ./Configure -Dprefix=/usr/local/perl-5.6.78.9 Install the bundle file you produced in the first step with something like cpan> install Bundle::mybundle and you're done. =item 4) When I install bundles or multiple modules with one command there is too much output to keep track of. You may want to configure something like o conf make_arg "| tee -ai /root/.cpan/logs/make.out" o conf make_install_arg "| tee -ai /root/.cpan/logs/make_install.out" so that STDOUT is captured in a file for later inspection. =item 5) I am not root, how can I install a module in a personal directory? As of CPAN 1.9463, if you do not have permission to write the default perl library directories, CPAN's configuration process will ask you whether you want to bootstrap <local::lib>, which makes keeping a personal perl library directory easy. Another thing you should bear in mind is that the UNINST parameter can be dangerous when you are installing into a private area because you might accidentally remove modules that other people depend on that are not using the private area. =item 6) How to get a package, unwrap it, and make a change before building it? Have a look at the C<look> (!) command. =item 7) I installed a Bundle and had a couple of fails. When I retried, everything resolved nicely. Can this be fixed to work on first try? The reason for this is that CPAN does not know the dependencies of all modules when it starts out. To decide about the additional items to install, it just uses data found in the META.yml file or the generated Makefile. An undetected missing piece breaks the process. But it may well be that your Bundle installs some prerequisite later than some depending item and thus your second try is able to resolve everything. Please note, CPAN.pm does not know the dependency tree in advance and cannot sort the queue of things to install in a topologically correct order. It resolves perfectly well B<if> all modules declare the prerequisites correctly with the PREREQ_PM attribute to MakeMaker or the C<requires> stanza of Module::Build. For bundles which fail and you need to install often, it is recommended to sort the Bundle definition file manually. =item 8) In our intranet, we have many modules for internal use. How can I integrate these modules with CPAN.pm but without uploading the modules to CPAN? Have a look at the CPAN::Site module. =item 9) When I run CPAN's shell, I get an error message about things in my C</etc/inputrc> (or C<~/.inputrc>) file. These are readline issues and can only be fixed by studying readline configuration on your architecture and adjusting the referenced file accordingly. Please make a backup of the C</etc/inputrc> or C<~/.inputrc> and edit them. Quite often harmless changes like uppercasing or lowercasing some arguments solves the problem. =item 10) Some authors have strange characters in their names. Internally CPAN.pm uses the UTF-8 charset. If your terminal is expecting ISO-8859-1 charset, a converter can be activated by setting term_is_latin to a true value in your config file. One way of doing so would be cpan> o conf term_is_latin 1 If other charset support is needed, please file a bug report against CPAN.pm at rt.cpan.org and describe your needs. Maybe we can extend the support or maybe UTF-8 terminals become widely available. Note: this config variable is deprecated and will be removed in a future version of CPAN.pm. It will be replaced with the conventions around the family of $LANG and $LC_* environment variables. =item 11) When an install fails for some reason and then I correct the error condition and retry, CPAN.pm refuses to install the module, saying C<Already tried without success>. Use the force pragma like so force install Foo::Bar Or you can use look Foo::Bar and then C<make install> directly in the subshell. =item 12) How do I install a "DEVELOPER RELEASE" of a module? By default, CPAN will install the latest non-developer release of a module. If you want to install a dev release, you have to specify the partial path starting with the author id to the tarball you wish to install, like so: cpan> install KWILLIAMS/Module-Build-0.27_07.tar.gz Note that you can use the C<ls> command to get this path listed. =item 13) How do I install a module and all its dependencies from the commandline, without being prompted for anything, despite my CPAN configuration (or lack thereof)? CPAN uses ExtUtils::MakeMaker's prompt() function to ask its questions, so if you set the PERL_MM_USE_DEFAULT environment variable, you shouldn't be asked any questions at all (assuming the modules you are installing are nice about obeying that variable as well): % PERL_MM_USE_DEFAULT=1 perl -MCPAN -e 'install My::Module' =item 14) How do I create a Module::Build based Build.PL derived from an ExtUtils::MakeMaker focused Makefile.PL? http://search.cpan.org/dist/Module-Build-Convert/ =item 15) I'm frequently irritated with the CPAN shell's inability to help me select a good mirror. CPAN can now help you select a "good" mirror, based on which ones have the lowest 'ping' round-trip times. From the shell, use the command 'o conf init urllist' and allow CPAN to automatically select mirrors for you. Beyond that help, the urllist config parameter is yours. You can add and remove sites at will. You should find out which sites have the best up-to-dateness, bandwidth, reliability, etc. and are topologically close to you. Some people prefer fast downloads, others up-to-dateness, others reliability. You decide which to try in which order. Henk P. Penning maintains a site that collects data about CPAN sites: http://mirrors.cpan.org/ Also, feel free to play with experimental features. Run o conf init randomize_urllist ftpstats_period ftpstats_size and choose your favorite parameters. After a few downloads running the C<hosts> command will probably assist you in choosing the best mirror sites. =item 16) Why do I get asked the same questions every time I start the shell? You can make your configuration changes permanent by calling the command C<o conf commit>. Alternatively set the C<auto_commit> variable to true by running C<o conf init auto_commit> and answering the following question with yes. =item 17) Older versions of CPAN.pm had the original root directory of all tarballs in the build directory. Now there are always random characters appended to these directory names. Why was this done? The random characters are provided by File::Temp and ensure that each module's individual build directory is unique. This makes running CPAN.pm in concurrent processes simultaneously safe. =item 18) Speaking of the build directory. Do I have to clean it up myself? You have the choice to set the config variable C<scan_cache> to C<never>. Then you must clean it up yourself. The other possible values, C<atstart> and C<atexit> clean up the build directory when you start (or more precisely, after the first extraction into the build directory) or exit the CPAN shell, respectively. If you never start up the CPAN shell, you probably also have to clean up the build directory yourself. =item 19) How can I switch to sudo instead of local::lib? The following 5 environment veriables need to be reset to the previous values: PATH, PERL5LIB, PERL_LOCAL_LIB_ROOT, PERL_MB_OPT, PERL_MM_OPT; and these two CPAN.pm config variables must be reconfigured: make_install_make_command and mbuild_install_build_command. The five env variables have probably been overwritten in your $HOME/.bashrc or some equivalent. You either find them there and delete their traces and logout/login or you override them temporarily, depending on your exact desire. The two cpanpm config variables can be set with: o conf init /install_.*_command/ probably followed by o conf commit =back =head1 COMPATIBILITY =head2 OLD PERL VERSIONS CPAN.pm is regularly tested to run under 5.005 and assorted newer versions. It is getting more and more difficult to get the minimal prerequisites working on older perls. It is close to impossible to get the whole Bundle::CPAN working there. If you're in the position to have only these old versions, be advised that CPAN is designed to work fine without the Bundle::CPAN installed. To get things going, note that GBARR/Scalar-List-Utils-1.18.tar.gz is compatible with ancient perls and that File::Temp is listed as a prerequisite but CPAN has reasonable workarounds if it is missing. =head2 CPANPLUS This module and its competitor, the CPANPLUS module, are both much cooler than the other. CPAN.pm is older. CPANPLUS was designed to be more modular, but it was never intended to be compatible with CPAN.pm. =head2 CPANMINUS In the year 2010 App::cpanminus was launched as a new approach to a cpan shell with a considerably smaller footprint. Very cool stuff. =head1 SECURITY ADVICE This software enables you to upgrade software on your computer and so is inherently dangerous because the newly installed software may contain bugs and may alter the way your computer works or even make it unusable. Please consider backing up your data before every upgrade. =head1 BUGS Please report bugs via L<http://rt.cpan.org/> Before submitting a bug, please make sure that the traditional method of building a Perl module package from a shell by following the installation instructions of that package still works in your environment. =head1 AUTHOR Andreas Koenig C<< <andk@cpan.org> >> =head1 LICENSE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L<http://www.perl.com/perl/misc/Artistic.html> =head1 TRANSLATIONS Kawai,Takanori provides a Japanese translation of a very old version of this manpage at L<http://homepage3.nifty.com/hippo2000/perltips/CPAN.htm> =head1 SEE ALSO Many people enter the CPAN shell by running the L<cpan> utility program which is installed in the same directory as perl itself. So if you have this directory in your PATH variable (or some equivalent in your operating system) then typing C<cpan> in a console window will work for you as well. Above that the utility provides several commandline shortcuts. melezhik (Alexey) sent me a link where he published a chef recipe to work with CPAN.pm: http://community.opscode.com/cookbooks/cpan. =cut PK�������!��pA��A����perl5/alienfile.pmnu�6$��������package alienfile; use strict; use warnings; use 5.008004; use Alien::Build; use Exporter (); use Path::Tiny (); use Carp (); sub _path { Path::Tiny::path(@_) } # ABSTRACT: Specification for defining an external dependency for CPAN our $VERSION = '2.80'; # VERSION our @EXPORT = qw( requires on plugin probe configure share sys download fetch decode prefer extract patch patch_ffi build build_ffi gather gather_ffi meta_prop ffi log test start_url before after digest ); sub requires { my($module, $version) = @_; $version ||= 0; my $caller = caller; my $meta = $caller->meta; $meta->add_requires($meta->{phase}, $module, $version); (); } sub plugin { my($name, @args) = @_; my $caller = caller; $caller->meta->apply_plugin($name, @args); return; } sub probe { my($instr) = @_; my $caller = caller; if(my $phase = $caller->meta->{phase}) { Carp::croak "probe must not be in a $phase block" if $phase ne 'any'; } $caller->meta->register_hook(probe => $instr); return; } sub _phase { my($code, $phase) = @_; my $caller = caller(1); my $meta = $caller->meta; local $meta->{phase} = $phase; $code->(); return; } sub configure (&) { _phase($_[0], 'configure'); } sub sys (&) { _phase($_[0], 'system'); } sub share (&) { _phase($_[0], 'share'); } sub _in_phase { my($phase) = @_; my $caller = caller(1); my(undef, undef, undef, $sub) = caller(1); my $meta = $caller->meta; $sub =~ s/^.*:://; Carp::croak "$sub must be in a $phase block" unless $meta->{phase} eq $phase; } sub start_url { my($url) = @_; _in_phase 'share'; my $caller = caller; my $meta = $caller->meta; $meta->prop->{start_url} = $url; $meta->add_requires('configure' => 'Alien::Build' => '1.19'); return; } sub digest { my($algo, $digest) = @_; my $caller = caller; $caller->meta->apply_plugin('Digest', [$algo, $digest]); return; } sub download { my($instr) = @_; _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(download => $instr); return; } sub fetch { my($instr) = @_; _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(fetch => $instr); return; } sub decode { my($instr) = @_; _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(decode => $instr); return; } sub prefer { my($instr) = @_; _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(prefer => $instr); return; } sub extract { my($instr) = @_; _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(extract => $instr); return; } sub patch { my($instr) = @_; _in_phase 'share'; my $caller = caller; my $suffix = $caller->meta->{build_suffix}; $caller->meta->register_hook("patch$suffix" => $instr); return; } sub patch_ffi { my($instr) = @_; Carp::carp("patch_ffi is deprecated, use ffi { patch ... } } instead"); _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(patch_ffi => $instr); return; } sub build { my($instr) = @_; _in_phase 'share'; my $caller = caller; my $suffix = $caller->meta->{build_suffix}; $caller->meta->register_hook("build$suffix" => $instr); return; } sub build_ffi { my($instr) = @_; Carp::carp("build_ffi is deprecated, use ffi { build ... } } instead"); _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(build_ffi => $instr); return; } sub gather { my($instr) = @_; my $caller = caller; my $meta = $caller->meta; my $phase = $meta->{phase}; Carp::croak "gather is not allowed in configure block" if $phase eq 'configure'; my $suffix = $caller->meta->{build_suffix}; if($suffix eq '_ffi') { $meta->register_hook(gather_ffi => $instr) } else { $meta->register_hook(gather_system => $instr) if $phase =~ /^(any|system)$/; $meta->register_hook(gather_share => $instr) if $phase =~ /^(any|share)$/; } return; } sub gather_ffi { my($instr) = @_; Carp::carp("gather_ffi is deprecated, use ffi { gather ... } } instead"); _in_phase 'share'; my $caller = caller; $caller->meta->register_hook(gather_ffi => $instr); return; } sub ffi (&) { my($code) = @_; _in_phase 'share'; my $caller = caller; local $caller->meta->{build_suffix} = '_ffi'; $code->(); return; } sub meta_prop { my $caller = caller; my $meta = $caller->meta; $meta->prop; } sub log { unshift @_, 'Alien::Build'; goto &Alien::Build::log; } sub test { my($instr) = @_; my $caller = caller; my $meta = $caller->meta; my $phase = $meta->{phase}; Carp::croak "test is not allowed in $phase block" if $phase eq 'any' || $phase eq 'configure'; $meta->add_requires('configure' => 'Alien::Build' => '1.14'); if($phase eq 'share') { my $suffix = $caller->meta->{build_suffix} || '_share'; $meta->register_hook( "test$suffix" => $instr, ); } elsif($phase eq 'system') { $meta->register_hook( "test_system" => $instr, ); } else { die "unknown phase: $phase"; } } my %modifiers = ( probe => { any => 'probe' }, download => { share => 'download' }, fetch => { share => 'fetch' }, decode => { share => 'fetch' }, prefer => { share => 'prefer' }, extract => { share => 'extract' }, patch => { share => 'patch$' }, build => { share => 'build$' }, test => { share => 'test$' }, # Note: below special case gather_ffi for the ffi block :P gather => { share => 'gather_share', system => 'gather_system', any => 'gather_share,gather_system' }, ); sub _add_modifier { my($type, $stage, $sub) = @_; my $method = "${type}_hook"; Carp::croak "No such stage $stage" unless defined $modifiers{$stage}; Carp::croak "$type $stage argument must be a code reference" unless defined $sub && ref($sub) eq 'CODE'; my $caller = caller; my $meta = $caller->meta; Carp::croak "$type $stage is not allowed in sys block" unless defined $modifiers{$stage}->{$meta->{phase}}; $meta->add_requires('configure' => 'Alien::Build' => '1.40'); my $suffix = $meta->{build_suffix}; if($suffix eq '_ffi' && $stage eq 'gather') { $meta->$method('gather_ffi' => $sub); } foreach my $hook ( map { split /,/, $_ } # split on , for when multiple hooks must be attached (gather in any) map { my $x = $_ ; $x =~ s/\$/$suffix/; $x } # substitute $ at the end for a suffix (_ffi) if any $modifiers{$stage}->{$meta->{phase}}) # get the list of modifiers { $meta->$method($hook => $sub); } return; } sub before { my($stage, $sub) = @_; @_ = ('before', @_); goto &alienfile::_add_modifier; } sub after { my($stage, $sub) = @_; @_ = ('after', @_); goto &alienfile::_add_modifier; } sub import { strict->import; warnings->import; goto &Exporter::import; } 1; __END__ =pod =encoding UTF-8 =head1 NAME alienfile - Specification for defining an external dependency for CPAN =head1 VERSION version 2.80 =head1 SYNOPSIS Do-it-yourself approach: use alienfile; probe [ 'pkg-config --exists libarchive' ]; share { start_url 'http://libarchive.org/downloads/libarchive-3.2.2.tar.gz'; # the first one which succeeds will be used download [ 'wget %{.meta.start_url}' ]; download [ 'curl -o %{.meta.start_url}' ]; extract [ 'tar xf %{.install.download}' ]; build [ # Note: will not work on Windows, better to use Build::Autoconf plugin # if you need windows support './configure --prefix=%{.install.prefix} --disable-shared', '%{make}', '%{make} install', ]; } gather [ [ 'pkg-config', '--modversion', 'libarchive', \'%{.runtime.version}' ], [ 'pkg-config', '--cflags', 'libarchive', \'%{.runtime.cflags}' ], [ 'pkg-config', '--libs', 'libarchive', \'%{.runtime.libs}' ], ]; With plugins (better): use alienfile; plugin 'PkgConfig' => 'libarchive'; share { start_url 'http://libarchive.org/downloads/'; plugin Download => ( filter => qr/^libarchive-.*\.tar\.gz$/, version => qr/([0-9\.]+)/, ); plugin Extract => 'tar.gz'; plugin 'Build::Autoconf'; plugin 'Gather::IsolateDynamic'; build [ '%{configure}', '%{make}', '%{make} install', ]; }; =head1 DESCRIPTION An alienfile is a recipe used by L<Alien::Build> to, probe for system libraries or download from the internet, and build source for those libraries. This document acts as reference for the alienfile system, but if you are starting out writing your own Alien you should read L<Alien::Build::Manual::AlienAuthor>, which will teach you how to write your own complete Alien using alienfile + L<Alien::Build> + L<ExtUtils::MakeMaker>. Special attention should be taken to the section "a note about dynamic vs. static libraries". =head1 DIRECTIVES =head2 requires "any" requirement (either share or system): requires $module; requires $module => $version; configure time requirement: configure { requires $module; requires $module => $version; }; system requirement: sys { requires $module; requires $module => $version; }; share requirement: share { requires $module; requires $module => $version; }; specifies a requirement. L<Alien::Build> takes advantage of dynamic requirements, so only modules that are needed for the specific type of install need to be loaded. Here are the different types of requirements: =over =item configure Configure requirements should already be installed before the alienfile is loaded. =item any "Any" requirements are those that are needed either for the probe stage, or in either the system or share installs. =item share Share requirements are those modules needed when downloading and building from source. =item system System requirements are those modules needed when the system provides the library or tool. =back =head2 plugin plugin $name => (%args); plugin $name => $arg; Load the given plugin. If you prefix the plugin name with an C<=> sign, then it will be assumed to be a fully qualified path name. Otherwise the plugin will be assumed to live in the C<Alien::Build::Plugin> namespace. If there is an appropriate negotiate plugin, that one will be loaded. Examples: # Loads Alien::Build::Plugin::Fetch::Negotiate # which will pick the best Alien::Build::Plugin::Fetch # plugin based on the URL, and system configuration plugin 'Fetch' => 'http://ftp.gnu.org/gnu/gcc'; # loads the plugin with the badly named class! plugin '=Badly::Named::Plugin::Not::In::Alien::Build::Namespace'; # explicitly loads Alien::Build::Plugin::Prefer::SortVersions plugin 'Prefer::SortVersions' => ( filter => qr/^gcc-.*\.tar\.gz$/, version => qr/([0-9\.]+)/, ); =head2 probe probe \&code; probe \@commandlist; Instructions for the probe stage. May be either a code reference, or a command list. Multiple probes and probe plugins can be given. These will be used in sequence, stopping at the first that detects a system installation. L<Alien::Build> will use a share install if no system installation is detected by the probes. =head2 configure configure { ... }; Configure block. The only directive allowed in a configure block is requires. =head2 sys sys { ... }; System block. Allowed directives are: requires and gather. =head2 share share { ... }; System block. Allowed directives are: download, fetch, decode, prefer, extract, build, gather. =head2 start_url share { start_url $url; }; Set the start URL for download. This should be the URL to an index page, or the actual tarball of the source. =head2 digest [experimental] share { digest $algorithm, $digest; }; Check fetched and downloaded files against the given algorithm and digest. Typically you will want to use SHA256 as the algorithm. =head2 download share { download \&code; download \@commandlist; }; Instructions for the download stage. May be either a code reference, or a command list. =head2 fetch share { fetch \&code; fetch \@commandlist; }; Instructions for the fetch stage. May be either a code reference, or a command list. =head2 decode share { decode \&code; decode \@commandlist; }; Instructions for the decode stage. May be either a code reference, or a command list. =head2 prefer share { prefer \&code; prefer \@commandlist; }; Instructions for the prefer stage. May be either a code reference, or a command list. =head2 extract share { extract \&code; extract \@commandlist; }; Instructions for the extract stage. May be either a code reference, or a command list. =head2 patch share { patch \&code; patch \@commandlist; }; Instructions for the patch stage. May be either a code reference, or a command list. =head2 patch_ffi share { patch_ffi \&code; patch_ffi \@commandlist; }; [DEPRECATED] Instructions for the patch_ffi stage. May be either a code reference, or a command list. =head2 build share { build \&code; build \@commandlist; }; Instructions for the build stage. May be either a code reference, or a command list. =head2 build_ffi share { build \&code; build \@commandlist; }; [DEPRECATED] Instructions for the build FFI stage. Builds shared libraries instead of static. This is optional, and is only necessary if a fresh and separate build needs to be done for FFI. =head2 gather gather \&code; gather \@commandlist; share { gather \&code; gather \@commandlist; }; sys { gather \&code; gather \@commandlist; }; Instructions for the gather stage. May be either a code reference, or a command list. In the root block of the alienfile it will trigger in both share and system build. In the share or sys block it will only trigger in the corresponding build. =head2 gather_ffi share { gather_ffi \&code; gather_ffi \@commandlist; } [DEPRECATED] Gather specific to C<build_ffi>. Not usually necessary. =head2 ffi share { ffi { patch \&code; patch \@commandlist; build \&code; build \@commandlist; gather \&code; gather \@commandlist; } } Specify patch, build or gather stages related to FFI. =head2 meta_prop my $hash = meta_prop; Get the meta_prop hash reference. =head2 meta my $meta = meta; Returns the meta object for your L<alienfile>. For methods that can be used on the meta object, see L<Alien::Build/"META METHODS">. =head2 log log($message); Prints the given log to stdout. =head2 test share { test \&code; test \@commandlist; }; sys { test \&code; test \@commandlist; }; Run the tests =head2 before before $stage => \&code; Execute the given code before the given stage. Stage should be one of C<probe>, C<download>, C<fetch>, C<decode>, C<prefer>, C<extract>, C<patch>, C<build>, C<test>, and C<gather>. The before directive is only legal in the same blocks as the stage would normally be legal in. For example, you can't do this: use alienfile; sys { before 'build' => sub { ... }; }; Because a C<build> wouldn't be legal inside a C<sys> block. =head2 after after $stage => \&code; Execute the given code after the given stage. Stage should be one of C<probe>, C<download>, C<fetch>, C<decode>, C<prefer>, C<extract>, C<patch>, C<build>, C<test>, and C<gather>. The after directive is only legal in the same blocks as the stage would normally be legal in. For example, you can't do this: use alienfile; sys { after 'build' => sub { ... }; }; Because a C<build> wouldn't be legal inside a C<sys> block. =head1 SEE ALSO =over 4 =item L<Alien> =item L<Alien::Build> =item L<Alien::Build::MM> =item L<Alien::Base> =back =head1 AUTHOR Author: Graham Ollis E<lt>plicease@cpan.orgE<gt> Contributors: Diab Jerius (DJERIUS) Roy Storey (KIWIROY) Ilya Pavlov David Mertens (run4flat) Mark Nunberg (mordy, mnunberg) Christian Walde (Mithaldu) Brian Wightman (MidLifeXis) Zaki Mughal (zmughal) mohawk (mohawk2, ETJ) Vikas N Kumar (vikasnkumar) Flavio Poletti (polettix) Salvador Fandiño (salva) Gianni Ceccarelli (dakkar) Pavel Shaydo (zwon, trinitum) Kang-min Liu (劉康民, gugod) Nicholas Shipp (nshp) Juan Julián Merelo Guervós (JJ) Joel Berger (JBERGER) Petr Písař (ppisar) Lance Wicks (LANCEW) Ahmad Fatoum (a3f, ATHREEF) José Joaquín Atria (JJATRIA) Duke Leto (LETO) Shoichi Kaji (SKAJI) Shawn Laffan (SLAFFAN) Paul Evans (leonerd, PEVANS) Håkon Hægland (hakonhagland, HAKONH) nick nauwelaerts (INPHOBIA) Florian Weimer =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2011-2022 by Graham Ollis. 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 PK�������!�]/7��7����perl5/File/Listing.pmnu�6$��������package File::Listing; use strict; use warnings; use Carp (); use HTTP::Date qw(str2time); use Exporter 5.57 qw( import ); # ABSTRACT: Parse directory listing our $VERSION = '6.16'; # VERSION sub Version { $File::Listing::VERSION; } our @EXPORT = qw(parse_dir); sub parse_dir ($;$$$) { my($dir, $tz, $fstype, $error) = @_; $fstype ||= 'unix'; $fstype = "File::Listing::" . lc $fstype; my @args = $_[0]; push(@args, $tz) if(@_ >= 2); push(@args, $error) if(@_ >= 4); $fstype->parse(@args); } sub line { Carp::croak("Not implemented yet"); } sub init { } # Dummy sub sub file_mode ($) { Carp::croak("Input to file_mode() must be a 10 character string.") unless length($_[0]) == 10; # This routine was originally borrowed from Graham Barr's # Net::FTP package. local $_ = shift; my $mode = 0; my($type); s/^(.)// and $type = $1; # When the set-group-ID bit (file mode bit 02000) is set, and the group # execution bit (file mode bit 00020) is unset, and it is a regular file, # some implementations of `ls' use the letter `S', others use `l' or `L'. # Convert this `S'. s/[Ll](...)$/S$1/; while (/(.)/g) { $mode <<= 1; $mode |= 1 if $1 ne "-" && $1 ne "*" && $1 ne 'S' && $1 ne 'T'; } $mode |= 0004000 if /^..s....../i; $mode |= 0002000 if /^.....s.../i; $mode |= 0001000 if /^........t/i; # De facto standard definitions. From 'stat.h' on Solaris 9. $type eq "p" and $mode |= 0010000 or # fifo $type eq "c" and $mode |= 0020000 or # character special $type eq "d" and $mode |= 0040000 or # directory $type eq "b" and $mode |= 0060000 or # block special $type eq "-" and $mode |= 0100000 or # regular $type eq "l" and $mode |= 0120000 or # symbolic link $type eq "s" and $mode |= 0140000 or # socket $type eq "D" and $mode |= 0150000 or # door Carp::croak("Unknown file type: $type"); $mode; } sub parse { my($pkg, $dir, $tz, $error) = @_; # First let's try to determine what kind of dir parameter we have # received. We allow both listings, reference to arrays and # file handles to read from. if (ref($dir) eq 'ARRAY') { # Already split up } elsif (ref($dir) eq 'GLOB') { # A file handle } elsif (ref($dir)) { Carp::croak("Illegal argument to parse_dir()"); } elsif ($dir =~ /^\*\w+(::\w+)+$/) { # This scalar looks like a file handle, so we assume it is } else { # A normal scalar listing $dir = [ split(/\n/, $dir) ]; } $pkg->init(); my @files = (); if (ref($dir) eq 'ARRAY') { for (@$dir) { push(@files, $pkg->line($_, $tz, $error)); } } else { local($_); while (my $line = <$dir>) { chomp $line; push(@files, $pkg->line($line, $tz, $error)); } } wantarray ? @files : \@files; ## no critic (Community::Wantarray) } package File::Listing::unix; use HTTP::Date qw(str2time); our @ISA = qw(File::Listing); # A place to remember current directory from last line parsed. our $curdir; sub init { $curdir = ''; } sub line { shift; # package name local($_) = shift; my($tz, $error) = @_; s/\015//g; #study; my ($kind, $size, $date, $name); if (($kind, $size, $date, $name) = /^([\-\*FlrwxsStTdD]{10}) # Type and permission bits .* # Graps \D(\d+) # File size \s+ # Some space (\w{3}\s+\d+\s+(?:\d{1,2}:\d{2}|\d{4})|\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}) # Date \s+ # Some more space (.*)$ # File name /x ) { return if $name eq '.' || $name eq '..'; $name = "$curdir/$name" if length $curdir; my $type = '?'; if ($kind =~ /^l/ && $name =~ /(.*) -> (.*)/ ) { $name = $1; $type = "l $2"; } elsif ($kind =~ /^[\-F]/) { # (hopefully) a regular file $type = 'f'; } elsif ($kind =~ /^[dD]/) { $type = 'd'; $size = undef; # Don't believe the reported size } return [$name, $type, $size, str2time($date, $tz), File::Listing::file_mode($kind)]; } elsif (/^(.+):$/ && !/^[dcbsp].*\s.*\s.*:$/ ) { my $dir = $1; return () if $dir eq '.'; $curdir = $dir; return (); } elsif (/^[Tt]otal\s+(\d+)$/ || /^\s*$/) { return (); } elsif (/not found/ || # OSF1, HPUX, and SunOS return # "$file not found" /No such file/ || # IRIX returns # "UX:ls: ERROR: Cannot access $file: No such file or directory" # Solaris returns # "$file: No such file or directory" /cannot find/ # Windows NT returns # "The system cannot find the path specified." ) { return () unless defined $error; &$error($_) if ref($error) eq 'CODE'; warn "Error: $_\n" if $error eq 'warn'; return (); } elsif ($_ eq '') { # AIX, and Linux return nothing return () unless defined $error; &$error("No such file or directory") if ref($error) eq 'CODE'; warn "Warning: No such file or directory\n" if $error eq 'warn'; return (); } else { # parse failed, check if the dosftp parse understands it File::Listing::dosftp->init(); return(File::Listing::dosftp->line($_,$tz,$error)); } } package File::Listing::dosftp; use HTTP::Date qw(str2time); our @ISA = qw(File::Listing); # A place to remember current directory from last line parsed. our $curdir; sub init { $curdir = ''; } sub line { shift; # package name local($_) = shift; my($tz, $error) = @_; s/\015//g; my ($date, $size_or_dir, $name, $size); # usual format: # 02-05-96 10:48AM 1415 src.slf # 09-10-96 09:18AM <DIR> sl_util # alternative dos format with four-digit year: # 02-05-2022 10:48AM 1415 src.slf # 09-10-2022 09:18AM <DIR> sl_util if (($date, $size_or_dir, $name) = /^(\d\d-\d\d-\d{2,4}\s+\d\d:\d\d\wM) # Date and time info \s+ # Some space (<\w{3}>|\d+) # Dir or Size \s+ # Some more space (.+)$ # File name /x ) { return if $name eq '.' || $name eq '..'; $name = "$curdir/$name" if length $curdir; my $type = '?'; if ($size_or_dir eq '<DIR>') { $type = "d"; $size = ""; # directories have no size in the pc listing } else { $type = 'f'; $size = $size_or_dir; } return [$name, $type, $size, str2time($date, $tz), undef]; } else { return () unless defined $error; &$error($_) if ref($error) eq 'CODE'; warn "Can't parse: $_\n" if $error eq 'warn'; return (); } } package File::Listing::vms; our @ISA = qw(File::Listing); package File::Listing::netware; our @ISA = qw(File::Listing); package File::Listing::apache; our @ISA = qw(File::Listing); sub init { } sub line { shift; # package name local($_) = shift; my($tz, $error) = @_; # ignored for now... s!</?t[rd][^>]*>! !g; # clean away various table stuff if (m!<A\s+HREF=\"([^?\"]+)\">.*</A>.*?(\d+)-([a-zA-Z]+|\d+)-(\d+)\s+(\d+):(\d+)\s+(?:([\d\.]+[kMG]?|-))!i) { my($filename, $filesize) = ($1, $7); my($d,$m,$y, $H,$M) = ($2,$3,$4,$5,$6); if ($m =~ /^\d+$/) { ($d,$y) = ($y,$d) # iso date } else { $m = _monthabbrev_number($m); } $filesize = 0 if $filesize eq '-'; if ($filesize =~ s/k$//i) { $filesize *= 1024; } elsif ($filesize =~ s/M$//) { $filesize *= 1024*1024; } elsif ($filesize =~ s/G$//) { $filesize *= 1024*1024*1024; } $filesize = int $filesize; require Time::Local; my $filetime = Time::Local::timelocal(0,$M,$H,$d,$m-1,_guess_year($y)); my $filetype = ($filename =~ s|/$|| ? "d" : "f"); return [$filename, $filetype, $filesize, $filetime, undef]; } # the default listing doesn't include timestamps or file sizes # but we don't want to grab navigation links, so we ignore links # that have a non-trailing slash / character or ? elsif(m!<A\s+HREF=\"([^?/\"]+/?)\">.*</A>!i) { my $filename = $1; my $filetype = ($filename =~ s|/$|| ? "d" : "f"); return [$filename, $filetype, undef, undef, undef]; } return (); } sub _guess_year { my $y = shift; # if the year is already four digit then we shouldn't do # anything to modify it. if ($y >= 1900) { # do nothing # TODO: for hysterical er historical reasons we assume 9x is in the # 1990s we should probably not do that, but I don't have any examples # where apache provides two digit dates so I am leaving this as-is # for now. Possibly the right thing is to not handle two digit years. } elsif ($y >= 90) { $y = 1900+$y; } # TODO: likewise assuming 00-89 are 20xx is long term probably wrong. elsif ($y < 100) { $y = 2000+$y; } $y; } sub _monthabbrev_number { my $mon = shift; +{'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10, 'Nov' => 11, 'Dec' => 12, }->{$mon}; } 1; __END__ =pod =encoding UTF-8 =head1 NAME File::Listing - Parse directory listing =head1 VERSION version 6.16 =head1 SYNOPSIS use File::Listing qw(parse_dir); $ENV{LANG} = "C"; # dates in non-English locales not supported foreach my $file (parse_dir(`ls -l`)) { my ($name, $type, $size, $mtime, $mode) = @$file; next if $type ne 'f'; # plain file #... } # directory listing can also be read from a file open my $listing, "zcat ls-lR.gz|"; $dir = parse_dir($listing, '+0000'); =head1 DESCRIPTION This module exports a single function called C<parse_dir>, which can be used to parse directory listings. =head1 FUNCTIONS =head2 parse_dir my $dir = parse_dir( $listing ); my $dir = parse_dir( $listing, $time_zone ); my $dir = parse_dir( $listing, $time_zone, $type ); my $dir = parse_dir( $listing, $time_zone, $type, $error ); my @files = parse_dir( $listing ); my @files = parse_dir( $listing, $time_zone ); my @files = parse_dir( $listing, $time_zone, $type ); my @files = parse_dir( $listing, $time_zone, $type, $error ); The first parameter (C<$listing>) is the directory listing to parse. It can be a scalar, a reference to an array of directory lines or a glob representing a filehandle to read the directory listing from. The second parameter (C<$time_zone>) is the time zone to use when parsing time stamps in the listing. If this value is undefined, then the local time zone is assumed. The third parameter (C<$type>) is the type of listing to assume. Currently supported formats are C<'unix'>, C<'apache'> and C<'dosftp'>. The default value is C<'unix'>. Ideally, the listing type should be determined automatically. The fourth parameter (C<$error>) specifies how unparseable lines should be treated. Values can be C<'ignore'>, C<'warn'> or a code reference. Warn means that the perl warn() function will be called. If a code reference is passed, then this routine will be called and the return value from it will be incorporated in the listing. The default is C<'ignore'>. Only the first parameter is mandatory. # list context foreach my $file (parse_dir($listing)) { my($name, $type, $size, $mtime, $mode) = @$file; } # scalar context my $dir = parse_dir($listing); foreach my $file (@$dir) { my($name, $type, $size, $mtime, $mode) = @$file; } The return value from parse_dir() is a list of directory entries. In a scalar context the return value is a reference to the list. The directory entries are represented by an array consisting of: =over 4 =item name The name of the file. =item type One of: C<f> file, C<d> directory, C<l> symlink, C<?> unknown. =item size The size of the file. =item time The number of seconds since January 1, 1970. =item mode Bitmask a la the mode returned by C<stat>. =back =head1 SEE ALSO =over 4 =item L<File::Listing::Ftpcopy> Provides the same interface but uses XS and the parser implementation from C<ftpcopy>. =back =head1 AUTHOR Original author: Gisle Aas Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt> Contributors: Adam Kennedy Adam Sjogren Alex Kapranoff Alexey Tourbin Andreas J. Koenig Bill Mann Bron Gondwana DAVIDRW Daniel Hedlund David E. Wheeler David Steinbrunner Erik Esterer FWILES Father Chrysostomos Gavin Peters Graeme Thompson Grant Street Group Hans-H. Froehlich Ian Kilgore Jacob J Mark Stosberg Mike Schilli Ondrej Hanak Peter John Acklam Peter Rabbitson Robert Stone Rolf Grossmann Sean M. Burke Simon Legner Slaven Rezic Spiros Denaxas Steve Hay Todd Lipcon Tom Hukins Tony Finch Toru Yamaguchi Ville Skyttä Yuri Karaban Zefram amire80 jefflee john9art mschilli murphy phrstbrn ruff sasao uid39246 =head1 COPYRIGHT AND LICENSE This software is copyright (c) 1996-2022 by Gisle Aas. 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 PK�������!�dE *��*����perl5/File/chdir.pmnu�6$��������package File::chdir; use 5.004; use strict; use vars qw($VERSION @ISA @EXPORT $CWD @CWD); # ABSTRACT: a more sensible way to change directories our $VERSION = '0.1011'; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(*CWD); use Carp; use Cwd 3.16; use File::Spec::Functions 3.27 qw/canonpath splitpath catpath splitdir catdir/; tie $CWD, 'File::chdir::SCALAR' or die "Can't tie \$CWD"; tie @CWD, 'File::chdir::ARRAY' or die "Can't tie \@CWD"; sub _abs_path { # Otherwise we'll never work under taint mode. my($cwd) = Cwd::getcwd =~ /(.*)/s; # Run through File::Spec, since everything else uses it return canonpath($cwd); } # splitpath but also split directory sub _split_cwd { my ($vol, $dir) = splitpath(_abs_path, 1); my @dirs = splitdir( $dir ); shift @dirs; # get rid of leading empty "root" directory return ($vol, @dirs); } # catpath, but take list of directories # restore the empty root dir and provide an empty file to avoid warnings sub _catpath { my ($vol, @dirs) = @_; return catpath($vol, catdir(q{}, @dirs), q{}); } sub _chdir { # Untaint target directory my ($new_dir) = $_[0] =~ /(.*)/s; local $Carp::CarpLevel = $Carp::CarpLevel + 1; if ( ! CORE::chdir($new_dir) ) { croak "Failed to change directory to '$new_dir': $!"; }; return 1; } { package File::chdir::SCALAR; use Carp; BEGIN { *_abs_path = \&File::chdir::_abs_path; *_chdir = \&File::chdir::_chdir; *_split_cwd = \&File::chdir::_split_cwd; *_catpath = \&File::chdir::_catpath; } sub TIESCALAR { bless [], $_[0]; } # To be safe, in case someone chdir'd out from under us, we always # check the Cwd explicitly. sub FETCH { return _abs_path; } sub STORE { return unless defined $_[1]; _chdir($_[1]); } } { package File::chdir::ARRAY; use Carp; BEGIN { *_abs_path = \&File::chdir::_abs_path; *_chdir = \&File::chdir::_chdir; *_split_cwd = \&File::chdir::_split_cwd; *_catpath = \&File::chdir::_catpath; } sub TIEARRAY { bless {}, $_[0]; } sub FETCH { my($self, $idx) = @_; my ($vol, @cwd) = _split_cwd; return $cwd[$idx]; } sub STORE { my($self, $idx, $val) = @_; my ($vol, @cwd) = _split_cwd; if( $self->{Cleared} ) { @cwd = (); $self->{Cleared} = 0; } $cwd[$idx] = $val; my $dir = _catpath($vol,@cwd); _chdir($dir); return $cwd[$idx]; } sub FETCHSIZE { my ($vol, @cwd) = _split_cwd; return scalar @cwd; } sub STORESIZE {} sub PUSH { my($self) = shift; my $dir = _catpath(_split_cwd, @_); _chdir($dir); return $self->FETCHSIZE; } sub POP { my($self) = shift; my ($vol, @cwd) = _split_cwd; my $popped = pop @cwd; my $dir = _catpath($vol,@cwd); _chdir($dir); return $popped; } sub SHIFT { my($self) = shift; my ($vol, @cwd) = _split_cwd; my $shifted = shift @cwd; my $dir = _catpath($vol,@cwd); _chdir($dir); return $shifted; } sub UNSHIFT { my($self) = shift; my ($vol, @cwd) = _split_cwd; my $dir = _catpath($vol, @_, @cwd); _chdir($dir); return $self->FETCHSIZE; } sub CLEAR { my($self) = shift; $self->{Cleared} = 1; } sub SPLICE { my $self = shift; my $offset = shift || 0; my $len = shift || $self->FETCHSIZE - $offset; my @new_dirs = @_; my ($vol, @cwd) = _split_cwd; my @orig_dirs = splice @cwd, $offset, $len, @new_dirs; my $dir = _catpath($vol, @cwd); _chdir($dir); return @orig_dirs; } sub EXTEND { } sub EXISTS { my($self, $idx) = @_; return $self->FETCHSIZE >= $idx ? 1 : 0; } sub DELETE { my($self, $idx) = @_; croak "Can't delete except at the end of \@CWD" if $idx < $self->FETCHSIZE - 1; local $Carp::CarpLevel = $Carp::CarpLevel + 1; $self->POP; } } 1; __END__ =pod =encoding UTF-8 =head1 NAME File::chdir - a more sensible way to change directories =head1 VERSION version 0.1011 =head1 SYNOPSIS use File::chdir; $CWD = "/foo/bar"; # now in /foo/bar { local $CWD = "/moo/baz"; # now in /moo/baz ... } # still in /foo/bar! =head1 DESCRIPTION Perl's C<chdir()> has the unfortunate problem of being very, very, very global. If any part of your program calls C<chdir()> or if any library you use calls C<chdir()>, it changes the current working directory for the *whole* program. This sucks. File::chdir gives you an alternative, C<$CWD> and C<@CWD>. These two variables combine all the power of C<chdir()>, L<File::Spec> and L<Cwd>. =head1 $CWD Use the C<$CWD> variable instead of C<chdir()> and Cwd. use File::chdir; $CWD = $dir; # just like chdir($dir)! print $CWD; # prints the current working directory It can be localized, and it does the right thing. $CWD = "/foo"; # it's /foo out here. { local $CWD = "/bar"; # /bar in here } # still /foo out here! C<$CWD> always returns the absolute path in the native form for the operating system. C<$CWD> and normal C<chdir()> work together just fine. =head1 @CWD C<@CWD> represents the current working directory as an array, each directory in the path is an element of the array. This can often make the directory easier to manipulate, and you don't have to fumble with C<< File::Spec->splitpath >> and C<< File::Spec->catdir >> to make portable code. # Similar to chdir("/usr/local/src/perl") @CWD = qw(usr local src perl); pop, push, shift, unshift and splice all work. pop and push are probably the most useful. pop @CWD; # same as chdir(File::Spec->updir) push @CWD, 'some_dir' # same as chdir('some_dir') C<@CWD> and C<$CWD> both work fine together. *NOTE* Due to a perl bug you can't localize C<@CWD>. See L</CAVEATS> for a work around. =head1 EXAMPLES (We omit the C<use File::chdir> from these examples for terseness) Here's C<$CWD> instead of C<chdir()>: $CWD = 'foo'; # chdir('foo') and now instead of Cwd. print $CWD; # use Cwd; print Cwd::abs_path you can even do zsh style C<cd foo bar> $CWD = '/usr/local/foo'; $CWD =~ s/usr/var/; if you want to localize that, make sure you get the parens right { (local $CWD) =~ s/usr/var/; ... } It's most useful for writing polite subroutines which don't leave the program in some strange directory: sub foo { local $CWD = 'some/other/dir'; ...do your work... } which is much simpler than the equivalent: sub foo { use Cwd; my $orig_dir = Cwd::getcwd; chdir('some/other/dir'); ...do your work... chdir($orig_dir); } C<@CWD> comes in handy when you want to start moving up and down the directory hierarchy in a cross-platform manner without having to use File::Spec. pop @CWD; # chdir(File::Spec->updir); push @CWD, 'some', 'dir' # chdir(File::Spec->catdir(qw(some dir))); You can easily change your parent directory: # chdir from /some/dir/bar/moo to /some/dir/foo/moo $CWD[-2] = 'foo'; =head1 CAVEATS =head2 C<local @CWD> does not work. C<local @CWD> will not localize C<@CWD>. This is a bug in Perl, you can't localize tied arrays. As a work around localizing $CWD will effectively localize @CWD. { local $CWD; pop @CWD; ... } =head2 Assigning to C<@CWD> calls C<chdir()> for each element @CWD = qw/a b c d/; Internally, Perl clears C<@CWD> and assigns each element in turn. Thus, this code above will do this: chdir 'a'; chdir 'a/b'; chdir 'a/b/c'; chdir 'a/b/c/d'; Generally, avoid assigning to C<@CWD> and just use push and pop instead. =head2 Volumes not handled There is currently no way to change the current volume via File::chdir. =head1 NOTES C<$CWD> returns the current directory using native path separators, i.e. C<\> on Win32. This ensures that C<$CWD> will compare correctly with directories created using File::Spec. For example: my $working_dir = File::Spec->catdir( $CWD, "foo" ); $CWD = $working_dir; doing_stuff_might_chdir(); is( $CWD, $working_dir, "back to original working_dir?" ); Deleting the last item of C<@CWD> will act like a pop. Deleting from the middle will throw an exception. delete @CWD[-1]; # OK delete @CWD[-2]; # Dies What should %CWD do? Something with volumes? # chdir to C:\Program Files\Sierra\Half Life ? $CWD{C} = '\\Program Files\\Sierra\\Half Life'; =head1 DIAGNOSTICS If an error is encountered when changing C<$CWD> or C<@CWD>, one of the following exceptions will be thrown: * ~Can't delete except at the end of @CWD~ * ~Failed to change directory to '$dir'~ =head1 HISTORY Michael wanted C<local chdir> to work. p5p didn't. But it wasn't over! Was it over when the Germans bombed Pearl Harbor? Hell, no! Abigail and/or Bryan Warnock suggested the C<$CWD> thing (Michael forgets which). They were right. The C<chdir()> override was eliminated in 0.04. David became co-maintainer with 0.06_01 to fix some chronic Win32 path bugs. As of 0.08, if changing C<$CWD> or C<@CWD> fails to change the directory, an error will be thrown. =head1 SEE ALSO L<File::pushd>, L<File::Spec>, L<Cwd>, L<perlfunc/chdir>, "Animal House" L<http://www.imdb.com/title/tt0077975/quotes> =for :stopwords cpan testmatrix url annocpan anno 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<https://github.com/dagolden/File-chdir/issues>. 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<https://github.com/dagolden/File-chdir> git clone https://github.com/dagolden/File-chdir.git =head1 AUTHORS =over 4 =item * David Golden <dagolden@cpan.org> =item * Michael G. Schwern <schwern@pobox.com> =back =head1 CONTRIBUTORS =for stopwords David Golden Joel Berger Philippe Bruhat (BooK) =over 4 =item * David Golden <xdg@xdg.me> =item * Joel Berger <joel.a.berger@gmail.com> =item * Philippe Bruhat (BooK) <book@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2016 by Michael G. Schwern and David Golden. 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 PK�������!�m������perl5/LWP/MediaTypes.pmnu�6$��������package LWP::MediaTypes; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(guess_media_type media_suffix); @EXPORT_OK = qw(add_type add_encoding read_media_types); our $VERSION = '6.04'; use strict; use Scalar::Util qw(blessed); use Carp qw(croak); # note: These hashes will also be filled with the entries found in # the 'media.types' file. my %suffixType = ( 'txt' => 'text/plain', 'html' => 'text/html', 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'xml' => 'text/xml', ); my %suffixExt = ( 'text/plain' => 'txt', 'text/html' => 'html', 'image/gif' => 'gif', 'image/jpeg' => 'jpg', 'text/xml' => 'xml', ); #XXX: there should be some way to define this in the media.types files. my %suffixEncoding = ( 'Z' => 'compress', 'gz' => 'gzip', 'hqx' => 'x-hqx', 'uu' => 'x-uuencode', 'z' => 'x-pack', 'bz2' => 'x-bzip2', ); read_media_types(); sub guess_media_type { my($file, $header) = @_; return undef unless defined $file; my $fullname; if (ref $file) { croak("Unable to determine filetype on unblessed refs") unless blessed($file); if ($file->can('path')) { $file = $file->path; } elsif ($file->can('filename')) { $fullname = $file->filename; } else { $fullname = "" . $file; } } else { $fullname = $file; # enable peek at actual file } my @encoding = (); my $ct = undef; for (file_exts($file)) { # first check this dot part as encoding spec if (exists $suffixEncoding{$_}) { unshift(@encoding, $suffixEncoding{$_}); next; } if (exists $suffixEncoding{lc $_}) { unshift(@encoding, $suffixEncoding{lc $_}); next; } # check content-type if (exists $suffixType{$_}) { $ct = $suffixType{$_}; last; } if (exists $suffixType{lc $_}) { $ct = $suffixType{lc $_}; last; } # don't know nothing about this dot part, bail out last; } unless (defined $ct) { # Take a look at the file if (defined $fullname) { $ct = (-T $fullname) ? "text/plain" : "application/octet-stream"; } else { $ct = "application/octet-stream"; } } if ($header) { $header->header('Content-Type' => $ct); $header->header('Content-Encoding' => \@encoding) if @encoding; } wantarray ? ($ct, @encoding) : $ct; } sub media_suffix { if (!wantarray && @_ == 1 && $_[0] !~ /\*/) { return $suffixExt{lc $_[0]}; } my(@type) = @_; my(@suffix, $ext, $type); foreach (@type) { if (s/\*/.*/) { while(($ext,$type) = each(%suffixType)) { push(@suffix, $ext) if $type =~ /^$_$/i; } } else { my $ltype = lc $_; while(($ext,$type) = each(%suffixType)) { push(@suffix, $ext) if lc $type eq $ltype; } } } wantarray ? @suffix : $suffix[0]; } sub file_exts { require File::Basename; my @parts = reverse split(/\./, File::Basename::basename($_[0])); pop(@parts); # never consider first part @parts; } sub add_type { my($type, @exts) = @_; for my $ext (@exts) { $ext =~ s/^\.//; $suffixType{$ext} = $type; } $suffixExt{lc $type} = $exts[0] if @exts; } sub add_encoding { my($type, @exts) = @_; for my $ext (@exts) { $ext =~ s/^\.//; $suffixEncoding{$ext} = $type; } } sub read_media_types { my(@files) = @_; local($/, $_) = ("\n", undef); # ensure correct $INPUT_RECORD_SEPARATOR my @priv_files = (); push(@priv_files, "$ENV{HOME}/.media.types", "$ENV{HOME}/.mime.types") if defined $ENV{HOME}; # Some doesn't have a home (for instance Win32) # Try to locate "media.types" file, and initialize %suffixType from it my $typefile; unless (@files) { @files = map {"$_/LWP/media.types"} @INC; push @files, @priv_files; } for $typefile (@files) { local(*TYPE); open(TYPE, $typefile) || next; while (<TYPE>) { next if /^\s*#/; # comment line next if /^\s*$/; # blank line s/#.*//; # remove end-of-line comments my($type, @exts) = split(' ', $_); add_type($type, @exts); } close(TYPE); } } 1; __END__ =head1 NAME LWP::MediaTypes - guess media type for a file or a URL =head1 SYNOPSIS use LWP::MediaTypes qw(guess_media_type); $type = guess_media_type("/tmp/foo.gif"); =head1 DESCRIPTION This module provides functions for handling media (also known as MIME) types and encodings. The mapping from file extensions to media types is defined by the F<media.types> file. If the F<~/.media.types> file exists it is used instead. For backwards compatibility we will also look for F<~/.mime.types>. The following functions are exported by default: =over 4 =item guess_media_type( $filename ) =item guess_media_type( $uri ) =item guess_media_type( $filename_or_object, $header_to_modify ) This function tries to guess media type and encoding for a file or objects that support the a C<path> or C<filename> method, eg, L<URI> or L<File::Temp> objects. When an object does not support either method, it will be stringified to determine the filename. It returns the content type, which is a string like C<"text/html">. In array context it also returns any content encodings applied (in the order used to encode the file). You can pass a URI object reference, instead of the file name. If the type can not be deduced from looking at the file name, then guess_media_type() will let the C<-T> Perl operator take a look. If this works (and C<-T> returns a TRUE value) then we return I<text/plain> as the type, otherwise we return I<application/octet-stream> as the type. The optional second argument should be a reference to a HTTP::Headers object or any object that implements the $obj->header method in a similar way. When it is present the values of the 'Content-Type' and 'Content-Encoding' will be set for this header. =item media_suffix( $type, ... ) This function will return all suffixes that can be used to denote the specified media type(s). Wildcard types can be used. In a scalar context it will return the first suffix found. Examples: @suffixes = media_suffix('image/*', 'audio/basic'); $suffix = media_suffix('text/html'); =back The following functions are only exported by explicit request: =over 4 =item add_type( $type, @exts ) Associate a list of file extensions with the given media type. Example: add_type("x-world/x-vrml" => qw(wrl vrml)); =item add_encoding( $type, @ext ) Associate a list of file extensions with an encoding type. Example: add_encoding("x-gzip" => "gz"); =item read_media_types( @files ) Parse media types files and add the type mappings found there. Example: read_media_types("conf/mime.types"); =back =head1 COPYRIGHT Copyright 1995-1999 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. PK�������!�)l5������perl5/LWP/media.typesnu�6$��������# This file maps Internet media types to unique file extension(s). # Although created for httpd, this file is used by many software systems # and has been placed in the public domain for unlimited redisribution. # # The table below contains both registered and (common) unregistered types. # A type that has no unique extension can be ignored -- they are listed # here to guide configurations toward known types and to make it easier to # identify "new" types. File extensions are also commonly used to indicate # content languages and encodings, so choose them carefully. # # Internet media types should be registered as described in RFC 4288. # The registry is at <http://www.iana.org/assignments/media-types/>. # # MIME type (lowercased) Extensions # ============================================ ========== # application/1d-interleaved-parityfec # application/3gpp-ims+xml # application/activemessage application/andrew-inset ez # application/applefile application/applixware aw application/atom+xml atom application/atomcat+xml atomcat # application/atomicmail application/atomsvc+xml atomsvc # application/auth-policy+xml # application/batch-smtp # application/beep+xml # application/cals-1840 application/ccxml+xml ccxml application/cdmi-capability cdmia application/cdmi-container cdmic application/cdmi-domain cdmid application/cdmi-object cdmio application/cdmi-queue cdmiq # application/cea-2018+xml # application/cellml+xml # application/cfw # application/cnrp+xml # application/commonground # application/conference-info+xml # application/cpl+xml # application/csta+xml # application/cstadata+xml application/cu-seeme cu # application/cybercash application/davmount+xml davmount # application/dca-rft # application/dec-dx # application/dialog-info+xml # application/dicom # application/dns # application/dskpp+xml application/dssc+der dssc application/dssc+xml xdssc # application/dvcs application/ecmascript ecma # application/edi-consent # application/edi-x12 # application/edifact application/emma+xml emma # application/epp+xml application/epub+zip epub # application/eshop # application/example application/exi exi # application/fastinfoset # application/fastsoap # application/fits application/font-tdpfr pfr # application/framework-attributes+xml # application/h224 # application/held+xml # application/http application/hyperstudio stk # application/ibe-key-request+xml # application/ibe-pkg-reply+xml # application/ibe-pp-data # application/iges # application/im-iscomposing+xml # application/index # application/index.cmd # application/index.obj # application/index.response # application/index.vnd # application/iotp application/ipfix ipfix # application/ipp # application/isup application/java-archive jar application/java-serialized-object ser application/java-vm class application/javascript js application/json json # application/kpml-request+xml # application/kpml-response+xml application/lost+xml lostxml application/mac-binhex40 hqx application/mac-compactpro cpt # application/macwriteii application/mads+xml mads application/marc mrc application/marcxml+xml mrcx application/mathematica ma nb mb # application/mathml-content+xml # application/mathml-presentation+xml application/mathml+xml mathml # application/mbms-associated-procedure-description+xml # application/mbms-deregister+xml # application/mbms-envelope+xml # application/mbms-msk+xml # application/mbms-msk-response+xml # application/mbms-protection-description+xml # application/mbms-reception-report+xml # application/mbms-register+xml # application/mbms-register-response+xml # application/mbms-user-service-description+xml application/mbox mbox # application/media_control+xml application/mediaservercontrol+xml mscml application/metalink4+xml meta4 application/mets+xml mets # application/mikey application/mods+xml mods # application/moss-keys # application/moss-signature # application/mosskey-data # application/mosskey-request application/mp21 m21 mp21 application/mp4 mp4s # application/mpeg4-generic # application/mpeg4-iod # application/mpeg4-iod-xmt # application/msc-ivr+xml # application/msc-mixer+xml application/msword doc dot application/mxf mxf # application/nasdata # application/news-checkgroups # application/news-groupinfo # application/news-transmission # application/nss # application/ocsp-request # application/ocsp-response application/octet-stream bin dms lha lrf lzh so iso dmg dist distz pkg bpk dump elc deploy application/oda oda application/oebps-package+xml opf application/ogg ogx application/onenote onetoc onetoc2 onetmp onepkg # application/parityfec application/patch-ops-error+xml xer application/pdf pdf application/pgp-encrypted pgp # application/pgp-keys application/pgp-signature asc sig application/pics-rules prf # application/pidf+xml # application/pidf-diff+xml application/pkcs10 p10 application/pkcs7-mime p7m p7c application/pkcs7-signature p7s application/pkcs8 p8 application/pkix-attr-cert ac application/pkix-cert cer application/pkix-crl crl application/pkix-pkipath pkipath application/pkixcmp pki application/pls+xml pls # application/poc-settings+xml application/postscript ai eps ps # application/prs.alvestrand.titrax-sheet application/prs.cww cww # application/prs.nprend # application/prs.plucker # application/prs.rdf-xml-crypt # application/prs.xsf+xml application/pskc+xml pskcxml # application/qsig application/rdf+xml rdf application/reginfo+xml rif application/relax-ng-compact-syntax rnc # application/remote-printing application/resource-lists+xml rl application/resource-lists-diff+xml rld # application/riscos # application/rlmi+xml application/rls-services+xml rs application/rsd+xml rsd application/rss+xml rss application/rtf rtf # application/rtx # application/samlassertion+xml # application/samlmetadata+xml application/sbml+xml sbml application/scvp-cv-request scq application/scvp-cv-response scs application/scvp-vp-request spq application/scvp-vp-response spp application/sdp sdp # application/set-payment application/set-payment-initiation setpay # application/set-registration application/set-registration-initiation setreg # application/sgml # application/sgml-open-catalog application/shf+xml shf # application/sieve # application/simple-filter+xml # application/simple-message-summary # application/simplesymbolcontainer # application/slate # application/smil application/smil+xml smi smil # application/soap+fastinfoset # application/soap+xml application/sparql-query rq application/sparql-results+xml srx # application/spirits-event+xml application/srgs gram application/srgs+xml grxml application/sru+xml sru application/ssml+xml ssml # application/tamp-apex-update # application/tamp-apex-update-confirm # application/tamp-community-update # application/tamp-community-update-confirm # application/tamp-error # application/tamp-sequence-adjust # application/tamp-sequence-adjust-confirm # application/tamp-status-query # application/tamp-status-response # application/tamp-update # application/tamp-update-confirm application/tei+xml tei teicorpus application/thraud+xml tfi # application/timestamp-query # application/timestamp-reply application/timestamped-data tsd # application/tve-trigger # application/ulpfec # application/vemmi # application/vividence.scriptfile # application/vnd.3gpp.bsf+xml application/vnd.3gpp.pic-bw-large plb application/vnd.3gpp.pic-bw-small psb application/vnd.3gpp.pic-bw-var pvb # application/vnd.3gpp.sms # application/vnd.3gpp2.bcmcsinfo+xml # application/vnd.3gpp2.sms application/vnd.3gpp2.tcap tcap application/vnd.3m.post-it-notes pwn application/vnd.accpac.simply.aso aso application/vnd.accpac.simply.imp imp application/vnd.acucobol acu application/vnd.acucorp atc acutc application/vnd.adobe.air-application-installer-package+zip air application/vnd.adobe.fxp fxp fxpl # application/vnd.adobe.partial-upload application/vnd.adobe.xdp+xml xdp application/vnd.adobe.xfdf xfdf # application/vnd.aether.imp # application/vnd.ah-barcode application/vnd.ahead.space ahead application/vnd.airzip.filesecure.azf azf application/vnd.airzip.filesecure.azs azs application/vnd.amazon.ebook azw application/vnd.americandynamics.acc acc application/vnd.amiga.ami ami # application/vnd.amundsen.maze+xml application/vnd.android.package-archive apk application/vnd.anser-web-certificate-issue-initiation cii application/vnd.anser-web-funds-transfer-initiation fti application/vnd.antix.game-component atx application/vnd.apple.installer+xml mpkg application/vnd.apple.mpegurl m3u8 # application/vnd.arastra.swi application/vnd.aristanetworks.swi swi application/vnd.audiograph aep # application/vnd.autopackage # application/vnd.avistar+xml application/vnd.blueice.multipass mpm # application/vnd.bluetooth.ep.oob application/vnd.bmi bmi application/vnd.businessobjects rep # application/vnd.cab-jscript # application/vnd.canon-cpdl # application/vnd.canon-lips # application/vnd.cendio.thinlinc.clientconf application/vnd.chemdraw+xml cdxml application/vnd.chipnuts.karaoke-mmd mmd application/vnd.cinderella cdy # application/vnd.cirpack.isdn-ext application/vnd.claymore cla application/vnd.cloanto.rp9 rp9 application/vnd.clonk.c4group c4g c4d c4f c4p c4u application/vnd.cluetrust.cartomobile-config c11amc application/vnd.cluetrust.cartomobile-config-pkg c11amz # application/vnd.commerce-battelle application/vnd.commonspace csp application/vnd.contact.cmsg cdbcmsg application/vnd.cosmocaller cmc application/vnd.crick.clicker clkx application/vnd.crick.clicker.keyboard clkk application/vnd.crick.clicker.palette clkp application/vnd.crick.clicker.template clkt application/vnd.crick.clicker.wordbank clkw application/vnd.criticaltools.wbs+xml wbs application/vnd.ctc-posml pml # application/vnd.ctct.ws+xml # application/vnd.cups-pdf # application/vnd.cups-postscript application/vnd.cups-ppd ppd # application/vnd.cups-raster # application/vnd.cups-raw application/vnd.curl.car car application/vnd.curl.pcurl pcurl # application/vnd.cybank application/vnd.data-vision.rdz rdz application/vnd.dece.data uvf uvvf uvd uvvd application/vnd.dece.ttml+xml uvt uvvt application/vnd.dece.unspecified uvx uvvx application/vnd.denovo.fcselayout-link fe_launch # application/vnd.dir-bi.plate-dl-nosuffix application/vnd.dna dna application/vnd.dolby.mlp mlp # application/vnd.dolby.mobile.1 # application/vnd.dolby.mobile.2 application/vnd.dpgraph dpg application/vnd.dreamfactory dfac application/vnd.dvb.ait ait # application/vnd.dvb.dvbj # application/vnd.dvb.esgcontainer # application/vnd.dvb.ipdcdftnotifaccess # application/vnd.dvb.ipdcesgaccess # application/vnd.dvb.ipdcesgaccess2 # application/vnd.dvb.ipdcesgpdd # application/vnd.dvb.ipdcroaming # application/vnd.dvb.iptv.alfec-base # application/vnd.dvb.iptv.alfec-enhancement # application/vnd.dvb.notif-aggregate-root+xml # application/vnd.dvb.notif-container+xml # application/vnd.dvb.notif-generic+xml # application/vnd.dvb.notif-ia-msglist+xml # application/vnd.dvb.notif-ia-registration-request+xml # application/vnd.dvb.notif-ia-registration-response+xml # application/vnd.dvb.notif-init+xml # application/vnd.dvb.pfr application/vnd.dvb.service svc # application/vnd.dxr application/vnd.dynageo geo # application/vnd.easykaraoke.cdgdownload # application/vnd.ecdis-update application/vnd.ecowin.chart mag # application/vnd.ecowin.filerequest # application/vnd.ecowin.fileupdate # application/vnd.ecowin.series # application/vnd.ecowin.seriesrequest # application/vnd.ecowin.seriesupdate # application/vnd.emclient.accessrequest+xml application/vnd.enliven nml application/vnd.epson.esf esf application/vnd.epson.msf msf application/vnd.epson.quickanime qam application/vnd.epson.salt slt application/vnd.epson.ssf ssf # application/vnd.ericsson.quickcall application/vnd.eszigno3+xml es3 et3 # application/vnd.etsi.aoc+xml # application/vnd.etsi.cug+xml # application/vnd.etsi.iptvcommand+xml # application/vnd.etsi.iptvdiscovery+xml # application/vnd.etsi.iptvprofile+xml # application/vnd.etsi.iptvsad-bc+xml # application/vnd.etsi.iptvsad-cod+xml # application/vnd.etsi.iptvsad-npvr+xml # application/vnd.etsi.iptvservice+xml # application/vnd.etsi.iptvsync+xml # application/vnd.etsi.iptvueprofile+xml # application/vnd.etsi.mcid+xml # application/vnd.etsi.overload-control-policy-dataset+xml # application/vnd.etsi.sci+xml # application/vnd.etsi.simservs+xml # application/vnd.etsi.tsl+xml # application/vnd.etsi.tsl.der # application/vnd.eudora.data application/vnd.ezpix-album ez2 application/vnd.ezpix-package ez3 # application/vnd.f-secure.mobile application/vnd.fdf fdf application/vnd.fdsn.mseed mseed application/vnd.fdsn.seed seed dataless # application/vnd.ffsns # application/vnd.fints application/vnd.flographit gph application/vnd.fluxtime.clip ftc # application/vnd.font-fontforge-sfd application/vnd.framemaker fm frame maker book application/vnd.frogans.fnc fnc application/vnd.frogans.ltf ltf application/vnd.fsc.weblaunch fsc application/vnd.fujitsu.oasys oas application/vnd.fujitsu.oasys2 oa2 application/vnd.fujitsu.oasys3 oa3 application/vnd.fujitsu.oasysgp fg5 application/vnd.fujitsu.oasysprs bh2 # application/vnd.fujixerox.art-ex # application/vnd.fujixerox.art4 # application/vnd.fujixerox.hbpl application/vnd.fujixerox.ddd ddd application/vnd.fujixerox.docuworks xdw application/vnd.fujixerox.docuworks.binder xbd # application/vnd.fut-misnet application/vnd.fuzzysheet fzs application/vnd.genomatix.tuxedo txd # application/vnd.geocube+xml application/vnd.geogebra.file ggb application/vnd.geogebra.tool ggt application/vnd.geometry-explorer gex gre application/vnd.geonext gxt application/vnd.geoplan g2w application/vnd.geospace g3w # application/vnd.globalplatform.card-content-mgt # application/vnd.globalplatform.card-content-mgt-response application/vnd.gmx gmx application/vnd.google-earth.kml+xml kml application/vnd.google-earth.kmz kmz application/vnd.grafeq gqf gqs # application/vnd.gridmp application/vnd.groove-account gac application/vnd.groove-help ghf application/vnd.groove-identity-message gim application/vnd.groove-injector grv application/vnd.groove-tool-message gtm application/vnd.groove-tool-template tpl application/vnd.groove-vcard vcg application/vnd.hal+xml hal application/vnd.handheld-entertainment+xml zmm application/vnd.hbci hbci # application/vnd.hcl-bireports application/vnd.hhe.lesson-player les application/vnd.hp-hpgl hpgl application/vnd.hp-hpid hpid application/vnd.hp-hps hps application/vnd.hp-jlyt jlt application/vnd.hp-pcl pcl application/vnd.hp-pclxl pclxl # application/vnd.httphone application/vnd.hydrostatix.sof-data sfd-hdstx application/vnd.hzn-3d-crossword x3d # application/vnd.ibm.afplinedata # application/vnd.ibm.electronic-media application/vnd.ibm.minipay mpy application/vnd.ibm.modcap afp listafp list3820 application/vnd.ibm.rights-management irm application/vnd.ibm.secure-container sc application/vnd.iccprofile icc icm application/vnd.igloader igl application/vnd.immervision-ivp ivp application/vnd.immervision-ivu ivu # application/vnd.informedcontrol.rms+xml # application/vnd.informix-visionary # application/vnd.infotech.project # application/vnd.infotech.project+xml application/vnd.insors.igm igm application/vnd.intercon.formnet xpw xpx application/vnd.intergeo i2g # application/vnd.intertrust.digibox # application/vnd.intertrust.nncp application/vnd.intu.qbo qbo application/vnd.intu.qfx qfx # application/vnd.iptc.g2.conceptitem+xml # application/vnd.iptc.g2.knowledgeitem+xml # application/vnd.iptc.g2.newsitem+xml # application/vnd.iptc.g2.packageitem+xml application/vnd.ipunplugged.rcprofile rcprofile application/vnd.irepository.package+xml irp application/vnd.is-xpr xpr application/vnd.isac.fcs fcs application/vnd.jam jam # application/vnd.japannet-directory-service # application/vnd.japannet-jpnstore-wakeup # application/vnd.japannet-payment-wakeup # application/vnd.japannet-registration # application/vnd.japannet-registration-wakeup # application/vnd.japannet-setstore-wakeup # application/vnd.japannet-verification # application/vnd.japannet-verification-wakeup application/vnd.jcp.javame.midlet-rms rms application/vnd.jisp jisp application/vnd.joost.joda-archive joda application/vnd.kahootz ktz ktr application/vnd.kde.karbon karbon application/vnd.kde.kchart chrt application/vnd.kde.kformula kfo application/vnd.kde.kivio flw application/vnd.kde.kontour kon application/vnd.kde.kpresenter kpr kpt application/vnd.kde.kspread ksp application/vnd.kde.kword kwd kwt application/vnd.kenameaapp htke application/vnd.kidspiration kia application/vnd.kinar kne knp application/vnd.koan skp skd skt skm application/vnd.kodak-descriptor sse application/vnd.las.las+xml lasxml # application/vnd.liberty-request+xml application/vnd.llamagraphics.life-balance.desktop lbd application/vnd.llamagraphics.life-balance.exchange+xml lbe application/vnd.lotus-1-2-3 123 application/vnd.lotus-approach apr application/vnd.lotus-freelance pre application/vnd.lotus-notes nsf application/vnd.lotus-organizer org application/vnd.lotus-screencam scm application/vnd.lotus-wordpro lwp application/vnd.macports.portpkg portpkg # application/vnd.marlin.drm.actiontoken+xml # application/vnd.marlin.drm.conftoken+xml # application/vnd.marlin.drm.license+xml # application/vnd.marlin.drm.mdcf application/vnd.mcd mcd application/vnd.medcalcdata mc1 application/vnd.mediastation.cdkey cdkey # application/vnd.meridian-slingshot application/vnd.mfer mwf application/vnd.mfmp mfm application/vnd.micrografx.flo flo application/vnd.micrografx.igx igx application/vnd.mif mif # application/vnd.minisoft-hp3000-save # application/vnd.mitsubishi.misty-guard.trustweb application/vnd.mobius.daf daf application/vnd.mobius.dis dis application/vnd.mobius.mbk mbk application/vnd.mobius.mqy mqy application/vnd.mobius.msl msl application/vnd.mobius.plc plc application/vnd.mobius.txf txf application/vnd.mophun.application mpn application/vnd.mophun.certificate mpc # application/vnd.motorola.flexsuite # application/vnd.motorola.flexsuite.adsi # application/vnd.motorola.flexsuite.fis # application/vnd.motorola.flexsuite.gotap # application/vnd.motorola.flexsuite.kmr # application/vnd.motorola.flexsuite.ttc # application/vnd.motorola.flexsuite.wem # application/vnd.motorola.iprm application/vnd.mozilla.xul+xml xul application/vnd.ms-artgalry cil # application/vnd.ms-asf application/vnd.ms-cab-compressed cab application/vnd.ms-excel xls xlm xla xlc xlt xlw application/vnd.ms-excel.addin.macroenabled.12 xlam application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb application/vnd.ms-excel.sheet.macroenabled.12 xlsm application/vnd.ms-excel.template.macroenabled.12 xltm application/vnd.ms-fontobject eot application/vnd.ms-htmlhelp chm application/vnd.ms-ims ims application/vnd.ms-lrm lrm # application/vnd.ms-office.activex+xml application/vnd.ms-officetheme thmx application/vnd.ms-pki.seccat cat application/vnd.ms-pki.stl stl # application/vnd.ms-playready.initiator+xml application/vnd.ms-powerpoint ppt pps pot application/vnd.ms-powerpoint.addin.macroenabled.12 ppam application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm application/vnd.ms-powerpoint.slide.macroenabled.12 sldm application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm application/vnd.ms-powerpoint.template.macroenabled.12 potm application/vnd.ms-project mpp mpt # application/vnd.ms-tnef # application/vnd.ms-wmdrm.lic-chlg-req # application/vnd.ms-wmdrm.lic-resp # application/vnd.ms-wmdrm.meter-chlg-req # application/vnd.ms-wmdrm.meter-resp application/vnd.ms-word.document.macroenabled.12 docm application/vnd.ms-word.template.macroenabled.12 dotm application/vnd.ms-works wps wks wcm wdb application/vnd.ms-wpl wpl application/vnd.ms-xpsdocument xps application/vnd.mseq mseq # application/vnd.msign # application/vnd.multiad.creator # application/vnd.multiad.creator.cif # application/vnd.music-niff application/vnd.musician mus application/vnd.muvee.style msty # application/vnd.ncd.control # application/vnd.ncd.reference # application/vnd.nervana # application/vnd.netfpx application/vnd.neurolanguage.nlu nlu application/vnd.noblenet-directory nnd application/vnd.noblenet-sealer nns application/vnd.noblenet-web nnw # application/vnd.nokia.catalogs # application/vnd.nokia.conml+wbxml # application/vnd.nokia.conml+xml # application/vnd.nokia.isds-radio-presets # application/vnd.nokia.iptv.config+xml # application/vnd.nokia.landmark+wbxml # application/vnd.nokia.landmark+xml # application/vnd.nokia.landmarkcollection+xml # application/vnd.nokia.n-gage.ac+xml application/vnd.nokia.n-gage.data ngdat application/vnd.nokia.n-gage.symbian.install n-gage # application/vnd.nokia.ncd # application/vnd.nokia.pcd+wbxml # application/vnd.nokia.pcd+xml application/vnd.nokia.radio-preset rpst application/vnd.nokia.radio-presets rpss application/vnd.novadigm.edm edm application/vnd.novadigm.edx edx application/vnd.novadigm.ext ext # application/vnd.ntt-local.file-transfer # application/vnd.ntt-local.sip-ta_remote # application/vnd.ntt-local.sip-ta_tcp_stream application/vnd.oasis.opendocument.chart odc application/vnd.oasis.opendocument.chart-template otc application/vnd.oasis.opendocument.database odb application/vnd.oasis.opendocument.formula odf application/vnd.oasis.opendocument.formula-template odft application/vnd.oasis.opendocument.graphics odg application/vnd.oasis.opendocument.graphics-template otg application/vnd.oasis.opendocument.image odi application/vnd.oasis.opendocument.image-template oti application/vnd.oasis.opendocument.presentation odp application/vnd.oasis.opendocument.presentation-template otp application/vnd.oasis.opendocument.spreadsheet ods application/vnd.oasis.opendocument.spreadsheet-template ots application/vnd.oasis.opendocument.text odt application/vnd.oasis.opendocument.text-master odm application/vnd.oasis.opendocument.text-template ott application/vnd.oasis.opendocument.text-web oth # application/vnd.obn # application/vnd.oipf.contentaccessdownload+xml # application/vnd.oipf.contentaccessstreaming+xml # application/vnd.oipf.cspg-hexbinary # application/vnd.oipf.dae.svg+xml # application/vnd.oipf.dae.xhtml+xml # application/vnd.oipf.mippvcontrolmessage+xml # application/vnd.oipf.pae.gem # application/vnd.oipf.spdiscovery+xml # application/vnd.oipf.spdlist+xml # application/vnd.oipf.ueprofile+xml # application/vnd.oipf.userprofile+xml application/vnd.olpc-sugar xo # application/vnd.oma-scws-config # application/vnd.oma-scws-http-request # application/vnd.oma-scws-http-response # application/vnd.oma.bcast.associated-procedure-parameter+xml # application/vnd.oma.bcast.drm-trigger+xml # application/vnd.oma.bcast.imd+xml # application/vnd.oma.bcast.ltkm # application/vnd.oma.bcast.notification+xml # application/vnd.oma.bcast.provisioningtrigger # application/vnd.oma.bcast.sgboot # application/vnd.oma.bcast.sgdd+xml # application/vnd.oma.bcast.sgdu # application/vnd.oma.bcast.simple-symbol-container # application/vnd.oma.bcast.smartcard-trigger+xml # application/vnd.oma.bcast.sprov+xml # application/vnd.oma.bcast.stkm # application/vnd.oma.cab-address-book+xml # application/vnd.oma.cab-pcc+xml # application/vnd.oma.dcd # application/vnd.oma.dcdc application/vnd.oma.dd2+xml dd2 # application/vnd.oma.drm.risd+xml # application/vnd.oma.group-usage-list+xml # application/vnd.oma.poc.detailed-progress-report+xml # application/vnd.oma.poc.final-report+xml # application/vnd.oma.poc.groups+xml # application/vnd.oma.poc.invocation-descriptor+xml # application/vnd.oma.poc.optimized-progress-report+xml # application/vnd.oma.push # application/vnd.oma.scidm.messages+xml # application/vnd.oma.xcap-directory+xml # application/vnd.omads-email+xml # application/vnd.omads-file+xml # application/vnd.omads-folder+xml # application/vnd.omaloc-supl-init application/vnd.openofficeorg.extension oxt # application/vnd.openxmlformats-officedocument.custom-properties+xml # application/vnd.openxmlformats-officedocument.customxmlproperties+xml # application/vnd.openxmlformats-officedocument.drawing+xml # application/vnd.openxmlformats-officedocument.drawingml.chart+xml # application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml # application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml # application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml # application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml # application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml # application/vnd.openxmlformats-officedocument.extended-properties+xml # application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml # application/vnd.openxmlformats-officedocument.presentationml.comments+xml # application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml # application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml # application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml application/vnd.openxmlformats-officedocument.presentationml.presentation pptx # application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml # application/vnd.openxmlformats-officedocument.presentationml.presprops+xml application/vnd.openxmlformats-officedocument.presentationml.slide sldx # application/vnd.openxmlformats-officedocument.presentationml.slide+xml # application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml # application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx # application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml # application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml # application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml # application/vnd.openxmlformats-officedocument.presentationml.tags+xml application/vnd.openxmlformats-officedocument.presentationml.template potx # application/vnd.openxmlformats-officedocument.presentationml.template.main+xml # application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx # application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx # application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml # application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml # application/vnd.openxmlformats-officedocument.theme+xml # application/vnd.openxmlformats-officedocument.themeoverride+xml # application/vnd.openxmlformats-officedocument.vmldrawing # application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml application/vnd.openxmlformats-officedocument.wordprocessingml.document docx # application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx # application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml # application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml # application/vnd.openxmlformats-package.core-properties+xml # application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml # application/vnd.openxmlformats-package.relationships+xml # application/vnd.quobject-quoxdocument # application/vnd.osa.netdeploy application/vnd.osgeo.mapguide.package mgp # application/vnd.osgi.bundle application/vnd.osgi.dp dp # application/vnd.otps.ct-kip+xml application/vnd.palm pdb pqa oprc # application/vnd.paos.xml application/vnd.pawaafile paw application/vnd.pg.format str application/vnd.pg.osasli ei6 # application/vnd.piaccess.application-licence application/vnd.picsel efif application/vnd.pmi.widget wg # application/vnd.poc.group-advertisement+xml application/vnd.pocketlearn plf application/vnd.powerbuilder6 pbd # application/vnd.powerbuilder6-s # application/vnd.powerbuilder7 # application/vnd.powerbuilder7-s # application/vnd.powerbuilder75 # application/vnd.powerbuilder75-s # application/vnd.preminet application/vnd.previewsystems.box box application/vnd.proteus.magazine mgz application/vnd.publishare-delta-tree qps application/vnd.pvi.ptid1 ptid # application/vnd.pwg-multiplexed # application/vnd.pwg-xhtml-print+xml # application/vnd.qualcomm.brew-app-res application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb # application/vnd.radisys.moml+xml # application/vnd.radisys.msml+xml # application/vnd.radisys.msml-audit+xml # application/vnd.radisys.msml-audit-conf+xml # application/vnd.radisys.msml-audit-conn+xml # application/vnd.radisys.msml-audit-dialog+xml # application/vnd.radisys.msml-audit-stream+xml # application/vnd.radisys.msml-conf+xml # application/vnd.radisys.msml-dialog+xml # application/vnd.radisys.msml-dialog-base+xml # application/vnd.radisys.msml-dialog-fax-detect+xml # application/vnd.radisys.msml-dialog-fax-sendrecv+xml # application/vnd.radisys.msml-dialog-group+xml # application/vnd.radisys.msml-dialog-speech+xml # application/vnd.radisys.msml-dialog-transform+xml # application/vnd.rainstor.data # application/vnd.rapid application/vnd.realvnc.bed bed application/vnd.recordare.musicxml mxl application/vnd.recordare.musicxml+xml musicxml # application/vnd.renlearn.rlprint application/vnd.rig.cryptonote cryptonote application/vnd.rim.cod cod application/vnd.rn-realmedia rm application/vnd.route66.link66+xml link66 # application/vnd.ruckus.download # application/vnd.s3sms application/vnd.sailingtracker.track st # application/vnd.sbm.cid # application/vnd.sbm.mid2 # application/vnd.scribus # application/vnd.sealed.3df # application/vnd.sealed.csf # application/vnd.sealed.doc # application/vnd.sealed.eml # application/vnd.sealed.mht # application/vnd.sealed.net # application/vnd.sealed.ppt # application/vnd.sealed.tiff # application/vnd.sealed.xls # application/vnd.sealedmedia.softseal.html # application/vnd.sealedmedia.softseal.pdf application/vnd.seemail see application/vnd.sema sema application/vnd.semd semd application/vnd.semf semf application/vnd.shana.informed.formdata ifm application/vnd.shana.informed.formtemplate itp application/vnd.shana.informed.interchange iif application/vnd.shana.informed.package ipk application/vnd.simtech-mindmapper twd twds application/vnd.smaf mmf # application/vnd.smart.notebook application/vnd.smart.teacher teacher # application/vnd.software602.filler.form+xml # application/vnd.software602.filler.form-xml-zip application/vnd.solent.sdkm+xml sdkm sdkd application/vnd.spotfire.dxp dxp application/vnd.spotfire.sfs sfs # application/vnd.sss-cod # application/vnd.sss-dtf # application/vnd.sss-ntf application/vnd.stardivision.calc sdc application/vnd.stardivision.draw sda application/vnd.stardivision.impress sdd application/vnd.stardivision.math smf application/vnd.stardivision.writer sdw vor application/vnd.stardivision.writer-global sgl application/vnd.stepmania.stepchart sm # application/vnd.street-stream application/vnd.sun.xml.calc sxc application/vnd.sun.xml.calc.template stc application/vnd.sun.xml.draw sxd application/vnd.sun.xml.draw.template std application/vnd.sun.xml.impress sxi application/vnd.sun.xml.impress.template sti application/vnd.sun.xml.math sxm application/vnd.sun.xml.writer sxw application/vnd.sun.xml.writer.global sxg application/vnd.sun.xml.writer.template stw # application/vnd.sun.wadl+xml application/vnd.sus-calendar sus susp application/vnd.svd svd # application/vnd.swiftview-ics application/vnd.symbian.install sis sisx application/vnd.syncml+xml xsm application/vnd.syncml.dm+wbxml bdm application/vnd.syncml.dm+xml xdm # application/vnd.syncml.dm.notification # application/vnd.syncml.ds.notification application/vnd.tao.intent-module-archive tao application/vnd.tmobile-livetv tmo application/vnd.trid.tpt tpt application/vnd.triscape.mxs mxs application/vnd.trueapp tra # application/vnd.truedoc # application/vnd.ubisoft.webplayer application/vnd.ufdl ufd ufdl application/vnd.uiq.theme utz application/vnd.umajin umj application/vnd.unity unityweb application/vnd.uoml+xml uoml # application/vnd.uplanet.alert # application/vnd.uplanet.alert-wbxml # application/vnd.uplanet.bearer-choice # application/vnd.uplanet.bearer-choice-wbxml # application/vnd.uplanet.cacheop # application/vnd.uplanet.cacheop-wbxml # application/vnd.uplanet.channel # application/vnd.uplanet.channel-wbxml # application/vnd.uplanet.list # application/vnd.uplanet.list-wbxml # application/vnd.uplanet.listcmd # application/vnd.uplanet.listcmd-wbxml # application/vnd.uplanet.signal application/vnd.vcx vcx # application/vnd.vd-study # application/vnd.vectorworks # application/vnd.verimatrix.vcas # application/vnd.vidsoft.vidconference application/vnd.visio vsd vst vss vsw application/vnd.visionary vis # application/vnd.vividence.scriptfile application/vnd.vsf vsf # application/vnd.wap.sic # application/vnd.wap.slc application/vnd.wap.wbxml wbxml application/vnd.wap.wmlc wmlc application/vnd.wap.wmlscriptc wmlsc application/vnd.webturbo wtb # application/vnd.wfa.wsc # application/vnd.wmc # application/vnd.wmf.bootstrap # application/vnd.wolfram.mathematica # application/vnd.wolfram.mathematica.package application/vnd.wolfram.player nbp application/vnd.wordperfect wpd application/vnd.wqd wqd # application/vnd.wrq-hp3000-labelled application/vnd.wt.stf stf # application/vnd.wv.csp+wbxml # application/vnd.wv.csp+xml # application/vnd.wv.ssp+xml application/vnd.xara xar application/vnd.xfdl xfdl # application/vnd.xfdl.webform # application/vnd.xmi+xml # application/vnd.xmpie.cpkg # application/vnd.xmpie.dpkg # application/vnd.xmpie.plan # application/vnd.xmpie.ppkg # application/vnd.xmpie.xlim application/vnd.yamaha.hv-dic hvd application/vnd.yamaha.hv-script hvs application/vnd.yamaha.hv-voice hvp application/vnd.yamaha.openscoreformat osf application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg # application/vnd.yamaha.remote-setup application/vnd.yamaha.smaf-audio saf application/vnd.yamaha.smaf-phrase spf # application/vnd.yamaha.tunnel-udpencap application/vnd.yellowriver-custom-menu cmp application/vnd.zul zir zirz application/vnd.zzazz.deck+xml zaz application/voicexml+xml vxml # application/vq-rtcpxr # application/watcherinfo+xml # application/whoispp-query # application/whoispp-response application/widget wgt application/winhlp hlp # application/wita # application/wordperfect5.1 application/wsdl+xml wsdl application/wspolicy+xml wspolicy application/x-7z-compressed 7z application/x-abiword abw application/x-ace-compressed ace application/x-authorware-bin aab x32 u32 vox application/x-authorware-map aam application/x-authorware-seg aas application/x-bcpio bcpio application/x-bittorrent torrent application/x-bzip bz application/x-bzip2 bz2 boz application/x-cdlink vcd application/x-chat chat application/x-chess-pgn pgn # application/x-compress application/x-cpio cpio application/x-csh csh application/x-debian-package deb udeb application/x-director dir dcr dxr cst cct cxt w3d fgd swa application/x-doom wad application/x-dtbncx+xml ncx application/x-dtbook+xml dtb application/x-dtbresource+xml res application/x-dvi dvi application/x-font-bdf bdf # application/x-font-dos # application/x-font-framemaker application/x-font-ghostscript gsf # application/x-font-libgrx application/x-font-linux-psf psf application/x-font-otf otf application/x-font-pcf pcf application/x-font-snf snf # application/x-font-speedo # application/x-font-sunos-news application/x-font-ttf ttf ttc application/x-font-type1 pfa pfb pfm afm application/x-font-woff woff # application/x-font-vfont application/x-futuresplash spl application/x-gnumeric gnumeric application/x-gtar gtar # application/x-gzip application/x-hdf hdf application/x-java-jnlp-file jnlp application/x-latex latex application/x-mobipocket-ebook prc mobi application/x-ms-application application application/x-ms-wmd wmd application/x-ms-wmz wmz application/x-ms-xbap xbap application/x-msaccess mdb application/x-msbinder obd application/x-mscardfile crd application/x-msclip clp application/x-msdownload exe dll com bat msi application/x-msmediaview mvb m13 m14 application/x-msmetafile wmf application/x-msmoney mny application/x-mspublisher pub application/x-msschedule scd application/x-msterminal trm application/x-mswrite wri application/x-netcdf nc cdf application/x-pkcs12 p12 pfx application/x-pkcs7-certificates p7b spc application/x-pkcs7-certreqresp p7r application/x-rar-compressed rar application/x-sh sh application/x-shar shar application/x-shockwave-flash swf application/x-silverlight-app xap application/x-stuffit sit application/x-stuffitx sitx application/x-sv4cpio sv4cpio application/x-sv4crc sv4crc application/x-tar tar application/x-tcl tcl application/x-tex tex application/x-tex-tfm tfm application/x-texinfo texinfo texi application/x-ustar ustar application/x-wais-source src application/x-x509-ca-cert der crt application/x-xfig fig application/x-xpinstall xpi # application/x400-bp # application/xcap-att+xml # application/xcap-caps+xml application/xcap-diff+xml xdf # application/xcap-el+xml # application/xcap-error+xml # application/xcap-ns+xml # application/xcon-conference-info-diff+xml # application/xcon-conference-info+xml application/xenc+xml xenc application/xhtml+xml xhtml xht # application/xhtml-voice+xml application/xml xml xsl application/xml-dtd dtd # application/xml-external-parsed-entity # application/xmpp+xml application/xop+xml xop application/xslt+xml xslt application/xspf+xml xspf application/xv+xml mxml xhvml xvml xvm application/yang yang application/yin+xml yin application/zip zip # audio/1d-interleaved-parityfec # audio/32kadpcm # audio/3gpp # audio/3gpp2 # audio/ac3 audio/adpcm adp # audio/amr # audio/amr-wb # audio/amr-wb+ # audio/asc # audio/atrac-advanced-lossless # audio/atrac-x # audio/atrac3 audio/basic au snd # audio/bv16 # audio/bv32 # audio/clearmode # audio/cn # audio/dat12 # audio/dls # audio/dsr-es201108 # audio/dsr-es202050 # audio/dsr-es202211 # audio/dsr-es202212 # audio/dvi4 # audio/eac3 # audio/evrc # audio/evrc-qcp # audio/evrc0 # audio/evrc1 # audio/evrcb # audio/evrcb0 # audio/evrcb1 # audio/evrcwb # audio/evrcwb0 # audio/evrcwb1 # audio/example # audio/g719 # audio/g722 # audio/g7221 # audio/g723 # audio/g726-16 # audio/g726-24 # audio/g726-32 # audio/g726-40 # audio/g728 # audio/g729 # audio/g7291 # audio/g729d # audio/g729e # audio/gsm # audio/gsm-efr # audio/gsm-hr-08 # audio/ilbc # audio/l16 # audio/l20 # audio/l24 # audio/l8 # audio/lpc audio/midi mid midi kar rmi # audio/mobile-xmf audio/mp4 mp4a # audio/mp4a-latm # audio/mpa # audio/mpa-robust audio/mpeg mpga mp2 mp2a mp3 m2a m3a # audio/mpeg4-generic audio/ogg oga ogg spx # audio/parityfec # audio/pcma # audio/pcma-wb # audio/pcmu-wb # audio/pcmu # audio/prs.sid # audio/qcelp # audio/red # audio/rtp-enc-aescm128 # audio/rtp-midi # audio/rtx # audio/smv # audio/smv0 # audio/smv-qcp # audio/sp-midi # audio/speex # audio/t140c # audio/t38 # audio/telephone-event # audio/tone # audio/uemclip # audio/ulpfec # audio/vdvi # audio/vmr-wb # audio/vnd.3gpp.iufp # audio/vnd.4sb # audio/vnd.audiokoz # audio/vnd.celp # audio/vnd.cisco.nse # audio/vnd.cmles.radio-events # audio/vnd.cns.anp1 # audio/vnd.cns.inf1 audio/vnd.dece.audio uva uvva audio/vnd.digital-winds eol # audio/vnd.dlna.adts # audio/vnd.dolby.heaac.1 # audio/vnd.dolby.heaac.2 # audio/vnd.dolby.mlp # audio/vnd.dolby.mps # audio/vnd.dolby.pl2 # audio/vnd.dolby.pl2x # audio/vnd.dolby.pl2z # audio/vnd.dolby.pulse.1 audio/vnd.dra dra audio/vnd.dts dts audio/vnd.dts.hd dtshd # audio/vnd.everad.plj # audio/vnd.hns.audio audio/vnd.lucent.voice lvp audio/vnd.ms-playready.media.pya pya # audio/vnd.nokia.mobile-xmf # audio/vnd.nortel.vbk audio/vnd.nuera.ecelp4800 ecelp4800 audio/vnd.nuera.ecelp7470 ecelp7470 audio/vnd.nuera.ecelp9600 ecelp9600 # audio/vnd.octel.sbc # audio/vnd.qcelp # audio/vnd.rhetorex.32kadpcm audio/vnd.rip rip # audio/vnd.sealedmedia.softseal.mpeg # audio/vnd.vmx.cvsd # audio/vorbis # audio/vorbis-config audio/webm weba audio/x-aac aac audio/x-aiff aif aiff aifc audio/x-mpegurl m3u audio/x-ms-wax wax audio/x-ms-wma wma audio/x-pn-realaudio ram ra audio/x-pn-realaudio-plugin rmp audio/x-wav wav chemical/x-cdx cdx chemical/x-cif cif chemical/x-cmdf cmdf chemical/x-cml cml chemical/x-csml csml # chemical/x-pdb chemical/x-xyz xyz image/bmp bmp image/cgm cgm # image/example # image/fits image/g3fax g3 image/gif gif image/ief ief # image/jp2 image/jpeg jpeg jpg jpe # image/jpm # image/jpx image/ktx ktx # image/naplps image/png png image/prs.btif btif # image/prs.pti image/svg+xml svg svgz # image/t38 image/tiff tiff tif # image/tiff-fx image/vnd.adobe.photoshop psd # image/vnd.cns.inf2 image/vnd.dece.graphic uvi uvvi uvg uvvg image/vnd.dvb.subtitle sub image/vnd.djvu djvu djv image/vnd.dwg dwg image/vnd.dxf dxf image/vnd.fastbidsheet fbs image/vnd.fpx fpx image/vnd.fst fst image/vnd.fujixerox.edmics-mmr mmr image/vnd.fujixerox.edmics-rlc rlc # image/vnd.globalgraphics.pgb # image/vnd.microsoft.icon # image/vnd.mix image/vnd.ms-modi mdi image/vnd.net-fpx npx # image/vnd.radiance # image/vnd.sealed.png # image/vnd.sealedmedia.softseal.gif # image/vnd.sealedmedia.softseal.jpg # image/vnd.svf image/vnd.wap.wbmp wbmp image/vnd.xiff xif image/webp webp image/x-cmu-raster ras image/x-cmx cmx image/x-freehand fh fhc fh4 fh5 fh7 image/x-icon ico image/x-pcx pcx image/x-pict pic pct image/x-portable-anymap pnm image/x-portable-bitmap pbm image/x-portable-graymap pgm image/x-portable-pixmap ppm image/x-rgb rgb image/x-xbitmap xbm image/x-xpixmap xpm image/x-xwindowdump xwd # message/cpim # message/delivery-status # message/disposition-notification # message/example # message/external-body # message/feedback-report # message/global # message/global-delivery-status # message/global-disposition-notification # message/global-headers # message/http # message/imdn+xml # message/news # message/partial message/rfc822 eml mime # message/s-http # message/sip # message/sipfrag # message/tracking-status # message/vnd.si.simp # model/example model/iges igs iges model/mesh msh mesh silo model/vnd.collada+xml dae model/vnd.dwf dwf # model/vnd.flatland.3dml model/vnd.gdl gdl # model/vnd.gs-gdl # model/vnd.gs.gdl model/vnd.gtw gtw # model/vnd.moml+xml model/vnd.mts mts # model/vnd.parasolid.transmit.binary # model/vnd.parasolid.transmit.text model/vnd.vtu vtu model/vrml wrl vrml # multipart/alternative # multipart/appledouble # multipart/byteranges # multipart/digest # multipart/encrypted # multipart/example # multipart/form-data # multipart/header-set # multipart/mixed # multipart/parallel # multipart/related # multipart/report # multipart/signed # multipart/voice-message # text/1d-interleaved-parityfec text/calendar ics ifb text/css css text/csv csv # text/directory # text/dns # text/ecmascript # text/enriched # text/example text/html html htm # text/javascript text/n3 n3 # text/parityfec text/plain txt text conf def list log in # text/prs.fallenstein.rst text/prs.lines.tag dsc # text/vnd.radisys.msml-basic-layout # text/red # text/rfc822-headers text/richtext rtx # text/rtf # text/rtp-enc-aescm128 # text/rtx text/sgml sgml sgm # text/t140 text/tab-separated-values tsv text/troff t tr roff man me ms text/turtle ttl # text/ulpfec text/uri-list uri uris urls # text/vnd.abc text/vnd.curl curl text/vnd.curl.dcurl dcurl text/vnd.curl.scurl scurl text/vnd.curl.mcurl mcurl # text/vnd.dmclientscript # text/vnd.esmertec.theme-descriptor text/vnd.fly fly text/vnd.fmi.flexstor flx text/vnd.graphviz gv text/vnd.in3d.3dml 3dml text/vnd.in3d.spot spot # text/vnd.iptc.newsml # text/vnd.iptc.nitf # text/vnd.latex-z # text/vnd.motorola.reflex # text/vnd.ms-mediapackage # text/vnd.net2phone.commcenter.command # text/vnd.si.uricatalogue text/vnd.sun.j2me.app-descriptor jad # text/vnd.trolltech.linguist # text/vnd.wap.si # text/vnd.wap.sl text/vnd.wap.wml wml text/vnd.wap.wmlscript wmls text/x-asm s asm text/x-c c cc cxx cpp h hh dic text/x-fortran f for f77 f90 text/x-pascal p pas text/x-java-source java text/x-setext etx text/x-uuencode uu text/x-vcalendar vcs text/x-vcard vcf # text/xml # text/xml-external-parsed-entity # video/1d-interleaved-parityfec video/3gpp 3gp # video/3gpp-tt video/3gpp2 3g2 # video/bmpeg # video/bt656 # video/celb # video/dv # video/example video/h261 h261 video/h263 h263 # video/h263-1998 # video/h263-2000 video/h264 h264 # video/h264-rcdo # video/h264-svc video/jpeg jpgv # video/jpeg2000 video/jpm jpm jpgm video/mj2 mj2 mjp2 # video/mp1s # video/mp2p # video/mp2t video/mp4 mp4 mp4v mpg4 # video/mp4v-es video/mpeg mpeg mpg mpe m1v m2v # video/mpeg4-generic # video/mpv # video/nv video/ogg ogv # video/parityfec # video/pointer video/quicktime qt mov # video/raw # video/rtp-enc-aescm128 # video/rtx # video/smpte292m # video/ulpfec # video/vc1 # video/vnd.cctv video/vnd.dece.hd uvh uvvh video/vnd.dece.mobile uvm uvvm # video/vnd.dece.mp4 video/vnd.dece.pd uvp uvvp video/vnd.dece.sd uvs uvvs video/vnd.dece.video uvv uvvv # video/vnd.directv.mpeg # video/vnd.directv.mpeg-tts # video/vnd.dlna.mpeg-tts video/vnd.fvt fvt # video/vnd.hns.video # video/vnd.iptvforum.1dparityfec-1010 # video/vnd.iptvforum.1dparityfec-2005 # video/vnd.iptvforum.2dparityfec-1010 # video/vnd.iptvforum.2dparityfec-2005 # video/vnd.iptvforum.ttsavc # video/vnd.iptvforum.ttsmpeg2 # video/vnd.motorola.video # video/vnd.motorola.videop video/vnd.mpegurl mxu m4u video/vnd.ms-playready.media.pyv pyv # video/vnd.nokia.interleaved-multimedia # video/vnd.nokia.videovoip # video/vnd.objectvideo # video/vnd.sealed.mpeg1 # video/vnd.sealed.mpeg4 # video/vnd.sealed.swf # video/vnd.sealedmedia.softseal.mov video/vnd.uvvu.mp4 uvu uvvu video/vnd.vivo viv video/webm webm video/x-f4v f4v video/x-fli fli video/x-flv flv video/x-m4v m4v video/x-ms-asf asf asx video/x-ms-wm wm video/x-ms-wmv wmv video/x-ms-wmx wmx video/x-ms-wvx wvx video/x-msvideo avi video/x-sgi-movie movie x-conference/x-cooltalk ice PK�������!�[oY��Y����perl5/LWP/Debug/TraceHTTP.pmnu�6$��������package LWP::Debug::TraceHTTP; # Just call: # # require LWP::Debug::TraceHTTP; # LWP::Protocol::implementor('http', 'LWP::Debug::TraceHTTP'); # # to use this module to trace all calls to the HTTP socket object in # programs that use LWP. use strict; use parent 'LWP::Protocol::http'; our $VERSION = '6.77'; package # hide from PAUSE LWP::Debug::TraceHTTP::Socket; use Data::Dump 1.13; use Data::Dump::Trace qw(autowrap mcall); autowrap("LWP::Protocol::http::Socket" => "sock"); sub new { my $class = shift; return mcall("LWP::Protocol::http::Socket" => "new", undef, @_); } 1; PK�������!�,cC���C�����perl5/LWP/DebugFile.pmnu�6$��������package LWP::DebugFile; our $VERSION = '6.77'; # legacy stub 1; PK�������!�K'c(������perl5/LWP/RobotUA.pmnu�6$��������package LWP::RobotUA; use parent qw(LWP::UserAgent); our $VERSION = '6.77'; require WWW::RobotRules; require HTTP::Request; require HTTP::Response; use Carp (); use HTTP::Status (); use HTTP::Date qw(time2str); use strict; # # Additional attributes in addition to those found in LWP::UserAgent: # # $self->{'delay'} Required delay between request to the same # server in minutes. # # $self->{'rules'} A WWW::RobotRules object # sub new { my $class = shift; my %cnf; if (@_ < 4) { # legacy args @cnf{qw(agent from rules)} = @_; } else { %cnf = @_; } Carp::croak('LWP::RobotUA agent required') unless $cnf{agent}; Carp::croak('LWP::RobotUA from address required') unless $cnf{from} && $cnf{from} =~ m/\@/; my $delay = delete $cnf{delay} || 1; my $use_sleep = delete $cnf{use_sleep}; $use_sleep = 1 unless defined($use_sleep); my $rules = delete $cnf{rules}; my $self = LWP::UserAgent->new(%cnf); $self = bless $self, $class; $self->{'delay'} = $delay; # minutes $self->{'use_sleep'} = $use_sleep; if ($rules) { $rules->agent($cnf{agent}); $self->{'rules'} = $rules; } else { $self->{'rules'} = WWW::RobotRules->new($cnf{agent}); } $self; } sub delay { shift->_elem('delay', @_); } sub use_sleep { shift->_elem('use_sleep', @_); } sub agent { my $self = shift; my $old = $self->SUPER::agent(@_); if (@_) { # Changing our name means to start fresh $self->{'rules'}->agent($self->{'agent'}); } $old; } sub rules { my $self = shift; my $old = $self->_elem('rules', @_); $self->{'rules'}->agent($self->{'agent'}) if @_; $old; } sub no_visits { my($self, $netloc) = @_; $self->{'rules'}->no_visits($netloc) || 0; } *host_count = \&no_visits; # backwards compatibility with LWP-5.02 sub host_wait { my($self, $netloc) = @_; return undef unless defined $netloc; my $last = $self->{'rules'}->last_visit($netloc); if ($last) { my $wait = int($self->{'delay'} * 60 - (time - $last)); $wait = 0 if $wait < 0; return $wait; } return 0; } sub simple_request { my($self, $request, $arg, $size) = @_; # Do we try to access a new server? my $allowed = $self->{'rules'}->allowed($request->uri); if ($allowed < 0) { # Host is not visited before, or robots.txt expired; fetch "robots.txt" my $robot_url = $request->uri->clone; $robot_url->path("robots.txt"); $robot_url->query(undef); # make access to robot.txt legal since this will be a recursive call $self->{'rules'}->parse($robot_url, ""); my $robot_req = HTTP::Request->new('GET', $robot_url); my $parse_head = $self->parse_head(0); my $robot_res = $self->request($robot_req); $self->parse_head($parse_head); my $fresh_until = $robot_res->fresh_until; my $content = ""; if ($robot_res->is_success && $robot_res->content_is_text) { $content = $robot_res->decoded_content; $content = "" unless $content && $content =~ /^\s*Disallow\s*:/mi; } $self->{'rules'}->parse($robot_url, $content, $fresh_until); # recalculate allowed... $allowed = $self->{'rules'}->allowed($request->uri); } # Check rules unless ($allowed) { my $res = HTTP::Response->new( HTTP::Status::RC_FORBIDDEN, 'Forbidden by robots.txt'); $res->request( $request ); # bind it to that request return $res; } my $netloc = eval { local $SIG{__DIE__}; $request->uri->host_port; }; my $wait = $self->host_wait($netloc); if ($wait) { if ($self->{'use_sleep'}) { sleep($wait) } else { my $res = HTTP::Response->new( HTTP::Status::RC_SERVICE_UNAVAILABLE, 'Please, slow down'); $res->header('Retry-After', time2str(time + $wait)); $res->request( $request ); # bind it to that request return $res; } } # Perform the request my $res = $self->SUPER::simple_request($request, $arg, $size); $self->{'rules'}->visit($netloc); $res; } sub as_string { my $self = shift; my @s; push(@s, "Robot: $self->{'agent'} operated by $self->{'from'} [$self]"); push(@s, " Minimum delay: " . int($self->{'delay'}*60) . "s"); push(@s, " Will sleep if too early") if $self->{'use_sleep'}; push(@s, " Rules = $self->{'rules'}"); join("\n", @s, ''); } 1; __END__ =pod =head1 NAME LWP::RobotUA - a class for well-behaved Web robots =head1 SYNOPSIS use LWP::RobotUA; my $ua = LWP::RobotUA->new('my-robot/0.1', 'me@foo.com'); $ua->delay(10); # be very nice -- max one hit every ten minutes! ... # Then just use it just like a normal LWP::UserAgent: my $response = $ua->get('http://whatever.int/...'); ... =head1 DESCRIPTION This class implements a user agent that is suitable for robot applications. Robots should be nice to the servers they visit. They should consult the F</robots.txt> file to ensure that they are welcomed and they should not make requests too frequently. But before you consider writing a robot, take a look at L<http://www.robotstxt.org/>. When you use an I<LWP::RobotUA> object as your user agent, then you do not really have to think about these things yourself; C<robots.txt> files are automatically consulted and obeyed, the server isn't queried too rapidly, and so on. Just send requests as you do when you are using a normal I<LWP::UserAgent> object (using C<< $ua->get(...) >>, C<< $ua->head(...) >>, C<< $ua->request(...) >>, etc.), and this special agent will make sure you are nice. =head1 METHODS The LWP::RobotUA is a sub-class of L<LWP::UserAgent> and implements the same methods. In addition the following methods are provided: =head2 new my $ua = LWP::RobotUA->new( %options ) my $ua = LWP::RobotUA->new( $agent, $from ) my $ua = LWP::RobotUA->new( $agent, $from, $rules ) The LWP::UserAgent options C<agent> and C<from> are mandatory. The options C<delay>, C<use_sleep> and C<rules> initialize attributes private to the RobotUA. If C<rules> are not provided, then L<WWW::RobotRules> is instantiated providing an internal database of F<robots.txt>. It is also possible to just pass the value of C<agent>, C<from> and optionally C<rules> as plain positional arguments. =head2 delay my $delay = $ua->delay; $ua->delay( $minutes ); Get/set the minimum delay between requests to the same server, in I<minutes>. The default is C<1> minute. Note that this number doesn't have to be an integer; for example, this sets the delay to C<10> seconds: $ua->delay(10/60); =head2 use_sleep my $bool = $ua->use_sleep; $ua->use_sleep( $boolean ); Get/set a value indicating whether the UA should L<LWP::RobotUA/sleep> if requests arrive too fast, defined as C<< $ua->delay >> minutes not passed since last request to the given server. The default is true. If this value is false then an internal C<SERVICE_UNAVAILABLE> response will be generated. It will have a C<Retry-After> header that indicates when it is OK to send another request to this server. =head2 rules my $rules = $ua->rules; $ua->rules( $rules ); Set/get which I<WWW::RobotRules> object to use. =head2 no_visits my $num = $ua->no_visits( $netloc ) Returns the number of documents fetched from this server host. Yeah I know, this method should probably have been named C<num_visits> or something like that. :-( =head2 host_wait my $num = $ua->host_wait( $netloc ) Returns the number of I<seconds> (from now) you must wait before you can make a new request to this host. =head2 as_string my $string = $ua->as_string; Returns a string that describes the state of the UA. Mainly useful for debugging. =head1 SEE ALSO L<LWP::UserAgent>, L<WWW::RobotRules> =head1 COPYRIGHT Copyright 1996-2004 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK�������!�W?b ��b ����perl5/LWP/Debug.pmnu�6$��������package LWP::Debug; # legacy our $VERSION = '6.77'; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(level trace debug conns); use Carp (); my @levels = qw(trace debug conns); our %current_level = (); sub import { my $pack = shift; my $callpkg = caller(0); my @symbols = (); my @levels = (); for (@_) { if (/^[-+]/) { push(@levels, $_); } else { push(@symbols, $_); } } Exporter::export($pack, $callpkg, @symbols); level(@levels); } sub level { for (@_) { if ($_ eq '+') { # all on # switch on all levels %current_level = map { $_ => 1 } @levels; } elsif ($_ eq '-') { # all off %current_level = (); } elsif (/^([-+])(\w+)$/) { $current_level{$2} = $1 eq '+'; } else { Carp::croak("Illegal level format $_"); } } } sub trace { _log(@_) if $current_level{'trace'}; } sub debug { _log(@_) if $current_level{'debug'}; } sub conns { _log(@_) if $current_level{'conns'}; } sub _log { my $msg = shift; $msg .= "\n" unless $msg =~ /\n$/; # ensure trailing "\n" my ($package, $filename, $line, $sub) = caller(2); print STDERR "$sub: $msg"; } 1; __END__ =pod =head1 NAME LWP::Debug - deprecated =head1 DESCRIPTION This module has been deprecated. Please see L<LWP::ConsoleLogger> for your debugging needs. LWP::Debug is used to provide tracing facilities, but these are not used by LWP any more. The code in this module is kept around (undocumented) so that 3rd party code that happens to use the old interfaces continue to run. One useful feature that LWP::Debug provided (in an imprecise and troublesome way) was network traffic monitoring. The following section provides some hints about recommended replacements. =head2 Network traffic monitoring The best way to monitor the network traffic that LWP generates is to use an external TCP monitoring program. The L<WireShark|http://www.wireshark.org/> program is highly recommended for this. Another approach it to use a debugging HTTP proxy server and make LWP direct all its traffic via this one. Call C<< $ua->proxy >> to set it up and then just use LWP as before. For less precise monitoring needs just setting up a few simple handlers might do. The following example sets up handlers to dump the request and response objects that pass through LWP: use LWP::UserAgent; $ua = LWP::UserAgent->new; $ua->default_header('Accept-Encoding' => scalar HTTP::Message::decodable()); $ua->add_handler("request_send", sub { shift->dump; return }); $ua->add_handler("response_done", sub { shift->dump; return }); $ua->get("http://www.example.com"); =head1 SEE ALSO L<LWP::ConsoleLogger>, L<LWP::ConsoleLogger::Everywhere>, L<LWP::UserAgent> =cut PK�������!�`m@���� ��perl5/LWP/UserAgent/DNS/Hosts.pmnu�6$��������package LWP::UserAgent::DNS::Hosts; use 5.008001; use strict; use warnings; use Carp; use LWP::Protocol; use Scope::Guard qw(guard); our $VERSION = '0.14'; $VERSION = eval $VERSION; our @Protocols = qw(http https); our %Implementors; our %Hosts; sub register_host { my ($class, $host, $peer_addr) = @_; $Hosts{$host} = $peer_addr; } sub register_hosts { my ($class, %pairs) = @_; while (my ($host, $peer_addr) = each %pairs) { $class->register_host($host, $peer_addr); } } sub clear_hosts { %Hosts = (); } sub read_hosts { my ($class, $source) = @_; if (ref $source eq 'GLOB') { $class->_read_hosts_from_handle($source); } elsif ($source !~ /[\x0D\x0A]/ && -f $source) { $class->_read_hosts_from_file($source); } else { $class->_read_hosts_from_string($source); } } sub _read_hosts_from_handle { my ($class, $handle) = @_; while (<$handle>) { chomp; s/^\s+//g; s/\s+$//g; next if !$_ || /^#/; my ($addr, @hosts) = split /\s+/; for my $host (@hosts) { $class->register_host($host, $addr); } } } sub _read_hosts_from_file { my ($class, $file) = @_; open my $fh, '<', $file or croak $!; $class->_read_hosts_from_handle($fh); close $fh; } sub _read_hosts_from_string { my ($class, $string) = @_; open my $fh, '<', \$string or croak $!; $class->_read_hosts_from_handle($fh); close $fh; } sub _registered_peer_addr { my ($class, $host) = @_; return $Hosts{$host}; } sub _implementor { my ($class, $proto) = @_; return sprintf 'LWP::Protocol::%s::hosts' => $proto; } sub enable_override { my $class = shift; for my $proto (@Protocols) { if (my $orig = LWP::Protocol::implementor($proto)) { my $impl = $class->_implementor($proto); if (eval "require $impl; 1") { LWP::Protocol::implementor($proto => $impl); $Implementors{$proto} = $orig; } } else { carp("LWP::Protocol::$proto is unavailable. Skip overriding it."); } } if (defined wantarray) { return guard { $class->disable_override }; } } sub disable_override { my $class = shift; for my $proto (@Protocols) { if (my $impl = $Implementors{$proto}) { LWP::Protocol::implementor($proto, $impl); } } } 1; =encoding utf-8 =for stopwords =head1 NAME LWP::UserAgent::DNS::Hosts - Override LWP HTTP/HTTPS request's host like /etc/hosts =head1 SYNOPSIS use LWP::UserAgent; use LWP::UserAgent::DNS::Hosts; # add entry LWP::UserAgent::DNS::Hosts->register_host( 'www.cpan.org' => '127.0.0.1', ); # add entries LWP::UserAgent::DNS::Hosts->register_hosts( 'search.cpan.org' => '192.168.0.100', 'pause.perl.org' => '192.168.0.101', ); # read hosts file LWP::UserAgent::DNS::Hosts->read_hosts('/path/to/my/hosts'); LWP::UserAgent::DNS::Hosts->enable_override; # override request hosts with peer addr defined above my $ua = LWP::UserAgent->new; my $res = $ua->get("http://www.cpan.org/"); print $res->content; # is same as "http://127.0.0.1/" content =head1 DESCRIPTION LWP::UserAgent::DNS::Hosts is a module to override HTTP/HTTPS request peer addresses that uses LWP::UserAgent. This module concept was got from L<LWP::Protocol::PSGI>. =head1 METHODS =over 4 =item register_host($host, $peer_addr) LWP::UserAgent::DNS::Hosts->register_host($host, $peer_addr); Registers a pair of hostname and peer ip address. # /etc/hosts 127.0.0.1 example.com equals to: LWP::UserAgent::DNS::Hosts->register_hosts('example.com', '127.0.0.1'); =item register_hosts(%host_addr_pairs) LWP::UserAgent::DNS::Hosts->register_hosts( 'example.com' => '192.168.0.1', 'example.org' => '192.168.0.2', ... ); Registers pairs of hostname and peer ip address. =item read_hosts($file_or_string) LWP::UserAgent::DNS::Hosts->read_hosts('hosts.my'); LWP::UserAgent::DNS::Hosts->read_hosts(<<'__HOST__'); 127.0.0.1 example.com 192.168.0.1 example.net example.org __HOST__ Registers "/etc/hosts" syntax entries. =item clear_hosts Clears registered pairs. =item enable_override LWP::UserAgent::DNS::Hosts->enable_override; my $guard = LWP::UserAgent::DNS::Hosts->enable_override; Enables to override hook. If called in a non-void context, returns a L<Guard> object that automatically resets the override when it goes out of context. =item disable_override LWP::UserAgent::DNS::Hosts->disable_override; Disables to override hook. If you use the guard interface described above, it will be automatically called for you. =back =head1 AUTHOR NAKAGAWA Masaki E<lt>masaki@cpan.orgE<gt> =head1 LICENSE This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L<LWP::Protocol>, L<LWP::Protocol::http>, L<LWP::Protocol::https> =cut PK�������!�5mJ��J����perl5/LWP/Protocol/ftp.pmnu�6$��������package LWP::Protocol::ftp; # Implementation of the ftp protocol (RFC 959). We let the Net::FTP # package do all the dirty work. use parent qw(LWP::Protocol); use strict; our $VERSION = '6.77'; use Carp (); use HTTP::Status (); use HTTP::Negotiate (); use HTTP::Response (); use LWP::MediaTypes (); use File::Listing (); { package # hide from PAUSE LWP::Protocol::MyFTP; use strict; use parent qw(Net::FTP); sub new { my $class = shift; my $self = $class->SUPER::new(@_) || return undef; my $mess = $self->message; # welcome message $mess =~ s|\n.*||s; # only first line left $mess =~ s|\s*ready\.?$||; # Make the version number more HTTP like $mess =~ s|\s*\(Version\s*|/| and $mess =~ s|\)$||; ${*$self}{myftp_server} = $mess; #$response->header("Server", $mess); $self; } sub http_server { my $self = shift; ${*$self}{myftp_server}; } sub home { my $self = shift; my $old = ${*$self}{myftp_home}; if (@_) { ${*$self}{myftp_home} = shift; } $old; } sub go_home { my $self = shift; $self->cwd(${*$self}{myftp_home}); } sub request_count { my $self = shift; ++${*$self}{myftp_reqcount}; } sub ping { my $self = shift; return $self->go_home; } } sub _connect { my ($self, $host, $port, $user, $account, $password, $timeout) = @_; my $key; my $conn_cache = $self->{ua}{conn_cache}; if ($conn_cache) { $key = "$host:$port:$user"; $key .= ":$account" if defined($account); if (my $ftp = $conn_cache->withdraw("ftp", $key)) { if ($ftp->ping) { # save it again $conn_cache->deposit("ftp", $key, $ftp); return $ftp; } } } # try to make a connection my $ftp = LWP::Protocol::MyFTP->new( $host, Port => $port, Timeout => $timeout, LocalAddr => $self->{ua}{local_address}, ); # XXX Should be some what to pass on 'Passive' (header??) unless ($ftp) { $@ =~ s/^Net::FTP: //; return HTTP::Response->new(HTTP::Status::RC_INTERNAL_SERVER_ERROR, $@); } unless ($ftp->login($user, $password, $account)) { # Unauthorized. Let's fake a RC_UNAUTHORIZED response my $mess = scalar($ftp->message); $mess =~ s/\n$//; my $res = HTTP::Response->new(HTTP::Status::RC_UNAUTHORIZED, $mess); $res->header("Server", $ftp->http_server); $res->header("WWW-Authenticate", qq(Basic Realm="FTP login")); return $res; } my $home = $ftp->pwd; $ftp->home($home); $conn_cache->deposit("ftp", $key, $ftp) if $conn_cache; return $ftp; } sub request { my ($self, $request, $proxy, $arg, $size, $timeout) = @_; $size = 4096 unless $size; # check proxy if (defined $proxy) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'You can not proxy through the ftp'); } my $url = $request->uri; if ($url->scheme ne 'ftp') { my $scheme = $url->scheme; return HTTP::Response->new(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "LWP::Protocol::ftp::request called for '$scheme'"); } # check method my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD' || $method eq 'PUT') { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'ftp:' URLs"); } my $host = $url->host; my $port = $url->port; my $user = $url->user; my $password = $url->password; # If a basic authorization header is present than we prefer these over # the username/password specified in the URL. { my ($u, $p) = $request->authorization_basic; if (defined $u) { $user = $u; $password = $p; } } # We allow the account to be specified in the "Account" header my $account = $request->header('Account'); my $ftp = $self->_connect($host, $port, $user, $account, $password, $timeout); return $ftp if ref($ftp) eq "HTTP::Response"; # ugh! # Create an initial response object my $response = HTTP::Response->new(HTTP::Status::RC_OK, "OK"); $response->header(Server => $ftp->http_server); $response->header('Client-Request-Num' => $ftp->request_count); $response->request($request); # Get & fix the path my @path = grep {length} $url->path_segments; my $remote_file = pop(@path); $remote_file = '' unless defined $remote_file; my $type; if (ref $remote_file) { my @params; ($remote_file, @params) = @$remote_file; for (@params) { $type = $_ if s/^type=//; } } if ($type && $type eq 'a') { $ftp->ascii; } else { $ftp->binary; } for (@path) { unless ($ftp->cwd($_)) { return HTTP::Response->new(HTTP::Status::RC_NOT_FOUND, "Can't chdir to $_"); } } if ($method eq 'GET' || $method eq 'HEAD') { if (my $mod_time = $ftp->mdtm($remote_file)) { $response->last_modified($mod_time); if (my $ims = $request->if_modified_since) { if ($mod_time <= $ims) { $response->code(HTTP::Status::RC_NOT_MODIFIED); $response->message("Not modified"); return $response; } } } # We'll use this later to abort the transfer if necessary. # if $max_size is defined, we need to abort early. Otherwise, it's # a normal transfer my $max_size = undef; # Set resume location, if the client requested it if ($request->header('Range') && $ftp->supported('REST')) { my $range_info = $request->header('Range'); # Change bytes=2772992-6781209 to just 2772992 my ($start_byte, $end_byte) = $range_info =~ /.*=\s*(\d+)-(\d+)?/; if (defined $start_byte && !defined $end_byte) { # open range -- only the start is specified $ftp->restart($start_byte); # don't define $max_size, we don't want to abort early } elsif (defined $start_byte && defined $end_byte && $start_byte >= 0 && $end_byte >= $start_byte) { $ftp->restart($start_byte); $max_size = $end_byte - $start_byte; } else { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'Incorrect syntax for Range request'); } } elsif ($request->header('Range') && !$ftp->supported('REST')) { return HTTP::Response->new(HTTP::Status::RC_NOT_IMPLEMENTED, "Server does not support resume." ); } my $data; # the data handle if (length($remote_file) and $data = $ftp->retr($remote_file)) { my ($type, @enc) = LWP::MediaTypes::guess_media_type($remote_file); $response->header('Content-Type', $type) if $type; for (@enc) { $response->push_header('Content-Encoding', $_); } my $mess = $ftp->message; if ($mess =~ /\((\d+)\s+bytes\)/) { $response->header('Content-Length', "$1"); } if ($method ne 'HEAD') { # Read data from server $response = $self->collect( $arg, $response, sub { my $content = ''; my $result = $data->read($content, $size); # Stop early if we need to. if (defined $max_size) { # We need an interface to Net::FTP::dataconn for getting # the number of bytes already read my $bytes_received = $data->bytes_read(); # We were already over the limit. (Should only happen # once at the end.) if ($bytes_received - length($content) > $max_size) { $content = ''; } # We just went over the limit elsif ($bytes_received > $max_size) { # Trim content $content = substr($content, 0, $max_size - ($bytes_received - length($content))); } # We're under the limit else { } } return \$content; } ); } # abort is needed for HEAD, it's == close if the transfer has # already completed. unless ($data->abort) { # Something did not work too well. Note that we treat # responses to abort() with code 0 in case of HEAD as ok # (at least wu-ftpd 2.6.1(1) does that). if ($method ne 'HEAD' || $ftp->code != 0) { $response->code(HTTP::Status::RC_INTERNAL_SERVER_ERROR); $response->message("FTP close response: " . $ftp->code . " " . $ftp->message); } } } elsif (!length($remote_file) || ($ftp->code >= 400 && $ftp->code < 600)) { # not a plain file, try to list instead if (length($remote_file) && !$ftp->cwd($remote_file)) { return HTTP::Response->new(HTTP::Status::RC_NOT_FOUND, "File '$remote_file' not found" ); } # It should now be safe to try to list the directory my @lsl = $ftp->dir; # Try to figure out if the user want us to convert the # directory listing to HTML. my @variants = ( ['html', 0.60, 'text/html'], ['dir', 1.00, 'text/ftp-dir-listing'] ); #$HTTP::Negotiate::DEBUG=1; my $prefer = HTTP::Negotiate::choose(\@variants, $request); my $content = ''; if (!defined($prefer)) { return HTTP::Response->new(HTTP::Status::RC_NOT_ACCEPTABLE, "Neither HTML nor directory listing wanted"); } elsif ($prefer eq 'html') { $response->header('Content-Type' => 'text/html'); $content = "<HEAD><TITLE>File Listing\n"; my $base = $request->uri->clone; my $path = $base->path; $base->path("$path/") unless $path =~ m|/$|; $content .= qq(\n\n); $content .= "\n
    \n"; for (File::Listing::parse_dir(\@lsl, 'GMT')) { my ($name, $type, $size, $mtime, $mode) = @$_; $content .= qq(
  • $name); $content .= " $size bytes" if $type eq 'f'; $content .= "\n"; } $content .= "
\n"; } else { $response->header('Content-Type', 'text/ftp-dir-listing'); $content = join("\n", @lsl, ''); } $response->header('Content-Length', length($content)); if ($method ne 'HEAD') { $response = $self->collect_once($arg, $response, $content); } } else { my $res = HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, "FTP return code " . $ftp->code); $res->content_type("text/plain"); $res->content($ftp->message); return $res; } } elsif ($method eq 'PUT') { # method must be PUT unless (length($remote_file)) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, "Must have a file name to PUT to" ); } my $data; if ($data = $ftp->stor($remote_file)) { my $content = $request->content; my $bytes = 0; if (defined $content) { if (ref($content) eq 'SCALAR') { $bytes = $data->write($$content, length($$content)); } elsif (ref($content) eq 'CODE') { my ($buf, $n); while (length($buf = &$content)) { $n = $data->write($buf, length($buf)); last unless $n; $bytes += $n; } } elsif (!ref($content)) { if (defined $content && length($content)) { $bytes = $data->write($content, length($content)); } } else { die "Bad content"; } } $data->close; $response->code(HTTP::Status::RC_CREATED); $response->header('Content-Type', 'text/plain'); $response->content("$bytes bytes stored as $remote_file on $host\n") } else { my $res = HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, "FTP return code " . $ftp->code); $res->content_type("text/plain"); $res->content($ftp->message); return $res; } } else { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, "Illegal method $method"); } $response; } 1; __END__ # This is what RFC 1738 has to say about FTP access: # -------------------------------------------------- # # 3.2. FTP # # The FTP URL scheme is used to designate files and directories on # Internet hosts accessible using the FTP protocol (RFC959). # # A FTP URL follow the syntax described in Section 3.1. If : is # omitted, the port defaults to 21. # # 3.2.1. FTP Name and Password # # A user name and password may be supplied; they are used in the ftp # "USER" and "PASS" commands after first making the connection to the # FTP server. If no user name or password is supplied and one is # requested by the FTP server, the conventions for "anonymous" FTP are # to be used, as follows: # # The user name "anonymous" is supplied. # # The password is supplied as the Internet e-mail address # of the end user accessing the resource. # # If the URL supplies a user name but no password, and the remote # server requests a password, the program interpreting the FTP URL # should request one from the user. # # 3.2.2. FTP url-path # # The url-path of a FTP URL has the following syntax: # # //...//;type= # # Where through and are (possibly encoded) strings # and is one of the characters "a", "i", or "d". The part # ";type=" may be omitted. The and parts may be # empty. The whole url-path may be omitted, including the "/" # delimiting it from the prefix containing user, password, host, and # port. # # The url-path is interpreted as a series of FTP commands as follows: # # Each of the elements is to be supplied, sequentially, as the # argument to a CWD (change working directory) command. # # If the typecode is "d", perform a NLST (name list) command with # as the argument, and interpret the results as a file # directory listing. # # Otherwise, perform a TYPE command with as the argument, # and then access the file whose name is (for example, using # the RETR command.) # # Within a name or CWD component, the characters "/" and ";" are # reserved and must be encoded. The components are decoded prior to # their use in the FTP protocol. In particular, if the appropriate FTP # sequence to access a particular file requires supplying a string # containing a "/" as an argument to a CWD or RETR command, it is # necessary to encode each "/". # # For example, the URL is # interpreted by FTP-ing to "host.dom", logging in as "myname" # (prompting for a password if it is asked for), and then executing # "CWD /etc" and then "RETR motd". This has a different meaning from # which would "CWD etc" and then # "RETR motd"; the initial "CWD" might be executed relative to the # default directory for "myname". On the other hand, # , would "CWD " with a null # argument, then "CWD etc", and then "RETR motd". # # FTP URLs may also be used for other operations; for example, it is # possible to update a file on a remote file server, or infer # information about it from the directory listings. The mechanism for # doing so is not spelled out here. # # 3.2.3. FTP Typecode is Optional # # The entire ;type= part of a FTP URL is optional. If it is # omitted, the client program interpreting the URL must guess the # appropriate mode to use. In general, the data content type of a file # can only be guessed from the name, e.g., from the suffix of the name; # the appropriate type code to be used for transfer of the file can # then be deduced from the data content of the file. # # 3.2.4 Hierarchy # # For some file systems, the "/" used to denote the hierarchical # structure of the URL corresponds to the delimiter used to construct a # file name hierarchy, and thus, the filename will look similar to the # URL path. This does NOT mean that the URL is a Unix filename. # # 3.2.5. Optimization # # Clients accessing resources via FTP may employ additional heuristics # to optimize the interaction. For some FTP servers, for example, it # may be reasonable to keep the control connection open while accessing # multiple URLs from the same server. However, there is no common # hierarchical model to the FTP protocol, so if a directory change # command has been given, it is impossible in general to deduce what # sequence should be given to navigate to another directory for a # second retrieval, if the paths are different. The only reliable # algorithm is to disconnect and reestablish the control connection. PK! perl5/LWP/Protocol/http/hosts.pmnu6$package LWP::Protocol::http::hosts; use strict; use warnings; use parent 'LWP::Protocol::http'; use LWP::UserAgent::DNS::Hosts; sub _extra_sock_opts { my ($self, $host, $port) = @_; my @opts = $self->SUPER::_extra_sock_opts($host, $port); if (my $peer_addr = LWP::UserAgent::DNS::Hosts->_registered_peer_addr($host)) { push @opts, (PeerAddr => $peer_addr, Host => $host); } return @opts; } sub socket_class { 'LWP::Protocol::http::Socket' } 1; __END__ PK!ƻSSperl5/LWP/Protocol/cpan.pmnu6$package LWP::Protocol::cpan; use strict; use parent qw(LWP::Protocol); our $VERSION = '6.77'; require URI; require HTTP::Status; require HTTP::Response; our $CPAN; unless ($CPAN) { # Try to find local CPAN mirror via $CPAN::Config eval { require CPAN::Config; if($CPAN::Config) { my $urls = $CPAN::Config->{urllist}; if (ref($urls) eq "ARRAY") { my $file; for (@$urls) { if (/^file:/) { $file = $_; last; } } if ($file) { $CPAN = $file; } else { $CPAN = $urls->[0]; } } } }; $CPAN ||= "http://cpan.org/"; # last resort } # ensure that we don't chop of last part $CPAN .= "/" unless $CPAN =~ m,/$,; sub request { my($self, $request, $proxy, $arg, $size) = @_; # check proxy if (defined $proxy) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'You can not proxy with cpan'); } # check method my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD') { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'cpan:' URLs"); } my $path = $request->uri->path; $path =~ s,^/,,; my $response = HTTP::Response->new(HTTP::Status::RC_FOUND); $response->header("Location" => URI->new_abs($path, $CPAN)); $response; } 1; PK!eperl5/LWP/Protocol/file.pmnu6$package LWP::Protocol::file; use parent qw(LWP::Protocol); use strict; our $VERSION = '6.77'; require LWP::MediaTypes; require HTTP::Request; require HTTP::Response; require HTTP::Status; require HTTP::Date; sub request { my($self, $request, $proxy, $arg, $size) = @_; $size = 4096 unless defined $size and $size > 0; # check proxy if (defined $proxy) { return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'You can not proxy through the filesystem'); } # check method my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD') { return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'file:' URLs"); } # check url my $url = $request->uri; my $scheme = $url->scheme; if ($scheme ne 'file') { return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "LWP::Protocol::file::request called for '$scheme'"); } # URL OK, look at file my $path = $url->file; # test file exists and is readable unless (-e $path) { return HTTP::Response->new( HTTP::Status::RC_NOT_FOUND, "File `$path' does not exist"); } unless (-r _) { return HTTP::Response->new( HTTP::Status::RC_FORBIDDEN, 'User does not have read permission'); } # looks like file exists my($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$filesize, $atime,$mtime,$ctime,$blksize,$blocks) = stat(_); # XXX should check Accept headers? # check if-modified-since my $ims = $request->header('If-Modified-Since'); if (defined $ims) { my $time = HTTP::Date::str2time($ims); if (defined $time and $time >= $mtime) { return HTTP::Response->new( HTTP::Status::RC_NOT_MODIFIED, "$method $path"); } } # Ok, should be an OK response by now... my $response = HTTP::Response->new( HTTP::Status::RC_OK ); # fill in response headers $response->header('Last-Modified', HTTP::Date::time2str($mtime)); if (-d _) { # If the path is a directory, process it # generate the HTML for directory opendir(D, $path) or return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Cannot read directory '$path': $!"); my(@files) = sort readdir(D); closedir(D); # Make directory listing require URI::Escape; require HTML::Entities; my $pathe = $path . ( $^O eq 'MacOS' ? ':' : '/'); for (@files) { my $furl = URI::Escape::uri_escape($_); if ( -d "$pathe$_" ) { $furl .= '/'; $_ .= '/'; } my $desc = HTML::Entities::encode($_); $_ = qq{
  • $desc}; } # Ensure that the base URL is "/" terminated my $base = $url->clone; unless ($base->path =~ m|/$|) { $base->path($base->path . "/"); } my $html = join("\n", "\n", "Directory $path", "", "\n", "

    Directory listing of $path

    ", "
      ", @files, "
    ", "\n\n"); $response->header('Content-Type', 'text/html'); $response->header('Content-Length', length $html); $html = "" if $method eq "HEAD"; return $self->collect_once($arg, $response, $html); } # path is a regular file $response->header('Content-Length', $filesize); LWP::MediaTypes::guess_media_type($path, $response); # read the file if ($method ne "HEAD") { open(my $fh, '<', $path) or return new HTTP::Response(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Cannot read file '$path': $!"); binmode($fh); $response = $self->collect($arg, $response, sub { my $content = ""; my $bytes = sysread($fh, $content, $size); return \$content if $bytes > 0; return \ ""; }); close($fh); } $response; } 1; PK!7lddperl5/LWP/Protocol/nogo.pmnu6$package LWP::Protocol::nogo; # If you want to disable access to a particular scheme, use this # class and then call # LWP::Protocol::implementor(that_scheme, 'LWP::Protocol::nogo'); # For then on, attempts to access URLs with that scheme will generate # a 500 error. use strict; our $VERSION = '6.77'; require HTTP::Response; require HTTP::Status; use parent qw(LWP::Protocol); sub request { my($self, $request) = @_; my $scheme = $request->uri->scheme; return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Access to \'$scheme\' URIs has been disabled" ); } 1; PK!%Ir88perl5/LWP/Protocol/mailto.pmnu6$package LWP::Protocol::mailto; # This module implements the mailto protocol. It is just a simple # frontend to the Unix sendmail program except on MacOS, where it uses # Mail::Internet. require HTTP::Request; require HTTP::Response; require HTTP::Status; use Carp; use strict; our $VERSION = '6.77'; use parent qw(LWP::Protocol); our $SENDMAIL; unless ($SENDMAIL = $ENV{SENDMAIL}) { for my $sm (qw(/usr/sbin/sendmail /usr/lib/sendmail /usr/ucblib/sendmail )) { if (-x $sm) { $SENDMAIL = $sm; last; } } die "Can't find the 'sendmail' program" unless $SENDMAIL; } sub request { my($self, $request, $proxy, $arg, $size) = @_; my ($mail, $addr) if $^O eq "MacOS"; my @text = () if $^O eq "MacOS"; # check proxy if (defined $proxy) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'You can not proxy with mail'); } # check method my $method = $request->method; if ($method ne 'POST') { return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'mailto:' URLs"); } # check url my $url = $request->uri; my $scheme = $url->scheme; if ($scheme ne 'mailto') { return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "LWP::Protocol::mailto::request called for '$scheme'"); } if ($^O eq "MacOS") { eval { require Mail::Internet; }; if($@) { return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "You don't have MailTools installed"); } unless ($ENV{SMTPHOSTS}) { return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "You don't have SMTPHOSTS defined"); } } else { unless (-x $SENDMAIL) { return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "You don't have $SENDMAIL"); } } if ($^O eq "MacOS") { $mail = Mail::Internet->new or return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Can't get a Mail::Internet object"); } else { open(SENDMAIL, "| $SENDMAIL -oi -t") or return HTTP::Response->new( HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Can't run $SENDMAIL: $!"); } if ($^O eq "MacOS") { $addr = $url->encoded822addr; } else { $request = $request->clone; # we modify a copy my @h = $url->headers; # URL headers override those in the request while (@h) { my $k = shift @h; my $v = shift @h; next unless defined $v; if (lc($k) eq "body") { $request->content($v); } else { $request->push_header($k => $v); } } } if ($^O eq "MacOS") { $mail->add(To => $addr); $mail->add(split(/[:\n]/,$request->headers_as_string)); } else { print SENDMAIL $request->headers_as_string; print SENDMAIL "\n"; } my $content = $request->content; if (defined $content) { my $contRef = ref($content) ? $content : \$content; if (ref($contRef) eq 'SCALAR') { if ($^O eq "MacOS") { @text = split("\n",$$contRef); foreach (@text) { $_ .= "\n"; } } else { print SENDMAIL $$contRef; } } elsif (ref($contRef) eq 'CODE') { # Callback provides data my $d; if ($^O eq "MacOS") { my $stuff = ""; while (length($d = &$contRef)) { $stuff .= $d; } @text = split("\n",$stuff); foreach (@text) { $_ .= "\n"; } } else { print SENDMAIL $d; } } } if ($^O eq "MacOS") { $mail->body(\@text); unless ($mail->smtpsend) { return HTTP::Response->new(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "Mail::Internet->smtpsend unable to send message to <$addr>"); } } else { unless (close(SENDMAIL)) { my $err = $! ? "$!" : "Exit status $?"; return HTTP::Response->new(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "$SENDMAIL: $err"); } } my $response = HTTP::Response->new(HTTP::Status::RC_ACCEPTED, "Mail accepted"); $response->header('Content-Type', 'text/plain'); if ($^O eq "MacOS") { $response->header('Server' => "Mail::Internet $Mail::Internet::VERSION"); $response->content("Message sent to <$addr>\n"); } else { $response->header('Server' => $SENDMAIL); my $to = $request->header("To"); $response->content("Message sent to <$to>\n"); } return $response; } 1; PK!wLLperl5/LWP/Protocol/loopback.pmnu6$package LWP::Protocol::loopback; use strict; our $VERSION = '6.77'; require HTTP::Response; use parent qw(LWP::Protocol); sub request { my($self, $request, $proxy, $arg, $size, $timeout) = @_; my $response = HTTP::Response->new(200, "OK"); $response->content_type("message/http; msgtype=request"); $response->header("Via", "loopback/1.0 $proxy") if $proxy; $response->header("X-Arg", $arg); $response->header("X-Read-Size", $size); $response->header("X-Timeout", $timeout); return $self->collect_once($arg, $response, $request->as_string); } 1; PK!eperl5/LWP/Protocol/https.pmnu6$package LWP::Protocol::https; use strict; use warnings; our $VERSION = '6.14'; use parent qw(LWP::Protocol::http); require Net::HTTPS; sub socket_type { return "https"; } sub _extra_sock_opts { my $self = shift; my %ssl_opts = %{$self->{ua}{ssl_opts} || {}}; if (delete $ssl_opts{verify_hostname}) { $ssl_opts{SSL_verify_mode} ||= 1; $ssl_opts{SSL_verifycn_scheme} = 'www'; } else { if ( $Net::HTTPS::SSL_SOCKET_CLASS eq 'Net::SSL' ) { $ssl_opts{SSL_verifycn_scheme} = ''; } else { $ssl_opts{SSL_verifycn_scheme} = 'none'; } } if ($ssl_opts{SSL_verify_mode}) { unless (exists $ssl_opts{SSL_ca_file} || exists $ssl_opts{SSL_ca_path}) { if ($Net::HTTPS::SSL_SOCKET_CLASS eq 'IO::Socket::SSL' && defined &IO::Socket::SSL::default_ca && IO::Socket::SSL::default_ca() ) { # IO::Socket::SSL has a usable default CA } elsif ( my $cafile = eval { require Mozilla::CA; Mozilla::CA::SSL_ca_file() }) { # use Mozilla::CA $ssl_opts{SSL_ca_file} = $cafile; } else { die <<'EOT'; Can't verify SSL peers without knowing which Certificate Authorities to trust. This problem can be fixed by either setting the PERL_LWP_SSL_CA_FILE environment variable to the file where your trusted CA are, or by installing the Mozilla::CA module for set of commonly trusted CAs. To completely disable the verification that you talk to the correct SSL peer you can set SSL_verify_mode to 0 within ssl_opts. But, if you do this you can't be sure that you communicate with the expected peer. EOT } } } $self->{ssl_opts} = \%ssl_opts; return (%ssl_opts, MultiHomed => 1, $self->SUPER::_extra_sock_opts); } # This is a subclass of LWP::Protocol::http. # That parent class calls ->_check_sock() during the # request method. This allows us to hook in and run checks # sub _check_sock # { # my($self, $req, $sock) = @_; # } sub _get_sock_info { my $self = shift; $self->SUPER::_get_sock_info(@_); my($res, $sock) = @_; if ($sock->can('get_sslversion') and my $sslversion = $sock->get_sslversion) { $res->header("Client-SSL-Version" => $sslversion); } $res->header("Client-SSL-Cipher" => $sock->get_cipher); my $cert = $sock->get_peer_certificate; if ($cert) { $res->header("Client-SSL-Cert-Subject" => $cert->subject_name); $res->header("Client-SSL-Cert-Issuer" => $cert->issuer_name); } if (!$self->{ssl_opts}{SSL_verify_mode}) { $res->push_header("Client-SSL-Warning" => "Peer certificate not verified"); } elsif (!$self->{ssl_opts}{SSL_verifycn_scheme}) { $res->push_header("Client-SSL-Warning" => "Peer hostname match with certificate not verified"); } $res->header("Client-SSL-Socket-Class" => $Net::HTTPS::SSL_SOCKET_CLASS); } # upgrade plain socket to SSL, used for CONNECT tunnel when proxying https # will only work if the underlying socket class of Net::HTTPS is # IO::Socket::SSL, but code will only be called in this case if ( $Net::HTTPS::SSL_SOCKET_CLASS->can('start_SSL')) { *_upgrade_sock = sub { my ($self,$sock,$url) = @_; # SNI should be passed there only if it is not an IP address. # Details: https://github.com/libwww-perl/libwww-perl/issues/449#issuecomment-1896175509 my $host = $url->host() =~ m/:|^[\d.]+$/s ? undef : $url->host(); $sock = LWP::Protocol::https::Socket->start_SSL( $sock, SSL_verifycn_name => $url->host, SSL_hostname => $host, $self->_extra_sock_opts, ); $@ = LWP::Protocol::https::Socket->errstr if ! $sock; return $sock; } } #----------------------------------------------------------- package LWP::Protocol::https::Socket; use parent -norequire, qw(Net::HTTPS LWP::Protocol::http::SocketMethods); 1; __END__ =head1 NAME LWP::Protocol::https - Provide https support for LWP::UserAgent =head1 SYNOPSIS use LWP::UserAgent; $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 }); $res = $ua->get("https://www.example.com"); # specify a CA path $ua = LWP::UserAgent->new( ssl_opts => { SSL_ca_path => '/etc/ssl/certs', verify_hostname => 1, } ); =head1 DESCRIPTION The LWP::Protocol::https module provides support for using https schemed URLs with LWP. This module is a plug-in to the LWP protocol handling, so you don't use it directly. Once the module is installed LWP is able to access sites using HTTP over SSL/TLS. If hostname verification is requested by LWP::UserAgent's C, and neither C nor C is set, then C is implied to be the one provided by L. If the Mozilla::CA module isn't available SSL requests will fail. Either install this module, set up an alternative C or disable hostname verification. This module used to be bundled with the libwww-perl, but it was unbundled in v6.02 in order to be able to declare its dependencies properly for the CPAN tool-chain. Applications that need https support can just declare their dependency on LWP::Protocol::https and will no longer need to know what underlying modules to install. =head1 SEE ALSO L, L, L =head1 COPYRIGHT & LICENSE Copyright (c) 1997-2011 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!Wbbperl5/LWP/Protocol/gopher.pmnu6$package LWP::Protocol::gopher; # Implementation of the gopher protocol (RFC 1436) # # This code is based on 'wwwgopher.pl,v 0.10 1994/10/17 18:12:34 shelden' # which in turn is a vastly modified version of Oscar's http'get() # dated 28/3/94 in # including contributions from Marc van Heyningen and Martijn Koster. use strict; our $VERSION = '6.77'; require HTTP::Response; require HTTP::Status; require IO::Socket; require IO::Select; use parent qw(LWP::Protocol); my %gopher2mimetype = ( '0' => 'text/plain', # 0 file '1' => 'text/html', # 1 menu # 2 CSO phone-book server # 3 Error '4' => 'application/mac-binhex40', # 4 BinHexed Macintosh file '5' => 'application/zip', # 5 DOS binary archive of some sort '6' => 'application/octet-stream', # 6 UNIX uuencoded file. '7' => 'text/html', # 7 Index-Search server # 8 telnet session '9' => 'application/octet-stream', # 9 binary file 'h' => 'text/html', # html 'g' => 'image/gif', # gif 'I' => 'image/*', # some kind of image ); my %gopher2encoding = ( '6' => 'x_uuencode', # 6 UNIX uuencoded file. ); sub request { my($self, $request, $proxy, $arg, $size, $timeout) = @_; $size = 4096 unless $size; # check proxy if (defined $proxy) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'You can not proxy through the gopher'); } my $url = $request->uri; die "bad scheme" if $url->scheme ne 'gopher'; my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD') { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'gopher:' URLs"); } my $gophertype = $url->gopher_type; unless (exists $gopher2mimetype{$gophertype}) { return HTTP::Response->new(HTTP::Status::RC_NOT_IMPLEMENTED, 'Library does not support gophertype ' . $gophertype); } my $response = HTTP::Response->new(HTTP::Status::RC_OK, "OK"); $response->header('Content-type' => $gopher2mimetype{$gophertype} || 'text/plain'); $response->header('Content-Encoding' => $gopher2encoding{$gophertype}) if exists $gopher2encoding{$gophertype}; if ($method eq 'HEAD') { # XXX: don't even try it so we set this header $response->header('Client-Warning' => 'Client answer only'); return $response; } if ($gophertype eq '7' && ! $url->search) { # the url is the prompt for a gopher search; supply boiler-plate return $self->collect_once($arg, $response, <<"EOT"); Gopher Index

    $url
    Gopher Search

    This is a searchable Gopher index. Use the search function of your browser to enter search terms. EOT } my $host = $url->host; my $port = $url->port; my $requestLine = ""; my $selector = $url->selector; if (defined $selector) { $requestLine .= $selector; my $search = $url->search; if (defined $search) { $requestLine .= "\t$search"; my $string = $url->string; if (defined $string) { $requestLine .= "\t$string"; } } } $requestLine .= "\015\012"; # potential request headers are just ignored # Ok, lets make the request my $socket = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $port, LocalAddr => $self->{ua}{local_address}, Proto => 'tcp', Timeout => $timeout); die "Can't connect to $host:$port" unless $socket; my $sel = IO::Select->new($socket); { die "write timeout" if $timeout && !$sel->can_write($timeout); my $n = syswrite($socket, $requestLine, length($requestLine)); die $! unless defined($n); die "short write" if $n != length($requestLine); } my $user_arg = $arg; # must handle menus in a special way since they are to be # converted to HTML. Undefing $arg ensures that the user does # not see the data before we get a change to convert it. $arg = undef if $gophertype eq '1' || $gophertype eq '7'; # collect response my $buf = ''; $response = $self->collect($arg, $response, sub { die "read timeout" if $timeout && !$sel->can_read($timeout); my $n = sysread($socket, $buf, $size); die $! unless defined($n); return \$buf; } ); # Convert menu to HTML and return data to user. if ($gophertype eq '1' || $gophertype eq '7') { my $content = menu2html($response->content); if (defined $user_arg) { $response = $self->collect_once($user_arg, $response, $content); } else { $response->content($content); } } $response; } sub gopher2url { my($gophertype, $path, $host, $port) = @_; my $url; if ($gophertype eq '8' || $gophertype eq 'T') { # telnet session $url = $HTTP::URI_CLASS->new($gophertype eq '8' ? 'telnet:':'tn3270:'); $url->user($path) if defined $path; } else { $path = URI::Escape::uri_escape($path); $url = $HTTP::URI_CLASS->new("gopher:/$gophertype$path"); } $url->host($host); $url->port($port); $url; } sub menu2html { my($menu) = @_; $menu =~ tr/\015//d; # remove carriage return my $tmp = <<"EOT"; Gopher menu

    Gopher menu

    EOT for (split("\n", $menu)) { last if /^\./; my($pretty, $path, $host, $port) = split("\t"); $pretty =~ s/^(.)//; my $type = $1; my $url = gopher2url($type, $path, $host, $port)->as_string; $tmp .= qq{$pretty
    \n}; } $tmp .= "\n\n"; $tmp; } 1; PK!ݹD::perl5/LWP/Protocol/http.pmnu6$package LWP::Protocol::http; use strict; our $VERSION = '6.77'; require HTTP::Response; require HTTP::Status; require Net::HTTP; use parent qw(LWP::Protocol); our @EXTRA_SOCK_OPTS; my $CRLF = "\015\012"; sub _new_socket { my($self, $host, $port, $timeout) = @_; # IPv6 literal IP address should be [bracketed] to remove # ambiguity between ip address and port number. if ( ($host =~ /:/) && ($host !~ /^\[/) ) { $host = "[$host]"; } local($^W) = 0; # IO::Socket::INET can be noisy my $sock = $self->socket_class->new(PeerAddr => $host, PeerPort => $port, LocalAddr => $self->{ua}{local_address}, Proto => 'tcp', Timeout => $timeout, KeepAlive => !!$self->{ua}{conn_cache}, SendTE => $self->{ua}{send_te}, $self->_extra_sock_opts($host, $port), ); unless ($sock) { # IO::Socket::INET leaves additional error messages in $@ my $status = "Can't connect to $host:$port"; if ($@ =~ /\bconnect: (.*)/ || $@ =~ /\b(Bad hostname)\b/ || $@ =~ /\b(nodename nor servname provided, or not known)\b/ || $@ =~ /\b(certificate verify failed)\b/ || $@ =~ /\b(Crypt-SSLeay can't verify hostnames)\b/ ) { $status .= " ($1)"; } elsif ($@) { $status .= " ($@)"; } die "$status\n\n$@"; } $sock->blocking(0); $sock; } sub socket_type { return "http"; } sub socket_class { my $self = shift; (ref($self) || $self) . "::Socket"; } sub _extra_sock_opts # to be overridden by subclass { return @EXTRA_SOCK_OPTS; } sub _check_sock { #my($self, $req, $sock) = @_; } sub _get_sock_info { my($self, $res, $sock) = @_; if (defined(my $peerhost = $sock->peerhost)) { $res->header("Client-Peer" => "$peerhost:" . $sock->peerport); } } sub _fixup_header { my($self, $h, $url, $proxy) = @_; # Extract 'Host' header my $hhost = $url->authority; if ($hhost =~ s/^([^\@]*)\@//) { # get rid of potential "user:pass@" # add authorization header if we need them. HTTP URLs do # not really support specification of user and password, but # we allow it. if (defined($1) && not $h->header('Authorization')) { require URI::Escape; $h->authorization_basic(map URI::Escape::uri_unescape($_), split(":", $1, 2)); } } $h->init_header('Host' => $hhost); if ($proxy && $url->scheme ne 'https') { # Check the proxy URI's userinfo() for proxy credentials # export http_proxy="http://proxyuser:proxypass@proxyhost:port". # For https only the initial CONNECT requests needs authorization. my $p_auth = $proxy->userinfo(); if(defined $p_auth) { require URI::Escape; $h->proxy_authorization_basic(map URI::Escape::uri_unescape($_), split(":", $p_auth, 2)) } } } sub hlist_remove { my($hlist, $k) = @_; $k = lc $k; for (my $i = @$hlist - 2; $i >= 0; $i -= 2) { next unless lc($hlist->[$i]) eq $k; splice(@$hlist, $i, 2); } } sub request { my($self, $request, $proxy, $arg, $size, $timeout) = @_; $size ||= 4096; # check method my $method = $request->method; unless ($method =~ /^[A-Za-z0-9_!\#\$%&\'*+\-.^\`|~]+$/) { # HTTP token return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'http:' URLs"); } my $url = $request->uri; # Proxying SSL with a http proxy needs issues a CONNECT request to build a # tunnel and then upgrades the tunnel to SSL. But when doing keep-alive the # https request does not need to be the first request in the connection, so # we need to distinguish between # - not yet connected (create socket and ssl upgrade) # - connected but not inside ssl tunnel (ssl upgrade) # - inside ssl tunnel to the target - once we are in the tunnel to the # target we cannot only reuse the tunnel for more https requests with the # same target my $ssl_tunnel = $proxy && $url->scheme eq 'https' && $url->host_port(); my ($host,$port) = $proxy ? ($proxy->host,$proxy->port) : ($url->host,$url->port); my $fullpath = $method eq 'CONNECT' ? $url->host_port() : $proxy && ! $ssl_tunnel ? $url->as_string : do { my $path = $url->path_query; $path = "/$path" if $path !~m{^/}; $path }; my $socket; my $conn_cache = $self->{ua}{conn_cache}; my $cache_key; if ( $conn_cache ) { $cache_key = "$host:$port"; # For https we reuse the socket immediately only if it has an established # tunnel to the target. Otherwise a CONNECT request followed by an SSL # upgrade need to be done first. The request itself might reuse an # existing non-ssl connection to the proxy $cache_key .= "!".$ssl_tunnel if $ssl_tunnel; if ( $socket = $conn_cache->withdraw($self->socket_type,$cache_key)) { if ($socket->can_read(0)) { # if the socket is readable, then either the peer has closed the # connection or there are some garbage bytes on it. In either # case we abandon it. $socket->close; $socket = undef; } # else use $socket else { $socket->timeout($timeout); } } } if ( ! $socket && $ssl_tunnel ) { my $proto_https = LWP::Protocol::create('https',$self->{ua}) or die "no support for scheme https found"; # only if ssl socket class is IO::Socket::SSL we can upgrade # a plain socket to SSL. In case of Net::SSL we fall back to # the old version if ( my $upgrade_sub = $proto_https->can('_upgrade_sock')) { my $response = $self->request( HTTP::Request->new('CONNECT',"http://$ssl_tunnel"), $proxy, undef,$size,$timeout ); $response->is_success or die "establishing SSL tunnel failed: ".$response->status_line; $socket = $upgrade_sub->($proto_https, $response->{client_socket},$url) or die "SSL upgrade failed: $@"; } else { $socket = $proto_https->_new_socket($url->host,$url->port,$timeout); } } if ( ! $socket ) { # connect to remote site w/o reusing established socket $socket = $self->_new_socket($host, $port, $timeout ); } my $http_version = ""; if (my $proto = $request->protocol) { if ($proto =~ /^(?:HTTP\/)?(1.\d+)$/) { $http_version = $1; $socket->http_version($http_version); $socket->send_te(0) if $http_version eq "1.0"; } } $self->_check_sock($request, $socket); my @h; my $request_headers = $request->headers->clone; $self->_fixup_header($request_headers, $url, $proxy); $request_headers->scan(sub { my($k, $v) = @_; $k =~ s/^://; $v =~ tr/\n/ /; push(@h, $k, $v); }); my $content_ref = $request->content_ref; $content_ref = $$content_ref if ref($$content_ref); my $chunked; my $has_content; if (ref($content_ref) eq 'CODE') { my $clen = $request_headers->header('Content-Length'); $has_content++ if $clen; unless (defined $clen) { push(@h, "Transfer-Encoding" => "chunked"); $has_content++; $chunked++; } } else { # Set (or override) Content-Length header my $clen = $request_headers->header('Content-Length'); if (defined($$content_ref) && length($$content_ref)) { $has_content = length($$content_ref); if (!defined($clen) || $clen ne $has_content) { if (defined $clen) { warn "Content-Length header value was wrong, fixed"; hlist_remove(\@h, 'Content-Length'); } push(@h, 'Content-Length' => $has_content); } } elsif ($clen) { warn "Content-Length set when there is no content, fixed"; hlist_remove(\@h, 'Content-Length'); } } my $write_wait = 0; $write_wait = 2 if ($request_headers->header("Expect") || "") =~ /100-continue/; my $req_buf = $socket->format_request($method, $fullpath, @h); #print "------\n$req_buf\n------\n"; if (!$has_content || $write_wait || $has_content > 8*1024) { WRITE: { # Since this just writes out the header block it should almost # always succeed to send the whole buffer in a single write call. my $n = $socket->syswrite($req_buf, length($req_buf)); unless (defined $n) { redo WRITE if $!{EINTR}; if ($!{EWOULDBLOCK} || $!{EAGAIN}) { select(undef, undef, undef, 0.1); redo WRITE; } die "write failed: $!"; } if ($n) { substr($req_buf, 0, $n, ""); } else { select(undef, undef, undef, 0.5); } redo WRITE if length $req_buf; } } my($code, $mess, @junk); my $drop_connection; if ($has_content) { my $eof; my $wbuf; my $woffset = 0; INITIAL_READ: if ($write_wait) { # skip filling $wbuf when waiting for 100-continue # because if the response is a redirect or auth required # the request will be cloned and there is no way # to reset the input stream # return here via the label after the 100-continue is read } elsif (ref($content_ref) eq 'CODE') { my $buf = &$content_ref(); $buf = "" unless defined($buf); $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF if $chunked; substr($buf, 0, 0) = $req_buf if $req_buf; $wbuf = \$buf; } else { if ($req_buf) { my $buf = $req_buf . $$content_ref; $wbuf = \$buf; } else { $wbuf = $content_ref; } $eof = 1; } my $fbits = ''; vec($fbits, fileno($socket), 1) = 1; WRITE: while ($write_wait || $woffset < length($$wbuf)) { my $sel_timeout = $timeout; if ($write_wait) { $sel_timeout = $write_wait if $write_wait < $sel_timeout; } my $time_before; $time_before = time if $sel_timeout; my $rbits = $fbits; my $wbits = $write_wait ? undef : $fbits; my $sel_timeout_before = $sel_timeout; SELECT: { my $nfound = select($rbits, $wbits, undef, $sel_timeout); if ($nfound < 0) { if ($!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN}) { if ($time_before) { $sel_timeout = $sel_timeout_before - (time - $time_before); $sel_timeout = 0 if $sel_timeout < 0; } redo SELECT; } die "select failed: $!"; } } if ($write_wait) { $write_wait -= time - $time_before; $write_wait = 0 if $write_wait < 0; } if (defined($rbits) && $rbits =~ /[^\0]/) { # readable my $buf = $socket->_rbuf; my $n = $socket->sysread($buf, 1024, length($buf)); unless (defined $n) { die "read failed: $!" unless $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN}; # if we get here the rest of the block will do nothing # and we will retry the read on the next round } elsif ($n == 0) { # the server closed the connection before we finished # writing all the request content. No need to write any more. $drop_connection++; last WRITE; } $socket->_rbuf($buf); if (!$code && $buf =~ /\015?\012\015?\012/) { # a whole response header is present, so we can read it without blocking ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk, ); if ($code eq "100") { $write_wait = 0; undef($code); goto INITIAL_READ; } else { $drop_connection++; last WRITE; # XXX should perhaps try to abort write in a nice way too } } } if (defined($wbits) && $wbits =~ /[^\0]/) { my $n = $socket->syswrite($$wbuf, length($$wbuf), $woffset); unless (defined $n) { die "write failed: $!" unless $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN}; $n = 0; # will retry write on the next round } elsif ($n == 0) { die "write failed: no bytes written"; } $woffset += $n; if (!$eof && $woffset >= length($$wbuf)) { # need to refill buffer from $content_ref code my $buf = &$content_ref(); $buf = "" unless defined($buf); $eof++ unless length($buf); $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF if $chunked; $wbuf = \$buf; $woffset = 0; } } } # WRITE } ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk) unless $code; ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk) if $code eq "100"; my $response = HTTP::Response->new($code, $mess); my $peer_http_version = $socket->peer_http_version; $response->protocol("HTTP/$peer_http_version"); { local $HTTP::Headers::TRANSLATE_UNDERSCORE; $response->push_header(@h); } $response->push_header("Client-Junk" => \@junk) if @junk; $response->request($request); $self->_get_sock_info($response, $socket); if ($method eq "CONNECT") { $response->{client_socket} = $socket; # so it can be picked up return $response; } if (my @te = $response->remove_header('Transfer-Encoding')) { $response->push_header('Client-Transfer-Encoding', \@te); } $response->push_header('Client-Response-Num', scalar $socket->increment_response_count); my $complete; $response = $self->collect($arg, $response, sub { my $buf = ""; #prevent use of uninitialized value in SSLeay.xs my $n; READ: { $n = $socket->read_entity_body($buf, $size); unless (defined $n) { redo READ if $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN} || $!{ENOTTY}; die "read failed: $!"; } redo READ if $n == -1; } $complete++ if !$n; return \$buf; } ); $drop_connection++ unless $complete; @h = $socket->get_trailers; if (@h) { local $HTTP::Headers::TRANSLATE_UNDERSCORE; $response->push_header(@h); } # keep-alive support unless ($drop_connection) { if ($cache_key) { my %connection = map { (lc($_) => 1) } split(/\s*,\s*/, ($response->header("Connection") || "")); if (($peer_http_version eq "1.1" && !$connection{close}) || $connection{"keep-alive"}) { $conn_cache->deposit($self->socket_type, $cache_key, $socket); } } } $response; } #----------------------------------------------------------- package # hide from PAUSE LWP::Protocol::http::SocketMethods; sub ping { my $self = shift; !$self->can_read(0); } sub increment_response_count { my $self = shift; return ++${*$self}{'myhttp_response_count'}; } #----------------------------------------------------------- package # hide from PAUSE LWP::Protocol::http::Socket; use parent -norequire, qw(LWP::Protocol::http::SocketMethods Net::HTTP); 1; PK!ܯffperl5/LWP/Protocol/nntp.pmnu6$package LWP::Protocol::nntp; # Implementation of the Network News Transfer Protocol (RFC 977) use parent qw(LWP::Protocol); our $VERSION = '6.77'; require HTTP::Response; require HTTP::Status; require Net::NNTP; use strict; sub request { my ($self, $request, $proxy, $arg, $size, $timeout) = @_; $size = 4096 unless $size; # Check for proxy if (defined $proxy) { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'You can not proxy through NNTP'); } # Check that the scheme is as expected my $url = $request->uri; my $scheme = $url->scheme; unless ($scheme eq 'news' || $scheme eq 'nntp') { return HTTP::Response->new(HTTP::Status::RC_INTERNAL_SERVER_ERROR, "LWP::Protocol::nntp::request called for '$scheme'"); } # check for a valid method my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD' || $method eq 'POST') { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for '$scheme:' URLs"); } # extract the identifier and check against posting to an article my $groupart = $url->_group; my $is_art = $groupart =~ /@/; if ($is_art && $method eq 'POST') { return HTTP::Response->new(HTTP::Status::RC_BAD_REQUEST, "Can't post to an article <$groupart>"); } my $nntp = Net::NNTP->new( $url->host, #Port => 18574, Timeout => $timeout, #Debug => 1, ); die "Can't connect to nntp server" unless $nntp; # Check the initial welcome message from the NNTP server if ($nntp->status != 2) { return HTTP::Response->new(HTTP::Status::RC_SERVICE_UNAVAILABLE, $nntp->message); } my $response = HTTP::Response->new(HTTP::Status::RC_OK, "OK"); my $mess = $nntp->message; # Try to extract server name from greeting message. # Don't know if this works well for a large class of servers, but # this works for our server. $mess =~ s/\s+ready\b.*//; $mess =~ s/^\S+\s+//; $response->header(Server => $mess); # First we handle posting of articles if ($method eq 'POST') { $nntp->quit; $nntp = undef; $response->code(HTTP::Status::RC_NOT_IMPLEMENTED); $response->message("POST not implemented yet"); return $response; } # The method must be "GET" or "HEAD" by now if (!$is_art) { if (!$nntp->group($groupart)) { $response->code(HTTP::Status::RC_NOT_FOUND); $response->message($nntp->message); } $nntp->quit; $nntp = undef; # HEAD: just check if the group exists if ($method eq 'GET' && $response->is_success) { $response->code(HTTP::Status::RC_NOT_IMPLEMENTED); $response->message("GET newsgroup not implemented yet"); } return $response; } # Send command to server to retrieve an article (or just the headers) my $get = $method eq 'HEAD' ? "head" : "article"; my $art = $nntp->$get("<$groupart>"); unless ($art) { $nntp->quit; $response->code(HTTP::Status::RC_NOT_FOUND); $response->message($nntp->message); $nntp = undef; return $response; } # Parse headers my ($key, $val); local $_; while ($_ = shift @$art) { if (/^\s+$/) { last; # end of headers } elsif (/^(\S+):\s*(.*)/) { $response->push_header($key, $val) if $key; ($key, $val) = ($1, $2); } elsif (/^\s+(.*)/) { next unless $key; $val .= $1; } else { unshift(@$art, $_); last; } } $response->push_header($key, $val) if $key; # Ensure that there is a Content-Type header $response->header("Content-Type", "text/plain") unless $response->header("Content-Type"); # Collect the body $response = $self->collect_once($arg, $response, join("", @$art)) if @$art; # Say goodbye to the server $nntp->quit; $nntp = undef; $response; } 1; PK!:,}}!perl5/LWP/Protocol/https/hosts.pmnu6$package LWP::Protocol::https::hosts; use strict; use warnings; use parent 'LWP::Protocol::https'; use LWP::UserAgent::DNS::Hosts; sub _extra_sock_opts { my ($self, $host, $port) = @_; my @opts = $self->SUPER::_extra_sock_opts($host, $port); if (my $peer_addr = LWP::UserAgent::DNS::Hosts->_registered_peer_addr($host)) { push @opts, ( PeerAddr => $peer_addr, Host => $host, SSL_verifycn_name => $host, SSL_hostname => $host, # for SNI ); } return @opts; } sub socket_class { 'LWP::Protocol::https::Socket' } 1; __END__ PK!B%perl5/LWP/Protocol/data.pmnu6$package LWP::Protocol::data; # Implements access to data:-URLs as specified in RFC 2397 use strict; our $VERSION = '6.77'; require HTTP::Response; require HTTP::Status; use parent qw(LWP::Protocol); use HTTP::Date qw(time2str); require LWP; # needs version number sub request { my($self, $request, $proxy, $arg, $size) = @_; # check proxy if (defined $proxy) { return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'You can not proxy with data'); } # check method my $method = $request->method; unless ($method eq 'GET' || $method eq 'HEAD') { return HTTP::Response->new( HTTP::Status::RC_BAD_REQUEST, 'Library does not allow method ' . "$method for 'data:' URLs"); } my $url = $request->uri; my $response = HTTP::Response->new( HTTP::Status::RC_OK, "Document follows"); my $media_type = $url->media_type; my $data = $url->data; $response->header('Content-Type' => $media_type, 'Content-Length' => length($data), 'Date' => time2str(time), 'Server' => "libwww-perl-internal/$LWP::VERSION" ); $data = "" if $method eq "HEAD"; return $self->collect_once($arg, $response, $data); } 1; PK!Lv%v%perl5/LWP/Protocol.pmnu6$package LWP::Protocol; use parent 'LWP::MemberMixin'; our $VERSION = '6.77'; use strict; use Carp (); use HTTP::Status (); use HTTP::Response (); use Scalar::Util qw(openhandle); use Try::Tiny qw(try catch); my %ImplementedBy = (); # scheme => classname my %ImplementorAlreadyTested; sub new { my($class, $scheme, $ua) = @_; my $self = bless { scheme => $scheme, ua => $ua, # historical/redundant max_size => $ua->{max_size}, }, $class; $self; } sub create { my($scheme, $ua) = @_; my $impclass = LWP::Protocol::implementor($scheme) or Carp::croak("Protocol scheme '$scheme' is not supported"); # hand-off to scheme specific implementation sub-class my $protocol = $impclass->new($scheme, $ua); return $protocol; } sub implementor { my($scheme, $impclass) = @_; if ($impclass) { $ImplementedBy{$scheme} = $impclass; } my $ic = $ImplementedBy{$scheme}; # module does not exist return $ic if $ic || $ImplementorAlreadyTested{$scheme}; return '' unless $scheme =~ /^([.+\-\w]+)$/; # check valid URL schemes $scheme = $1; # untaint # scheme not yet known, look for a 'use'd implementation $ic = $scheme; $ic =~ tr/.+-/_/; # make it a legal module name $ic = "LWP::Protocol::$ic"; # default location $ic = "LWP::Protocol::nntp" if $scheme eq 'news'; #XXX ugly hack no strict 'refs'; # check we actually have one for the scheme: unless (@{"${ic}::ISA"}) { # try to autoload it try { (my $class = $ic) =~ s{::}{/}g; $class .= '.pm' unless $class =~ /\.pm$/; require $class; } catch { my $error = $_; if ($error =~ /Can't locate/) { $ic = ''; } else { die "$error\n"; } }; } $ImplementedBy{$scheme} = $ic if $ic; $ImplementorAlreadyTested{$scheme} = 1; $ic; } sub request { my($self, $request, $proxy, $arg, $size, $timeout) = @_; Carp::croak('LWP::Protocol::request() needs to be overridden in subclasses'); } # legacy sub timeout { shift->_elem('timeout', @_); } sub max_size { shift->_elem('max_size', @_); } sub collect { my ($self, $arg, $response, $collector) = @_; my $content; my($ua, $max_size) = @{$self}{qw(ua max_size)}; # This can't be moved to Try::Tiny due to the closures within causing # leaks on any version of Perl prior to 5.18. # https://perl5.git.perl.org/perl.git/commitdiff/a0d2bbd5c my $error = do { #catch local $@; local $\; # protect the print below from surprises eval { # try if (!defined($arg) || !$response->is_success) { $response->{default_add_content} = 1; } elsif (defined(openhandle($arg)) || !ref($arg) && length($arg)) { my $existing_fh = defined(openhandle($arg)); my $mode = $existing_fh ? '>&=' : '>'; open(my $fh, $mode, $arg) or die "Can't write to '$arg': $!"; binmode($fh); push(@{$response->{handlers}{response_data}}, { callback => sub { print $fh $_[3] or die "Can't write to '$arg': $!"; 1; }, }); push(@{$response->{handlers}{response_done}}, { callback => sub { unless ($existing_fh) { close($fh) or die "Can't write to '$arg': $!"; } undef($fh); }, }); } elsif (ref($arg) eq 'CODE') { push(@{$response->{handlers}{response_data}}, { callback => sub { &$arg($_[3], $_[0], $self); 1; }, }); } else { die "Unexpected collect argument '$arg'"; } $ua->run_handlers("response_header", $response); if (delete $response->{default_add_content}) { push(@{$response->{handlers}{response_data}}, { callback => sub { $_[0]->add_content($_[3]); 1; }, }); } my $content_size = 0; my $length = $response->content_length; my %skip_h; while ($content = &$collector, length $$content) { for my $h ($ua->handlers("response_data", $response)) { next if $skip_h{$h}; unless ($h->{callback}->($response, $ua, $h, $$content)) { # XXX remove from $response->{handlers}{response_data} if present $skip_h{$h}++; } } $content_size += length($$content); $ua->progress(($length ? ($content_size / $length) : "tick"), $response); if (defined($max_size) && $content_size > $max_size) { $response->push_header("Client-Aborted", "max_size"); last; } } 1; }; $@; }; if ($error) { chomp($error); $response->push_header('X-Died' => $error); $response->push_header("Client-Aborted", "die"); }; delete $response->{handlers}{response_data}; delete $response->{handlers} unless %{$response->{handlers}}; return $response; } sub collect_once { my($self, $arg, $response) = @_; my $content = \ $_[3]; my $first = 1; $self->collect($arg, $response, sub { return $content if $first--; return \ ""; }); } 1; __END__ =pod =head1 NAME LWP::Protocol - Base class for LWP protocols =head1 SYNOPSIS package LWP::Protocol::foo; use parent qw(LWP::Protocol); =head1 DESCRIPTION This class is used as the base class for all protocol implementations supported by the LWP library. When creating an instance of this class using C, and you get an initialized subclass appropriate for that access method. In other words, the L function calls the constructor for one of its subclasses. All derived C classes need to override the C method which is used to service a request. The overridden method can make use of the C method to collect together chunks of data as it is received. =head1 METHODS The following methods and functions are provided: =head2 new my $prot = LWP::Protocol->new(); The LWP::Protocol constructor is inherited by subclasses. As this is a virtual base class this method should B be called directly. =head2 create my $prot = LWP::Protocol::create($scheme) Create an object of the class implementing the protocol to handle the given scheme. This is a function, not a method. It is more an object factory than a constructor. This is the function user agents should use to access protocols. =head2 implementor my $class = LWP::Protocol::implementor($scheme, [$class]) Get and/or set implementor class for a scheme. Returns C<''> if the specified scheme is not supported. =head2 request $response = $protocol->request($request, $proxy, undef); $response = $protocol->request($request, $proxy, '/tmp/sss'); $response = $protocol->request($request, $proxy, \&callback, 1024); $response = $protocol->request($request, $proxy, $fh); Dispatches a request over the protocol, and returns a response object. This method needs to be overridden in subclasses. Refer to L for description of the arguments. =head2 collect my $res = $prot->collect(undef, $response, $collector); # stored in $response my $res = $prot->collect($filename, $response, $collector); my $res = $prot->collect(sub { ... }, $response, $collector); Collect the content of a request, and process it appropriately into a scalar, file, or by calling a callback. If the first parameter is undefined, then the content is stored within the C<$response>. If it's a simple scalar, then it's interpreted as a file name and the content is written to this file. If it's a code reference, then content is passed to this routine. If it is a filehandle, or similar, such as a L object, content will be written to it. The collector is a routine that will be called and which is responsible for returning pieces (as ref to scalar) of the content to process. The C<$collector> signals C by returning a reference to an empty string. The return value is the L object reference. B We will only use the callback or file argument if C<< $response->is_success() >>. This avoids sending content data for redirects and authentication responses to the callback which would be confusing. =head2 collect_once $prot->collect_once($arg, $response, $content) Can be called when the whole response content is available as content. This will invoke L with a collector callback that returns a reference to C<$content> the first time and an empty string the next. =head1 SEE ALSO Inspect the F and F files for examples of usage. =head1 COPYRIGHT Copyright 1995-2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!*(*(perl5/LWP/UserAgent.pmnu6$package LWP::UserAgent; use strict; use parent qw(LWP::MemberMixin); use Carp (); use File::Copy (); use HTTP::Request (); use HTTP::Response (); use HTTP::Date (); use LWP (); use HTTP::Status (); use LWP::Protocol (); use Module::Load qw( load ); use Scalar::Util qw(blessed openhandle); use Try::Tiny qw(try catch); our $VERSION = '6.77'; sub new { # Check for common user mistake Carp::croak("Options to LWP::UserAgent should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH'; my($class, %cnf) = @_; my $agent = delete $cnf{agent}; my $from = delete $cnf{from}; my $def_headers = delete $cnf{default_headers}; my $timeout = delete $cnf{timeout}; $timeout = 3*60 unless defined $timeout; my $local_address = delete $cnf{local_address}; my $ssl_opts = delete $cnf{ssl_opts} || {}; unless (exists $ssl_opts->{verify_hostname}) { # The processing of HTTPS_CA_* below is for compatibility with Crypt::SSLeay if (exists $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}) { $ssl_opts->{verify_hostname} = $ENV{PERL_LWP_SSL_VERIFY_HOSTNAME}; } elsif ($ENV{HTTPS_CA_FILE} || $ENV{HTTPS_CA_DIR}) { # Crypt-SSLeay compatibility (verify peer certificate; but not the hostname) $ssl_opts->{verify_hostname} = 0; $ssl_opts->{SSL_verify_mode} = 1; } else { $ssl_opts->{verify_hostname} = 1; } } unless (exists $ssl_opts->{SSL_ca_file}) { if (my $ca_file = $ENV{PERL_LWP_SSL_CA_FILE} || $ENV{HTTPS_CA_FILE}) { $ssl_opts->{SSL_ca_file} = $ca_file; } } unless (exists $ssl_opts->{SSL_ca_path}) { if (my $ca_path = $ENV{PERL_LWP_SSL_CA_PATH} || $ENV{HTTPS_CA_DIR}) { $ssl_opts->{SSL_ca_path} = $ca_path; } } my $use_eval = delete $cnf{use_eval}; $use_eval = 1 unless defined $use_eval; my $parse_head = delete $cnf{parse_head}; $parse_head = 1 unless defined $parse_head; my $send_te = delete $cnf{send_te}; $send_te = 1 unless defined $send_te; my $show_progress = delete $cnf{show_progress}; my $max_size = delete $cnf{max_size}; my $max_redirect = delete $cnf{max_redirect}; $max_redirect = 7 unless defined $max_redirect; my $env_proxy = exists $cnf{env_proxy} ? delete $cnf{env_proxy} : $ENV{PERL_LWP_ENV_PROXY}; my $no_proxy = exists $cnf{no_proxy} ? delete $cnf{no_proxy} : []; Carp::croak(qq{no_proxy must be an arrayref, not $no_proxy!}) if ref $no_proxy ne 'ARRAY'; my $cookie_jar = delete $cnf{cookie_jar}; my $conn_cache = delete $cnf{conn_cache}; my $keep_alive = delete $cnf{keep_alive}; Carp::croak("Can't mix conn_cache and keep_alive") if $conn_cache && $keep_alive; my $protocols_allowed = delete $cnf{protocols_allowed}; my $protocols_forbidden = delete $cnf{protocols_forbidden}; my $requests_redirectable = delete $cnf{requests_redirectable}; $requests_redirectable = ['GET', 'HEAD'] unless defined $requests_redirectable; my $cookie_jar_class = delete $cnf{cookie_jar_class}; $cookie_jar_class = 'HTTP::Cookies' unless defined $cookie_jar_class; # Actually ""s are just as good as 0's, but for concision we'll just say: Carp::croak("protocols_allowed has to be an arrayref or 0, not \"$protocols_allowed\"!") if $protocols_allowed and ref($protocols_allowed) ne 'ARRAY'; Carp::croak("protocols_forbidden has to be an arrayref or 0, not \"$protocols_forbidden\"!") if $protocols_forbidden and ref($protocols_forbidden) ne 'ARRAY'; Carp::croak("requests_redirectable has to be an arrayref or 0, not \"$requests_redirectable\"!") if $requests_redirectable and ref($requests_redirectable) ne 'ARRAY'; if (%cnf && $^W) { Carp::carp("Unrecognized LWP::UserAgent options: @{[sort keys %cnf]}"); } my $self = bless { def_headers => $def_headers, timeout => $timeout, local_address => $local_address, ssl_opts => $ssl_opts, use_eval => $use_eval, show_progress => $show_progress, max_size => $max_size, max_redirect => $max_redirect, # We set proxy later as we do validation on the values proxy => {}, no_proxy => [ @{ $no_proxy } ], protocols_allowed => $protocols_allowed, protocols_forbidden => $protocols_forbidden, requests_redirectable => $requests_redirectable, send_te => $send_te, cookie_jar_class => $cookie_jar_class, }, $class; $self->agent(defined($agent) ? $agent : $class->_agent) if defined($agent) || !$def_headers || !$def_headers->header("User-Agent"); $self->from($from) if $from; $self->cookie_jar($cookie_jar) if $cookie_jar; $self->parse_head($parse_head); $self->env_proxy if $env_proxy; if (exists $cnf{proxy}) { Carp::croak(qq{proxy must be an arrayref, not $cnf{proxy}!}) if ref $cnf{proxy} ne 'ARRAY'; $self->proxy($cnf{proxy}); } $self->protocols_allowed( $protocols_allowed ) if $protocols_allowed; $self->protocols_forbidden($protocols_forbidden) if $protocols_forbidden; if ($keep_alive) { $conn_cache ||= { total_capacity => $keep_alive }; } $self->conn_cache($conn_cache) if $conn_cache; return $self; } sub send_request { my($self, $request, $arg, $size) = @_; my($method, $url) = ($request->method, $request->uri); my $scheme = $url->scheme; local($SIG{__DIE__}); # protect against user defined die handlers $self->progress("begin", $request); my $response = $self->run_handlers("request_send", $request); unless ($response) { my $protocol; { # Honor object-specific restrictions by forcing protocol objects # into class LWP::Protocol::nogo. my $x; if($x = $self->protocols_allowed) { if (grep lc($_) eq $scheme, @$x) { } else { require LWP::Protocol::nogo; $protocol = LWP::Protocol::nogo->new; } } elsif ($x = $self->protocols_forbidden) { if(grep lc($_) eq $scheme, @$x) { require LWP::Protocol::nogo; $protocol = LWP::Protocol::nogo->new; } } # else fall thru and create the protocol object normally } # Locate protocol to use my $proxy = $request->{proxy}; if ($proxy) { $scheme = $proxy->scheme; } unless ($protocol) { try { $protocol = LWP::Protocol::create($scheme, $self); } catch { my $error = $_; $error =~ s/ at .* line \d+.*//s; # remove file/line number $response = _new_response($request, HTTP::Status::RC_NOT_IMPLEMENTED, $error); if ($scheme eq "https") { $response->message($response->message . " (LWP::Protocol::https not installed)"); $response->content_type("text/plain"); $response->content(<{use_eval}) { # we eval, and turn dies into responses below try { $response = $protocol->request($request, $proxy, $arg, $size, $self->{timeout}) || die "No response returned by $protocol"; } catch { my $error = $_; if (blessed($error) && $error->isa("HTTP::Response")) { $response = $error; $response->request($request); } else { my $full = $error; (my $status = $error) =~ s/\n.*//s; $status =~ s/ at .* line \d+.*//s; # remove file/line number my $code = ($status =~ s/^(\d\d\d)\s+//) ? $1 : HTTP::Status::RC_INTERNAL_SERVER_ERROR; $response = _new_response($request, $code, $status, $full); } }; } elsif (!$response) { $response = $protocol->request($request, $proxy, $arg, $size, $self->{timeout}); # XXX: Should we die unless $response->is_success ??? } } $response->request($request); # record request for reference $response->header("Client-Date" => HTTP::Date::time2str(time)); $self->run_handlers("response_done", $response); $self->progress("end", $response); return $response; } sub prepare_request { my($self, $request) = @_; die "Method missing" unless $request->method; my $url = $request->uri; die "URL missing" unless $url; die "URL must be absolute" unless $url->scheme; $self->run_handlers("request_preprepare", $request); if (my $def_headers = $self->{def_headers}) { for my $h ($def_headers->header_field_names) { $request->init_header($h => [$def_headers->header($h)]); } } $self->run_handlers("request_prepare", $request); return $request; } sub simple_request { my($self, $request, $arg, $size) = @_; # sanity check the request passed in if (defined $request) { if (ref $request) { Carp::croak("You need a request object, not a " . ref($request) . " object") if ref($request) eq 'ARRAY' or ref($request) eq 'HASH' or !$request->can('method') or !$request->can('uri'); } else { Carp::croak("You need a request object, not '$request'"); } } else { Carp::croak("No request object passed in"); } my $error; try { $request = $self->prepare_request($request); } catch { $error = $_; $error =~ s/ at .* line \d+.*//s; # remove file/line number }; if ($error) { return _new_response($request, HTTP::Status::RC_BAD_REQUEST, $error); } return $self->send_request($request, $arg, $size); } sub request { my ($self, $request, $arg, $size, $previous) = @_; my $response = $self->simple_request($request, $arg, $size); $response->previous($previous) if $previous; if ($response->redirects >= $self->{max_redirect}) { if ($response->header('Location')) { $response->header("Client-Warning" => "Redirect loop detected (max_redirect = $self->{max_redirect})" ); } return $response; } if (my $req = $self->run_handlers("response_redirect", $response)) { return $self->request($req, $arg, $size, $response); } my $code = $response->code; if ( $code == HTTP::Status::RC_MOVED_PERMANENTLY or $code == HTTP::Status::RC_FOUND or $code == HTTP::Status::RC_SEE_OTHER or $code == HTTP::Status::RC_TEMPORARY_REDIRECT or $code == HTTP::Status::RC_PERMANENT_REDIRECT) { my $referral = $request->clone; # These headers should never be forwarded $referral->remove_header('Host', 'Cookie'); if ( $referral->header('Referer') && $request->uri->scheme eq 'https' && $referral->uri->scheme eq 'http') { # RFC 2616, section 15.1.3. # https -> http redirect, suppressing Referer $referral->remove_header('Referer'); } if ( $code == HTTP::Status::RC_SEE_OTHER || $code == HTTP::Status::RC_FOUND) { my $method = uc($referral->method); unless ($method eq "GET" || $method eq "HEAD") { $referral->method("GET"); $referral->content(""); $referral->remove_content_headers; } } # And then we update the URL based on the Location:-header. my $referral_uri = $response->header('Location'); { # Some servers erroneously return a relative URL for redirects, # so make it absolute if it not already is. local $URI::ABS_ALLOW_RELATIVE_SCHEME = 1; my $base = $response->base; $referral_uri = "" unless defined $referral_uri; $referral_uri = $HTTP::URI_CLASS->new($referral_uri, $base)->abs($base); } $referral->uri($referral_uri); return $response unless $self->redirect_ok($referral, $response); return $self->request($referral, $arg, $size, $response); } elsif ($code == HTTP::Status::RC_UNAUTHORIZED || $code == HTTP::Status::RC_PROXY_AUTHENTICATION_REQUIRED) { my $proxy = ($code == HTTP::Status::RC_PROXY_AUTHENTICATION_REQUIRED); my $ch_header = $proxy || $request->method eq 'CONNECT' ? "Proxy-Authenticate" : "WWW-Authenticate"; my @challenges = $response->header($ch_header); unless (@challenges) { $response->header( "Client-Warning" => "Missing Authenticate header"); return $response; } require HTTP::Headers::Util; CHALLENGE: for my $challenge (@challenges) { $challenge =~ tr/,/;/; # "," is used to separate auth-params!! ($challenge) = HTTP::Headers::Util::split_header_words($challenge); my $scheme = shift(@$challenge); shift(@$challenge); # no value $challenge = {@$challenge}; # make rest into a hash unless ($scheme =~ /^([a-z]+(?:-[a-z]+)*)$/) { $response->header( "Client-Warning" => "Bad authentication scheme '$scheme'"); return $response; } $scheme = $1; # untainted now my $class = "LWP::Authen::\u$scheme"; $class =~ tr/-/_/; no strict 'refs'; unless (%{"$class\::"}) { # try to load it my $error; try { (my $req = $class) =~ s{::}{/}g; $req .= '.pm' unless $req =~ /\.pm$/; require $req; } catch { $error = $_; }; if ($error) { if ($error =~ /^Can\'t locate/) { $response->header("Client-Warning" => "Unsupported authentication scheme '$scheme'"); } else { $response->header("Client-Warning" => $error); } next CHALLENGE; } } unless ($class->can("authenticate")) { $response->header("Client-Warning" => "Unsupported authentication scheme '$scheme'"); next CHALLENGE; } my $re = $class->authenticate($self, $proxy, $challenge, $response, $request, $arg, $size); next CHALLENGE if $re->code == HTTP::Status::RC_UNAUTHORIZED; return $re; } return $response; } return $response; } # # Now the shortcuts... # sub get { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters,1); return $self->request( HTTP::Request::Common::GET( @parameters ), @suff ); } sub _maybe_copy_default_content_type { my $self = shift; my $req = shift; my $default_ct = $self->default_header('Content-Type'); return unless defined $default_ct; # drop url shift; # adapted from HTTP::Request::Common::request_type_with_data my $content; $content = shift if @_ and ref $_[0]; # We only care about the final value, really my $ct; my ($k, $v); while (($k, $v) = splice(@_, 0, 2)) { if (lc($k) eq 'content') { $content = $v; } elsif (lc($k) eq 'content-type') { $ct = $v; } } # Content-type provided and truthy? skip return if $ct; # Content is not just a string? Then it must be x-www-form-urlencoded return if defined $content && ref($content); # Provide default $req->header('Content-Type' => $default_ct); } sub post { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1)); my $req = HTTP::Request::Common::POST(@parameters); $self->_maybe_copy_default_content_type($req, @parameters); return $self->request($req, @suff); } sub head { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters,1); return $self->request( HTTP::Request::Common::HEAD( @parameters ), @suff ); } sub patch { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1)); # this work-around is in place as HTTP::Request::Common # did not implement a patch convenience method until # version 6.12. Once we can bump the prereq to at least # that version, we can use ::PATCH instead of this hack my $req = HTTP::Request::Common::PUT(@parameters); $req->method('PATCH'); $self->_maybe_copy_default_content_type($req, @parameters); return $self->request($req, @suff); } sub put { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters, (ref($parameters[1]) ? 2 : 1)); my $req = HTTP::Request::Common::PUT(@parameters); $self->_maybe_copy_default_content_type($req, @parameters); return $self->request($req, @suff); } sub delete { require HTTP::Request::Common; my($self, @parameters) = @_; my @suff = $self->_process_colonic_headers(\@parameters,1); return $self->request( HTTP::Request::Common::DELETE( @parameters ), @suff ); } sub _process_colonic_headers { # Process :content_cb / :content_file / :read_size_hint headers. my($self, $args, $start_index) = @_; my($arg, $size); for(my $i = $start_index; $i < @$args; $i += 2) { next unless defined $args->[$i]; #printf "Considering %s => %s\n", $args->[$i], $args->[$i + 1]; if($args->[$i] eq ':content_cb') { # Some sanity-checking... $arg = $args->[$i + 1]; Carp::croak("A :content_cb value can't be undef") unless defined $arg; Carp::croak("A :content_cb value must be a coderef") unless ref $arg and UNIVERSAL::isa($arg, 'CODE'); } elsif ($args->[$i] eq ':content_file') { $arg = $args->[$i + 1]; # Some sanity-checking... Carp::croak("A :content_file value can't be undef") unless defined $arg; unless ( defined openhandle($arg) ) { Carp::croak("A :content_file value can't be a reference") if ref $arg; Carp::croak("A :content_file value can't be \"\"") unless length $arg; } } elsif ($args->[$i] eq ':read_size_hint') { $size = $args->[$i + 1]; # Bother checking it? } else { next; } splice @$args, $i, 2; $i -= 2; } # And return a suitable suffix-list for request(REQ,...) return unless defined $arg; return $arg, $size if defined $size; return $arg; } sub is_online { my $self = shift; return 1 if $self->get("http://www.msftncsi.com/ncsi.txt")->content eq "Microsoft NCSI"; return 1 if $self->get("http://www.apple.com")->content =~ m,Apple,; return 0; } my @ANI = qw(- \ | /); sub progress { my($self, $status, $m) = @_; return unless $self->{show_progress}; local($,, $\); if ($status eq "begin") { print STDERR "** ", $m->method, " ", $m->uri, " ==> "; $self->{progress_start} = time; $self->{progress_lastp} = ""; $self->{progress_ani} = 0; } elsif ($status eq "end") { delete $self->{progress_lastp}; delete $self->{progress_ani}; print STDERR $m->status_line; my $t = time - delete $self->{progress_start}; print STDERR " (${t}s)" if $t; print STDERR "\n"; } elsif ($status eq "tick") { print STDERR "$ANI[$self->{progress_ani}++]\b"; $self->{progress_ani} %= @ANI; } else { my $p = sprintf "%3.0f%%", $status * 100; return if $p eq $self->{progress_lastp}; print STDERR "$p\b\b\b\b"; $self->{progress_lastp} = $p; } STDERR->flush; } # # This whole allow/forbid thing is based on man 1 at's way of doing things. # sub is_protocol_supported { my($self, $scheme) = @_; if (ref $scheme) { # assume we got a reference to an URI object $scheme = $scheme->scheme; } else { Carp::croak("Illegal scheme '$scheme' passed to is_protocol_supported") if $scheme =~ /\W/; $scheme = lc $scheme; } my $x; if(ref($self) and $x = $self->protocols_allowed) { return 0 unless grep lc($_) eq $scheme, @$x; } elsif (ref($self) and $x = $self->protocols_forbidden) { return 0 if grep lc($_) eq $scheme, @$x; } local($SIG{__DIE__}); # protect against user defined die handlers $x = LWP::Protocol::implementor($scheme); return 1 if $x and $x ne 'LWP::Protocol::nogo'; return 0; } sub protocols_allowed { shift->_elem('protocols_allowed' , @_) } sub protocols_forbidden { shift->_elem('protocols_forbidden' , @_) } sub requests_redirectable { shift->_elem('requests_redirectable', @_) } sub redirect_ok { # RFC 2616, section 10.3.2 and 10.3.3 say: # If the 30[12] status code is received in response to a request other # than GET or HEAD, the user agent MUST NOT automatically redirect the # request unless it can be confirmed by the user, since this might # change the conditions under which the request was issued. # Note that this routine used to be just: # return 0 if $_[1]->method eq "POST"; return 1; my($self, $new_request, $response) = @_; my $method = $response->request->method; return 0 unless grep $_ eq $method, @{ $self->requests_redirectable || [] }; if ($new_request->uri->scheme eq 'file') { $response->header("Client-Warning" => "Can't redirect to a file:// URL!"); return 0; } # Otherwise it's apparently okay... return 1; } sub credentials { my $self = shift; my $netloc = lc(shift || ''); my $realm = shift || ""; my $old = $self->{basic_authentication}{$netloc}{$realm}; if (@_) { $self->{basic_authentication}{$netloc}{$realm} = [@_]; } return unless $old; return @$old if wantarray; return join(":", @$old); } sub get_basic_credentials { my($self, $realm, $uri, $proxy) = @_; return if $proxy; return $self->credentials($uri->host_port, $realm); } sub timeout { my $self = shift; my $old = $self->{timeout}; if (@_) { $self->{timeout} = shift; if (my $conn_cache = $self->conn_cache) { for my $conn ($conn_cache->get_connections) { $conn->timeout($self->{timeout}); } } } return $old; } sub local_address{ shift->_elem('local_address',@_); } sub max_size { shift->_elem('max_size', @_); } sub max_redirect { shift->_elem('max_redirect', @_); } sub show_progress{ shift->_elem('show_progress', @_); } sub send_te { shift->_elem('send_te', @_); } sub ssl_opts { my $self = shift; if (@_ == 1) { my $k = shift; return $self->{ssl_opts}{$k}; } if (@_) { my $old; while (@_) { my($k, $v) = splice(@_, 0, 2); $old = $self->{ssl_opts}{$k} unless @_; if (defined $v) { $self->{ssl_opts}{$k} = $v; } else { delete $self->{ssl_opts}{$k}; } } %{$self->{ssl_opts}} = (%{$self->{ssl_opts}}, @_); return $old; } my @opts= sort keys %{$self->{ssl_opts}}; return @opts; } sub parse_head { my $self = shift; if (@_) { my $flag = shift; my $parser; my $old = $self->set_my_handler("response_header", $flag ? sub { my($response, $ua) = @_; require HTML::HeadParser; $parser = HTML::HeadParser->new; $parser->xml_mode(1) if $response->content_is_xhtml; $parser->utf8_mode(1) if $HTML::Parser::VERSION >= 3.40; push(@{$response->{handlers}{response_data}}, { callback => sub { return unless $parser; unless ($parser->parse($_[3])) { my $h = $parser->header; my $r = $_[0]; for my $f ($h->header_field_names) { $r->init_header($f, [$h->header($f)]); } undef($parser); } }, }); } : undef, m_media_type => "html", ); return !!$old; } else { return !!$self->get_my_handler("response_header"); } } sub cookie_jar { my $self = shift; my $old = $self->{cookie_jar}; return $old unless @_; my $jar = shift; if (ref($jar) eq "HASH") { my $class = $self->{cookie_jar_class}; try { load($class); $jar = $class->new(%$jar); } catch { my $error = $_; if ($error =~ /Can't locate/) { die "cookie_jar_class '$class' not found\n"; } else { die "$error\n"; } }; } $self->{cookie_jar} = $jar; $self->set_my_handler("request_prepare", $jar ? sub { return if $_[0]->header("Cookie"); $jar->add_cookie_header($_[0]); } : undef, ); $self->set_my_handler("response_done", $jar ? sub { $jar->extract_cookies($_[0]); } : undef, ); return $old; } sub default_headers { my $self = shift; my $old = $self->{def_headers} ||= HTTP::Headers->new; if (@_) { Carp::croak("default_headers not set to HTTP::Headers compatible object") unless @_ == 1 && $_[0]->can("header_field_names"); $self->{def_headers} = shift; } return $old; } sub default_header { my $self = shift; return $self->default_headers->header(@_); } sub _agent { "libwww-perl/$VERSION" } sub agent { my $self = shift; if (@_) { my $agent = shift; if ($agent) { $agent .= $self->_agent if $agent =~ /\s+$/; } else { undef($agent) } return $self->default_header("User-Agent", $agent); } return $self->default_header("User-Agent"); } sub from { # legacy my $self = shift; return $self->default_header("From", @_); } sub conn_cache { my $self = shift; my $old = $self->{conn_cache}; if (@_) { my $cache = shift; if ( ref($cache) eq "HASH" ) { require LWP::ConnCache; $cache = LWP::ConnCache->new(%$cache); } elsif ( defined $cache) { for my $conn ( $cache->get_connections ) { $conn->timeout( $self->timeout ); } } $self->{conn_cache} = $cache; } return $old; } sub add_handler { my($self, $phase, $cb, %spec) = @_; $spec{line} ||= join(":", (caller)[1,2]); my $conf = $self->{handlers}{$phase} ||= do { require HTTP::Config; HTTP::Config->new; }; $conf->add(%spec, callback => $cb); } sub set_my_handler { my($self, $phase, $cb, %spec) = @_; $spec{owner} = (caller(1))[3] unless exists $spec{owner}; $self->remove_handler($phase, %spec); $spec{line} ||= join(":", (caller)[1,2]); $self->add_handler($phase, $cb, %spec) if $cb; } sub get_my_handler { my $self = shift; my $phase = shift; my $init = pop if @_ % 2; my %spec = @_; my $conf = $self->{handlers}{$phase}; unless ($conf) { return unless $init; require HTTP::Config; $conf = $self->{handlers}{$phase} = HTTP::Config->new; } $spec{owner} = (caller(1))[3] unless exists $spec{owner}; my @h = $conf->find(%spec); if (!@h && $init) { if (ref($init) eq "CODE") { $init->(\%spec); } elsif (ref($init) eq "HASH") { $spec{$_}= $init->{$_} for keys %$init; } $spec{callback} ||= sub {}; $spec{line} ||= join(":", (caller)[1,2]); $conf->add(\%spec); return \%spec; } return wantarray ? @h : $h[0]; } sub remove_handler { my($self, $phase, %spec) = @_; if ($phase) { my $conf = $self->{handlers}{$phase} || return; my @h = $conf->remove(%spec); delete $self->{handlers}{$phase} if $conf->empty; return @h; } return unless $self->{handlers}; return map $self->remove_handler($_), sort keys %{$self->{handlers}}; } sub handlers { my($self, $phase, $o) = @_; my @h; if ($o->{handlers} && $o->{handlers}{$phase}) { push(@h, @{$o->{handlers}{$phase}}); } if (my $conf = $self->{handlers}{$phase}) { push(@h, $conf->matching($o)); } return @h; } sub run_handlers { my($self, $phase, $o) = @_; # here we pass $_[2] to the callbacks, instead of $o, so that they # can assign to it; e.g. request_prepare is documented to allow # that if (defined(wantarray)) { for my $h ($self->handlers($phase, $o)) { my $ret = $h->{callback}->($_[2], $self, $h); return $ret if $ret; } return undef; } for my $h ($self->handlers($phase, $o)) { $h->{callback}->($_[2], $self, $h); } } # deprecated sub use_eval { shift->_elem('use_eval', @_); } sub use_alarm { Carp::carp("LWP::UserAgent->use_alarm(BOOL) is a no-op") if @_ > 1 && $^W; ""; } sub clone { my $self = shift; my $copy = bless { %$self }, ref $self; # copy most fields delete $copy->{handlers}; delete $copy->{conn_cache}; # copy any plain arrays and hashes; known not to need recursive copy for my $k (qw(proxy no_proxy requests_redirectable ssl_opts)) { next unless $copy->{$k}; if (ref($copy->{$k}) eq "ARRAY") { $copy->{$k} = [ @{$copy->{$k}} ]; } elsif (ref($copy->{$k}) eq "HASH") { $copy->{$k} = { %{$copy->{$k}} }; } } if ($self->{def_headers}) { $copy->{def_headers} = $self->{def_headers}->clone; } # re-enable standard handlers $copy->parse_head($self->parse_head); # no easy way to clone the cookie jar; so let's just remove it for now $copy->cookie_jar(undef); $copy; } sub mirror { my($self, $url, $file) = @_; die "Local file name is missing" unless defined $file && length $file; my $request = HTTP::Request->new('GET', $url); # If the file exists, add a cache-related header if ( -e $file ) { my ($mtime) = ( stat($file) )[9]; if ($mtime) { $request->header( 'If-Modified-Since' => HTTP::Date::time2str($mtime) ); } } require File::Temp; my ($tmpfh, $tmpfile) = File::Temp::tempfile("$file-XXXXXX"); close($tmpfh) or die "Could not close tmpfile '$tmpfile': $!"; my $response = $self->request($request, $tmpfile); if ( $response->header('X-Died') ) { unlink($tmpfile); die $response->header('X-Died'); } # Only fetching a fresh copy of the file would be considered success. # If the file was not modified, "304" would returned, which # is considered by HTTP::Status to be a "redirect", /not/ "success" if ( $response->is_success ) { my @stat = stat($tmpfile) or die "Could not stat tmpfile '$tmpfile': $!"; my $file_length = $stat[7]; my ($content_length) = $response->header('Content-length'); if ( defined $content_length and $file_length < $content_length ) { unlink($tmpfile); die "Transfer truncated: only $file_length out of $content_length bytes received\n"; } elsif ( defined $content_length and $file_length > $content_length ) { unlink($tmpfile); die "Content-length mismatch: expected $content_length bytes, got $file_length\n"; } # The file was the expected length. else { # Replace the stale file with a fresh copy # File::Copy will attempt to do it atomically, # and fall back to a delete + copy if that fails. File::Copy::move( $tmpfile, $file ) or die "Cannot rename '$tmpfile' to '$file': $!\n"; # Set standard file permissions if umask is supported. # If not, leave what File::Temp created in effect. if ( defined(my $umask = umask()) ) { my $mode = 0666 &~ $umask; chmod $mode, $file or die sprintf("Cannot chmod %o '%s': %s\n", $mode, $file, $!); } # make sure the file has the same last modification time if ( my $lm = $response->last_modified ) { utime $lm, $lm, $file or warn "Cannot update modification time of '$file': $!\n"; } } } # The local copy is fresh enough, so just delete the temp file else { unlink($tmpfile); } return $response; } sub _need_proxy { my($req, $ua) = @_; return if exists $req->{proxy}; my $proxy = $ua->{proxy}{$req->uri->scheme} || return; if ($ua->{no_proxy}) { if (my $host = eval { $req->uri->host }) { for my $domain (@{$ua->{no_proxy}}) { $domain =~ s/^\.//; return if $host =~ /(?:^|\.)\Q$domain\E$/; } } } $req->{proxy} = $HTTP::URI_CLASS->new($proxy); } sub proxy { my $self = shift; my $key = shift; if (!@_ && ref $key eq 'ARRAY') { die 'odd number of items in proxy arrayref!' unless @{$key} % 2 == 0; # This map reads the elements of $key 2 at a time return map { $self->proxy($key->[2 * $_], $key->[2 * $_ + 1]) } (0 .. @{$key} / 2 - 1); } return map { $self->proxy($_, @_) } @$key if ref $key; Carp::croak("'$key' is not a valid URI scheme") unless $key =~ /^$URI::scheme_re\z/; my $old = $self->{'proxy'}{$key}; if (@_) { my $url = shift; if (defined($url) && length($url)) { Carp::croak("Proxy must be specified as absolute URI; '$url' is not") unless $url =~ /^$URI::scheme_re:/; Carp::croak("Bad http proxy specification '$url'") if $url =~ /^https?:/ && $url !~ m,^https?://[\w[],; } $self->{proxy}{$key} = $url; $self->set_my_handler("request_preprepare", \&_need_proxy) } return $old; } sub env_proxy { my ($self) = @_; require Encode; require Encode::Locale; my $env_request_method= $ENV{REQUEST_METHOD}; my %seen; foreach my $k (sort keys %ENV) { my $real_key= $k; my $v= $ENV{$k} or next; if ( $env_request_method ) { # Need to be careful when called in the CGI environment, as # the HTTP_PROXY variable is under control of that other guy. next if $k =~ /^HTTP_/; $k = "HTTP_PROXY" if $k eq "CGI_HTTP_PROXY"; } $k = lc($k); if (my $from_key= $seen{$k}) { warn "Environment contains multiple differing definitions for '$k'.\n". "Using value from '$from_key' ($ENV{$from_key}) and ignoring '$real_key' ($v)" if $v ne $ENV{$from_key}; next; } else { $seen{$k}= $real_key; } next unless $k =~ /^(.*)_proxy$/; $k = $1; if ($k eq 'no') { $self->no_proxy(split(/\s*,\s*/, $v)); } else { # Ignore random _proxy variables, allow only valid schemes next unless $k =~ /^$URI::scheme_re\z/; # Ignore xxx_proxy variables if xxx isn't a supported protocol next unless LWP::Protocol::implementor($k); $self->proxy($k, Encode::decode(locale => $v)); } } } sub no_proxy { my($self, @no) = @_; if (@no) { push(@{ $self->{'no_proxy'} }, @no); } else { $self->{'no_proxy'} = []; } } sub _new_response { my($request, $code, $message, $content) = @_; $message ||= HTTP::Status::status_message($code); my $response = HTTP::Response->new($code, $message); $response->request($request); $response->header("Client-Date" => HTTP::Date::time2str(time)); $response->header("Client-Warning" => "Internal response"); $response->header("Content-Type" => "text/plain"); $response->content($content || "$code $message\n"); return $response; } 1; __END__ =pod =head1 NAME LWP::UserAgent - Web user agent class =head1 SYNOPSIS use strict; use warnings; use LWP::UserAgent (); my $ua = LWP::UserAgent->new(timeout => 10); $ua->env_proxy; my $response = $ua->get('http://example.com'); if ($response->is_success) { print $response->decoded_content; } else { die $response->status_line; } Extra layers of security (note the C and C): use strict; use warnings; use HTTP::CookieJar::LWP (); use LWP::UserAgent (); my $jar = HTTP::CookieJar::LWP->new; my $ua = LWP::UserAgent->new( cookie_jar => $jar, protocols_allowed => ['http', 'https'], timeout => 10, ); $ua->env_proxy; my $response = $ua->get('http://example.com'); if ($response->is_success) { print $response->decoded_content; } else { die $response->status_line; } =head1 DESCRIPTION The L is a class implementing a web user agent. L objects can be used to dispatch web requests. In normal use the application creates an L object, and then configures it with values for timeouts, proxies, name, etc. It then creates an instance of L for the request that needs to be performed. This request is then passed to one of the request method the UserAgent, which dispatches it using the relevant protocol, and returns a L object. There are convenience methods for sending the most common request types: L, L, L, L and L. When using these methods, the creation of the request object is hidden as shown in the synopsis above. The basic approach of the library is to use HTTP-style communication for all protocol schemes. This means that you will construct L objects and receive L objects even for non-HTTP resources like I and I. In order to achieve even more similarity to HTTP-style communications, I menus and file directories are converted to HTML documents. =head1 CONSTRUCTOR METHODS The following constructor methods are available: =head2 clone my $ua2 = $ua->clone; Returns a copy of the L object. B: Please be aware that the clone method does not copy or clone your C attribute. Due to the limited restrictions on what can be used for your cookie jar, there is no way to clone the attribute. The C attribute will be C in the new object instance. =head2 new my $ua = LWP::UserAgent->new( %options ) This method constructs a new L object and returns it. Key/value pair arguments may be provided to set up the initial state. The following options correspond to attribute methods described below: KEY DEFAULT ----------- -------------------- agent "libwww-perl/#.###" conn_cache undef cookie_jar undef cookie_jar_class HTTP::Cookies default_headers HTTP::Headers->new from undef local_address undef max_redirect 7 max_size undef no_proxy [] parse_head 1 protocols_allowed undef protocols_forbidden undef proxy {} requests_redirectable ['GET', 'HEAD'] send_te 1 show_progress undef ssl_opts { verify_hostname => 1 } timeout 180 The following additional options are also accepted: If the C option is passed in with a true value, then proxy settings are read from environment variables (see L). If C isn't provided, the C environment variable controls if L is called during initialization. If the C option value is defined and non-zero, then an C is set up (see L). The C value is passed on as the C for the connection cache. C must be set as an arrayref of key/value pairs. C takes an arrayref of domains. =head1 ATTRIBUTES The settings of the configuration attributes modify the behaviour of the L when it dispatches requests. Most of these can also be initialized by options passed to the constructor method. The following attribute methods are provided. The attribute value is left unchanged if no argument is given. The return value from each method is the old attribute value. =head2 agent my $agent = $ua->agent; $ua->agent('Checkbot/0.4 '); # append the default to the end $ua->agent('Mozilla/5.0'); $ua->agent(""); # don't identify Get/set the product token that is used to identify the user agent on the network. The agent value is sent as the C header in the requests. The default is a string of the form C, where C<#.###> is substituted with the version number of this library. If the provided string ends with space, the default C string is appended to it. The user agent string should be one or more simple product identifiers with an optional version number separated by the C character. =head2 conn_cache my $cache_obj = $ua->conn_cache; $ua->conn_cache( $cache_obj ); Get/set the L object to use. See L for details. =head2 cookie_jar my $jar = $ua->cookie_jar; $ua->cookie_jar( $cookie_jar_obj ); Get/set the cookie jar object to use. The only requirement is that the cookie jar object must implement the C and C methods. These methods will then be invoked by the user agent as requests are sent and responses are received. Normally this will be a L object or some subclass. You are, however, encouraged to use L instead. See L for more information. use HTTP::CookieJar::LWP (); my $jar = HTTP::CookieJar::LWP->new; my $ua = LWP::UserAgent->new( cookie_jar => $jar ); # or after object creation $ua->cookie_jar( $cookie_jar ); The default is to have no cookie jar, i.e. never automatically add C headers to the requests. If C<$jar> contains an unblessed hash reference, a new cookie jar object is created for you automatically. The object is of the class set with the C constructor argument, which defaults to L. $ua->cookie_jar({ file => "$ENV{HOME}/.cookies.txt" }); is really just a shortcut for: require HTTP::Cookies; $ua->cookie_jar(HTTP::Cookies->new(file => "$ENV{HOME}/.cookies.txt")); As described above and in L, you should set C to C<"HTTP::CookieJar::LWP"> to get a safer cookie jar. my $ua = LWP::UserAgent->new( cookie_jar_class => 'HTTP::CookieJar::LWP' ); $ua->cookie_jar({}); # HTTP::CookieJar::LWP takes no args These can also be combined into the constructor, so a jar is created at instantiation. my $ua = LWP::UserAgent->new( cookie_jar_class => 'HTTP::CookieJar::LWP', cookie_jar => {}, ); =head2 credentials my $creds = $ua->credentials(); $ua->credentials( $netloc, $realm ); $ua->credentials( $netloc, $realm, $uname, $pass ); $ua->credentials("www.example.com:80", "Some Realm", "foo", "secret"); Get/set the user name and password to be used for a realm. The C<$netloc> is a string of the form C<< : >>. The username and password will only be passed to this server. =head2 default_header $ua->default_header( $field ); $ua->default_header( $field => $value ); $ua->default_header('Accept-Encoding' => scalar HTTP::Message::decodable()); $ua->default_header('Accept-Language' => "no, en"); This is just a shortcut for C<< $ua->default_headers->header( $field => $value ) >>. =head2 default_headers my $headers = $ua->default_headers; $ua->default_headers( $headers_obj ); Get/set the headers object that will provide default header values for any requests sent. By default this will be an empty L object. =head2 from my $from = $ua->from; $ua->from('foo@bar.com'); Get/set the email address for the human user who controls the requesting user agent. The address should be machine-usable, as defined in L. The C value is sent as the C header in the requests. The default is to not send a C header. See L for the more general interface that allow any header to be defaulted. =head2 local_address my $address = $ua->local_address; $ua->local_address( $address ); Get/set the local interface to bind to for network connections. The interface can be specified as a hostname or an IP address. This value is passed as the C argument to L. =head2 max_redirect my $max = $ua->max_redirect; $ua->max_redirect( $n ); This reads or sets the object's limit of how many times it will obey redirection responses in a given request cycle. By default, the value is C<7>. This means that if you call L and the response is a redirect elsewhere which is in turn a redirect, and so on seven times, then LWP gives up after that seventh request. =head2 max_size my $size = $ua->max_size; $ua->max_size( $bytes ); Get/set the size limit for response content. The default is C, which means that there is no limit. If the returned response content is only partial, because the size limit was exceeded, then a C header will be added to the response. The content might end up longer than C as we abort once appending a chunk of data makes the length exceed the limit. The C header, if present, will indicate the length of the full content and will normally not be the same as C<< length($res->content) >>. =head2 parse_head my $bool = $ua->parse_head; $ua->parse_head( $boolean ); Get/set a value indicating whether we should initialize response headers from the Ehead> section of HTML documents. The default is true. I unless you know what you are doing. =head2 protocols_allowed my $aref = $ua->protocols_allowed; # get allowed protocols $ua->protocols_allowed( \@protocols ); # allow ONLY these $ua->protocols_allowed(undef); # delete the list $ua->protocols_allowed(['http',]); # ONLY allow http By default, an object has neither a C list, nor a L list. This reads (or sets) this user agent's list of protocols that the request methods will exclusively allow. The protocol names are case insensitive. For example: C<< $ua->protocols_allowed( [ 'http', 'https'] ); >> means that this user agent will I those protocols, and attempts to use this user agent to access URLs with any other schemes (like C) will result in a 500 error. Note that having a C list causes any L list to be ignored. =head2 protocols_forbidden my $aref = $ua->protocols_forbidden; # get the forbidden list $ua->protocols_forbidden(\@protocols); # do not allow these $ua->protocols_forbidden(['http',]); # All http reqs get a 500 $ua->protocols_forbidden(undef); # delete the list This reads (or sets) this user agent's list of protocols that the request method will I allow. The protocol names are case insensitive. For example: C<< $ua->protocols_forbidden( [ 'file', 'mailto'] ); >> means that this user agent will I allow those protocols, and attempts to use this user agent to access URLs with those schemes will result in a 500 error. =head2 requests_redirectable my $aref = $ua->requests_redirectable; $ua->requests_redirectable( \@requests ); $ua->requests_redirectable(['GET', 'HEAD',]); # the default This reads or sets the object's list of request names that L will allow redirection for. By default, this is C<['GET', 'HEAD']>, as per L. To change to include C, consider: push @{ $ua->requests_redirectable }, 'POST'; =head2 send_te my $bool = $ua->send_te; $ua->send_te( $boolean ); If true, will send a C header along with the request. The default is true. Set it to false to disable the C header for systems who can't handle it. =head2 show_progress my $bool = $ua->show_progress; $ua->show_progress( $boolean ); Get/set a value indicating whether a progress bar should be displayed on the terminal as requests are processed. The default is false. =head2 ssl_opts my @keys = $ua->ssl_opts; my $val = $ua->ssl_opts( $key ); $ua->ssl_opts( $key => $value ); Get/set the options for SSL connections. Without argument return the list of options keys currently set. With a single argument return the current value for the given option. With 2 arguments set the option value and return the old. Setting an option to the value C removes this option. The options that LWP relates to are: =over =item C => $bool When TRUE LWP will for secure protocol schemes ensure it connects to servers that have a valid certificate matching the expected hostname. If FALSE no checks are made and you can't be sure that you communicate with the expected peer. The no checks behaviour was the default for libwww-perl-5.837 and earlier releases. This option is initialized from the C environment variable. If this environment variable isn't set; then C defaults to 1. Please note that that recently the overall effect of this option with regards to SSL handling has changed. As of version 6.11 of L, which is an external module, SSL certificate verification was harmonized to behave in sync with L. With this change, setting this option no longer disables all SSL certificate verification, only the hostname checks. To disable all verification, use the C option in the C attribute. For example: C<$ua->ssl_opts(SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE);> =item C => $path The path to a file containing Certificate Authority certificates. A default setting for this option is provided by checking the environment variables C and C in order. =item C => $path The path to a directory containing files containing Certificate Authority certificates. A default setting for this option is provided by checking the environment variables C and C in order. =back Other options can be set and are processed directly by the SSL Socket implementation in use. See L or L for details. The libwww-perl core no longer bundles protocol plugins for SSL. You will need to install L separately to enable support for processing https-URLs. =head2 timeout my $secs = $ua->timeout; $ua->timeout( $secs ); Get/set the timeout value in seconds. The default value is 180 seconds, i.e. 3 minutes. The request is aborted if no activity on the connection to the server is observed for C seconds. This means that the time it takes for the complete transaction and the L method to actually return might be longer. When a request times out, a response object is still returned. The response will have a standard HTTP Status Code (500). This response will have the "Client-Warning" header set to the value of "Internal response". See the L method description below for further details. =head1 PROXY ATTRIBUTES The following methods set up when requests should be passed via a proxy server. =head2 env_proxy $ua->env_proxy; Load proxy settings from C<*_proxy> environment variables. You might specify proxies like this (sh-syntax): gopher_proxy=http://proxy.my.place/ wais_proxy=http://proxy.my.place/ no_proxy="localhost,example.com" export gopher_proxy wais_proxy no_proxy csh or tcsh users should use the C command to define these environment variables. On systems with case insensitive environment variables there exists a name clash between the CGI environment variables and the C environment variable normally picked up by C. Because of this C is not honored for CGI scripts. The C environment variable can be used instead. =head2 no_proxy $ua->no_proxy( @domains ); $ua->no_proxy('localhost', 'example.com'); $ua->no_proxy(); # clear the list Do not proxy requests to the given domains, including subdomains. Calling C without any domains clears the list of domains. =head2 proxy $ua->proxy(\@schemes, $proxy_url) $ua->proxy(['http', 'ftp'], 'http://proxy.sn.no:8001/'); # For a single scheme: $ua->proxy($scheme, $proxy_url) $ua->proxy('gopher', 'http://proxy.sn.no:8001/'); # To set multiple proxies at once: $ua->proxy([ ftp => 'http://ftp.example.com:8001/', [ 'http', 'https' ] => 'http://http.example.com:8001/', ]); Set/retrieve proxy URL for a scheme. The first form specifies that the URL is to be used as a proxy for access methods listed in the list in the first method argument, i.e. C and C. The second form shows a shorthand form for specifying proxy URL for a single access scheme. The third form demonstrates setting multiple proxies at once. This is also the only form accepted by the constructor. =head1 HANDLERS Handlers are code that injected at various phases during the processing of requests. The following methods are provided to manage the active handlers: =head2 add_handler $ua->add_handler( $phase => \&cb, %matchspec ) Add handler to be invoked in the given processing phase. For how to specify C<%matchspec> see L. The possible values C<$phase> and the corresponding callback signatures are as follows. Note that the handlers are documented in the order in which they will be run, which is: request_preprepare request_prepare request_send response_header response_data response_done response_redirect =over =item request_preprepare => sub { my($request, $ua, $handler) = @_; ... } The handler is called before the C and other standard initialization of the request. This can be used to set up headers and attributes that the C handler depends on. Proxy initialization should take place here; but in general don't register handlers for this phase. =item request_prepare => sub { my($request, $ua, $handler) = @_; ... } The handler is called before the request is sent and can modify the request any way it see fit. This can for instance be used to add certain headers to specific requests. The method can assign a new request object to C<$_[0]> to replace the request that is sent fully. The return value from the callback is ignored. If an exception is raised it will abort the request and make the request method return a "400 Bad request" response. =item request_send => sub { my($request, $ua, $handler) = @_; ... } This handler gets a chance of handling requests before they're sent to the protocol handlers. It should return an L object if it wishes to terminate the processing; otherwise it should return nothing. The C and C handlers will not be invoked for this response, but the C will be. =item response_header => sub { my($response, $ua, $handler) = @_; ... } This handler is called right after the response headers have been received, but before any content data. The handler might set up handlers for data and might croak to abort the request. The handler might set the C<< $response->{default_add_content} >> value to control if any received data should be added to the response object directly. This will initially be false if the C<< $ua->request() >> method was called with a C<$content_file> or C<$content_cb argument>; otherwise true. =item response_data => sub { my($response, $ua, $handler, $data) = @_; ... } This handler is called for each chunk of data received for the response. The handler might croak to abort the request. This handler needs to return a TRUE value to be called again for subsequent chunks for the same request. =item response_done => sub { my($response, $ua, $handler) = @_; ... } The handler is called after the response has been fully received, but before any redirect handling is attempted. The handler can be used to extract information or modify the response. =item response_redirect => sub { my($response, $ua, $handler) = @_; ... } The handler is called in C<< $ua->request >> after C. If the handler returns an L object we'll start over with processing this request instead. =back For all of these, C<$handler> is a code reference to the handler that is currently being run. =head2 get_my_handler $ua->get_my_handler( $phase, %matchspec ); $ua->get_my_handler( $phase, %matchspec, $init ); Will retrieve the matching handler as hash ref. If C<$init> is passed as a true value, create and add the handler if it's not found. If C<$init> is a subroutine reference, then it's called with the created handler hash as argument. This sub might populate the hash with extra fields; especially the callback. If C<$init> is a hash reference, merge the hashes. =head2 handlers $ua->handlers( $phase, $request ) $ua->handlers( $phase, $response ) Returns the handlers that apply to the given request or response at the given processing phase. =head2 remove_handler $ua->remove_handler( undef, %matchspec ); $ua->remove_handler( $phase, %matchspec ); $ua->remove_handler(); # REMOVE ALL HANDLERS IN ALL PHASES Remove handlers that match the given C<%matchspec>. If C<$phase> is not provided, remove handlers from all phases. Be careful as calling this function with C<%matchspec> that is not specific enough can remove handlers not owned by you. It's probably better to use the L method instead. The removed handlers are returned. =head2 set_my_handler $ua->set_my_handler( $phase, $cb, %matchspec ); $ua->set_my_handler($phase, undef); # remove handler for phase Set handlers private to the executing subroutine. Works by defaulting an C field to the C<%matchspec> that holds the name of the called subroutine. You might pass an explicit C to override this. If C<$cb> is passed as C, remove the handler. =head1 REQUEST METHODS The methods described in this section are used to dispatch requests via the user agent. The following request methods are provided: =head2 delete my $res = $ua->delete( $url ); my $res = $ua->delete( $url, $field_name => $value, ... ); This method will dispatch a C request on the given URL. Additional headers and content options are the same as for the L method. This method will use the C function from L to build the request. See L for a details on how to pass form content and other advanced features. =head2 get my $res = $ua->get( $url ); my $res = $ua->get( $url , $field_name => $value, ... ); This method will dispatch a C request on the given URL. Further arguments can be given to initialize the headers of the request. These are given as separate name/value pairs. The return value is a response object. See L for a description of the interface it provides. There will still be a response object returned when LWP can't connect to the server specified in the URL or when other failures in protocol handlers occur. These internal responses use the standard HTTP status codes, so the responses can't be differentiated by testing the response status code alone. Error responses that LWP generates internally will have the "Client-Warning" header set to the value "Internal response". If you need to differentiate these internal responses from responses that a remote server actually generates, you need to test this header value. Fields names that start with ":" are special. These will not initialize headers of the request but will determine how the response content is treated. The following special field names are recognized: ':content_file' => $filename # or $filehandle ':content_cb' => \&callback ':read_size_hint' => $bytes If a C<$filename> or C<$filehandle> is provided with the C<:content_file> option, then the response content will be saved here instead of in the response object. The C<$filehandle> may also be an object with an open file descriptor, such as a L object. If a callback is provided with the C<:content_cb> option then this function will be called for each chunk of the response content as it is received from the server. If neither of these options are given, then the response content will accumulate in the response object itself. This might not be suitable for very large response bodies. Only one of C<:content_file> or C<:content_cb> can be specified. The content of unsuccessful responses will always accumulate in the response object itself, regardless of the C<:content_file> or C<:content_cb> options passed in. Note that errors writing to the content file (for example due to permission denied or the filesystem being full) will be reported via the C or C response headers, and not the C method. The C<:read_size_hint> option is passed to the protocol module which will try to read data from the server in chunks of this size. A smaller value for the C<:read_size_hint> will result in a higher number of callback invocations. The callback function is called with 3 arguments: a chunk of data, a reference to the response object, and a reference to the protocol object. The callback can abort the request by invoking C. The exception message will show up as the "X-Died" header field in the response returned by the C<< $ua->get() >> method. =head2 head my $res = $ua->head( $url ); my $res = $ua->head( $url , $field_name => $value, ... ); This method will dispatch a C request on the given URL. Otherwise it works like the L method described above. =head2 is_protocol_supported my $bool = $ua->is_protocol_supported( $scheme ); You can use this method to test whether this user agent object supports the specified C. (The C might be a string (like C or C) or it might be an L object reference.) Whether a scheme is supported is determined by the user agent's C or C lists (if any), and by the capabilities of LWP. I.e., this will return true only if LWP supports this protocol I it's permitted for this particular object. =head2 is_online my $bool = $ua->is_online; Tries to determine if you have access to the Internet. Returns C<1> (true) if the built-in heuristics determine that the user agent is able to access the Internet (over HTTP) or C<0> (false). See also L. =head2 mirror my $res = $ua->mirror( $url, $filename ); This method will get the document identified by URL and store it in file called C<$filename>. If the file already exists, then the request will contain an C header matching the modification time of the file. If the document on the server has not changed since this time, then nothing happens. If the document has been updated, it will be downloaded again. The modification time of the file will be forced to match that of the server. Uses L to attempt to atomically replace the C<$filename>. The return value is an L object. =head2 patch # Any version of HTTP::Message works with this form: my $res = $ua->patch( $url, $field_name => $value, Content => $content ); # Using hash or array references requires HTTP::Message >= 6.12 use HTTP::Request 6.12; my $res = $ua->patch( $url, \%form ); my $res = $ua->patch( $url, \@form ); my $res = $ua->patch( $url, \%form, $field_name => $value, ... ); my $res = $ua->patch( $url, $field_name => $value, Content => \%form ); my $res = $ua->patch( $url, $field_name => $value, Content => \@form ); This method will dispatch a C request on the given URL, with C<%form> or C<@form> providing the key/value pairs for the fill-in form content. Additional headers and content options are the same as for the L method. CAVEAT: This method can only accept content that is in key-value pairs when using L prior to version C<6.12>. Any use of hash or array references will result in an error prior to version C<6.12>. This method will use the C function from L to build the request. See L for a details on how to pass form content and other advanced features. =head2 post my $res = $ua->post( $url, \%form ); my $res = $ua->post( $url, \@form ); my $res = $ua->post( $url, \%form, $field_name => $value, ... ); my $res = $ua->post( $url, $field_name => $value, Content => \%form ); my $res = $ua->post( $url, $field_name => $value, Content => \@form ); my $res = $ua->post( $url, $field_name => $value, Content => $content ); This method will dispatch a C request on the given URL, with C<%form> or C<@form> providing the key/value pairs for the fill-in form content. Additional headers and content options are the same as for the L method. This method will use the C function from L to build the request. See L for a details on how to pass form content and other advanced features. =head2 put # Any version of HTTP::Message works with this form: my $res = $ua->put( $url, $field_name => $value, Content => $content ); # Using hash or array references requires HTTP::Message >= 6.07 use HTTP::Request 6.07; my $res = $ua->put( $url, \%form ); my $res = $ua->put( $url, \@form ); my $res = $ua->put( $url, \%form, $field_name => $value, ... ); my $res = $ua->put( $url, $field_name => $value, Content => \%form ); my $res = $ua->put( $url, $field_name => $value, Content => \@form ); This method will dispatch a C request on the given URL, with C<%form> or C<@form> providing the key/value pairs for the fill-in form content. Additional headers and content options are the same as for the L method. CAVEAT: This method can only accept content that is in key-value pairs when using L prior to version C<6.07>. Any use of hash or array references will result in an error prior to version C<6.07>. This method will use the C function from L to build the request. See L for a details on how to pass form content and other advanced features. =head2 request my $res = $ua->request( $request ); my $res = $ua->request( $request, $content_file ); my $res = $ua->request( $request, $content_cb ); my $res = $ua->request( $request, $content_cb, $read_size_hint ); This method will dispatch the given C<$request> object. Normally this will be an instance of the L class, but any object with a similar interface will do. The return value is an L object. The C method will process redirects and authentication responses transparently. This means that it may actually send several simple requests via the L method described below. The request methods described above; L, L, L and L will all dispatch the request they build via this method. They are convenience methods that simply hide the creation of the request object for you. The C<$content_file>, C<$content_cb> and C<$read_size_hint> all correspond to options described with the L method above. Note that errors writing to the content file (for example due to permission denied or the filesystem being full) will be reported via the C or C response headers, and not the C method. You are allowed to use a CODE reference as C in the request object passed in. The C function should return the content when called. The content can be returned in chunks. The content function will be invoked repeatedly until it return an empty string to signal that there is no more content. =head2 simple_request my $request = HTTP::Request->new( ... ); my $res = $ua->simple_request( $request ); my $res = $ua->simple_request( $request, $content_file ); my $res = $ua->simple_request( $request, $content_cb ); my $res = $ua->simple_request( $request, $content_cb, $read_size_hint ); This method dispatches a single request and returns the response received. Arguments are the same as for the L described above. The difference from L is that C will not try to handle redirects or authentication responses. The L method will, in fact, invoke this method for each simple request it sends. =head1 CALLBACK METHODS The following methods will be invoked as requests are processed. These methods are documented here because subclasses of L might want to override their behaviour. =head2 get_basic_credentials # This checks wantarray and can either return an array: my ($user, $pass) = $ua->get_basic_credentials( $realm, $uri, $isproxy ); # or a string that looks like "user:pass" my $creds = $ua->get_basic_credentials($realm, $uri, $isproxy); This is called by L to retrieve credentials for documents protected by Basic or Digest Authentication. The arguments passed in is the C<$realm> provided by the server, the C<$uri> requested and a C to indicate if this is authentication against a proxy server. The method should return a username and password. It should return an empty list to abort the authentication resolution attempt. Subclasses can override this method to prompt the user for the information. An example of this can be found in C program distributed with this library. The base implementation simply checks a set of pre-stored member variables, set up with the L method. =head2 prepare_request $request = $ua->prepare_request( $request ); This method is invoked by L. Its task is to modify the given C<$request> object by setting up various headers based on the attributes of the user agent. The return value should normally be the C<$request> object passed in. If a different request object is returned it will be the one actually processed. The headers affected by the base implementation are; C, C, C and C. =head2 progress my $prog = $ua->progress( $status, $request_or_response ); This is called frequently as the response is received regardless of how the content is processed. The method is called with C<$status> "begin" at the start of processing the request and with C<$state> "end" before the request method returns. In between these C<$status> will be the fraction of the response currently received or the string "tick" if the fraction can't be calculated. When C<$status> is "begin" the second argument is the L object, otherwise it is the L object. =head2 redirect_ok my $bool = $ua->redirect_ok( $prospective_request, $response ); This method is called by L before it tries to follow a redirection to the request in C<$response>. This should return a true value if this redirection is permissible. The C<$prospective_request> will be the request to be sent if this method returns true. The base implementation will return false unless the method is in the object's C list, false if the proposed redirection is to a C URL, and true otherwise. =head1 BEST PRACTICES The default settings can get you up and running quickly, but there are settings you can change in order to make your life easier. =head2 Handling Cookies You are encouraged to install L and use L as your cookie jar. L provides a better security model matching that of current Web browsers when L is installed. use HTTP::CookieJar::LWP (); my $jar = HTTP::CookieJar::LWP->new; my $ua = LWP::UserAgent->new( cookie_jar => $jar ); See L for more information. =head2 Managing Protocols C gives you the ability to allow arbitrary protocols. my $ua = LWP::UserAgent->new( protocols_allowed => [ 'http', 'https' ] ); This will prevent you from inadvertently following URLs like C. See L. C gives you the ability to deny arbitrary protocols. my $ua = LWP::UserAgent->new( protocols_forbidden => [ 'file', 'mailto', 'ssh', ] ); This can also prevent you from inadvertently following URLs like C. See L. =head1 SEE ALSO See L for a complete overview of libwww-perl5. See L and the scripts F and F for examples of usage. See L and L for a description of the message objects dispatched and received. See L and L for other ways to build request objects. See L and L for examples of more specialized user agents based on L. =head1 COPYRIGHT AND LICENSE Copyright 1995-2009 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!䌀perl5/LWP/Simple.pmnu6$package LWP::Simple; use strict; our $VERSION = '6.77'; require Exporter; our @EXPORT = qw(get head getprint getstore mirror); our @EXPORT_OK = qw($ua); # I really hate this. It was a bad idea to do it in the first place. # Wonder how to get rid of it??? (It even makes LWP::Simple 7% slower # for trivial tests) use HTTP::Status; push(@EXPORT, @HTTP::Status::EXPORT); sub import { my $pkg = shift; my $callpkg = caller; Exporter::export($pkg, $callpkg, @_); } use LWP::UserAgent (); use HTTP::Date (); our $ua = LWP::UserAgent->new; # we create a global UserAgent object $ua->agent("LWP::Simple/$VERSION "); $ua->env_proxy; sub get ($) { my $response = $ua->get(shift); return $response->decoded_content if $response->is_success; return undef; } sub head ($) { my($url) = @_; my $request = HTTP::Request->new(HEAD => $url); my $response = $ua->request($request); if ($response->is_success) { return $response unless wantarray; return (scalar $response->header('Content-Type'), scalar $response->header('Content-Length'), HTTP::Date::str2time($response->header('Last-Modified')), HTTP::Date::str2time($response->header('Expires')), scalar $response->header('Server'), ); } return; } sub getprint ($) { my($url) = @_; my $request = HTTP::Request->new(GET => $url); local($\) = ""; # ensure standard $OUTPUT_RECORD_SEPARATOR my $callback = sub { print $_[0] }; if ($^O eq "MacOS") { $callback = sub { $_[0] =~ s/\015?\012/\n/g; print $_[0] } } my $response = $ua->request($request, $callback); unless ($response->is_success) { print STDERR $response->status_line, " \n"; } $response->code; } sub getstore ($$) { my($url, $file) = @_; my $request = HTTP::Request->new(GET => $url); my $response = $ua->request($request, $file); $response->code; } sub mirror ($$) { my($url, $file) = @_; my $response = $ua->mirror($url, $file); $response->code; } 1; __END__ =pod =head1 NAME LWP::Simple - simple procedural interface to LWP =head1 SYNOPSIS perl -MLWP::Simple -e 'getprint "http://www.sn.no"' use LWP::Simple; $content = get("http://www.sn.no/"); die "Couldn't get it!" unless defined $content; if (mirror("http://www.sn.no/", "foo") == RC_NOT_MODIFIED) { ... } if (is_success(getprint("http://www.sn.no/"))) { ... } =head1 DESCRIPTION This module is meant for people who want a simplified view of the libwww-perl library. It should also be suitable for one-liners. If you need more control or access to the header fields in the requests sent and responses received, then you should use the full object-oriented interface provided by the L module. The module will also export the L object as C<$ua> if you ask for it explicitly. The user agent created by this module will identify itself as C and will initialize its proxy defaults from the environment (by calling C<< $ua->env_proxy >>). =head1 FUNCTIONS The following functions are provided (and exported) by this module: =head2 get my $res = get($url); The get() function will fetch the document identified by the given URL and return it. It returns C if it fails. The C<$url> argument can be either a string or a reference to a L object. You will not be able to examine the response code or response headers (like C) when you are accessing the web using this function. If you need that information you should use the full OO interface (see L). =head2 head my $res = head($url); Get document headers. Returns the following 5 values if successful: ($content_type, $document_length, $modified_time, $expires, $server) Returns an empty list if it fails. In scalar context returns TRUE if successful. =head2 getprint my $code = getprint($url); Get and print a document identified by a URL. The document is printed to the selected default filehandle for output (normally STDOUT) as data is received from the network. If the request fails, then the status code and message are printed on STDERR. The return value is the HTTP response code. =head2 getstore my $code = getstore($url, $file) my $code = getstore($url, $filehandle) Gets a document identified by a URL and stores it in the file. The return value is the HTTP response code. You may also pass a writeable filehandle or similar, such as a L object. =head2 mirror my $code = mirror($url, $file); Get and store a document identified by a URL, using I, and checking the I. Returns the HTTP response code. =head1 STATUS CONSTANTS This module also exports the L constants and procedures. You can use them when you check the response code from L, L or L. The constants are: RC_CONTINUE RC_SWITCHING_PROTOCOLS RC_OK RC_CREATED RC_ACCEPTED RC_NON_AUTHORITATIVE_INFORMATION RC_NO_CONTENT RC_RESET_CONTENT RC_PARTIAL_CONTENT RC_MULTIPLE_CHOICES RC_MOVED_PERMANENTLY RC_MOVED_TEMPORARILY RC_SEE_OTHER RC_NOT_MODIFIED RC_USE_PROXY RC_BAD_REQUEST RC_UNAUTHORIZED RC_PAYMENT_REQUIRED RC_FORBIDDEN RC_NOT_FOUND RC_METHOD_NOT_ALLOWED RC_NOT_ACCEPTABLE RC_PROXY_AUTHENTICATION_REQUIRED RC_REQUEST_TIMEOUT RC_CONFLICT RC_GONE RC_LENGTH_REQUIRED RC_PRECONDITION_FAILED RC_REQUEST_ENTITY_TOO_LARGE RC_REQUEST_URI_TOO_LARGE RC_UNSUPPORTED_MEDIA_TYPE RC_INTERNAL_SERVER_ERROR RC_NOT_IMPLEMENTED RC_BAD_GATEWAY RC_SERVICE_UNAVAILABLE RC_GATEWAY_TIMEOUT RC_HTTP_VERSION_NOT_SUPPORTED =head1 CLASSIFICATION FUNCTIONS The L classification functions are: =head2 is_success my $bool = is_success($rc); True if response code indicated a successful request. =head2 is_error my $bool = is_error($rc) True if response code indicated that an error occurred. =head1 CAVEAT Note that if you are using both LWP::Simple and the very popular L module, you may be importing a C function from each module, producing a warning like C. Get around this problem by just not importing LWP::Simple's C function, like so: use LWP::Simple qw(!head); use CGI qw(:standard); # then only CGI.pm defines a head() Then if you do need LWP::Simple's C function, you can just call it as C. =head1 SEE ALSO L, L, L, L, L, L =cut PK!"/"/"perl5/LWP/ConnCache.pmnu6$package LWP::ConnCache; use strict; our $VERSION = '6.77'; our $DEBUG; sub new { my($class, %cnf) = @_; my $total_capacity = 1; if (exists $cnf{total_capacity}) { $total_capacity = delete $cnf{total_capacity}; } if (%cnf && $^W) { require Carp; Carp::carp("Unrecognised options: @{[sort keys %cnf]}") } my $self = bless { cc_conns => [] }, $class; $self->total_capacity($total_capacity); $self; } sub deposit { my($self, $type, $key, $conn) = @_; push(@{$self->{cc_conns}}, [$conn, $type, $key, time]); $self->enforce_limits($type); return; } sub withdraw { my($self, $type, $key) = @_; my $conns = $self->{cc_conns}; for my $i (0 .. @$conns - 1) { my $c = $conns->[$i]; next unless $c->[1] eq $type && $c->[2] eq $key; splice(@$conns, $i, 1); # remove it return $c->[0]; } return undef; } sub total_capacity { my $self = shift; my $old = $self->{cc_limit_total}; if (@_) { $self->{cc_limit_total} = shift; $self->enforce_limits; } $old; } sub capacity { my $self = shift; my $type = shift; my $old = $self->{cc_limit}{$type}; if (@_) { $self->{cc_limit}{$type} = shift; $self->enforce_limits($type); } $old; } sub enforce_limits { my($self, $type) = @_; my $conns = $self->{cc_conns}; my @types = $type ? ($type) : ($self->get_types); for $type (@types) { next unless $self->{cc_limit}; my $limit = $self->{cc_limit}{$type}; next unless defined $limit; for my $i (reverse 0 .. @$conns - 1) { next unless $conns->[$i][1] eq $type; if (--$limit < 0) { $self->dropping(splice(@$conns, $i, 1), "$type capacity exceeded"); } } } if (defined(my $total = $self->{cc_limit_total})) { while (@$conns > $total) { $self->dropping(shift(@$conns), "Total capacity exceeded"); } } } sub dropping { my($self, $c, $reason) = @_; print "DROPPING @$c [$reason]\n" if $DEBUG; } sub drop { my($self, $checker, $reason) = @_; if (ref($checker) ne "CODE") { # make it so if (!defined $checker) { $checker = sub { 1 }; # drop all of them } elsif (_looks_like_number($checker)) { my $age_limit = $checker; my $time_limit = time - $age_limit; $reason ||= "older than $age_limit"; $checker = sub { $_[3] < $time_limit }; } else { my $type = $checker; $reason ||= "drop $type"; $checker = sub { $_[1] eq $type }; # match on type } } $reason ||= "drop"; local $SIG{__DIE__}; # don't interfere with eval below local $@; my @c; for (@{$self->{cc_conns}}) { my $drop; eval { if (&$checker(@$_)) { $self->dropping($_, $reason); $drop++; } }; push(@c, $_) unless $drop; } @{$self->{cc_conns}} = @c; } sub prune { my $self = shift; $self->drop(sub { !shift->ping }, "ping"); } sub get_types { my $self = shift; my %t; $t{$_->[1]}++ for @{$self->{cc_conns}}; return keys %t; } sub get_connections { my($self, $type) = @_; my @c; for (@{$self->{cc_conns}}) { push(@c, $_->[0]) if !$type || ($type && $type eq $_->[1]); } @c; } sub _looks_like_number { $_[0] =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/; } 1; __END__ =pod =head1 NAME LWP::ConnCache - Connection cache manager =head1 NOTE This module is experimental. Details of its interface is likely to change in the future. =head1 SYNOPSIS use LWP::ConnCache; my $cache = LWP::ConnCache->new; $cache->deposit($type, $key, $sock); $sock = $cache->withdraw($type, $key); =head1 DESCRIPTION The C class is the standard connection cache manager for L. =head1 METHODS The following basic methods are provided: =head2 new my $cache = LWP::ConnCache->new( %options ) This method constructs a new L object. The only option currently accepted is C. If specified it initializes the L option. It defaults to C<1>. =head2 total_capacity my $cap = $cache->total_capacity; $cache->total_capacity(0); # drop all immediately $cache->total_capacity(undef); # no limit $cache->total_capacity($number); Get/sets the number of connection that will be cached. Connections will start to be dropped when this limit is reached. If set to C<0>, then all connections are immediately dropped. If set to C, then there is no limit. =head2 capacity my $http_capacity = $cache->capacity('http'); $cache->capacity('http', 2 ); Get/set a limit for the number of connections of the specified type that can be cached. The first parameter is a short string like C<"http"> or C<"ftp">. =head2 drop $cache->drop(); # Drop ALL connections # which is just a synonym for: $cache->drop(sub{1}); # Drop ALL connections # drop all connections older than 22 seconds and add a reason for it! $cache->drop(22, "Older than 22 secs dropped"); # which is just a synonym for: $cache->drop(sub { my ($conn, $type, $key, $deposit_time) = @_; if ($deposit_time < 22) { # true values drop the connection return 1; } # false values don't drop the connection return 0; }, "Older than 22 secs dropped" ); Drop connections by some criteria. The $checker argument is a subroutine that is called for each connection. If the routine returns a TRUE value then the connection is dropped. The routine is called with C<($conn, $type, $key, $deposit_time)> as arguments. Shortcuts: If the C<$checker> argument is absent (or C) all cached connections are dropped. If the $checker is a number then all connections untouched that the given number of seconds or more are dropped. If $checker is a string then all connections of the given type are dropped. The C is passed on to the L method. =head2 prune $cache->prune(); Calling this method will drop all connections that are dead. This is tested by calling the L method on the connections. If the L method exists and returns a false value, then the connection is dropped. =head2 get_types my @types = $cache->get_types(); This returns all the C fields used for the currently cached connections. =head2 get_connections my @conns = $cache->get_connections(); # all connections my @conns = $cache->get_connections('http'); # connections for http This returns all connection objects of the specified type. If no type is specified then all connections are returned. In scalar context the number of cached connections of the specified type is returned. =head1 PROTOCOL METHODS The following methods are called by low-level protocol modules to try to save away connections and to get them back. =head2 deposit $cache->deposit($type, $key, $conn); This method adds a new connection to the cache. As a result, other already cached connections might be dropped. Multiple connections with the same type/key might be added. =head2 withdraw my $conn = $cache->withdraw($type, $key); This method tries to fetch back a connection that was previously deposited. If no cached connection with the specified $type/$key is found, then C is returned. There is not guarantee that a deposited connection can be withdrawn, as the cache manger is free to drop connections at any time. =head1 INTERNAL METHODS The following methods are called internally. Subclasses might want to override them. =head2 enforce_limits $conn->enforce_limits([$type]) This method is called with after a new connection is added (deposited) in the cache or capacity limits are adjusted. The default implementation drops connections until the specified capacity limits are not exceeded. =head2 dropping $conn->dropping($conn_record, $reason) This method is called when a connection is dropped. The record belonging to the dropped connection is passed as the first argument and a string describing the reason for the drop is passed as the second argument. The default implementation makes some noise if the C<$LWP::ConnCache::DEBUG> variable is set and nothing more. =head1 SUBCLASSING For specialized cache policy it makes sense to subclass C and perhaps override the L, L, and L methods. The object itself is a hash. Keys prefixed with C are reserved for the base class. =head1 SEE ALSO L =head1 COPYRIGHT Copyright 2001 Gisle Aas. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!|perl5/LWP/Authen/Digest.pmnu6$package LWP::Authen::Digest; use strict; use parent 'LWP::Authen::Basic'; our $VERSION = '6.77'; require Digest::MD5; sub _reauth_requested { my ($class, $auth_param, $ua, $request, $auth_header) = @_; my $ret = defined($$auth_param{stale}) && lc($$auth_param{stale}) eq 'true'; if ($ret) { my $hdr = $request->header($auth_header); $hdr =~ tr/,/;/; # "," is used to separate auth-params!! ($hdr) = HTTP::Headers::Util::split_header_words($hdr); my $nonce = {@$hdr}->{nonce}; delete $$ua{authen_md5_nonce_count}{$nonce}; } return $ret; } sub auth_header { my($class, $user, $pass, $request, $ua, $h) = @_; my $auth_param = $h->{auth_param}; my $nc = sprintf "%08X", ++$ua->{authen_md5_nonce_count}{$auth_param->{nonce}}; my $cnonce = sprintf "%8x", time; my $uri = $request->uri->path_query; $uri = "/" unless length $uri; 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(":", $request->method, $uri)); 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, $uri, $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{$_}; # RFC2617 says that qop-value and nc-value should be unquoted. if ( $_ eq 'qop' || $_ eq 'nc' ) { push(@pairs, "$_=" . $resp{$_}); } else { push(@pairs, "$_=" . qq("$resp{$_}")); } } my $auth_value = "Digest " . join(", ", @pairs); return $auth_value; } 1; PK!lX< < perl5/LWP/Authen/Basic.pmnu6$package LWP::Authen::Basic; use strict; our $VERSION = '6.77'; require Encode; require MIME::Base64; sub auth_header { my($class, $user, $pass, $request, $ua, $h) = @_; my $userpass = "$user:$pass"; # https://tools.ietf.org/html/rfc7617#section-2.1 my $charset = uc($h->{auth_param}->{charset} || ""); $userpass = Encode::encode($charset, $userpass) if ($charset eq "UTF-8"); return "Basic " . MIME::Base64::encode($userpass, ""); } sub _reauth_requested { return 0; } sub authenticate { my($class, $ua, $proxy, $auth_param, $response, $request, $arg, $size) = @_; my $realm = $auth_param->{realm} || ""; my $url = $proxy ? $request->{proxy} : $request->uri_canonical; return $response unless $url; my $host_port = $url->host_port; my $auth_header = $proxy ? "Proxy-Authorization" : "Authorization"; my @m = $proxy ? (m_proxy => $url) : (m_host_port => $host_port); push(@m, realm => $realm); my $h = $ua->get_my_handler("request_prepare", @m, sub { $_[0]{callback} = sub { my($req, $ua, $h) = @_; my($user, $pass) = $ua->credentials($host_port, $h->{realm}); if (defined $user) { my $auth_value = $class->auth_header($user, $pass, $req, $ua, $h); $req->header($auth_header => $auth_value); } }; }); $h->{auth_param} = $auth_param; my $reauth_requested = $class->_reauth_requested($auth_param, $ua, $request, $auth_header); if ( !$proxy && (!$request->header($auth_header) || $reauth_requested) && $ua->credentials($host_port, $realm)) { # we can make sure this handler applies and retry add_path($h, $url->path) unless $reauth_requested; # Do not clobber up path list for retries return $ua->request($request->clone, $arg, $size, $response); } my($user, $pass) = $ua->get_basic_credentials($realm, $url, $proxy); unless (defined $user and defined $pass) { $ua->set_my_handler("request_prepare", undef, @m); # delete handler return $response; } # check that the password has changed my ($olduser, $oldpass) = $ua->credentials($host_port, $realm); return $response if (defined $olduser and defined $oldpass and $user eq $olduser and $pass eq $oldpass); $ua->credentials($host_port, $realm, $user, $pass); add_path($h, $url->path) unless $proxy; return $ua->request($request->clone, $arg, $size, $response); } sub add_path { my($h, $path) = @_; $path =~ s,[^/]+\z,,; push(@{$h->{m_path_prefix}}, $path); } 1; PK!Bg(perl5/LWP/Authen/Ntlm.pmnu6$package LWP::Authen::Ntlm; use strict; our $VERSION = '6.77'; use Authen::NTLM "1.02"; use MIME::Base64 "2.12"; sub authenticate { my($class, $ua, $proxy, $auth_param, $response, $request, $arg, $size) = @_; my($user, $pass) = $ua->get_basic_credentials($auth_param->{realm}, $request->uri, $proxy); unless(defined $user and defined $pass) { return $response; } if (!$ua->conn_cache()) { warn "The keep_alive option must be enabled for NTLM authentication to work. NTLM authentication aborted.\n"; return $response; } my($domain, $username) = split(/\\/, $user); ntlm_domain($domain); ntlm_user($username); ntlm_password($pass); my $auth_header = $proxy ? "Proxy-Authorization" : "Authorization"; # my ($challenge) = $response->header('WWW-Authenticate'); my $challenge; foreach ($response->header('WWW-Authenticate')) { last if /^NTLM/ && ($challenge=$_); } if ($challenge eq 'NTLM') { # First phase, send handshake my $auth_value = "NTLM " . ntlm(); ntlm_reset(); # Need to check this isn't a repeated fail! my $r = $response; my $retry_count = 0; while ($r) { my $auth = $r->request->header($auth_header); ++$retry_count if ($auth && $auth eq $auth_value); if ($retry_count > 2) { # here we know this failed before $response->header("Client-Warning" => "Credentials for '$user' failed before"); return $response; } $r = $r->previous; } my $referral = $request->clone; $referral->header($auth_header => $auth_value); return $ua->request($referral, $arg, $size, $response); } else { # Second phase, use the response challenge (unless non-401 code # was returned, in which case, we just send back the response # object, as is my $auth_value; if ($response->code ne '401') { return $response; } else { my $challenge; foreach ($response->header('WWW-Authenticate')) { last if /^NTLM/ && ($challenge=$_); } $challenge =~ s/^NTLM //; ntlm(); $auth_value = "NTLM " . ntlm($challenge); ntlm_reset(); } my $referral = $request->clone; $referral->header($auth_header => $auth_value); my $response2 = $ua->request($referral, $arg, $size, $response); return $response2; } } 1; __END__ =pod =head1 NAME LWP::Authen::Ntlm - Library for enabling NTLM authentication (Microsoft) in LWP =head1 SYNOPSIS use LWP::UserAgent; use HTTP::Request::Common; my $url = 'http://www.company.com/protected_page.html'; # Set up the ntlm client and then the base64 encoded ntlm handshake message my $ua = LWP::UserAgent->new(keep_alive=>1); $ua->credentials('www.company.com:80', '', "MyDomain\\MyUserCode", 'MyPassword'); $request = GET $url; print "--Performing request now...-----------\n"; $response = $ua->request($request); print "--Done with request-------------------\n"; if ($response->is_success) {print "It worked!->" . $response->code . "\n"} else {print "It didn't work!->" . $response->code . "\n"} =head1 DESCRIPTION L allows LWP to authenticate against servers that are using the NTLM authentication scheme popularized by Microsoft. This type of authentication is common on intranets of Microsoft-centric organizations. The module takes advantage of the Authen::NTLM module by Mark Bush. Since there is also another Authen::NTLM module available from CPAN by Yee Man Chan with an entirely different interface, it is necessary to ensure that you have the correct NTLM module. In addition, there have been problems with incompatibilities between different versions of L, which Bush's L makes use of. Therefore, it is necessary to ensure that your Mime::Base64 module supports exporting of the C and C functions. =head1 USAGE The module is used indirectly through LWP, rather than including it directly in your code. The LWP system will invoke the NTLM authentication when it encounters the authentication scheme while attempting to retrieve a URL from a server. In order for the NTLM authentication to work, you must have a few things set up in your code prior to attempting to retrieve the URL: =over 4 =item * Enable persistent HTTP connections To do this, pass the C<< "keep_alive=>1" >> option to the L when creating it, like this: my $ua = LWP::UserAgent->new(keep_alive=>1); =item * Set the credentials on the UserAgent object The credentials must be set like this: $ua->credentials('www.company.com:80', '', "MyDomain\\MyUserCode", 'MyPassword'); Note that you cannot use the L object's C method to set the credentials. Note, too, that the C<'www.company.com:80'> portion only sets credentials on the specified port AND it is case-sensitive (this is due to the way LWP is coded, and has nothing to do with LWP::Authen::Ntlm) =back =head1 AVAILABILITY General queries regarding LWP should be made to the LWP Mailing List. Questions specific to LWP::Authen::Ntlm can be forwarded to jtillman@bigfoot.com =head1 COPYRIGHT Copyright (c) 2002 James Tillman. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L. =cut PK!tmmperl5/LWP/MemberMixin.pmnu6$package LWP::MemberMixin; our $VERSION = '6.77'; sub _elem { my $self = shift; my $elem = shift; my $old = $self->{$elem}; $self->{$elem} = shift if @_; return $old; } 1; __END__ =pod =head1 NAME LWP::MemberMixin - Member access mixin class =head1 SYNOPSIS package Foo; use parent qw(LWP::MemberMixin); =head1 DESCRIPTION A mixin class to get methods that provide easy access to member variables in the C<%$self>. Ideally there should be better Perl language support for this. =head1 METHODS There is only one method provided: =head2 _elem _elem($elem [, $val]) Internal method to get/set the value of member variable C<$elem>. If C<$val> is present it is used as the new value for the member variable. If it is not present the current value is not touched. In both cases the previous value of the member variable is returned. =cut PK! ՙTT perl5/LWP.pmnu6$package LWP; our $VERSION = '6.77'; require LWP::UserAgent; # this should load everything you need 1; __END__ =pod =encoding utf-8 =head1 NAME LWP - The World-Wide Web library for Perl =head1 SYNOPSIS use LWP; print "This is libwww-perl-$LWP::VERSION\n"; =head1 DESCRIPTION The libwww-perl collection is a set of Perl modules which provides a simple and consistent application programming interface (API) to the World-Wide Web. The main focus of the library is to provide classes and functions that allow you to write WWW clients. The library also contain modules that are of more general use and even classes that help you implement simple HTTP servers. Most modules in this library provide an object oriented API. The user agent, requests sent and responses received from the WWW server are all represented by objects. This makes a simple and powerful interface to these services. The interface is easy to extend and customize for your own needs. The main features of the library are: =over 3 =item * Contains various reusable components (modules) that can be used separately or together. =item * Provides an object oriented model of HTTP-style communication. Within this framework we currently support access to C, C, C, C, C, C, and C resources. =item * Provides a full object oriented interface or a very simple procedural interface. =item * Supports the basic and digest authorization schemes. =item * Supports transparent redirect handling. =item * Supports access through proxy servers. =item * Provides parser for F files and a framework for constructing robots. =item * Supports parsing of HTML forms. =item * Implements HTTP content negotiation algorithm that can be used both in protocol modules and in server scripts (like CGI scripts). =item * Supports HTTP cookies. =item * Some simple command line clients, for instance C and C. =back =head1 HTTP STYLE COMMUNICATION The libwww-perl library is based on HTTP style communication. This section tries to describe what that means. Let us start with this quote from the HTTP specification document L: =over 3 =item * The HTTP protocol is based on a request/response paradigm. A client establishes a connection with a server and sends a request to the server in the form of a request method, URI, and protocol version, followed by a MIME-like message containing request modifiers, client information, and possible body content. The server responds with a status line, including the message's protocol version and a success or error code, followed by a MIME-like message containing server information, entity meta-information, and possible body content. =back What this means to libwww-perl is that communication always take place through these steps: First a I object is created and configured. This object is then passed to a server and we get a I object in return that we can examine. A request is always independent of any previous requests, i.e. the service is stateless. The same simple model is used for any kind of service we want to access. For example, if we want to fetch a document from a remote file server, then we send it a request that contains a name for that document and the response will contain the document itself. If we access a search engine, then the content of the request will contain the query parameters and the response will contain the query result. If we want to send a mail message to somebody then we send a request object which contains our message to the mail server and the response object will contain an acknowledgment that tells us that the message has been accepted and will be forwarded to the recipient(s). It is as simple as that! =head2 The Request Object The libwww-perl request object has the class name L. The fact that the class name uses C as a prefix only implies that we use the HTTP model of communication. It does not limit the kind of services we can try to pass this I to. For instance, we will send Ls both to ftp and gopher servers, as well as to the local file system. The main attributes of the request objects are: =over 3 =item * B is a short string that tells what kind of request this is. The most common methods are B, B, B and B. =item * B is a string denoting the protocol, server and the name of the "document" we want to access. The B might also encode various other parameters. =item * B contains additional information about the request and can also used to describe the content. The headers are a set of keyword/value pairs. =item * B is an arbitrary amount of data. =back =head2 The Response Object The libwww-perl response object has the class name L. The main attributes of objects of this class are: =over 3 =item * B is a numerical value that indicates the overall outcome of the request. =item * B is a short, human readable string that corresponds to the I. =item * B contains additional information about the response and describe the content. =item * B is an arbitrary amount of data. =back Since we don't want to handle all possible I values directly in our programs, a libwww-perl response object has methods that can be used to query what kind of response this is. The most commonly used response classification methods are: =over 3 =item is_success() The request was successfully received, understood or accepted. =item is_error() The request failed. The server or the resource might not be available, access to the resource might be denied or other things might have failed for some reason. =back =head2 The User Agent Let us assume that we have created a I object. What do we actually do with it in order to receive a I? The answer is that you pass it to a I object and this object takes care of all the things that need to be done (like low-level communication and error handling) and returns a I object. The user agent represents your application on the network and provides you with an interface that can accept I and return I. The user agent is an interface layer between your application code and the network. Through this interface you are able to access the various servers on the network. The class name for the user agent is L. Every libwww-perl application that wants to communicate should create at least one object of this class. The main method provided by this object is request(). This method takes an L object as argument and (eventually) returns a L object. The user agent has many other attributes that let you configure how it will interact with the network and with your application. =over 3 =item * B specifies how much time we give remote servers to respond before the library disconnects and creates an internal I response. =item * B specifies the name that your application uses when it presents itself on the network. =item * B can be set to the e-mail address of the person responsible for running the application. If this is set, then the address will be sent to the servers with every request. =item * B specifies whether we should initialize response headers from the C<< >> section of HTML documents. =item * B and B specify if and when to go through a proxy server. L =item * B provides a way to set up user names and passwords needed to access certain services. =back Many applications want even more control over how they interact with the network and they get this by sub-classing L. The library includes a sub-class, L, for robot applications. =head2 An Example This example shows how the user agent, a request and a response are represented in actual perl code: # Create a user agent object use LWP::UserAgent; my $ua = LWP::UserAgent->new; $ua->agent("MyApp/0.1 "); # Create a request my $req = HTTP::Request->new(POST => 'http://search.cpan.org/search'); $req->content_type('application/x-www-form-urlencoded'); $req->content('query=libwww-perl&mode=dist'); # Pass request to the user agent and get a response back my $res = $ua->request($req); # Check the outcome of the response if ($res->is_success) { print $res->content; } else { print $res->status_line, "\n"; } The C<$ua> is created once when the application starts up. New request objects should normally created for each request sent. =head1 NETWORK SUPPORT This section discusses the various protocol schemes and the HTTP style methods that headers may be used for each. For all requests, a "User-Agent" header is added and initialized from the C<< $ua->agent >> attribute before the request is handed to the network layer. In the same way, a "From" header is initialized from the $ua->from attribute. For all responses, the library adds a header called "Client-Date". This header holds the time when the response was received by your application. The format and semantics of the header are the same as the server created "Date" header. You may also encounter other "Client-XXX" headers. They are all generated by the library internally and are not received from the servers. =head2 HTTP Requests HTTP requests are just handed off to an HTTP server and it decides what happens. Few servers implement methods beside the usual "GET", "HEAD", "POST" and "PUT", but CGI-scripts may implement any method they like. If the server is not available then the library will generate an internal error response. The library automatically adds a "Host" and a "Content-Length" header to the HTTP request before it is sent over the network. For a GET request you might want to add an "If-Modified-Since" or "If-None-Match" header to make the request conditional. For a POST request you should add the "Content-Type" header. When you try to emulate HTML EFORM> handling you should usually let the value of the "Content-Type" header be "application/x-www-form-urlencoded". See L for examples of this. The libwww-perl HTTP implementation currently support the HTTP/1.1 and HTTP/1.0 protocol. The library allows you to access proxy server through HTTP. This means that you can set up the library to forward all types of request through the HTTP protocol module. See L for documentation of this. =head2 HTTPS Requests HTTPS requests are HTTP requests over an encrypted network connection using the SSL protocol developed by Netscape. Everything about HTTP requests above also apply to HTTPS requests. In addition the library will add the headers "Client-SSL-Cipher", "Client-SSL-Cert-Subject" and "Client-SSL-Cert-Issuer" to the response. These headers denote the encryption method used and the name of the server owner. The request can contain the header "If-SSL-Cert-Subject" in order to make the request conditional on the content of the server certificate. If the certificate subject does not match, no request is sent to the server and an internally generated error response is returned. The value of the "If-SSL-Cert-Subject" header is interpreted as a Perl regular expression. =head2 FTP Requests The library currently supports GET, HEAD and PUT requests. GET retrieves a file or a directory listing from an FTP server. PUT stores a file on a ftp server. You can specify a ftp account for servers that want this in addition to user name and password. This is specified by including an "Account" header in the request. User name/password can be specified using basic authorization or be encoded in the URL. Failed logins return an UNAUTHORIZED response with "WWW-Authenticate: Basic" and can be treated like basic authorization for HTTP. The library supports ftp ASCII transfer mode by specifying the "type=a" parameter in the URL. It also supports transfer of ranges for FTP transfers using the "Range" header. Directory listings are by default returned unprocessed (as returned from the ftp server) with the content media type reported to be "text/ftp-dir-listing". The L module provides methods for parsing of these directory listing. The ftp module is also able to convert directory listings to HTML and this can be requested via the standard HTTP content negotiation mechanisms (add an "Accept: text/html" header in the request if you want this). For normal file retrievals, the "Content-Type" is guessed based on the file name suffix. See L. The "If-Modified-Since" request header works for servers that implement the C command. It will probably not work for directory listings though. Example: $req = HTTP::Request->new(GET => 'ftp://me:passwd@ftp.some.where.com/'); $req->header(Accept => "text/html, */*;q=0.1"); =head2 News Requests Access to the USENET News system is implemented through the NNTP protocol. The name of the news server is obtained from the NNTP_SERVER environment variable and defaults to "news". It is not possible to specify the hostname of the NNTP server in news: URLs. The library supports GET and HEAD to retrieve news articles through the NNTP protocol. You can also post articles to newsgroups by using (surprise!) the POST method. GET on newsgroups is not implemented yet. Examples: $req = HTTP::Request->new(GET => 'news:abc1234@a.sn.no'); $req = HTTP::Request->new(POST => 'news:comp.lang.perl.test'); $req->header(Subject => 'This is a test', From => 'me@some.where.org'); $req->content(<new(GET => 'gopher://gopher.sn.no/'); =head2 File Request The library supports GET and HEAD methods for file requests. The "If-Modified-Since" header is supported. All other headers are ignored. The I component of the file URL must be empty or set to "localhost". Any other I value will be treated as an error. Directories are always converted to an HTML document. For normal files, the "Content-Type" and "Content-Encoding" in the response are guessed based on the file suffix. Example: $req = HTTP::Request->new(GET => 'file:/etc/passwd'); =head2 Mailto Request You can send (aka "POST") mail messages using the library. All headers specified for the request are passed on to the mail system. The "To" header is initialized from the mail address in the URL. Example: $req = HTTP::Request->new(POST => 'mailto:libwww@perl.org'); $req->header(Subject => "subscribe"); $req->content("Please subscribe me to the libwww-perl mailing list!\n"); =head2 CPAN Requests URLs with scheme C are redirected to a suitable CPAN mirror. If you have your own local mirror of CPAN you might tell LWP to use it for C URLs by an assignment like this: $LWP::Protocol::cpan::CPAN = "file:/local/CPAN/"; Suitable CPAN mirrors are also picked up from the configuration for the CPAN.pm, so if you have used that module a suitable mirror should be picked automatically. If neither of these apply, then a redirect to the generic CPAN http location is issued. Example request to download the newest perl: $req = HTTP::Request->new(GET => "cpan:src/latest.tar.gz"); =head1 OVERVIEW OF CLASSES AND PACKAGES This table should give you a quick overview of the classes provided by the library. Indentation shows class inheritance. LWP::MemberMixin -- Access to member variables of Perl5 classes LWP::UserAgent -- WWW user agent class LWP::RobotUA -- When developing a robot applications LWP::Protocol -- Interface to various protocol schemes LWP::Protocol::http -- http:// access LWP::Protocol::file -- file:// access LWP::Protocol::ftp -- ftp:// access ... LWP::Authen::Basic -- Handle 401 and 407 responses LWP::Authen::Digest HTTP::Headers -- MIME/RFC822 style header (used by HTTP::Message) HTTP::Message -- HTTP style message HTTP::Request -- HTTP request HTTP::Response -- HTTP response HTTP::Daemon -- A HTTP server class WWW::RobotRules -- Parse robots.txt files WWW::RobotRules::AnyDBM_File -- Persistent RobotRules Net::HTTP -- Low level HTTP client The following modules provide various functions and definitions. LWP -- This file. Library version number and documentation. LWP::MediaTypes -- MIME types configuration (text/html etc.) LWP::Simple -- Simplified procedural interface for common functions HTTP::Status -- HTTP status code (200 OK etc) HTTP::Date -- Date parsing module for HTTP date formats HTTP::Negotiate -- HTTP content negotiation calculation File::Listing -- Parse directory listings HTML::Form -- Processing for
    s in HTML documents =head1 MORE DOCUMENTATION All modules contain detailed information on the interfaces they provide. The L manpage is the libwww-perl cookbook that contain examples of typical usage of the library. You might want to take a look at how the scripts L, L, L and L are implemented. =head1 ENVIRONMENT The following environment variables are used by LWP: =over =item HOME The L functions will look for the F<.media.types> and F<.mime.types> files relative to you home directory. =item http_proxy =item ftp_proxy =item xxx_proxy =item no_proxy These environment variables can be set to enable communication through a proxy server. See the description of the C method in L. =item PERL_LWP_ENV_PROXY If set to a TRUE value, then the L will by default call C during initialization. This makes LWP honor the proxy variables described above. =item PERL_LWP_SSL_VERIFY_HOSTNAME The default C setting for L. If not set the default will be 1. Set it as 0 to disable hostname verification (the default prior to libwww-perl 5.840. =item PERL_LWP_SSL_CA_FILE =item PERL_LWP_SSL_CA_PATH The file and/or directory where the trusted Certificate Authority certificates is located. See L for details. =item PERL_HTTP_URI_CLASS Used to decide what URI objects to instantiate. The default is L. You might want to set it to L for compatibility with old times. =back =head1 AUTHORS LWP was made possible by contributions from Adam Newby, Albert Dvornik, Alexandre Duret-Lutz, Andreas Gustafsson, Andreas König, Andrew Pimlott, Andy Lester, Ben Coleman, Benjamin Low, Ben Low, Ben Tilly, Blair Zajac, Bob Dalgleish, BooK, Brad Hughes, Brian J. Murrell, Brian McCauley, Charles C. Fu, Charles Lane, Chris Nandor, Christian Gilmore, Chris W. Unger, Craig Macdonald, Dale Couch, Dan Kubb, Dave Dunkin, Dave W. Smith, David Coppit, David Dick, David D. Kilzer, Doug MacEachern, Edward Avis, erik, Gary Shea, Gisle Aas, Graham Barr, Gurusamy Sarathy, Hans de Graaff, Harald Joerg, Harry Bochner, Hugo, Ilya Zakharevich, INOUE Yoshinari, Ivan Panchenko, Jack Shirazi, James Tillman, Jan Dubois, Jared Rhine, Jim Stern, Joao Lopes, John Klar, Johnny Lee, Josh Kronengold, Josh Rai, Joshua Chamas, Joshua Hoblitt, Kartik Subbarao, Keiichiro Nagano, Ken Williams, KONISHI Katsuhiro, Lee T Lindley, Liam Quinn, Marc Hedlund, Marc Langheinrich, Mark D. Anderson, Marko Asplund, Mark Stosberg, Markus B Krüger, Markus Laker, Martijn Koster, Martin Thurn, Matthew Eldridge, Matthew.van.Eerde, Matt Sergeant, Michael A. Chase, Michael Quaranta, Michael Thompson, Mike Schilli, Moshe Kaminsky, Nathan Torkington, Nicolai Langfeldt, Norton Allen, Olly Betts, Paul J. Schinder, peterm, Philip Guenther, Daniel Buenzli, Pon Hwa Lin, Radoslaw Zielinski, Radu Greab, Randal L. Schwartz, Richard Chen, Robin Barker, Roy Fielding, Sander van Zoest, Sean M. Burke, shildreth, Slaven Rezic, Steve A Fink, Steve Hay, Steven Butler, Steve_Kilbane, Takanori Ugai, Thomas Lotterer, Tim Bunce, Tom Hughes, Tony Finch, Ville Skyttä, Ward Vandewege, William York, Yale Huang, and Yitzchak Scott-Thoennes. LWP owes a lot in motivation, design, and code, to the libwww-perl library for Perl4 by Roy Fielding, which included work from Alberto Accomazzi, James Casey, Brooks Cutter, Martijn Koster, Oscar Nierstrasz, Mel Melchner, Gertjan van Oosten, Jared Rhine, Jack Shirazi, Gene Spafford, Marc VanHeyningen, Steven E. Brenner, Marion Hakanson, Waldemar Kebsch, Tony Sanders, and Larry Wall; see the libwww-perl-0.40 library for details. =head1 COPYRIGHT Copyright 1995-2009, Gisle Aas Copyright 1995, Martijn Koster This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AVAILABILITY The latest version of this library is likely to be available from CPAN as well as: http://github.com/libwww-perl/libwww-perl The best place to discuss this code is on the mailing list. =cut PK!RRRRperl5/FFI/CheckLib.pmnu6$package FFI::CheckLib; use strict; use warnings; use File::Spec; use List::Util 1.33 qw( any ); use Carp qw( croak carp ); use Env qw( @FFI_CHECKLIB_PATH ); use base qw( Exporter ); our @EXPORT = qw( find_lib assert_lib check_lib check_lib_or_exit find_lib_or_exit find_lib_or_die ); our @EXPORT_OK = qw( which where has_symbols ); # ABSTRACT: Check that a library is available for FFI our $VERSION = '0.31'; # VERSION our $system_path = []; our $os ||= $^O; my $try_ld_on_text = 0; sub _homebrew_lib_path { require File::Which; return undef unless File::Which::which('brew'); chomp(my $brew_path = (qx`brew --prefix`)[0]); return "$brew_path/lib"; } sub _macports_lib_path { require File::Which; my $port_path = File::Which::which('port'); return undef unless $port_path; $port_path =~ s|bin/port|lib|; return $port_path; } sub _darwin_extra_paths { my $pkg_managers = lc( $ENV{FFI_CHECKLIB_PACKAGE} || 'homebrew,macports' ); return () if $pkg_managers eq 'none'; my $supported_managers = { homebrew => \&_homebrew_lib_path, macports => \&_macports_lib_path }; my @extra_paths = (); foreach my $pkg_manager (split( /,/, $pkg_managers )) { if (my $lib_path = $supported_managers->{$pkg_manager}()) { push @extra_paths, $lib_path; } } return @extra_paths; } my @extra_paths = (); if($os eq 'MSWin32' || $os eq 'msys') { $system_path = eval { require Env; Env->import('@PATH'); \our @PATH; }; die $@ if $@; } else { $system_path = eval { require DynaLoader; no warnings 'once'; \@DynaLoader::dl_library_path; }; die $@ if $@; @extra_paths = _darwin_extra_paths() if $os eq 'darwin'; } our $pattern = [ qr{^lib(.*?)\.so(?:\.([0-9]+(?:\.[0-9]+)*))?$} ]; our $version_split = qr/\./; if($os eq 'cygwin') { push @$pattern, qr{^cyg(.*?)(?:-([0-9])+)?\.dll$}; } elsif($os eq 'msys') { # doesn't seem as though msys uses psudo libfoo.so files # in the way that cygwin sometimes does. we can revisit # this if we find otherwise. $pattern = [ qr{^msys-(.*?)(?:-([0-9])+)?\.dll$} ]; } elsif($os eq 'MSWin32') { # handle cases like libgeos-3-7-0___.dll, libproj_9_1.dll and libgtk-2.0-0.dll $pattern = [ qr{^(?:lib)?(\w+?)(?:[_-]([0-9\-\._]+))?_*\.dll$}i ]; $version_split = qr/[_\-]/; } elsif($os eq 'darwin') { push @$pattern, qr{^lib(.*?)(?:\.([0-9]+(?:\.[0-9]+)*))?\.(?:dylib|bundle)$}; } elsif($os eq 'linux') { if(-e '/etc/redhat-release' && -x '/usr/bin/ld') { $try_ld_on_text = 1; } } sub _matches { my($filename, $path) = @_; foreach my $regex (@$pattern) { return [ $1, # 0 capture group 1 library name File::Spec->catfile($path, $filename), # 1 full path to library defined $2 ? (split $version_split, $2) : (), # 2... capture group 2 library version ] if $filename =~ $regex; } return (); } sub _cmp { my($A,$B) = @_; return $A->[0] cmp $B->[0] if $A->[0] ne $B->[0]; my $i=2; while(1) { return 0 if !defined($A->[$i]) && !defined($B->[$i]); return -1 if !defined $A->[$i]; return 1 if !defined $B->[$i]; return $B->[$i] <=> $A->[$i] if $A->[$i] != $B->[$i]; $i++; } } my $diagnostic; sub _is_binary { -B $_[0] } sub find_lib { my(%args) = @_; undef $diagnostic; croak "find_lib requires lib argument" unless defined $args{lib}; my $recursive = $args{_r} || $args{recursive} || 0; # make arguments be lists. foreach my $arg (qw( lib libpath symbol verify alien )) { next if ref $args{$arg} eq 'ARRAY'; if(defined $args{$arg}) { $args{$arg} = [ $args{$arg} ]; } else { $args{$arg} = []; } } if(defined $args{systempath} && !ref($args{systempath})) { $args{systempath} = [ $args{systempath} ]; } my @path = @{ $args{libpath} }; @path = map { _recurse($_) } @path if $recursive; if(defined $args{systempath}) { push @path, grep { defined } @{ $args{systempath} } } else { # This is a little convaluted, but: # 1. These are modifications of what we consider the "system" path # if systempath isn't explicitly passed in as systempath # 2. FFI_CHECKLIB_PATH is considered an authortative modification # so it goes first and overrides FFI_CHECKLIB_PACKAGE # 3. otherwise FFI_CHECKLIB_PACKAGE does its thing and goes on # the end because homebrew does a good job of not replacing # anything in the system by default. # 4. We finally add what we consider the "system" path to the end of # the search path so that libpath will be searched first. my @system_path = @$system_path; if($ENV{FFI_CHECKLIB_PATH}) { @system_path = (@FFI_CHECKLIB_PATH, @system_path); } else { foreach my $extra_path (@extra_paths) { push @path, $extra_path unless any { $_ eq $extra_path } @path; } } push @path, @system_path; } my $any = any { $_ eq '*' } @{ $args{lib} }; my %missing = map { $_ => 1 } @{ $args{lib} }; my %symbols = map { $_ => 1 } @{ $args{symbol} }; my @found; delete $missing{'*'}; alien: foreach my $alien (reverse @{ $args{alien} }) { unless($alien =~ /^([A-Za-z_][A-Za-z_0-9]*)(::[A-Za-z_][A-Za-z_0-9]*)*$/) { croak "Doesn't appear to be a valid Alien name $alien"; } unless(eval { $alien->can('dynamic_libs') }) { { my $pm = "$alien.pm"; $pm =~ s/::/\//g; local $@ = ''; eval { require $pm }; next alien if $@; } unless(eval { $alien->can('dynamic_libs') }) { croak "Alien $alien doesn't provide a dynamic_libs method"; } } unshift @path, [$alien->dynamic_libs]; } foreach my $path (@path) { next if ref $path ne 'ARRAY' && ! -d $path; my @maybe = # make determinist based on names and versions sort { _cmp($a,$b) } # Filter out the items that do not match the name that we are looking for # Filter out any broken symbolic links grep { ($any || $missing{$_->[0]} ) && (-e $_->[1]) } ref $path eq 'ARRAY' ? do { map { my($v, $d, $f) = File::Spec->splitpath($_); _matches($f, File::Spec->catpath($v,$d,'')); } @$path; } : do { my $dh; opendir $dh, $path; # get [ name, full_path ] mapping, # each entry is a 2 element list ref map { _matches($_,$path) } readdir $dh; }; if($try_ld_on_text && $args{try_linker_script}) { # This is tested in t/ci.t only @maybe = map { -B $_->[1] ? $_ : do { my($name, $so) = @$_; my $output = `/usr/bin/ld -t $so -o /dev/null -shared`; $output =~ /\((.*?lib.*\.so.*?)\)/ ? [$name, $1] : die "unable to parse ld output"; } } @maybe; } midloop: foreach my $lib (@maybe) { next unless $any || $missing{$lib->[0]}; foreach my $verify (@{ $args{verify} }) { next midloop unless $verify->(@$lib); } delete $missing{$lib->[0]}; if(%symbols) { require DynaLoader; my $dll = DynaLoader::dl_load_file($lib->[1],0); foreach my $symbol (keys %symbols) { if(DynaLoader::dl_find_symbol($dll, $symbol) ? 1 : 0) { delete $symbols{$symbol} } } DynaLoader::dl_unload_file($dll); } my $found = $lib->[1]; unless($any) { while(-l $found) { require File::Basename; my $dir = File::Basename::dirname($found); $found = File::Spec->rel2abs( readlink($found), $dir ); } } push @found, $found; } } if(%missing) { my @missing = sort keys %missing; if(@missing > 1) { $diagnostic = "libraries not found: @missing" } else { $diagnostic = "library not found: @missing" } } elsif(%symbols) { my @missing = sort keys %symbols; if(@missing > 1) { $diagnostic = "symbols not found: @missing" } else { $diagnostic = "symbol not found: @missing" } } return if %symbols; return $found[0] unless wantarray; return @found; } sub _recurse { my($dir) = @_; return unless -d $dir; my $dh; opendir $dh, $dir; my @list = grep { -d $_ } map { File::Spec->catdir($dir, $_) } grep !/^\.\.?$/, readdir $dh; closedir $dh; ($dir, map { _recurse($_) } @list); } sub assert_lib { croak $diagnostic || 'library not found' unless check_lib(@_); } sub check_lib_or_exit { unless(check_lib(@_)) { carp $diagnostic || 'library not found'; exit; } } sub find_lib_or_exit { my(@libs) = find_lib(@_); unless(@libs) { carp $diagnostic || 'library not found'; exit; } return unless @libs; wantarray ? @libs : $libs[0]; } sub find_lib_or_die { my(@libs) = find_lib(@_); unless(@libs) { croak $diagnostic || 'library not found'; } return unless @libs; wantarray ? @libs : $libs[0]; } sub check_lib { find_lib(@_) ? 1 : 0; } sub which { my($name) = @_; croak("cannot which *") if $name eq '*'; scalar find_lib( lib => $name ); } sub where { my($name) = @_; $name eq '*' ? find_lib(lib => '*') : find_lib(lib => '*', verify => sub { $_[0] eq $name }); } sub has_symbols { my($path, @symbols) = @_; require DynaLoader; my $dll = DynaLoader::dl_load_file($path, 0); my $ok = 1; foreach my $symbol (@symbols) { unless(DynaLoader::dl_find_symbol($dll, $symbol)) { $ok = 0; last; } } DynaLoader::dl_unload_file($dll); $ok; } sub system_path { $system_path; } 1; __END__ =pod =encoding UTF-8 =head1 NAME FFI::CheckLib - Check that a library is available for FFI =head1 VERSION version 0.31 =head1 SYNOPSIS use FFI::CheckLib; check_lib_or_exit( lib => 'jpeg', symbol => 'jinit_memory_mgr' ); check_lib_or_exit( lib => [ 'iconv', 'jpeg' ] ); # or prompt for path to library and then: print "where to find jpeg library: "; my $path = ; check_lib_or_exit( lib => 'jpeg', libpath => $path ); =head1 DESCRIPTION This module checks whether a particular dynamic library is available for FFI to use. It is modeled heavily on L, but will find dynamic libraries even when development packages are not installed. It also provides a L function that will return the full path to the found dynamic library, which can be feed directly into L or another FFI system. Although intended mainly for FFI modules via L and similar, this module does not actually use any FFI to do its detection and probing. This module does not have any non-core runtime dependencies. The test suite does depend on L. =head1 FUNCTIONS All of these take the same named parameters and are exported by default. =head2 find_lib my(@libs) = find_lib(%args); This will return a list of dynamic libraries, or empty list if none were found. [version 0.05] If called in scalar context it will return the first library found. Arguments are key value pairs with these keys: =over 4 =item lib Must be either a string with the name of a single library or a reference to an array of strings of library names. Depending on your platform, C will prepend C or append C<.dll> or C<.so> when searching. [version 0.11] As a special case, if C<*> is specified then any libs found will match. =item libpath A string or array of additional paths to search for libraries. =item systempath [version 0.11] A string or array of system paths to search for instead of letting L determine the system path. You can set this to C<[]> in order to not search I system paths. =item symbol A string or a list of symbol names that must be found. =item verify A code reference used to verify a library really is the one that you want. It should take two arguments, which is the name of the library and the full path to the library pathname. It should return true if it is acceptable, and false otherwise. You can use this in conjunction with L to determine if it is going to meet your needs. Example: use FFI::CheckLib; use FFI::Platypus; my($lib) = find_lib( lib => 'foo', verify => sub { my($name, $libpath) = @_; my $ffi = FFI::Platypus->new; $ffi->lib($libpath); my $f = $ffi->function('foo_version', [] => 'int'); return $f->call() >= 500; # we accept version 500 or better }, ); =item recursive [version 0.11] Recursively search for libraries in any non-system paths (those provided via C above). =item try_linker_script [version 0.24] Some vendors provide C<.so> files that are linker scripts that point to the real binary shared library. These linker scripts can be used by gcc or clang, but are not directly usable by L and friends. On select platforms, this options will use the linker command (C) to attempt to resolve the real C<.so> for non-binary files. Since there is extra overhead this is off by default. An example is libyaml on Red Hat based Linux distributions. On Debian these are handled with symlinks and no trickery is required. =item alien [version 0.25] If no libraries can be found, try the given aliens instead. The Alien classes specified must provide the L interface for dynamic libraries, which is to say they should provide a method called C that returns a list of dynamic libraries. [version 0.28] In 0.28 and later, if the L is not installed then it will be ignored and this module will search in system or specified directories only. This module I still throw an exception, if the L doesn't look like a module name or if it does not provide a C method (which is implemented by all L subclasses). [version 0.30] [breaking change] Starting with version 0.30, libraries provided by Ls is preferred over the system libraries. The original thinking was that you want to prefer the system libraries because they are more likely to get patched with regular system updates. Unfortunately, the reason a module needs to install an Alien is likely because the system library is not new enough, so we now prefer the Ls instead. =back =head2 assert_lib assert_lib(%args); This behaves exactly the same as L, except that instead of returning empty list of failure it throws an exception. =head2 check_lib_or_exit check_lib_or_exit(%args); This behaves exactly the same as L, except that instead of dying, it warns (with exactly the same error message) and exists. This is intended for use in C or C =head2 find_lib_or_exit [version 0.05] my(@libs) = find_lib_or_exit(%args); This behaves exactly the same as L, except that if the library is not found, it will call exit with an appropriate diagnostic. =head2 find_lib_or_die [version 0.06] my(@libs) = find_lib_or_die(%args); This behaves exactly the same as L, except that if the library is not found, it will die with an appropriate diagnostic. =head2 check_lib my $bool = check_lib(%args); This behaves exactly the same as L, except that it returns true (1) on finding the appropriate libraries or false (0) otherwise. =head2 which [version 0.17] my $path = which($name); Return the path to the first library that matches the given name. Not exported by default. =head2 where [version 0.17] my @paths = where($name); Return the paths to all the libraries that match the given name. Not exported by default. =head2 has_symbols [version 0.17] my $bool = has_symbols($path, @symbol_names); Returns true if I of the symbols can be found in the dynamic library located at the given path. Can be useful in conjunction with C with C above. Not exported by default. =head2 system_path [version 0.20] my $path = FFI::CheckLib::system_path; Returns the system path as a list reference. On some systems, this is C on others it might be C on still others it could be something completely different. So although you I add items to this list, you should probably do some careful consideration before you do so. This function is not exportable, even on request. =head1 ENVIRONMENT L responds to these environment variables: =over 4 =item FFI_CHECKLIB_PACKAGE On macOS platforms with L and/or L installed, their corresponding lib paths will be automatically appended to C<$system_path>. In case of having both managers installed, Homebrew will appear before. This behaviour can be overridden using the environment variable C. Allowed values are: - C: Won't use either Homebrew's path nor MacPorts - C: Will append C<$(brew --prefix)/lib> to the system paths - C: Will append C's default lib path A comma separated list is also valid: export FFI_CHECKLIB_PACKAGE=macports,homebrew Order matters. So in this example, MacPorts' lib path appears before Homebrew's path. =item FFI_CHECKLIB_PATH List of directories that will be considered by L as additional "system directories". They will be searched before other system directories but after C. The variable is colon separated on Unix and semicolon separated on Windows. If you use this variable, C will be ignored. =item PATH On Windows the C environment variable will be used as a search path for libraries. =back On some operating systems C, C, C or others I be used as part of the search for dynamic libraries and I be used (indirectly) by L as well. =head1 FAQ =over 4 =item Why not just use C? Calling C on a library name and then C immediately can tell you if you have the exact name of a library available on a system. It does have a number of drawbacks as well. =over 4 =item No absolute or relative path It only tells you that the library is I on the system, not having the absolute or relative path makes it harder to generate useful diagnostics. =item POSIX only This doesn't work on non-POSIX systems like Microsoft Windows. If you are using a POSIX emulation layer on Windows that provides C, like Cygwin, there are a number of gotchas there as well. Having a layer written in Perl handles this means that developers on Unix can develop FFI that will more likely work on these platforms without special casing them. =item inconsistent implementations Even on POSIX systems you have inconsistent implementations. OpenBSD for example don't usually include symlinks for C<.so> files meaning you need to know the exact C<.so> version. =item non-system directories By default C only works for libraries in the system paths. Most platforms have a way of configuring the search for different non-system paths, but none of them are portable, and are usually discouraged anyway. L and friends need to do searches for dynamic libraries in non-system directories for C installs. =back =item My 64-bit Perl is misconfigured and has 32-bit libraries in its search path. Is that a bug in L? Nope. =item The way L is implemented it won't work on AIX, HP-UX, OpenVMS or Plan 9. I know for a fact that it doesn't work on AIX I because I used to develop on AIX in the early 2000s, and I am aware of some of the technical challenges. There are probably other systems that it won't work on. I would love to add support for these platforms. Realistically these platforms have a tiny market share, and absent patches from users or the companies that own these operating systems (patches welcome), or hardware / CPU time donations, these platforms are unsupportable anyway. =back =head1 SEE ALSO =over 4 =item L Call library functions dynamically without a compiler. =item L L plugin for this module. =back =head1 AUTHOR Author: Graham Ollis Eplicease@cpan.orgE Contributors: Bakkiaraj Murugesan (bakkiaraj) Dan Book (grinnz, DBOOK) Ilya Pavlov (Ilya, ILUX) Shawn Laffan (SLAFFAN) Petr Písař (ppisar) Michael R. Davis (MRDVT) Shawn Laffan (SLAFFAN) Carlos D. Álvaro (cdalvaro) =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2014-2022 by Graham Ollis. 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 PK!vavaperl5/lwptut.podnu6$=head1 NAME lwptut -- An LWP Tutorial =head1 DESCRIPTION LWP (short for "Library for WWW in Perl") is a very popular group of Perl modules for accessing data on the Web. Like most Perl module-distributions, each of LWP's component modules comes with documentation that is a complete reference to its interface. However, there are so many modules in LWP that it's hard to know where to start looking for information on how to do even the simplest most common things. Really introducing you to using LWP would require a whole book -- a book that just happens to exist, called I. But this article should give you a taste of how you can go about some common tasks with LWP. =head2 Getting documents with LWP::Simple If you just want to get what's at a particular URL, the simplest way to do it is LWP::Simple's functions. In a Perl program, you can call its C function. It will try getting that URL's content. If it works, then it'll return the content; but if there's some error, it'll return undef. my $url = 'http://www.npr.org/programs/fa/?todayDate=current'; # Just an example: the URL for the most recent /Fresh Air/ show use LWP::Simple; my $content = get $url; die "Couldn't get $url" unless defined $content; # Then go do things with $content, like this: if($content =~ m/jazz/i) { print "They're talking about jazz today on Fresh Air!\n"; } else { print "Fresh Air is apparently jazzless today.\n"; } The handiest variant on C is C, which is useful in Perl one-liners. If it can get the page whose URL you provide, it sends it to STDOUT; otherwise it complains to STDERR. % perl -MLWP::Simple -e "getprint 'http://www.cpan.org/RECENT'" That is the URL of a plain text file that lists new files in CPAN in the past two weeks. You can easily make it part of a tidy little shell command, like this one that mails you the list of new C modules: % perl -MLWP::Simple -e "getprint 'http://www.cpan.org/RECENT'" \ | grep "/by-module/Acme" | mail -s "New Acme modules! Joy!" $USER There are other useful functions in LWP::Simple, including one function for running a HEAD request on a URL (useful for checking links, or getting the last-revised time of a URL), and two functions for saving/mirroring a URL to a local file. See L for the full details, or chapter 2 of I for more examples. =for comment ########################################################################## =head2 The Basics of the LWP Class Model LWP::Simple's functions are handy for simple cases, but its functions don't support cookies or authorization, don't support setting header lines in the HTTP request, generally don't support reading header lines in the HTTP response (notably the full HTTP error message, in case of an error). To get at all those features, you'll have to use the full LWP class model. While LWP consists of dozens of classes, the main two that you have to understand are L and L. LWP::UserAgent is a class for "virtual browsers" which you use for performing requests, and L is a class for the responses (or error messages) that you get back from those requests. The basic idiom is C<< $response = $browser->get($url) >>, or more fully illustrated: # Early in your program: use LWP 5.64; # Loads all important LWP classes, and makes # sure your version is reasonably recent. my $browser = LWP::UserAgent->new; ... # Then later, whenever you need to make a get request: my $url = 'http://www.npr.org/programs/fa/?todayDate=current'; my $response = $browser->get( $url ); die "Can't get $url -- ", $response->status_line unless $response->is_success; die "Hey, I was expecting HTML, not ", $response->content_type unless $response->content_type eq 'text/html'; # or whatever content-type you're equipped to deal with # Otherwise, process the content somehow: if($response->decoded_content =~ m/jazz/i) { print "They're talking about jazz today on Fresh Air!\n"; } else { print "Fresh Air is apparently jazzless today.\n"; } There are two objects involved: C<$browser>, which holds an object of class LWP::UserAgent, and then the C<$response> object, which is of class HTTP::Response. You really need only one browser object per program; but every time you make a request, you get back a new HTTP::Response object, which will have some interesting attributes: =over =item * A status code indicating success or failure (which you can test with C<< $response->is_success >>). =item * An HTTP status line that is hopefully informative if there's failure (which you can see with C<< $response->status_line >>, returning something like "404 Not Found"). =item * A MIME content-type like "text/html", "image/gif", "application/xml", etc., which you can see with C<< $response->content_type >> =item * The actual content of the response, in C<< $response->decoded_content >>. If the response is HTML, that's where the HTML source will be; if it's a GIF, then C<< $response->decoded_content >> will be the binary GIF data. =item * And dozens of other convenient and more specific methods that are documented in the docs for L, and its superclasses L and L. =back =for comment ########################################################################## =head2 Adding Other HTTP Request Headers The most commonly used syntax for requests is C<< $response = $browser->get($url) >>, but in truth, you can add extra HTTP header lines to the request by adding a list of key-value pairs after the URL, like so: $response = $browser->get( $url, $key1, $value1, $key2, $value2, ... ); For example, here's how to send some commonly used headers, in case you're dealing with a site that would otherwise reject your request: my @ns_headers = ( 'User-Agent' => 'Mozilla/4.76 [en] (Win98; U)', 'Accept' => 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*', 'Accept-Charset' => 'iso-8859-1,*,utf-8', 'Accept-Language' => 'en-US', ); ... $response = $browser->get($url, @ns_headers); If you weren't reusing that array, you could just go ahead and do this: $response = $browser->get($url, 'User-Agent' => 'Mozilla/4.76 [en] (Win98; U)', 'Accept' => 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*', 'Accept-Charset' => 'iso-8859-1,*,utf-8', 'Accept-Language' => 'en-US', ); If you were only ever changing the 'User-Agent' line, you could just change the C<$browser> object's default line from "libwww-perl/5.65" (or the like) to whatever you like, using the LWP::UserAgent C method: $browser->agent('Mozilla/4.76 [en] (Win98; U)'); =for comment ########################################################################## =head2 Enabling Cookies A default LWP::UserAgent object acts like a browser with its cookies support turned off. There are various ways of turning it on, by setting its C attribute. A "cookie jar" is an object representing a little database of all the HTTP cookies that a browser knows about. It can correspond to a file on disk or an in-memory object that starts out empty, and whose collection of cookies will disappear once the program is finished running. To give a browser an in-memory empty cookie jar, you set its C attribute like so: use HTTP::CookieJar::LWP; $browser->cookie_jar( HTTP::CookieJar::LWP->new ); To save a cookie jar to disk, see L<< HTTP::CookieJar/dump_cookies >>. To load cookies from disk into a jar, see L<< HTTP::CookieJar/load_cookies >>. =for comment ########################################################################## =head2 Posting Form Data Many HTML forms send data to their server using an HTTP POST request, which you can send with this syntax: $response = $browser->post( $url, [ formkey1 => value1, formkey2 => value2, ... ], ); Or if you need to send HTTP headers: $response = $browser->post( $url, [ formkey1 => value1, formkey2 => value2, ... ], headerkey1 => value1, headerkey2 => value2, ); For example, the following program makes a search request to AltaVista (by sending some form data via an HTTP POST request), and extracts from the HTML the report of the number of matches: use strict; use warnings; use LWP 5.64; my $browser = LWP::UserAgent->new; my $word = 'tarragon'; my $url = 'http://search.yahoo.com/yhs/search'; my $response = $browser->post( $url, [ 'q' => $word, # the Altavista query string 'fr' => 'altavista', 'pg' => 'q', 'avkw' => 'tgz', 'kl' => 'XX', ] ); die "$url error: ", $response->status_line unless $response->is_success; die "Weird content type at $url -- ", $response->content_type unless $response->content_is_html; if( $response->decoded_content =~ m{([0-9,]+)(?:<.*?>)? results for} ) { # The substring will be like "996,000 results for" print "$word: $1\n"; } else { print "Couldn't find the match-string in the response\n"; } =for comment ########################################################################## =head2 Sending GET Form Data Some HTML forms convey their form data not by sending the data in an HTTP POST request, but by making a normal GET request with the data stuck on the end of the URL. For example, if you went to C and ran a search on "Blade Runner", the URL you'd see in your browser window would be: http://www.imdb.com/find?s=all&q=Blade+Runner To run the same search with LWP, you'd use this idiom, which involves the URI class: use URI; my $url = URI->new( 'http://www.imdb.com/find' ); # makes an object representing the URL $url->query_form( # And here the form data pairs: 'q' => 'Blade Runner', 's' => 'all', ); my $response = $browser->get($url); See chapter 5 of I for a longer discussion of HTML forms and of form data, and chapters 6 through 9 for a longer discussion of extracting data from HTML. =head2 Absolutizing URLs The URI class that we just mentioned above provides all sorts of methods for accessing and modifying parts of URLs (such as asking sort of URL it is with C<< $url->scheme >>, and asking what host it refers to with C<< $url->host >>, and so on, as described in L. However, the methods of most immediate interest are the C method seen above, and now the C method for taking a probably-relative URL string (like "../foo.html") and getting back an absolute URL (like "http://www.perl.com/stuff/foo.html"), as shown here: use URI; $abs = URI->new_abs($maybe_relative, $base); For example, consider this program that matches URLs in the HTML list of new modules in CPAN: use strict; use warnings; use LWP; my $browser = LWP::UserAgent->new; my $url = 'http://www.cpan.org/RECENT.html'; my $response = $browser->get($url); die "Can't get $url -- ", $response->status_line unless $response->is_success; my $html = $response->decoded_content; while( $html =~ m/ method, by changing the C loop to this: while( $html =~ m/new_abs( $1, $response->base ) ,"\n"; } (The C<< $response->base >> method from L is for returning what URL should be used for resolving relative URLs -- it's usually just the same as the URL that you requested.) That program then emits nicely absolute URLs: http://www.cpan.org/MIRRORING.FROM http://www.cpan.org/RECENT http://www.cpan.org/RECENT.html http://www.cpan.org/authors/00whois.html http://www.cpan.org/authors/01mailrc.txt.gz http://www.cpan.org/authors/id/A/AA/AASSAD/CHECKSUMS ... See chapter 4 of I for a longer discussion of URI objects. Of course, using a regexp to match hrefs is a bit simplistic, and for more robust programs, you'll probably want to use an HTML-parsing module like L or L or even maybe L. =for comment ########################################################################## =head2 Other Browser Attributes LWP::UserAgent objects have many attributes for controlling how they work. Here are a few notable ones: =over =item * C<< $browser->timeout(15); >> This sets this browser object to give up on requests that don't answer within 15 seconds. =item * C<< $browser->protocols_allowed( [ 'http', 'gopher'] ); >> This sets this browser object to not speak any protocols other than HTTP and gopher. If it tries accessing any other kind of URL (like an "ftp:" or "mailto:" or "news:" URL), then it won't actually try connecting, but instead will immediately return an error code 500, with a message like "Access to 'ftp' URIs has been disabled". =item * C<< use LWP::ConnCache; $browser->conn_cache(LWP::ConnCache->new()); >> This tells the browser object to try using the HTTP/1.1 "Keep-Alive" feature, which speeds up requests by reusing the same socket connection for multiple requests to the same server. =item * C<< $browser->agent( 'SomeName/1.23 (more info here maybe)' ) >> This changes how the browser object will identify itself in the default "User-Agent" line is its HTTP requests. By default, it'll send "libwww-perl/I", like "libwww-perl/5.65". You can change that to something more descriptive like this: $browser->agent( 'SomeName/3.14 (contact@robotplexus.int)' ); Or if need be, you can go in disguise, like this: $browser->agent( 'Mozilla/4.0 (compatible; MSIE 5.12; Mac_PowerPC)' ); =item * C<< push @{ $ua->requests_redirectable }, 'POST'; >> This tells this browser to obey redirection responses to POST requests (like most modern interactive browsers), even though the HTTP RFC says that should not normally be done. =back For more options and information, see L. =for comment ########################################################################## =head2 Writing Polite Robots If you want to make sure that your LWP-based program respects F files and doesn't make too many requests too fast, you can use the LWP::RobotUA class instead of the LWP::UserAgent class. LWP::RobotUA class is just like LWP::UserAgent, and you can use it like so: use LWP::RobotUA; my $browser = LWP::RobotUA->new('YourSuperBot/1.34', 'you@yoursite.com'); # Your bot's name and your email address my $response = $browser->get($url); But HTTP::RobotUA adds these features: =over =item * If the F on C<$url>'s server forbids you from accessing C<$url>, then the C<$browser> object (assuming it's of class LWP::RobotUA) won't actually request it, but instead will give you back (in C<$response>) a 403 error with a message "Forbidden by robots.txt". That is, if you have this line: die "$url -- ", $response->status_line, "\nAborted" unless $response->is_success; then the program would die with an error message like this: http://whatever.site.int/pith/x.html -- 403 Forbidden by robots.txt Aborted at whateverprogram.pl line 1234 =item * If this C<$browser> object sees that the last time it talked to C<$url>'s server was too recently, then it will pause (via C) to avoid making too many requests too often. How long it will pause for, is by default one minute -- but you can control it with the C<< $browser->delay( I ) >> attribute. For example, this code: $browser->delay( 7/60 ); ...means that this browser will pause when it needs to avoid talking to any given server more than once every 7 seconds. =back For more options and information, see L. =for comment ########################################################################## =head2 Using Proxies In some cases, you will want to (or will have to) use proxies for accessing certain sites and/or using certain protocols. This is most commonly the case when your LWP program is running (or could be running) on a machine that is behind a firewall. To make a browser object use proxies that are defined in the usual environment variables (C, etc.), just call the C on a user-agent object before you go making any requests on it. Specifically: use LWP::UserAgent; my $browser = LWP::UserAgent->new; # And before you go making any requests: $browser->env_proxy; For more information on proxy parameters, see L, specifically the C, C, and C methods. =for comment ########################################################################## =head2 HTTP Authentication Many web sites restrict access to documents by using "HTTP Authentication". This isn't just any form of "enter your password" restriction, but is a specific mechanism where the HTTP server sends the browser an HTTP code that says "That document is part of a protected 'realm', and you can access it only if you re-request it and add some special authorization headers to your request". For example, the Unicode.org admins stop email-harvesting bots from harvesting the contents of their mailing list archives, by protecting them with HTTP Authentication, and then publicly stating the username and password (at C) -- namely username "unicode-ml" and password "unicode". For example, consider this URL, which is part of the protected area of the web site: http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html If you access that with a browser, you'll get a prompt like "Enter username and password for 'Unicode-MailList-Archives' at server 'www.unicode.org'". In LWP, if you just request that URL, like this: use LWP; my $browser = LWP::UserAgent->new; my $url = 'http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html'; my $response = $browser->get($url); die "Error: ", $response->header('WWW-Authenticate') || 'Error accessing', # ('WWW-Authenticate' is the realm-name) "\n ", $response->status_line, "\n at $url\n Aborting" unless $response->is_success; Then you'll get this error: Error: Basic realm="Unicode-MailList-Archives" 401 Authorization Required at http://www.unicode.org/mail-arch/unicode-ml/y2002-m08/0067.html Aborting at auth1.pl line 9. [or wherever] ...because the C<$browser> doesn't know any the username and password for that realm ("Unicode-MailList-Archives") at that host ("www.unicode.org"). The simplest way to let the browser know about this is to use the C method to let it know about a username and password that it can try using for that realm at that host. The syntax is: $browser->credentials( 'servername:portnumber', 'realm-name', 'username' => 'password' ); In most cases, the port number is 80, the default TCP/IP port for HTTP; and you usually call the C method before you make any requests. For example: $browser->credentials( 'reports.mybazouki.com:80', 'web_server_usage_reports', 'plinky' => 'banjo123' ); So if we add the following to the program above, right after the C<< $browser = LWP::UserAgent->new; >> line... $browser->credentials( # add this to our $browser 's "key ring" 'www.unicode.org:80', 'Unicode-MailList-Archives', 'unicode-ml' => 'unicode' ); ...then when we run it, the request succeeds, instead of causing the C to be called. =for comment ########################################################################## =head2 Accessing HTTPS URLs When you access an HTTPS URL, it'll work for you just like an HTTP URL would -- if your LWP installation has HTTPS support (via an appropriate Secure Sockets Layer library). For example: use LWP; my $url = 'https://www.paypal.com/'; # Yes, HTTPS! my $browser = LWP::UserAgent->new; my $response = $browser->get($url); die "Error at $url\n ", $response->status_line, "\n Aborting" unless $response->is_success; print "Whee, it worked! I got that ", $response->content_type, " document!\n"; If your LWP installation doesn't have HTTPS support set up, then the response will be unsuccessful, and you'll get this error message: Error at https://www.paypal.com/ 501 Protocol scheme 'https' is not supported Aborting at paypal.pl line 7. [or whatever program and line] If your LWP installation I have HTTPS support installed, then the response should be successful, and you should be able to consult C<$response> just like with any normal HTTP response. For information about installing HTTPS support for your LWP installation, see the helpful F file that comes in the libwww-perl distribution. =for comment ########################################################################## =head2 Getting Large Documents When you're requesting a large (or at least potentially large) document, a problem with the normal way of using the request methods (like C<< $response = $browser->get($url) >>) is that the response object in memory will have to hold the whole document -- I. If the response is a thirty megabyte file, this is likely to be quite an imposition on this process's memory usage. A notable alternative is to have LWP save the content to a file on disk, instead of saving it up in memory. This is the syntax to use: $response = $ua->get($url, ':content_file' => $filespec, ); For example, $response = $ua->get('http://search.cpan.org/', ':content_file' => '/tmp/sco.html' ); When you use this C<:content_file> option, the C<$response> will have all the normal header lines, but C<< $response->content >> will be empty. Errors writing to the content file (for example due to permission denied or the filesystem being full) will be reported via the C or C response headers, and not the C method: if ($response->header('Client-Aborted') eq 'die') { # handle error ... Note that this ":content_file" option isn't supported under older versions of LWP, so you should consider adding C to check the LWP version, if you think your program might run on systems with older versions. If you need to be compatible with older LWP versions, then use this syntax, which does the same thing: use HTTP::Request::Common; $response = $ua->request( GET($url), $filespec ); =for comment ########################################################################## =head1 SEE ALSO Remember, this article is just the most rudimentary introduction to LWP -- to learn more about LWP and LWP-related tasks, you really must read from the following: =over =item * L -- simple functions for getting/heading/mirroring URLs =item * L -- overview of the libwww-perl modules =item * L -- the class for objects that represent "virtual browsers" =item * L -- the class for objects that represent the response to a LWP response, as in C<< $response = $browser->get(...) >> =item * L and L -- classes that provide more methods to HTTP::Response. =item * L -- class for objects that represent absolute or relative URLs =item * L -- functions for URL-escaping and URL-unescaping strings (like turning "this & that" to and from "this%20%26%20that"). =item * L -- functions for HTML-escaping and HTML-unescaping strings (like turning "C. & E. BrontE" to and from "C. & E. Brontë") =item * L and L -- classes for parsing HTML =item * L -- class for finding links in HTML documents =item * The book I by Sean M. Burke. O'Reilly & Associates, 2002. ISBN: 0-596-00178-9, L. The whole book is also available free online: L. =back =head1 COPYRIGHT Copyright 2002, Sean M. Burke. You can redistribute this document and/or modify it, but only under the same terms as Perl itself. =head1 AUTHOR Sean M. Burke C =for comment ########################################################################## =cut # End of Pod PK!vy!11perl5/HTML/Tagset.pmnu6$package HTML::Tagset; use strict; =head1 NAME HTML::Tagset - data tables useful in parsing HTML =head1 VERSION Version 3.24 =cut our $VERSION = '3.24'; =head1 SYNOPSIS use HTML::Tagset; # Then use any of the items in the HTML::Tagset package # as need arises =head1 DESCRIPTION This module contains several data tables useful in various kinds of HTML parsing operations. Note that all tag names used are lowercase. In the following documentation, a "hashset" is a hash being used as a set -- the hash conveys that its keys are there, and the actual values associated with the keys are not significant. (But what values are there, are always true.) =head1 VARIABLES Note that none of these variables are exported. =head2 hashset %HTML::Tagset::emptyElement This hashset has as values the tag-names (GIs) of elements that cannot have content. (For example, "base", "br", "hr".) So C<$HTML::Tagset::emptyElement{'hr'}> exists and is true. C<$HTML::Tagset::emptyElement{'dl'}> does not exist, and so is not true. =cut our %emptyElement = map { $_ => 1 } qw( base link meta isindex img br hr wbr input area param embed bgsound spacer basefont col frame ~comment ~literal ~declaration ~pi ); # The "~"-initial names are for pseudo-elements used by HTML::Entities # and TreeBuilder =head2 hashset %HTML::Tagset::optionalEndTag This hashset lists tag-names for elements that can have content, but whose end-tags are generally, "safely", omissible. Example: C<$HTML::Tagset::emptyElement{'li'}> exists and is true. =cut our %optionalEndTag = map { $_ => 1 } qw( p li dt dd ); # option th tr td); =head2 hash %HTML::Tagset::linkElements Values in this hash are tagnames for elements that might contain links, and the value for each is a reference to an array of the names of attributes whose values can be links. =cut our %linkElements = ( 'a' => ['href'], 'applet' => ['archive', 'codebase', 'code'], 'area' => ['href'], 'base' => ['href'], 'bgsound' => ['src'], 'blockquote' => ['cite'], 'body' => ['background'], 'del' => ['cite'], 'embed' => ['pluginspage', 'src'], 'form' => ['action'], 'frame' => ['src', 'longdesc'], 'iframe' => ['src', 'longdesc'], 'ilayer' => ['background'], 'img' => ['src', 'lowsrc', 'longdesc', 'usemap'], 'input' => ['src', 'usemap'], 'ins' => ['cite'], 'isindex' => ['action'], 'head' => ['profile'], 'layer' => ['background', 'src'], 'link' => ['href'], 'object' => ['classid', 'codebase', 'data', 'archive', 'usemap'], 'q' => ['cite'], 'script' => ['src', 'for'], 'table' => ['background'], 'td' => ['background'], 'th' => ['background'], 'tr' => ['background'], 'xmp' => ['href'], ); =head2 hash %HTML::Tagset::boolean_attr This hash (not hashset) lists what attributes of what elements can be printed without showing the value (for example, the "noshade" attribute of "hr" elements). For elements with only one such attribute, its value is simply that attribute name. For elements with many such attributes, the value is a reference to a hashset containing all such attributes. =cut our %boolean_attr = ( # TODO: make these all hashes 'area' => 'nohref', 'dir' => 'compact', 'dl' => 'compact', 'hr' => 'noshade', 'img' => 'ismap', 'input' => { 'checked' => 1, 'readonly' => 1, 'disabled' => 1 }, 'menu' => 'compact', 'ol' => 'compact', 'option' => 'selected', 'select' => 'multiple', 'td' => 'nowrap', 'th' => 'nowrap', 'ul' => 'compact', ); #========================================================================== # List of all elements from Extensible HTML version 1.0 Transitional DTD: # # a abbr acronym address applet area b base basefont bdo big # blockquote body br button caption center cite code col colgroup # dd del dfn dir div dl dt em fieldset font form h1 h2 h3 h4 h5 h6 # head hr html i iframe img input ins isindex kbd label legend li # link map menu meta noframes noscript object ol optgroup option p # param pre q s samp script select small span strike strong style # sub sup table tbody td textarea tfoot th thead title tr tt u ul # var # # Varia from Mozilla source internal table of tags: # Implemented: # xmp listing wbr nobr frame frameset noframes ilayer # layer nolayer spacer embed multicol # But these are unimplemented: # sound?? keygen?? server?? # Also seen here and there: # marquee?? app?? (both unimplemented) #========================================================================== =head2 hashset %HTML::Tagset::isPhraseMarkup This hashset contains all phrasal-level elements. =cut our %isPhraseMarkup = map { $_ => 1 } qw( span abbr acronym q sub sup cite code em kbd samp strong var dfn strike b i u s tt small big ins del a img br wbr nobr blink font basefont bdo spacer embed noembed ); # had: center, hr, table =head2 hashset %HTML::Tagset::is_Possible_Strict_P_Content This hashset contains all phrasal-level elements that be content of a P element, for a strict model of HTML. =cut our %isFormElement; # Forward declaration our %is_Possible_Strict_P_Content = ( %isPhraseMarkup, %isFormElement, map {; $_ => 1} qw( object script map ) # I've no idea why there's these latter exceptions. # I'm just following the HTML4.01 DTD. ); #from html4 strict: # # # # # # # # # # =head2 hashset %HTML::Tagset::isHeadElement This hashset contains all elements that elements that should be present only in the 'head' element of an HTML document. =cut our %isHeadElement = map { $_ => 1 } qw(title base link meta isindex script style object bgsound); =head2 hashset %HTML::Tagset::isList This hashset contains all elements that can contain "li" elements. =cut our %isList = map { $_ => 1 } qw( ul ol dir menu ); =head2 hashset %HTML::Tagset::isTableElement This hashset contains all elements that are to be found only in/under a "table" element. =cut our %isTableElement = map { $_ => 1 } qw(tr td th thead tbody tfoot caption col colgroup); =head2 hashset %HTML::Tagset::isFormElement This hashset contains all elements that are to be found only in/under a "form" element. =cut # Declared earlier in the file %isFormElement = map { $_ => 1 } qw(input select option optgroup textarea button label); =head2 hashset %HTML::Tagset::isBodyElement This hashset contains all elements that are to be found only in/under the "body" element of an HTML document. =cut our %isBodyElement = map { $_ => 1 } qw( h1 h2 h3 h4 h5 h6 p div pre plaintext address blockquote xmp listing center multicol iframe ilayer nolayer bgsound hr ol ul dir menu li dl dt dd ins del fieldset legend map area applet param object isindex script noscript table center form ), keys %isFormElement, keys %isPhraseMarkup, # And everything phrasal keys %isTableElement, ; =head2 hashset %HTML::Tagset::isHeadOrBodyElement This hashset includes all elements that I notice can fall either in the head or in the body. =cut our %isHeadOrBodyElement = map { $_ => 1 } qw(script isindex style object map area param noscript bgsound); # i.e., if we find 'script' in the 'body' or the 'head', don't freak out. =head2 hashset %HTML::Tagset::isKnown This hashset lists all known HTML elements. =cut our %isKnown = (%isHeadElement, %isBodyElement, map{ $_ => 1 } qw( head body html frame frameset noframes ~comment ~pi ~directive ~literal )); # that should be all known tags ever ever =head2 hashset %HTML::Tagset::canTighten This hashset lists elements that might have ignorable whitespace as children or siblings. =cut our %canTighten = %isKnown; delete @canTighten{ keys(%isPhraseMarkup), 'input', 'select', 'xmp', 'listing', 'plaintext', 'pre', }; # xmp, listing, plaintext, and pre are untightenable, and # in a really special way. @canTighten{'hr','br'} = (1,1); # exceptional 'phrasal' things that ARE subject to tightening. # The one case where I can think of my tightening rules failing is: #

    foo bar

    baz quux ... # ^-- that would get deleted. # But that's pretty gruesome code anyhow. You gets what you pays for. #========================================================================== =head2 array @HTML::Tagset::p_closure_barriers This array has a meaning that I have only seen a need for in C, but I include it here on the off chance that someone might find it of use: When we see a "EpE" token, we go lookup up the lineage for a p element we might have to minimize. At first sight, we might say that if there's a p anywhere in the lineage of this new p, it should be closed. But that's wrong. Consider this document: foo

    foo
    foo

    bar

    The second p is quite legally inside a much higher p. My formalization of the reason why this is legal, but this:

    foo

    bar

    isn't, is that something about the table constitutes a "barrier" to the application of the rule about what p must minimize. So C<@HTML::Tagset::p_closure_barriers> is the list of all such barrier-tags. =cut our @p_closure_barriers = qw( li blockquote ul ol menu dir dl dt dd td th tr table caption div ); # In an ideal world (i.e., XHTML) we wouldn't have to bother with any of this # monkey business of barriers to minimization! =head2 hashset %isCDATA_Parent This hashset includes all elements whose content is CDATA. =cut our %isCDATA_Parent = map { $_ => 1 } qw(script style xmp listing plaintext); # TODO: there's nothing else that takes CDATA children, right? # As the HTML3 DTD (Raggett 1995-04-24) noted: # The XMP, LISTING and PLAINTEXT tags are incompatible with SGML # and derive from very early versions of HTML. They require non- # standard parsers and will cause problems for processing # documents with standard SGML tools. =head1 CAVEATS You may find it useful to alter the behavior of modules (like C or C) that use C's data tables by altering the data tables themselves. You are welcome to try, but be careful; and be aware that different modules may or may react differently to the data tables being changed. Note that it may be inappropriate to use these tables for I HTML -- for example, C<%isHeadOrBodyElement> lists the tagnames for all elements that can appear either in the head or in the body, such as "script". That doesn't mean that I am saying your code that produces HTML should feel free to put script elements in either place! If you are producing programs that spit out HTML, you should be I familiar with the DTDs for HTML or XHTML (available at C), and you should slavishly obey them, not the data tables in this document. =head1 SEE ALSO L, L, L =head1 COPYRIGHT & LICENSE Copyright 1995-2000 Gisle Aas. Copyright 2000-2005 Sean M. Burke. Copyright 2005-2024 Andy Lester. This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License version 2.0. =head1 ACKNOWLEDGEMENTS Most of the code/data in this module was adapted from code written by Gisle Aas for C, C, and C. Then it was maintained by Sean M. Burke. =head1 AUTHOR Current maintainer: Andy Lester, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =cut 1; PK!4;{g perl5/lib/core/only.pmnu6$package lib::core::only; use strict; use warnings FATAL => 'all'; use Config; sub import { @INC = @Config{qw(privlibexp archlibexp)}; return } =head1 NAME lib::core::only - Remove all non-core paths from @INC to avoid site/vendor dirs =head1 SYNOPSIS use lib::core::only; # now @INC contains only the two core directories To get only the core directories plus the ones for the local::lib in scope: $ perl -mlocal::lib -Mlib::core::only -Mlocal::lib=~/perl5 myscript.pl To attempt to do a self-contained build (but note this will not reliably propagate into subprocesses, see the CAVEATS below): $ PERL5OPT='-mlocal::lib -Mlib::core::only -Mlocal::lib=~/perl5' cpan Please note that it is necessary to use C twice for this to work. First so that C doesn't prevent C from loading (it's not currently in core) and then again after C so that the local paths are not removed. =head1 DESCRIPTION lib::core::only is simply a shortcut to say "please reduce my @INC to only the core lib and archlib (architecture-specific lib) directories of this perl". You might want to do this to ensure a local::lib contains only the code you need, or to test an L tree, or to avoid known bad vendor packages. You might want to use this to try and install a self-contained tree of perl modules. Be warned that that probably won't work (see L). This module was extracted from L's --self-contained feature, and contains the only part that ever worked. I apologise to anybody who thought anything else did. =head1 CAVEATS This does B propagate properly across perl invocations like local::lib's stuff does. It can't. It's only a module import, so it B. If you want to cascade it across invocations, you can set the PERL5OPT environment variable to '-Mlib::core::only' and it'll sort of work. But be aware that taint mode ignores this, so some modules' build and test code probably will as well. You also need to be aware that perl's command line options are not processed in order - -I options take effect before -M options, so perl -Mlib::core::only -Ilib is unlike to do what you want - it's exactly equivalent to: perl -Mlib::core::only If you want to combine a core-only @INC with additional paths, you need to add the additional paths using -M options and the L module: perl -Mlib::core::only -Mlib=lib # or if you're trying to test compiled code: perl -Mlib::core::only -Mblib For more information on the impossibility of sanely propagating this across module builds without help from the build program, see L - and for ways to achieve the old --self-contained feature's results, look at L's tree function, and at L's --local-lib-contained feature. =head1 AUTHOR Matt S. Trout =head1 LICENSE This library is free software under the same terms as perl itself. =head1 COPYRIGHT (c) 2010 the lib::core::only L as specified above. =cut 1; PK!qperl5/cPanelUserConfig.pmnu[# cpanel - cPanelUserConfig.pm Copyright(c) 2021 cPanel, L.L.C. # All Rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited if ( $> != 0 ) { my $b__dir = ( getpwuid($>) )[7] . '/perl'; unshift @INC, $b__dir . '5/lib/perl5', $b__dir . '5/lib/perl5/x86_64-linux-thread-multi', map { $b__dir . $_ } grep {$_ ne '.'} @INC; } 1; PK! 3 3 perl5/AppConfig/Getopt.pmnu6$#============================================================================ # # AppConfig::Getopt.pm # # Perl5 module to interface AppConfig::* to Johan Vromans' Getopt::Long # module. Getopt::Long implements the POSIX standard for command line # options, with GNU extensions, and also traditional one-letter options. # AppConfig::Getopt constructs the necessary Getopt:::Long configuration # from the internal AppConfig::State and delegates the parsing of command # line arguments to it. Internal variable values are updated by callback # from GetOptions(). # # Written by Andy Wardley # # Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. # #============================================================================ package AppConfig::Getopt; use 5.006; use strict; use warnings; use AppConfig::State; use Getopt::Long 2.17; our $VERSION = '1.71'; #------------------------------------------------------------------------ # new($state, \@args) # # Module constructor. The first, mandatory parameter should be a # reference to an AppConfig::State object to which all actions should # be applied. The second parameter may be a reference to a list of # command line arguments. This list reference is passed to parse() for # processing. # # Returns a reference to a newly created AppConfig::Getopt object. #------------------------------------------------------------------------ sub new { my $class = shift; my $state = shift; my $self = { STATE => $state, }; bless $self, $class; # call parse() to parse any arg list passed $self->parse(@_) if @_; return $self; } #------------------------------------------------------------------------ # parse(@$config, \@args) # # Constructs the appropriate configuration information and then delegates # the task of processing command line options to Getopt::Long. # # Returns 1 on success or 0 if one or more warnings were raised. #------------------------------------------------------------------------ sub parse { my $self = shift; my $state = $self->{ STATE }; my (@config, $args, $getopt); local $" = ', '; # we trap $SIG{__WARN__} errors and patch them into AppConfig::State local $SIG{__WARN__} = sub { my $msg = shift; # AppConfig::State doesn't expect CR terminated error messages # and it uses printf, so we protect any embedded '%' chars chomp($msg); $state->_error("%s", $msg); }; # slurp all config items into @config push(@config, shift) while defined $_[0] && ! ref($_[0]); # add debug status if appropriate (hmm...can't decide about this) # push(@config, 'debug') if $state->_debug(); # next parameter may be a reference to a list of args $args = shift; # copy any args explicitly specified into @ARGV @ARGV = @$args if defined $args; # we enclose in an eval block because constructor may die() eval { # configure Getopt::Long Getopt::Long::Configure(@config); # construct options list from AppConfig::State variables my @opts = $self->{ STATE }->_getopt_state(); # DEBUG if ($state->_debug()) { print STDERR "Calling GetOptions(@opts)\n"; print STDERR "\@ARGV = (@ARGV)\n"; }; # call GetOptions() with specifications constructed from the state $getopt = GetOptions(@opts); }; if ($@) { chomp($@); $state->_error("%s", $@); return 0; } # udpdate any args reference passed to include only that which is left # in @ARGV @$args = @ARGV if defined $args; return $getopt; } #======================================================================== # AppConfig::State #======================================================================== package AppConfig::State; #------------------------------------------------------------------------ # _getopt_state() # # Constructs option specs in the Getopt::Long format for each variable # definition. # # Returns a list of specification strings. #------------------------------------------------------------------------ sub _getopt_state { my $self = shift; my ($var, $spec, $args, $argcount, @specs); my $linkage = sub { $self->set(@_) }; foreach $var (keys %{ $self->{ VARIABLE } }) { $spec = join('|', $var, @{ $self->{ ALIASES }->{ $var } || [ ] }); # an ARGS value is used, if specified unless (defined ($args = $self->{ ARGS }->{ $var })) { # otherwise, construct a basic one from ARGCOUNT ARGCOUNT: { last ARGCOUNT unless defined ($argcount = $self->{ ARGCOUNT }->{ $var }); $args = "=s", last ARGCOUNT if $argcount eq ARGCOUNT_ONE; $args = "=s@", last ARGCOUNT if $argcount eq ARGCOUNT_LIST; $args = "=s%", last ARGCOUNT if $argcount eq ARGCOUNT_HASH; $args = "!"; } } $spec .= $args if defined $args; push(@specs, $spec, $linkage); } return @specs; } 1; __END__ =head1 NAME AppConfig::Getopt - Perl5 module for processing command line arguments via delegation to Getopt::Long. =head1 SYNOPSIS use AppConfig::Getopt; my $state = AppConfig::State->new(\%cfg); my $getopt = AppConfig::Getopt->new($state); $getopt->parse(\@args); # read args =head1 OVERVIEW AppConfig::Getopt is a Perl5 module which delegates to Johan Vroman's Getopt::Long module to parse command line arguments and update values in an AppConfig::State object accordingly. AppConfig::Getopt is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::Getopt MODULE To import and use the AppConfig::Getopt module the following line should appear in your Perl script: use AppConfig::Getopt; AppConfig::Getopt is used automatically if you use the AppConfig module and create an AppConfig::Getopt object through the getopt() method. AppConfig::Getopt is implemented using object-oriented methods. A new AppConfig::Getopt object is created and initialised using the new() method. This returns a reference to a new AppConfig::Getopt object. A reference to an AppConfig::State object should be passed in as the first parameter: my $state = AppConfig::State->new(); my $getopt = AppConfig::Getopt->new($state); This will create and return a reference to a new AppConfig::Getopt object. =head2 PARSING COMMAND LINE ARGUMENTS The C method is used to read a list of command line arguments and update the state accordingly. The first (non-list reference) parameters may contain a number of configuration strings to pass to Getopt::Long::Configure. A reference to a list of arguments may additionally be passed or @ARGV is used by default. $getopt->parse(); # uses @ARGV $getopt->parse(\@myargs); $getopt->parse(qw(auto_abbrev debug)); # uses @ARGV $getopt->parse(qw(debug), \@myargs); See Getopt::Long for details of the configuartion options available. A Getopt::Long specification string is constructed for each variable defined in the AppConfig::State. This consists of the name, any aliases and the ARGS value for the variable. These specification string are then passed to Getopt::Long, the arguments are parsed and the values in the AppConfig::State updated. See AppConfig for information about using the AppConfig::Getopt module via the getopt() method. =head1 AUTHOR Andy Wardley, Eabw@wardley.orgE =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 ACKNOWLEDGMENTS Many thanks are due to Johan Vromans for the Getopt::Long module. He was kind enough to offer assistance and access to early releases of his code to enable this module to be written. =head1 SEE ALSO AppConfig, AppConfig::State, AppConfig::Args, Getopt::Long =cut PK!Lperl5/AppConfig/CGI.pmnu6$#============================================================================ # # AppConfig::CGI.pm # # Perl5 module to provide a CGI interface to AppConfig. Internal variables # may be set through the CGI "arguments" appended to a URL. # # Written by Andy Wardley # # Copyright (C) 1997-2003 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. # #============================================================================ package AppConfig::CGI; use 5.006; use strict; use warnings; use AppConfig::State; our $VERSION = '1.71'; #------------------------------------------------------------------------ # new($state, $query) # # Module constructor. The first, mandatory parameter should be a # reference to an AppConfig::State object to which all actions should # be applied. The second parameter may be a string containing a CGI # QUERY_STRING which is then passed to parse() to process. If no second # parameter is specifiied then the parse() process is skipped. # # Returns a reference to a newly created AppConfig::CGI object. #------------------------------------------------------------------------ sub new { my $class = shift; my $state = shift; my $self = { STATE => $state, # AppConfig::State ref DEBUG => $state->_debug(), # store local copy of debug PEDANTIC => $state->_pedantic, # and pedantic flags }; bless $self, $class; # call parse(@_) to parse any arg list passed $self->parse(@_) if @_; return $self; } #------------------------------------------------------------------------ # parse($query) # # Method used to parse a CGI QUERY_STRING and set internal variable # values accordingly. If a query is not passed as the first parameter, # then _get_cgi_query() is called to try to determine the query by # examing the environment as per CGI protocol. # # Returns 0 if one or more errors or warnings were raised or 1 if the # string parsed successfully. #------------------------------------------------------------------------ sub parse { my $self = shift; my $query = shift; my $warnings = 0; my ($variable, $value, $nargs); # take a local copy of the state to avoid much hash dereferencing my ($state, $debug, $pedantic) = @$self{ qw( STATE DEBUG PEDANTIC ) }; # get the cgi query if not defined $query = $ENV{ QUERY_STRING } unless defined $query; # no query to process return 1 unless defined $query; # we want to install a custom error handler into the AppConfig::State # which appends filename and line info to error messages and then # calls the previous handler; we start by taking a copy of the # current handler.. my $errhandler = $state->_ehandler(); # install a closure as a new error handler $state->_ehandler( sub { # modify the error message my $format = shift; $format =~ s//>/g; $format = "

    \n[ AppConfig::CGI error: $format ] \n

    \n"; # send error to stdout for delivery to web client printf($format, @_); } ); PARAM: foreach (split('&', $query)) { # extract parameter and value from query token ($variable, $value) = map { _unescape($_) } split('='); # check an argument was provided if one was expected if ($nargs = $state->_argcount($variable)) { unless (defined $value) { $state->_error("$variable expects an argument"); $warnings++; last PARAM if $pedantic; next; } } # default an undefined value to 1 if ARGCOUNT_NONE else { $value = 1 unless defined $value; } # set the variable, noting any error unless ($state->set($variable, $value)) { $warnings++; last PARAM if $pedantic; } } # restore original error handler $state->_ehandler($errhandler); # return $warnings => 0, $success => 1 return $warnings ? 0 : 1; } # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # The following sub-routine was lifted from Lincoln Stein's CGI.pm # module, version 2.36. Name has been prefixed by a '_'. # unescape URL-encoded data sub _unescape { my($todecode) = @_; $todecode =~ tr/+/ /; # pluses become spaces $todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge; return $todecode; } # # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1; __END__ =head1 NAME AppConfig::CGI - Perl5 module for processing CGI script parameters. =head1 SYNOPSIS use AppConfig::CGI; my $state = AppConfig::State->new(\%cfg); my $cgi = AppConfig::CGI->new($state); $cgi->parse($cgi_query); $cgi->parse(); # looks for CGI query in environment =head1 OVERVIEW AppConfig::CGI is a Perl5 module which implements a CGI interface to AppConfig. It examines the QUERY_STRING environment variable, or a string passed explicitly by parameter, which represents the additional parameters passed to a CGI query. This is then used to update variable values in an AppConfig::State object accordingly. AppConfig::CGI is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::CGI MODULE To import and use the AppConfig::CGI module the following line should appear in your Perl script: use AppConfig::CGI; AppConfig::CGI is used automatically if you use the AppConfig module and create an AppConfig::CGI object through the cgi() method. AppConfig::CGI is implemented using object-oriented methods. A new AppConfig::CGI object is created and initialised using the new() method. This returns a reference to a new AppConfig::CGI object. A reference to an AppConfig::State object should be passed in as the first parameter: my $state = AppConfig::State->new(); my $cgi = AppConfig::CGI->new($state); This will create and return a reference to a new AppConfig::CGI object. =head2 PARSING CGI QUERIES The C method is used to parse a CGI query which can be specified explicitly, or is automatically extracted from the "QUERY_STRING" CGI environment variable. This currently limits the module to only supporting the GET method. See AppConfig for information about using the AppConfig::CGI module via the cgi() method. =head1 AUTHOR Andy Wardley, Cabw@wardley.org> =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO AppConfig, AppConfig::State =cut PK!\perl5/AppConfig/Args.pmnu6$#============================================================================ # # AppConfig::Args.pm # # Perl5 module to read command line argument and update the variable # values in an AppConfig::State object accordingly. # # Written by Andy Wardley # # Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. #============================================================================ package AppConfig::Args; use 5.006; use strict; use warnings; use AppConfig::State; our $VERSION = '1.71'; #------------------------------------------------------------------------ # new($state, \@args) # # Module constructor. The first, mandatory parameter should be a # reference to an AppConfig::State object to which all actions should # be applied. The second parameter may be a reference to a list of # command line arguments. This list reference is passed to args() for # processing. # # Returns a reference to a newly created AppConfig::Args object. #------------------------------------------------------------------------ sub new { my $class = shift; my $state = shift; my $self = { STATE => $state, # AppConfig::State ref DEBUG => $state->_debug(), # store local copy of debug PEDANTIC => $state->_pedantic, # and pedantic flags }; bless $self, $class; # call parse() to parse any arg list passed $self->parse(shift) if @_; return $self; } #------------------------------------------------------------------------ # parse(\@args) # # Examines the argument list and updates the contents of the # AppConfig::State referenced by $self->{ STATE } accordingly. If # no argument list is provided then the method defaults to examining # @ARGV. The method reports any warning conditions (such as undefined # variables) by calling $self->{ STATE }->_error() and then continues to # examine the rest of the list. If the PEDANTIC option is set in the # AppConfig::State object, this behaviour is overridden and the method # returns 0 immediately on any parsing error. # # Returns 1 on success or 0 if one or more warnings were raised. #------------------------------------------------------------------------ sub parse { my $self = shift; my $argv = shift || \@ARGV; my $warnings = 0; my ($arg, $nargs, $variable, $value); # take a local copy of the state to avoid much hash dereferencing my ($state, $debug, $pedantic) = @$self{ qw( STATE DEBUG PEDANTIC ) }; # loop around arguments ARG: while (@$argv && $argv->[0] =~ /^-/) { $arg = shift(@$argv); # '--' indicates the end of the options last if $arg eq '--'; # strip leading '-'; ($variable = $arg) =~ s/^-(-)?//; # test for '--' prefix and push back any '=value' item if (defined $1) { ($variable, $value) = split(/=/, $variable); unshift(@$argv, $value) if defined $value; } # check the variable exists if ($state->_exists($variable)) { # see if it expects any mandatory arguments $nargs = $state->_argcount($variable); if ($nargs) { # check there's another arg and it's not another '-opt' if(defined($argv->[0])) { $value = shift(@$argv); } else { $state->_error("$arg expects an argument"); $warnings++; last ARG if $pedantic; next; } } else { # set a value of 1 if option doesn't expect an argument $value = 1; } # set the variable with the new value $state->set($variable, $value); } else { $state->_error("$arg: invalid option"); $warnings++; last ARG if $pedantic; } } # return status return $warnings ? 0 : 1; } 1; __END__ =head1 NAME AppConfig::Args - Perl5 module for reading command line arguments. =head1 SYNOPSIS use AppConfig::Args; my $state = AppConfig::State->new(\%cfg); my $cfgargs = AppConfig::Args->new($state); $cfgargs->parse(\@args); # read args =head1 OVERVIEW AppConfig::Args is a Perl5 module which reads command line arguments and uses the options therein to update variable values in an AppConfig::State object. AppConfig::File is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::Args MODULE To import and use the AppConfig::Args module the following line should appear in your Perl script: use AppConfig::Args; AppConfig::Args is used automatically if you use the AppConfig module and create an AppConfig::Args object through the parse() method. AppConfig::File is implemented using object-oriented methods. A new AppConfig::Args object is created and initialised using the new() method. This returns a reference to a new AppConfig::File object. A reference to an AppConfig::State object should be passed in as the first parameter: my $state = AppConfig::State->new(); my $cfgargs = AppConfig::Args->new($state); This will create and return a reference to a new AppConfig::Args object. =head2 PARSING COMMAND LINE ARGUMENTS The C method is used to read a list of command line arguments and update the STATE accordingly. A reference to the list of arguments should be passed in. $cfgargs->parse(\@ARGV); If the method is called without a reference to an argument list then it will examine and manipulate @ARGV. If the PEDANTIC option is turned off in the AppConfig::State object, any parsing errors (invalid variables, unvalidated values, etc) will generate warnings, but not cause the method to return. Having processed all arguments, the method will return 1 if processed without warning or 0 if one or more warnings were raised. When the PEDANTIC option is turned on, the method generates a warning and immediately returns a value of 0 as soon as it encounters any parsing error. The method continues parsing arguments until it detects the first one that does not start with a leading dash, '-'. Arguments that constitute values for other options are not examined in this way. =head1 FUTURE DEVELOPMENT This module was developed to provide backwards compatibility (to some degree) with the preceeding App::Config module. The argument parsing it provides is basic but offers a quick and efficient solution for those times when simple option handling is all that is required. If you require more flexibility in parsing command line arguments, then you should consider using the AppConfig::Getopt module. This is loaded and used automatically by calling the AppConfig getopt() method. The AppConfig::Getopt module provides considerably extended functionality over the AppConfig::Args module by delegating out the task of argument parsing to Johan Vromans' Getopt::Long module. For advanced command-line parsing, this module (either Getopt::Long by itself, or in conjunction with AppConfig::Getopt) is highly recommended. =head1 AUTHOR Andy Wardley, Eabw@wardley.orgE =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO AppConfig, AppConfig::State, AppConfig::Getopt, Getopt::Long =cut PK!fOM^^perl5/AppConfig/File.pmnu6$#============================================================================ # # AppConfig::File.pm # # Perl5 module to read configuration files and use the contents therein # to update variable values in an AppConfig::State object. # # Written by Andy Wardley # # Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. # #============================================================================ package AppConfig::File; use 5.006; use strict; use warnings; use AppConfig; use AppConfig::State; our $VERSION = '1.71'; #------------------------------------------------------------------------ # new($state, $file, [$file, ...]) # # Module constructor. The first, mandatory parameter should be a # reference to an AppConfig::State object to which all actions should # be applied. The remaining parameters are assumed to be file names or # file handles for reading and are passed to parse(). # # Returns a reference to a newly created AppConfig::File object. #------------------------------------------------------------------------ sub new { my $class = shift; my $state = shift; my $self = { STATE => $state, # AppConfig::State ref DEBUG => $state->_debug(), # store local copy of debug PEDANTIC => $state->_pedantic, # and pedantic flags }; bless $self, $class; # call parse(@_) to parse any files specified as further params $self->parse(@_) if @_; return $self; } #------------------------------------------------------------------------ # parse($file, [file, ...]) # # Reads and parses a config file, updating the contents of the # AppConfig::State referenced by $self->{ STATE } according to the # contents of the file. Multiple files may be specified and are # examined in turn. The method reports any error condition via # $self->{ STATE }->_error() and immediately returns undef if it # encounters a system error (i.e. cannot open one of the files. # Parsing errors such as unknown variables or unvalidated values will # also cause warnings to be raised vi the same _error(), but parsing # continues to the end of the current file and through any subsequent # files. If the PEDANTIC option is set in the $self->{ STATE } object, # the behaviour is overridden and the method returns 0 immediately on # any system or parsing error. # # The EXPAND option for each variable determines how the variable # value should be expanded. # # Returns undef on system error, 0 if all files were parsed but generated # one or more warnings, 1 if all files parsed without warnings. #------------------------------------------------------------------------ sub parse { my $self = shift; my $warnings = 0; my $prefix; # [block] defines $prefix my $file; my $flag; # take a local copy of the state to avoid much hash dereferencing my ($state, $debug, $pedantic) = @$self{ qw( STATE DEBUG PEDANTIC ) }; # we want to install a custom error handler into the AppConfig::State # which appends filename and line info to error messages and then # calls the previous handler; we start by taking a copy of the # current handler.. my $errhandler = $state->_ehandler(); # ...and if it doesn't exist, we craft a default handler $errhandler = sub { warn(sprintf(shift, @_), "\n") } unless defined $errhandler; # install a closure as a new error handler $state->_ehandler( sub { # modify the error message my $format = shift; $format .= ref $file ? " at line $." : " at $file line $."; # chain call to prevous handler &$errhandler($format, @_); } ); # trawl through all files passed as params FILE: while ($file = shift) { # local/lexical vars ensure opened files get closed my $handle; local *FH; # if the file is a reference, we assume it's a file handle, if # not, we assume it's a filename and attempt to open it $handle = $file; if (ref($file)) { $handle = $file; # DEBUG print STDERR "reading from file handle: $file\n" if $debug; } else { # open and read config file open(FH, $file) or do { # restore original error handler and report error $state->_ehandler($errhandler); $state->_error("$file: $!"); return undef; }; $handle = \*FH; # DEBUG print STDERR "reading file: $file\n" if $debug; } # initialise $prefix to nothing (no [block]) $prefix = ''; local $_; while (<$handle>) { chomp; # Throw away everything from an unescaped # to EOL s/(^|\s+)#.*/$1/; # add next line if there is one and this is a continuation if (s/\\$// && !eof($handle)) { $_ .= <$handle>; redo; } # Convert \# -> # s/\\#/#/g; # ignore blank lines next if /^\s*$/; # strip leading and trailing whitespace s/^\s+//; s/\s+$//; # look for a [block] to set $prefix if (/^\[([^\]]+)\]$/) { $prefix = $1; print STDERR "Entering [$prefix] block\n" if $debug; next; } # split line up by whitespace (\s+) or "equals" (\s*=\s*) if (/^([^\s=]+)(?:(?:(?:\s*=\s*)|\s+)(.*))?/) { my ($variable, $value) = ($1, $2); if (defined $value) { # here document if ($value =~ /^([^\s=]+\s*=)?\s*<<(['"]?)(\S+)\2$/) { # '<) { last if $_ eq $boundary; $value .= $_; }; $value =~ s/[\r\n]$//; } else { # strip any quoting from the variable value $value =~ s/^(['"])(.*)\1$/$2/; }; }; # strip any leading '+/-' from the variable $variable =~ s/^([\-+]?)//; $flag = $1; # $variable gets any $prefix $variable = $prefix . '_' . $variable if length $prefix; # if the variable doesn't exist, we call set() to give # AppConfig::State a chance to auto-create it unless ($state->_exists($variable) || $state->set($variable, 1)) { $warnings++; last FILE if $pedantic; next; } my $nargs = $state->_argcount($variable); # variables prefixed '-' are reset to their default values if ($flag eq '-') { $state->_default($variable); next; } # those prefixed '+' get set to 1 elsif ($flag eq '+') { $value = 1 unless defined $value; } # determine if any extra arguments were expected if ($nargs) { if (defined $value && length $value) { # expand any embedded variables, ~uids or # environment variables, testing the return value # for errors; we pass in any variable-specific # EXPAND value unless ($self->_expand(\$value, $state->_expand($variable), $prefix)) { print STDERR "expansion of [$value] failed\n" if $debug; $warnings++; last FILE if $pedantic; } } else { $state->_error("$variable expects an argument"); $warnings++; last FILE if $pedantic; next; } } # $nargs = 0 else { # default value to 1 unless it is explicitly defined # as '0' or "off" if (defined $value) { # "off" => 0 $value = 0 if $value =~ /off/i; # any value => 1 $value = 1 if $value; } else { # assume 1 unless explicitly defined off/0 $value = 1; } print STDERR "$variable => $value (no expansion)\n" if $debug; } # set the variable, noting any failure from set() unless ($state->set($variable, $value)) { $warnings++; last FILE if $pedantic; } } else { $state->_error("parse error"); $warnings++; } } } # restore original error handler $state->_ehandler($errhandler); # return $warnings => 0, $success => 1 return $warnings ? 0 : 1; } #======================================================================== # ----- PRIVATE METHODS ----- #======================================================================== #------------------------------------------------------------------------ # _expand(\$value, $expand, $prefix) # # The variable value string, referenced by $value, is examined and any # embedded variables, environment variables or tilde globs (home # directories) are replaced with their respective values, depending on # the value of the second parameter, $expand. The third paramter may # specify the name of the current [block] in which the parser is # parsing. This prefix is prepended to any embedded variable name that # can't otherwise be resolved. This allows the following to work: # # [define] # home = /home/abw # html = $define_home/public_html # html = $home/public_html # same as above, 'define' is prefix # # Modifications are made directly into the variable referenced by $value. # The method returns 1 on success or 0 if any warnings (undefined # variables) were encountered. #------------------------------------------------------------------------ sub _expand { my ($self, $value, $expand, $prefix) = @_; my $warnings = 0; my ($sys, $var, $val); # ensure prefix contains something (nothing!) valid for length() $prefix = "" unless defined $prefix; # take a local copy of the state to avoid much hash dereferencing my ($state, $debug, $pedantic) = @$self{ qw( STATE DEBUG PEDANTIC ) }; # bail out if there's nothing to do return 1 unless $expand && defined($$value); # create an AppConfig::Sys instance, or re-use a previous one, # to handle platform dependant functions: getpwnam(), getpwuid() unless ($sys = $self->{ SYS }) { require AppConfig::Sys; $sys = $self->{ SYS } = AppConfig::Sys->new(); } print STDERR "Expansion of [$$value] " if $debug; EXPAND: { # # EXPAND_VAR # expand $(var) and $var as AppConfig::State variables # if ($expand & AppConfig::EXPAND_VAR) { $$value =~ s{ (? $(var) | $3 => $var } { # embedded variable name will be one of $2 or $3 $var = defined $1 ? $1 : $2; # expand the variable if defined if ($state->_exists($var)) { $val = $state->get($var); } elsif (length $prefix && $state->_exists($prefix . '_' . $var)) { print STDERR "(\$$var => \$${prefix}_$var) " if $debug; $var = $prefix . '_' . $var; $val = $state->get($var); } else { # raise a warning if EXPAND_WARN set if ($expand & AppConfig::EXPAND_WARN) { $state->_error("$var: no such variable"); $warnings++; } # replace variable with nothing $val = ''; } # $val gets substituted back into the $value string $val; }gex; $$value =~ s/\\\$/\$/g; # bail out now if we need to last EXPAND if $warnings && $pedantic; } # # EXPAND_UID # expand ~uid as home directory (for $< if uid not specified) # if ($expand & AppConfig::EXPAND_UID) { $$value =~ s{ ~(\w+)? # $1 => username (optional) } { $val = undef; # embedded user name may be in $1 if (defined ($var = $1)) { # try and get user's home directory if ($sys->can_getpwnam()) { $val = ($sys->getpwnam($var))[7]; } } else { # determine home directory $val = $ENV{ HOME }; } # catch-all for undefined $dir unless (defined $val) { # raise a warning if EXPAND_WARN set if ($expand & AppConfig::EXPAND_WARN) { $state->_error("cannot determine home directory%s", defined $var ? " for $var" : ""); $warnings++; } # replace variable with nothing $val = ''; } # $val gets substituted back into the $value string $val; }gex; # bail out now if we need to last EXPAND if $warnings && $pedantic; } # # EXPAND_ENV # expand ${VAR} as environment variables # if ($expand & AppConfig::EXPAND_ENV) { $$value =~ s{ ( \$ \{ (\w+) \} ) } { $var = $2; # expand the variable if defined if (exists $ENV{ $var }) { $val = $ENV{ $var }; } elsif ( $var eq 'HOME' ) { # In the special case of HOME, if not set # use the internal version $val = $self->{ HOME }; } else { # raise a warning if EXPAND_WARN set if ($expand & AppConfig::EXPAND_WARN) { $state->_error("$var: no such environment variable"); $warnings++; } # replace variable with nothing $val = ''; } # $val gets substituted back into the $value string $val; }gex; # bail out now if we need to last EXPAND if $warnings && $pedantic; } } print STDERR "=> [$$value] (EXPAND = $expand)\n" if $debug; # return status return $warnings ? 0 : 1; } #------------------------------------------------------------------------ # _dump() # # Dumps the contents of the Config object. #------------------------------------------------------------------------ sub _dump { my $self = shift; foreach my $key (keys %$self) { printf("%-10s => %s\n", $key, defined($self->{ $key }) ? $self->{ $key } : ""); } } 1; __END__ =head1 NAME AppConfig::File - Perl5 module for reading configuration files. =head1 SYNOPSIS use AppConfig::File; my $state = AppConfig::State->new(\%cfg1); my $cfgfile = AppConfig::File->new($state, $file); $cfgfile->parse($file); # read config file =head1 OVERVIEW AppConfig::File is a Perl5 module which reads configuration files and use the contents therein to update variable values in an AppConfig::State object. AppConfig::File is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::File MODULE To import and use the AppConfig::File module the following line should appear in your Perl script: use AppConfig::File; AppConfig::File is used automatically if you use the AppConfig module and create an AppConfig::File object through the file() method. AppConfig::File is implemented using object-oriented methods. A new AppConfig::File object is created and initialised using the AppConfig::File->new() method. This returns a reference to a new AppConfig::File object. A reference to an AppConfig::State object should be passed in as the first parameter: my $state = AppConfig::State->new(); my $cfgfile = AppConfig::File->new($state); This will create and return a reference to a new AppConfig::File object. =head2 READING CONFIGURATION FILES The C method is used to read a configuration file and have the contents update the STATE accordingly. $cfgfile->parse($file); Multiple files maye be specified and will be read in turn. $cfgfile->parse($file1, $file2, $file3); The method will return an undef value if it encounters any errors opening the files. It will return immediately without processing any further files. By default, the PEDANTIC option in the AppConfig::State object, $self->{ STATE }, is turned off and any parsing errors (invalid variables, unvalidated values, etc) will generated warnings, but not cause the method to return. Having processed all files, the method will return 1 if all files were processed without warning or 0 if one or more warnings were raised. When the PEDANTIC option is turned on, the method generates a warning and immediately returns a value of 0 as soon as it encounters any parsing error. Variables values in the configuration files may be expanded depending on the value of their EXPAND option, as determined from the App::State object. See L for more information on variable expansion. =head2 CONFIGURATION FILE FORMAT A configuration file may contain blank lines and comments which are ignored. Comments begin with a '#' as the first character on a line or following one or more whitespace tokens, and continue to the end of the line. # this is a comment foo = bar # so is this url = index.html#hello # this too, but not the '#welcome' Notice how the '#welcome' part of the URL is not treated as a comment because a whitespace character doesn't precede it. Long lines can be continued onto the next line by ending the first line with a '\'. callsign = alpha bravo camel delta echo foxtrot golf hipowls \ india juliet kilo llama mike november oscar papa \ quebec romeo sierra tango umbrella victor whiskey \ x-ray yankee zebra Variables that are simple flags and do not expect an argument (ARGCOUNT = ARGCOUNT_NONE) can be specified without any value. They will be set with the value 1, with any value explicitly specified (except "0" and "off") being ignored. The variable may also be specified with a "no" prefix to implicitly set the variable to 0. verbose # on (1) verbose = 1 # on (1) verbose = 0 # off (0) verbose off # off (0) verbose on # on (1) verbose mumble # on (1) noverbose # off (0) Variables that expect an argument (ARGCOUNT = ARGCOUNT_ONE) will be set to whatever follows the variable name, up to the end of the current line. An equals sign may be inserted between the variable and value for clarity. room = /home/kitchen room /home/bedroom Each subsequent re-definition of the variable value overwrites the previous value. print $config->room(); # prints "/home/bedroom" Variables may be defined to accept multiple values (ARGCOUNT = ARGCOUNT_LIST). Each subsequent definition of the variable adds the value to the list of previously set values for the variable. drink = coffee drink = tea A reference to a list of values is returned when the variable is requested. my $beverages = $config->drinks(); print join(", ", @$beverages); # prints "coffee, tea" Variables may also be defined as hash lists (ARGCOUNT = ARGCOUNT_HASH). Each subsequent definition creates a new key and value in the hash array. alias l="ls -CF" alias h="history" A reference to the hash is returned when the variable is requested. my $aliases = $config->alias(); foreach my $k (keys %$aliases) { print "$k => $aliases->{ $k }\n"; } A large chunk of text can be defined using Perl's "heredoc" quoting style. scalar = < for more information on expanding variable values. The configuration files may have variables arranged in blocks. A block header, consisting of the block name in square brackets, introduces a configuration block. The block name and an underscore are then prefixed to the names of all variables subsequently referenced in that block. The block continues until the next block definition or to the end of the current file. [block1] foo = 10 # block1_foo = 10 [block2] foo = 20 # block2_foo = 20 =head1 AUTHOR Andy Wardley, Eabw@wardley.orgE =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO AppConfig, AppConfig::State =cut PK!k))perl5/AppConfig/Sys.pmnu6$#============================================================================ # # AppConfig::Sys.pm # # Perl5 module providing platform-specific information and operations as # required by other AppConfig::* modules. # # Written by Andy Wardley # # Copyright (C) 1997-2003 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. # # $Id: Sys.pm,v 1.61 2004/02/04 10:11:23 abw Exp $ # #============================================================================ package AppConfig::Sys; use 5.006; use strict; use warnings; use POSIX qw( getpwnam getpwuid ); our $VERSION = '1.71'; our ($AUTOLOAD, $OS, %CAN, %METHOD); BEGIN { # define the methods that may be available if($^O =~ m/win32/i) { $METHOD{ getpwuid } = sub { return wantarray() ? ( (undef) x 7, getlogin() ) : getlogin(); }; $METHOD{ getpwnam } = sub { die("Can't getpwnam on win32"); }; } else { $METHOD{ getpwuid } = sub { getpwuid( defined $_[0] ? shift : $< ); }; $METHOD{ getpwnam } = sub { getpwnam( defined $_[0] ? shift : '' ); }; } # try out each METHOD to see if it's supported on this platform; # it's important we do this before defining AUTOLOAD which would # otherwise catch the unresolved call foreach my $method (keys %METHOD) { eval { &{ $METHOD{ $method } }() }; $CAN{ $method } = ! $@; } } #------------------------------------------------------------------------ # new($os) # # Module constructor. An optional operating system string may be passed # to explicitly define the platform type. # # Returns a reference to a newly created AppConfig::Sys object. #------------------------------------------------------------------------ sub new { my $class = shift; my $self = { METHOD => \%METHOD, CAN => \%CAN, }; bless $self, $class; $self->_configure(@_); return $self; } #------------------------------------------------------------------------ # AUTOLOAD # # Autoload function called whenever an unresolved object method is # called. If the method name relates to a METHODS entry, then it is # called iff the corresponding CAN_$method is set true. If the # method name relates to a CAN_$method value then that is returned. #------------------------------------------------------------------------ sub AUTOLOAD { my $self = shift; my $method; # splat the leading package name ($method = $AUTOLOAD) =~ s/.*:://; # ignore destructor $method eq 'DESTROY' && return; # can_method() if ($method =~ s/^can_//i && exists $self->{ CAN }->{ $method }) { return $self->{ CAN }->{ $method }; } # method() elsif (exists $self->{ METHOD }->{ $method }) { if ($self->{ CAN }->{ $method }) { return &{ $self->{ METHOD }->{ $method } }(@_); } else { return undef; } } # variable elsif (exists $self->{ uc $method }) { return $self->{ uc $method }; } else { warn("AppConfig::Sys->", $method, "(): no such method or variable\n"); } return undef; } #------------------------------------------------------------------------ # _configure($os) # # Uses the first parameter, $os, the package variable $AppConfig::Sys::OS, # the value of $^O, or as a last resort, the value of # $Config::Config('osname') to determine the current operating # system/platform. Sets internal variables accordingly. #------------------------------------------------------------------------ sub _configure { my $self = shift; # operating system may be defined as a parameter or in $OS my $os = shift || $OS; # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # The following was lifted (and adapated slightly) from Lincoln Stein's # CGI.pm module, version 2.36... # # FIGURE OUT THE OS WE'RE RUNNING UNDER # Some systems support the $^O variable. If not # available then require() the Config library unless ($os) { unless ($os = $^O) { require Config; $os = $Config::Config{'osname'}; } } if ($os =~ /win32/i) { $os = 'WINDOWS'; } elsif ($os =~ /vms/i) { $os = 'VMS'; } elsif ($os =~ /mac/i) { $os = 'MACINTOSH'; } elsif ($os =~ /os2/i) { $os = 'OS2'; } else { $os = 'UNIX'; } # The path separator is a slash, backslash or semicolon, depending # on the platform. my $ps = { UNIX => '/', OS2 => '\\', WINDOWS => '\\', MACINTOSH => ':', VMS => '\\' }->{ $os }; # # Thanks Lincoln! # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - $self->{ OS } = $os; $self->{ PATHSEP } = $ps; } #------------------------------------------------------------------------ # _dump() # # Dump internals for debugging. #------------------------------------------------------------------------ sub _dump { my $self = shift; print "=" x 71, "\n"; print "Status of AppConfig::Sys (Version $VERSION) object: $self\n"; print " Operating System : ", $self->{ OS }, "\n"; print " Path Separator : ", $self->{ PATHSEP }, "\n"; print " Available methods :\n"; foreach my $can (keys %{ $self->{ CAN } }) { printf "%20s : ", $can; print $self->{ CAN }->{ $can } ? "yes" : "no", "\n"; } print "=" x 71, "\n"; } 1; __END__ =pod =head1 NAME AppConfig::Sys - Perl5 module defining platform-specific information and methods for other AppConfig::* modules. =head1 SYNOPSIS use AppConfig::Sys; my $sys = AppConfig::Sys->new(); @fields = $sys->getpwuid($userid); @fields = $sys->getpwnam($username); =head1 OVERVIEW AppConfig::Sys is a Perl5 module provides platform-specific information and operations as required by other AppConfig::* modules. AppConfig::Sys is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::Sys MODULE To import and use the AppConfig::Sys module the following line should appear in your Perl script: use AppConfig::Sys; AppConfig::Sys is implemented using object-oriented methods. A new AppConfig::Sys object is created and initialised using the AppConfig::Sys->new() method. This returns a reference to a new AppConfig::Sys object. my $sys = AppConfig::Sys->new(); This will attempt to detect your operating system and create a reference to a new AppConfig::Sys object that is applicable to your platform. You may explicitly specify an operating system name to override this automatic detection: $unix_sys = AppConfig::Sys->new("Unix"); Alternatively, the package variable $AppConfig::Sys::OS can be set to an operating system name. The valid operating system names are: Win32, VMS, Mac, OS2 and Unix. They are not case-specific. =head2 AppConfig::Sys METHODS AppConfig::Sys defines the following methods: =over 4 =item getpwnam() Calls the system function getpwnam() if available and returns the result. Returns undef if not available. The can_getpwnam() method can be called to determine if this function is available. =item getpwuid() Calls the system function getpwuid() if available and returns the result. Returns undef if not available. The can_getpwuid() method can be called to determine if this function is available. =back =head1 AUTHOR Andy Wardley, Eabw@wardley.orgE =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the term of the Perl Artistic License. =head1 SEE ALSO AppConfig, AppConfig::File =cut PK!f perl5/AppConfig/State.pmnu6$#============================================================================ # # AppConfig::State.pm # # Perl5 module in which configuration information for an application can # be stored and manipulated. AppConfig::State objects maintain knowledge # about variables; their identities, options, aliases, targets, callbacks # and so on. This module is used by a number of other AppConfig::* modules. # # Written by Andy Wardley # # Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. # Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. # #---------------------------------------------------------------------------- # # TODO # # * Change varlist() to varhash() and provide another varlist() method # which returns a list. Multiple parameters passed implies a hash # slice/list grep, a single parameter should indicate a regex. # # * Perhaps allow a callback to be installed which is called *instead* of # the get() and set() methods (or rather, is called by them). # # * Maybe CMDARG should be in there to specify extra command-line only # options that get added to the AppConfig::GetOpt alias construction, # but not applied in config files, general usage, etc. The GLOBAL # CMDARG might be specified as a format, e.g. "-%c" where %s = name, # %c = first character, %u - first unique sequence(?). Will # GetOpt::Long handle --long to -l application automagically? # # * ..and an added thought is that CASE sensitivity may be required for the # command line (-v vs -V, -r vs -R, for example), but not for parsing # config files where you may wish to treat "Name", "NAME" and "name" alike. # #============================================================================ package AppConfig::State; use 5.006; use strict; use warnings; our $VERSION = '1.71'; our $DEBUG = 0; our $AUTOLOAD; # need access to AppConfig::ARGCOUNT_* use AppConfig ':argcount'; # internal per-variable hashes that AUTOLOAD should provide access to my %METHVARS; @METHVARS{ qw( EXPAND ARGS ARGCOUNT ) } = (); # internal values that AUTOLOAD should provide access to my %METHFLAGS; @METHFLAGS{ qw( PEDANTIC ) } = (); # variable attributes that may be specified in GLOBAL; my @GLOBAL_OK = qw( DEFAULT EXPAND VALIDATE ACTION ARGS ARGCOUNT ); #------------------------------------------------------------------------ # new(\%config, @vars) # # Module constructor. A reference to a hash array containing # configuration options may be passed as the first parameter. This is # passed off to _configure() for processing. See _configure() for # information about configurarion options. The remaining parameters # may be variable definitions and are passed en masse to define() for # processing. # # Returns a reference to a newly created AppConfig::State object. #------------------------------------------------------------------------ sub new { my $class = shift; my $self = { # internal hash arrays to store variable specification information VARIABLE => { }, # variable values DEFAULT => { }, # default values ALIAS => { }, # known aliases ALIAS => VARIABLE ALIASES => { }, # reverse alias lookup VARIABLE => ALIASES ARGCOUNT => { }, # arguments expected ARGS => { }, # specific argument pattern (AppConfig::Getopt) EXPAND => { }, # variable expansion (AppConfig::File) VALIDATE => { }, # validation regexen or functions ACTION => { }, # callback functions for when variable is set GLOBAL => { }, # default global settings for new variables # other internal data CREATE => 0, # auto-create variables when set CASE => 0, # case sensitivity flag (1 = sensitive) PEDANTIC => 0, # return immediately on parse warnings EHANDLER => undef, # error handler (let's hope we don't need it!) ERROR => '', # error message }; bless $self, $class; # configure if first param is a config hash ref $self->_configure(shift) if ref($_[0]) eq 'HASH'; # call define(@_) to handle any variables definitions $self->define(@_) if @_; return $self; } #------------------------------------------------------------------------ # define($variable, \%cfg, [$variable, \%cfg, ...]) # # Defines one or more variables. The first parameter specifies the # variable name. The following parameter may reference a hash of # configuration options for the variable. Further variables and # configuration hashes may follow and are processed in turn. If the # parameter immediately following a variable name isn't a hash reference # then it is ignored and the variable is defined without a specific # configuration, although any default parameters as specified in the # GLOBAL option will apply. # # The $variable value may contain an alias/args definition in compact # format, such as "Foo|Bar=1". # # A warning is issued (via _error()) if an invalid option is specified. #------------------------------------------------------------------------ sub define { my $self = shift; my ($var, $args, $count, $opt, $val, $cfg, @names); while (@_) { $var = shift; $cfg = ref($_[0]) eq 'HASH' ? shift : { }; # variable may be specified in compact format, 'foo|bar=i@' if ($var =~ s/(.+?)([!+=:].*)/$1/) { # anything coming after the name|alias list is the ARGS $cfg->{ ARGS } = $2 if length $2; } # examine any ARGS option if (defined ($args = $cfg->{ ARGS })) { ARGGCOUNT: { $count = ARGCOUNT_NONE, last if $args =~ /^!/; $count = ARGCOUNT_LIST, last if $args =~ /@/; $count = ARGCOUNT_HASH, last if $args =~ /%/; $count = ARGCOUNT_ONE; } $cfg->{ ARGCOUNT } = $count; } # split aliases out @names = split(/\|/, $var); $var = shift @names; $cfg->{ ALIAS } = [ @names ] if @names; # variable name gets folded to lower unless CASE sensitive $var = lc $var unless $self->{ CASE }; # activate $variable (so it does 'exist()') $self->{ VARIABLE }->{ $var } = undef; # merge GLOBAL and variable-specific configurations $cfg = { %{ $self->{ GLOBAL } }, %$cfg }; # examine each variable configuration parameter while (($opt, $val) = each %$cfg) { $opt = uc $opt; # DEFAULT, VALIDATE, EXPAND, ARGS and ARGCOUNT are stored as # they are; $opt =~ /^DEFAULT|VALIDATE|EXPAND|ARGS|ARGCOUNT$/ && do { $self->{ $opt }->{ $var } = $val; next; }; # CMDARG has been deprecated $opt eq 'CMDARG' && do { $self->_error("CMDARG has been deprecated. " . "Please use an ALIAS if required."); next; }; # ACTION should be a code ref $opt eq 'ACTION' && do { unless (ref($val) eq 'CODE') { $self->_error("'$opt' value is not a code reference"); next; }; # store code ref, forcing keyword to upper case $self->{ ACTION }->{ $var } = $val; next; }; # ALIAS creates alias links to the variable name $opt eq 'ALIAS' && do { # coerce $val to an array if not already so $val = [ split(/\|/, $val) ] unless ref($val) eq 'ARRAY'; # fold to lower case unless CASE sensitivity set unless ($self->{ CASE }) { @$val = map { lc } @$val; } # store list of aliases... $self->{ ALIASES }->{ $var } = $val; # ...and create ALIAS => VARIABLE lookup hash entries foreach my $a (@$val) { $self->{ ALIAS }->{ $a } = $var; } next; }; # default $self->_error("$opt is not a valid configuration item"); } # set variable to default value $self->_default($var); # DEBUG: dump new variable definition if ($DEBUG) { print STDERR "Variable defined:\n"; $self->_dump_var($var); } } } #------------------------------------------------------------------------ # get($variable) # # Returns the value of the variable specified, $variable. Returns undef # if the variable does not exists or is undefined and send a warning # message to the _error() function. #------------------------------------------------------------------------ sub get { my $self = shift; my $variable = shift; my $negate = 0; my $value; # _varname returns variable name after aliasing and case conversion # $negate indicates if the name got converted from "no" to "" $variable = $self->_varname($variable, \$negate); # check the variable has been defined unless (exists($self->{ VARIABLE }->{ $variable })) { $self->_error("$variable: no such variable"); return undef; } # DEBUG print STDERR "$self->get($variable) => ", defined $self->{ VARIABLE }->{ $variable } ? $self->{ VARIABLE }->{ $variable } : "", "\n" if $DEBUG; # return variable value, possibly negated if the name was "no" $value = $self->{ VARIABLE }->{ $variable }; return $negate ? !$value : $value; } #------------------------------------------------------------------------ # set($variable, $value) # # Assigns the value, $value, to the variable specified. # # Returns 1 if the variable is successfully updated or 0 if the variable # does not exist. If an ACTION sub-routine exists for the variable, it # will be executed and its return value passed back. #------------------------------------------------------------------------ sub set { my $self = shift; my $variable = shift; my $value = shift; my $negate = 0; my $create; # _varname returns variable name after aliasing and case conversion # $negate indicates if the name got converted from "no" to "" $variable = $self->_varname($variable, \$negate); # check the variable exists if (exists($self->{ VARIABLE }->{ $variable })) { # variable found, so apply any value negation $value = $value ? 0 : 1 if $negate; } else { # auto-create variable if CREATE is 1 or a pattern matching # the variable name (real name, not an alias) $create = $self->{ CREATE }; if (defined $create && ($create eq '1' || $variable =~ /$create/)) { $self->define($variable); print STDERR "Auto-created $variable\n" if $DEBUG; } else { $self->_error("$variable: no such variable"); return 0; } } # call the validate($variable, $value) method to perform any validation unless ($self->_validate($variable, $value)) { $self->_error("$variable: invalid value: $value"); return 0; } # DEBUG print STDERR "$self->set($variable, ", defined $value ? $value : "", ")\n" if $DEBUG; # set the variable value depending on its ARGCOUNT my $argcount = $self->{ ARGCOUNT }->{ $variable }; $argcount = AppConfig::ARGCOUNT_ONE unless defined $argcount; if ($argcount eq AppConfig::ARGCOUNT_LIST) { # push value onto the end of the list push(@{ $self->{ VARIABLE }->{ $variable } }, $value); } elsif ($argcount eq AppConfig::ARGCOUNT_HASH) { # insert "=" data into hash my ($k, $v) = split(/\s*=\s*/, $value, 2); # strip quoting $v =~ s/^(['"])(.*)\1$/$2/ if defined $v; $self->{ VARIABLE }->{ $variable }->{ $k } = $v; } else { # set simple variable $self->{ VARIABLE }->{ $variable } = $value; } # call any ACTION function bound to this variable return &{ $self->{ ACTION }->{ $variable } }($self, $variable, $value) if (exists($self->{ ACTION }->{ $variable })); # ...or just return 1 (ok) return 1; } #------------------------------------------------------------------------ # varlist($criteria, $filter) # # Returns a hash array of all variables and values whose real names # match the $criteria regex pattern passed as the first parameter. # If $filter is set to any true value, the keys of the hash array # (variable names) will have the $criteria part removed. This allows # the caller to specify the variables from one particular [block] and # have the "block_" prefix removed, for example. # # TODO: This should be changed to varhash(). varlist() should return a # list. Also need to consider specification by list rather than regex. # #------------------------------------------------------------------------ sub varlist { my $self = shift; my $criteria = shift; my $strip = shift; $criteria = "" unless defined $criteria; # extract relevant keys and slice out corresponding values my @keys = grep(/$criteria/, keys %{ $self->{ VARIABLE } }); my @vals = @{ $self->{ VARIABLE } }{ @keys }; my %set; # clean off the $criteria part if $strip is set @keys = map { s/$criteria//; $_ } @keys if $strip; # slice values into the target hash @set{ @keys } = @vals; return %set; } #------------------------------------------------------------------------ # AUTOLOAD # # Autoload function called whenever an unresolved object method is # called. If the method name relates to a defined VARIABLE, we patch # in $self->get() and $self->set() to magically update the varaiable # (if a parameter is supplied) and return the previous value. # # Thus the function can be used in the folowing ways: # $state->variable(123); # set a new value # $foo = $state->variable(); # get the current value # # Returns the current value of the variable, taken before any new value # is set. Prints a warning if the variable isn't defined (i.e. doesn't # exist rather than exists with an undef value) and returns undef. #------------------------------------------------------------------------ sub AUTOLOAD { my $self = shift; my ($variable, $attrib); # splat the leading package name ($variable = $AUTOLOAD) =~ s/.*:://; # ignore destructor $variable eq 'DESTROY' && return; # per-variable attributes and internal flags listed as keys in # %METHFLAGS and %METHVARS respectively can be accessed by a # method matching the attribute or flag name in lower case with # a leading underscore_ if (($attrib = $variable) =~ s/_//g) { $attrib = uc $attrib; if (exists $METHFLAGS{ $attrib }) { return $self->{ $attrib }; } if (exists $METHVARS{ $attrib }) { # next parameter should be variable name $variable = shift; $variable = $self->_varname($variable); # check we've got a valid variable # $self->_error("$variable: no such variable or method"), # return undef # unless exists($self->{ VARIABLE }->{ $variable }); # return attribute return $self->{ $attrib }->{ $variable }; } } # set a new value if a parameter was supplied or return the old one return defined($_[0]) ? $self->set($variable, shift) : $self->get($variable); } #======================================================================== # ----- PRIVATE METHODS ----- #======================================================================== #------------------------------------------------------------------------ # _configure(\%cfg) # # Sets the various configuration options using the values passed in the # hash array referenced by $cfg. #------------------------------------------------------------------------ sub _configure { my $self = shift; my $cfg = shift || return; # construct a regex to match values which are ok to be found in GLOBAL my $global_ok = join('|', @GLOBAL_OK); foreach my $opt (keys %$cfg) { # GLOBAL must be a hash ref $opt =~ /^GLOBALS?$/i && do { unless (ref($cfg->{ $opt }) eq 'HASH') { $self->_error("\U$opt\E parameter is not a hash ref"); next; } # we check each option is ok to be in GLOBAL, but we don't do # any error checking on the values they contain (but should?). foreach my $global ( keys %{ $cfg->{ $opt } } ) { # continue if the attribute is ok to be GLOBAL next if ($global =~ /(^$global_ok$)/io); $self->_error( "\U$global\E parameter cannot be GLOBAL"); } $self->{ GLOBAL } = $cfg->{ $opt }; next; }; # CASE, CREATE and PEDANTIC are stored as they are $opt =~ /^CASE|CREATE|PEDANTIC$/i && do { $self->{ uc $opt } = $cfg->{ $opt }; next; }; # ERROR triggers $self->_ehandler() $opt =~ /^ERROR$/i && do { $self->_ehandler($cfg->{ $opt }); next; }; # DEBUG triggers $self->_debug() $opt =~ /^DEBUG$/i && do { $self->_debug($cfg->{ $opt }); next; }; # warn about invalid options $self->_error("\U$opt\E is not a valid configuration option"); } } #------------------------------------------------------------------------ # _varname($variable, \$negated) # # Variable names are treated case-sensitively or insensitively, depending # on the value of $self->{ CASE }. When case-insensitive ($self->{ CASE } # != 0), all variable names are converted to lower case. Variable values # are not converted. This function simply converts the parameter # (variable) to lower case if $self->{ CASE } isn't set. _varname() also # expands a variable alias to the name of the target variable. # # Variables with an ARGCOUNT of ARGCOUNT_ZERO may be specified as # "no" in which case, the intended value should be negated. The # leading "no" part is stripped from the variable name. A reference to # a scalar value can be passed as the second parameter and if the # _varname() method identified such a variable, it will negate the value. # This allows the intended value or a simple negate flag to be passed by # reference and be updated to indicate any negation activity taking place. # # The (possibly modified) variable name is returned. #------------------------------------------------------------------------ sub _varname { my $self = shift; my $variable = shift; my $negated = shift; # convert to lower case if case insensitive $variable = $self->{ CASE } ? $variable : lc $variable; # get the actual name if this is an alias $variable = $self->{ ALIAS }->{ $variable } if (exists($self->{ ALIAS }->{ $variable })); # if the variable doesn't exist, we can try to chop off a leading # "no" and see if the remainder matches an ARGCOUNT_ZERO variable unless (exists($self->{ VARIABLE }->{ $variable })) { # see if the variable is specified as "no" if ($variable =~ /^no(.*)/) { # see if the real variable (minus "no") exists and it # has an ARGOUNT of ARGCOUNT_NONE (or no ARGCOUNT at all) my $novar = $self->_varname($1); if (exists($self->{ VARIABLE }->{ $novar }) && ! $self->{ ARGCOUNT }->{ $novar }) { # set variable name and negate value $variable = $novar; $$negated = ! $$negated if defined $negated; } } } # return the variable name $variable; } #------------------------------------------------------------------------ # _default($variable) # # Sets the variable specified to the default value or undef if it doesn't # have a default. The default value is returned. #------------------------------------------------------------------------ sub _default { my $self = shift; my $variable = shift; # _varname returns variable name after aliasing and case conversion $variable = $self->_varname($variable); # check the variable exists if (exists($self->{ VARIABLE }->{ $variable })) { # set variable value to the default scalar, an empty list or empty # hash array, depending on its ARGCOUNT value my $argcount = $self->{ ARGCOUNT }->{ $variable }; $argcount = AppConfig::ARGCOUNT_ONE unless defined $argcount; if ($argcount == AppConfig::ARGCOUNT_NONE) { return $self->{ VARIABLE }->{ $variable } = $self->{ DEFAULT }->{ $variable } || 0; } elsif ($argcount == AppConfig::ARGCOUNT_LIST) { my $deflist = $self->{ DEFAULT }->{ $variable }; return $self->{ VARIABLE }->{ $variable } = [ ref $deflist eq 'ARRAY' ? @$deflist : ( ) ]; } elsif ($argcount == AppConfig::ARGCOUNT_HASH) { my $defhash = $self->{ DEFAULT }->{ $variable }; return $self->{ VARIABLE }->{ $variable } = { ref $defhash eq 'HASH' ? %$defhash : () }; } else { return $self->{ VARIABLE }->{ $variable } = $self->{ DEFAULT }->{ $variable }; } } else { $self->_error("$variable: no such variable"); return 0; } } #------------------------------------------------------------------------ # _exists($variable) # # Returns 1 if the variable specified exists or 0 if not. #------------------------------------------------------------------------ sub _exists { my $self = shift; my $variable = shift; # _varname returns variable name after aliasing and case conversion $variable = $self->_varname($variable); # check the variable has been defined return exists($self->{ VARIABLE }->{ $variable }); } #------------------------------------------------------------------------ # _validate($variable, $value) # # Uses any validation rules or code defined for the variable to test if # the specified value is acceptable. # # Returns 1 if the value passed validation checks, 0 if not. #------------------------------------------------------------------------ sub _validate { my $self = shift; my $variable = shift; my $value = shift; my $validator; # _varname returns variable name after aliasing and case conversion $variable = $self->_varname($variable); # return OK unless there is a validation function return 1 unless defined($validator = $self->{ VALIDATE }->{ $variable }); # # the validation performed is based on the validator type; # # CODE ref: code executed, returning 1 (ok) or 0 (failed) # SCALAR : a regex which should match the value # # CODE ref ref($validator) eq 'CODE' && do { # run the validation function and return the result return &$validator($variable, $value); }; # non-ref (i.e. scalar) ref($validator) || do { # not a ref - assume it's a regex return $value =~ /$validator/; }; # validation failed return 0; } #------------------------------------------------------------------------ # _error($format, @params) # # Checks for the existence of a user defined error handling routine and # if defined, passes all variable straight through to that. The routine # is expected to handle a string format and optional parameters as per # printf(3C). If no error handler is defined, the message is formatted # and passed to warn() which prints it to STDERR. #------------------------------------------------------------------------ sub _error { my $self = shift; my $format = shift; # user defined error handler? if (ref($self->{ EHANDLER }) eq 'CODE') { &{ $self->{ EHANDLER } }($format, @_); } else { warn(sprintf("$format\n", @_)); } } #------------------------------------------------------------------------ # _ehandler($handler) # # Allows a new error handler to be installed. The current value of # the error handler is returned. # # This is something of a kludge to allow other AppConfig::* modules to # install their own error handlers to format error messages appropriately. # For example, AppConfig::File appends a message of the form # "at $file line $line" to each error message generated while parsing # configuration files. The previous handler is returned (and presumably # stored by the caller) to allow new error handlers to chain control back # to any user-defined handler, and also restore the original handler when # done. #------------------------------------------------------------------------ sub _ehandler { my $self = shift; my $handler = shift; # save previous value my $previous = $self->{ EHANDLER }; # update internal reference if a new handler vas provide if (defined $handler) { # check this is a code reference if (ref($handler) eq 'CODE') { $self->{ EHANDLER } = $handler; # DEBUG print STDERR "installed new ERROR handler: $handler\n" if $DEBUG; } else { $self->_error("ERROR handler parameter is not a code ref"); } } return $previous; } #------------------------------------------------------------------------ # _debug($debug) # # Sets the package debugging variable, $AppConfig::State::DEBUG depending # on the value of the $debug parameter. 1 turns debugging on, 0 turns # debugging off. # # May be called as an object method, $state->_debug(1), or as a package # function, AppConfig::State::_debug(1). Returns the previous value of # $DEBUG, before any new value was applied. #------------------------------------------------------------------------ sub _debug { # object reference may not be present if called as a package function my $self = shift if ref($_[0]); my $newval = shift; # save previous value my $oldval = $DEBUG; # update $DEBUG if a new value was provided $DEBUG = $newval if defined $newval; # return previous value $oldval; } #------------------------------------------------------------------------ # _dump_var($var) # # Displays the content of the specified variable, $var. #------------------------------------------------------------------------ sub _dump_var { my $self = shift; my $var = shift; return unless defined $var; # $var may be an alias, so we resolve the real variable name my $real = $self->_varname($var); if ($var eq $real) { print STDERR "$var\n"; } else { print STDERR "$real ('$var' is an alias)\n"; $var = $real; } # for some bizarre reason, the variable VALUE is stored in VARIABLE # (it made sense at some point in time) printf STDERR " VALUE => %s\n", defined($self->{ VARIABLE }->{ $var }) ? $self->{ VARIABLE }->{ $var } : ""; # the rest of the values can be read straight out of their hashes foreach my $param (qw( DEFAULT ARGCOUNT VALIDATE ACTION EXPAND )) { printf STDERR " %-12s => %s\n", $param, defined($self->{ $param }->{ $var }) ? $self->{ $param }->{ $var } : ""; } # summarise all known aliases for this variable print STDERR " ALIASES => ", join(", ", @{ $self->{ ALIASES }->{ $var } }), "\n" if defined $self->{ ALIASES }->{ $var }; } #------------------------------------------------------------------------ # _dump() # # Dumps the contents of the Config object and all stored variables. #------------------------------------------------------------------------ sub _dump { my $self = shift; my $var; print STDERR "=" x 71, "\n"; print STDERR "Status of AppConfig::State (version $VERSION) object:\n\t$self\n"; print STDERR "- " x 36, "\nINTERNAL STATE:\n"; foreach (qw( CREATE CASE PEDANTIC EHANDLER ERROR )) { printf STDERR " %-12s => %s\n", $_, defined($self->{ $_ }) ? $self->{ $_ } : ""; } print STDERR "- " x 36, "\nVARIABLES:\n"; foreach $var (keys %{ $self->{ VARIABLE } }) { $self->_dump_var($var); } print STDERR "- " x 36, "\n", "ALIASES:\n"; foreach $var (keys %{ $self->{ ALIAS } }) { printf(" %-12s => %s\n", $var, $self->{ ALIAS }->{ $var }); } print STDERR "=" x 72, "\n"; } 1; __END__ =head1 NAME AppConfig::State - application configuration state =head1 SYNOPSIS use AppConfig::State; my $state = AppConfig::State->new(\%cfg); $state->define("foo"); # very simple variable definition $state->define("bar", \%varcfg); # variable specific configuration $state->define("foo|bar=i@"); # compact format $state->set("foo", 123); # trivial set/get examples $state->get("foo"); $state->foo(); # shortcut variable access $state->foo(456); # shortcut variable update =head1 OVERVIEW AppConfig::State is a Perl5 module to handle global configuration variables for perl programs. It maintains the state of any number of variables, handling default values, aliasing, validation, update callbacks and option arguments for use by other AppConfig::* modules. AppConfig::State is distributed as part of the AppConfig bundle. =head1 DESCRIPTION =head2 USING THE AppConfig::State MODULE To import and use the AppConfig::State module the following line should appear in your Perl script: use AppConfig::State; The AppConfig::State module is loaded automatically by the new() constructor of the AppConfig module. AppConfig::State is implemented using object-oriented methods. A new AppConfig::State object is created and initialised using the new() method. This returns a reference to a new AppConfig::State object. my $state = AppConfig::State->new(); This will create a reference to a new AppConfig::State with all configuration options set to their default values. You can initialise the object by passing a reference to a hash array containing configuration options: $state = AppConfig::State->new( { CASE => 1, ERROR => \&my_error, } ); The new() constructor of the AppConfig module automatically passes all parameters to the AppConfig::State new() constructor. Thus, any global configuration values and variable definitions for AppConfig::State are also applicable to AppConfig. The following configuration options may be specified. =over 4 =item CASE Determines if the variable names are treated case sensitively. Any non-zero value makes case significant when naming variables. By default, CASE is set to 0 and thus "Variable", "VARIABLE" and "VaRiAbLe" are all treated as "variable". =item CREATE By default, CREATE is turned off meaning that all variables accessed via set() (which includes access via shortcut such as C<$state-Evariable($value)> which delegates to set()) must previously have been defined via define(). When CREATE is set to 1, calling set($variable, $value) on a variable that doesn't exist will cause it to be created automatically. When CREATE is set to any other non-zero value, it is assumed to be a regular expression pattern. If the variable name matches the regex, the variable is created. This can be used to specify configuration file blocks in which variables should be created, for example: $state = AppConfig::State->new( { CREATE => '^define_', } ); In a config file: [define] name = fred # define_name gets created automatically [other] name = john # other_name doesn't - warning raised Note that a regex pattern specified in CREATE is applied to the real variable name rather than any alias by which the variables may be accessed. =item PEDANTIC The PEDANTIC option determines what action the configuration file (AppConfig::File) or argument parser (AppConfig::Args) should take on encountering a warning condition (typically caused when trying to set an undeclared variable). If PEDANTIC is set to any true value, the parsing methods will immediately return a value of 0 on encountering such a condition. If PEDANTIC is not set, the method will continue to parse the remainder of the current file(s) or arguments, returning 0 when complete. If no warnings or errors are encountered, the method returns 1. In the case of a system error (e.g. unable to open a file), the method returns undef immediately, regardless of the PEDANTIC option. =item ERROR Specifies a user-defined error handling routine. When the handler is called, a format string is passed as the first parameter, followed by any additional values, as per printf(3C). =item DEBUG Turns debugging on or off when set to 1 or 0 accordingly. Debugging may also be activated by calling _debug() as an object method (C<$state-E_debug(1)>) or as a package function (C), passing in a true/false value to set the debugging state accordingly. The package variable $AppConfig::State::DEBUG can also be set directly. The _debug() method returns the current debug value. If a new value is passed in, the internal value is updated, but the previous value is returned. Note that any AppConfig::File or App::Config::Args objects that are instantiated with a reference to an App::State will inherit the DEBUG (and also PEDANTIC) values of the state at that time. Subsequent changes to the AppConfig::State debug value will not affect them. =item GLOBAL The GLOBAL option allows default values to be set for the DEFAULT, ARGCOUNT, EXPAND, VALIDATE and ACTION options for any subsequently defined variables. $state = AppConfig::State->new({ GLOBAL => { DEFAULT => '', # default value for new vars ARGCOUNT => 1, # vars expect an argument ACTION => \&my_set_var, # callback when vars get set } }); Any attributes specified explicitly when a variable is defined will override any GLOBAL values. See L below which describes these options in detail. =back =head2 DEFINING VARIABLES The C function is used to pre-declare a variable and specify its configuration. $state->define("foo"); In the simple example above, a new variable called "foo" is defined. A reference to a hash array may also be passed to specify configuration information for the variable: $state->define("foo", { DEFAULT => 99, ALIAS => 'metavar1', }); Any variable-wide GLOBAL values passed to the new() constructor in the configuration hash will also be applied. Values explicitly specified in a variable's define() configuration will override the respective GLOBAL values. The following configuration options may be specified =over 4 =item DEFAULT The DEFAULT value is used to initialise the variable. $state->define("drink", { DEFAULT => 'coffee', }); print $state->drink(); # prints "coffee" =item ALIAS The ALIAS option allows a number of alternative names to be specified for this variable. A single alias should be specified as a string. Multiple aliases can be specified as a reference to an array of alternatives or as a string of names separated by vertical bars, '|'. e.g.: # either $state->define("name", { ALIAS => 'person', }); # or $state->define("name", { ALIAS => [ 'person', 'user', 'uid' ], }); # or $state->define("name", { ALIAS => 'person|user|uid', }); $state->user('abw'); # equivalent to $state->name('abw'); =item ARGCOUNT The ARGCOUNT option specifies the number of arguments that should be supplied for this variable. By default, no additional arguments are expected for variables (ARGCOUNT_NONE). The ARGCOUNT_* constants can be imported from the AppConfig module: use AppConfig ':argcount'; $state->define('foo', { ARGCOUNT => ARGCOUNT_ONE }); or can be accessed directly from the AppConfig package: use AppConfig; $state->define('foo', { ARGCOUNT => AppConfig::ARGCOUNT_ONE }); The following values for ARGCOUNT may be specified. =over 4 =item ARGCOUNT_NONE (0) Indicates that no additional arguments are expected. If the variable is identified in a confirguration file or in the command line arguments, it is set to a value of 1 regardless of whatever arguments follow it. =item ARGCOUNT_ONE (1) Indicates that the variable expects a single argument to be provided. The variable value will be overwritten with a new value each time it is encountered. =item ARGCOUNT_LIST (2) Indicates that the variable expects multiple arguments. The variable value will be appended to the list of previous values each time it is encountered. =item ARGCOUNT_HASH (3) Indicates that the variable expects multiple arguments and that each argument is of the form "key=value". The argument will be split into a key/value pair and inserted into the hash of values each time it is encountered. =back =item ARGS The ARGS option can also be used to specify advanced command line options for use with AppConfig::Getopt, which itself delegates to Getopt::Long. See those two modules for more information on the format and meaning of these options. $state->define("name", { ARGS => "=i@", }); =item EXPAND The EXPAND option specifies how the AppConfig::File processor should expand embedded variables in the configuration file values it reads. By default, EXPAND is turned off (EXPAND_NONE) and no expansion is made. The EXPAND_* constants can be imported from the AppConfig module: use AppConfig ':expand'; $state->define('foo', { EXPAND => EXPAND_VAR }); or can be accessed directly from the AppConfig package: use AppConfig; $state->define('foo', { EXPAND => AppConfig::EXPAND_VAR }); The following values for EXPAND may be specified. Multiple values should be combined with vertical bars , '|', e.g. C). =over 4 =item EXPAND_NONE Indicates that no variable expansion should be attempted. =item EXPAND_VAR Indicates that variables embedded as $var or $(var) should be expanded to the values of the relevant AppConfig::State variables. =item EXPAND_UID Indicates that '~' or '~uid' patterns in the string should be expanded to the current users ($<), or specified user's home directory. In the first case, C<~> is expanded to the value of the C environment variable. In the second case, the C method is used if it is available on your system (which it isn't on Win32). =item EXPAND_ENV Inidicates that variables embedded as ${var} should be expanded to the value of the relevant environment variable. =item EXPAND_ALL Equivalent to C). =item EXPAND_WARN Indicates that embedded variables that are not defined should raise a warning. If PEDANTIC is set, this will cause the read() method to return 0 immediately. =back =item VALIDATE Each variable may have a sub-routine or regular expression defined which is used to validate the intended value for a variable before it is set. If VALIDATE is defined as a regular expression, it is applied to the value and deemed valid if the pattern matches. In this case, the variable is then set to the new value. A warning message is generated if the pattern match fails. VALIDATE may also be defined as a reference to a sub-routine which takes as its arguments the name of the variable and its intended value. The sub-routine should return 1 or 0 to indicate that the value is valid or invalid, respectively. An invalid value will cause a warning error message to be generated. If the GLOBAL VALIDATE variable is set (see GLOBAL in L above) then this value will be used as the default VALIDATE for each variable unless otherwise specified. $state->define("age", { VALIDATE => '\d+', }); $state->define("pin", { VALIDATE => \&check_pin, }); =item ACTION The ACTION option allows a sub-routine to be bound to a variable as a callback that is executed whenever the variable is set. The ACTION is passed a reference to the AppConfig::State object, the name of the variable and the value of the variable. The ACTION routine may be used, for example, to post-process variable data, update the value of some other dependant variable, generate a warning message, etc. Example: $state->define("foo", { ACTION => \&my_notify }); sub my_notify { my $state = shift; my $var = shift; my $val = shift; print "$variable set to $value"; } $state->foo(42); # prints "foo set to 42" Be aware that calling C<$state-Eset()> to update the same variable from within the ACTION function will cause a recursive loop as the ACTION function is repeatedly called. =back =head2 DEFINING VARIABLES USING THE COMPACT FORMAT Variables may be defined in a compact format which allows any ALIAS and ARGS values to be specified as part of the variable name. This is designed to mimic the behaviour of Johan Vromans' Getopt::Long module. Aliases for a variable should be specified after the variable name, separated by vertical bars, '|'. Any ARGS parameter should be appended after the variable name(s) and/or aliases. The following examples are equivalent: $state->define("foo", { ALIAS => [ 'bar', 'baz' ], ARGS => '=i', }); $state->define("foo|bar|baz=i"); =head2 READING AND MODIFYING VARIABLE VALUES AppConfig::State defines two methods to manipulate variable values: set($variable, $value); get($variable); Both functions take the variable name as the first parameter and C takes an additional parameter which is the new value for the variable. C returns 1 or 0 to indicate successful or unsuccessful update of the variable value. If there is an ACTION routine associated with the named variable, the value returned will be passed back from C. The C function returns the current value of the variable. Once defined, variables may be accessed directly as object methods where the method name is the same as the variable name. i.e. $state->set("verbose", 1); is equivalent to $state->verbose(1); Without parameters, the current value of the variable is returned. If a parameter is specified, the variable is set to that value and the result of the set() operation is returned. $state->age(29); # sets 'age' to 29, returns 1 (ok) =head2 VARLIST The varlist() method can be used to extract a number of variables into a hash array. The first parameter should be a regular expression used for matching against the variable names. my %vars = $state->varlist("^file"); # all "file*" variables A second parameter may be specified (any true value) to indicate that the part of the variable name matching the regex should be removed when copied to the target hash. $state->file_name("/tmp/file"); $state->file_path("/foo:/bar:/baz"); my %vars = $state->varlist("^file_", 1); # %vars: # name => /tmp/file # path => "/foo:/bar:/baz" =head2 INTERNAL METHODS The interal (private) methods of the AppConfig::State class are listed below. They aren't intended for regular use and potential users should consider the fact that nothing about the internal implementation is guaranteed to remain the same. Having said that, the AppConfig::State class is intended to co-exist and work with a number of other modules and these are considered "friend" classes. These methods are provided, in part, as services to them. With this acknowledged co-operation in mind, it is safe to assume some stability in this core interface. The _varname() method can be used to determine the real name of a variable from an alias: $varname->_varname($alias); Note that all methods that take a variable name, including those listed below, can accept an alias and automatically resolve it to the correct variable name. There is no need to call _varname() explicitly to do alias expansion. The _varname() method will fold all variables names to lower case unless CASE sensititvity is set. The _exists() method can be used to check if a variable has been defined: $state->_exists($varname); The _default() method can be used to reset a variable to its default value: $state->_default($varname); The _expand() method can be used to determine the EXPAND value for a variable: print "$varname EXPAND: ", $state->_expand($varname), "\n"; The _argcount() method returns the value of the ARGCOUNT attribute for a variable: print "$varname ARGCOUNT: ", $state->_argcount($varname), "\n"; The _validate() method can be used to determine if a new value for a variable meets any validation criteria specified for it. The variable name and intended value should be passed in. The methods returns a true/false value depending on whether or not the validation succeeded: print "OK\n" if $state->_validate($varname, $value); The _pedantic() method can be called to determine the current value of the PEDANTIC option. print "pedantic mode is ", $state->_pedantic() ? "on" ; "off", "\n"; The _debug() method can be used to turn debugging on or off (pass 1 or 0 as a parameter). It can also be used to check the debug state, returning the current internal value of $AppConfig::State::DEBUG. If a new debug value is provided, the debug state is updated and the previous state is returned. $state->_debug(1); # debug on, returns previous value The _dump_var($varname) and _dump() methods may also be called for debugging purposes. $state->_dump_var($varname); # show variable state $state->_dump(); # show internal state and all vars =head1 AUTHOR Andy Wardley, Eabw@wardley.orgE =head1 COPYRIGHT Copyright (C) 1997-2007 Andy Wardley. All Rights Reserved. Copyright (C) 1997,1998 Canon Research Centre Europe Ltd. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO AppConfig, AppConfig::File, AppConfig::Args, AppConfig::Getopt =cut PK! perl5/Expect.pmnu7m# -*-cperl-*- # This module is copyrighted as per the usual perl legalese: # Copyright (c) 1997 Austin Schutz. # expect() interface & functionality enhancements (c) 1999 Roland Giersig. # # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl # itself. # # Don't blame/flame me if you bust your stuff. # Austin Schutz # # This module now is maintained by # Dave Jacoby # use 5.006; package Expect; use strict; use warnings; use IO::Pty 1.11; # We need make_slave_controlling_terminal() use IO::Tty; use POSIX qw(:sys_wait_h :unistd_h); # For WNOHANG and isatty use Fcntl qw(:DEFAULT); # For checking file handle settings. use Carp qw(cluck croak carp confess); use IO::Handle (); use Exporter qw(import); use Errno; use Scalar::Util qw/ looks_like_number /; # This is necessary to make routines within Expect work. @Expect::ISA = qw(IO::Pty); @Expect::EXPORT = qw(expect exp_continue exp_continue_timeout); BEGIN { $Expect::VERSION = '1.38'; # These are defaults which may be changed per object, or set as # the user wishes. # This will be unset, since the default behavior differs between # spawned processes and initialized filehandles. # $Expect::Log_Stdout = 1; $Expect::Log_Group = 1; $Expect::Debug = 0; $Expect::Exp_Max_Accum = 0; # unlimited $Expect::Exp_Internal = 0; $Expect::IgnoreEintr = 0; $Expect::Manual_Stty = 0; $Expect::Multiline_Matching = 1; $Expect::Do_Soft_Close = 0; @Expect::Before_List = (); @Expect::After_List = (); %Expect::Spawned_PIDs = (); } sub version { my ($version) = @_; warn "Version $version is later than $Expect::VERSION. It may not be supported" if ( defined($version) && ( $version > $Expect::VERSION ) ); die "Versions before 1.03 are not supported in this release" if ( ( defined($version) ) && ( $version < 1.03 ) ); return $Expect::VERSION; } sub new { my ($class, @args) = @_; $class = ref($class) if ref($class); # so we can be called as $exp->new() # Create the pty which we will use to pass process info. my ($self) = IO::Pty->new; die "$class: Could not assign a pty" unless $self; bless $self => $class; $self->autoflush(1); # This is defined here since the default is different for # initialized handles as opposed to spawned processes. ${*$self}{exp_Log_Stdout} = 1; $self->_init_vars(); if (@args) { # we got add'l parms, so pass them to spawn return $self->spawn(@args); } return $self; } sub timeout { my $self = shift; ${*$self}{expect_timeout} = shift if @_; return ${*$self}{expect_timeout}; } sub spawn { my ($class, @cmd) = @_; # spawn is passed command line args. my $self; if ( ref($class) ) { $self = $class; } else { $self = $class->new(); } croak "Cannot reuse an object with an already spawned command" if exists ${*$self}{"exp_Command"}; ${*$self}{"exp_Command"} = \@cmd; # set up pipe to detect childs exec error pipe( FROM_CHILD, TO_PARENT ) or die "Cannot open pipe: $!"; pipe( FROM_PARENT, TO_CHILD ) or die "Cannot open pipe: $!"; TO_PARENT->autoflush(1); TO_CHILD->autoflush(1); eval { fcntl( TO_PARENT, Fcntl::F_SETFD, Fcntl::FD_CLOEXEC ); }; my $pid = fork; unless ( defined($pid) ) { warn "Cannot fork: $!" if $^W; return; } if ($pid) { # parent my $errno; ${*$self}{exp_Pid} = $pid; close TO_PARENT; close FROM_PARENT; $self->close_slave(); $self->set_raw() if $self->raw_pty and isatty($self); close TO_CHILD; # so child gets EOF and can go ahead # now wait for child exec (eof due to close-on-exit) or exec error my $errstatus = sysread( FROM_CHILD, $errno, 256 ); die "Cannot sync with child: $!" if not defined $errstatus; close FROM_CHILD; if ($errstatus) { $! = $errno + 0; warn "Cannot exec(@cmd): $!\n" if $^W; return; } } else { # child close FROM_CHILD; close TO_CHILD; $self->make_slave_controlling_terminal(); my $slv = $self->slave() or die "Cannot get slave: $!"; $slv->set_raw() if $self->raw_pty; close($self); # wait for parent before we detach my $buffer; my $errstatus = sysread( FROM_PARENT, $buffer, 256 ); die "Cannot sync with parent: $!" if not defined $errstatus; close FROM_PARENT; close(STDIN); open( STDIN, "<&" . $slv->fileno() ) or die "Couldn't reopen STDIN for reading, $!\n"; close(STDOUT); open( STDOUT, ">&" . $slv->fileno() ) or die "Couldn't reopen STDOUT for writing, $!\n"; close(STDERR); open( STDERR, ">&" . $slv->fileno() ) or die "Couldn't reopen STDERR for writing, $!\n"; { exec(@cmd) }; print TO_PARENT $! + 0; die "Cannot exec(@cmd): $!\n"; } # This is sort of for code compatibility, and to make debugging a little # easier. By code compatibility I mean that previously the process's # handle was referenced by $process{Pty_Handle} instead of just $process. # This is almost like 'naming' the handle to the process. # I think this also reflects Tcl Expect-like behavior. ${*$self}{exp_Pty_Handle} = "spawn id(" . $self->fileno() . ")"; if ( ( ${*$self}{"exp_Debug"} ) or ( ${*$self}{"exp_Exp_Internal"} ) ) { cluck( "Spawned '@cmd'\r\n", "\t${*$self}{exp_Pty_Handle}\r\n", "\tPid: ${*$self}{exp_Pid}\r\n", "\tTty: " . $self->SUPER::ttyname() . "\r\n", ); } $Expect::Spawned_PIDs{ ${*$self}{exp_Pid} } = undef; return $self; } sub exp_init { my ($class, $self) = @_; # take a filehandle, for use later with expect() or interconnect() . # All the functions are written for reading from a tty, so if the naming # scheme looks odd, that's why. bless $self, $class; croak "exp_init not passed a file object, stopped" unless defined( $self->fileno() ); $self->autoflush(1); # Define standard variables.. debug states, etc. $self->_init_vars(); # Turn of logging. By default we don't want crap from a file to get spewed # on screen as we read it. ${*$self}{exp_Log_Stdout} = 0; ${*$self}{exp_Pty_Handle} = "handle id(" . $self->fileno() . ")"; ${*$self}{exp_Pty_Handle} = "STDIN" if $self->fileno() == fileno(STDIN); print STDERR "Initialized ${*$self}{exp_Pty_Handle}.'\r\n" if ${*$self}{"exp_Debug"}; return $self; } # make an alias *init = \&exp_init; ###################################################################### # We're happy OOP people. No direct access to stuff. # For standard read-writeable parameters, we define some autoload magic... my %Writeable_Vars = ( debug => 'exp_Debug', exp_internal => 'exp_Exp_Internal', do_soft_close => 'exp_Do_Soft_Close', max_accum => 'exp_Max_Accum', match_max => 'exp_Max_Accum', notransfer => 'exp_NoTransfer', log_stdout => 'exp_Log_Stdout', log_user => 'exp_Log_Stdout', log_group => 'exp_Log_Group', manual_stty => 'exp_Manual_Stty', restart_timeout_upon_receive => 'exp_Continue', raw_pty => 'exp_Raw_Pty', ); my %Readable_Vars = ( pid => 'exp_Pid', exp_pid => 'exp_Pid', exp_match_number => 'exp_Match_Number', match_number => 'exp_Match_Number', exp_error => 'exp_Error', error => 'exp_Error', exp_command => 'exp_Command', command => 'exp_Command', exp_match => 'exp_Match', match => 'exp_Match', exp_matchlist => 'exp_Matchlist', matchlist => 'exp_Matchlist', exp_before => 'exp_Before', before => 'exp_Before', exp_after => 'exp_After', after => 'exp_After', exp_exitstatus => 'exp_Exit', exitstatus => 'exp_Exit', exp_pty_handle => 'exp_Pty_Handle', pty_handle => 'exp_Pty_Handle', exp_logfile => 'exp_Log_File', logfile => 'exp_Log_File', %Writeable_Vars, ); sub AUTOLOAD { my ($self, @args) = @_; my $type = ref($self) or croak "$self is not an object"; use vars qw($AUTOLOAD); my $name = $AUTOLOAD; $name =~ s/.*:://; # strip fully-qualified portion unless ( exists $Readable_Vars{$name} ) { croak "ERROR: cannot find method `$name' in class $type"; } my $varname = $Readable_Vars{$name}; my $tmp; $tmp = ${*$self}{$varname} if exists ${*$self}{$varname}; if (@args) { if ( exists $Writeable_Vars{$name} ) { my $ref = ref($tmp); if ( $ref eq 'ARRAY' ) { ${*$self}{$varname} = [@args]; } elsif ( $ref eq 'HASH' ) { ${*$self}{$varname} = {@args}; } else { ${*$self}{$varname} = shift @args; } } else { carp "Trying to set read-only variable `$name'" if $^W; } } my $ref = ref($tmp); return ( wantarray ? @{$tmp} : $tmp ) if ( $ref eq 'ARRAY' ); return ( wantarray ? %{$tmp} : $tmp ) if ( $ref eq 'HASH' ); return $tmp; } ###################################################################### sub set_seq { my ( $self, $escape_sequence, $function, $params, @args ) = @_; # Set an escape sequence/function combo for a read handle for interconnect. # Ex: $read_handle->set_seq('',\&function,\@parameters); ${ ${*$self}{exp_Function} }{$escape_sequence} = $function; if ( ( !defined($function) ) || ( $function eq 'undef' ) ) { ${ ${*$self}{exp_Function} }{$escape_sequence} = \&_undef; } ${ ${*$self}{exp_Parameters} }{$escape_sequence} = $params; # This'll be a joy to execute. :) if ( ${*$self}{"exp_Debug"} ) { print STDERR "Escape seq. '" . $escape_sequence; print STDERR "' function for ${*$self}{exp_Pty_Handle} set to '"; print STDERR ${ ${*$self}{exp_Function} }{$escape_sequence}; print STDERR "(" . join( ',', @args ) . ")'\r\n"; } } sub set_group { my ($self, @args) = @_; # Make sure we can read from the read handle if ( !defined( $args[0] ) ) { if ( defined( ${*$self}{exp_Listen_Group} ) ) { return @{ ${*$self}{exp_Listen_Group} }; } else { # Refrain from referencing an undef return; } } @{ ${*$self}{exp_Listen_Group} } = (); if ( $self->_get_mode() !~ 'r' ) { warn( "Attempting to set a handle group on ${*$self}{exp_Pty_Handle}, ", "a non-readable handle!\r\n" ); } while ( my $write_handle = shift @args ) { if ( $write_handle->_get_mode() !~ 'w' ) { warn( "Attempting to set a non-writeable listen handle ", "${*$write_handle}{exp_Pty_handle} for ", "${*$self}{exp_Pty_Handle}!\r\n" ); } push( @{ ${*$self}{exp_Listen_Group} }, $write_handle ); } } sub log_file { my ($self, $file, $mode) = @_; $mode ||= "a"; return ( ${*$self}{exp_Log_File} ) if @_ < 2; # we got no param, return filehandle # $e->log_file(undef) is an acceptable call hence we need to check the number of parameters here if ( ${*$self}{exp_Log_File} and ref( ${*$self}{exp_Log_File} ) ne 'CODE' ) { close( ${*$self}{exp_Log_File} ); } ${*$self}{exp_Log_File} = undef; return if ( not $file ); my $fh = $file; if ( not ref($file) ) { # it's a filename $fh = IO::File->new( $file, $mode ) or croak "Cannot open logfile $file: $!"; } if ( ref($file) ne 'CODE' ) { croak "Given logfile doesn't have a 'print' method" if not $fh->can("print"); $fh->autoflush(1); # so logfile is up to date } ${*$self}{exp_Log_File} = $fh; return $fh; } # I'm going to leave this here in case I might need to change something. # Previously this was calling `stty`, in a most bastardized manner. sub exp_stty { my ($self) = shift; my ($mode) = "@_"; return unless defined $mode; if ( not defined $INC{"IO/Stty.pm"} ) { carp "IO::Stty not installed, cannot change mode"; return; } if ( ${*$self}{"exp_Debug"} ) { print STDERR "Setting ${*$self}{exp_Pty_Handle} to tty mode '$mode'\r\n"; } unless ( POSIX::isatty($self) ) { if ( ${*$self}{"exp_Debug"} or $^W ) { warn "${*$self}{exp_Pty_Handle} is not a tty. Not changing mode"; } return ''; # No undef to avoid warnings elsewhere. } IO::Stty::stty( $self, split( /\s/, $mode ) ); } *stty = \&exp_stty; # If we want to clear the buffer. Otherwise Accum will grow during send_slow # etc. and contain the remainder after matches. sub clear_accum { my ($self) = @_; return $self->set_accum(''); } sub set_accum { my ($self, $accum) = @_; my $old_accum = ${*$self}{exp_Accum}; ${*$self}{exp_Accum} = $accum; # return the contents of the accumulator. return $old_accum; } sub get_accum { my ($self) = @_; return ${*$self}{exp_Accum}; } ###################################################################### # define constants for pattern subs sub exp_continue {"exp_continue"} sub exp_continue_timeout {"exp_continue_timeout"} ###################################################################### # Expect on multiple objects at once. # # Call as Expect::expect($timeout, -i => \@exp_list, @patternlist, # -i => $exp, @pattern_list, ...); # or $exp->expect($timeout, @patternlist, -i => \@exp_list, @patternlist, # -i => $exp, @pattern_list, ...); # # Patterns are arrays that consist of # [ $pattern_type, $pattern, $sub, @subparms ] # # Optional $pattern_type is '-re' (RegExp, default) or '-ex' (exact); # # $sub is optional CODE ref, which is called as &{$sub}($exp, @subparms) # if pattern matched; may return exp_continue or exp_continue_timeout. # # Old-style syntax (pure pattern strings with optional type) also supported. # sub expect { my $self; print STDERR ("expect(@_) called...\n") if $Expect::Debug; if ( defined( $_[0] ) ) { if ( ref( $_[0] ) and $_[0]->isa('Expect') ) { $self = shift; } elsif ( $_[0] eq 'Expect' ) { shift; # or as Expect->expect } } croak "expect(): not enough arguments, should be expect(timeout, [patterns...])" if @_ < 1; my $timeout; if ( looks_like_number($_[0]) or not defined $_[0] ) { $timeout = shift; } else { $timeout = $self->timeout; } my $timeout_hook = undef; my @object_list; my %patterns; my @pattern_list; my @timeout_list; my $curr_list; if ($self) { $curr_list = [$self]; } else { # called directly, so first parameter must be '-i' to establish # object list. $curr_list = []; croak "expect(): ERROR: if called directly (not as \$obj->expect(...), but as Expect::expect(...), first parameter MUST be '-i' to set an object (list) for the patterns to work on." if ( $_[0] ne '-i' ); } # Let's make a list of patterns wanting to be evaled as regexps. my $parm; my $parm_nr = 1; while ( defined( $parm = shift ) ) { print STDERR ("expect(): handling param '$parm'...\n") if $Expect::Debug; if ( ref($parm) ) { if ( ref($parm) eq 'Regexp' ) { push @pattern_list, [ $parm_nr, '-re', $parm, undef ]; } elsif ( ref($parm) eq 'ARRAY' ) { # if ( ref($parm) eq 'ARRAY' ) { my $err = _add_patterns_to_list( \@pattern_list, \@timeout_list, $parm_nr, $parm ); carp( "expect(): Warning: multiple `timeout' patterns (", scalar(@timeout_list), ").\r\n" ) if @timeout_list > 1; $timeout_hook = $timeout_list[-1] if $timeout_list[-1]; croak $err if $err; $parm_nr++; } else { croak("expect(): Unknown pattern ref $parm"); } } else { # not a ref, is an option or raw pattern if ( substr( $parm, 0, 1 ) eq '-' ) { # it's an option print STDERR ("expect(): handling option '$parm'...\n") if $Expect::Debug; if ( $parm eq '-i' ) { # first add collected patterns to object list if ( scalar(@$curr_list) ) { push @object_list, $curr_list if not exists $patterns{"$curr_list"}; push @{ $patterns{"$curr_list"} }, @pattern_list; @pattern_list = (); } # now put parm(s) into current object list if ( ref( $_[0] ) eq 'ARRAY' ) { $curr_list = shift; } else { $curr_list = [shift]; } } elsif ( $parm eq '-re' or $parm eq '-ex' ) { if ( ref( $_[1] ) eq 'CODE' ) { push @pattern_list, [ $parm_nr, $parm, shift, shift ]; } else { push @pattern_list, [ $parm_nr, $parm, shift, undef ]; } $parm_nr++; } else { croak("Unknown option $parm"); } } else { # a plain pattern, check if it is followed by a CODE ref if ( ref( $_[0] ) eq 'CODE' ) { if ( $parm eq 'timeout' ) { push @timeout_list, shift; carp( "expect(): Warning: multiple `timeout' patterns (", scalar(@timeout_list), ").\r\n" ) if @timeout_list > 1; $timeout_hook = $timeout_list[-1] if $timeout_list[-1]; } elsif ( $parm eq 'eof' ) { push @pattern_list, [ $parm_nr, "-$parm", undef, shift ]; } else { push @pattern_list, [ $parm_nr, '-ex', $parm, shift ]; } } else { print STDERR ("expect(): exact match '$parm'...\n") if $Expect::Debug; push @pattern_list, [ $parm_nr, '-ex', $parm, undef ]; } $parm_nr++; } } } # add rest of collected patterns to object list carp "expect(): Empty object list" unless $curr_list; push @object_list, $curr_list if not exists $patterns{"$curr_list"}; push @{ $patterns{"$curr_list"} }, @pattern_list; my $debug = $self ? ${*$self}{exp_Debug} : $Expect::Debug; my $internal = $self ? ${*$self}{exp_Exp_Internal} : $Expect::Exp_Internal; # now start matching... if (@Expect::Before_List) { print STDERR ("Starting BEFORE pattern matching...\r\n") if ( $debug or $internal ); _multi_expect( 0, undef, @Expect::Before_List ); } cluck("Starting EXPECT pattern matching...\r\n") if ( $debug or $internal ); my @ret; @ret = _multi_expect( $timeout, $timeout_hook, map { [ $_, @{ $patterns{"$_"} } ] } @object_list ); if (@Expect::After_List) { print STDERR ("Starting AFTER pattern matching...\r\n") if ( $debug or $internal ); _multi_expect( 0, undef, @Expect::After_List ); } return wantarray ? @ret : $ret[0]; } ###################################################################### # the real workhorse # sub _multi_expect { my ($timeout, $timeout_hook, @params) = @_; if ($timeout_hook) { croak "Unknown timeout_hook type $timeout_hook" unless ( ref($timeout_hook) eq 'CODE' or ref($timeout_hook) eq 'ARRAY' ); } foreach my $pat (@params) { my @patterns = @{$pat}[ 1 .. $#{$pat} ]; foreach my $exp ( @{ $pat->[0] } ) { ${*$exp}{exp_New_Data} = 1; # first round we always try to match if ( exists ${*$exp}{"exp_Max_Accum"} and ${*$exp}{"exp_Max_Accum"} ) { ${*$exp}{exp_Accum} = $exp->_trim_length( ${*$exp}{exp_Accum}, ${*$exp}{exp_Max_Accum} ); } print STDERR ( "${*$exp}{exp_Pty_Handle}: beginning expect.\r\n", "\tTimeout: ", ( defined($timeout) ? $timeout : "unlimited" ), " seconds.\r\n", "\tCurrent time: " . localtime() . "\r\n", ) if $Expect::Debug; # What are we expecting? What do you expect? :-) if ( ${*$exp}{exp_Exp_Internal} ) { print STDERR "${*$exp}{exp_Pty_Handle}: list of patterns:\r\n"; foreach my $pattern (@patterns) { print STDERR ( ' ', defined( $pattern->[0] ) ? '#' . $pattern->[0] . ': ' : '', $pattern->[1], " `", _make_readable( $pattern->[2] ), "'\r\n" ); } print STDERR "\r\n"; } } } my $successful_pattern; my $exp_matched; my $err; my $before; my $after; my $match; my @matchlist; # Set the last loop time to now for time comparisons at end of loop. my $start_loop_time = time(); my $exp_cont = 1; READLOOP: while ($exp_cont) { $exp_cont = 1; $err = ""; my $rmask = ''; my $time_left = undef; if ( defined $timeout ) { $time_left = $timeout - ( time() - $start_loop_time ); $time_left = 0 if $time_left < 0; } $exp_matched = undef; # Test for a match first so we can test the current Accum w/out # worrying about an EOF. foreach my $pat (@params) { my @patterns = @{$pat}[ 1 .. $#{$pat} ]; foreach my $exp ( @{ $pat->[0] } ) { # build mask for select in next section... my $fn = $exp->fileno(); vec( $rmask, $fn, 1 ) = 1 if defined $fn; next unless ${*$exp}{exp_New_Data}; # clear error status ${*$exp}{exp_Error} = undef; ${*$exp}{exp_After} = undef; ${*$exp}{exp_Match_Number} = undef; ${*$exp}{exp_Match} = undef; # This could be huge. We should attempt to do something # about this. Because the output is used for debugging # I'm of the opinion that showing smaller amounts if the # total is huge should be ok. # Thus the 'trim_length' print STDERR ( "\r\n${*$exp}{exp_Pty_Handle}: Does `", $exp->_trim_length( _make_readable( ${*$exp}{exp_Accum} ) ), "'\r\nmatch:\r\n" ) if ${*$exp}{exp_Exp_Internal}; # we don't keep the parameter number anymore # (clashes with before & after), instead the parameter number is # stored inside the pattern; we keep the pattern ref # and look up the number later. foreach my $pattern (@patterns) { print STDERR ( " pattern", defined( $pattern->[0] ) ? ' #' . $pattern->[0] : '', ": ", $pattern->[1], " `", _make_readable( $pattern->[2] ), "'? " ) if ( ${*$exp}{exp_Exp_Internal} ); # Matching exactly if ( $pattern->[1] eq '-ex' ) { my $match_index = index( ${*$exp}{exp_Accum}, $pattern->[2] ); # We matched if $match_index > -1 if ( $match_index > -1 ) { $before = substr( ${*$exp}{exp_Accum}, 0, $match_index ); $match = substr( ${*$exp}{exp_Accum}, $match_index, length( $pattern->[2] ) ); $after = substr( ${*$exp}{exp_Accum}, $match_index + length( $pattern->[2] ) ); ${*$exp}{exp_Before} = $before; ${*$exp}{exp_Match} = $match; ${*$exp}{exp_After} = $after; ${*$exp}{exp_Match_Number} = $pattern->[0]; $exp_matched = $exp; } } elsif ( $pattern->[1] eq '-re' ) { if ($Expect::Multiline_Matching) { @matchlist = ( ${*$exp}{exp_Accum} =~ m/($pattern->[2])/m); } else { @matchlist = ( ${*$exp}{exp_Accum} =~ m/($pattern->[2])/); } if (@matchlist) { # Matching regexp $match = shift @matchlist; my $start = index ${*$exp}{exp_Accum}, $match; die 'The match could not be found' if $start == -1; $before = substr ${*$exp}{exp_Accum}, 0, $start; $after = substr ${*$exp}{exp_Accum}, $start + length($match); ${*$exp}{exp_Before} = $before; ${*$exp}{exp_Match} = $match; ${*$exp}{exp_After} = $after; #pop @matchlist; # remove kludged empty bracket from end @{ ${*$exp}{exp_Matchlist} } = @matchlist; ${*$exp}{exp_Match_Number} = $pattern->[0]; $exp_matched = $exp; } } else { # 'timeout' or 'eof' } if ($exp_matched) { ${*$exp}{exp_Accum} = $after unless ${*$exp}{exp_NoTransfer}; print STDERR "YES!!\r\n" if ${*$exp}{exp_Exp_Internal}; print STDERR ( " Before match string: `", $exp->_trim_length( _make_readable( ($before) ) ), "'\r\n", " Match string: `", _make_readable($match), "'\r\n", " After match string: `", $exp->_trim_length( _make_readable( ($after) ) ), "'\r\n", " Matchlist: (", join( ", ", map { "`" . $exp->_trim_length( _make_readable( ($_) ) ) . "'" } @matchlist, ), ")\r\n", ) if ( ${*$exp}{exp_Exp_Internal} ); # call hook function if defined if ( $pattern->[3] ) { print STDERR ( "Calling hook $pattern->[3]...\r\n", ) if ( ${*$exp}{exp_Exp_Internal} or $Expect::Debug ); if ( $#{$pattern} > 3 ) { # call with parameters if given $exp_cont = &{ $pattern->[3] }( $exp, @{$pattern}[ 4 .. $#{$pattern} ] ); } else { $exp_cont = &{ $pattern->[3] }($exp); } } if ( $exp_cont and $exp_cont eq exp_continue ) { print STDERR ("Continuing expect, restarting timeout...\r\n") if ( ${*$exp}{exp_Exp_Internal} or $Expect::Debug ); $start_loop_time = time(); # restart timeout count next READLOOP; } elsif ( $exp_cont and $exp_cont eq exp_continue_timeout ) { print STDERR ("Continuing expect...\r\n") if ( ${*$exp}{exp_Exp_Internal} or $Expect::Debug ); next READLOOP; } last READLOOP; } print STDERR "No.\r\n" if ${*$exp}{exp_Exp_Internal}; } print STDERR "\r\n" if ${*$exp}{exp_Exp_Internal}; # don't have to match again until we get new data ${*$exp}{exp_New_Data} = 0; } } # End of matching section # No match, let's see what is pending on the filehandles... print STDERR ( "Waiting for new data (", defined($time_left) ? $time_left : 'unlimited', " seconds)...\r\n", ) if ( $Expect::Exp_Internal or $Expect::Debug ); my $nfound; SELECT: { $nfound = select( $rmask, undef, undef, $time_left ); if ( $nfound < 0 ) { if ( $!{EINTR} and $Expect::IgnoreEintr ) { print STDERR ("ignoring EINTR, restarting select()...\r\n") if ( $Expect::Exp_Internal or $Expect::Debug ); next SELECT; } print STDERR ("select() returned error code '$!'\r\n") if ( $Expect::Exp_Internal or $Expect::Debug ); # returned error $err = "4:$!"; last READLOOP; } } # go until we don't find something (== timeout). if ( $nfound == 0 ) { # No pattern, no EOF. Did we time out? $err = "1:TIMEOUT"; foreach my $pat (@params) { foreach my $exp ( @{ $pat->[0] } ) { $before = ${*$exp}{exp_Before} = ${*$exp}{exp_Accum}; next if not defined $exp->fileno(); # skip already closed ${*$exp}{exp_Error} = $err unless ${*$exp}{exp_Error}; } } print STDERR ("TIMEOUT\r\n") if ( $Expect::Debug or $Expect::Exp_Internal ); if ($timeout_hook) { my $ret; print STDERR ("Calling timeout function $timeout_hook...\r\n") if ( $Expect::Debug or $Expect::Exp_Internal ); if ( ref($timeout_hook) eq 'CODE' ) { $ret = &{$timeout_hook}( $params[0]->[0] ); } else { if ( $#{$timeout_hook} > 3 ) { $ret = &{ $timeout_hook->[3] }( $params[0]->[0], @{$timeout_hook}[ 4 .. $#{$timeout_hook} ] ); } else { $ret = &{ $timeout_hook->[3] }( $params[0]->[0] ); } } if ( $ret and $ret eq exp_continue ) { $start_loop_time = time(); # restart timeout count next READLOOP; } } last READLOOP; } my @bits = split( //, unpack( 'b*', $rmask ) ); foreach my $pat (@params) { foreach my $exp ( @{ $pat->[0] } ) { next if not defined $exp->fileno(); # skip already closed if ( $bits[ $exp->fileno() ] ) { print STDERR ("${*$exp}{exp_Pty_Handle}: new data.\r\n") if $Expect::Debug; # read in what we found. my $buffer; my $nread = sysread( $exp, $buffer, 2048 ); # Make errors (nread undef) show up as EOF. $nread = 0 unless defined($nread); if ( $nread == 0 ) { print STDERR ("${*$exp}{exp_Pty_Handle}: EOF\r\n") if ($Expect::Debug); $before = ${*$exp}{exp_Before} = $exp->clear_accum(); $err = "2:EOF"; ${*$exp}{exp_Error} = $err; ${*$exp}{exp_Has_EOF} = 1; $exp_cont = undef; foreach my $eof_pat ( grep { $_->[1] eq '-eof' } @{$pat}[ 1 .. $#{$pat} ] ) { my $ret; print STDERR ( "Calling EOF hook $eof_pat->[3]...\r\n", ) if ($Expect::Debug); if ( $#{$eof_pat} > 3 ) { # call with parameters if given $ret = &{ $eof_pat->[3] }( $exp, @{$eof_pat}[ 4 .. $#{$eof_pat} ] ); } else { $ret = &{ $eof_pat->[3] }($exp); } if ($ret and ( $ret eq exp_continue or $ret eq exp_continue_timeout ) ) { $exp_cont = $ret; } } # is it dead? if ( defined( ${*$exp}{exp_Pid} ) ) { my $ret = waitpid( ${*$exp}{exp_Pid}, POSIX::WNOHANG ); if ( $ret == ${*$exp}{exp_Pid} ) { printf STDERR ( "%s: exit(0x%02X)\r\n", ${*$exp}{exp_Pty_Handle}, $? ) if ($Expect::Debug); $err = "3:Child PID ${*$exp}{exp_Pid} exited with status $?"; ${*$exp}{exp_Error} = $err; ${*$exp}{exp_Exit} = $?; delete $Expect::Spawned_PIDs{ ${*$exp}{exp_Pid} }; ${*$exp}{exp_Pid} = undef; } } print STDERR ("${*$exp}{exp_Pty_Handle}: closing...\r\n") if ($Expect::Debug); $exp->hard_close(); next; } print STDERR ("${*$exp}{exp_Pty_Handle}: read $nread byte(s).\r\n") if ($Expect::Debug); # ugly hack for broken solaris ttys that spew # into our pretty output $buffer =~ s/ \cH//g if not ${*$exp}{exp_Raw_Pty}; # Append it to the accumulator. ${*$exp}{exp_Accum} .= $buffer; if ( exists ${*$exp}{exp_Max_Accum} and ${*$exp}{exp_Max_Accum} ) { ${*$exp}{exp_Accum} = $exp->_trim_length( ${*$exp}{exp_Accum}, ${*$exp}{exp_Max_Accum} ); } ${*$exp}{exp_New_Data} = 1; # next round we try to match again $exp_cont = exp_continue if ( exists ${*$exp}{exp_Continue} and ${*$exp}{exp_Continue} ); # Now propagate what we have read to other listeners... $exp->_print_handles($buffer); # End handle reading section. } } } # end read loop $start_loop_time = time() # restart timeout count if ( $exp_cont and $exp_cont eq exp_continue ); } # End READLOOP # Post loop. Do we have anything? # Tell us status if ( $Expect::Debug or $Expect::Exp_Internal ) { if ($exp_matched) { print STDERR ( "Returning from expect ", ${*$exp_matched}{exp_Error} ? 'un' : '', "successfully.", ${*$exp_matched}{exp_Error} ? "\r\n Error: ${*$exp_matched}{exp_Error}." : '', "\r\n" ); } else { print STDERR ("Returning from expect with TIMEOUT or EOF\r\n"); } if ( $Expect::Debug and $exp_matched ) { print STDERR " ${*$exp_matched}{exp_Pty_Handle}: accumulator: `"; if ( ${*$exp_matched}{exp_Error} ) { print STDERR ( $exp_matched->_trim_length( _make_readable( ${*$exp_matched}{exp_Before} ) ), "'\r\n" ); } else { print STDERR ( $exp_matched->_trim_length( _make_readable( ${*$exp_matched}{exp_Accum} ) ), "'\r\n" ); } } } if ($exp_matched) { return wantarray ? ( ${*$exp_matched}{exp_Match_Number}, ${*$exp_matched}{exp_Error}, ${*$exp_matched}{exp_Match}, ${*$exp_matched}{exp_Before}, ${*$exp_matched}{exp_After}, $exp_matched, ) : ${*$exp_matched}{exp_Match_Number}; } return wantarray ? ( undef, $err, undef, $before, undef, undef ) : undef; } # Patterns are arrays that consist of # [ $pattern_type, $pattern, $sub, @subparms ] # optional $pattern_type is '-re' (RegExp, default) or '-ex' (exact); # $sub is optional CODE ref, which is called as &{$sub}($exp, @subparms) # if pattern matched; # the $parm_nr gets unshifted onto the array for reporting purposes. sub _add_patterns_to_list { my ($listref, $timeoutlistref,$store_parm_nr, @params) = @_; # $timeoutlistref gets timeout patterns my $parm_nr = $store_parm_nr || 1; foreach my $parm (@params) { if ( not ref($parm) eq 'ARRAY' ) { return "Parameter #$parm_nr is not an ARRAY ref."; } $parm = [@$parm]; # make copy if ( $parm->[0] =~ m/\A-/ ) { # it's an option if ( $parm->[0] ne '-re' and $parm->[0] ne '-ex' ) { return "Unknown option $parm->[0] in pattern #$parm_nr"; } } else { if ( $parm->[0] eq 'timeout' ) { if ( defined $timeoutlistref ) { splice @$parm, 0, 1, ( "-$parm->[0]", undef ); unshift @$parm, $store_parm_nr ? $parm_nr : undef; push @$timeoutlistref, $parm; } next; } elsif ( $parm->[0] eq 'eof' ) { splice @$parm, 0, 1, ( "-$parm->[0]", undef ); } else { unshift @$parm, '-re'; # defaults to RegExp } } if ( @$parm > 2 ) { if ( ref( $parm->[2] ) ne 'CODE' ) { croak( "Pattern #$parm_nr doesn't have a CODE reference", "after the pattern." ); } } else { push @$parm, undef; # make sure we have three elements } unshift @$parm, $store_parm_nr ? $parm_nr : undef; push @$listref, $parm; $parm_nr++; } return; } ###################################################################### # $process->interact([$in_handle],[$escape sequence]) # If you don't specify in_handle STDIN will be used. sub interact { my ($self, $infile, $escape_sequence) = @_; my $outfile; my @old_group = $self->set_group(); # If the handle is STDIN we'll # $infile->fileno == 0 should be stdin.. follow stdin rules. no strict 'subs'; # Allow bare word 'STDIN' unless ( defined($infile) ) { # We need a handle object Associated with STDIN. $infile = IO::File->new; $infile->IO::File::fdopen( STDIN, 'r' ); $outfile = IO::File->new; $outfile->IO::File::fdopen( STDOUT, 'w' ); } elsif ( fileno($infile) == fileno(STDIN) ) { # With STDIN we want output to go to stdout. $outfile = IO::File->new; $outfile->IO::File::fdopen( STDOUT, 'w' ); } else { undef($outfile); } # Here we assure ourselves we have an Expect object. my $in_object = Expect->exp_init($infile); if ( defined($outfile) ) { # as above.. we want output to go to stdout if we're given stdin. my $out_object = Expect->exp_init($outfile); $out_object->manual_stty(1); $self->set_group($out_object); } else { $self->set_group($in_object); } $in_object->set_group($self); $in_object->set_seq( $escape_sequence, undef ) if defined($escape_sequence); # interconnect normally sets stty -echo raw. Interact really sort # of implies we don't do that by default. If anyone wanted to they could # set it before calling interact, of use interconnect directly. my $old_manual_stty_val = $self->manual_stty(); $self->manual_stty(1); # I think this is right. Don't send stuff from in_obj to stdout by default. # in theory whatever 'self' is should echo what's going on. my $old_log_stdout_val = $self->log_stdout(); $self->log_stdout(0); $in_object->log_stdout(0); # Allow for the setting of an optional EOF escape function. # $in_object->set_seq('EOF',undef); # $self->set_seq('EOF',undef); Expect::interconnect( $self, $in_object ); $self->log_stdout($old_log_stdout_val); $self->set_group(@old_group); # If old_group was undef, make sure that occurs. This is a slight hack since # it modifies the value directly. # Normally an undef passed to set_group will return the current groups. # It is possible that it may be of worth to make it possible to undef # The current group without doing this. unless (@old_group) { @{ ${*$self}{exp_Listen_Group} } = (); } $self->manual_stty($old_manual_stty_val); return; } sub interconnect { my (@handles) = @_; # my ($handle)=(shift); call as Expect::interconnect($spawn1,$spawn2,...) my ( $nread ); my ( $rout, $emask, $eout ); my ( $escape_character_buffer ); my ( $read_mask, $temp_mask ) = ( '', '' ); # Get read/write handles foreach my $handle (@handles) { $temp_mask = ''; vec( $temp_mask, $handle->fileno(), 1 ) = 1; # Under Linux w/ 5.001 the next line comes up w/ 'Uninit var.'. # It appears to be impossible to make the warning go away. # doing something like $temp_mask='' unless defined ($temp_mask) # has no effect whatsoever. This may be a bug in 5.001. $read_mask = $read_mask | $temp_mask; } if ($Expect::Debug) { print STDERR "Read handles:\r\n"; foreach my $handle (@handles) { print STDERR "\tRead handle: "; print STDERR "'${*$handle}{exp_Pty_Handle}'\r\n"; print STDERR "\t\tListen Handles:"; foreach my $write_handle ( @{ ${*$handle}{exp_Listen_Group} } ) { print STDERR " '${*$write_handle}{exp_Pty_Handle}'"; } print STDERR ".\r\n"; } } # I think if we don't set raw/-echo here we may have trouble. We don't # want a bunch of echoing crap making all the handles jabber at each other. foreach my $handle (@handles) { unless ( ${*$handle}{"exp_Manual_Stty"} ) { # This is probably O/S specific. ${*$handle}{exp_Stored_Stty} = $handle->exp_stty('-g'); print STDERR "Setting tty for ${*$handle}{exp_Pty_Handle} to 'raw -echo'.\r\n" if ${*$handle}{"exp_Debug"}; $handle->exp_stty("raw -echo"); } foreach my $write_handle ( @{ ${*$handle}{exp_Listen_Group} } ) { unless ( ${*$write_handle}{"exp_Manual_Stty"} ) { ${*$write_handle}{exp_Stored_Stty} = $write_handle->exp_stty('-g'); print STDERR "Setting ${*$write_handle}{exp_Pty_Handle} to 'raw -echo'.\r\n" if ${*$handle}{"exp_Debug"}; $write_handle->exp_stty("raw -echo"); } } } print STDERR "Attempting interconnection\r\n" if $Expect::Debug; # Wait until the process dies or we get EOF # In the case of !${*$handle}{exp_Pid} it means # the handle was exp_inited instead of spawned. CONNECT_LOOP: # Go until we have a reason to stop while (1) { # test each handle to see if it's still alive. foreach my $read_handle (@handles) { waitpid( ${*$read_handle}{exp_Pid}, WNOHANG ) if ( exists( ${*$read_handle}{exp_Pid} ) and ${*$read_handle}{exp_Pid} ); if ( exists( ${*$read_handle}{exp_Pid} ) and ( ${*$read_handle}{exp_Pid} ) and ( !kill( 0, ${*$read_handle}{exp_Pid} ) ) ) { print STDERR "Got EOF (${*$read_handle}{exp_Pty_Handle} died) reading ${*$read_handle}{exp_Pty_Handle}\r\n" if ${*$read_handle}{"exp_Debug"}; last CONNECT_LOOP unless defined( ${ ${*$read_handle}{exp_Function} }{"EOF"} ); last CONNECT_LOOP unless &{ ${ ${*$read_handle}{exp_Function} }{"EOF"} } ( @{ ${ ${*$read_handle}{exp_Parameters} }{"EOF"} } ); } } # Every second? No, go until we get something from someone. my $nfound = select( $rout = $read_mask, undef, $eout = $emask, undef ); # Is there anything to share? May be -1 if interrupted by a signal... next CONNECT_LOOP if not defined $nfound or $nfound < 1; # Which handles have stuff? my @bits = split( //, unpack( 'b*', $rout ) ); $eout = 0 unless defined($eout); my @ebits = split( //, unpack( 'b*', $eout ) ); # print "Ebits: $eout\r\n"; foreach my $read_handle (@handles) { if ( $bits[ $read_handle->fileno() ] ) { $nread = sysread( $read_handle, ${*$read_handle}{exp_Pty_Buffer}, 1024 ); # Appease perl -w $nread = 0 unless defined($nread); print STDERR "interconnect: read $nread byte(s) from ${*$read_handle}{exp_Pty_Handle}.\r\n" if ${*$read_handle}{"exp_Debug"} > 1; # Test for escape seq. before printing. # Appease perl -w $escape_character_buffer = '' unless defined($escape_character_buffer); $escape_character_buffer .= ${*$read_handle}{exp_Pty_Buffer}; foreach my $escape_sequence ( keys( %{ ${*$read_handle}{exp_Function} } ) ) { print STDERR "Tested escape sequence $escape_sequence from ${*$read_handle}{exp_Pty_Handle}" if ${*$read_handle}{"exp_Debug"} > 1; # Make sure it doesn't grow out of bounds. $escape_character_buffer = $read_handle->_trim_length( $escape_character_buffer, ${*$read_handle}{"exp_Max_Accum"} ) if ( ${*$read_handle}{"exp_Max_Accum"} ); if ( $escape_character_buffer =~ /($escape_sequence)/ ) { my $match = $1; if ( ${*$read_handle}{"exp_Debug"} ) { print STDERR "\r\ninterconnect got escape sequence from ${*$read_handle}{exp_Pty_Handle}.\r\n"; # I'm going to make the esc. seq. pretty because it will # probably contain unprintable characters. print STDERR "\tEscape Sequence: '" . _trim_length( undef, _make_readable($escape_sequence) ) . "'\r\n"; print STDERR "\tMatched by string: '" . _trim_length( undef, _make_readable($match) ) . "'\r\n"; } # Print out stuff before the escape. # Keep in mind that the sequence may have been split up # over several reads. # Let's get rid of it from this read. If part of it was # in the last read there's not a lot we can do about it now. if ( ${*$read_handle}{exp_Pty_Buffer} =~ /([\w\W]*)($escape_sequence)/ ) { $read_handle->_print_handles($1); } else { $read_handle->_print_handles( ${*$read_handle}{exp_Pty_Buffer} ); } # Clear the buffer so no more matches can be made and it will # only be printed one time. ${*$read_handle}{exp_Pty_Buffer} = ''; $escape_character_buffer = ''; # Do the function here. Must return non-zero to continue. # More cool syntax. Maybe I should turn these in to objects. last CONNECT_LOOP unless &{ ${ ${*$read_handle}{exp_Function} }{$escape_sequence} } ( @{ ${ ${*$read_handle}{exp_Parameters} }{$escape_sequence} } ); } } $nread = 0 unless defined($nread); # Appease perl -w? waitpid( ${*$read_handle}{exp_Pid}, WNOHANG ) if ( defined( ${*$read_handle}{exp_Pid} ) && ${*$read_handle}{exp_Pid} ); if ( $nread == 0 ) { print STDERR "Got EOF reading ${*$read_handle}{exp_Pty_Handle}\r\n" if ${*$read_handle}{"exp_Debug"}; last CONNECT_LOOP unless defined( ${ ${*$read_handle}{exp_Function} }{"EOF"} ); last CONNECT_LOOP unless &{ ${ ${*$read_handle}{exp_Function} }{"EOF"} } ( @{ ${ ${*$read_handle}{exp_Parameters} }{"EOF"} } ); } last CONNECT_LOOP if ( $nread < 0 ); # This would be an error $read_handle->_print_handles( ${*$read_handle}{exp_Pty_Buffer} ); } # I'm removing this because I haven't determined what causes exceptions # consistently. if (0) #$ebits[$read_handle->fileno()]) { print STDERR "Got Exception reading ${*$read_handle}{exp_Pty_Handle}\r\n" if ${*$read_handle}{"exp_Debug"}; last CONNECT_LOOP unless defined( ${ ${*$read_handle}{exp_Function} }{"EOF"} ); last CONNECT_LOOP unless &{ ${ ${*$read_handle}{exp_Function} }{"EOF"} } ( @{ ${ ${*$read_handle}{exp_Parameters} }{"EOF"} } ); } } } foreach my $handle (@handles) { unless ( ${*$handle}{"exp_Manual_Stty"} ) { $handle->exp_stty( ${*$handle}{exp_Stored_Stty} ); } foreach my $write_handle ( @{ ${*$handle}{exp_Listen_Group} } ) { unless ( ${*$write_handle}{"exp_Manual_Stty"} ) { $write_handle->exp_stty( ${*$write_handle}{exp_Stored_Stty} ); } } } return; } # user can decide if log output gets also sent to logfile sub print_log_file { my ($self, @params) = @_; if ( ${*$self}{exp_Log_File} ) { if ( ref( ${*$self}{exp_Log_File} ) eq 'CODE' ) { ${*$self}{exp_Log_File}->(@params); } else { ${*$self}{exp_Log_File}->print(@params); } } return; } # we provide our own print so we can debug what gets sent to the # processes... sub print { my ( $self, @args ) = @_; return if not defined $self->fileno(); # skip if closed if ( ${*$self}{exp_Exp_Internal} ) { my $args = _make_readable( join( '', @args ) ); cluck "Sending '$args' to ${*$self}{exp_Pty_Handle}\r\n"; } foreach my $arg (@args) { while ( length($arg) > 80 ) { $self->SUPER::print( substr( $arg, 0, 80 ) ); $arg = substr( $arg, 80 ); } $self->SUPER::print($arg); } return; } # make an alias for Tcl/Expect users for a DWIM experience... *send = \&print; # This is an Expect standard. It's nice for talking to modems and the like # where from time to time they get unhappy if you send items too quickly. sub send_slow { my ($self, $sleep_time, @chunks) = @_; return if not defined $self->fileno(); # skip if closed # Flushing makes it so each character can be seen separately. my $chunk; while ( $chunk = shift @chunks ) { my @linechars = split( '', $chunk ); foreach my $char (@linechars) { # How slow? select( undef, undef, undef, $sleep_time ); print $self $char; print STDERR "Printed character \'" . _make_readable($char) . "\' to ${*$self}{exp_Pty_Handle}.\r\n" if ${*$self}{"exp_Debug"} > 1; # I think I can get away with this if I save it in accum if ( ${*$self}{"exp_Log_Stdout"} || ${*$self}{exp_Log_Group} ) { my $rmask = ""; vec( $rmask, $self->fileno(), 1 ) = 1; # .01 sec granularity should work. If we miss something it will # probably get flushed later, maybe in an expect call. while ( select( $rmask, undef, undef, .01 ) ) { my $ret = sysread( $self, ${*$self}{exp_Pty_Buffer}, 1024 ); last if not defined $ret or $ret == 0; # Is this necessary to keep? Probably.. # # if you need to expect it later. ${*$self}{exp_Accum} .= ${*$self}{exp_Pty_Buffer}; ${*$self}{exp_Accum} = $self->_trim_length( ${*$self}{exp_Accum}, ${*$self}{"exp_Max_Accum"} ) if ( ${*$self}{"exp_Max_Accum"} ); $self->_print_handles( ${*$self}{exp_Pty_Buffer} ); print STDERR "Received \'" . $self->_trim_length( _make_readable($char) ) . "\' from ${*$self}{exp_Pty_Handle}\r\n" if ${*$self}{"exp_Debug"} > 1; } } } } return; } sub test_handles { my ($timeout, @handle_list) = @_; # This should be called by Expect::test_handles($timeout,@objects); my ( $allmask, $rout ); foreach my $handle (@handle_list) { my $rmask = ''; vec( $rmask, $handle->fileno(), 1 ) = 1; $allmask = '' unless defined($allmask); $allmask = $allmask | $rmask; } my $nfound = select( $rout = $allmask, undef, undef, $timeout ); return () unless $nfound; # Which handles have stuff? my @bits = split( //, unpack( 'b*', $rout ) ); my $handle_num = 0; my @return_list = (); foreach my $handle (@handle_list) { # I go to great lengths to get perl -w to shut the hell up. if ( defined( $bits[ $handle->fileno() ] ) and ( $bits[ $handle->fileno() ] ) ) { push( @return_list, $handle_num ); } } continue { $handle_num++; } return @return_list; } # Be nice close. This should emulate what an interactive shell does after a # command finishes... sort of. We're not as patient as a shell. sub soft_close { my ($self) = @_; my ( $nfound, $nread, $rmask, $end_time, $temp_buffer ); # Give it 15 seconds to cough up an eof. cluck "Closing ${*$self}{exp_Pty_Handle}.\r\n" if ${*$self}{exp_Debug}; return -1 if not defined $self->fileno(); # skip if handle already closed unless ( exists ${*$self}{exp_Has_EOF} and ${*$self}{exp_Has_EOF} ) { $end_time = time() + 15; while ( $end_time > time() ) { my $select_time = $end_time - time(); # Sanity check. $select_time = 0 if $select_time < 0; $rmask = ''; vec( $rmask, $self->fileno(), 1 ) = 1; ($nfound) = select( $rmask, undef, undef, $select_time ); last unless ( defined($nfound) && $nfound ); $nread = sysread( $self, $temp_buffer, 8096 ); # 0 = EOF. unless ( defined($nread) && $nread ) { print STDERR "Got EOF from ${*$self}{exp_Pty_Handle}.\r\n" if ${*$self}{exp_Debug}; last; } $self->_print_handles($temp_buffer); } if ( ( $end_time <= time() ) && ${*$self}{exp_Debug} ) { print STDERR "Timed out waiting for an EOF from ${*$self}{exp_Pty_Handle}.\r\n"; } } my $close_status = $self->close(); if ( $close_status && ${*$self}{exp_Debug} ) { print STDERR "${*$self}{exp_Pty_Handle} closed.\r\n"; } # quit now if it isn't a process. return $close_status unless defined( ${*$self}{exp_Pid} ); # Now give it 15 seconds to die. $end_time = time() + 15; while ( $end_time > time() ) { my $returned_pid = waitpid( ${*$self}{exp_Pid}, &WNOHANG ); # Stop here if the process dies. if ( defined($returned_pid) && $returned_pid ) { delete $Expect::Spawned_PIDs{$returned_pid}; if ( ${*$self}{exp_Debug} ) { printf STDERR ( "Pid %d of %s exited, Status: 0x%02X\r\n", ${*$self}{exp_Pid}, ${*$self}{exp_Pty_Handle}, $? ); } ${*$self}{exp_Pid} = undef; ${*$self}{exp_Exit} = $?; return ${*$self}{exp_Exit}; } sleep 1; # Keep loop nice. } # Send it a term if it isn't dead. if ( ${*$self}{exp_Debug} ) { print STDERR "${*$self}{exp_Pty_Handle} not exiting, sending TERM.\r\n"; } kill TERM => ${*$self}{exp_Pid}; # Now to be anal retentive.. wait 15 more seconds for it to die. $end_time = time() + 15; while ( $end_time > time() ) { my $returned_pid = waitpid( ${*$self}{exp_Pid}, &WNOHANG ); if ( defined($returned_pid) && $returned_pid ) { delete $Expect::Spawned_PIDs{$returned_pid}; if ( ${*$self}{exp_Debug} ) { printf STDERR ( "Pid %d of %s terminated, Status: 0x%02X\r\n", ${*$self}{exp_Pid}, ${*$self}{exp_Pty_Handle}, $? ); } ${*$self}{exp_Pid} = undef; ${*$self}{exp_Exit} = $?; return $?; } sleep 1; } # Since this is a 'soft' close, sending it a -9 would be inappropriate. return; } # 'Make it go away' close. sub hard_close { my ($self) = @_; cluck "Closing ${*$self}{exp_Pty_Handle}.\r\n" if ${*$self}{exp_Debug}; # Don't wait for an EOF. my $close_status = $self->close(); if ( $close_status && ${*$self}{exp_Debug} ) { print STDERR "${*$self}{exp_Pty_Handle} closed.\r\n"; } # Return now if handle. return $close_status unless defined( ${*$self}{exp_Pid} ); # Now give it 5 seconds to die. Less patience here if it won't die. my $end_time = time() + 5; while ( $end_time > time() ) { my $returned_pid = waitpid( ${*$self}{exp_Pid}, &WNOHANG ); # Stop here if the process dies. if ( defined($returned_pid) && $returned_pid ) { delete $Expect::Spawned_PIDs{$returned_pid}; if ( ${*$self}{exp_Debug} ) { printf STDERR ( "Pid %d of %s terminated, Status: 0x%02X\r\n", ${*$self}{exp_Pid}, ${*$self}{exp_Pty_Handle}, $? ); } ${*$self}{exp_Pid} = undef; ${*$self}{exp_Exit} = $?; return ${*$self}{exp_Exit}; } sleep 1; # Keep loop nice. } # Send it a term if it isn't dead. if ( ${*$self}{exp_Debug} ) { print STDERR "${*$self}{exp_Pty_Handle} not exiting, sending TERM.\r\n"; } kill TERM => ${*$self}{exp_Pid}; # wait 15 more seconds for it to die. $end_time = time() + 15; while ( $end_time > time() ) { my $returned_pid = waitpid( ${*$self}{exp_Pid}, &WNOHANG ); if ( defined($returned_pid) && $returned_pid ) { delete $Expect::Spawned_PIDs{$returned_pid}; if ( ${*$self}{exp_Debug} ) { printf STDERR ( "Pid %d of %s terminated, Status: 0x%02X\r\n", ${*$self}{exp_Pid}, ${*$self}{exp_Pty_Handle}, $? ); } ${*$self}{exp_Pid} = undef; ${*$self}{exp_Exit} = $?; return ${*$self}{exp_Exit}; } sleep 1; } kill KILL => ${*$self}{exp_Pid}; # wait 5 more seconds for it to die. $end_time = time() + 5; while ( $end_time > time() ) { my $returned_pid = waitpid( ${*$self}{exp_Pid}, &WNOHANG ); if ( defined($returned_pid) && $returned_pid ) { delete $Expect::Spawned_PIDs{$returned_pid}; if ( ${*$self}{exp_Debug} ) { printf STDERR ( "Pid %d of %s killed, Status: 0x%02X\r\n", ${*$self}{exp_Pid}, ${*$self}{exp_Pty_Handle}, $? ); } ${*$self}{exp_Pid} = undef; ${*$self}{exp_Exit} = $?; return ${*$self}{exp_Exit}; } sleep 1; } warn "Pid ${*$self}{exp_Pid} of ${*$self}{exp_Pty_Handle} is HUNG.\r\n"; ${*$self}{exp_Pid} = undef; return; } # These should not be called externally. sub _init_vars { my ($self) = @_; # for every spawned process or filehandle. ${*$self}{exp_Log_Stdout} = $Expect::Log_Stdout if defined($Expect::Log_Stdout); ${*$self}{exp_Log_Group} = $Expect::Log_Group; ${*$self}{exp_Debug} = $Expect::Debug; ${*$self}{exp_Exp_Internal} = $Expect::Exp_Internal; ${*$self}{exp_Manual_Stty} = $Expect::Manual_Stty; ${*$self}{exp_Stored_Stty} = 'sane'; ${*$self}{exp_Do_Soft_Close} = $Expect::Do_Soft_Close; # sysread doesn't like my or local vars. ${*$self}{exp_Pty_Buffer} = ''; # Initialize accumulator. ${*$self}{exp_Max_Accum} = $Expect::Exp_Max_Accum; ${*$self}{exp_Accum} = ''; ${*$self}{exp_NoTransfer} = 0; # create empty expect_before & after lists ${*$self}{exp_expect_before_list} = []; ${*$self}{exp_expect_after_list} = []; return; } sub _make_readable { my ($s) = @_; $s = '' if not defined($s); study $s; # Speed things up? $s =~ s/\\/\\\\/g; # So we can tell easily(?) what is a backslash $s =~ s/\n/\\n/g; $s =~ s/\r/\\r/g; $s =~ s/\t/\\t/g; $s =~ s/\'/\\\'/g; # So we can tell whassa quote and whassa notta quote. $s =~ s/\"/\\\"/g; # Formfeed (does anyone use formfeed?) $s =~ s/\f/\\f/g; $s =~ s/\010/\\b/g; # escape control chars high/low, but allow ISO 8859-1 chars $s =~ s/([\000-\037\177-\237\377])/sprintf("\\%03lo",ord($1))/ge; return $s; } sub _trim_length { my ($self, $string, $length) = @_; # This is sort of a reverse truncation function # Mostly so we don't have to see the full output when we're using # Also used if Max_Accum gets set to limit the size of the accumulator # for matching functions. # exp_internal croak('No string passed') if not defined $string; # If we're not passed a length (_trim_length is being used for debugging # purposes) AND debug >= 3, don't trim. return ($string) if (defined($self) and ${*$self}{"exp_Debug"} >= 3 and ( !( defined($length) ) ) ); my $indicate_truncation = ($length ? '' : '...'); $length ||= 1021; return $string if $length >= length $string; # We wouldn't want the accumulator to begin with '...' if max_accum is passed # This is because this funct. gets called internally w/ max_accum # and is also used to print information back to the user. return $indicate_truncation . substr( $string, ( length($string) - $length ), $length ); } sub _print_handles { my ($self, $print_this) = @_; # Given crap from 'self' and the handles self wants to print to, print to # them. these are indicated by the handle's 'group' if ( ${*$self}{exp_Log_Group} ) { foreach my $handle ( @{ ${*$self}{exp_Listen_Group} } ) { $print_this = '' unless defined($print_this); # Appease perl -w print STDERR "Printed '" . $self->_trim_length( _make_readable($print_this) ) . "' to ${*$handle}{exp_Pty_Handle} from ${*$self}{exp_Pty_Handle}.\r\n" if ( ${*$handle}{"exp_Debug"} > 1 ); print $handle $print_this; } } # If ${*$self}{exp_Pty_Handle} is STDIN this would make it echo. print STDOUT $print_this if ${*$self}{"exp_Log_Stdout"}; $self->print_log_file($print_this); $| = 1; # This should not be necessary but autoflush() doesn't always work. return; } sub _get_mode { my ($handle) = @_; my ($fcntl_flags) = ''; # What mode are we opening with? use fcntl to find out. $fcntl_flags = fcntl( \*{$handle}, Fcntl::F_GETFL, $fcntl_flags ); die "fcntl returned undef during exp_init of $handle, $!\r\n" unless defined($fcntl_flags); if ( $fcntl_flags | (Fcntl::O_RDWR) ) { return 'rw'; } elsif ( $fcntl_flags | (Fcntl::O_WRONLY) ) { return 'w'; } else { # Under Solaris (among others?) O_RDONLY is implemented as 0. so |O_RDONLY would fail. return 'r'; } } sub _undef { return undef; # Seems a little retarded but &CORE::undef fails in interconnect. # This is used for the default escape sequence function. # w/out the leading & it won't compile. } # clean up child processes sub DESTROY { my ($self) = @_; my $status = $?; # save this as it gets mangled by the terminating spawned children if ( ${*$self}{exp_Do_Soft_Close} ) { $self->soft_close(); } $self->hard_close(); $? = $status; # restore it. otherwise deleting an Expect object may mangle $?, which is unintuitive return; } 1; __END__ =head1 NAME Expect - automate interactions with command line programs that expose a text terminal interface. =head1 SYNOPSIS use Expect; # create an Expect object by spawning another process my $exp = Expect->spawn($command, @params) or die "Cannot spawn $command: $!\n"; # or by using an already opened filehandle (e.g. from Net::Telnet) my $exp = Expect->exp_init(\*FILEHANDLE); # if you prefer the OO mindset: my $exp = Expect->new; $exp->raw_pty(1); $exp->spawn($command, @parameters) or die "Cannot spawn $command: $!\n"; # send some string there: $exp->send("string\n"); # or, for the filehandle mindset: print $exp "string\n"; # then do some pattern matching with either the simple interface $patidx = $exp->expect($timeout, @match_patterns); # or multi-match on several spawned commands with callbacks, # just like the Tcl version $exp->expect($timeout, [ qr/regex1/ => sub { my $exp = shift; $exp->send("response\n"); exp_continue; } ], [ "regexp2" , \&callback, @cbparms ], ); # if no longer needed, do a soft_close to nicely shut down the command $exp->soft_close(); # or be less patient with $exp->hard_close(); Expect.pm is built to either spawn a process or take an existing filehandle and interact with it such that normally interactive tasks can be done without operator assistance. This concept makes more sense if you are already familiar with the versatile Tcl version of Expect. The public functions that make up Expect.pm are: Expect->new() Expect::interconnect(@objects_to_be_read_from) Expect::test_handles($timeout, @objects_to_test) Expect::version($version_requested | undef); $object->spawn(@command) $object->clear_accum() $object->set_accum($value) $object->debug($debug_level) $object->exp_internal(0 | 1) $object->notransfer(0 | 1) $object->raw_pty(0 | 1) $object->stty(@stty_modes) # See the IO::Stty docs $object->slave() $object->before(); $object->match(); $object->after(); $object->matchlist(); $object->match_number(); $object->error(); $object->command(); $object->exitstatus(); $object->pty_handle(); $object->do_soft_close(); $object->restart_timeout_upon_receive(0 | 1); $object->interact($other_object, $escape_sequence) $object->log_group(0 | 1 | undef) $object->log_user(0 | 1 | undef) $object->log_file("filename" | $filehandle | \&coderef | undef) $object->manual_stty(0 | 1 | undef) $object->match_max($max_buffersize or undef) $object->pid(); $object->send_slow($delay, @strings_to_send) $object->set_group(@listen_group_objects | undef) $object->set_seq($sequence,\&function,\@parameters); There are several configurable package variables that affect the behavior of Expect. They are: $Expect::Debug; $Expect::Exp_Internal; $Expect::IgnoreEintr; $Expect::Log_Group; $Expect::Log_Stdout; $Expect::Manual_Stty; $Expect::Multiline_Matching; $Expect::Do_Soft_Close; =head1 DESCRIPTION See an explanation of L The Expect module is a successor of Comm.pl and a descendent of Chat.pl. It more closely resembles the Tcl Expect language than its predecessors. It does not contain any of the networking code found in Comm.pl. I suspect this would be obsolete anyway given the advent of IO::Socket and external tools such as netcat. Expect.pm is an attempt to have more of a switch() & case feeling to make decision processing more fluid. Three separate types of debugging have been implemented to make code production easier. It is possible to interconnect multiple file handles (and processes) much like Tcl's Expect. An attempt was made to enable all the features of Tcl's Expect without forcing Tcl on the victim programmer :-) . Please, before you consider using Expect, read the FAQs about L and L =head1 USAGE =over 4 =item new Creates a new Expect object, i.e. a pty. You can change parameters on it before actually spawning a command. This is important if you want to modify the terminal settings for the slave. See slave() below. The object returned is actually a reblessed IO::Pty filehandle, so see there for additional methods. =item Expect->exp_init(\*FILEHANDLE) I =item Expect->init(\*FILEHANDLE) Initializes $new_handle_object for use with other Expect functions. It must be passed a B<_reference_> to FILEHANDLE if you want it to work properly. IO::File objects are preferable. Returns a reference to the newly created object. You can use only real filehandles, certain tied filehandles (e.g. Net::SSH2) that lack a fileno() will not work. Net::Telnet objects can be used but have been reported to work only for certain hosts. YMMV. =item Expect->spawn($command, @parameters) I =item $object->spawn($command, @parameters) I =item Expect->new($command, @parameters) Forks and execs $command. Returns an Expect object upon success or C if the fork was unsuccessful or the command could not be found. spawn() passes its parameters unchanged to Perls exec(), so look there for detailed semantics. Note that if spawn cannot exec() the given command, the Expect object is still valid and the next expect() will see "Cannot exec", so you can use that for error handling. Also note that you cannot reuse an object with an already spawned command, even if that command has exited. Sorry, but you have to allocate a new object... =item $object->debug(0 | 1 | 2 | 3 | undef) Sets debug level for $object. 1 refers to general debugging information, 2 refers to verbose debugging and 0 refers to no debugging. If you call debug() with no parameters it will return the current debugging level. When the object is created the debugging level will match that $Expect::Debug, normally 0. The '3' setting is new with 1.05, and adds the additional functionality of having the _full_ accumulated buffer printed every time data is read from an Expect object. This was implemented by request. I recommend against using this unless you think you need it as it can create quite a quantity of output under some circumstances.. =item $object->exp_internal(1 | 0) Sets/unsets 'exp_internal' debugging. This is similar in nature to its Tcl counterpart. It is extremely valuable when debugging expect() sequences. When the object is created the exp_internal setting will match the value of $Expect::Exp_Internal, normally 0. Returns the current setting if called without parameters. It is highly recommended that you make use of the debugging features lest you have angry code. =item $object->raw_pty(1 | 0) Set pty to raw mode before spawning. This disables echoing, CR->LF translation and an ugly hack for broken Solaris TTYs (which send to slow things down) and thus gives a more pipe-like behaviour (which is important if you want to transfer binary content). Note that this must be set I spawning the program. =item $object->stty(qw(mode1 mode2...)) Sets the tty mode for $object's associated terminal to the given modes. Note that on many systems the master side of the pty is not a tty, so you have to modify the slave pty instead, see next item. This needs IO::Stty installed, which is no longer required. =item $object->slave() Returns a filehandle to the slave part of the pty. Very useful in modifying the terminal settings: $object->slave->stty(qw(raw -echo)); Typical values are 'sane', 'raw', and 'raw -echo'. Note that I recommend setting the terminal to 'raw' or 'raw -echo', as this avoids a lot of hassle and gives pipe-like (i.e. transparent) behaviour (without the buffering issue). =item $object->print(@strings) I =item $object->send(@strings) Sends the given strings to the spawned command. Note that the strings are not logged in the logfile (see print_log_file) but will probably be echoed back by the pty, depending on pty settings (default is echo) and thus end up there anyway. This must also be taken into account when expect()ing for an answer: the next string will be the command just sent. I suggest setting the pty to raw, which disables echo and makes the pty transparently act like a bidirectional pipe. =item $object->expect($timeout, @match_patterns) =over 4 =item Simple interface Given $timeout in seconds Expect will wait for $object's handle to produce one of the match_patterns, which are matched exactly by default. If you want a regexp match, use a regexp object (C) or prefix the pattern with '-re'. $object->expect(15, 'match me exactly', qr/match\s+me\s+exactly/); $object->expect(15, 'match me exactly','-re','match\s+me\s+exactly'); Due to o/s limitations $timeout should be a round number. If $timeout is 0 Expect will check one time to see if $object's handle contains any of the match_patterns. If $timeout is undef Expect will wait forever for a pattern to match. If you don't want to explicitly put the timeout on all calls to C, you can set it via the C method . If the first argument of C doesn't look like a number, that value will be used. $object->timeout(15); $object->expect('match me exactly','-re','match\s+me\s+exactly'); If called in a scalar context, expect() will return the position of the matched pattern within @matched_patterns, or undef if no pattern was matched. This is a position starting from 1, so if you want to know which of an array of @matched_patterns matched you should subtract one from the return value. If called in an array context expect() will return ($matched_pattern_position, $error, $successfully_matching_string, $before_match, and $after_match). C<$matched_pattern_position> will contain the value that would have been returned if expect() had been called in a scalar context. C<$error> is the error that occurred that caused expect() to return. $error will contain a number followed by a string equivalent expressing the nature of the error. Possible values are undef, indicating no error, '1:TIMEOUT' indicating that $timeout seconds had elapsed without a match, '2:EOF' indicating an eof was read from $object, '3: spawn id($fileno) died' indicating that the process exited before matching and '4:$!' indicating whatever error was set in $ERRNO during the last read on $object's handle or during select(). All handles indicated by set_group plus STDOUT will have all data to come out of $object printed to them during expect() if log_group and log_stdout are set. C<$successfully_matching_string> C<$before_match> C<$after_match> Changed from older versions is the regular expression handling. By default now all strings passed to expect() are treated as literals. To match a regular expression pass '-re' as a parameter in front of the pattern you want to match as a regexp. This change makes it possible to match literals and regular expressions in the same expect() call. Also new is multiline matching. ^ will now match the beginning of lines. Unfortunately, because perl doesn't use $/ in determining where lines break using $ to find the end of a line frequently doesn't work. This is because your terminal is returning "\r\n" at the end of every line. One way to check for a pattern at the end of a line would be to use \r?$ instead of $. Example: Spawning telnet to a host, you might look for the escape character. telnet would return to you "\r\nEscape character is '^]'.\r\n". To find this you might use $match='^Escape char.*\.\r?$'; $telnet->expect(10,'-re',$match); =item New more Tcl/Expect-like interface expect($timeout, '-i', [ $obj1, $obj2, ... ], [ $re_pattern, sub { ...; exp_continue; }, @subparms, ], [ 'eof', sub { ... } ], [ 'timeout', sub { ... }, \$subparm1 ], '-i', [ $objn, ...], '-ex', $exact_pattern, sub { ... }, $exact_pattern, sub { ...; exp_continue_timeout; }, '-re', $re_pattern, sub { ... }, '-i', \@object_list, @pattern_list, ...); It's now possible to expect on more than one connection at a time by specifying 'C<-i>' and a single Expect object or a ref to an array containing Expect objects, e.g. expect($timeout, '-i', $exp1, @patterns_1, '-i', [ $exp2, $exp3 ], @patterns_2_3, ) Furthermore, patterns can now be specified as array refs containing [$regexp, sub { ...}, @optional_subprams] . When the pattern matches, the subroutine is called with parameters ($matched_expect_obj, @optional_subparms). The subroutine can return the symbol `exp_continue' to continue the expect matching with timeout starting anew or return the symbol `exp_continue_timeout' for continuing expect without resetting the timeout count. $exp->expect($timeout, [ qr/username: /i, sub { my $self = shift; $self->send("$username\n"); exp_continue; }], [ qr/password: /i, sub { my $self = shift; $self->send("$password\n"); exp_continue; }], $shell_prompt); `expect' is now exported by default. =back =item $object->exp_before() I =item $object->before() before() returns the 'before' part of the last expect() call. If the last expect() call didn't match anything, exp_before() will return the entire output of the object accumulated before the expect() call finished. Note that this is something different than Tcl Expects before()!! =item $object->exp_after() I =item $object->after() returns the 'after' part of the last expect() call. If the last expect() call didn't match anything, exp_after() will return undef(). =item $object->exp_match() I =item $object->match() returns the string matched by the last expect() call, undef if no string was matched. =item $object->exp_match_number() I =item $object->match_number() exp_match_number() returns the number of the pattern matched by the last expect() call. Keep in mind that the first pattern in a list of patterns is 1, not 0. Returns undef if no pattern was matched. =item $object->exp_matchlist() I =item $object->matchlist() exp_matchlist() returns a list of matched substrings from the brackets () inside the regexp that last matched. ($object->matchlist)[0] thus corresponds to $1, ($object->matchlist)[1] to $2, etc. =item $object->exp_error() I =item $object->error() exp_error() returns the error generated by the last expect() call if no pattern was matched. It is typically useful to examine the value returned by before() to find out what the output of the object was in determining why it didn't match any of the patterns. =item $object->clear_accum() Clear the contents of the accumulator for $object. This gets rid of any residual contents of a handle after expect() or send_slow() such that the next expect() call will only see new data from $object. The contents of the accumulator are returned. =item $object->set_accum($value) Sets the content of the accumulator for $object to $value. The previous content of the accumulator is returned. =item $object->exp_command() I =item $object->command() exp_command() returns the string that was used to spawn the command. Helpful for debugging and for reused patternmatch subroutines. =item $object->exp_exitstatus() I =item $object->exitstatus() Returns the exit status of $object (if it already exited). =item $object->exp_pty_handle() I =item $object->pty_handle() Returns a string representation of the attached pty, for example: `spawn id(5)' (pty has fileno 5), `handle id(7)' (pty was initialized from fileno 7) or `STDIN'. Useful for debugging. =item $object->restart_timeout_upon_receive(0 | 1) If this is set to 1, the expect timeout is retriggered whenever something is received from the spawned command. This allows to perform some aliveness testing and still expect for patterns. $exp->restart_timeout_upon_receive(1); $exp->expect($timeout, [ timeout => \&report_timeout ], [ qr/pattern/ => \&handle_pattern], ); Now the timeout isn't triggered if the command produces any kind of output, i.e. is still alive, but you can act upon patterns in the output. =item $object->notransfer(1 | 0) Do not truncate the content of the accumulator after a match. Normally, the accumulator is set to the remains that come after the matched string. Note that this setting is per object and not per pattern, so if you want to have normal acting patterns that truncate the accumulator, you have to add a $exp->set_accum($exp->after); to their callback, e.g. $exp->notransfer(1); $exp->expect($timeout, # accumulator not truncated, pattern1 will match again [ "pattern1" => sub { my $self = shift; ... } ], # accumulator truncated, pattern2 will not match again [ "pattern2" => sub { my $self = shift; ... $self->set_accum($self->after()); } ], ); This is only a temporary fix until I can rewrite the pattern matching part so it can take that additional -notransfer argument. =item Expect::interconnect(@objects); Read from @objects and print to their @listen_groups until an escape sequence is matched from one of @objects and the associated function returns 0 or undef. The special escape sequence 'EOF' is matched when an object's handle returns an end of file. Note that it is not necessary to include objects that only accept data in @objects since the escape sequence is _read_ from an object. Further note that the listen_group for a write-only object is always empty. Why would you want to have objects listening to STDOUT (for example)? By default every member of @objects _as well as every member of its listen group_ will be set to 'raw -echo' for the duration of interconnection. Setting $object->manual_stty() will stop this behavior per object. The original tty settings will be restored as interconnect exits. For a generic way to interconnect processes, take a look at L. =item Expect::test_handles(@objects) Given a set of objects determines which objects' handles have data ready to be read. B who's members are positions in @objects that have ready handles. Returns undef if there are no such handles ready. =item Expect::version($version_requested or undef); Returns current version of Expect. As of .99 earlier versions are not supported. Too many things were changed to make versioning possible. =item $object->interact( C<\*FILEHANDLE, $escape_sequence>) interact() is essentially a macro for calling interconnect() for connecting 2 processes together. \*FILEHANDLE defaults to \*STDIN and $escape_sequence defaults to undef. Interaction ceases when $escape_sequence is read from B, not $object. $object's listen group will consist solely of \*FILEHANDLE for the duration of the interaction. \*FILEHANDLE will not be echoed on STDOUT. =item $object->log_group(0 | 1 | undef) Set/unset logging of $object to its 'listen group'. If set all objects in the listen group will have output from $object printed to them during $object->expect(), $object->send_slow(), and C. Default value is on. During creation of $object the setting will match the value of $Expect::Log_Group, normally 1. =item $object->log_user(0 | 1 | undef) I =item $object->log_stdout(0 | 1 | undef) Set/unset logging of object's handle to STDOUT. This corresponds to Tcl's log_user variable. Returns current setting if called without parameters. Default setting is off for initialized handles. When a process object is created (not a filehandle initialized with exp_init) the log_stdout setting will match the value of $Expect::Log_Stdout variable, normally 1. If/when you initialize STDIN it is usually associated with a tty which will by default echo to STDOUT anyway, so be careful or you will have multiple echoes. =item $object->log_file("filename" | $filehandle | \&coderef | undef) Log session to a file. All characters send to or received from the spawned process are written to the file. Normally appends to the logfile, but you can pass an additional mode of "w" to truncate the file upon open(): $object->log_file("filename", "w"); Returns the logfilehandle. If called with an undef value, stops logging and closes logfile: $object->log_file(undef); If called without argument, returns the logfilehandle: $fh = $object->log_file(); Can be set to a code ref, which will be called instead of printing to the logfile: $object->log_file(\&myloggerfunc); =item $object->print_log_file(@strings) Prints to logfile (if opened) or calls the logfile hook function. This allows the user to add arbitrary text to the logfile. Note that this could also be done as $object->log_file->print() but would only work for log files, not code hooks. =item $object->set_seq($sequence, \&function, \@function_parameters) During Expect->interconnect() if $sequence is read from $object &function will be executed with parameters @function_parameters. It is B<_highly recommended_> that the escape sequence be a single character since the likelihood is great that the sequence will be broken into to separate reads from the $object's handle, making it impossible to strip $sequence from getting printed to $object's listen group. \&function should be something like 'main::control_w_function' and @function_parameters should be an array defined by the caller, passed by reference to set_seq(). Your function should return a non-zero value if execution of interconnect() is to resume after the function returns, zero or undefined if interconnect() should return after your function returns. The special sequence 'EOF' matches the end of file being reached by $object. See interconnect() for details. =item $object->set_group(@listener_objects) @listener_objects is the list of objects that should have their handles printed to by $object when Expect::interconnect, $object->expect() or $object->send_slow() are called. Calling w/out parameters will return the current list of the listener objects. =item $object->manual_stty(0 | 1 | undef) Sets/unsets whether or not Expect should make reasonable guesses as to when and how to set tty parameters for $object. Will match $Expect::Manual_Stty value (normally 0) when $object is created. If called without parameters manual_stty() will return the current manual_stty setting. =item $object->match_max($maximum_buffer_length | undef) I =item $object->max_accum($maximum_buffer_length | undef) Set the maximum accumulator size for object. This is useful if you think that the accumulator will grow out of hand during expect() calls. Since the buffer will be matched by every match_pattern it may get slow if the buffer gets too large. Returns current value if called without parameters. Not defined by default. =item $object->notransfer(0 | 1) If set, matched strings will not be deleted from the accumulator. Returns current value if called without parameters. False by default. =item $object->exp_pid() I =item $object->pid() Return pid of $object, if one exists. Initialized filehandles will not have pids (of course). =item $object->send_slow($delay, @strings); print each character from each string of @strings one at a time with $delay seconds before each character. This is handy for devices such as modems that can be annoying if you send them data too fast. After each character $object will be checked to determine whether or not it has any new data ready and if so update the accumulator for future expect() calls and print the output to STDOUT and @listen_group if log_stdout and log_group are appropriately set. =back =head2 Configurable Package Variables: =over 4 =item $Expect::Debug Defaults to 0. Newly created objects have a $object->debug() value of $Expect::Debug. See $object->debug(); =item $Expect::Do_Soft_Close Defaults to 0. When destroying objects, soft_close may take up to half a minute to shut everything down. From now on, only hard_close will be called, which is less polite but still gives the process a chance to terminate properly. Set this to '1' for old behaviour. =item $Expect::Exp_Internal Defaults to 0. Newly created objects have a $object->exp_internal() value of $Expect::Exp_Internal. See $object->exp_internal(). =item $Expect::IgnoreEintr Defaults to 0. If set to 1, when waiting for new data, Expect will ignore EINTR errors and restart the select() call instead. =item $Expect::Log_Group Defaults to 1. Newly created objects have a $object->log_group() value of $Expect::Log_Group. See $object->log_group(). =item $Expect::Log_Stdout Defaults to 1 for spawned commands, 0 for file handles attached with exp_init(). Newly created objects have a $object->log_stdout() value of $Expect::Log_Stdout. See $object->log_stdout(). =item $Expect::Manual_Stty Defaults to 0. Newly created objects have a $object->manual_stty() value of $Expect::Manual_Stty. See $object->manual_stty(). =item $Expect::Multiline_Matching Defaults to 1. Affects whether or not expect() uses the /m flag for doing regular expression matching. If set to 1 /m is used. This makes a difference when you are trying to match ^ and $. If you have this on you can match lines in the middle of a page of output using ^ and $ instead of it matching the beginning and end of the entire expression. I think this is handy. The $Expect::Multiline_Matching turns on and off Expect's multi-line matching mode. But this only has an effect if you pass in a string, and then use '-re' mode. If you pass in a regular expression value (via qr//), then the qr//'s own flags are preserved irrespective of what it gets interpolated into. There was a bug in Perl 5.8.x where interpolating a regex without /m into a match with /m would incorrectly apply the /m to the inner regex too, but this was fixed in Perl 5.10. The correct behavior, as seen in Perl 5.10, is that if you pass in a regex (via qr//), then $Expect::Multiline_Matching has no effect. So if you pass in a regex, then you must use the qr's flags to control whether it is multiline (which by default it is not, opposite of the default behavior of Expect). =back =head1 CONTRIBUTIONS Lee Eakin has ported the kibitz script from Tcl/Expect to Perl/Expect. Jeff Carr provided a simple example of how handle terminal window resize events (transmitted via the WINCH signal) in a ssh session. You can find both scripts in the examples/ subdir. Thanks to both! Historical notes: There are still a few lines of code dating back to the inspirational Comm.pl and Chat.pl modules without which this would not have been possible. Kudos to Eric Arnold and Randal 'Nuke your NT box with one line of perl code' Schwartz for making these available to the perl public. As of .98 I think all the old code is toast. No way could this have been done without it though. Special thanks to Graham Barr for helping make sense of the IO::Handle stuff as well as providing the highly recommended IO::Tty module. =head1 REFERENCES Mark Rogaski wrote: "I figured that you'd like to know that Expect.pm has been very useful to AT&T Labs over the past couple of years (since I first talked to Austin about design decisions). We use Expect.pm for managing the switches in our network via the telnet interface, and such automation has significantly increased our reliability. So, you can honestly say that one of the largest digital networks in existence (AT&T Frame Relay) uses Expect.pm quite extensively." =head1 FAQ - Frequently Asked Questions This is a growing collection of things that might help. Please send you questions that are not answered here to RGiersig@cpan.org =head2 What systems does Expect run on? Expect itself doesn't have real system dependencies, but the underlying IO::Tty needs pseudoterminals. IO::Stty uses POSIX.pm and Fcntl.pm. I have used it on Solaris, Linux and AIX, others report *BSD and OSF as working. Generally, any modern POSIX Unix should do, but there are exceptions to every rule. Feedback is appreciated. See L for a list of verified systems. =head2 Can I use this module with ActivePerl on Windows? Up to now, the answer was 'No', but this has changed. You still cannot use ActivePerl, but if you use the Cygwin environment (http://sources.redhat.com), which brings its own perl, and have the latest IO::Tty (v0.05 or later) installed, it should work (feedback appreciated). =head2 The examples in the tutorial don't work! The tutorial is hopelessly out of date and needs a serious overhaul. I apologize for this, I have concentrated my efforts mainly on the functionality. Volunteers welcomed. =head2 How can I find out what Expect is doing? If you set $Expect::Exp_Internal = 1; Expect will tell you very verbosely what it is receiving and sending, what matching it is trying and what it found. You can do this on a per-command base with $exp->exp_internal(1); You can also set $Expect::Debug = 1; # or 2, 3 for more verbose output or $exp->debug(1); which gives you even more output. =head2 I am seeing the output of the command I spawned. Can I turn that off? Yes, just set $Expect::Log_Stdout = 0; to globally disable it or $exp->log_stdout(0); for just that command. 'log_user' is provided as an alias so Tcl/Expect user get a DWIM experience... :-) =head2 No, I mean that when I send some text to the spawned process, it gets echoed back and I have to deal with it in the next expect. This is caused by the pty, which has probably 'echo' enabled. A solution would be to set the pty to raw mode, which in general is cleaner for communication between two programs (no more unexpected character translations). Unfortunately this would break a lot of old code that sends "\r" to the program instead of "\n" (translating this is also handled by the pty), so I won't add this to Expect just like that. But feel free to experiment with C<$exp-Eraw_pty(1)>. =head2 How do I send control characters to a process? A: You can send any characters to a process with the print command. To represent a control character in Perl, use \c followed by the letter. For example, control-G can be represented with "\cG" . Note that this will not work if you single-quote your string. So, to send control-C to a process in $exp, do: print $exp "\cC"; Or, if you prefer: $exp->send("\cC"); The ability to include control characters in a string like this is provided by Perl, not by Expect.pm . Trying to learn Expect.pm without a thorough grounding in Perl can be very daunting. We suggest you look into some of the excellent Perl learning material, such as the books _Programming Perl_ and _Learning Perl_ by O'Reilly, as well as the extensive online Perl documentation available through the perldoc command. =head2 My script fails from time to time without any obvious reason. It seems that I am sometimes loosing output from the spawned program. You could be exiting too fast without giving the spawned program enough time to finish. Try adding $exp->soft_close() to terminate the program gracefully or do an expect() for 'eof'. Alternatively, try adding a 'sleep 1' after you spawn() the program. It could be that pty creation on your system is just slow (but this is rather improbable if you are using the latest IO-Tty). =head2 I want to automate password entry for su/ssh/scp/rsh/... You shouldn't use Expect for this. Putting passwords, especially root passwords, into scripts in clear text can mean severe security problems. I strongly recommend using other means. For 'su', consider switching to 'sudo', which gives you root access on a per-command and per-user basis without the need to enter passwords. 'ssh'/'scp' can be set up with RSA authentication without passwords. 'rsh' can use the .rhost mechanism, but I'd strongly suggest to switch to 'ssh'; to mention 'rsh' and 'security' in the same sentence makes an oxymoron. It will work for 'telnet', though, and there are valid uses for it, but you still might want to consider using 'ssh', as keeping cleartext passwords around is very insecure. =head2 I want to use Expect to automate [anything with a buzzword]... Are you sure there is no other, easier way? As a rule of thumb, Expect is useful for automating things that expect to talk to a human, where no formal standard applies. For other tasks that do follow a well-defined protocol, there are often better-suited modules that already can handle those protocols. Don't try to do HTTP requests by spawning telnet to port 80, use LWP instead. To automate FTP, take a look at L or C (http://www.ncftp.org). You don't use a screwdriver to hammer in your nails either, or do you? =head2 Is it possible to use threads with Expect? Basically yes, with one restriction: you must spawn() your programs in the main thread and then pass the Expect objects to the handling threads. The reason is that spawn() uses fork(), and L: "Thinking of mixing fork() and threads? Please lie down and wait until the feeling passes." =head2 I want to log the whole session to a file. Use $exp->log_file("filename"); or $exp->log_file($filehandle); or even $exp->log_file(\&log_procedure); for maximum flexibility. Note that the logfile is appended to by default, but you can specify an optional mode "w" to truncate the logfile: $exp->log_file("filename", "w"); To stop logging, just call it with a false argument: $exp->log_file(undef); =head2 How can I turn off multi-line matching for my regexps? To globally unset multi-line matching for all regexps: $Expect::Multiline_Matching = 0; You can do that on a per-regexp basis by stating C<(?-m)> inside the regexp (you need perl5.00503 or later for that). =head2 How can I expect on multiple spawned commands? You can use the B<-i> parameter to specify a single object or a list of Expect objects. All following patterns will be evaluated against that list. You can specify B<-i> multiple times to create groups of objects and patterns to match against within the same expect statement. This works just like in Tcl/Expect. See the source example below. =head2 I seem to have problems with ptys! Well, pty handling is really a black magic, as it is extremely system dependent. I have extensively revised IO-Tty, so these problems should be gone. If your system is listed in the "verified" list of IO::Tty, you probably have some non-standard setup, e.g. you compiled your Linux-kernel yourself and disabled ptys. Please ask your friendly sysadmin for help. If your system is not listed, unpack the latest version of IO::Tty, do a 'perl Makefile.PL; make; make test; uname C<-a>' and send me the results and I'll see what I can deduce from that. =head2 I just want to read the output of a process without expect()ing anything. How can I do this? [ Are you sure you need Expect for this? How about qx() or open("prog|")? ] By using expect without any patterns to match. $process->expect(undef); # Forever until EOF $process->expect($timeout); # For a few seconds $process->expect(0); # Is there anything ready on the handle now? =head2 Ok, so now how do I get what was read on the handle? $read = $process->before(); =head2 Where's IO::Pty? Find it on CPAN as IO-Tty, which provides both. =head2 How come when I automate the passwd program to change passwords for me passwd dies before changing the password sometimes/every time? What's happening is you are closing the handle before passwd exits. When you close the handle to a process, it is sent a signal (SIGPIPE?) telling it that STDOUT has gone away. The default behavior for processes is to die in this circumstance. Two ways you can make this not happen are: $process->soft_close(); This will wait 15 seconds for a process to come up with an EOF by itself before killing it. $process->expect(undef); This will wait forever for the process to match an empty set of patterns. It will return when the process hits an EOF. As a rule, you should always expect() the result of your transaction before you continue with processing. =head2 How come when I try to make a logfile with log_file() or set_group() it doesn't print anything after the last time I run expect()? Output is only printed to the logfile/group when Expect reads from the process, during expect(), send_slow() and interconnect(). One way you can force this is to make use of $process->expect(undef); and $process->expect(0); which will make expect() run with an empty pattern set forever or just for an instant to capture the output of $process. The output is available in the accumulator, so you can grab it using $process->before(). =head2 I seem to have problems with terminal settings, double echoing, etc. Tty settings are a major pain to keep track of. If you find unexpected behavior such as double-echoing or a frozen session, doublecheck the documentation for default settings. When in doubt, handle them yourself using $exp->stty() and manual_stty() functions. As of .98 you shouldn't have to worry about stty settings getting fouled unless you use interconnect or intentionally change them (like doing -echo to get a password). If you foul up your terminal's tty settings, kill any hung processes and enter 'stty sane' at a shell prompt. This should make your terminal manageable again. Note that IO::Tty returns ptys with your systems default setting regarding echoing, CRLF translation etc. and Expect does not change them. I have considered setting the ptys to 'raw' without any translation whatsoever, but this would break a lot of existing things, as '\r' translation would not work anymore. On the other hand, a raw pty works much like a pipe and is more WYGIWYE (what you get is what you expect), so I suggest you set it to 'raw' by yourself: $exp = Expect->new; $exp->raw_pty(1); $exp->spawn(...); To disable echo: $exp->slave->stty(qw(-echo)); =head2 I'm spawning a telnet/ssh session and then let the user interact with it. But screen-oriented applications on the other side don't work properly. You have to set the terminal screen size for that. Luckily, IO::Pty already has a method for that, so modify your code to look like this: my $exp = Expect->new; $exp->slave->clone_winsize_from(\*STDIN); $exp->spawn("telnet somehost); Also, some applications need the TERM shell variable set so they know how to move the cursor across the screen. When logging in, the remote shell sends a query (Ctrl-Z I think) and expects the terminal to answer with a string, e.g. 'xterm'. If you really want to go that way (be aware, madness lies at its end), you can handle that and send back the value in $ENV{TERM}. This is only a hand-waving explanation, please figure out the details by yourself. =head2 I set the terminal size as explained above, but if I resize the window, the application does not notice this. You have to catch the signal WINCH ("window size changed"), change the terminal size and propagate the signal to the spawned application: my $exp = Expect->new; $exp->slave->clone_winsize_from(\*STDIN); $exp->spawn("ssh somehost); $SIG{WINCH} = \&winch; sub winch { $exp->slave->clone_winsize_from(\*STDIN); kill WINCH => $exp->pid if $exp->pid; $SIG{WINCH} = \&winch; } $exp->interact(); There is an example file ssh.pl in the examples/ subdir that shows how this works with ssh. Please note that I do strongly object against using Expect to automate ssh login, as there are better way to do that (see L). =head2 I noticed that the test uses a string that resembles, but not exactly matches, a well-known sentence that contains every character. What does that mean? That means you are anal-retentive. :-) [Gotcha there!] =head2 I get a "Could not assign a pty" error when running as a non-root user on an IRIX box? The OS may not be configured to grant additional pty's (pseudo terminals) to non-root users. /usr/sbin/mkpts should be 4755, not 700 for this to work. I don't know about security implications if you do this. =head2 How come I don't notice when the spawned process closes its stdin/out/err?? You are probably on one of the systems where the master doesn't get an EOF when the slave closes stdin/out/err. One possible solution is when you spawn a process, follow it with a unique string that would indicate the process is finished. $process = Expect->spawn('telnet somehost; echo ____END____'); And then $process->expect($timeout,'____END____','other','patterns'); =head1 Source Examples =head2 How to automate login my $telnet = Net::Telnet->new("remotehost") # see Net::Telnet or die "Cannot telnet to remotehost: $!\n";; my $exp = Expect->exp_init($telnet); # deprecated use of spawned telnet command # my $exp = Expect->spawn("telnet localhost") # or die "Cannot spawn telnet: $!\n";; my $spawn_ok; $exp->expect($timeout, [ qr'login: $', sub { $spawn_ok = 1; my $fh = shift; $fh->send("$username\n"); exp_continue; } ], [ 'Password: $', sub { my $fh = shift; print $fh "$password\n"; exp_continue; } ], [ eof => sub { if ($spawn_ok) { die "ERROR: premature EOF in login.\n"; } else { die "ERROR: could not spawn telnet.\n"; } } ], [ timeout => sub { die "No login.\n"; } ], '-re', qr'[#>:] $', #' wait for shell prompt, then exit expect ); =head2 How to expect on multiple spawned commands foreach my $cmd (@list_of_commands) { push @commands, Expect->spawn($cmd); } expect($timeout, '-i', \@commands, [ qr"pattern", # find this pattern in output of all commands sub { my $obj = shift; # object that matched print $obj "something\n"; exp_continue; # we don't want to terminate the expect call } ], '-i', $some_other_command, [ "some other pattern", sub { my ($obj, $parmref) = @_; # ... # now we exit the expect command }, \$parm ], ); =head2 How to propagate terminal sizes my $exp = Expect->new; $exp->slave->clone_winsize_from(\*STDIN); $exp->spawn("ssh somehost); $SIG{WINCH} = \&winch; sub winch { $exp->slave->clone_winsize_from(\*STDIN); kill WINCH => $exp->pid if $exp->pid; $SIG{WINCH} = \&winch; } $exp->interact(); =head1 HOMEPAGE L though the source code is now in GitHub: L =head1 MAILING LISTS There are two mailing lists available, expectperl-announce and expectperl-discuss, at http://lists.sourceforge.net/lists/listinfo/expectperl-announce and http://lists.sourceforge.net/lists/listinfo/expectperl-discuss =head1 BUG TRACKING You can use the CPAN Request Tracker http://rt.cpan.org/ and submit new bugs under http://rt.cpan.org/Ticket/Create.html?Queue=Expect =head1 AUTHORS (c) 1997 Austin Schutz EFE (retired) expect() interface & functionality enhancements (c) 1999-2006 Roland Giersig. This module is now maintained by Dave Jacoby EFE =head1 LICENSE This module can be used under the same terms as Perl. =head1 DISCLAIMER THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. In other words: Use at your own risk. Provided as is. Your mileage may vary. Read the source, Luke! And finally, just to be sure: Any Use of This Product, in Any Manner Whatsoever, Will Increase the Amount of Disorder in the Universe. Although No Liability Is Implied Herein, the Consumer Is Warned That This Process Will Ultimately Lead to the Heat Death of the Universe. =cut PK! OOperl5/local/lib.pmnu6$package local::lib; use 5.006; BEGIN { if ($ENV{RELEASE_TESTING}) { require strict; strict->import; require warnings; warnings->import; } } use Config (); our $VERSION = '2.000029'; $VERSION =~ tr/_//d; BEGIN { *_WIN32 = ($^O eq 'MSWin32' || $^O eq 'NetWare' || $^O eq 'symbian') ? sub(){1} : sub(){0}; # punt on these systems *_USE_FSPEC = ($^O eq 'MacOS' || $^O eq 'VMS' || $INC{'File/Spec.pm'}) ? sub(){1} : sub(){0}; } my $_archname = $Config::Config{archname}; my $_version = $Config::Config{version}; my @_inc_version_list = reverse split / /, $Config::Config{inc_version_list}; my $_path_sep = $Config::Config{path_sep}; our $_DIR_JOIN = _WIN32 ? '\\' : '/'; our $_DIR_SPLIT = (_WIN32 || $^O eq 'cygwin') ? qr{[\\/]} : qr{/}; our $_ROOT = _WIN32 ? do { my $UNC = qr{[\\/]{2}[^\\/]+[\\/][^\\/]+}; qr{^(?:$UNC|[A-Za-z]:|)$_DIR_SPLIT}; } : qr{^/}; our $_PERL; sub _perl { if (!$_PERL) { # untaint and validate ($_PERL, my $exe) = $^X =~ /((?:.*$_DIR_SPLIT)?(.+))/; $_PERL = 'perl' if $exe !~ /perl/; if (_is_abs($_PERL)) { } elsif (-x $Config::Config{perlpath}) { $_PERL = $Config::Config{perlpath}; } elsif ($_PERL =~ $_DIR_SPLIT && -x $_PERL) { $_PERL = _rel2abs($_PERL); } else { ($_PERL) = map { /(.*)/ } grep { -x $_ } map { ($_, _WIN32 ? ("$_.exe") : ()) } map { join($_DIR_JOIN, $_, $_PERL) } split /\Q$_path_sep\E/, $ENV{PATH}; } } $_PERL; } sub _cwd { if (my $cwd = defined &Cwd::sys_cwd ? \&Cwd::sys_cwd : defined &Cwd::cwd ? \&Cwd::cwd : undef ) { no warnings 'redefine'; *_cwd = $cwd; goto &$cwd; } my $drive = shift; return Win32::GetCwd() if _WIN32 && defined &Win32::GetCwd && !$drive; local @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; delete @ENV{qw(PATH IFS CDPATH ENV BASH_ENV)}; my $cmd = $drive ? "eval { Cwd::getdcwd(q($drive)) }" : 'getcwd'; my $perl = _perl; my $cwd = `"$perl" -MCwd -le "print $cmd"`; chomp $cwd; if (!length $cwd && $drive) { $cwd = $drive; } $cwd =~ s/$_DIR_SPLIT?$/$_DIR_JOIN/; $cwd; } sub _catdir { if (_USE_FSPEC) { require File::Spec; File::Spec->catdir(@_); } else { my $dir = join($_DIR_JOIN, @_); $dir =~ s{($_DIR_SPLIT)(?:\.?$_DIR_SPLIT)+}{$1}g; $dir; } } sub _is_abs { if (_USE_FSPEC) { require File::Spec; File::Spec->file_name_is_absolute($_[0]); } else { $_[0] =~ $_ROOT; } } sub _rel2abs { my ($dir, $base) = @_; return $dir if _is_abs($dir); $base = _WIN32 && $dir =~ s/^([A-Za-z]:)// ? _cwd("$1") : $base ? _rel2abs($base) : _cwd; return _catdir($base, $dir); } our $_DEVNULL; sub _devnull { return $_DEVNULL ||= _USE_FSPEC ? (require File::Spec, File::Spec->devnull) : _WIN32 ? 'nul' : $^O eq 'os2' ? '/dev/nul' : '/dev/null'; } sub import { my ($class, @args) = @_; if ($0 eq '-') { push @args, @ARGV; require Cwd; } my @steps; my %opts; my %attr; my $shelltype; while (@args) { my $arg = shift @args; # check for lethal dash first to stop processing before causing problems # the fancy dash is U+2212 or \xE2\x88\x92 if ($arg =~ /\xE2\x88\x92/) { die <<'DEATH'; WHOA THERE! It looks like you've got some fancy dashes in your commandline! These are *not* the traditional -- dashes that software recognizes. You probably got these by copy-pasting from the perldoc for this module as rendered by a UTF8-capable formatter. This most typically happens on an OS X terminal, but can happen elsewhere too. Please try again after replacing the dashes with normal minus signs. DEATH } elsif ($arg eq '--self-contained') { die <<'DEATH'; FATAL: The local::lib --self-contained flag has never worked reliably and the original author, Mark Stosberg, was unable or unwilling to maintain it. As such, this flag has been removed from the local::lib codebase in order to prevent misunderstandings and potentially broken builds. The local::lib authors recommend that you look at the lib::core::only module shipped with this distribution in order to create a more robust environment that is equivalent to what --self-contained provided (although quite possibly not what you originally thought it provided due to the poor quality of the documentation, for which we apologise). DEATH } elsif( $arg =~ /^--deactivate(?:=(.*))?$/ ) { my $path = defined $1 ? $1 : shift @args; push @steps, ['deactivate', $path]; } elsif ( $arg eq '--deactivate-all' ) { push @steps, ['deactivate_all']; } elsif ( $arg =~ /^--shelltype(?:=(.*))?$/ ) { $shelltype = defined $1 ? $1 : shift @args; } elsif ( $arg eq '--no-create' ) { $opts{no_create} = 1; } elsif ( $arg eq '--quiet' ) { $attr{quiet} = 1; } elsif ( $arg eq '--always' ) { $attr{always} = 1; } elsif ( $arg =~ /^--/ ) { die "Unknown import argument: $arg"; } else { push @steps, ['activate', $arg, \%opts]; } } if (!@steps) { push @steps, ['activate', undef, \%opts]; } my $self = $class->new(%attr); for (@steps) { my ($method, @args) = @$_; $self = $self->$method(@args); } if ($0 eq '-') { print $self->environment_vars_string($shelltype); exit 0; } else { $self->setup_local_lib; } } sub new { my $class = shift; bless {@_}, $class; } sub clone { my $self = shift; bless {%$self, @_}, ref $self; } sub inc { $_[0]->{inc} ||= \@INC } sub libs { $_[0]->{libs} ||= [ \'PERL5LIB' ] } sub bins { $_[0]->{bins} ||= [ \'PATH' ] } sub roots { $_[0]->{roots} ||= [ \'PERL_LOCAL_LIB_ROOT' ] } sub extra { $_[0]->{extra} ||= {} } sub quiet { $_[0]->{quiet} } sub _as_list { my $list = shift; grep length, map { !(ref $_ && ref $_ eq 'SCALAR') ? $_ : ( defined $ENV{$$_} ? split(/\Q$_path_sep/, $ENV{$$_}) : () ) } ref $list ? @$list : $list; } sub _remove_from { my ($list, @remove) = @_; return @$list if !@remove; my %remove = map { $_ => 1 } @remove; grep !$remove{$_}, _as_list($list); } my @_lib_subdirs = ( [$_version, $_archname], [$_version], [$_archname], (map [$_], @_inc_version_list), [], ); sub install_base_bin_path { my ($class, $path) = @_; return _catdir($path, 'bin'); } sub install_base_perl_path { my ($class, $path) = @_; return _catdir($path, 'lib', 'perl5'); } sub install_base_arch_path { my ($class, $path) = @_; _catdir($class->install_base_perl_path($path), $_archname); } sub lib_paths_for { my ($class, $path) = @_; my $base = $class->install_base_perl_path($path); return map { _catdir($base, @$_) } @_lib_subdirs; } sub _mm_escape_path { my $path = shift; $path =~ s/\\/\\\\/g; if ($path =~ s/ /\\ /g) { $path = qq{"$path"}; } return $path; } sub _mb_escape_path { my $path = shift; $path =~ s/\\/\\\\/g; return qq{"$path"}; } sub installer_options_for { my ($class, $path) = @_; return ( PERL_MM_OPT => defined $path ? "INSTALL_BASE="._mm_escape_path($path) : undef, PERL_MB_OPT => defined $path ? "--install_base "._mb_escape_path($path) : undef, ); } sub active_paths { my ($self) = @_; $self = ref $self ? $self : $self->new; return grep { # screen out entries that aren't actually reflected in @INC my $active_ll = $self->install_base_perl_path($_); grep { $_ eq $active_ll } @{$self->inc}; } _as_list($self->roots); } sub deactivate { my ($self, $path) = @_; $self = $self->new unless ref $self; $path = $self->resolve_path($path); $path = $self->normalize_path($path); my @active_lls = $self->active_paths; if (!grep { $_ eq $path } @active_lls) { warn "Tried to deactivate inactive local::lib '$path'\n"; return $self; } my %args = ( bins => [ _remove_from($self->bins, $self->install_base_bin_path($path)) ], libs => [ _remove_from($self->libs, $self->install_base_perl_path($path)) ], inc => [ _remove_from($self->inc, $self->lib_paths_for($path)) ], roots => [ _remove_from($self->roots, $path) ], ); $args{extra} = { $self->installer_options_for($args{roots}[0]) }; $self->clone(%args); } sub deactivate_all { my ($self) = @_; $self = $self->new unless ref $self; my @active_lls = $self->active_paths; my %args; if (@active_lls) { %args = ( bins => [ _remove_from($self->bins, map $self->install_base_bin_path($_), @active_lls) ], libs => [ _remove_from($self->libs, map $self->install_base_perl_path($_), @active_lls) ], inc => [ _remove_from($self->inc, map $self->lib_paths_for($_), @active_lls) ], roots => [ _remove_from($self->roots, @active_lls) ], ); } $args{extra} = { $self->installer_options_for(undef) }; $self->clone(%args); } sub activate { my ($self, $path, $opts) = @_; $opts ||= {}; $self = $self->new unless ref $self; $path = $self->resolve_path($path); $self->ensure_dir_structure_for($path, { quiet => $self->quiet }) unless $opts->{no_create}; $path = $self->normalize_path($path); my @active_lls = $self->active_paths; if (grep { $_ eq $path } @active_lls[1 .. $#active_lls]) { $self = $self->deactivate($path); } my %args; if ($opts->{always} || !@active_lls || $active_lls[0] ne $path) { %args = ( bins => [ $self->install_base_bin_path($path), @{$self->bins} ], libs => [ $self->install_base_perl_path($path), @{$self->libs} ], inc => [ $self->lib_paths_for($path), @{$self->inc} ], roots => [ $path, @{$self->roots} ], ); } $args{extra} = { $self->installer_options_for($path) }; $self->clone(%args); } sub normalize_path { my ($self, $path) = @_; $path = ( Win32::GetShortPathName($path) || $path ) if $^O eq 'MSWin32'; return $path; } sub build_environment_vars_for { my $self = $_[0]->new->activate($_[1], { always => 1 }); $self->build_environment_vars; } sub build_activate_environment_vars_for { my $self = $_[0]->new->activate($_[1], { always => 1 }); $self->build_environment_vars; } sub build_deactivate_environment_vars_for { my $self = $_[0]->new->deactivate($_[1]); $self->build_environment_vars; } sub build_deact_all_environment_vars_for { my $self = $_[0]->new->deactivate_all; $self->build_environment_vars; } sub build_environment_vars { my $self = shift; ( PATH => join($_path_sep, _as_list($self->bins)), PERL5LIB => join($_path_sep, _as_list($self->libs)), PERL_LOCAL_LIB_ROOT => join($_path_sep, _as_list($self->roots)), %{$self->extra}, ); } sub setup_local_lib_for { my $self = $_[0]->new->activate($_[1]); $self->setup_local_lib; } sub setup_local_lib { my $self = shift; # if Carp is already loaded, ensure Carp::Heavy is also loaded, to avoid # $VERSION mismatch errors (Carp::Heavy loads Carp, so we do not need to # check in the other direction) require Carp::Heavy if $INC{'Carp.pm'}; $self->setup_env_hash; @INC = @{$self->inc}; } sub setup_env_hash_for { my $self = $_[0]->new->activate($_[1]); $self->setup_env_hash; } sub setup_env_hash { my $self = shift; my %env = $self->build_environment_vars; for my $key (keys %env) { if (defined $env{$key}) { $ENV{$key} = $env{$key}; } else { delete $ENV{$key}; } } } sub print_environment_vars_for { print $_[0]->environment_vars_string_for(@_[1..$#_]); } sub environment_vars_string_for { my $self = $_[0]->new->activate($_[1], { always => 1}); $self->environment_vars_string; } sub environment_vars_string { my ($self, $shelltype) = @_; $shelltype ||= $self->guess_shelltype; my $extra = $self->extra; my @envs = ( PATH => $self->bins, PERL5LIB => $self->libs, PERL_LOCAL_LIB_ROOT => $self->roots, map { $_ => $extra->{$_} } sort keys %$extra, ); $self->_build_env_string($shelltype, \@envs); } sub _build_env_string { my ($self, $shelltype, $envs) = @_; my @envs = @$envs; my $build_method = "build_${shelltype}_env_declaration"; my $out = ''; while (@envs) { my ($name, $value) = (shift(@envs), shift(@envs)); if ( ref $value && @$value == 1 && ref $value->[0] && ref $value->[0] eq 'SCALAR' && ${$value->[0]} eq $name) { next; } $out .= $self->$build_method($name, $value); } my $wrap_method = "wrap_${shelltype}_output"; if ($self->can($wrap_method)) { return $self->$wrap_method($out); } return $out; } sub build_bourne_env_declaration { my ($class, $name, $args) = @_; my $value = $class->_interpolate($args, '${%s:-}', qr/["\\\$!`]/, '\\%s'); if (!defined $value) { return qq{unset $name;\n}; } $value =~ s/(^|\G|$_path_sep)\$\{$name:-\}$_path_sep/$1\${$name}\${$name:+$_path_sep}/g; $value =~ s/$_path_sep\$\{$name:-\}$/\${$name:+$_path_sep\${$name}}/; qq{${name}="$value"; export ${name};\n} } sub build_csh_env_declaration { my ($class, $name, $args) = @_; my ($value, @vars) = $class->_interpolate($args, '${%s}', qr/["\$]/, '"\\%s"'); if (!defined $value) { return qq{unsetenv $name;\n}; } my $out = ''; for my $var (@vars) { $out .= qq{if ! \$?$name setenv $name '';\n}; } my $value_without = $value; if ($value_without =~ s/(?:^|$_path_sep)\$\{$name\}(?:$_path_sep|$)//g) { $out .= qq{if "\${$name}" != '' setenv $name "$value";\n}; $out .= qq{if "\${$name}" == '' }; } $out .= qq{setenv $name "$value_without";\n}; return $out; } sub build_cmd_env_declaration { my ($class, $name, $args) = @_; my $value = $class->_interpolate($args, '%%%s%%', qr(%), '%s'); if (!$value) { return qq{\@set $name=\n}; } my $out = ''; my $value_without = $value; if ($value_without =~ s/(?:^|$_path_sep)%$name%(?:$_path_sep|$)//g) { $out .= qq{\@if not "%$name%"=="" set "$name=$value"\n}; $out .= qq{\@if "%$name%"=="" }; } $out .= qq{\@set "$name=$value_without"\n}; return $out; } sub build_powershell_env_declaration { my ($class, $name, $args) = @_; my $value = $class->_interpolate($args, '$env:%s', qr/["\$]/, '`%s'); if (!$value) { return qq{Remove-Item -ErrorAction 0 Env:\\$name;\n}; } my $maybe_path_sep = qq{\$(if("\$env:$name"-eq""){""}else{"$_path_sep"})}; $value =~ s/(^|\G|$_path_sep)\$env:$name$_path_sep/$1\$env:$name"+$maybe_path_sep+"/g; $value =~ s/$_path_sep\$env:$name$/"+$maybe_path_sep+\$env:$name+"/; qq{\$env:$name = \$("$value");\n}; } sub wrap_powershell_output { my ($class, $out) = @_; return $out || " \n"; } sub build_fish_env_declaration { my ($class, $name, $args) = @_; my $value = $class->_interpolate($args, '$%s', qr/[\\"'$ ]/, '\\%s'); if (!defined $value) { return qq{set -e $name;\n}; } # fish has special handling for PATH, CDPATH, and MANPATH. They are always # treated as arrays, and joined with ; when storing the environment. Other # env vars can be arrays, but will be joined without a separator. We only # really care about PATH, but might as well make this routine more general. if ($name =~ /^(?:CD|MAN)?PATH$/) { $value =~ s/$_path_sep/ /g; my $silent = $name =~ /^(?:CD)?PATH$/ ? " 2>"._devnull : ''; return qq{set -x $name $value$silent;\n}; } my $out = ''; my $value_without = $value; if ($value_without =~ s/(?:^|$_path_sep)\$$name(?:$_path_sep|$)//g) { $out .= qq{set -q $name; and set -x $name $value;\n}; $out .= qq{set -q $name; or }; } $out .= qq{set -x $name $value_without;\n}; $out; } sub _interpolate { my ($class, $args, $var_pat, $escape, $escape_pat) = @_; return unless defined $args; my @args = ref $args ? @$args : $args; return unless @args; my @vars = map { $$_ } grep { ref $_ eq 'SCALAR' } @args; my $string = join $_path_sep, map { ref $_ eq 'SCALAR' ? sprintf($var_pat, $$_) : do { s/($escape)/sprintf($escape_pat, $1)/ge; $_; }; } @args; return wantarray ? ($string, \@vars) : $string; } sub pipeline; sub pipeline { my @methods = @_; my $last = pop(@methods); if (@methods) { \sub { my ($obj, @args) = @_; $obj->${pipeline @methods}( $obj->$last(@args) ); }; } else { \sub { shift->$last(@_); }; } } sub resolve_path { my ($class, $path) = @_; $path = $class->${pipeline qw( resolve_relative_path resolve_home_path resolve_empty_path )}($path); $path; } sub resolve_empty_path { my ($class, $path) = @_; if (defined $path) { $path; } else { '~/perl5'; } } sub resolve_home_path { my ($class, $path) = @_; $path =~ /^~([^\/]*)/ or return $path; my $user = $1; my $homedir = do { if (! length($user) && defined $ENV{HOME}) { $ENV{HOME}; } else { require File::Glob; File::Glob::bsd_glob("~$user", File::Glob::GLOB_TILDE()); } }; unless (defined $homedir) { require Carp; require Carp::Heavy; Carp::croak( "Couldn't resolve homedir for " .(defined $user ? $user : 'current user') ); } $path =~ s/^~[^\/]*/$homedir/; $path; } sub resolve_relative_path { my ($class, $path) = @_; _rel2abs($path); } sub ensure_dir_structure_for { my ($class, $path, $opts) = @_; $opts ||= {}; my @dirs; foreach my $dir ( $class->lib_paths_for($path), $class->install_base_bin_path($path), ) { my $d = $dir; while (!-d $d) { push @dirs, $d; require File::Basename; $d = File::Basename::dirname($d); } } warn "Attempting to create directory ${path}\n" if !$opts->{quiet} && @dirs; my %seen; foreach my $dir (reverse @dirs) { next if $seen{$dir}++; mkdir $dir or -d $dir or die "Unable to create $dir: $!" } return; } sub guess_shelltype { my $shellbin = defined $ENV{SHELL} && length $ENV{SHELL} ? ($ENV{SHELL} =~ /([\w.]+)$/)[-1] : ( $^O eq 'MSWin32' && exists $ENV{'!EXITCODE'} ) ? 'bash' : ( $^O eq 'MSWin32' && $ENV{PROMPT} && $ENV{COMSPEC} ) ? ($ENV{COMSPEC} =~ /([\w.]+)$/)[-1] : ( $^O eq 'MSWin32' && !$ENV{PROMPT} ) ? 'powershell.exe' : 'sh'; for ($shellbin) { return /csh$/ ? 'csh' : /fish$/ ? 'fish' : /command(?:\.com)?$/i ? 'cmd' : /cmd(?:\.exe)?$/i ? 'cmd' : /4nt(?:\.exe)?$/i ? 'cmd' : /powershell(?:\.exe)?$/i ? 'powershell' : 'bourne'; } } 1; __END__ =encoding utf8 =head1 NAME local::lib - create and use a local lib/ for perl modules with PERL5LIB =head1 SYNOPSIS In code - use local::lib; # sets up a local lib at ~/perl5 use local::lib '~/foo'; # same, but ~/foo # Or... use FindBin; use local::lib "$FindBin::Bin/../support"; # app-local support library From the shell - # Install LWP and its missing dependencies to the '~/perl5' directory perl -MCPAN -Mlocal::lib -e 'CPAN::install(LWP)' # Just print out useful shell commands $ perl -Mlocal::lib PERL_MB_OPT='--install_base /home/username/perl5'; export PERL_MB_OPT; PERL_MM_OPT='INSTALL_BASE=/home/username/perl5'; export PERL_MM_OPT; PERL5LIB="/home/username/perl5/lib/perl5"; export PERL5LIB; PATH="/home/username/perl5/bin:$PATH"; export PATH; PERL_LOCAL_LIB_ROOT="/home/usename/perl5:$PERL_LOCAL_LIB_ROOT"; export PERL_LOCAL_LIB_ROOT; From a F<.bash_profile> or F<.bashrc> file - eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)" =head2 The bootstrapping technique A typical way to install local::lib is using what is known as the "bootstrapping" technique. You would do this if your system administrator hasn't already installed local::lib. In this case, you'll need to install local::lib in your home directory. Even if you do have administrative privileges, you will still want to set up your environment variables, as discussed in step 4. Without this, you would still install the modules into the system CPAN installation and also your Perl scripts will not use the lib/ path you bootstrapped with local::lib. By default local::lib installs itself and the CPAN modules into ~/perl5. Windows users must also see L. =over 4 =item 1. Download and unpack the local::lib tarball from CPAN (search for "Download" on the CPAN page about local::lib). Do this as an ordinary user, not as root or administrator. Unpack the file in your home directory or in any other convenient location. =item 2. Run this: perl Makefile.PL --bootstrap If the system asks you whether it should automatically configure as much as possible, you would typically answer yes. =item 3. Run this: (local::lib assumes you have make installed on your system) make test && make install =item 4. Now we need to setup the appropriate environment variables, so that Perl starts using our newly generated lib/ directory. If you are using bash or any other Bourne shells, you can add this to your shell startup script this way: echo 'eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"' >>~/.bashrc If you are using C shell, you can do this as follows: % echo $SHELL /bin/csh $ echo 'eval `perl -I$HOME/perl5/lib/perl5 -Mlocal::lib`' >> ~/.cshrc After writing your shell configuration file, be sure to re-read it to get the changed settings into your current shell's environment. Bourne shells use C<. ~/.bashrc> for this, whereas C shells use C. =back =head3 Bootstrapping into an alternate directory In order to install local::lib into a directory other than the default, you need to specify the name of the directory when you call bootstrap. Then, when setting up the environment variables, both perl and local::lib must be told the location of the bootstrap directory. The setup process would look as follows: perl Makefile.PL --bootstrap=~/foo make test && make install echo 'eval "$(perl -I$HOME/foo/lib/perl5 -Mlocal::lib=$HOME/foo)"' >>~/.bashrc . ~/.bashrc =head3 Other bootstrapping options If you're on a slower machine, or are operating under draconian disk space limitations, you can disable the automatic generation of manpages from POD when installing modules by using the C<--no-manpages> argument when bootstrapping: perl Makefile.PL --bootstrap --no-manpages To avoid doing several bootstrap for several Perl module environments on the same account, for example if you use it for several different deployed applications independently, you can use one bootstrapped local::lib installation to install modules in different directories directly this way: cd ~/mydir1 perl -Mlocal::lib=./ eval $(perl -Mlocal::lib=./) ### To set the environment for this shell alone printenv ### You will see that ~/mydir1 is in the PERL5LIB perl -MCPAN -e install ... ### whatever modules you want cd ../mydir2 ... REPEAT ... If you use F<.bashrc> to activate a local::lib automatically, the local::lib will be re-enabled in any sub-shells used, overriding adjustments you may have made in the parent shell. To avoid this, you can initialize the local::lib in F<.bash_profile> rather than F<.bashrc>, or protect the local::lib invocation with a C<$SHLVL> check: [ $SHLVL -eq 1 ] && eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)" If you are working with several C environments, you may want to remove some of them from the current environment without disturbing the others. You can deactivate one environment like this (using bourne sh): eval $(perl -Mlocal::lib=--deactivate,~/path) which will generate and run the commands needed to remove C<~/path> from your various search paths. Whichever environment was B will remain the target for module installations. That is, if you activate C<~/path_A> and then you activate C<~/path_B>, new modules you install will go in C<~/path_B>. If you deactivate C<~/path_B> then modules will be installed into C<~/pathA> -- but if you deactivate C<~/path_A> then they will still be installed in C<~/pathB> because pathB was activated later. You can also ask C to clean itself completely out of the current shell's environment with the C<--deactivate-all> option. For multiple environments for multiple apps you may need to include a modified version of the C<< use FindBin >> instructions in the "In code" sample above. If you did something like the above, you have a set of Perl modules at C<< ~/mydir1/lib >>. If you have a script at C<< ~/mydir1/scripts/myscript.pl >>, you need to tell it where to find the modules you installed for it at C<< ~/mydir1/lib >>. In C<< ~/mydir1/scripts/myscript.pl >>: use strict; use warnings; use local::lib "$FindBin::Bin/.."; ### points to ~/mydir1 and local::lib finds lib use lib "$FindBin::Bin/../lib"; ### points to ~/mydir1/lib Put this before any BEGIN { ... } blocks that require the modules you installed. =head2 Differences when using this module under Win32 To set up the proper environment variables for your current session of C, you can use this: C:\>perl -Mlocal::lib set PERL_MB_OPT=--install_base C:\DOCUME~1\ADMINI~1\perl5 set PERL_MM_OPT=INSTALL_BASE=C:\DOCUME~1\ADMINI~1\perl5 set PERL5LIB=C:\DOCUME~1\ADMINI~1\perl5\lib\perl5 set PATH=C:\DOCUME~1\ADMINI~1\perl5\bin;%PATH% ### To set the environment for this shell alone C:\>perl -Mlocal::lib > %TEMP%\tmp.bat && %TEMP%\tmp.bat && del %TEMP%\tmp.bat ### instead of $(perl -Mlocal::lib=./) If you want the environment entries to persist, you'll need to add them to the Control Panel's System applet yourself or use L. The "~" is translated to the user's profile directory (the directory named for the user under "Documents and Settings" (Windows XP or earlier) or "Users" (Windows Vista or later)) unless $ENV{HOME} exists. After that, the home directory is translated to a short name (which means the directory must exist) and the subdirectories are created. =head3 PowerShell local::lib also supports PowerShell, and can be used with the C cmdlet. Invoke-Expression "$(perl -Mlocal::lib)" =head1 RATIONALE The version of a Perl package on your machine is not always the version you need. Obviously, the best thing to do would be to update to the version you need. However, you might be in a situation where you're prevented from doing this. Perhaps you don't have system administrator privileges; or perhaps you are using a package management system such as Debian, and nobody has yet gotten around to packaging up the version you need. local::lib solves this problem by allowing you to create your own directory of Perl packages downloaded from CPAN (in a multi-user system, this would typically be within your own home directory). The existing system Perl installation is not affected; you simply invoke Perl with special options so that Perl uses the packages in your own local package directory rather than the system packages. local::lib arranges things so that your locally installed version of the Perl packages takes precedence over the system installation. If you are using a package management system (such as Debian), you don't need to worry about Debian and CPAN stepping on each other's toes. Your local version of the packages will be written to an entirely separate directory from those installed by Debian. =head1 DESCRIPTION This module provides a quick, convenient way of bootstrapping a user-local Perl module library located within the user's home directory. It also constructs and prints out for the user the list of environment variables using the syntax appropriate for the user's current shell (as specified by the C environment variable), suitable for directly adding to one's shell configuration file. More generally, local::lib allows for the bootstrapping and usage of a directory containing Perl modules outside of Perl's C<@INC>. This makes it easier to ship an application with an app-specific copy of a Perl module, or collection of modules. Useful in cases like when an upstream maintainer hasn't applied a patch to a module of theirs that you need for your application. On import, local::lib sets the following environment variables to appropriate values: =over 4 =item PERL_MB_OPT =item PERL_MM_OPT =item PERL5LIB =item PATH =item PERL_LOCAL_LIB_ROOT =back When possible, these will be appended to instead of overwritten entirely. These values are then available for reference by any code after import. =head1 CREATING A SELF-CONTAINED SET OF MODULES See L for one way to do this - but note that there are a number of caveats, and the best approach is always to perform a build against a clean perl (i.e. site and vendor as close to empty as possible). =head1 IMPORT OPTIONS Options are values that can be passed to the C import besides the directory to use. They are specified as C or C. =head2 --deactivate Remove the chosen path (or the default path) from the module search paths if it was added by C, instead of adding it. =head2 --deactivate-all Remove all directories that were added to search paths by C from the search paths. =head2 --quiet Don't output any messages about directories being created. =head2 --always Always add directories to environment variables, ignoring if they are already included. =head2 --shelltype Specify the shell type to use for output. By default, the shell will be detected based on the environment. Should be one of: C, C, C, or C. =head2 --no-create Prevents C from creating directories when activating dirs. This is likely to cause issues on Win32 systems. =head1 CLASS METHODS =head2 ensure_dir_structure_for =over 4 =item Arguments: $path =item Return value: None =back Attempts to create a local::lib directory, including subdirectories and all required parent directories. Throws an exception on failure. =head2 print_environment_vars_for =over 4 =item Arguments: $path =item Return value: None =back Prints to standard output the variables listed above, properly set to use the given path as the base directory. =head2 build_environment_vars_for =over 4 =item Arguments: $path =item Return value: %environment_vars =back Returns a hash with the variables listed above, properly set to use the given path as the base directory. =head2 setup_env_hash_for =over 4 =item Arguments: $path =item Return value: None =back Constructs the C<%ENV> keys for the given path, by calling L. =head2 active_paths =over 4 =item Arguments: None =item Return value: @paths =back Returns a list of active C paths, according to the C environment variable and verified against what is really in C<@INC>. =head2 install_base_perl_path =over 4 =item Arguments: $path =item Return value: $install_base_perl_path =back Returns a path describing where to install the Perl modules for this local library installation. Appends the directories C and C to the given path. =head2 lib_paths_for =over 4 =item Arguments: $path =item Return value: @lib_paths =back Returns the list of paths perl will search for libraries, given a base path. This includes the base path itself, the architecture specific subdirectory, and perl version specific subdirectories. These paths may not all exist. =head2 install_base_bin_path =over 4 =item Arguments: $path =item Return value: $install_base_bin_path =back Returns a path describing where to install the executable programs for this local library installation. Appends the directory C to the given path. =head2 installer_options_for =over 4 =item Arguments: $path =item Return value: %installer_env_vars =back Returns a hash of environment variables that should be set to cause installation into the given path. =head2 resolve_empty_path =over 4 =item Arguments: $path =item Return value: $base_path =back Builds and returns the base path into which to set up the local module installation. Defaults to C<~/perl5>. =head2 resolve_home_path =over 4 =item Arguments: $path =item Return value: $home_path =back Attempts to find the user's home directory. If no definite answer is available, throws an exception. =head2 resolve_relative_path =over 4 =item Arguments: $path =item Return value: $absolute_path =back Translates the given path into an absolute path. =head2 resolve_path =over 4 =item Arguments: $path =item Return value: $absolute_path =back Calls the following in a pipeline, passing the result from the previous to the next, in an attempt to find where to configure the environment for a local library installation: L, L, L. Passes the given path argument to L which then returns a result that is passed to L, which then has its result passed to L. The result of this final call is returned from L. =head1 OBJECT INTERFACE =head2 new =over 4 =item Arguments: %attributes =item Return value: $local_lib =back Constructs a new C object, representing the current state of C<@INC> and the relevant environment variables. =head1 ATTRIBUTES =head2 roots An arrayref representing active C directories. =head2 inc An arrayref representing C<@INC>. =head2 libs An arrayref representing the PERL5LIB environment variable. =head2 bins An arrayref representing the PATH environment variable. =head2 extra A hashref of extra environment variables (e.g. C and C) =head2 no_create If set, C will not try to create directories when activating them. =head1 OBJECT METHODS =head2 clone =over 4 =item Arguments: %attributes =item Return value: $local_lib =back Constructs a new C object based on the existing one, overriding the specified attributes. =head2 activate =over 4 =item Arguments: $path =item Return value: $new_local_lib =back Constructs a new instance with the specified path active. =head2 deactivate =over 4 =item Arguments: $path =item Return value: $new_local_lib =back Constructs a new instance with the specified path deactivated. =head2 deactivate_all =over 4 =item Arguments: None =item Return value: $new_local_lib =back Constructs a new instance with all C directories deactivated. =head2 environment_vars_string =over 4 =item Arguments: [ $shelltype ] =item Return value: $shell_env_string =back Returns a string to set up the C, meant to be run by a shell. =head2 build_environment_vars =over 4 =item Arguments: None =item Return value: %environment_vars =back Returns a hash with the variables listed above, properly set to use the given path as the base directory. =head2 setup_env_hash =over 4 =item Arguments: None =item Return value: None =back Constructs the C<%ENV> keys for the given path, by calling L. =head2 setup_local_lib Constructs the C<%ENV> hash using L, and set up C<@INC>. =head1 A WARNING ABOUT UNINST=1 Be careful about using local::lib in combination with "make install UNINST=1". The idea of this feature is that will uninstall an old version of a module before installing a new one. However it lacks a safety check that the old version and the new version will go in the same directory. Used in combination with local::lib, you can potentially delete a globally accessible version of a module while installing the new version in a local place. Only combine "make install UNINST=1" and local::lib if you understand these possible consequences. =head1 LIMITATIONS =over 4 =item * Directory names with spaces in them are not well supported by the perl toolchain and the programs it uses. Pure-perl distributions should support spaces, but problems are more likely with dists that require compilation. A workaround you can do is moving your local::lib to a directory with spaces B you installed all modules inside your local::lib bootstrap. But be aware that you can't update or install CPAN modules after the move. =item * Rather basic shell detection. Right now anything with csh in its name is assumed to be a C shell or something compatible, and everything else is assumed to be Bourne, except on Win32 systems. If the C environment variable is not set, a Bourne-compatible shell is assumed. =item * Kills any existing PERL_MM_OPT or PERL_MB_OPT. =item * Should probably auto-fixup CPAN config if not already done. =item * On VMS and MacOS Classic (pre-OS X), local::lib loads L. This means any L version installed in the local::lib will be ignored by scripts using local::lib. A workaround for this is using C instead of using C directly. =item * Conflicts with L's C option. C uses the C option, as it has more predictable and sane behavior. If something attempts to use the C option when running a F, L will refuse to run, as the two options conflict. This can be worked around by temporarily unsetting the C environment variable. =item * Conflicts with L's C<--prefix> option. Similar to the previous limitation, but any C<--prefix> option specified will be ignored. This can be worked around by temporarily unsetting the C environment variable. =back Patches very much welcome for any of the above. =over 4 =item * On Win32 systems, does not have a way to write the created environment variables to the registry, so that they can persist through a reboot. =back =head1 TROUBLESHOOTING If you've configured local::lib to install CPAN modules somewhere in to your home directory, and at some point later you try to install a module with C, but it fails with an error like: C and buried within the install log is an error saying C<'INSTALL_BASE' is not a known MakeMaker parameter name>, then you've somehow lost your updated ExtUtils::MakeMaker module. To remedy this situation, rerun the bootstrapping procedure documented above. Then, run C Finally, re-run C and it should install without problems. =head1 ENVIRONMENT =over 4 =item SHELL =item COMSPEC local::lib looks at the user's C environment variable when printing out commands to add to the shell configuration file. On Win32 systems, C is also examined. =back =head1 SEE ALSO =over 4 =item * L =back =head1 SUPPORT IRC: Join #toolchain on irc.perl.org. =head1 AUTHOR Matt S Trout http://www.shadowcat.co.uk/ auto_install fixes kindly sponsored by http://www.takkle.com/ =head1 CONTRIBUTORS Patches to correctly output commands for csh style shells, as well as some documentation additions, contributed by Christopher Nehren . Doc patches for a custom local::lib directory, more cleanups in the english documentation and a L contributed by Torsten Raudssus . Hans Dieter Pearcey sent in some additional tests for ensuring things will install properly, submitted a fix for the bug causing problems with writing Makefiles during bootstrapping, contributed an example program, and submitted yet another fix to ensure that local::lib can install and bootstrap properly. Many, many thanks! pattern of Freenode IRC contributed the beginnings of the Troubleshooting section. Many thanks! Patch to add Win32 support contributed by Curtis Jewell . Warnings for missing PATH/PERL5LIB (as when not running interactively) silenced by a patch from Marco Emilio Poleggi. Mark Stosberg provided the code for the now deleted '--self-contained' option. Documentation patches to make win32 usage clearer by David Mertens (run4flat). Brazilian L and minor doc patches contributed by Breno G. de Oliveira . Improvements to stacking multiple local::lib dirs and removing them from the environment later on contributed by Andrew Rodland . Patch for Carp version mismatch contributed by Hakim Cassimally . Rewrite of internals and numerous bug fixes and added features contributed by Graham Knop . =head1 COPYRIGHT Copyright (c) 2007 - 2013 the local::lib L and L as listed above. =head1 LICENSE 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 PK!  perl5/TimeDate.pmnu6$package TimeDate; use vars qw($VERSION); $VERSION = "1.21"; =pod This is an empty module which is just there to get ownership on TimeDate using first-come permissions from PAUSE. This was required during the release of 1.21 for transferring ownership. =cut 1; PK!new( 'my-robot/1.0', 'cachefile' ); my $ua = WWW::RobotUA->new( 'my-robot/1.0', 'me@foo.com', $rules ); # Then just use $ua as usual $res = $ua->request($req); =head1 DESCRIPTION This is a subclass of I that uses the AnyDBM_File package to implement persistent diskcaching of F and host visit information. The constructor (the new() method) takes an extra argument specifying the name of the DBM file to use. If the DBM file already exists, then you can specify undef as agent name as the name can be obtained from the DBM database. =cut sub new { my ($class, $ua, $file) = @_; Carp::croak('WWW::RobotRules::AnyDBM_File filename required') unless $file; my $self = bless { }, $class; $self->{'filename'} = $file; tie %{$self->{'dbm'}}, 'AnyDBM_File', $file, O_CREAT|O_RDWR, 0640 or Carp::croak("Can't open $file: $!"); if ($ua) { $self->agent($ua); } else { # Try to obtain name from DBM file $ua = $self->{'dbm'}{"|ua-name|"}; Carp::croak("No agent name specified") unless $ua; } $self; } sub agent { my($self, $newname) = @_; my $old = $self->{'dbm'}{"|ua-name|"}; if (defined $newname) { $newname =~ s!/?\s*\d+.\d+\s*$!!; # loose version unless ($old && $old eq $newname) { # Old info is now stale. my $file = $self->{'filename'}; untie %{$self->{'dbm'}}; tie %{$self->{'dbm'}}, 'AnyDBM_File', $file, O_TRUNC|O_RDWR, 0640; %{$self->{'dbm'}} = (); $self->{'dbm'}{"|ua-name|"} = $newname; } } $old; } sub no_visits { my ($self, $netloc) = @_; my $t = $self->{'dbm'}{"$netloc|vis"}; return 0 unless $t; (split(/;\s*/, $t))[0]; } sub last_visit { my ($self, $netloc) = @_; my $t = $self->{'dbm'}{"$netloc|vis"}; return undef unless $t; (split(/;\s*/, $t))[1]; } sub fresh_until { my ($self, $netloc, $fresh) = @_; my $old = $self->{'dbm'}{"$netloc|exp"}; if ($old) { $old =~ s/;.*//; # remove cleartext } if (defined $fresh) { $fresh .= "; " . localtime($fresh); $self->{'dbm'}{"$netloc|exp"} = $fresh; } $old; } sub visit { my($self, $netloc, $time) = @_; $time ||= time; my $count = 0; my $old = $self->{'dbm'}{"$netloc|vis"}; if ($old) { my $last; ($count,$last) = split(/;\s*/, $old); $time = $last if $last > $time; } $count++; $self->{'dbm'}{"$netloc|vis"} = "$count; $time; " . localtime($time); } sub push_rules { my($self, $netloc, @rules) = @_; my $cnt = 1; $cnt++ while $self->{'dbm'}{"$netloc|r$cnt"}; foreach (@rules) { $self->{'dbm'}{"$netloc|r$cnt"} = $_; $cnt++; } } sub clear_rules { my($self, $netloc) = @_; my $cnt = 1; while ($self->{'dbm'}{"$netloc|r$cnt"}) { delete $self->{'dbm'}{"$netloc|r$cnt"}; $cnt++; } } sub rules { my($self, $netloc) = @_; my @rules = (); my $cnt = 1; while (1) { my $rule = $self->{'dbm'}{"$netloc|r$cnt"}; last unless $rule; push(@rules, $rule); $cnt++; } @rules; } sub dump { } 1; =head1 SEE ALSO L, L =head1 AUTHORS Hakan Ardo Ehakan@munin.ub2.lu.se>, Gisle Aas Eaas@sn.no> =cut PK!av“++perl5/WWW/RobotRules.pmnu6$package WWW::RobotRules; $VERSION = "6.02"; sub Version { $VERSION; } use strict; use URI (); sub new { my($class, $ua) = @_; # This ugly hack is needed to ensure backwards compatibility. # The "WWW::RobotRules" class is now really abstract. $class = "WWW::RobotRules::InCore" if $class eq "WWW::RobotRules"; my $self = bless { }, $class; $self->agent($ua); $self; } sub parse { my($self, $robot_txt_uri, $txt, $fresh_until) = @_; $robot_txt_uri = URI->new("$robot_txt_uri"); my $netloc = $robot_txt_uri->host . ":" . $robot_txt_uri->port; $self->clear_rules($netloc); $self->fresh_until($netloc, $fresh_until || (time + 365*24*3600)); my $ua; my $is_me = 0; # 1 iff this record is for me my $is_anon = 0; # 1 iff this record is for * my $seen_disallow = 0; # watch for missing record separators my @me_disallowed = (); # rules disallowed for me my @anon_disallowed = (); # rules disallowed for * # blank lines are significant, so turn CRLF into LF to avoid generating # false ones $txt =~ s/\015\012/\012/g; # split at \012 (LF) or \015 (CR) (Mac text files have just CR for EOL) for(split(/[\012\015]/, $txt)) { # Lines containing only a comment are discarded completely, and # therefore do not indicate a record boundary. next if /^\s*\#/; s/\s*\#.*//; # remove comments at end-of-line if (/^\s*$/) { # blank line last if $is_me; # That was our record. No need to read the rest. $is_anon = 0; $seen_disallow = 0; } elsif (/^\s*User-Agent\s*:\s*(.*)/i) { $ua = $1; $ua =~ s/\s+$//; if ($seen_disallow) { # treat as start of a new record $seen_disallow = 0; last if $is_me; # That was our record. No need to read the rest. $is_anon = 0; } if ($is_me) { # This record already had a User-agent that # we matched, so just continue. } elsif ($ua eq '*') { $is_anon = 1; } elsif($self->is_me($ua)) { $is_me = 1; } } elsif (/^\s*Disallow\s*:\s*(.*)/i) { unless (defined $ua) { warn "RobotRules <$robot_txt_uri>: Disallow without preceding User-agent\n" if $^W; $is_anon = 1; # assume that User-agent: * was intended } my $disallow = $1; $disallow =~ s/\s+$//; $seen_disallow = 1; if (length $disallow) { my $ignore; eval { my $u = URI->new_abs($disallow, $robot_txt_uri); $ignore++ if $u->scheme ne $robot_txt_uri->scheme; $ignore++ if lc($u->host) ne lc($robot_txt_uri->host); $ignore++ if $u->port ne $robot_txt_uri->port; $disallow = $u->path_query; $disallow = "/" unless length $disallow; }; next if $@; next if $ignore; } if ($is_me) { push(@me_disallowed, $disallow); } elsif ($is_anon) { push(@anon_disallowed, $disallow); } } elsif (/\S\s*:/) { # ignore } else { warn "RobotRules <$robot_txt_uri>: Malformed record: <$_>\n" if $^W; } } if ($is_me) { $self->push_rules($netloc, @me_disallowed); } else { $self->push_rules($netloc, @anon_disallowed); } } # # Returns TRUE if the given name matches the # name of this robot # sub is_me { my($self, $ua_line) = @_; my $me = $self->agent; # See whether my short-name is a substring of the # "User-Agent: ..." line that we were passed: if(index(lc($me), lc($ua_line)) >= 0) { return 1; } else { return ''; } } sub allowed { my($self, $uri) = @_; $uri = URI->new("$uri"); return 1 unless $uri->scheme eq 'http' or $uri->scheme eq 'https'; # Robots.txt applies to only those schemes. my $netloc = $uri->host . ":" . $uri->port; my $fresh_until = $self->fresh_until($netloc); return -1 if !defined($fresh_until) || $fresh_until < time; my $str = $uri->path_query; my $rule; for $rule ($self->rules($netloc)) { return 1 unless length $rule; return 0 if index($str, $rule) == 0; } return 1; } # The following methods must be provided by the subclass. sub agent; sub visit; sub no_visits; sub last_visits; sub fresh_until; sub push_rules; sub clear_rules; sub rules; sub dump; package WWW::RobotRules::InCore; use vars qw(@ISA); @ISA = qw(WWW::RobotRules); sub agent { my ($self, $name) = @_; my $old = $self->{'ua'}; if ($name) { # Strip it so that it's just the short name. # I.e., "FooBot" => "FooBot" # "FooBot/1.2" => "FooBot" # "FooBot/1.2 [http://foobot.int; foo@bot.int]" => "FooBot" $name = $1 if $name =~ m/(\S+)/; # get first word $name =~ s!/.*!!; # get rid of version unless ($old && $old eq $name) { delete $self->{'loc'}; # all old info is now stale $self->{'ua'} = $name; } } $old; } sub visit { my($self, $netloc, $time) = @_; return unless $netloc; $time ||= time; $self->{'loc'}{$netloc}{'last'} = $time; my $count = \$self->{'loc'}{$netloc}{'count'}; if (!defined $$count) { $$count = 1; } else { $$count++; } } sub no_visits { my ($self, $netloc) = @_; $self->{'loc'}{$netloc}{'count'}; } sub last_visit { my ($self, $netloc) = @_; $self->{'loc'}{$netloc}{'last'}; } sub fresh_until { my ($self, $netloc, $fresh_until) = @_; my $old = $self->{'loc'}{$netloc}{'fresh'}; if (defined $fresh_until) { $self->{'loc'}{$netloc}{'fresh'} = $fresh_until; } $old; } sub push_rules { my($self, $netloc, @rules) = @_; push (@{$self->{'loc'}{$netloc}{'rules'}}, @rules); } sub clear_rules { my($self, $netloc) = @_; delete $self->{'loc'}{$netloc}{'rules'}; } sub rules { my($self, $netloc) = @_; if (defined $self->{'loc'}{$netloc}{'rules'}) { return @{$self->{'loc'}{$netloc}{'rules'}}; } else { return (); } } sub dump { my $self = shift; for (keys %$self) { next if $_ eq 'loc'; print "$_ = $self->{$_}\n"; } for (keys %{$self->{'loc'}}) { my @rules = $self->rules($_); print "$_: ", join("; ", @rules), "\n"; } } 1; __END__ # Bender: "Well, I don't have anything else # planned for today. Let's get drunk!" =head1 NAME WWW::RobotRules - database of robots.txt-derived permissions =head1 SYNOPSIS use WWW::RobotRules; my $rules = WWW::RobotRules->new('MOMspider/1.0'); use LWP::Simple qw(get); { my $url = "http://some.place/robots.txt"; my $robots_txt = get $url; $rules->parse($url, $robots_txt) if defined $robots_txt; } { my $url = "http://some.other.place/robots.txt"; my $robots_txt = get $url; $rules->parse($url, $robots_txt) if defined $robots_txt; } # Now we can check if a URL is valid for those servers # whose "robots.txt" files we've gotten and parsed: if($rules->allowed($url)) { $c = get $url; ... } =head1 DESCRIPTION This module parses F files as specified in "A Standard for Robot Exclusion", at Webmasters can use the F file to forbid conforming robots from accessing parts of their web site. The parsed files are kept in a WWW::RobotRules object, and this object provides methods to check if access to a given URL is prohibited. The same WWW::RobotRules object can be used for one or more parsed F files on any number of hosts. The following methods are provided: =over 4 =item $rules = WWW::RobotRules->new($robot_name) This is the constructor for WWW::RobotRules objects. The first argument given to new() is the name of the robot. =item $rules->parse($robot_txt_url, $content, $fresh_until) The parse() method takes as arguments the URL that was used to retrieve the F file, and the contents of the file. =item $rules->allowed($uri) Returns TRUE if this robot is allowed to retrieve this URL. =item $rules->agent([$name]) Get/set the agent name. NOTE: Changing the agent name will clear the robots.txt rules and expire times out of the cache. =back =head1 ROBOTS.TXT The format and semantics of the "/robots.txt" file are as follows (this is an edited abstract of ): The file consists of one or more records separated by one or more blank lines. Each record contains lines of the form : The field name is case insensitive. Text after the '#' character on a line is ignored during parsing. This is used for comments. The following can be used: =over 3 =item User-Agent The value of this field is the name of the robot the record is describing access policy for. If more than one I field is present the record describes an identical access policy for more than one robot. At least one field needs to be present per record. If the value is '*', the record describes the default access policy for any robot that has not not matched any of the other records. The I fields must occur before the I fields. If a record contains a I field after a I field, that constitutes a malformed record. This parser will assume that a blank line should have been placed before that I field, and will break the record into two. All the fields before the I field will constitute a record, and the I field will be the first field in a new record. =item Disallow The value of this field specifies a partial URL that is not to be visited. This can be a full path, or a partial path; any URL that starts with this value will not be retrieved =back Unrecognized records are ignored. =head1 ROBOTS.TXT EXAMPLES The following example "/robots.txt" file specifies that no robots should visit any URL starting with "/cyberworld/map/" or "/tmp/": User-agent: * Disallow: /cyberworld/map/ # This is an infinite virtual URL space Disallow: /tmp/ # these will soon disappear This example "/robots.txt" file specifies that no robots should visit any URL starting with "/cyberworld/map/", except the robot called "cybermapper": User-agent: * Disallow: /cyberworld/map/ # This is an infinite virtual URL space # Cybermapper knows where to go. User-agent: cybermapper Disallow: This example indicates that no robots should visit this site further: # go away User-agent: * Disallow: / This is an example of a malformed robots.txt file. # robots.txt for ancientcastle.example.com # I've locked myself away. User-agent: * Disallow: / # The castle is your home now, so you can go anywhere you like. User-agent: Belle Disallow: /west-wing/ # except the west wing! # It's good to be the Prince... User-agent: Beast Disallow: This file is missing the required blank lines between records. However, the intention is clear. =head1 SEE ALSO L, L =head1 COPYRIGHT Copyright 1995-2009, Gisle Aas Copyright 1995, Martijn Koster This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. PK!GJJperl5/IO/Lines.pmnu6$package IO::Lines; use strict; use Carp; use IO::ScalarArray; # The package version, both in 1.23 style *and* usable by MakeMaker: our $VERSION = '2.113'; # Inheritance: our @ISA = qw(IO::ScalarArray); ### also gets us new_tie :-) =head1 NAME IO::Lines - IO:: interface for reading/writing an array of lines =head1 SYNOPSIS use IO::Lines; ### See IO::ScalarArray for details =head1 DESCRIPTION This class implements objects which behave just like FileHandle (or IO::Handle) objects, except that you may use them to write to (or read from) an array of lines. C capable as well. This is a subclass of L in which the underlying array has its data stored in a line-oriented-format: that is, every element ends in a C<"\n">, with the possible exception of the final element. This makes C I more efficient; if you plan to do line-oriented reading/printing, you want this class. The C method will enforce this rule, so you can print arbitrary data to the line-array: it will break the data at newlines appropriately. See L for full usage and warnings. =cut #------------------------------ # # getline # # Instance method, override. # Return the next line, or undef on end of data. # Can safely be called in an array context. # Currently, lines are delimited by "\n". # sub getline { my $self = shift; if (!defined $/) { return join( '', $self->_getlines_for_newlines ); } elsif ($/ eq "\n") { if (!*$self->{Pos}) { ### full line... return *$self->{AR}[*$self->{Str}++]; } else { ### partial line... my $partial = substr(*$self->{AR}[*$self->{Str}++], *$self->{Pos}); *$self->{Pos} = 0; return $partial; } } else { croak 'unsupported $/: must be "\n" or undef'; } } #------------------------------ # # getlines # # Instance method, override. # Return an array comprised of the remaining lines, or () on end of data. # Must be called in an array context. # Currently, lines are delimited by "\n". # sub getlines { my $self = shift; wantarray or croak("can't call getlines in scalar context!"); if ((defined $/) and ($/ eq "\n")) { return $self->_getlines_for_newlines(@_); } else { ### slow but steady return $self->SUPER::getlines(@_); } } #------------------------------ # # _getlines_for_newlines # # Instance method, private. # If $/ is newline, do fast getlines. # This CAN NOT invoke getline! # sub _getlines_for_newlines { my $self = shift; my ($rArray, $Str, $Pos) = @{*$self}{ qw( AR Str Pos ) }; my @partial = (); if ($Pos) { ### partial line... @partial = (substr( $rArray->[ $Str++ ], $Pos )); *$self->{Pos} = 0; } *$self->{Str} = scalar @$rArray; ### about to exhaust @$rArray return (@partial, @$rArray[ $Str .. $#$rArray ]); ### remaining full lines... } #------------------------------ # # print ARGS... # # Instance method, override. # Print ARGS to the underlying line array. # sub print { if (defined $\ && $\ ne "\n") { croak 'unsupported $\: must be "\n" or undef'; } my $self = shift; ### print STDERR "\n[[ARRAY WAS...\n", @{*$self->{AR}}, "<>\n"; my @lines = split /^/, join('', @_); @lines or return 1; ### Did the previous print not end with a newline? ### If so, append first line: if (@{*$self->{AR}} and (*$self->{AR}[-1] !~ /\n\Z/)) { *$self->{AR}[-1] .= shift @lines; } push @{*$self->{AR}}, @lines; ### add the remainder ### print STDERR "\n[[ARRAY IS NOW...\n", @{*$self->{AR}}, "<>\n"; 1; } #------------------------------ 1; __END__ =head1 VERSION $Id: Lines.pm,v 1.3 2005/02/10 21:21:53 dfs Exp $ =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!LLperl5/IO/HTML.pmnu6$#--------------------------------------------------------------------- package IO::HTML; # # Copyright 2020 Christopher J. Madsen # # Author: Christopher J. Madsen # Created: 14 Jan 2012 # # This program is free software; you can redistribute it and/or modify # it under the same terms as Perl itself. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the # GNU General Public License or the Artistic License for more details. # # ABSTRACT: Open an HTML file with automatic charset detection #--------------------------------------------------------------------- use 5.008; use strict; use warnings; use Carp 'croak'; use Encode 2.10 qw(decode find_encoding); # need utf-8-strict encoding use Exporter 5.57 'import'; our $VERSION = '1.004'; # This file is part of IO-HTML 1.004 (September 26, 2020) our $bytes_to_check ||= 1024; our $default_encoding ||= 'cp1252'; our @EXPORT = qw(html_file); our @EXPORT_OK = qw(find_charset_in html_file_and_encoding html_outfile sniff_encoding); our %EXPORT_TAGS = ( rw => [qw( html_file html_file_and_encoding html_outfile )], all => [ @EXPORT, @EXPORT_OK ], ); #===================================================================== sub html_file { (&html_file_and_encoding)[0]; # return just the filehandle } # end html_file # Note: I made html_file and html_file_and_encoding separate functions # (instead of making html_file context-sensitive) because I wanted to # use html_file in function calls (i.e. list context) without having # to write "scalar html_file" all the time. sub html_file_and_encoding { my ($filename, $options) = @_; $options ||= {}; open(my $in, '<:raw', $filename) or croak "Failed to open $filename: $!"; my ($encoding, $bom) = sniff_encoding($in, $filename, $options); if (not defined $encoding) { croak "No default encoding specified" unless defined($encoding = $default_encoding); $encoding = find_encoding($encoding) if $options->{encoding}; } # end if we didn't find an encoding binmode $in, sprintf(":encoding(%s):crlf", $options->{encoding} ? $encoding->name : $encoding); return ($in, $encoding, $bom); } # end html_file_and_encoding #--------------------------------------------------------------------- sub html_outfile { my ($filename, $encoding, $bom) = @_; if (not defined $encoding) { croak "No default encoding specified" unless defined($encoding = $default_encoding); } # end if we didn't find an encoding elsif (ref $encoding) { $encoding = $encoding->name; } open(my $out, ">:encoding($encoding)", $filename) or croak "Failed to open $filename: $!"; print $out "\x{FeFF}" if $bom; return $out; } # end html_outfile #--------------------------------------------------------------------- sub sniff_encoding { my ($in, $filename, $options) = @_; $filename = 'file' unless defined $filename; $options ||= {}; my $pos = tell $in; croak "Could not seek $filename: $!" if $pos < 0; croak "Could not read $filename: $!" unless defined read $in, my($buf), $bytes_to_check; seek $in, $pos, 0 or croak "Could not seek $filename: $!"; # Check for BOM: my $bom; my $encoding = do { if ($buf =~ /^\xFe\xFF/) { $bom = 2; 'UTF-16BE'; } elsif ($buf =~ /^\xFF\xFe/) { $bom = 2; 'UTF-16LE'; } elsif ($buf =~ /^\xEF\xBB\xBF/) { $bom = 3; 'utf-8-strict'; } else { find_charset_in($buf, $options); # check for } }; # end $encoding if ($bom) { seek $in, $bom, 1 or croak "Could not seek $filename: $!"; $bom = 1; } elsif (not defined $encoding) { # try decoding as UTF-8 my $test = decode('utf-8-strict', $buf, Encode::FB_QUIET); if ($buf =~ /^(?: # nothing left over | [\xC2-\xDF] # incomplete 2-byte char | [\xE0-\xEF] [\x80-\xBF]? # incomplete 3-byte char | [\xF0-\xF4] [\x80-\xBF]{0,2} # incomplete 4-byte char )\z/x and $test =~ /[^\x00-\x7F]/) { $encoding = 'utf-8-strict'; } # end if valid UTF-8 with at least one multi-byte character: } # end if testing for UTF-8 if (defined $encoding and $options->{encoding} and not ref $encoding) { $encoding = find_encoding($encoding); } # end if $encoding is a string and we want an object return wantarray ? ($encoding, $bom) : $encoding; } # end sniff_encoding #===================================================================== # Based on HTML5 8.2.2.2 Determining the character encoding: # Get attribute from current position of $_ sub _get_attribute { m!\G[\x09\x0A\x0C\x0D /]+!gc; # skip whitespace or / return if /\G>/gc or not /\G(=?[^\x09\x0A\x0C\x0D =]*)/gc; my ($name, $value) = (lc $1, ''); if (/\G[\x09\x0A\x0C\x0D ]*=[\x09\x0A\x0C\x0D ]*/gc) { if (/\G"/gc) { # Double-quoted attribute value /\G([^"]*)("?)/gc; return unless $2; # Incomplete attribute (missing closing quote) $value = lc $1; } elsif (/\G'/gc) { # Single-quoted attribute value /\G([^']*)('?)/gc; return unless $2; # Incomplete attribute (missing closing quote) $value = lc $1; } else { # Unquoted attribute value /\G([^\x09\x0A\x0C\x0D >]*)/gc; $value = lc $1; } } # end if attribute has value return wantarray ? ($name, $value) : 1; } # end _get_attribute # Examine a meta value for a charset: sub _get_charset_from_meta { for (shift) { while (/charset[\x09\x0A\x0C\x0D ]*=[\x09\x0A\x0C\x0D ]*/ig) { return $1 if (/\G"([^"]*)"/gc or /\G'([^']*)'/gc or /\G(?!['"])([^\x09\x0A\x0C\x0D ;]+)/gc); } } # end for value return undef; } # end _get_charset_from_meta #--------------------------------------------------------------------- sub find_charset_in { for (shift) { my $options = shift || {}; # search only the first $bytes_to_check bytes (default 1024) my $stop = length > $bytes_to_check ? $bytes_to_check : length; my $expect_pragma = (defined $options->{need_pragma} ? $options->{need_pragma} : 1); pos() = 0; while (pos() < $stop) { if (/\G IO::WrapTie::Master >--isa--> IO::Handle V / .-------------. | | | | * Perl i/o operators work on the tied object, | "Master" | invoking the C methods. | | * Method invocations are delegated to the tied | | slave. `-------------' | tied(*$WT) | .---isa--> IO::WrapTie::Slave V / .-------------. | | | "Slave" | * Instance of FileHandle-like class which doesn't | | actually use file descriptors, like IO::Scalar. | IO::Scalar | * The slave can be any kind of object. | | * Must implement the C interface. `-------------' I just as an L is really just a blessed reference to a I file handle glob. So also, an C is really just a blessed reference to a file handle glob I =head2 How C works =over 4 =item 1. The call to function C is passed onto C. Note that class C is a subclass of L. =item 2. The C<< IO::WrapTie::Master->new >> method creates a new L object, re-blessed into class C. This object is the I, which will be returned from the constructor. At the same time... =item 3. The C method also creates the I: this is an instance of C which is created by tying the master's L to C via C. This call to C creates the slave in the following manner: =item 4. Class C is sent the message C; it will usually delegate this to C<< SLAVECLASS->new(TIEARGS) >>, resulting in a new instance of C being created and returned. =item 5. Once both master and slave have been created, the master is returned to the caller. =back =head2 How I/O operators work (on the master) Consider using an i/o operator on the master: print $WT "Hello, world!\n"; Since the master C<$WT> is really a C reference to a glob, the normal Perl I/O operators like C may be used on it. They will just operate on the symbol part of the glob. Since the glob is tied to the slave, the slave's C method (part of the C interface) will be automatically invoked. If the slave is an L, that means L will be invoked, and that method happens to delegate to the C method of the same class. So the I work is ultimately done by L. =head2 How methods work (on the master) Consider using a method on the master: $WT->print("Hello, world!\n"); Since the master C<$WT> is blessed into the class C, Perl first attempts to find a C method there. Failing that, Perl next attempts to find a C method in the super class, L. It just so happens that there I such a method; that method merely invokes the C I/O operator on the self object... and for that, see above! But let's suppose we're dealing with a method which I part of L... for example: my $sref = $WT->sref; In this case, the intuitive behavior is to have the master delegate the method invocation to the slave (now do you see where the designations come from?). This is indeed what happens: C contains an C method which performs the delegation. So: when C can't be found in L, the C method of C is invoked, and the standard behavior of delegating the method to the underlying slave (here, an L) is done. Sometimes, to get this to work properly, you may need to create a subclass of C which is an effective master for I class, and do the delegation there. =head1 NOTES B Because that means forsaking the use of named operators like C, and you may need to pass the object to a subroutine which will attempt to use those operators: $O = FooHandle->new(&FOO_RDWR, 2); $O->print("Hello, world\n"); ### OO syntax is okay, BUT.... sub nope { print $_[0] "Nope!\n" } X nope($O); ### ERROR!!! (not a glob ref) B Because (1) you have to use C to invoke methods in the object's public interface (yuck), and (2) you may need to pass the tied symbol to another subroutine which will attempt to treat it in an OO-way... and that will break it: tie *T, 'FooHandle', &FOO_RDWR, 2; print T "Hello, world\n"; ### Operator is okay, BUT... tied(*T)->other_stuff; ### yuck! AND... sub nope { shift->print("Nope!\n") } X nope(\*T); ### ERROR!!! (method "print" on unblessed ref) B Why not simply write C to inherit from L I tried this, with an implementation similar to that of L. The problem is that I. Subclassing L will work fine for the OO stuff, and fine with named operators I you C... but if you just attempt to say: $IO = FooHandle->new(&FOO_RDWR, 2); print $IO "Hello!\n"; you get a warning from Perl like: Filehandle GEN001 never opened because it's trying to do system-level I/O on an (unopened) file descriptor. To avoid this, you apparently have to C the handle... which brings us right back to where we started! At least the L mixin lets us say: $IO = FooHandle->new_tie(&FOO_RDWR, 2); print $IO "Hello!\n"; and so is not I bad. C<:-)> =head1 WARNINGS Remember: this stuff is for doing L-like I/O on things I. If you have an underlying file descriptor, you're better off just inheriting from L. B it does B return an instance of the I/O class you're tying to! Invoking some methods on the master object causes C to delegate them to the slave object... so it I like you're manipulating a C object directly, but you're not. I have not explored all the ramifications of this use of C. I. =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!^::perl5/IO/Scalar.pmnu6$package IO::Scalar; use strict; use Carp; use IO::Handle; ### Stringification, courtesy of B. K. Oxley (binkley): :-) use overload '""' => sub { ${*{$_[0]}->{SR}} }; use overload 'bool' => sub { 1 }; ### have to do this, so object is true! ### The package version, both in 1.23 style *and* usable by MakeMaker: our $VERSION = '2.113'; ### Inheritance: our @ISA = qw(IO::Handle); ### This stuff should be got rid of ASAP. require IO::WrapTie and push @ISA, 'IO::WrapTie::Slave' if ($] >= 5.004); #============================== =head1 NAME IO::Scalar - IO:: interface for reading/writing a scalar =head1 SYNOPSIS Perform I/O on strings, using the basic OO interface... use 5.005; use IO::Scalar; $data = "My message:\n"; ### Open a handle on a string, and append to it: $SH = new IO::Scalar \$data; $SH->print("Hello"); $SH->print(", world!\nBye now!\n"); print "The string is now: ", $data, "\n"; ### Open a handle on a string, read it line-by-line, then close it: $SH = new IO::Scalar \$data; while (defined($_ = $SH->getline)) { print "Got line: $_"; } $SH->close; ### Open a handle on a string, and slurp in all the lines: $SH = new IO::Scalar \$data; print "All lines:\n", $SH->getlines; ### Get the current position (either of two ways): $pos = $SH->getpos; $offset = $SH->tell; ### Set the current position (either of two ways): $SH->setpos($pos); $SH->seek($offset, 0); ### Open an anonymous temporary scalar: $SH = new IO::Scalar; $SH->print("Hi there!"); print "I printed: ", ${$SH->sref}, "\n"; ### get at value Don't like OO for your I/O? No problem. Thanks to the magic of an invisible tie(), the following now works out of the box, just as it does with IO::Handle: use 5.005; use IO::Scalar; $data = "My message:\n"; ### Open a handle on a string, and append to it: $SH = new IO::Scalar \$data; print $SH "Hello"; print $SH ", world!\nBye now!\n"; print "The string is now: ", $data, "\n"; ### Open a handle on a string, read it line-by-line, then close it: $SH = new IO::Scalar \$data; while (<$SH>) { print "Got line: $_"; } close $SH; ### Open a handle on a string, and slurp in all the lines: $SH = new IO::Scalar \$data; print "All lines:\n", <$SH>; ### Get the current position (WARNING: requires 5.6): $offset = tell $SH; ### Set the current position (WARNING: requires 5.6): seek $SH, $offset, 0; ### Open an anonymous temporary scalar: $SH = new IO::Scalar; print $SH "Hi there!"; print "I printed: ", ${$SH->sref}, "\n"; ### get at value And for you folks with 1.x code out there: the old tie() style still works, though this is I: use IO::Scalar; ### Writing to a scalar... my $s; tie *OUT, 'IO::Scalar', \$s; print OUT "line 1\nline 2\n", "line 3\n"; print "String is now: $s\n" ### Reading and writing an anonymous scalar... tie *OUT, 'IO::Scalar'; print OUT "line 1\nline 2\n", "line 3\n"; tied(OUT)->seek(0,0); while () { print "Got line: ", $_; } Stringification works, too! my $SH = new IO::Scalar \$data; print $SH "Hello, "; print $SH "world!"; print "I printed: $SH\n"; =head1 DESCRIPTION This class is part of the IO::Stringy distribution; see L for change log and general information. The IO::Scalar class implements objects which behave just like IO::Handle (or FileHandle) objects, except that you may use them to write to (or read from) scalars. These handles are automatically Cd (though please see L<"WARNINGS"> for information relevant to your Perl version). Basically, this: my $s; $SH = new IO::Scalar \$s; $SH->print("Hel", "lo, "); ### OO style $SH->print("world!\n"); ### ditto Or this: my $s; $SH = tie *OUT, 'IO::Scalar', \$s; print OUT "Hel", "lo, "; ### non-OO style print OUT "world!\n"; ### ditto Causes $s to be set to: "Hello, world!\n" =head1 PUBLIC INTERFACE =head2 Construction =over 4 =cut #------------------------------ =item new [ARGS...] I Return a new, unattached scalar handle. If any arguments are given, they're sent to open(). =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = bless \do { local *FH }, $class; tie *$self, $class, $self; $self->open(@_); ### open on anonymous by default $self; } sub DESTROY { shift->close; } #------------------------------ =item open [SCALARREF] I Open the scalar handle on a new scalar, pointed to by SCALARREF. If no SCALARREF is given, a "private" scalar is created to hold the file data. Returns the self object on success, undefined on error. =cut sub open { my ($self, $sref) = @_; ### Sanity: defined($sref) or do {my $s = ''; $sref = \$s}; (ref($sref) eq "SCALAR") or croak "open() needs a ref to a scalar"; ### Setup: *$self->{Pos} = 0; ### seek position *$self->{SR} = $sref; ### scalar reference $self; } #------------------------------ =item opened I Is the scalar handle opened on something? =cut sub opened { *{shift()}->{SR}; } #------------------------------ =item close I Disassociate the scalar handle from its underlying scalar. Done automatically on destroy. =cut sub close { my $self = shift; %{*$self} = (); 1; } =back =cut #============================== =head2 Input and output =over 4 =cut #------------------------------ =item flush I No-op, provided for OO compatibility. =cut sub flush { "0 but true" } #------------------------------ =item fileno I No-op, returns undef =cut sub fileno { } #------------------------------ =item getc I Return the next character, or undef if none remain. =cut sub getc { my $self = shift; ### Return undef right away if at EOF; else, move pos forward: return undef if $self->eof; substr(${*$self->{SR}}, *$self->{Pos}++, 1); } #------------------------------ =item getline I Return the next line, or undef on end of string. Can safely be called in an array context. Currently, lines are delimited by "\n". =cut sub getline { my $self = shift; ### Return undef right away if at EOF: return undef if $self->eof; ### Get next line: my $sr = *$self->{SR}; my $i = *$self->{Pos}; ### Start matching at this point. ### Minimal impact implementation! ### We do the fast thing (no regexps) if using the ### classic input record separator. ### Case 1: $/ is undef: slurp all... if (!defined($/)) { *$self->{Pos} = length $$sr; return substr($$sr, $i); } ### Case 2: $/ is "\n": zoom zoom zoom... elsif ($/ eq "\012") { ### Seek ahead for "\n"... yes, this really is faster than regexps. my $len = length($$sr); for (; $i < $len; ++$i) { last if ord (substr ($$sr, $i, 1)) == 10; } ### Extract the line: my $line; if ($i < $len) { ### We found a "\n": $line = substr ($$sr, *$self->{Pos}, $i - *$self->{Pos} + 1); *$self->{Pos} = $i+1; ### Remember where we finished up. } else { ### No "\n"; slurp the remainder: $line = substr ($$sr, *$self->{Pos}, $i - *$self->{Pos}); *$self->{Pos} = $len; } return $line; } ### Case 3: $/ is ref to int. Do fixed-size records. ### (Thanks to Dominique Quatravaux.) elsif (ref($/)) { my $len = length($$sr); my $i = ${$/} + 0; my $line = substr ($$sr, *$self->{Pos}, $i); *$self->{Pos} += $i; *$self->{Pos} = $len if (*$self->{Pos} > $len); return $line; } ### Case 4: $/ is either "" (paragraphs) or something weird... ### This is Graham's general-purpose stuff, which might be ### a tad slower than Case 2 for typical data, because ### of the regexps. else { pos($$sr) = $i; ### If in paragraph mode, skip leading lines (and update i!): length($/) or (($$sr =~ m/\G\n*/g) and ($i = pos($$sr))); ### If we see the separator in the buffer ahead... if (length($/) ? $$sr =~ m,\Q$/\E,g ### (ordinary sep) TBD: precomp! : $$sr =~ m,\n\n,g ### (a paragraph) ) { *$self->{Pos} = pos $$sr; return substr($$sr, $i, *$self->{Pos}-$i); } ### Else if no separator remains, just slurp the rest: else { *$self->{Pos} = length $$sr; return substr($$sr, $i); } } } #------------------------------ =item getlines I Get all remaining lines. It will croak() if accidentally called in a scalar context. =cut sub getlines { my $self = shift; wantarray or croak("can't call getlines in scalar context!"); my ($line, @lines); push @lines, $line while (defined($line = $self->getline)); @lines; } #------------------------------ =item print ARGS... I Print ARGS to the underlying scalar. B this continues to always cause a seek to the end of the string, but if you perform seek()s and tell()s, it is still safer to explicitly seek-to-end before subsequent print()s. =cut sub print { my $self = shift; *$self->{Pos} = length(${*$self->{SR}} .= join('', @_) . (defined($\) ? $\ : "")); 1; } sub _unsafe_print { my $self = shift; my $append = join('', @_) . $\; ${*$self->{SR}} .= $append; *$self->{Pos} += length($append); 1; } sub _old_print { my $self = shift; ${*$self->{SR}} .= join('', @_) . $\; *$self->{Pos} = length(${*$self->{SR}}); 1; } #------------------------------ =item read BUF, NBYTES, [OFFSET] I Read some bytes from the scalar. Returns the number of bytes actually read, 0 on end-of-file, undef on error. =cut sub read { my $self = $_[0]; my $n = $_[2]; my $off = $_[3] || 0; my $read = substr(${*$self->{SR}}, *$self->{Pos}, $n); $n = length($read); *$self->{Pos} += $n; ($off ? substr($_[1], $off) : $_[1]) = $read; return $n; } #------------------------------ =item write BUF, NBYTES, [OFFSET] I Write some bytes to the scalar. =cut sub write { my $self = $_[0]; my $n = $_[2]; my $off = $_[3] || 0; my $data = substr($_[1], $off, $n); $n = length($data); $self->print($data); return $n; } #------------------------------ =item sysread BUF, LEN, [OFFSET] I Read some bytes from the scalar. Returns the number of bytes actually read, 0 on end-of-file, undef on error. =cut sub sysread { my $self = shift; $self->read(@_); } #------------------------------ =item syswrite BUF, NBYTES, [OFFSET] I Write some bytes to the scalar. =cut sub syswrite { my $self = shift; $self->write(@_); } =back =cut #============================== =head2 Seeking/telling and other attributes =over 4 =cut #------------------------------ =item autoflush I No-op, provided for OO compatibility. =cut sub autoflush {} #------------------------------ =item binmode I No-op, provided for OO compatibility. =cut sub binmode {} #------------------------------ =item clearerr I Clear the error and EOF flags. A no-op. =cut sub clearerr { 1 } #------------------------------ =item eof I Are we at end of file? =cut sub eof { my $self = shift; (*$self->{Pos} >= length(${*$self->{SR}})); } #------------------------------ =item seek OFFSET, WHENCE I Seek to a given position in the stream. =cut sub seek { my ($self, $pos, $whence) = @_; my $eofpos = length(${*$self->{SR}}); ### Seek: if ($whence == 0) { *$self->{Pos} = $pos } ### SEEK_SET elsif ($whence == 1) { *$self->{Pos} += $pos } ### SEEK_CUR elsif ($whence == 2) { *$self->{Pos} = $eofpos + $pos} ### SEEK_END else { croak "bad seek whence ($whence)" } ### Fixup: if (*$self->{Pos} < 0) { *$self->{Pos} = 0 } if (*$self->{Pos} > $eofpos) { *$self->{Pos} = $eofpos } return 1; } #------------------------------ =item sysseek OFFSET, WHENCE I Identical to C, I =cut sub sysseek { my $self = shift; $self->seek (@_); } #------------------------------ =item tell I Return the current position in the stream, as a numeric offset. =cut sub tell { *{shift()}->{Pos} } #------------------------------ # # use_RS [YESNO] # # I # Obey the current setting of $/, like IO::Handle does? # Default is false in 1.x, but cold-welded true in 2.x and later. # sub use_RS { my ($self, $yesno) = @_; carp "use_RS is deprecated and ignored; \$/ is always consulted\n"; } #------------------------------ =item setpos POS I Set the current position, using the opaque value returned by C. =cut sub setpos { shift->seek($_[0],0) } #------------------------------ =item getpos I Return the current position in the string, as an opaque object. =cut *getpos = \&tell; #------------------------------ =item sref I Return a reference to the underlying scalar. =cut sub sref { *{shift()}->{SR} } #------------------------------ # Tied handle methods... #------------------------------ # Conventional tiehandle interface: sub TIEHANDLE { ((defined($_[1]) && UNIVERSAL::isa($_[1], "IO::Scalar")) ? $_[1] : shift->new(@_)); } sub GETC { shift->getc(@_) } sub PRINT { shift->print(@_) } sub PRINTF { shift->print(sprintf(shift, @_)) } sub READ { shift->read(@_) } sub READLINE { wantarray ? shift->getlines(@_) : shift->getline(@_) } sub WRITE { shift->write(@_); } sub CLOSE { shift->close(@_); } sub SEEK { shift->seek(@_); } sub TELL { shift->tell(@_); } sub EOF { shift->eof(@_); } sub BINMODE { 1; } #------------------------------------------------------------ 1; __END__ =back =cut =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!l]?@?@perl5/IO/ScalarArray.pmnu6$package IO::ScalarArray; use strict; use Carp; use IO::Handle; # The package version, both in 1.23 style *and* usable by MakeMaker: our $VERSION = '2.113'; # Inheritance: our @ISA = qw(IO::Handle); require IO::WrapTie and push @ISA, 'IO::WrapTie::Slave' if ($] >= 5.004); =head1 NAME IO::ScalarArray - IO:: interface for reading/writing an array of scalars =head1 SYNOPSIS Perform I/O on strings, using the basic OO interface... use IO::ScalarArray; @data = ("My mes", "sage:\n"); ### Open a handle on an array, and append to it: $AH = new IO::ScalarArray \@data; $AH->print("Hello"); $AH->print(", world!\nBye now!\n"); print "The array is now: ", @data, "\n"; ### Open a handle on an array, read it line-by-line, then close it: $AH = new IO::ScalarArray \@data; while (defined($_ = $AH->getline)) { print "Got line: $_"; } $AH->close; ### Open a handle on an array, and slurp in all the lines: $AH = new IO::ScalarArray \@data; print "All lines:\n", $AH->getlines; ### Get the current position (either of two ways): $pos = $AH->getpos; $offset = $AH->tell; ### Set the current position (either of two ways): $AH->setpos($pos); $AH->seek($offset, 0); ### Open an anonymous temporary array: $AH = new IO::ScalarArray; $AH->print("Hi there!"); print "I printed: ", @{$AH->aref}, "\n"; ### get at value Don't like OO for your I/O? No problem. Thanks to the magic of an invisible tie(), the following now works out of the box, just as it does with IO::Handle: use IO::ScalarArray; @data = ("My mes", "sage:\n"); ### Open a handle on an array, and append to it: $AH = new IO::ScalarArray \@data; print $AH "Hello"; print $AH ", world!\nBye now!\n"; print "The array is now: ", @data, "\n"; ### Open a handle on a string, read it line-by-line, then close it: $AH = new IO::ScalarArray \@data; while (<$AH>) { print "Got line: $_"; } close $AH; ### Open a handle on a string, and slurp in all the lines: $AH = new IO::ScalarArray \@data; print "All lines:\n", <$AH>; ### Get the current position (WARNING: requires 5.6): $offset = tell $AH; ### Set the current position (WARNING: requires 5.6): seek $AH, $offset, 0; ### Open an anonymous temporary scalar: $AH = new IO::ScalarArray; print $AH "Hi there!"; print "I printed: ", @{$AH->aref}, "\n"; ### get at value And for you folks with 1.x code out there: the old tie() style still works, though this is I: use IO::ScalarArray; ### Writing to a scalar... my @a; tie *OUT, 'IO::ScalarArray', \@a; print OUT "line 1\nline 2\n", "line 3\n"; print "Array is now: ", @a, "\n" ### Reading and writing an anonymous scalar... tie *OUT, 'IO::ScalarArray'; print OUT "line 1\nline 2\n", "line 3\n"; tied(OUT)->seek(0,0); while () { print "Got line: ", $_; } =head1 DESCRIPTION This class is part of the IO::Stringy distribution; see L for change log and general information. The IO::ScalarArray class implements objects which behave just like IO::Handle (or FileHandle) objects, except that you may use them to write to (or read from) arrays of scalars. Logically, an array of scalars defines an in-core "file" whose contents are the concatenation of the scalars in the array. The handles created by this class are automatically Cd (though please see L<"WARNINGS"> for information relevant to your Perl version). For writing large amounts of data with individual print() statements, this class is likely to be more efficient than IO::Scalar. Basically, this: my @a; $AH = new IO::ScalarArray \@a; $AH->print("Hel", "lo, "); ### OO style $AH->print("world!\n"); ### ditto Or this: my @a; $AH = new IO::ScalarArray \@a; print $AH "Hel", "lo, "; ### non-OO style print $AH "world!\n"; ### ditto Causes @a to be set to the following array of 3 strings: ( "Hel" , "lo, " , "world!\n" ) See L and compare with this class. =head1 PUBLIC INTERFACE =head2 Construction =over 4 =cut #------------------------------ =item new [ARGS...] I Return a new, unattached array handle. If any arguments are given, they're sent to open(). =cut sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = bless \do { local *FH }, $class; tie *$self, $class, $self; $self->open(@_); ### open on anonymous by default $self; } sub DESTROY { shift->close; } #------------------------------ =item open [ARRAYREF] I Open the array handle on a new array, pointed to by ARRAYREF. If no ARRAYREF is given, a "private" array is created to hold the file data. Returns the self object on success, undefined on error. =cut sub open { my ($self, $aref) = @_; ### Sanity: defined($aref) or do {my @a; $aref = \@a}; (ref($aref) eq "ARRAY") or croak "open needs a ref to a array"; ### Setup: $self->setpos([0,0]); *$self->{AR} = $aref; $self; } #------------------------------ =item opened I Is the array handle opened on something? =cut sub opened { *{shift()}->{AR}; } #------------------------------ =item close I Disassociate the array handle from its underlying array. Done automatically on destroy. =cut sub close { my $self = shift; %{*$self} = (); 1; } =back =cut #============================== =head2 Input and output =over 4 =cut #------------------------------ =item flush I No-op, provided for OO compatibility. =cut sub flush { "0 but true" } #------------------------------ =item fileno I No-op, returns undef =cut sub fileno { } #------------------------------ =item getc I Return the next character, or undef if none remain. This does a read(1), which is somewhat costly. =cut sub getc { my $buf = ''; ($_[0]->read($buf, 1) ? $buf : undef); } #------------------------------ =item getline I Return the next line, or undef on end of data. Can safely be called in an array context. Currently, lines are delimited by "\n". =cut sub getline { my $self = shift; my ($str, $line) = (undef, ''); ### Minimal impact implementation! ### We do the fast thing (no regexps) if using the ### classic input record separator. ### Case 1: $/ is undef: slurp all... if (!defined($/)) { return undef if ($self->eof); ### Get the rest of the current string, followed by remaining strings: my $ar = *$self->{AR}; my @slurp = ( substr($ar->[*$self->{Str}], *$self->{Pos}), @$ar[(1 + *$self->{Str}) .. $#$ar ] ); ### Seek to end: $self->_setpos_to_eof; return join('', @slurp); } ### Case 2: $/ is "\n": elsif ($/ eq "\012") { ### Until we hit EOF (or exited because of a found line): until ($self->eof) { ### If at end of current string, go fwd to next one (won't be EOF): if ($self->_eos) {++*$self->{Str}, *$self->{Pos}=0}; ### Get ref to current string in array, and set internal pos mark: $str = \(*$self->{AR}[*$self->{Str}]); ### get current string pos($$str) = *$self->{Pos}; ### start matching from here ### Get from here to either \n or end of string, and add to line: $$str =~ m/\G(.*?)((\n)|\Z)/g; ### match to 1st \n or EOS $line .= $1.$2; ### add it *$self->{Pos} += length($1.$2); ### move fwd by len matched return $line if $3; ### done, got line with "\n" } return ($line eq '') ? undef : $line; ### return undef if EOF } ### Case 3: $/ is ref to int. Bail out. elsif (ref($/)) { croak '$/ given as a ref to int; currently unsupported'; } ### Case 4: $/ is either "" (paragraphs) or something weird... ### Bail for now. else { croak '$/ as given is currently unsupported'; } } #------------------------------ =item getlines I Get all remaining lines. It will croak() if accidentally called in a scalar context. =cut sub getlines { my $self = shift; wantarray or croak("can't call getlines in scalar context!"); my ($line, @lines); push @lines, $line while (defined($line = $self->getline)); @lines; } #------------------------------ =item print ARGS... I Print ARGS to the underlying array. Currently, this always causes a "seek to the end of the array" and generates a new array entry. This may change in the future. =cut sub print { my $self = shift; push @{*$self->{AR}}, join('', @_) . (defined($\) ? $\ : ""); ### add the data $self->_setpos_to_eof; 1; } #------------------------------ =item read BUF, NBYTES, [OFFSET]; I Read some bytes from the array. Returns the number of bytes actually read, 0 on end-of-file, undef on error. =cut sub read { my $self = $_[0]; ### we must use $_[1] as a ref my $n = $_[2]; my $off = $_[3] || 0; ### print "getline\n"; my $justread; my $len; ($off ? substr($_[1], $off) : $_[1]) = ''; ### Stop when we have zero bytes to go, or when we hit EOF: my @got; until (!$n or $self->eof) { ### If at end of current string, go forward to next one (won't be EOF): if ($self->_eos) { ++*$self->{Str}; *$self->{Pos} = 0; } ### Get longest possible desired substring of current string: $justread = substr(*$self->{AR}[*$self->{Str}], *$self->{Pos}, $n); $len = length($justread); push @got, $justread; $n -= $len; *$self->{Pos} += $len; } $_[1] .= join('', @got); return length($_[1])-$off; } #------------------------------ =item write BUF, NBYTES, [OFFSET]; I Write some bytes into the array. =cut sub write { my $self = $_[0]; my $n = $_[2]; my $off = $_[3] || 0; my $data = substr($_[1], $n, $off); $n = length($data); $self->print($data); return $n; } =back =cut #============================== =head2 Seeking/telling and other attributes =over 4 =cut #------------------------------ =item autoflush I No-op, provided for OO compatibility. =cut sub autoflush {} #------------------------------ =item binmode I No-op, provided for OO compatibility. =cut sub binmode {} #------------------------------ =item clearerr I Clear the error and EOF flags. A no-op. =cut sub clearerr { 1 } #------------------------------ =item eof I Are we at end of file? =cut sub eof { ### print "checking EOF [*$self->{Str}, *$self->{Pos}]\n"; ### print "SR = ", $#{*$self->{AR}}, "\n"; return 0 if (*{$_[0]}->{Str} < $#{*{$_[0]}->{AR}}); ### before EOA return 1 if (*{$_[0]}->{Str} > $#{*{$_[0]}->{AR}}); ### after EOA ### ### at EOA, past EOS: ((*{$_[0]}->{Str} == $#{*{$_[0]}->{AR}}) && ($_[0]->_eos)); } #------------------------------ # # _eos # # I Are we at end of the CURRENT string? # sub _eos { (*{$_[0]}->{Pos} >= length(*{$_[0]}->{AR}[*{$_[0]}->{Str}])); ### past last char } #------------------------------ =item seek POS,WHENCE I Seek to a given position in the stream. Only a WHENCE of 0 (SEEK_SET) is supported. =cut sub seek { my ($self, $pos, $whence) = @_; ### Seek: if ($whence == 0) { $self->_seek_set($pos); } elsif ($whence == 1) { $self->_seek_cur($pos); } elsif ($whence == 2) { $self->_seek_end($pos); } else { croak "bad seek whence ($whence)" } return 1; } #------------------------------ # # _seek_set POS # # Instance method, private. # Seek to $pos relative to start: # sub _seek_set { my ($self, $pos) = @_; ### Advance through array until done: my $istr = 0; while (($pos >= 0) && ($istr < scalar(@{*$self->{AR}}))) { if (length(*$self->{AR}[$istr]) > $pos) { ### it's in this string! return $self->setpos([$istr, $pos]); } else { ### it's in next string $pos -= length(*$self->{AR}[$istr++]); ### move forward one string } } ### If we reached this point, pos is at or past end; zoom to EOF: return $self->_setpos_to_eof; } #------------------------------ # # _seek_cur POS # # Instance method, private. # Seek to $pos relative to current position. # sub _seek_cur { my ($self, $pos) = @_; $self->_seek_set($self->tell + $pos); } #------------------------------ # # _seek_end POS # # Instance method, private. # Seek to $pos relative to end. # We actually seek relative to beginning, which is simple. # sub _seek_end { my ($self, $pos) = @_; $self->_seek_set($self->_tell_eof + $pos); } #------------------------------ =item tell I Return the current position in the stream, as a numeric offset. =cut sub tell { my $self = shift; my $off = 0; my ($s, $str_s); for ($s = 0; $s < *$self->{Str}; $s++) { ### count all "whole" scalars defined($str_s = *$self->{AR}[$s]) or $str_s = ''; ###print STDERR "COUNTING STRING $s (". length($str_s) . ")\n"; $off += length($str_s); } ###print STDERR "COUNTING POS ($self->{Pos})\n"; return ($off += *$self->{Pos}); ### plus the final, partial one } #------------------------------ # # _tell_eof # # Instance method, private. # Get position of EOF, as a numeric offset. # This is identical to the size of the stream - 1. # sub _tell_eof { my $self = shift; my $len = 0; foreach (@{*$self->{AR}}) { $len += length($_) } $len; } #------------------------------ =item setpos POS I Seek to a given position in the array, using the opaque getpos() value. Don't expect this to be a number. =cut sub setpos { my ($self, $pos) = @_; (ref($pos) eq 'ARRAY') or die "setpos: only use a value returned by getpos!\n"; (*$self->{Str}, *$self->{Pos}) = @$pos; } #------------------------------ # # _setpos_to_eof # # Fast-forward to EOF. # sub _setpos_to_eof { my $self = shift; $self->setpos([scalar(@{*$self->{AR}}), 0]); } #------------------------------ =item getpos I Return the current position in the array, as an opaque value. Don't expect this to be a number. =cut sub getpos { [*{$_[0]}->{Str}, *{$_[0]}->{Pos}]; } #------------------------------ =item aref I Return a reference to the underlying array. =cut sub aref { *{shift()}->{AR}; } =back =cut #------------------------------ # Tied handle methods... #------------------------------ ### Conventional tiehandle interface: sub TIEHANDLE { (defined($_[1]) && UNIVERSAL::isa($_[1],"IO::ScalarArray")) ? $_[1] : shift->new(@_) } sub GETC { shift->getc(@_) } sub PRINT { shift->print(@_) } sub PRINTF { shift->print(sprintf(shift, @_)) } sub READ { shift->read(@_) } sub READLINE { wantarray ? shift->getlines(@_) : shift->getline(@_) } sub WRITE { shift->write(@_); } sub CLOSE { shift->close(@_); } sub SEEK { shift->seek(@_); } sub TELL { shift->tell(@_); } sub EOF { shift->eof(@_); } sub BINMODE { 1; } #------------------------------------------------------------ 1; __END__ # SOME PRIVATE NOTES: # # * The "current position" is the position before the next # character to be read/written. # # * Str gives the string index of the current position, 0-based # # * Pos gives the offset within AR[Str], 0-based. # # * Inital pos is [0,0]. After print("Hello"), it is [1,0]. =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!perl5/IO/InnerFile.pmnu6$package IO::InnerFile; use strict; use warnings; use Symbol; our $VERSION = '2.113'; sub new { my ($class, $fh, $start, $lg) = @_; $start = 0 if (!$start or ($start < 0)); $lg = 0 if (!$lg or ($lg < 0)); ### Create the underlying "object": my $a = { FH => $fh, CRPOS => 0, START => $start, LG => $lg, }; ### Create a new filehandle tied to this object: $fh = gensym; tie(*$fh, $class, $a); return bless($fh, $class); } sub TIEHANDLE { my ($class, $data) = @_; return bless($data, $class); } sub DESTROY { my ($self) = @_; $self->close() if (ref($self) eq 'SCALAR'); } sub set_length { tied(${$_[0]})->{LG} = $_[1]; } sub get_length { tied(${$_[0]})->{LG}; } sub add_length { tied(${$_[0]})->{LG} += $_[1]; } sub set_start { tied(${$_[0]})->{START} = $_[1]; } sub get_start { tied(${$_[0]})->{START}; } sub set_end { tied(${$_[0]})->{LG} = $_[1] - tied(${$_[0]})->{START}; } sub get_end { tied(${$_[0]})->{LG} + tied(${$_[0]})->{START}; } sub write { shift->WRITE(@_) } sub print { shift->PRINT(@_) } sub printf { shift->PRINTF(@_) } sub flush { "0 but true"; } sub fileno { } sub binmode { 1; } sub getc { return GETC(tied(${$_[0]}) ); } sub read { return READ( tied(${$_[0]}), @_[1,2,3] ); } sub readline { return READLINE( tied(${$_[0]}) ); } sub getline { return READLINE( tied(${$_[0]}) ); } sub close { return CLOSE(tied(${$_[0]}) ); } sub seek { my ($self, $ofs, $whence) = @_; $self = tied( $$self ); $self->{CRPOS} = $ofs if ($whence == 0); $self->{CRPOS}+= $ofs if ($whence == 1); $self->{CRPOS} = $self->{LG} + $ofs if ($whence == 2); $self->{CRPOS} = 0 if ($self->{CRPOS} < 0); $self->{CRPOS} = $self->{LG} if ($self->{CRPOS} > $self->{LG}); return 1; } sub tell { return tied(${$_[0]})->{CRPOS}; } sub WRITE { die "inner files can only open for reading\n"; } sub PRINT { die "inner files can only open for reading\n"; } sub PRINTF { die "inner files can only open for reading\n"; } sub GETC { my ($self) = @_; return 0 if ($self->{CRPOS} >= $self->{LG}); my $data; ### Save and seek... my $old_pos = $self->{FH}->tell; $self->{FH}->seek($self->{CRPOS}+$self->{START}, 0); ### ...read... my $lg = $self->{FH}->read($data, 1); $self->{CRPOS} += $lg; ### ...and restore: $self->{FH}->seek($old_pos, 0); $self->{LG} = $self->{CRPOS} unless ($lg); return ($lg ? $data : undef); } sub READ { my ($self, $undefined, $lg, $ofs) = @_; $undefined = undef; return 0 if ($self->{CRPOS} >= $self->{LG}); $lg = $self->{LG} - $self->{CRPOS} if ($self->{CRPOS} + $lg > $self->{LG}); return 0 unless ($lg); ### Save and seek... my $old_pos = $self->{FH}->tell; $self->{FH}->seek($self->{CRPOS}+$self->{START}, 0); ### ...read... $lg = $self->{FH}->read($_[1], $lg, $_[3] ); $self->{CRPOS} += $lg; ### ...and restore: $self->{FH}->seek($old_pos, 0); $self->{LG} = $self->{CRPOS} unless ($lg); return $lg; } sub READLINE { my ($self) = @_; return $self->_readline_helper() unless wantarray; my @arr; while(defined(my $line = $self->_readline_helper())) { push(@arr, $line); } return @arr; } sub _readline_helper { my ($self) = @_; return undef if ($self->{CRPOS} >= $self->{LG}); # Handle slurp mode (CPAN ticket #72710) if (! defined($/)) { my $text; $self->READ($text, $self->{LG} - $self->{CRPOS}); return $text; } ### Save and seek... my $old_pos = $self->{FH}->tell; $self->{FH}->seek($self->{CRPOS}+$self->{START}, 0); ### ...read... my $text = $self->{FH}->getline; ### ...and restore: $self->{FH}->seek($old_pos, 0); #### If we detected a new EOF ... unless (defined $text) { $self->{LG} = $self->{CRPOS}; return undef; } my $lg=length($text); $lg = $self->{LG} - $self->{CRPOS} if ($self->{CRPOS} + $lg > $self->{LG}); $self->{CRPOS} += $lg; return substr($text, 0,$lg); } sub CLOSE { %{$_[0]}=(); } 1; __END__ __END__ =head1 NAME IO::InnerFile - define a file inside another file =head1 SYNOPSIS use strict; use warnings; use IO::InnerFile; # Read a subset of a file: my $fh = _some_file_handle; my $start = 10; my $length = 50; my $inner = IO::InnerFile->new($fh, $start, $length); while (my $line = <$inner>) { # ... } =head1 DESCRIPTION If you have a file handle that can C and C, then you can open an L on a range of the underlying file. =head1 CONSTRUCTORS L implements the following constructors. =head2 new my $inner = IO::InnerFile->new($fh); $inner = IO::InnerFile->new($fh, 10); $inner = IO::InnerFile->new($fh, 10, 50); Create a new L opened on the given file handle. The file handle supplied B be able to both C and C. The second and third parameters are start and length. Both are defaulted to zero (C<0>). Negative values are silently coerced to zero. =head1 METHODS L implements the following methods. =head2 add_length $inner->add_length(30); Add to the virtual length of the inner file by the number given in bytes. =head2 add_start $inner->add_start(30); Add to the virtual position of the inner file by the number given in bytes. =head2 binmode $inner->binmode(); This is a NOOP method just to satisfy the normal L interface. =head2 close =head2 fileno $inner->fileno(); This is a NOOP method just to satisfy the normal L interface. =head2 flush $inner->flush(); This is a NOOP method just to satisfy the normal L interface. =head2 get_end my $num_bytes = $inner->get_end(); Get the virtual end position of the inner file in bytes. =head2 get_length my $num_bytes = $inner->get_length(); Get the virtual length of the inner file in bytes. =head2 get_start my $num_bytes = $inner->get_start(); Get the virtual position of the inner file in bytes. =head2 getc =head2 getline =head2 print LIST =head2 printf =head2 read =head2 readline =head2 seek =head2 set_end $inner->set_end(30); Set the virtual end of the inner file in bytes (this basically just alters the length). =head2 set_length $inner->set_length(30); Set the virtual length of the inner file in bytes. =head2 set_start $inner->set_start(30); Set the virtual start position of the inner file in bytes. =head2 tell =head2 write =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!rperl5/IO/AtomicFile.pmnu6$package IO::AtomicFile; use strict; use warnings; use parent 'IO::File'; our $VERSION = '2.113'; #------------------------------ # new ARGS... #------------------------------ # Class method, constructor. # Any arguments are sent to open(). # sub new { my $class = shift; my $self = $class->SUPER::new(); ${*$self}{'io_atomicfile_suffix'} = ''; $self->open(@_) if @_; $self; } #------------------------------ # DESTROY #------------------------------ # Destructor. # sub DESTROY { shift->close(1); ### like close, but raises fatal exception on failure } #------------------------------ # open PATH, MODE #------------------------------ # Class/instance method. # sub open { my ($self, $path, $mode) = @_; ref($self) or $self = $self->new; ### now we have an instance! ### Create tmp path, and remember this info: my $temp = "${path}..TMP" . ${*$self}{'io_atomicfile_suffix'}; ${*$self}{'io_atomicfile_temp'} = $temp; ${*$self}{'io_atomicfile_path'} = $path; ### Open the file! Returns filehandle on success, for use as a constructor: $self->SUPER::open($temp, $mode) ? $self : undef; } #------------------------------ # _closed [YESNO] #------------------------------ # Instance method, private. # Are we already closed? Argument sets new value, returns previous one. # sub _closed { my $self = shift; my $oldval = ${*$self}{'io_atomicfile_closed'}; ${*$self}{'io_atomicfile_closed'} = shift if @_; $oldval; } #------------------------------ # close #------------------------------ # Instance method. # Close the handle, and rename the temp file to its final name. # sub close { my ($self, $die) = @_; unless ($self->_closed(1)) { ### sentinel... if ($self->SUPER::close()) { rename(${*$self}{'io_atomicfile_temp'}, ${*$self}{'io_atomicfile_path'}) or ($die ? die "close (rename) atomic file: $!\n" : return undef); } else { ($die ? die "close atomic file: $!\n" : return undef); } } 1; } #------------------------------ # delete #------------------------------ # Instance method. # Close the handle, and delete the temp file. # sub delete { my $self = shift; unless ($self->_closed(1)) { ### sentinel... $self->SUPER::close(); return unlink(${*$self}{'io_atomicfile_temp'}); } 1; } #------------------------------ # detach #------------------------------ # Instance method. # Close the handle, but DO NOT delete the temp file. # sub detach { my $self = shift; $self->SUPER::close() unless ($self->_closed(1)); 1; } #------------------------------ 1; __END__ =head1 NAME IO::AtomicFile - write a file which is updated atomically =head1 SYNOPSIS use strict; use warnings; use feature 'say'; use IO::AtomicFile; # Write a temp file, and have it install itself when closed: my $fh = IO::AtomicFile->open("bar.dat", "w"); $fh->say("Hello!"); $fh->close || die "couldn't install atomic file: $!"; # Write a temp file, but delete it before it gets installed: my $fh = IO::AtomicFile->open("bar.dat", "w"); $fh->say("Hello!"); $fh->delete; # Write a temp file, but neither install it nor delete it: my $fh = IO::AtomicFile->open("bar.dat", "w"); $fh->say("Hello!"); $fh->detach; =head1 DESCRIPTION This module is intended for people who need to update files reliably in the face of unexpected program termination. For example, you generally don't want to be halfway in the middle of writing I and have your program terminate! Even the act of writing a single scalar to a filehandle is I atomic. But this module gives you true atomic updates, via C. When you open a file I via this module, you are I opening a temporary file I, and writing your output there. The act of closing this file (either explicitly via C, or implicitly via the destruction of the object) will cause C to be called... therefore, from the point of view of the outside world, the file's contents are updated in a single time quantum. To ensure that problems do not go undetected, the C method done by the destructor will raise a fatal exception if the C fails. The explicit C just returns C. You can also decide at any point to trash the file you've been building. =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 close $fh->close(); This method calls its parent L and then renames its temporary file as the original file name. =head2 delete $fh->delete(); This method calls its parent L and then deletes the temporary file. =head2 detach $fh->detach(); This method calls its parent L. Unlike L it does not then delete the temporary file. =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!*2U"U"perl5/IO/Wrap.pmnu6$package IO::Wrap; use strict; use Exporter; use FileHandle; use Carp; our $VERSION = '2.113'; our @ISA = qw(Exporter); our @EXPORT = qw(wraphandle); #------------------------------ # wraphandle RAW #------------------------------ sub wraphandle { my $raw = shift; new IO::Wrap $raw; } #------------------------------ # new STREAM #------------------------------ sub new { my ($class, $stream) = @_; no strict 'refs'; ### Convert raw scalar to globref: ref($stream) or $stream = \*$stream; ### Wrap globref and incomplete objects: if ((ref($stream) eq 'GLOB') or ### globref (ref($stream) eq 'FileHandle') && !defined(&FileHandle::read)) { return bless \$stream, $class; } $stream; ### already okay! } #------------------------------ # I/O methods... #------------------------------ sub close { my $self = shift; return close($$self); } sub fileno { my $self = shift; my $fh = $$self; return fileno($fh); } sub getline { my $self = shift; my $fh = $$self; return scalar(<$fh>); } sub getlines { my $self = shift; wantarray or croak("Can't call getlines in scalar context!"); my $fh = $$self; <$fh>; } sub print { my $self = shift; print { $$self } @_; } sub read { my $self = shift; return read($$self, $_[0], $_[1]); } sub seek { my $self = shift; return seek($$self, $_[0], $_[1]); } sub tell { my $self = shift; return tell($$self); } 1; __END__ =head1 NAME IO::Wrap - Wrap raw filehandles in the IO::Handle interface =head1 SYNOPSIS use strict; use warnings; use IO::Wrap; # this is a fairly senseless use case as IO::Handle already does this. my $wrap_fh = IO::Wrap->new(\*STDIN); my $line = $wrap_fh->getline(); # Do stuff with any kind of filehandle (including a bare globref), or # any kind of blessed object that responds to a print() message. # already have a globref? a FileHandle? a scalar filehandle name? $wrap_fh = IO::Wrap->new($some_unknown_thing); # At this point, we know we have an IO::Handle-like object! YAY $wrap_fh->print("Hey there!"); You can also do this using a convenience wrapper function use strict; use warnings; use IO::Wrap qw(wraphandle); # this is a fairly senseless use case as IO::Handle already does this. my $wrap_fh = wraphandle(\*STDIN); my $line = $wrap_fh->getline(); # Do stuff with any kind of filehandle (including a bare globref), or # any kind of blessed object that responds to a print() message. # already have a globref? a FileHandle? a scalar filehandle name? $wrap_fh = wraphandle($some_unknown_thing); # At this point, we know we have an IO::Handle-like object! YAY $wrap_fh->print("Hey there!"); =head1 DESCRIPTION Let's say you want to write some code which does I/O, but you don't want to force the caller to provide you with a L or L object. You want them to be able to say: do_stuff(\*STDOUT); do_stuff('STDERR'); do_stuff($some_FileHandle_object); do_stuff($some_IO_Handle_object); And even: do_stuff($any_object_with_a_print_method); Sure, one way to do it is to force the caller to use C. But that puts the burden on them. Another way to do it is to use B. Clearly, when wrapping a raw external filehandle (like C<\*STDOUT>), I didn't want to close the file descriptor when the wrapper object is destroyed; the user might not appreciate that! Hence, there's no C method in this class. When wrapping a L object, however, I believe that Perl will invoke the C when the last reference goes away, so in that case, the filehandle is closed if the wrapped L really was the last reference to it. =head1 FUNCTIONS L makes the following functions available. =head2 wraphandle # wrap a filehandle glob my $fh = wraphandle(\*STDIN); # wrap a raw filehandle glob by name $fh = wraphandle('STDIN'); # wrap a handle in an object $fh = wraphandle('Class::HANDLE'); # wrap a blessed FileHandle object use FileHandle; my $fho = FileHandle->new("/tmp/foo.txt", "r"); $fh = wraphandle($fho); # wrap any other blessed object that shares IO::Handle's interface $fh = wraphandle($some_object); This function is simply a wrapper to the L constructor method. =head1 METHODS L implements the following methods. =head2 close $fh->close(); The C method will attempt to close the system file descriptor. For a more complete description, read L. =head2 fileno my $int = $fh->fileno(); The C method returns the file descriptor for the wrapped filehandle. See L for more information. =head2 getline my $data = $fh->getline(); The C method mimics the function by the same name in L. It's like calling C<< my $data = <$fh>; >> but only in scalar context. =head2 getlines my @data = $fh->getlines(); The C method mimics the function by the same name in L. It's like calling C<< my @data = <$fh>; >> but only in list context. Calling this method in scalar context will result in a croak. =head2 new # wrap a filehandle glob my $fh = IO::Wrap->new(\*STDIN); # wrap a raw filehandle glob by name $fh = IO::Wrap->new('STDIN'); # wrap a handle in an object $fh = IO::Wrap->new('Class::HANDLE'); # wrap a blessed FileHandle object use FileHandle; my $fho = FileHandle->new("/tmp/foo.txt", "r"); $fh = IO::Wrap->new($fho); # wrap any other blessed object that shares IO::Handle's interface $fh = IO::Wrap->new($some_object); The C constructor method takes in a single argument and decides to wrap it or not it based on what it seems to be. A raw scalar file handle name, like C<"STDOUT"> or C<"Class::HANDLE"> can be wrapped, returning an L object instance. A raw filehandle glob, like C<\*STDOUT> can also be wrapped, returning an L object instance. A blessed L object can also be wrapped. This is a special case where an L object instance will only be returned in the case that your L object doesn't support the C method. Also, any other kind of blessed object that conforms to the L interface can be passed in. In this case, you just get back that object. In other words, we only wrap it into an L object when what you've supplied doesn't already conform to the L interface. If you get back an L object, it will obey a basic subset of the C interface. It will do so with object B, not B. =head3 CAVEATS This module does not allow you to wrap filehandle names which are given as strings that lack the package they were opened in. That is, if a user opens FOO in package Foo, they must pass it to you either as C<\*FOO> or as C<"Foo::FOO">. However, C<"STDIN"> and friends will work just fine. =head2 print $fh->print("Some string"); $fh->print("more", " than one", " string"); The C method will attempt to print a string or list of strings to the filehandle. For a more complete description, read L. =head2 read my $buffer; # try to read 30 chars into the buffer starting at the # current cursor position. my $num_chars_read = $fh->read($buffer, 30); The L method attempts to read a number of characters, starting at the filehandle's current cursor position. It returns the number of characters actually read. See L for more information. =head2 seek use Fcntl qw(:seek); # import the SEEK_CUR, SEEK_SET, SEEK_END constants # seek to the position in bytes $fh->seek(0, SEEK_SET); # seek to the position in bytes from the current position $fh->seek(22, SEEK_CUR); # seek to the EOF plus bytes $fh->seek(0, SEEK_END); The C method will attempt to set the cursor to a given position in bytes for the wrapped file handle. See L for more information. =head2 tell my $bytes = $fh->tell(); The C method will attempt to return the current position of the cursor in bytes for the wrapped file handle. See L for more information. =head1 AUTHOR Eryq (F). President, ZeeGee Software Inc (F). =head1 CONTRIBUTORS Dianne Skoll (F). =head1 COPYRIGHT & LICENSE Copyright (c) 1997 Erik (Eryq) Dorfman, ZeeGee Software, Inc. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut PK!nP perl5/Time/Zone.pmnu6$ package Time::Zone; =head1 NAME Time::Zone -- miscellaneous timezone manipulations routines =head1 SYNOPSIS use Time::Zone; print tz2zone(); print tz2zone($ENV{'TZ'}); print tz2zone($ENV{'TZ'}, time()); print tz2zone($ENV{'TZ'}, undef, $isdst); $offset = tz_local_offset(); $offset = tz_offset($TZ); =head1 DESCRIPTION This is a collection of miscellaneous timezone manipulation routines. C parses the TZ environment variable and returns a timezone string suitable for inclusion in L-like output. It opionally takes a timezone string, a time, and a is-dst flag. C determins the offset from GMT time in seconds. It only does the calculation once. C determines the offset from GMT in seconds of a specified timezone. C determines the name of the timezone based on its offset =head1 AUTHORS Graham Barr David Muir Sharnoff Paul Foley =cut require 5.002; require Exporter; use Carp; use strict; use vars qw(@ISA @EXPORT $VERSION @tz_local); @ISA = qw(Exporter); @EXPORT = qw(tz2zone tz_local_offset tz_offset tz_name); $VERSION = "2.24"; # Parts stolen from code by Paul Foley sub tz2zone (;$$$) { my($TZ, $time, $isdst) = @_; use vars qw(%tzn_cache); $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : '' unless $TZ; # Hack to deal with 'PST8PDT' format of TZ # Note that this can't deal with all the esoteric forms, but it # does recognize the most common: [:]STDoff[DST[off][,rule]] if (! defined $isdst) { my $j; $time = time() unless $time; ($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time); } if (defined $tzn_cache{$TZ}->[$isdst]) { return $tzn_cache{$TZ}->[$isdst]; } if ($TZ =~ /^ ( [^:\d+\-,] {3,} ) ( [+-] ? \d {1,2} ( : \d {1,2} ) {0,2} ) ( [^\d+\-,] {3,} )? /x ) { my $dsttz = defined($4) ? $4 : $1; $TZ = $isdst ? $dsttz : $1; $tzn_cache{$TZ} = [ $1, $dsttz ]; } else { $tzn_cache{$TZ} = [ $TZ, $TZ ]; } return $TZ; } sub tz_local_offset (;$) { my ($time) = @_; $time = time() unless $time; my (@l) = localtime($time); my $isdst = $l[8]; if (defined($tz_local[$isdst])) { return $tz_local[$isdst]; } $tz_local[$isdst] = &calc_off($time); return $tz_local[$isdst]; } sub calc_off { my ($time) = @_; my (@l) = localtime($time); my (@g) = gmtime($time); my $off; $off = $l[0] - $g[0] + ($l[1] - $g[1]) * 60 + ($l[2] - $g[2]) * 3600; # subscript 7 is yday. if ($l[7] == $g[7]) { # done } elsif ($l[7] == $g[7] + 1) { $off += 86400; } elsif ($l[7] == $g[7] - 1) { $off -= 86400; } elsif ($l[7] < $g[7]) { # crossed over a year boundry! # localtime is beginning of year, gmt is end # therefore local is ahead $off += 86400; } else { $off -= 86400; } return $off; } # constants CONFIG: { use vars qw(%dstZone %zoneOff %dstZoneOff %Zone); my @dstZone = ( # "ndt" => -2*3600-1800, # Newfoundland Daylight "brst" => -2*3600, # Brazil Summer Time (East Daylight) "adt" => -3*3600, # Atlantic Daylight "edt" => -4*3600, # Eastern Daylight "cdt" => -5*3600, # Central Daylight "mdt" => -6*3600, # Mountain Daylight "pdt" => -7*3600, # Pacific Daylight "akdt" => -8*3600, # Alaska Daylight "ydt" => -8*3600, # Yukon Daylight "hdt" => -9*3600, # Hawaii Daylight "bst" => +1*3600, # British Summer "mest" => +2*3600, # Middle European Summer "metdst" => +2*3600, # Middle European DST "sst" => +2*3600, # Swedish Summer "fst" => +2*3600, # French Summer "cest" => +2*3600, # Central European Daylight "eest" => +3*3600, # Eastern European Summer "msd" => +4*3600, # Moscow Daylight "wadt" => +8*3600, # West Australian Daylight "kdt" => +10*3600, # Korean Daylight # "cadt" => +10*3600+1800, # Central Australian Daylight "aedt" => +11*3600, # Eastern Australian Daylight "eadt" => +11*3600, # Eastern Australian Daylight "nzd" => +13*3600, # New Zealand Daylight "nzdt" => +13*3600, # New Zealand Daylight ); my @Zone = ( "gmt" => 0, # Greenwich Mean "ut" => 0, # Universal (Coordinated) "utc" => 0, "wet" => 0, # Western European "wat" => -1*3600, # West Africa "at" => -2*3600, # Azores "fnt" => -2*3600, # Brazil Time (Extreme East - Fernando Noronha) "brt" => -3*3600, # Brazil Time (East Standard - Brasilia) # For completeness. BST is also British Summer, and GST is also Guam Standard. # "bst" => -3*3600, # Brazil Standard # "gst" => -3*3600, # Greenland Standard # "nft" => -3*3600-1800,# Newfoundland # "nst" => -3*3600-1800,# Newfoundland Standard "mnt" => -4*3600, # Brazil Time (West Standard - Manaus) "ewt" => -4*3600, # U.S. Eastern War Time "ast" => -4*3600, # Atlantic Standard "est" => -5*3600, # Eastern Standard "act" => -5*3600, # Brazil Time (Extreme West - Acre) "cst" => -6*3600, # Central Standard "mst" => -7*3600, # Mountain Standard "pst" => -8*3600, # Pacific Standard "akst" => -9*3600, # Alaska Standard "yst" => -9*3600, # Yukon Standard "hst" => -10*3600, # Hawaii Standard "cat" => -10*3600, # Central Alaska "ahst" => -10*3600, # Alaska-Hawaii Standard "nt" => -11*3600, # Nome "idlw" => -12*3600, # International Date Line West "cet" => +1*3600, # Central European "mez" => +1*3600, # Central European (German) "ect" => +1*3600, # Central European (French) "met" => +1*3600, # Middle European "mewt" => +1*3600, # Middle European Winter "swt" => +1*3600, # Swedish Winter "set" => +1*3600, # Seychelles "fwt" => +1*3600, # French Winter "eet" => +2*3600, # Eastern Europe, USSR Zone 1 "ukr" => +2*3600, # Ukraine "bt" => +3*3600, # Baghdad, USSR Zone 2 "msk" => +3*3600, # Moscow # "it" => +3*3600+1800,# Iran "zp4" => +4*3600, # USSR Zone 3 "zp5" => +5*3600, # USSR Zone 4 # "ist" => +5*3600+1800,# Indian Standard "zp6" => +6*3600, # USSR Zone 5 # For completeness. NST is also Newfoundland Stanard, and SST is also Swedish Summer. # "nst" => +6*3600+1800,# North Sumatra # "sst" => +7*3600, # South Sumatra, USSR Zone 6 # "jt" => +7*3600+1800,# Java (3pm in Cronusland!) "wst" => +8*3600, # West Australian Standard "hkt" => +8*3600, # Hong Kong "cct" => +8*3600, # China Coast, USSR Zone 7 "jst" => +9*3600, # Japan Standard, USSR Zone 8 "kst" => +9*3600, # Korean Standard # "cast" => +9*3600+1800,# Central Australian Standard "aest" => +10*3600, # Eastern Australian Standard "east" => +10*3600, # Eastern Australian Standard "gst" => +10*3600, # Guam Standard, USSR Zone 9 "nzt" => +12*3600, # New Zealand "nzst" => +12*3600, # New Zealand Standard "idle" => +12*3600, # International Date Line East ); %Zone = @Zone; %dstZone = @dstZone; %zoneOff = reverse(@Zone); %dstZoneOff = reverse(@dstZone); } sub tz_offset (;$$) { my ($zone, $time) = @_; return &tz_local_offset($time) unless($zone); $time = time() unless $time; my(@l) = localtime($time); my $dst = $l[8]; $zone = lc $zone; if($zone =~ /^(([\-\+])\d\d?)(\d\d)$/) { my $v = $2 . $3; return $1 * 3600 + $v * 60; } elsif (exists $dstZone{$zone} && ($dst || !exists $Zone{$zone})) { return $dstZone{$zone}; } elsif(exists $Zone{$zone}) { return $Zone{$zone}; } undef; } sub tz_name (;$$) { my ($off, $dst) = @_; $off = tz_offset() unless(defined $off); $dst = (localtime(time))[8] unless(defined $dst); if (exists $dstZoneOff{$off} && ($dst || !exists $zoneOff{$off})) { return $dstZoneOff{$off}; } elsif (exists $zoneOff{$off}) { return $zoneOff{$off}; } sprintf("%+05d", int($off / 60) * 100 + $off % 60); } 1; PK!E1$1$perl5/lwpcook.podnu6$=head1 NAME lwpcook - The libwww-perl cookbook =head1 DESCRIPTION This document contain some examples that show typical usage of the libwww-perl library. You should consult the documentation for the individual modules for more detail. All examples should be runnable programs. You can, in most cases, test the code sections by piping the program text directly to perl. =head1 GET It is very easy to use this library to just fetch documents from the net. The LWP::Simple module provides the get() function that return the document specified by its URL argument: use LWP::Simple; $doc = get 'http://search.cpan.org/dist/libwww-perl/'; or, as a perl one-liner using the getprint() function: perl -MLWP::Simple -e 'getprint "http://search.cpan.org/dist/libwww-perl/"' or, how about fetching the latest perl by running this command: perl -MLWP::Simple -e ' getstore "ftp://ftp.sunet.se/pub/lang/perl/CPAN/src/latest.tar.gz", "perl.tar.gz"' You will probably first want to find a CPAN site closer to you by running something like the following command: perl -MLWP::Simple -e 'getprint "http://www.cpan.org/SITES.html"' Enough of this simple stuff! The LWP object oriented interface gives you more control over the request sent to the server. Using this interface you have full control over headers sent and how you want to handle the response returned. use LWP::UserAgent; $ua = LWP::UserAgent->new; $ua->agent("$0/0.1 " . $ua->agent); # $ua->agent("Mozilla/8.0") # pretend we are very capable browser $req = HTTP::Request->new( GET => 'http://search.cpan.org/dist/libwww-perl/'); $req->header('Accept' => 'text/html'); # send request $res = $ua->request($req); # check the outcome if ($res->is_success) { print $res->decoded_content; } else { print "Error: " . $res->status_line . "\n"; } The lwp-request program (alias GET) that is distributed with the library can also be used to fetch documents from WWW servers. =head1 HEAD If you just want to check if a document is present (i.e. the URL is valid) try to run code that looks like this: use LWP::Simple; if (head($url)) { # ok document exists } The head() function really returns a list of meta-information about the document. The first three values of the list returned are the document type, the size of the document, and the age of the document. More control over the request or access to all header values returned require that you use the object oriented interface described for GET above. Just s/GET/HEAD/g. =head1 POST There is no simple procedural interface for posting data to a WWW server. You must use the object oriented interface for this. The most common POST operation is to access a WWW form application: use LWP::UserAgent; $ua = LWP::UserAgent->new; my $req = HTTP::Request->new( POST => 'https://rt.cpan.org/Public/Dist/Display.html'); $req->content_type('application/x-www-form-urlencoded'); $req->content('Status=Active&Name=libwww-perl'); my $res = $ua->request($req); print $res->as_string; Lazy people use the HTTP::Request::Common module to set up a suitable POST request message (it handles all the escaping issues) and has a suitable default for the content_type: use HTTP::Request::Common qw(POST); use LWP::UserAgent; $ua = LWP::UserAgent->new; my $req = POST 'https://rt.cpan.org/Public/Dist/Display.html', [ Status => 'Active', Name => 'libwww-perl' ]; print $ua->request($req)->as_string; The lwp-request program (alias POST) that is distributed with the library can also be used for posting data. =head1 PROXIES Some sites use proxies to go through fire wall machines, or just as cache in order to improve performance. Proxies can also be used for accessing resources through protocols not supported directly (or supported badly :-) by the libwww-perl library. You should initialize your proxy setting before you start sending requests: use LWP::UserAgent; $ua = LWP::UserAgent->new; $ua->env_proxy; # initialize from environment variables # or $ua->proxy(ftp => 'http://proxy.myorg.com'); $ua->proxy(wais => 'http://proxy.myorg.com'); $ua->no_proxy(qw(no se fi)); my $req = HTTP::Request->new(GET => 'wais://xxx.com/'); print $ua->request($req)->as_string; The LWP::Simple interface will call env_proxy() for you automatically. Applications that use the $ua->env_proxy() method will normally not use the $ua->proxy() and $ua->no_proxy() methods. Some proxies also require that you send it a username/password in order to let requests through. You should be able to add the required header, with something like this: use LWP::UserAgent; $ua = LWP::UserAgent->new; $ua->proxy(['http', 'ftp'] => 'http://username:password@proxy.myorg.com'); $req = HTTP::Request->new('GET',"http://www.perl.com"); $res = $ua->request($req); print $res->decoded_content if $res->is_success; Replace C, C and C with something suitable for your site. =head1 ACCESS TO PROTECTED DOCUMENTS Documents protected by basic authorization can easily be accessed like this: use LWP::UserAgent; $ua = LWP::UserAgent->new; $req = HTTP::Request->new(GET => 'http://www.linpro.no/secret/'); $req->authorization_basic('aas', 'mypassword'); print $ua->request($req)->as_string; The other alternative is to provide a subclass of I that overrides the get_basic_credentials() method. Study the I program for an example of this. =head1 COOKIES Some sites like to play games with cookies. By default LWP ignores cookies provided by the servers it visits. LWP will collect cookies and respond to cookie requests if you set up a cookie jar. LWP doesn't provide a cookie jar itself, but if you install L, it can be used like this: use LWP::UserAgent; use HTTP::CookieJar::LWP; $ua = LWP::UserAgent->new( cookie_jar => HTTP::CookieJar::LWP->new, ); # and then send requests just as you used to do $res = $ua->request(HTTP::Request->new(GET => "http://no.yahoo.com/")); print $res->status_line, "\n"; =head1 HTTPS URLs with https scheme are accessed in exactly the same way as with http scheme, provided that an SSL interface module for LWP has been properly installed (see the F file found in the libwww-perl distribution for more details). If no SSL interface is installed for LWP to use, then you will get "501 Protocol scheme 'https' is not supported" errors when accessing such URLs. Here's an example of fetching and printing a WWW page using SSL: use LWP::UserAgent; my $ua = LWP::UserAgent->new; my $req = HTTP::Request->new(GET => 'https://www.helsinki.fi/'); my $res = $ua->request($req); if ($res->is_success) { print $res->as_string; } else { print "Failed: ", $res->status_line, "\n"; } =head1 MIRRORING If you want to mirror documents from a WWW server, then try to run code similar to this at regular intervals: use LWP::Simple; %mirrors = ( 'http://www.sn.no/' => 'sn.html', 'http://www.perl.com/' => 'perl.html', 'http://search.cpan.org/distlibwww-perl/' => 'lwp.html', 'gopher://gopher.sn.no/' => 'gopher.html', ); while (($url, $localfile) = each(%mirrors)) { mirror($url, $localfile); } Or, as a perl one-liner: perl -MLWP::Simple -e 'mirror("http://www.perl.com/", "perl.html")'; The document will not be transferred unless it has been updated. =head1 LARGE DOCUMENTS If the document you want to fetch is too large to be kept in memory, then you have two alternatives. You can instruct the library to write the document content to a file (second $ua->request() argument is a file name): use LWP::UserAgent; $ua = LWP::UserAgent->new; my $req = HTTP::Request->new(GET => 'http://www.cpan.org/CPAN/authors/id/O/OA/OALDERS/libwww-perl-6.26.tar.gz'); $res = $ua->request($req, "libwww-perl.tar.gz"); if ($res->is_success) { print "ok\n"; } else { print $res->status_line, "\n"; } Or you can process the document as it arrives (second $ua->request() argument is a code reference): use LWP::UserAgent; $ua = LWP::UserAgent->new; $URL = 'ftp://ftp.isc.org/pub/rfc/rfc-index.txt'; my $expected_length; my $bytes_received = 0; my $res = $ua->request(HTTP::Request->new(GET => $URL), sub { my($chunk, $res) = @_; $bytes_received += length($chunk); unless (defined $expected_length) { $expected_length = $res->content_length || 0; } if ($expected_length) { printf STDERR "%d%% - ", 100 * $bytes_received / $expected_length; } print STDERR "$bytes_received bytes received\n"; # XXX Should really do something with the chunk itself # print $chunk; }); print $res->status_line, "\n"; =head1 COPYRIGHT Copyright 1996-2001, Gisle Aas This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. PK!Fperl5/x86_64-linux-thread-multi/.meta/WWW-RobotRules-6.02/install.jsonnu6${"pathname":"G/GA/GAAS/WWW-RobotRules-6.02.tar.gz","name":"WWW::RobotRules","version":6.02,"dist":"WWW-RobotRules-6.02","target":"WWW::RobotRules","provides":{"WWW::RobotRules":{"version":6.02,"file":"lib/WWW/RobotRules.pm"},"WWW::RobotRules::InCore":{"version":6.02,"file":"lib/WWW/RobotRules.pm"},"WWW::RobotRules::AnyDBM_File":{"file":"lib/WWW/RobotRules/AnyDBM_File.pm","version":"6.00"}}}PK!<Eperl5/x86_64-linux-thread-multi/.meta/WWW-RobotRules-6.02/MYMETA.jsonnu6${ "abstract" : "database of robots.txt-derived permissions", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 6.57_05, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "WWW-RobotRules", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "AnyDBM_File" : "0", "Fcntl" : "0", "URI" : "1.10", "perl" : "5.008001" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "http://github.com/gisle/www-robotrules" }, "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.02", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!- #nnDperl5/x86_64-linux-thread-multi/.meta/XML-SAX-Base-1.09/install.jsonnu6${"name":"XML::SAX::Base","pathname":"G/GR/GRANTM/XML-SAX-Base-1.09.tar.gz","version":1.09,"dist":"XML-SAX-Base-1.09","target":"XML::SAX::Base","provides":{"XML::SAX::Base":{"file":"lib/XML/SAX/Base.pm","version":1.09},"XML::SAX::Base::NoHandler":{"file":"lib/XML/SAX/Base.pm","version":1.09},"XML::SAX::Exception":{"version":1.09,"file":"lib/XML/SAX/Exception.pm"}}}PK!GCperl5/x86_64-linux-thread-multi/.meta/XML-SAX-Base-1.09/MYMETA.jsonnu6${ "abstract" : "Base class for SAX Drivers and Filters", "author" : [ "Grant McLean " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 5.022, CPAN::Meta::Converter version 2.142690, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-SAX-Base", "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "perl" : "5.008" } }, "test" : { "requires" : { "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "git://github.com/grantm/XML-SAX-Base.git", "web" : "https://github.com/grantm/XML-SAX-Base" } }, "version" : "1.09", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!=Aperl5/x86_64-linux-thread-multi/.meta/HTTP-Date-6.06/install.jsonnu6${"dist":"HTTP-Date-6.06","version":6.06,"pathname":"O/OA/OALDERS/HTTP-Date-6.06.tar.gz","name":"HTTP::Date","provides":{"HTTP::Date":{"version":6.06,"file":"lib/HTTP/Date.pm"}},"target":"HTTP::Date"}PK!OSww@perl5/x86_64-linux-thread-multi/.meta/HTTP-Date-6.06/MYMETA.jsonnu6${ "abstract" : "HTTP::Date - date conversion routines", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTTP-Date", "no_index" : { "directory" : [ "examples", "t", "xt" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "recommends" : { "Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007" }, "requires" : { "Code::TidyAll" : "0.71", "Code::TidyAll::Plugin::SortLines::Naturally" : "0.000003", "Code::TidyAll::Plugin::Test::Vars" : "0.04", "Code::TidyAll::Plugin::UniqueLines" : "0.000003", "Encode" : "0", "Parallel::ForkManager" : "1.19", "Perl::Critic" : "1.132", "Perl::Tidy" : "20180220", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Test::CPAN::Changes" : "0.19", "Test::EOL" : "0", "Test::Mojibake" : "0", "Test::More" : "0.96", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Vars" : "0.014", "Test::Version" : "1", "warnings" : "0" } }, "runtime" : { "requires" : { "Exporter" : "0", "Time::Local" : "1.28", "Time::Zone" : "0", "perl" : "5.006002", "strict" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Test::More" : "0", "perl" : "5.006002", "warnings" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/HTTP-Date/issues" }, "homepage" : "https://github.com/libwww-perl/HTTP-Date", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/HTTP-Date.git", "web" : "https://github.com/libwww-perl/HTTP-Date" }, "x_IRC" : "irc://irc.perl.org/#lwp", "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.06", "x_Dist_Zilla" : { "perl" : { "version" : "5.036000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 0, "check_all_prereqs" : 0, "modules" : [ "Dist::Zilla::PluginBundle::Author::OALDERS" ], "phase" : "build", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, build", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 1, "check_all_prereqs" : 1, "modules" : [], "phase" : "release", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, release", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "@Author::OALDERS/AutoPrereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "@Author::OALDERS/CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/MakeMaker", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CPANFile", "name" : "@Author::OALDERS/CPANFile", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::ContributorsFile", "name" : "@Author::OALDERS/ContributorsFile", "version" : "0.3.0" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Author::OALDERS/MetaJSON", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Author::OALDERS/MetaYAML", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Author::OALDERS/Manifest", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Author::OALDERS/MetaNoIndex", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Author::OALDERS/MetaConfig", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "@Author::OALDERS/MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Author::OALDERS/License", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::OALDERS/InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "requires" } }, "name" : "@Author::OALDERS/Modules for use with tidyall", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "@Author::OALDERS/ExecDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::PodSpelling", "config" : { "Dist::Zilla::Plugin::Test::PodSpelling" : { "directories" : [ "bin", "lib" ], "spell_cmd" : "", "stopwords" : [ "Alders", "Alders'", "TZ", "YYYY" ], "wordlist" : "Pod::Wordlist" } }, "name" : "@Author::OALDERS/Test::PodSpelling", "version" : "2.007005" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "@Author::OALDERS/MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Author::OALDERS/PodSyntaxTests", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::CPAN::Changes", "config" : { "Dist::Zilla::Plugin::Test::CPAN::Changes" : { "changelog" : "Changes" } }, "name" : "@Author::OALDERS/Test::CPAN::Changes", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":InstallModules", ":TestFiles" ], "trailing_whitespace" : 1 } }, "name" : "@Author::OALDERS/Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "@Author::OALDERS/Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "@Author::OALDERS/Test::Portability", "version" : "2.001001" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Author::OALDERS/TestRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Author::OALDERS/Test::ReportPrereqs", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "@Author::OALDERS/Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::PodWeaver", "config" : { "Dist::Zilla::Plugin::PodWeaver" : { "finder" : [ ":InstallModules", ":PerlExecFiles" ], "plugins" : [ { "class" : "Pod::Weaver::Plugin::EnsurePod5", "name" : "@CorePrep/EnsurePod5", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::H1Nester", "name" : "@CorePrep/H1Nester", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::SingleEncoding", "name" : "@Default/SingleEncoding", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Name", "name" : "@Default/Name", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Version", "name" : "@Default/Version", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/prelude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "SYNOPSIS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "DESCRIPTION", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "OVERVIEW", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "ATTRIBUTES", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "METHODS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "FUNCTIONS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Leftovers", "name" : "@Default/Leftovers", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/postlude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Authors", "name" : "@Default/Authors", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Legal", "name" : "@Default/Legal", "version" : "4.019" } ] } }, "name" : "@Author::OALDERS/PodWeaver", "version" : "4.010" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "@Author::OALDERS/PruneCruft", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", "name" : "@Author::OALDERS/CopyFilesFromBuild", "version" : "0.170880" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "@Author::OALDERS/GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "@Author::OALDERS/Git::GatherDir", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Install" ], "match" : [] } }, "name" : "@Author::OALDERS/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Check", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.41.0", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "@Author::OALDERS/Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "@Author::OALDERS/ReadmeMdInBuild", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::StaticInstall", "config" : { "Dist::Zilla::Plugin::StaticInstall" : { "dry_run" : 0, "mode" : "on" } }, "name" : "@Author::OALDERS/StaticInstall", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::ShareDir", "name" : "@Author::OALDERS/ShareDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CheckIssues", "name" : "@Author::OALDERS/CheckIssues", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Author::OALDERS/ConfirmRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Author::OALDERS/UploadToCPAN", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "recommends" } }, "name" : "@Author::OALDERS/@Git::VersionManager/pluginbundle version", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Author::OALDERS/@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Author::OALDERS/@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/release snapshot", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.06", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/Git::Tag", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Author::OALDERS/@Git::VersionManager/NextRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/post-release commit", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Push", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "requires" } }, "name" : "Prereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::ChangesHasContent", "name" : "Test::ChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "changes_has_content.t", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : 0, "fail_on_warning" : "author", "fake_home" : 0, "filename" : "t/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "test", "script_finder" : [ ":PerlExecFiles" ], "skips" : [], "switch" : [] } }, "name" : "Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.030" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.030" } }, "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "amire80 ", "Andreas J. Koenig ", "Bill Mann ", "Bron Gondwana ", "Daniel Hedlund ", "David E. Wheeler ", "DAVIDRW ", "Father Chrysostomos ", "FWILES ", "Gavin Peters ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Graeme Thompson ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "James Raspass ", "jefflee ", "john9art ", "Kyle Wright ", "Mark Stosberg ", "Mark Stosberg ", "Mark Stosberg ", "Mike Schilli ", "mschilli ", "murphy ", "Olaf Alders ", "Ondrej Hanak ", "Peter Rabbitson ", "Philip J. Ludlam ", "phrstbrn ", "pnull ", "Robert Stone ", "Rolf Grossmann ", "ruff ", "sasao ", "Sean M. Burke ", "Slaven Rezic ", "Slaven Rezic ", "Spiros Denaxas ", "Steve Hay ", "Todd Lipcon ", "Tom Hukins ", "Tony Finch ", "Toru Yamaguchi ", "uid39246 ", "Ville Skyttä ", "Yuri Karaban ", "Zefram " ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_static_install" : 1 } PK!svYSS>perl5/x86_64-linux-thread-multi/.meta/Encode-3.21/install.jsonnu6${"dist":"Encode-3.21","version":3.21,"name":"Encode","pathname":"D/DA/DANKOGAI/Encode-3.21.tar.gz","provides":{"Encode::MIME::Header":{"file":"lib/Encode/MIME/Header.pm","version":2.29},"Encode":{"file":"Encode.pm","version":3.21},"Encode::JP::JIS7":{"file":"lib/Encode/JP/JIS7.pm","version":2.08},"Encode::GSM0338":{"file":"lib/Encode/GSM0338.pm","version":"2.10"},"Encode::JP":{"file":"JP/JP.pm","version":2.05},"Encode::Config":{"file":"lib/Encode/Config.pm","version":2.05},"Encode::MIME::Header::ISO_2022_JP":{"version":1.09,"file":"lib/Encode/MIME/Header/ISO_2022_JP.pm"},"encoding":{"file":"encoding.pm","version":"3.00"},"Encode::Unicode::UTF7":{"version":"2.10","file":"lib/Encode/Unicode/UTF7.pm"},"Encode::MIME::Name":{"file":"lib/Encode/MIME/Name.pm","version":1.03},"Encode::Encoder":{"file":"lib/Encode/Encoder.pm","version":2.03},"Encode::TW":{"version":2.03,"file":"TW/TW.pm"},"Encode::Encoding":{"file":"lib/Encode/Encoding.pm","version":2.08},"Encode::utf8":{"file":"Encode.pm","version":3.21},"Encode::CN::HZ":{"file":"lib/Encode/CN/HZ.pm","version":"2.10"},"Encode::Unicode":{"file":"Unicode/Unicode.pm","version":"2.20"},"Encode::Guess":{"file":"lib/Encode/Guess.pm","version":2.08},"Encode::Alias":{"file":"lib/Encode/Alias.pm","version":2.25},"Encode::JP::H2Z":{"file":"lib/Encode/JP/H2Z.pm","version":2.02},"Encode::CN":{"file":"CN/CN.pm","version":2.03},"Encode::KR::2022_KR":{"version":2.04,"file":"lib/Encode/KR/2022_KR.pm"},"Encode::CJKConstants":{"version":2.02,"file":"lib/Encode/CJKConstants.pm"},"Encode::Byte":{"file":"Byte/Byte.pm","version":2.04},"Encode::XS":{"version":3.21,"file":"Encode.pm"},"Encode::KR":{"file":"KR/KR.pm","version":2.03},"Encode::UTF_EBCDIC":{"version":3.21,"file":"Encode.pm"},"Encode::Symbol":{"file":"Symbol/Symbol.pm","version":2.02},"Encode::EBCDIC":{"version":2.02,"file":"EBCDIC/EBCDIC.pm"}},"target":"Encode"}PK!=perl5/x86_64-linux-thread-multi/.meta/Encode-3.21/MYMETA.jsonnu6${ "abstract" : "character encodings in Perl", "author" : [ "Dan Kogai " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Encode", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Exporter" : "5.57", "Storable" : "0", "parent" : "0.221" } }, "test" : { "requires" : { "Test::More" : "0.92" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "https://github.com/dankogai/p5-encode" } }, "version" : "3.21", "x_contributors" : [ "Alex Davies ", "Alex Kapranoff ", "Alex Vandiver ", "Andreas J. Koenig ", "Andrew Pennebaker ", "Andy Grundman ", "Anton Tagunov ", "Autrijus Tang ", "Benjamin Goldberg ", "Bjoern Hoehrmann ", "Bjoern Jacke ", "bulk88 ", "Craig A. Berry ", "Curtis Jewell ", "Dan Kogai ", "Dave Evans ", "David Golden ", "David Steinbrunner ", "Deng Liu ", "Dominic Dunlop ", "drry", "Elizabeth Mattijsen ", "Flavio Poletti ", "Gerrit P. Haase ", "Gisle Aas ", "Graham Barr ", "Graham Knop ", "Graham Ollis ", "Gurusamy Sarathy ", "H.Merijn Brand ", "Hugo van der Sanden ", "chansen ", "Chris Nandor ", "Inaba Hiroto ", "Jarkko Hietaniemi ", "Jesse Vincent ", "Jungshik Shin ", "Karen Etheridge ", "Karl Williamson ", "Kenichi Ishigaki ", "KONNO Hiroharu ", "Laszlo Molnar ", "Makamaka ", "Mark-Jason Dominus ", "Masahiro Iuchi ", "MATSUNO Tokuhiro ", "Mattia Barbon ", "Michael G Schwern ", "Michael LaGrasta ", "Miron Cuperman ", "Moritz Lenz ", "MORIYAMA Masayuki ", "Nick Ing-Simmons ", "Nicholas Clark ", "Olivier Mengué ", "otsune", "Pali ", "Paul Marquess ", "Peter Prymmer ", "Peter Rabbitson ", "Philip Newton ", "Piotr Fusik ", "Rafael Garcia-Suarez ", "Randy Stauner ", "Reini Urban ", "Robin Barker ", "SADAHIRO Tomoyuki ", "Simon Cozens ", "Slaven Rezic ", "Spider Boardman ", "Steve Hay ", "Steve Peters ", "SUGAWARA Hajime ", "SUZUKI Norio ", "szr8 ", "Tatsuhiko Miyagawa ", "Tels ", "Tony Cook ", "Vadim Konovalov ", "Victor ", "Ville Skyttä ", "Vincent van Dam ", "Yitzchak Scott-Thoennes " ], "x_serialization_backend" : "JSON::PP version 2.97001" } PK!6^ Fperl5/x86_64-linux-thread-multi/.meta/HTTP-Negotiate-6.01/install.jsonnu6${"target":"HTTP::Negotiate","provides":{"HTTP::Negotiate":{"file":"lib/HTTP/Negotiate.pm","version":6.01}},"pathname":"G/GA/GAAS/HTTP-Negotiate-6.01.tar.gz","name":"HTTP::Negotiate","version":6.01,"dist":"HTTP-Negotiate-6.01"}PK!aE|ppEperl5/x86_64-linux-thread-multi/.meta/HTTP-Negotiate-6.01/MYMETA.jsonnu6${ "abstract" : "choose a variant to serve", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 6.57_05, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTTP-Negotiate", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "HTTP::Headers" : "6", "perl" : "5.008001" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "http://github.com/gisle/http-negotiate" }, "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.01", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!~ӑeeDperl5/x86_64-linux-thread-multi/.meta/File-chdir-0.1011/install.jsonnu6${"name":"File::chdir","target":"File::chdir","pathname":"D/DA/DAGOLDEN/File-chdir-0.1011.tar.gz","provides":{"File::chdir":{"version":"0.1011","file":"lib/File/chdir.pm"},"File::chdir::ARRAY":{"version":"0.1011","file":"lib/File/chdir.pm"},"File::chdir::SCALAR":{"version":"0.1011","file":"lib/File/chdir.pm"}},"dist":"File-chdir-0.1011","version":"0.1011"}PK!8 Cperl5/x86_64-linux-thread-multi/.meta/File-chdir-0.1011/MYMETA.jsonnu6${ "abstract" : "a more sensible way to change directories", "author" : [ "David Golden ", "Michael G. Schwern " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.008, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "File-chdir", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::PluginBundle::DAGOLDEN" : "0.072", "English" : "0", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Software::License::Perl_5" : "0", "Test::CPAN::Meta" : "0", "Test::More" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1", "blib" : "1.01", "perl" : "5.006", "warnings" : "0" } }, "runtime" : { "requires" : { "Carp" : "0", "Cwd" : "3.16", "Exporter" : "0", "File::Spec::Functions" : "3.27", "perl" : "5.006", "strict" : "0", "vars" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "Test::More" : "0", "warnings" : "0" } } }, "provides" : { "File::chdir" : { "file" : "lib/File/chdir.pm", "version" : "0.1011" }, "File::chdir::ARRAY" : { "file" : "lib/File/chdir.pm", "version" : "0.1011" }, "File::chdir::SCALAR" : { "file" : "lib/File/chdir.pm", "version" : "0.1011" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/dagolden/File-chdir/issues" }, "homepage" : "https://github.com/dagolden/File-chdir", "repository" : { "type" : "git", "url" : "https://github.com/dagolden/File-chdir.git", "web" : "https://github.com/dagolden/File-chdir" } }, "version" : "0.1011", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "David Golden ", "Joel Berger ", "Philippe Bruhat (BooK) " ], "x_serialization_backend" : "JSON::PP version 2.97001" } PK!9XQQAperl5/x86_64-linux-thread-multi/.meta/AppConfig-1.71/install.jsonnu6${"name":"AppConfig","provides":{"AppConfig::Getopt":{"version":1.71,"file":"lib/AppConfig/Getopt.pm"},"AppConfig":{"version":1.71,"file":"lib/AppConfig.pm"},"AppConfig::File":{"file":"lib/AppConfig/File.pm","version":1.71},"AppConfig::State":{"file":"lib/AppConfig/Getopt.pm","version":1.71},"AppConfig::Sys":{"version":1.71,"file":"lib/AppConfig/Sys.pm"},"AppConfig::CGI":{"version":1.71,"file":"lib/AppConfig/CGI.pm"},"AppConfig::Args":{"version":1.71,"file":"lib/AppConfig/Args.pm"}},"target":"AppConfig","version":1.71,"dist":"AppConfig-1.71","pathname":"N/NE/NEILB/AppConfig-1.71.tar.gz"}PK!ِ֚^^@perl5/x86_64-linux-thread-multi/.meta/AppConfig-1.71/MYMETA.jsonnu6${ "abstract" : "AppConfig is a bundle of Perl5 modules for reading configuration files and parsing command line arguments.", "author" : [ "Andy Wardley " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.02, CPAN::Meta::Converter version 2.143240, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "AppConfig", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Test::More" : "0", "perl" : "5.008008" } }, "test" : { "requires" : { "Test::Pod" : "1.0" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "git://github.com/neilbowers/AppConfig.git", "web" : "https://github.com/neilbowers/AppConfig" } }, "version" : "1.71", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!U$Cperl5/x86_64-linux-thread-multi/.meta/IO-Stringy-2.113/install.jsonnu6${"pathname":"C/CA/CAPOEIRAB/IO-Stringy-2.113.tar.gz","provides":{"IO::Stringy":{"file":"lib/IO/Stringy.pm","version":"2.113"},"IO::Scalar":{"version":"2.113","file":"lib/IO/Scalar.pm"},"IO::AtomicFile":{"file":"lib/IO/AtomicFile.pm","version":"2.113"},"IO::ScalarArray":{"version":"2.113","file":"lib/IO/ScalarArray.pm"},"IO::Wrap":{"version":"2.113","file":"lib/IO/Wrap.pm"},"IO::InnerFile":{"version":"2.113","file":"lib/IO/InnerFile.pm"},"IO::Lines":{"version":"2.113","file":"lib/IO/Lines.pm"},"IO::WrapTie":{"version":"2.113","file":"lib/IO/WrapTie.pm"}},"dist":"IO-Stringy-2.113","name":"IO::Stringy","version":"2.113","target":"IO::Scalar"}PK!LLBperl5/x86_64-linux-thread-multi/.meta/IO-Stringy-2.113/MYMETA.jsonnu6${ "abstract" : "I/O on in-core objects like strings and arrays", "author" : [ "Erik Dorfman " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "IO-Stringy", "no_index" : { "directory" : [ "eg", "examples", "inc", "share", "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Dist::Zilla" : "0", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Test::CPAN::Changes" : "0.4", "Test::CheckManifest" : "1.29", "Test::Kwalitee" : "1.22", "Test::More" : "0.88", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Pod::Spelling::CommonMistakes" : "1.000", "Test::Spelling" : "0.12", "Test::TrailingSpace" : "0", "Test::Version" : "1" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "5.57", "File::Spec" : "0", "FileHandle" : "0", "IO::File" : "0", "IO::Handle" : "0", "Symbol" : "0", "overload" : "0", "parent" : "0", "strict" : "0", "warnings" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Basename" : "0", "File::Spec" : "0", "File::Temp" : "0", "FileHandle" : "0", "IO::File" : "0", "IO::Handle" : "0", "Symbol" : "0", "Test::More" : "0.88", "Test::Tester" : "0", "strict" : "0", "warnings" : "0" } } }, "provides" : { "IO::AtomicFile" : { "file" : "lib/IO/AtomicFile.pm", "version" : "2.113" }, "IO::InnerFile" : { "file" : "lib/IO/InnerFile.pm", "version" : "2.113" }, "IO::Lines" : { "file" : "lib/IO/Lines.pm", "version" : "2.113" }, "IO::Scalar" : { "file" : "lib/IO/Scalar.pm", "version" : "2.113" }, "IO::ScalarArray" : { "file" : "lib/IO/ScalarArray.pm", "version" : "2.113" }, "IO::Stringy" : { "file" : "lib/IO/Stringy.pm", "version" : "2.113" }, "IO::Wrap" : { "file" : "lib/IO/Wrap.pm", "version" : "2.113" }, "IO::WrapTie" : { "file" : "lib/IO/WrapTie.pm", "version" : "2.113" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/genio/IO-Stringy/issues" }, "homepage" : "https://github.com/genio/IO-Stringy", "repository" : { "type" : "git", "url" : "https://github.com/genio/IO-Stringy.git", "web" : "https://github.com/genio/IO-Stringy" } }, "version" : "2.113", "x_Dist_Zilla" : { "perl" : { "version" : "5.030000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "LICENSE", "META.json", "Makefile.PL", "README.md", "t/00-report-prereqs.t" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "Git::GatherDir", "version" : "2.046" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Starter/MetaYAML", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Starter/MetaJSON", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Starter/License", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Pod2Readme", "name" : "@Starter/Pod2Readme", "version" : "0.004" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Starter/PodSyntaxTests", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Starter/Test::ReportPrereqs", "version" : "0.027" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : 0, "fail_on_warning" : "author", "fake_home" : 0, "filename" : "xt/author/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "develop", "script_finder" : [ ":PerlExecFiles" ], "skips" : [], "switch" : [] } }, "name" : "@Starter/Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::MakeMaker::Awesome", "config" : { "Dist::Zilla::Plugin::MakeMaker" : { "make_path" : "gmake", "version" : "6.012" }, "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1, "version" : "6.012" } }, "name" : "@Starter/MakeMaker::Awesome", "version" : "0.48" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Starter/Manifest", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "@Starter/PruneCruft", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::ManifestSkip", "name" : "@Starter/ManifestSkip", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1 } }, "name" : "@Starter/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 1, "skip_version_provider" : 0 } }, "name" : "@Starter/RewriteVersion", "version" : "0.018" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Starter/NextRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 } }, "name" : "@Starter/BumpVersionAfterRelease", "version" : "0.018" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Starter/TestRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Starter/ConfirmRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Starter/UploadToCPAN", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Starter/MetaConfig", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Starter/MetaNoIndex", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Package", "config" : { "Dist::Zilla::Plugin::MetaProvides::Package" : { "finder_objects" : [ { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "@Starter/MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.012" } ], "include_underscores" : 0 }, "Dist::Zilla::Role::MetaProvider::Provider" : { "$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004", "inherit_missing" : 1, "inherit_version" : 1, "meta_noindex" : 1 }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000036", "version" : "0.006" } }, "name" : "@Starter/MetaProvides::Package", "version" : "2.004003" }, { "class" : "Dist::Zilla::Plugin::ShareDir", "name" : "@Starter/ShareDir", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "@Starter/ExecDir", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "Markdown_Readme", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::Prereqs::FromCPANfile", "name" : "Prereqs::FromCPANfile", "version" : "0.08" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.16.1.windows.1", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "Git::Contributors", "version" : "0.035" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.16.1.windows.1", "repo_root" : "." } }, "name" : "@Git/Check", "version" : "2.046" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.16.1.windows.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git/Commit", "version" : "2.046" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v2.113", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.16.1.windows.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git/Tag", "version" : "2.046" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.16.1.windows.1", "repo_root" : "." } }, "name" : "@Git/Push", "version" : "2.046" }, { "class" : "Dist::Zilla::Plugin::CheckChangeLog", "name" : "CheckChangeLog", "version" : "0.05" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Test::ChangesHasContent", "name" : "Test::ChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Test::Kwalitee", "config" : { "Dist::Zilla::Plugin::Test::Kwalitee" : { "filename" : "xt/release/kwalitee.t", "skiptest" : [ "no_symlinks" ] } }, "name" : "Test::Kwalitee", "version" : "2.12" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::Test::PodSpelling", "config" : { "Dist::Zilla::Plugin::Test::PodSpelling" : { "directories" : [ "bin", "lib" ], "spell_cmd" : "", "stopwords" : [ "BUF", "Doru", "FOO", "Foo", "NBYTES", "POS", "SCALARREF", "SLAVECLASS", "ZeeGee", "aref", "dfs", "getline", "getlines", "getpos", "ing", "reblessed", "setpos", "sref", "tieable", "wraphandle" ], "wordlist" : "Pod::Wordlist" } }, "name" : "Test::PodSpelling", "version" : "2.007005" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", "name" : "CopyFilesFromBuild", "version" : "0.170880" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "@Starter/MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.012" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.012" } }, "x_contributors" : [ "Chase Whitener ", "Dianne Skoll " ], "x_generated_by_perl" : "v5.30.0", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!ͤIperl5/x86_64-linux-thread-multi/.meta/Template-Toolkit-3.101/install.jsonnu6${"version":3.101,"target":"Template::Constants","provides":{"Template::VMethods":{"file":"lib/Template/VMethods.pm","version":"3.100"},"Template::Plugin::Format":{"version":"3.100","file":"lib/Template/Plugin/Format.pm"},"Template::Base":{"version":"3.100","file":"lib/Template/Base.pm"},"Template::Test":{"file":"lib/Template/Test.pm","version":"3.100"},"Template::Grammar":{"version":"3.100","file":"lib/Template/Grammar.pm"},"Template::Toolkit":{"file":"lib/Template/Toolkit.pm","version":"3.100"},"Template::View":{"version":"3.100","file":"lib/Template/View.pm"},"Template::Plugin":{"file":"lib/Template/Plugin.pm","version":"3.100"},"Template::Constants":{"file":"lib/Template/Constants.pm","version":"3.100"},"Template::Plugin::Scalar":{"file":"lib/Template/Plugin/Scalar.pm","version":"3.100"},"Template::Exception":{"file":"lib/Template/Exception.pm","version":"3.100"},"Template::Stash":{"file":"lib/Template/Stash.pm","version":"3.100"},"Template::Plugin::Date::Calc":{"file":"lib/Template/Plugin/Date.pm","version":"3.100"},"Template::Monad::Scalar":{"version":"3.100","file":"lib/Template/Plugin/Scalar.pm"},"Template::Plugin::Date":{"file":"lib/Template/Plugin/Date.pm","version":"3.100"},"Template::Namespace::Constants":{"file":"lib/Template/Namespace/Constants.pm","version":"3.100"},"Template::Parser":{"version":"3.100","file":"lib/Template/Parser.pm"},"Template::Context":{"version":"3.100","file":"lib/Template/Context.pm"},"Template::Plugin::Datafile":{"version":"3.100","file":"lib/Template/Plugin/Datafile.pm"},"Template::Plugin::Image":{"version":"3.100","file":"lib/Template/Plugin/Image.pm"},"Template::Plugin::Pod":{"file":"lib/Template/Plugin/Pod.pm","version":"3.100"},"Template::Plugin::File":{"version":"3.100","file":"lib/Template/Plugin/File.pm"},"Template::Plugins":{"version":"3.100","file":"lib/Template/Plugins.pm"},"Template::Provider":{"version":"3.100","file":"lib/Template/Provider.pm"},"Template::Stash::Context":{"version":"3.100","file":"lib/Template/Stash/Context.pm"},"Template::Plugin::View":{"file":"lib/Template/Plugin/View.pm","version":"3.100"},"Template::Config":{"file":"lib/Template/Config.pm","version":"3.100"},"Template::Filters":{"version":"3.100","file":"lib/Template/Filters.pm"},"Template::Perl":{"file":"lib/Template/Filters.pm","version":"3.100"},"Template::Plugin::Assert":{"file":"lib/Template/Plugin/Assert.pm","version":"3.100"},"Template::Plugin::Table":{"version":"3.100","file":"lib/Template/Plugin/Table.pm"},"Template::Plugin::Filter":{"version":"3.100","file":"lib/Template/Plugin/Filter.pm"},"Template::Plugin::Procedural":{"file":"lib/Template/Plugin/Procedural.pm","version":"3.100"},"Template::Plugin::String":{"file":"lib/Template/Plugin/String.pm","version":"3.100"},"Template::Plugin::Directory":{"version":"3.100","file":"lib/Template/Plugin/Directory.pm"},"Template::Monad::Assert":{"version":"3.100","file":"lib/Template/Plugin/Assert.pm"},"Template::Plugin::URL":{"version":"3.100","file":"lib/Template/Plugin/URL.pm"},"Template::Plugin::Date::Manip":{"version":"3.100","file":"lib/Template/Plugin/Date.pm"},"Template::Directive":{"file":"lib/Template/Directive.pm","version":"3.100"},"Template::Plugin::Math":{"version":"3.100","file":"lib/Template/Plugin/Math.pm"},"Template::Plugin::HTML":{"file":"lib/Template/Plugin/HTML.pm","version":"3.100"},"Template::Document":{"file":"lib/Template/Document.pm","version":"3.100"},"Template::Iterator":{"file":"lib/Template/Iterator.pm","version":"3.100"},"Template::Plugin::Iterator":{"version":"3.100","file":"lib/Template/Plugin/Iterator.pm"},"Template":{"file":"lib/Template.pm","version":3.101},"Template::Stash::XS":{"file":"lib/Template/Stash/XS.pm"},"Template::Plugin::Wrap":{"file":"lib/Template/Plugin/Wrap.pm","version":"3.100"},"Template::Service":{"version":"3.100","file":"lib/Template/Service.pm"},"Template::Plugin::Dumper":{"file":"lib/Template/Plugin/Dumper.pm","version":"3.100"},"Template::TieString":{"file":"lib/Template/Config.pm","version":"3.100"}},"name":"Template","dist":"Template-Toolkit-3.101","pathname":"A/AB/ABW/Template-Toolkit-3.101.tar.gz"}PK!Hperl5/x86_64-linux-thread-multi/.meta/Template-Toolkit-3.101/MYMETA.jsonnu6${ "abstract" : "comprehensive template processing system", "author" : [ "Andy Wardley " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 6.66, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Template-Toolkit", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "AppConfig" : "1.56", "File::Spec" : "0.8", "File::Temp" : "0.12", "Scalar::Util" : "0" } }, "test" : { "requires" : { "Test::LeakTrace" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/abw/Template2/issues" }, "homepage" : "http://www.template-toolkit.org", "repository" : { "type" : "git", "url" : "https://github.com/abw/Template2.git", "web" : "https://github.com/abw/Template2" } }, "version" : "3.101", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!R2#mmAperl5/x86_64-linux-thread-multi/.meta/YAML-Syck-1.36/install.jsonnu6${"dist":"YAML-Syck-1.36","target":"YAML::Syck","name":"YAML::Syck","version":1.36,"provides":{"YAML::Syck":{"version":1.36,"file":"lib/YAML/Syck.pm"},"YAML::Loader::Syck":{"file":"lib/YAML/Loader/Syck.pm"},"YAML::Dumper::Syck":{"file":"lib/YAML/Dumper/Syck.pm"},"JSON::Syck":{"version":1.36,"file":"lib/JSON/Syck.pm"}},"pathname":"T/TO/TODDR/YAML-Syck-1.36.tar.gz"}PK!Z@perl5/x86_64-linux-thread-multi/.meta/YAML-Syck-1.36/MYMETA.jsonnu6${ "abstract" : "Fast, lightweight YAML loader and dumper", "author" : [ "Todd Rinaldo " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.76, CPAN::Meta::Converter version 2.150010", "license" : [ "mit" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "YAML-Syck", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.006" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/toddr/YAML-Syck/issues" }, "homepage" : "http://github.com/toddr/YAML-Syck", "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "http://github.com/toddr/YAML-Syck" } }, "version" : "1.36", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!olCperl5/x86_64-linux-thread-multi/.meta/Scope-Guard-0.21/install.jsonnu6${"pathname":"C/CH/CHOCOLATE/Scope-Guard-0.21.tar.gz","target":"Scope::Guard","provides":{"Scope::Guard":{"version":0.21,"file":"lib/Scope/Guard.pm"}},"version":0.21,"dist":"Scope-Guard-0.21","name":"Scope::Guard"}PK!eBperl5/x86_64-linux-thread-multi/.meta/Scope-Guard-0.21/MYMETA.jsonnu6${ "abstract" : "lexically-scoped resource management", "author" : [ "chocolateboy " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150005, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Scope-Guard", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.006001" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/chocolateboy/Scope-Guard/issues" }, "repository" : { "url" : "https://github.com/chocolateboy/Scope-Guard" } }, "version" : "0.21", "x_serialization_backend" : "JSON::PP version 2.97001", "x_test_requires" : { "Test::More" : 0 } } PK!OϢBperl5/x86_64-linux-thread-multi/.meta/Path-Tiny-0.144/install.jsonnu6${"provides":{"Path::Tiny":{"version":"0.144","file":"lib/Path/Tiny.pm"},"Path::Tiny::Error":{"version":"0.144","file":"lib/Path/Tiny.pm"}},"version":"0.144","dist":"Path-Tiny-0.144","pathname":"D/DA/DAGOLDEN/Path-Tiny-0.144.tar.gz","name":"Path::Tiny","target":"Path::Tiny"}PK!F<Aperl5/x86_64-linux-thread-multi/.meta/Path-Tiny-0.144/MYMETA.jsonnu6${ "abstract" : "File path utility", "author" : [ "David Golden " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010", "license" : [ "apache_2_0" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Path-Tiny", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB", "flock" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::MinimumPerl" : "0", "Dist::Zilla::Plugin::OnlyCorePrereqs" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::ReleaseStatus::FromVersion" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::DAGOLDEN" : "0.072", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Software::License::Apache_2_0" : "0", "Test::CPAN::Meta" : "0", "Test::MinimumVersion" : "0", "Test::More" : "0", "Test::Perl::Critic" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1" } }, "runtime" : { "recommends" : { "Unicode::UTF8" : "0.58" }, "requires" : { "Carp" : "0", "Cwd" : "0", "Digest" : "1.03", "Digest::SHA" : "5.45", "Encode" : "0", "Exporter" : "5.57", "Fcntl" : "0", "File::Compare" : "0", "File::Copy" : "0", "File::Glob" : "0", "File::Path" : "2.07", "File::Spec" : "0.86", "File::Temp" : "0.19", "File::stat" : "0", "constant" : "0", "overload" : "0", "perl" : "5.008001", "strict" : "0", "warnings" : "0", "warnings::register" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900", "Test::FailWarnings" : "0", "Test::MockRandom" : "0" }, "requires" : { "Digest::MD5" : "0", "ExtUtils::MakeMaker" : "0", "File::Basename" : "0", "File::Spec" : "0.86", "File::Spec::Functions" : "0", "File::Spec::Unix" : "0", "File::Temp" : "0.19", "Test::More" : "0.96", "lib" : "0", "open" : "0" } } }, "provides" : { "Path::Tiny" : { "file" : "lib/Path/Tiny.pm", "version" : "0.144" }, "Path::Tiny::Error" : { "file" : "lib/Path/Tiny.pm", "version" : "0.144" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/dagolden/Path-Tiny/issues" }, "homepage" : "https://github.com/dagolden/Path-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/dagolden/Path-Tiny.git", "web" : "https://github.com/dagolden/Path-Tiny" } }, "version" : "0.144", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "Alex Efros ", "Aristotle Pagaltzis ", "Chris Williams ", "Dan Book ", "Dave Rolsky ", "David Steinbrunner ", "Doug Bell ", "Elvin Aslanov ", "Flavio Poletti ", "Gabor Szabo ", "Gabriel Andrade ", "George Hartzell ", "Geraud Continsouzas ", "Goro Fuji ", "Graham Knop ", "Graham Ollis ", "Ian Sillitoe ", "James Hunt ", "John Karr ", "Karen Etheridge ", "Mark Ellis ", "Martin H. Sluka ", "Martin Kjeldsen ", "Mary Ehlers ", "Michael G. Schwern ", "Nicolas R ", "Nicolas Rochelemagne ", "Nigel Gregoire ", "Philippe Bruhat (BooK) ", "regina-verbae ", "Roy Ivy III ", "Shlomi Fish ", "Smylers ", "Tatsuhiko Miyagawa ", "Toby Inkster ", "Yanick Champoux ", "김도형 - Keedi Kim " ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Apache-2.0" } PK!D>perl5/x86_64-linux-thread-multi/.meta/Expect-1.38/install.jsonnu6${"target":"Expect","provides":{"Expect":{"version":1.38,"file":"lib/Expect.pm"}},"name":"Expect","version":1.38,"pathname":"J/JA/JACOBY/Expect-1.38.tar.gz","dist":"Expect-1.38"}PK!!66=perl5/x86_64-linux-thread-multi/.meta/Expect-1.38/MYMETA.jsonnu6${ "abstract" : "automate interactions with command line programs that expose a text terminal interface.", "author" : [ "Austin Schutz ", "Roland Giersig ", "Dave Jacoby " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.62, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Expect", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : {} }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.64" } }, "runtime" : { "requires" : { "Carp" : "0", "Errno" : "0", "Exporter" : "0", "Fcntl" : "0", "IO::Handle" : "0", "IO::Pty" : "1.11", "IO::Tty" : "1.11", "POSIX" : "0", "perl" : "5.006000" } }, "test" : { "requires" : { "File::Temp" : "0", "Test::More" : "1.00" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "http://github.com/jacoby/expect.pm.git", "web" : "http://github.com/jacoby/expect.pm" } }, "version" : "1.38", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!'  @perl5/x86_64-linux-thread-multi/.meta/TimeDate-2.33/install.jsonnu6${"provides":{"Date::Language::Hungarian":{"file":"lib/Date/Language/Hungarian.pm","version":1.01},"Date::Language::Tigrinya":{"file":"lib/Date/Language/Tigrinya.pm","version":"1.00"},"Date::Language::Amharic":{"file":"lib/Date/Language/Amharic.pm","version":"1.00"},"Date::Language::Chinese":{"file":"lib/Date/Language/Chinese.pm","version":"1.00"},"Time::Zone":{"version":2.24,"file":"lib/Time/Zone.pm"},"Date::Language::Sidama":{"version":0.99,"file":"lib/Date/Language/Sidama.pm"},"Date::Language::TigrinyaEthiopian":{"version":"1.00","file":"lib/Date/Language/TigrinyaEthiopian.pm"},"Date::Language::Oromo":{"file":"lib/Date/Language/Oromo.pm","version":0.99},"Date::Language::Icelandic":{"version":1.01,"file":"lib/Date/Language/Icelandic.pm"},"Date::Language::Afar":{"file":"lib/Date/Language/Afar.pm","version":0.99},"Date::Language::Somali":{"file":"lib/Date/Language/Somali.pm","version":0.99},"Date::Language::Dutch":{"version":1.02,"file":"lib/Date/Language/Dutch.pm"},"Date::Language::TigrinyaEritrean":{"file":"lib/Date/Language/TigrinyaEritrean.pm","version":"1.00"},"TimeDate":{"file":"lib/TimeDate.pm","version":1.21},"Date::Language::German":{"version":1.02,"file":"lib/Date/Language/German.pm"},"Date::Format":{"file":"lib/Date/Format.pm","version":2.24},"Date::Language::Chinese_GB":{"file":"lib/Date/Language/Chinese_GB.pm","version":1.01},"Date::Language::Swedish":{"file":"lib/Date/Language/Swedish.pm","version":1.01},"Date::Language::Danish":{"file":"lib/Date/Language/Danish.pm","version":1.01},"Date::Language::Norwegian":{"version":1.01,"file":"lib/Date/Language/Norwegian.pm"},"Date::Language::English":{"version":1.01,"file":"lib/Date/Language/English.pm"},"Date::Format::Generic":{"file":"lib/Date/Format.pm","version":2.24},"Date::Language::Brazilian":{"version":1.01,"file":"lib/Date/Language/Brazilian.pm"},"Date::Language::Bulgarian":{"version":1.01,"file":"lib/Date/Language/Bulgarian.pm"},"Date::Language::Finnish":{"file":"lib/Date/Language/Finnish.pm","version":1.01},"Date::Language::Spanish":{"file":"lib/Date/Language/Spanish.pm","version":"1.00"},"Date::Language::Russian":{"version":1.01,"file":"lib/Date/Language/Russian.pm"},"Date::Language::Greek":{"file":"lib/Date/Language/Greek.pm","version":"1.00"},"Date::Language::French":{"file":"lib/Date/Language/French.pm","version":1.04},"Date::Language::Russian_cp1251":{"file":"lib/Date/Language/Russian_cp1251.pm","version":1.01},"Date::Language::Russian_koi8r":{"file":"lib/Date/Language/Russian_koi8r.pm","version":1.01},"Date::Language::Romanian":{"version":1.01,"file":"lib/Date/Language/Romanian.pm"},"Date::Language::Occitan":{"file":"lib/Date/Language/Occitan.pm","version":1.04},"Date::Language":{"file":"lib/Date/Language.pm","version":"1.10"},"Date::Language::Turkish":{"file":"lib/Date/Language/Turkish.pm","version":"1.0"},"Date::Language::Austrian":{"version":1.01,"file":"lib/Date/Language/Austrian.pm"},"Date::Parse":{"version":2.33,"file":"lib/Date/Parse.pm"},"Date::Language::Italian":{"file":"lib/Date/Language/Italian.pm","version":1.01},"Date::Language::Gedeo":{"file":"lib/Date/Language/Gedeo.pm","version":0.99},"Date::Language::Czech":{"version":1.01,"file":"lib/Date/Language/Czech.pm"}},"target":"Time::Zone","dist":"TimeDate-2.33","version":2.33,"pathname":"A/AT/ATOOMIC/TimeDate-2.33.tar.gz","name":"Date::Parse"}PK!9{?perl5/x86_64-linux-thread-multi/.meta/TimeDate-2.33/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "Graham Barr " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "TimeDate", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/atoomic/perl-TimeDate/issues" }, "repository" : { "url" : "https://github.com/atoomic/perl-TimeDate" } }, "version" : "2.33", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!zk>perl5/x86_64-linux-thread-multi/.meta/IO-Tty-1.20/install.jsonnu6${"name":"IO::Tty","provides":{"IO::Pty":{"file":"Pty.pm","version":"1.20"},"IO::Tty::Constant":{"version":"1.20","file":"Tty/Constant.pm"},"IO::Tty":{"file":"Tty.pm","version":"1.20"}},"version":"1.20","target":"IO::Pty","dist":"IO-Tty-1.20","pathname":"T/TO/TODDR/IO-Tty-1.20.tar.gz"}PK!ǽAA=perl5/x86_64-linux-thread-multi/.meta/IO-Tty-1.20/MYMETA.jsonnu6${ "abstract" : "Pseudo ttys and constants", "author" : [ "Roland Giersig " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "IO-Tty", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/toddr/IO-Tty/issues" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "https://github.com/toddr/IO-Tty" } }, "version" : "1.20", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!'&Dperl5/x86_64-linux-thread-multi/.meta/common-sense-3.75/install.jsonnu6${"target":"common::sense","name":"common::sense","provides":{"common::sense":{"version":3.75,"file":"sense.pm.PL"}},"version":3.75,"pathname":"M/ML/MLEHMANN/common-sense-3.75.tar.gz","dist":"common-sense-3.75"}PK!PU??Cperl5/x86_64-linux-thread-multi/.meta/common-sense-3.75/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150001, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "common-sense", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } } }, "release_status" : "stable", "version" : 3.75, "x_serialization_backend" : "JSON::PP version 2.97001" } PK!+Cperl5/x86_64-linux-thread-multi/.meta/libwww-perl-6.77/install.jsonnu6${"provides":{"LWP::Authen::Digest":{"file":"lib/LWP/Authen/Digest.pm","version":"6.77"},"LWP::Protocol::data":{"file":"lib/LWP/Protocol/data.pm","version":"6.77"},"LWP::Authen::Ntlm":{"version":"6.77","file":"lib/LWP/Authen/Ntlm.pm"},"LWP::Authen::Basic":{"file":"lib/LWP/Authen/Basic.pm","version":"6.77"},"LWP::Protocol::http":{"file":"lib/LWP/Protocol/http.pm","version":"6.77"},"LWP::Debug":{"x_deprecated":1,"version":"6.77","file":"lib/LWP/Debug.pm"},"LWP::Debug::TraceHTTP":{"version":"6.77","file":"lib/LWP/Debug/TraceHTTP.pm"},"LWP::Simple":{"file":"lib/LWP/Simple.pm","version":"6.77"},"LWP::MemberMixin":{"version":"6.77","file":"lib/LWP/MemberMixin.pm"},"LWP::Protocol::nogo":{"version":"6.77","file":"lib/LWP/Protocol/nogo.pm"},"LWP::Protocol::nntp":{"version":"6.77","file":"lib/LWP/Protocol/nntp.pm"},"LWP::Protocol::gopher":{"version":"6.77","file":"lib/LWP/Protocol/gopher.pm"},"LWP::ConnCache":{"file":"lib/LWP/ConnCache.pm","version":"6.77"},"LWP::RobotUA":{"version":"6.77","file":"lib/LWP/RobotUA.pm"},"LWP::UserAgent":{"version":"6.77","file":"lib/LWP/UserAgent.pm"},"LWP::Protocol::mailto":{"version":"6.77","file":"lib/LWP/Protocol/mailto.pm"},"LWP::Protocol":{"version":"6.77","file":"lib/LWP/Protocol.pm"},"LWP":{"version":"6.77","file":"lib/LWP.pm"},"LWP::Protocol::ftp":{"file":"lib/LWP/Protocol/ftp.pm","version":"6.77"},"LWP::Protocol::file":{"file":"lib/LWP/Protocol/file.pm","version":"6.77"},"LWP::DebugFile":{"version":"6.77","file":"lib/LWP/DebugFile.pm"},"LWP::Protocol::cpan":{"version":"6.77","file":"lib/LWP/Protocol/cpan.pm"},"LWP::Protocol::loopback":{"version":"6.77","file":"lib/LWP/Protocol/loopback.pm"}},"target":"LWP::UserAgent","dist":"libwww-perl-6.77","version":"6.77","pathname":"O/OA/OALDERS/libwww-perl-6.77.tar.gz","name":"LWP"}PK!_vBperl5/x86_64-linux-thread-multi/.meta/libwww-perl-6.77/MYMETA.jsonnu6${ "abstract" : "The World-Wide Web library for Perl", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.031, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "libwww-perl", "no_index" : { "directory" : [ "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0", "File::Copy" : "0", "Getopt::Long" : "0" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "recommends" : { "Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007" }, "requires" : { "Authen::NTLM" : "1.02", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Spell" : "1.25", "Test::EOL" : "2.00", "Test::LeakTrace" : "0.16", "Test::MinimumVersion" : "0", "Test::Mojibake" : "0", "Test::More" : "0.94", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1" } }, "runtime" : { "requires" : { "Digest::MD5" : "0", "Encode" : "2.12", "Encode::Locale" : "0", "File::Copy" : "0", "File::Listing" : "6", "File::Temp" : "0", "Getopt::Long" : "0", "HTML::Entities" : "0", "HTML::HeadParser" : "3.71", "HTTP::Cookies" : "6", "HTTP::Date" : "6", "HTTP::Negotiate" : "6", "HTTP::Request" : "6.18", "HTTP::Request::Common" : "6.18", "HTTP::Response" : "6.18", "HTTP::Status" : "6.18", "IO::Select" : "0", "IO::Socket" : "0", "LWP::MediaTypes" : "6", "MIME::Base64" : "2.1", "Module::Load" : "0", "Net::FTP" : "2.58", "Net::HTTP" : "6.18", "Scalar::Util" : "0", "Try::Tiny" : "0", "URI" : "1.10", "URI::Escape" : "0", "WWW::RobotRules" : "6", "parent" : "0.217", "perl" : "5.008001", "strict" : "0", "warnings" : "0" }, "suggests" : { "Authen::NTLM" : "1.02", "Data::Dump" : "1.13", "IO::Socket::INET" : "0", "LWP::Protocol::https" : "6.02" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900", "Test::LeakTrace" : "0" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "FindBin" : "0", "HTTP::CookieJar::LWP" : "0", "HTTP::Daemon" : "6.12", "Test::Fatal" : "0", "Test::More" : "0.96", "Test::Needs" : "0", "Test::RequiresInternet" : "0" } } }, "provides" : { "LWP" : { "file" : "lib/LWP.pm", "version" : "6.77" }, "LWP::Authen::Basic" : { "file" : "lib/LWP/Authen/Basic.pm", "version" : "6.77" }, "LWP::Authen::Digest" : { "file" : "lib/LWP/Authen/Digest.pm", "version" : "6.77" }, "LWP::Authen::Ntlm" : { "file" : "lib/LWP/Authen/Ntlm.pm", "version" : "6.77" }, "LWP::ConnCache" : { "file" : "lib/LWP/ConnCache.pm", "version" : "6.77" }, "LWP::Debug" : { "file" : "lib/LWP/Debug.pm", "version" : "6.77", "x_deprecated" : 1 }, "LWP::Debug::TraceHTTP" : { "file" : "lib/LWP/Debug/TraceHTTP.pm", "version" : "6.77" }, "LWP::DebugFile" : { "file" : "lib/LWP/DebugFile.pm", "version" : "6.77" }, "LWP::MemberMixin" : { "file" : "lib/LWP/MemberMixin.pm", "version" : "6.77" }, "LWP::Protocol" : { "file" : "lib/LWP/Protocol.pm", "version" : "6.77" }, "LWP::Protocol::cpan" : { "file" : "lib/LWP/Protocol/cpan.pm", "version" : "6.77" }, "LWP::Protocol::data" : { "file" : "lib/LWP/Protocol/data.pm", "version" : "6.77" }, "LWP::Protocol::file" : { "file" : "lib/LWP/Protocol/file.pm", "version" : "6.77" }, "LWP::Protocol::ftp" : { "file" : "lib/LWP/Protocol/ftp.pm", "version" : "6.77" }, "LWP::Protocol::gopher" : { "file" : "lib/LWP/Protocol/gopher.pm", "version" : "6.77" }, "LWP::Protocol::http" : { "file" : "lib/LWP/Protocol/http.pm", "version" : "6.77" }, "LWP::Protocol::loopback" : { "file" : "lib/LWP/Protocol/loopback.pm", "version" : "6.77" }, "LWP::Protocol::mailto" : { "file" : "lib/LWP/Protocol/mailto.pm", "version" : "6.77" }, "LWP::Protocol::nntp" : { "file" : "lib/LWP/Protocol/nntp.pm", "version" : "6.77" }, "LWP::Protocol::nogo" : { "file" : "lib/LWP/Protocol/nogo.pm", "version" : "6.77" }, "LWP::RobotUA" : { "file" : "lib/LWP/RobotUA.pm", "version" : "6.77" }, "LWP::Simple" : { "file" : "lib/LWP/Simple.pm", "version" : "6.77" }, "LWP::UserAgent" : { "file" : "lib/LWP/UserAgent.pm", "version" : "6.77" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/libwww-perl/issues" }, "homepage" : "https://github.com/libwww-perl/libwww-perl", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/libwww-perl.git", "web" : "https://github.com/libwww-perl/libwww-perl" }, "x_IRC" : "irc://irc.perl.org/#lwp", "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.77", "x_Dist_Zilla" : { "perl" : { "version" : "5.034000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "LICENSE", "META.json", "README.md" ], "exclude_match" : [], "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "Git::GatherDir", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "MetaConfig", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Package", "config" : { "Dist::Zilla::Plugin::MetaProvides::Package" : { "finder_objects" : [ { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.031" } ], "include_underscores" : 0 }, "Dist::Zilla::Role::MetaProvider::Provider" : { "$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004", "inherit_missing" : 1, "inherit_version" : 1, "meta_noindex" : 1 }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "MetaProvides::Package", "version" : "2.004003" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "MetaNoIndex", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "MetaYAML", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "MetaJSON", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "MetaResources", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Deprecated", "config" : { "Dist::Zilla::Plugin::Deprecated" : { "all" : 0, "modules" : [ "LWP::Debug" ] } }, "name" : "Deprecated", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.34.1", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "Manifest", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "License", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "ExecDir", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Prereqs::FromCPANfile", "name" : "Prereqs::FromCPANfile", "version" : "0.08" }, { "class" : "Dist::Zilla::Plugin::MakeMaker::Awesome", "config" : { "Dist::Zilla::Plugin::MakeMaker" : { "make_path" : "make", "version" : "6.031" }, "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8", "version" : "6.031" } }, "name" : "MakeMaker::Awesome", "version" : "0.49" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "Test::ReportPrereqs", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : "1", "fail_on_warning" : "author", "fake_home" : 0, "filename" : "xt/author/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "develop", "script_finder" : [ ":PerlExecFiles" ], "skips" : [], "switch" : [] } }, "name" : "Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "00-compile.t", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "Test::Portability", "version" : "2.001001" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":InstallModules", ":TestFiles" ], "trailing_whitespace" : 1 } }, "name" : "Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::Test::MinimumVersion", "config" : { "Dist::Zilla::Plugin::Test::MinimumVersion" : { "max_target_perl" : null } }, "name" : "Test::MinimumVersion", "version" : "2.000010" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "PodSyntaxTests", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::Test::PodSpelling", "config" : { "Dist::Zilla::Plugin::Test::PodSpelling" : { "directories" : [ "bin", "lib" ], "spell_cmd" : "aspell --master=en_US list", "stopwords" : [ "Accomazzi", "Alexandre", "Andreas", "Asplund", "Betts", "Bochner", "BooK", "Buenzli", "CGI", "CPAN", "Chamas", "Coppit", "Dalgleish", "Dubois", "Dunkin", "Duret", "Dvornik", "Eldridge", "Gertjan", "Graaff", "Greab", "Guenther", "Gurusamy", "Gustafsson", "Hakanson", "Harald", "Hedlund", "Hoblitt", "Hwa", "INOUE", "Joao", "Joerg", "KONISHI", "Kaminsky", "Kartik", "Katsuhiro", "Kebsch", "Keiichiro", "Kilzer", "Klar", "Koster", "Kronengold", "Krüger", "Kubb", "König", "Laker", "Langfeldt", "Langheinrich", "Liam", "Lindley", "Lotterer", "Lutz", "MacEachern", "Macdonald", "Mailto", "Marko", "Markus", "Martijn", "McCauley", "Melchner", "Moshe", "Murrell", "NNTP", "NTLM", "Nagano", "Newby", "Nicolai", "Nierstrasz", "Olly", "Oosten", "Panchenko", "Pimlott", "Pon", "Quaranta", "Radoslaw", "Radu", "Rai", "Rezic", "RobotUA", "Sarathy", "Schilli", "Schinder", "Shirazi", "Skyttä", "Slaven", "Spafford", "Stosberg", "Subbarao", "TCP", "Takanori", "Thoennes", "Thurn", "Tilly", "UA", "Ugai", "Unger", "UserAgent", "VanHeyningen", "Vandewege", "Ville", "WireShark", "Yee", "Yitzchak", "Yoshinari", "Zajac", "Zakharevich", "Zielinski", "Zoest", "afPuUsSedvhx", "de", "erik", "getprint", "getstore", "instantiation", "peterm", "shildreth" ], "wordlist" : "Pod::Wordlist" } }, "name" : "Test::PodSpelling", "version" : "2.007005" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." } }, "name" : "Git::Check", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::CheckStrictVersion", "name" : "CheckStrictVersion", "version" : "0.001" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::CheckChangeLog", "name" : "CheckChangeLog", "version" : "0.05" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "TestRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "UploadToCPAN", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "Markdown_Readme", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "LICENSE", "META.json" ], "match" : [] } }, "name" : "CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "recommends" } }, "name" : "@Git::VersionManager/pluginbundle version", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "LICENSE", "META.json", "README.md" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/release snapshot", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.77", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/Git::Tag", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Git::VersionManager/NextRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/post-release commit", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." } }, "name" : "Git::Push", "version" : "2.049" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "ConfirmRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.031" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.031" } }, "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "amire80 ", "Andreas J. Koenig ", "Andrew Grangaard ", "Andrew Hewus Fresh ", "Anirvan Chatterjee ", "Arne Johannessen ", "Axel Burri ", "BGMNT ", "Bill Mann ", "Bron Gondwana ", "Bryan Cardillo ", "Burak Gursoy ", "Chase Whitener ", "Christopher J. Madsen ", "Colin Newell ", "Daina Pettit ", "Daniel Hedlund ", "David E. Wheeler ", "DAVIDRW ", "David Standish ", "David Steinbrunner ", "dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>", "Desmond Daignault ", "Dmitriy Shamatrin ", "Doug Bell ", "Eric Johnson ", "Fabian Zeindler ", "Father Chrysostomos ", "Frank Maas ", "FWILES ", "Galen Huntington ", "Gavin Peters ", "Gerhard Poul ", "Gianni Ceccarelli ", "Gisle Aas ", "Graeme Thompson ", "Graham Knop ", "Gregory Oschwald ", "grr ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "Jakub Wilk ", "James Raspass ", "Jason A Fesler ", "Javier Puche ", "jefflee ", "Jeremy Mates ", "Joe Atzberger ", "john9art ", "John Wittkoski ", "Jonathan Dahan ", "Julien Fiegehenn ", "Kacper Gutowski ", "Karen Etheridge ", "Katarina Durechova ", "leedo ", "Mark Fowler ", "Mark Stosberg ", "Martin H. Sluka ", "Matthew Horsfall ", "Max Maischein ", "michael gong ", "Michael G. Schwern ", "Michiel Beijen ", "Mike Schilli ", "Moritz Onken ", "murphy ", "Naveed Massjouni ", "Nigel Gregoire ", "Nik LaBelle ", "Niko Tyni ", "Olaf Alders ", "Ondrej Hanak ", "Patrik Lundin ", "Peter Rabbitson ", "phrstbrn ", "Piotr Roszatycki ", "Robert Stone ", "Rolf Grossmann ", "Roman Galeev ", "ruff ", "Russell Shingleton ", "sasao ", "Sean M. Burke ", "Sebastian Paaske Tørholm ", "Sergey Romanov ", "Shoichi Kaji ", "Slaven Rezic ", "Slaven Rezic ", "Spiros Denaxas ", "Steffen Ullrich ", "Steve Hay ", "Takumi Akiyama ", "Theodore Robert Campbell Jr ", "Theo van Hoesel ", "Tim Couzins ", "Todd Lipcon ", "Tomasz Konojacki ", "Tom Hukins ", "Tony Finch ", "Toru Yamaguchi ", "turugina ", "uid39246 ", "Ville Skyttä ", "Vyacheslav Matyukhin ", "Yuri Karaban ", "Yury Zavarin ", "Yves Orton ", "Zefram " ], "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } PK! 5Operl5/x86_64-linux-thread-multi/.meta/LWP-UserAgent-DNS-Hosts-0.14/install.jsonnu6${"pathname":"M/MA/MASAKI/LWP-UserAgent-DNS-Hosts-0.14.tar.gz","version":"0.14","provides":{"LWP::Protocol::http::hosts":{"file":"lib/LWP/Protocol/http/hosts.pm"},"LWP::UserAgent::DNS::Hosts":{"version":"0.14","file":"lib/LWP/UserAgent/DNS/Hosts.pm"},"LWP::Protocol::https::hosts":{"file":"lib/LWP/Protocol/https/hosts.pm"}},"target":"LWP::UserAgent::DNS::Hosts","dist":"LWP-UserAgent-DNS-Hosts-0.14","name":"LWP::UserAgent::DNS::Hosts"}PK!UD  Nperl5/x86_64-linux-thread-multi/.meta/LWP-UserAgent-DNS-Hosts-0.14/MYMETA.jsonnu6${ "abstract" : "Override LWP HTTP/HTTPS request's host like /etc/hosts", "author" : [ "NAKAGAWA Masaki " ], "dynamic_config" : 0, "generated_by" : "Minilla/v3.1.10, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "LWP-UserAgent-DNS-Hosts", "no_index" : { "directory" : [ "t", "xt", "inc", "share", "eg", "examples", "author", "builder" ] }, "optional_features" : { "https" : { "description" : "SSL support", "prereqs" : { "runtime" : { "recommends" : { "LWP::Protocol::https" : "0" } }, "test" : { "requires" : { "HTTP::Daemon::SSL" : "0" } } } } }, "prereqs" : { "configure" : { "requires" : { "Module::Build::Tiny" : "0.035" } }, "develop" : { "requires" : { "Test::CPAN::Meta" : "0", "Test::MinimumVersion::Fast" : "0.04", "Test::PAUSE::Permissions" : "0.07", "Test::Pod" : "1.41", "Test::Spellunker" : "v0.2.7" } }, "runtime" : { "requires" : { "LWP::Protocol" : "0", "LWP::Protocol::http" : "0", "Scope::Guard" : "0", "parent" : "0", "perl" : "5.008001" } }, "test" : { "requires" : { "File::Temp" : "0", "LWP::UserAgent" : "0", "Test::Fake::HTTPD" : "0.08", "Test::More" : "0.98", "Test::UseAllModules" : "0" } } }, "provides" : { "LWP::Protocol::http::hosts" : { "file" : "lib/LWP/Protocol/http/hosts.pm" }, "LWP::Protocol::https::hosts" : { "file" : "lib/LWP/Protocol/https/hosts.pm" }, "LWP::UserAgent::DNS::Hosts" : { "file" : "lib/LWP/UserAgent/DNS/Hosts.pm", "version" : "0.14" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/masaki/p5-LWP-UserAgent-DNS-Hosts/issues" }, "homepage" : "https://github.com/masaki/p5-LWP-UserAgent-DNS-Hosts", "repository" : { "type" : "git", "url" : "git://github.com/masaki/p5-LWP-UserAgent-DNS-Hosts.git", "web" : "https://github.com/masaki/p5-LWP-UserAgent-DNS-Hosts" } }, "version" : "0.14", "x_authority" : "cpan:MASAKI", "x_contributors" : [ "Anirvan Chatterjee ", "Masaki Nakagawa ", "Petr Písař ", "Tatsuhiko Miyagawa " ], "x_serialization_backend" : "JSON::PP version 2.97001", "x_static_install" : 1 } PK!¬¦Hperl5/x86_64-linux-thread-multi/.meta/Canary-Stability-2013/install.jsonnu6${"dist":"Canary-Stability-2013","pathname":"M/ML/MLEHMANN/Canary-Stability-2013.tar.gz","provides":{"Canary::Stability":{"version":2013,"file":"Stability.pm"}},"name":"Canary::Stability","version":2013,"target":"Canary::Stability"}PK!QEGperl5/x86_64-linux-thread-multi/.meta/Canary-Stability-2013/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Canary-Stability", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } } }, "release_status" : "stable", "version" : "2013", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!*T?perl5/x86_64-linux-thread-multi/.meta/XML-SAX-1.02/install.jsonnu6${"version":1.02,"dist":"XML-SAX-1.02","name":"XML::SAX","pathname":"G/GR/GRANTM/XML-SAX-1.02.tar.gz","provides":{"XML::SAX::PurePerl::Reader":{"file":"lib/XML/SAX/PurePerl/Reader.pm"},"XML::SAX::PurePerl::Exception":{"file":"lib/XML/SAX/PurePerl/Exception.pm"},"XML::SAX::ParserFactory":{"version":1.02,"file":"lib/XML/SAX/ParserFactory.pm"},"XML::SAX::PurePerl::Productions":{"file":"lib/XML/SAX/PurePerl/Productions.pm"},"XML::SAX::DocumentLocator":{"file":"lib/XML/SAX/DocumentLocator.pm"},"XML::SAX::PurePerl::Reader::Stream":{"file":"lib/XML/SAX/PurePerl/Reader/Stream.pm"},"XML::SAX::PurePerl::DebugHandler":{"file":"lib/XML/SAX/PurePerl/DebugHandler.pm"},"XML::SAX::PurePerl::Reader::URI":{"file":"lib/XML/SAX/PurePerl/Reader/URI.pm"},"XML::SAX::PurePerl":{"file":"lib/XML/SAX/PurePerl.pm","version":1.02},"XML::SAX":{"file":"lib/XML/SAX.pm","version":1.02},"XML::SAX::PurePerl::Reader::String":{"file":"lib/XML/SAX/PurePerl/Reader/String.pm"}},"target":"XML::SAX"}PK!|Wxx>perl5/x86_64-linux-thread-multi/.meta/XML-SAX-1.02/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.0401, CPAN::Meta::Converter version 2.150005, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-SAX", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "File::Temp" : "0", "XML::NamespaceSupport" : "0.03", "XML::SAX::Base" : "1.05" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "web" : "https://github.com/grantm/xml-sax" } }, "version" : "1.02", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!s?perl5/x86_64-linux-thread-multi/.meta/JSON-XS-4.03/install.jsonnu6${"target":"JSON::XS","version":4.03,"provides":{"JSON::XS":{"version":4.03,"file":"XS.pm"}},"name":"JSON::XS","pathname":"M/ML/MLEHMANN/JSON-XS-4.03.tar.gz","dist":"JSON-XS-4.03"}PK!| >perl5/x86_64-linux-thread-multi/.meta/JSON-XS-4.03/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150001, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "JSON-XS", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "Canary::Stability" : "0", "ExtUtils::MakeMaker" : "6.52" } }, "runtime" : { "requires" : { "Types::Serialiser" : "0", "common::sense" : "0" } } }, "release_status" : "stable", "version" : "4.03", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!l5}}Dperl5/x86_64-linux-thread-multi/.meta/HTTP-Cookies-6.11/install.jsonnu6${"name":"HTTP::Cookies","pathname":"O/OA/OALDERS/HTTP-Cookies-6.11.tar.gz","dist":"HTTP-Cookies-6.11","version":6.11,"target":"HTTP::Cookies","provides":{"HTTP::Cookies::Microsoft":{"file":"lib/HTTP/Cookies/Microsoft.pm","version":6.11},"HTTP::Cookies::Netscape":{"version":6.11,"file":"lib/HTTP/Cookies/Netscape.pm"},"HTTP::Cookies":{"file":"lib/HTTP/Cookies.pm","version":6.11}}}PK!I~UoUoCperl5/x86_64-linux-thread-multi/.meta/HTTP-Cookies-6.11/MYMETA.jsonnu6${ "abstract" : "HTTP cookie jars", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.031, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTTP-Cookies", "no_index" : { "directory" : [ "examples", "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::CPAN::Changes" : "0.19", "Test::EOL" : "0", "Test::Mojibake" : "0", "Test::More" : "0.96", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Version" : "1", "warnings" : "0" } }, "runtime" : { "requires" : { "Carp" : "0", "HTTP::Date" : "6", "HTTP::Headers::Util" : "6", "HTTP::Request" : "0", "locale" : "0", "perl" : "5.008001", "strict" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "HTTP::Response" : "0", "Test::More" : "0", "URI" : "0", "warnings" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/HTTP-Cookies/issues" }, "homepage" : "https://github.com/libwww-perl/HTTP-Cookies", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/HTTP-Cookies.git", "web" : "https://github.com/libwww-perl/HTTP-Cookies" } }, "version" : "6.11", "x_Dist_Zilla" : { "perl" : { "version" : "5.034000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::Encoding", "name" : "Encoding", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::OSPrereqs", "config" : { "Dist::Zilla::Plugin::OSPrereqs" : { "os" : "MSWin32" } }, "name" : "MSWin32", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "AutoPrereqs", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "requires" } }, "name" : "Prereqs", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 0, "check_all_prereqs" : 0, "modules" : [ "Dist::Zilla::PluginBundle::Author::OALDERS" ], "phase" : "build", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, build", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 1, "check_all_prereqs" : 1, "modules" : [], "phase" : "release", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, release", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "@Author::OALDERS/CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/MakeMaker", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::CPANFile", "name" : "@Author::OALDERS/CPANFile", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::ContributorsFile", "name" : "@Author::OALDERS/ContributorsFile", "version" : "0.3.0" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Author::OALDERS/MetaJSON", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Author::OALDERS/MetaYAML", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Author::OALDERS/Manifest", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Author::OALDERS/MetaNoIndex", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Author::OALDERS/MetaConfig", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "@Author::OALDERS/MetaResources", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Author::OALDERS/License", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::OALDERS/InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "@Author::OALDERS/ExecDir", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "@Author::OALDERS/MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Author::OALDERS/PodSyntaxTests", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Test::CPAN::Changes", "config" : { "Dist::Zilla::Plugin::Test::CPAN::Changes" : { "changelog" : "Changes" } }, "name" : "@Author::OALDERS/Test::CPAN::Changes", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":InstallModules", ":TestFiles" ], "trailing_whitespace" : 1 } }, "name" : "@Author::OALDERS/Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "@Author::OALDERS/Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "@Author::OALDERS/Test::Portability", "version" : "2.001001" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Author::OALDERS/TestRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Author::OALDERS/Test::ReportPrereqs", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "@Author::OALDERS/Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::PodWeaver", "config" : { "Dist::Zilla::Plugin::PodWeaver" : { "finder" : [ ":InstallModules", ":PerlExecFiles" ], "plugins" : [ { "class" : "Pod::Weaver::Plugin::EnsurePod5", "name" : "@CorePrep/EnsurePod5", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::H1Nester", "name" : "@CorePrep/H1Nester", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::SingleEncoding", "name" : "@Default/SingleEncoding", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Name", "name" : "@Default/Name", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Version", "name" : "@Default/Version", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/prelude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "SYNOPSIS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "DESCRIPTION", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "OVERVIEW", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "ATTRIBUTES", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "METHODS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "FUNCTIONS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Leftovers", "name" : "@Default/Leftovers", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/postlude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Authors", "name" : "@Default/Authors", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Legal", "name" : "@Default/Legal", "version" : "4.019" } ] } }, "name" : "@Author::OALDERS/PodWeaver", "version" : "4.010" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "@Author::OALDERS/PruneCruft", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", "name" : "@Author::OALDERS/CopyFilesFromBuild", "version" : "0.170880" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "@Author::OALDERS/GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "@Author::OALDERS/Git::GatherDir", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Install" ], "match" : [] } }, "name" : "@Author::OALDERS/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Check", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.34.1", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "@Author::OALDERS/Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "@Author::OALDERS/ReadmeMdInBuild", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::StaticInstall", "config" : { "Dist::Zilla::Plugin::StaticInstall" : { "dry_run" : 0, "mode" : "off" } }, "name" : "@Author::OALDERS/StaticInstall", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::ShareDir", "name" : "@Author::OALDERS/ShareDir", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::CheckIssues", "name" : "@Author::OALDERS/CheckIssues", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Author::OALDERS/ConfirmRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Author::OALDERS/UploadToCPAN", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Author::OALDERS/@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Author::OALDERS/@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/release snapshot", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.11", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/Git::Tag", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Author::OALDERS/@Git::VersionManager/NextRelease", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/post-release commit", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.34.1", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Push", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 } }, "name" : "BumpVersionAfterRelease", "version" : "0.018" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.031" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.031" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.031" } }, "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "Alex Peters ", "Alex Peters ", "amire80 ", "Andreas J. Koenig ", "Bernhard M. Wiedemann ", "Bill Mann ", "Bron Gondwana ", "Charlie Hothersall-Thomas ", "Chase Whitener ", "Colin Newell ", "Colin Newell ", "Daniel Hedlund ", "Dave Menninger ", "David E. Wheeler ", "DAVIDRW ", "Father Chrysostomos ", "FWILES ", "Gavin Peters ", "George Grozdev ", "Graeme Thompson ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "James McCoy ", "James Raspass ", "jefflee ", "john9art ", "Mark Raymond ", "Mark Stosberg ", "Mike Schilli ", "Mohammad S Anwar ", "murphy ", "Olaf Alders ", "Ondrej Hanak ", "Perlover ", "Peter Rabbitson ", "Philip J. Ludlam ", "phrstbrn ", "Robert Stone ", "robnagler ", "Rolf Grossmann ", "ruff ", "sasao ", "Sean M. Burke ", "simbabque ", "Slaven Rezic ", "Spiros Denaxas ", "Steve Hay ", "Todd Lipcon ", "Tom Hukins ", "Tony Finch ", "Toru Yamaguchi ", "Ville Skyttä ", "Yuri Karaban ", "Zefram " ], "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_static_install" : 0 } PK!BggZperl5/x86_64-linux-thread-multi/.meta/Alien-Build-Plugin-Download-GitLab-0.01/install.jsonnu6${"name":"Alien::Build::Plugin::Download::GitLab","target":"Alien::Build::Plugin::Download::GitLab","pathname":"P/PL/PLICEASE/Alien-Build-Plugin-Download-GitLab-0.01.tar.gz","provides":{"Alien::Build::Plugin::Download::GitLab":{"version":0.01,"file":"lib/Alien/Build/Plugin/Download/GitLab.pm"}},"version":0.01,"dist":"Alien-Build-Plugin-Download-GitLab-0.01"}PK!VYperl5/x86_64-linux-thread-multi/.meta/Alien-Build-Plugin-Download-GitLab-0.01/MYMETA.jsonnu6${ "abstract" : "Alien::Build plugin to download from GitLab", "author" : [ "Graham Ollis " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.025, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Alien-Build-Plugin-Download-GitLab", "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "recommends" : { "Dist::Zilla::Plugin::Author::Plicease::Core" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.71", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::Community::ArrayAssignAref" : "0", "Perl::Critic::Policy::Community::BarewordFilehandles" : "0", "Perl::Critic::Policy::Community::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Community::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Community::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Community::DiscouragedModules" : "0", "Perl::Critic::Policy::Community::DollarAB" : "0", "Perl::Critic::Policy::Community::Each" : "0", "Perl::Critic::Policy::Community::EmptyReturn" : "0", "Perl::Critic::Policy::Community::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Community::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Community::LoopOnHash" : "0", "Perl::Critic::Policy::Community::ModPerl" : "0", "Perl::Critic::Policy::Community::OpenArgs" : "0", "Perl::Critic::Policy::Community::OverloadOptions" : "0", "Perl::Critic::Policy::Community::POSIXImports" : "0", "Perl::Critic::Policy::Community::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Community::PreferredAlternatives" : "0", "Perl::Critic::Policy::Community::StrictWarnings" : "0", "Perl::Critic::Policy::Community::Threads" : "0", "Perl::Critic::Policy::Community::Wantarray" : "0", "Perl::Critic::Policy::Community::WarningsSwitch" : "0", "Perl::Critic::Policy::Community::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "File::Spec" : "0", "FindBin" : "0", "Perl::Critic" : "0", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::Spelling::CommonMistakes" : "0", "Test::Spelling" : "0", "Test::Strict" : "0", "YAML" : "0" } }, "runtime" : { "requires" : { "Alien::Build::Plugin" : "0", "JSON::PP" : "0", "Path::Tiny" : "0", "URI" : "0", "URI::Escape" : "0", "perl" : "5.008004" } }, "test" : { "requires" : { "Test2::V0" : "0.000121" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/PerlAlien/Alien-Build-Plugin-Download-GitLab/issues" }, "homepage" : "https://metacpan.org/pod/Alien::Build::Plugin::Download::GitLab", "repository" : { "type" : "git", "url" : "git://github.com/PerlAlien/Alien-Build-Plugin-Download-GitLab.git", "web" : "https://github.com/PerlAlien/Alien-Build-Plugin-Download-GitLab" }, "x_IRC" : "irc://irc.perl.org/#native" }, "version" : "0.01", "x_generated_by_perl" : "v5.35.10", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } PK!d>Cperl5/x86_64-linux-thread-multi/.meta/HTML-Tagset-3.24/install.jsonnu6${"pathname":"P/PE/PETDANCE/HTML-Tagset-3.24.tar.gz","name":"HTML::Tagset","version":3.24,"dist":"HTML-Tagset-3.24","target":"HTML::Tagset","provides":{"HTML::Tagset":{"file":"lib/HTML/Tagset.pm","version":3.24}}}PK!򃫴Bperl5/x86_64-linux-thread-multi/.meta/HTML-Tagset-3.24/MYMETA.jsonnu6${ "abstract" : "Data tables useful in parsing HTML", "author" : [ "Andy Lester " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "artistic_2" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTML-Tagset", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "6.46" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.46" } }, "runtime" : { "requires" : { "perl" : "5.010001" } }, "test" : { "requires" : { "Test::More" : "0.95" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/petdance/html-tagset/issues" }, "homepage" : "https://github.com/petdance/html-tagset", "license" : [ "https://opensource.org/licenses/artistic-license-2.0" ], "repository" : { "url" : "https://github.com/petdance/html-tagset" } }, "version" : "3.24", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!W44Bperl5/x86_64-linux-thread-multi/.meta/XML-Parser-2.47/install.jsonnu6${"target":"XML::Parser","provides":{"XML::Parser::Style::Subs":{"file":"Parser/Style/Subs.pm"},"XML::Parser::Style::Debug":{"file":"Parser/Style/Debug.pm"},"XML::Parser::Expat":{"file":"Expat/Expat.pm","version":2.47},"XML::Parser::Style::Tree":{"file":"Parser/Style/Tree.pm"},"XML::Parser::Style::Stream":{"file":"Parser/Style/Stream.pm"},"XML::Parser::Style::Objects":{"file":"Parser/Style/Objects.pm"},"XML::Parser":{"version":2.47,"file":"Parser.pm"}},"pathname":"T/TO/TODDR/XML-Parser-2.47.tar.gz","name":"XML::Parser","version":2.47,"dist":"XML-Parser-2.47"}PK!c/Aperl5/x86_64-linux-thread-multi/.meta/XML-Parser-2.47/MYMETA.jsonnu6${ "abstract" : "A perl module for parsing XML documents", "author" : [ "Clark Cooper (coopercc@netheaven.com)" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-Parser", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "LWP::UserAgent" : "0", "perl" : "5.00405" } }, "test" : { "requires" : { "Test::More" : "0", "warnings" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/toddr/XML-Parser/issues" }, "repository" : { "url" : "http://github.com/toddr/XML-Parser" } }, "version" : "2.47", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!ث@perl5/x86_64-linux-thread-multi/.meta/Try-Tiny-0.31/install.jsonnu6${"name":"Try::Tiny","provides":{"Try::Tiny":{"version":"0.31","file":"lib/Try/Tiny.pm"}},"version":"0.31","target":"Try::Tiny","dist":"Try-Tiny-0.31","pathname":"E/ET/ETHER/Try-Tiny-0.31.tar.gz"}PK!}t?perl5/x86_64-linux-thread-multi/.meta/Try-Tiny-0.31/MYMETA.jsonnu6${ "abstract" : "Minimal try/catch with proper preservation of $@", "author" : [ "יובל קוג'מן (Yuval Kogman) ", "Jesse Luehrs " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.024, CPAN::Meta::Converter version 2.150010", "license" : [ "mit" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Try-Tiny", "no_index" : { "directory" : [ "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "recommends" : { "Dist::Zilla::PluginBundle::Author::ETHER" : "0.161", "Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007" }, "requires" : { "Capture::Tiny" : "0.12", "Encode" : "0", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Sub::Name" : "0.08", "Sub::Util" : "0", "Test::CPAN::Changes" : "0.19", "Test::CPAN::Meta" : "0", "Test::EOL" : "0", "Test::Kwalitee" : "1.21", "Test::MinimumVersion" : "0", "Test::Mojibake" : "0", "Test::More" : "0.96", "Test::NoTabs" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Pod::No404s" : "0", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "5.57", "constant" : "0", "perl" : "5.006", "strict" : "0", "warnings" : "0" }, "suggests" : { "Sub::Name" : "0.08", "Sub::Util" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "Test::More" : "0", "if" : "0" }, "suggests" : { "CPAN::Meta::Check" : "0.011", "CPAN::Meta::Requirements" : "0", "Capture::Tiny" : "0.12" } }, "x_Dist_Zilla" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::Authority" : "1.009", "Dist::Zilla::Plugin::AutoMetaResources" : "0", "Dist::Zilla::Plugin::AutoPrereqs" : "5.038", "Dist::Zilla::Plugin::Breaks" : "0", "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : "0.004", "Dist::Zilla::Plugin::CheckIssues" : "0", "Dist::Zilla::Plugin::CheckMetaResources" : "0", "Dist::Zilla::Plugin::CheckPrereqsIndexed" : "0.019", "Dist::Zilla::Plugin::CheckSelfDependency" : "0", "Dist::Zilla::Plugin::CheckStrictVersion" : "0", "Dist::Zilla::Plugin::ConfirmRelease" : "0", "Dist::Zilla::Plugin::CopyFilesFromRelease" : "0", "Dist::Zilla::Plugin::EnsureLatestPerl" : "0", "Dist::Zilla::Plugin::FileFinder::ByName" : "0", "Dist::Zilla::Plugin::FileFinder::Filter" : "0", "Dist::Zilla::Plugin::GenerateFile::FromShareDir" : "0", "Dist::Zilla::Plugin::Git::Check" : "0", "Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch" : "0.004", "Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts" : "0", "Dist::Zilla::Plugin::Git::Commit" : "2.020", "Dist::Zilla::Plugin::Git::Contributors" : "0.029", "Dist::Zilla::Plugin::Git::Describe" : "0.004", "Dist::Zilla::Plugin::Git::GatherDir" : "2.016", "Dist::Zilla::Plugin::Git::Push" : "0", "Dist::Zilla::Plugin::Git::Remote::Check" : "0", "Dist::Zilla::Plugin::Git::Tag" : "0", "Dist::Zilla::Plugin::GitHub::Update" : "0.40", "Dist::Zilla::Plugin::GithubMeta" : "0.54", "Dist::Zilla::Plugin::InstallGuide" : "1.200005", "Dist::Zilla::Plugin::Keywords" : "0.004", "Dist::Zilla::Plugin::License" : "5.038", "Dist::Zilla::Plugin::MakeMaker" : "0", "Dist::Zilla::Plugin::Manifest" : "0", "Dist::Zilla::Plugin::MetaConfig" : "0", "Dist::Zilla::Plugin::MetaJSON" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::MetaProvides::Package" : "1.15000002", "Dist::Zilla::Plugin::MetaTests" : "0", "Dist::Zilla::Plugin::MetaYAML" : "0", "Dist::Zilla::Plugin::MinimumPerl" : "1.006", "Dist::Zilla::Plugin::MojibakeTests" : "0.8", "Dist::Zilla::Plugin::NextRelease" : "5.033", "Dist::Zilla::Plugin::OnlyCorePrereqs" : "0", "Dist::Zilla::Plugin::PodCoverageTests" : "5.040", "Dist::Zilla::Plugin::PodSyntaxTests" : "5.040", "Dist::Zilla::Plugin::PodWeaver" : "4.008", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::Prereqs::AuthorDeps" : "0.006", "Dist::Zilla::Plugin::Prereqs::Soften" : "0", "Dist::Zilla::Plugin::PromptIfStale" : "0", "Dist::Zilla::Plugin::Readme" : "0", "Dist::Zilla::Plugin::ReadmeAnyFromPod" : "0.142180", "Dist::Zilla::Plugin::RewriteVersion::Transitional" : "0.006", "Dist::Zilla::Plugin::Run::AfterBuild" : "0.041", "Dist::Zilla::Plugin::Run::AfterRelease" : "0.038", "Dist::Zilla::Plugin::RunExtraTests" : "0.024", "Dist::Zilla::Plugin::StaticInstall" : "0.005", "Dist::Zilla::Plugin::Substitute" : "0", "Dist::Zilla::Plugin::Test::CPAN::Changes" : "0.012", "Dist::Zilla::Plugin::Test::ChangesHasContent" : "0", "Dist::Zilla::Plugin::Test::CheckBreaks" : "0.018", "Dist::Zilla::Plugin::Test::Compile" : "2.039", "Dist::Zilla::Plugin::Test::EOL" : "0.17", "Dist::Zilla::Plugin::Test::Kwalitee" : "2.10", "Dist::Zilla::Plugin::Test::MinimumVersion" : "2.000010", "Dist::Zilla::Plugin::Test::NoTabs" : "0.08", "Dist::Zilla::Plugin::Test::Pod::No404s" : "1.003", "Dist::Zilla::Plugin::Test::PodSpelling" : "2.006003", "Dist::Zilla::Plugin::Test::Portability" : "2.000007", "Dist::Zilla::Plugin::Test::ReportPrereqs" : "0.022", "Dist::Zilla::Plugin::TestRelease" : "0", "Dist::Zilla::Plugin::UploadToCPAN" : "0", "Dist::Zilla::Plugin::UseUnsafeInc" : "0", "Dist::Zilla::PluginBundle::Author::ETHER" : "0.136", "Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007", "Software::License::MIT" : "0" } } }, "provides" : { "Try::Tiny" : { "file" : "lib/Try/Tiny.pm", "version" : "0.31" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-Try-Tiny@rt.cpan.org", "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=Try-Tiny" }, "homepage" : "https://github.com/p5sagit/Try-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/p5sagit/Try-Tiny.git", "web" : "https://github.com/p5sagit/Try-Tiny" } }, "version" : "0.31", "x_Dist_Zilla" : { "perl" : { "version" : "5.035006" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::FileFinder::Filter", "name" : "all_files_but_using_5.10_features", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "recommends" } }, "name" : "@Author::ETHER/pluginbundle version", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 0, "check_all_prereqs" : 0, "modules" : [ "Dist::Zilla::PluginBundle::Author::ETHER" ], "phase" : "build", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::ETHER/stale modules, build", "version" : "0.057" }, { "class" : "Dist::Zilla::Plugin::FileFinder::ByName", "name" : "@Author::ETHER/Examples", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "CONTRIBUTING", "INSTALL", "LICENCE", "README.pod" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "@Author::ETHER/Git::GatherDir", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Author::ETHER/MetaYAML", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Author::ETHER/MetaJSON", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Readme", "name" : "@Author::ETHER/Readme", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Author::ETHER/Manifest", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Author::ETHER/License", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::GenerateFile::FromShareDir", "config" : { "Dist::Zilla::Plugin::GenerateFile::FromShareDir" : { "destination_filename" : "CONTRIBUTING", "dist" : "Dist-Zilla-PluginBundle-Author-ETHER", "encoding" : "UTF-8", "has_xs" : 0, "location" : "build", "source_filename" : "CONTRIBUTING" }, "Dist::Zilla::Role::RepoFileInjector" : { "allow_overwrite" : 1, "repo_root" : ".", "version" : "0.009" } }, "name" : "@Author::ETHER/generate CONTRIBUTING", "version" : "0.015" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::ETHER/InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : 1, "fail_on_warning" : "author", "fake_home" : 0, "filename" : "xt/author/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "develop", "script_finder" : [ ":PerlExecFiles", "@Author::ETHER/Examples" ], "skips" : [], "switch" : [] } }, "name" : "@Author::ETHER/Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::Test::NoTabs", "config" : { "Dist::Zilla::Plugin::Test::NoTabs" : { "filename" : "xt/author/no-tabs.t", "finder" : [ ":InstallModules", ":ExecFiles", "@Author::ETHER/Examples", ":TestFiles", ":ExtraTestFiles" ] } }, "name" : "@Author::ETHER/Test::NoTabs", "version" : "0.15" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":ExtraTestFiles", ":InstallModules", ":TestFiles", "@Author::ETHER/Examples" ], "trailing_whitespace" : 1 } }, "name" : "@Author::ETHER/Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::MetaTests", "name" : "@Author::ETHER/MetaTests", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Test::CPAN::Changes", "config" : { "Dist::Zilla::Plugin::Test::CPAN::Changes" : { "changelog" : "Changes" } }, "name" : "@Author::ETHER/Test::CPAN::Changes", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::Test::ChangesHasContent", "name" : "@Author::ETHER/Test::ChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Test::MinimumVersion", "config" : { "Dist::Zilla::Plugin::Test::MinimumVersion" : { "max_target_perl" : "5.006" } }, "name" : "@Author::ETHER/Test::MinimumVersion", "version" : "2.000010" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Author::ETHER/PodSyntaxTests", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::PodCoverageTests", "name" : "@Author::ETHER/PodCoverageTests", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Test::PodSpelling", "config" : { "Dist::Zilla::Plugin::Test::PodSpelling" : { "directories" : [ "examples", "lib", "script", "t", "xt" ], "spell_cmd" : "", "stopwords" : [ "irc" ], "wordlist" : "Pod::Wordlist" } }, "name" : "@Author::ETHER/Test::PodSpelling", "version" : "2.007005" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::No404s", "name" : "@Author::ETHER/Test::Pod::No404s", "version" : "1.004" }, { "class" : "Dist::Zilla::Plugin::Test::Kwalitee", "config" : { "Dist::Zilla::Plugin::Test::Kwalitee" : { "filename" : "xt/author/kwalitee.t", "skiptest" : [] } }, "name" : "@Author::ETHER/Test::Kwalitee", "version" : "2.12" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "@Author::ETHER/MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Author::ETHER/Test::ReportPrereqs", "version" : "0.028" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "@Author::ETHER/Test::Portability", "version" : "2.001000" }, { "class" : "Dist::Zilla::Plugin::Git::Describe", "name" : "@Author::ETHER/Git::Describe", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::PodWeaver", "config" : { "Dist::Zilla::Plugin::PodWeaver" : { "config_plugins" : [ "@Author::ETHER" ], "finder" : [ ":InstallModules", ":ExecFiles" ], "plugins" : [ { "class" : "Pod::Weaver::Plugin::EnsurePod5", "name" : "@Author::ETHER/EnsurePod5", "version" : "4.018" }, { "class" : "Pod::Weaver::Plugin::H1Nester", "name" : "@Author::ETHER/H1Nester", "version" : "4.018" }, { "class" : "Pod::Weaver::Plugin::SingleEncoding", "name" : "@Author::ETHER/SingleEncoding", "version" : "4.018" }, { "class" : "Pod::Weaver::Plugin::Transformer", "name" : "@Author::ETHER/List", "version" : "4.018" }, { "class" : "Pod::Weaver::Plugin::Transformer", "name" : "@Author::ETHER/Verbatim", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Author::ETHER/header", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Name", "name" : "@Author::ETHER/Name", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Version", "name" : "@Author::ETHER/Version", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Author::ETHER/prelude", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "SYNOPSIS", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "DESCRIPTION", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "OVERVIEW", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "ATTRIBUTES", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "METHODS", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "FUNCTIONS", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "TYPES", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Leftovers", "name" : "@Author::ETHER/Leftovers", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Author::ETHER/postlude", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::GenerateSection", "name" : "@Author::ETHER/generate SUPPORT", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::AllowOverride", "name" : "@Author::ETHER/allow override SUPPORT", "version" : "0.05" }, { "class" : "Pod::Weaver::Section::Authors", "name" : "@Author::ETHER/Authors", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::AllowOverride", "name" : "@Author::ETHER/allow override AUTHOR", "version" : "0.05" }, { "class" : "Pod::Weaver::Section::Contributors", "name" : "@Author::ETHER/Contributors", "version" : "0.009" }, { "class" : "Pod::Weaver::Section::Legal", "name" : "@Author::ETHER/Legal", "version" : "4.018" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Author::ETHER/footer", "version" : "4.018" } ] } }, "name" : "@Author::ETHER/PodWeaver", "version" : "4.009" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "@Author::ETHER/GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::AutoMetaResources", "name" : "@Author::ETHER/AutoMetaResources", "version" : "1.21" }, { "class" : "Dist::Zilla::Plugin::Authority", "name" : "@Author::ETHER/Authority", "version" : "1.009" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Author::ETHER/MetaNoIndex", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Package", "config" : { "Dist::Zilla::Plugin::MetaProvides::Package" : { "finder" : [ ":InstallModules" ], "finder_objects" : [ { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.024" } ], "include_underscores" : 0 }, "Dist::Zilla::Role::MetaProvider::Provider" : { "$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004", "inherit_missing" : 0, "inherit_version" : 0, "meta_noindex" : 1 }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::ETHER/MetaProvides::Package", "version" : "2.004003" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Author::ETHER/MetaConfig", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Keywords", "config" : { "Dist::Zilla::Plugin::Keywords" : { "keywords" : [] } }, "name" : "@Author::ETHER/Keywords", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::UseUnsafeInc", "config" : { "Dist::Zilla::Plugin::UseUnsafeInc" : { "dot_in_INC" : 0 } }, "name" : "@Author::ETHER/UseUnsafeInc", "version" : "0.001" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "@Author::ETHER/AutoPrereqs", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Prereqs::AuthorDeps", "name" : "@Author::ETHER/Prereqs::AuthorDeps", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::MinimumPerl", "name" : "@Author::ETHER/MinimumPerl", "version" : "1.006" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 9 } }, "name" : "@Author::ETHER/MakeMaker", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.31.1", "include_authors" : 0, "include_releaser" : 1, "order_by" : "commits", "paths" : [] } }, "name" : "@Author::ETHER/Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::StaticInstall", "config" : { "Dist::Zilla::Plugin::StaticInstall" : { "dry_run" : 1, "mode" : "auto" } }, "name" : "@Author::ETHER/StaticInstall", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 9 } }, "name" : "@Author::ETHER/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::CheckSelfDependency", "config" : { "Dist::Zilla::Plugin::CheckSelfDependency" : { "finder" : [ ":InstallModules" ] }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::ETHER/CheckSelfDependency", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Run::AfterBuild", "config" : { "Dist::Zilla::Plugin::Run::Role::Runner" : { "fatal_errors" : 1, "quiet" : 1, "run" : [ "bash -c \"test -e .ackrc && grep -q -- '--ignore-dir=.latest' .ackrc || echo '--ignore-dir=.latest' >> .ackrc; if [[ `dirname '%d'` != .build ]]; then test -e .ackrc && grep -q -- '--ignore-dir=%d' .ackrc || echo '--ignore-dir=%d' >> .ackrc; fi\"" ], "version" : "0.048" } }, "name" : "@Author::ETHER/.ackrc", "version" : "0.048" }, { "class" : "Dist::Zilla::Plugin::Run::AfterBuild", "config" : { "Dist::Zilla::Plugin::Run::Role::Runner" : { "eval" : [ "if ('%d' =~ /^%n-[.[:xdigit:]]+$/) { unlink '.latest'; symlink '%d', '.latest'; }" ], "fatal_errors" : 0, "quiet" : 1, "version" : "0.048" } }, "name" : "@Author::ETHER/.latest", "version" : "0.048" }, { "class" : "Dist::Zilla::Plugin::CheckStrictVersion", "name" : "@Author::ETHER/CheckStrictVersion", "version" : "0.001" }, { "class" : "Dist::Zilla::Plugin::CheckMetaResources", "name" : "@Author::ETHER/CheckMetaResources", "version" : "0.001" }, { "class" : "Dist::Zilla::Plugin::EnsureLatestPerl", "config" : { "Dist::Zilla::Plugin::EnsureLatestPerl" : { "Module::CoreList" : "5.20211120" } }, "name" : "@Author::ETHER/EnsureLatestPerl", "version" : "0.008" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 1, "check_all_prereqs" : 1, "modules" : [], "phase" : "release", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::ETHER/stale modules, release", "version" : "0.057" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." } }, "name" : "@Author::ETHER/initial check", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::CheckFor::MergeConflicts", "config" : { "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." } }, "name" : "@Author::ETHER/Git::CheckFor::MergeConflicts", "version" : "0.014" }, { "class" : "Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch", "config" : { "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." } }, "name" : "@Author::ETHER/Git::CheckFor::CorrectBranch", "version" : "0.014" }, { "class" : "Dist::Zilla::Plugin::Git::Remote::Check", "name" : "@Author::ETHER/Git::Remote::Check", "version" : "0.1.2" }, { "class" : "Dist::Zilla::Plugin::CheckPrereqsIndexed", "name" : "@Author::ETHER/CheckPrereqsIndexed", "version" : "0.021" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Author::ETHER/TestRelease", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." } }, "name" : "@Author::ETHER/after tests", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::CheckIssues", "name" : "@Author::ETHER/CheckIssues", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Author::ETHER/UploadToCPAN", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "CONTRIBUTING", "INSTALL", "LICENCE", "LICENSE", "ppport.h" ], "match" : [] } }, "name" : "@Author::ETHER/copy generated files", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "@Author::ETHER/ReadmeAnyFromPod", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "recommends" } }, "name" : "@Author::ETHER/@Git::VersionManager/pluginbundle version", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 1, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Author::ETHER/@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Author::ETHER/@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Author::ETHER/@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [ "." ], "commit_msg" : "%N-%v%t%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "CONTRIBUTING", "Changes", "INSTALL", "LICENCE", "README.pod" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::ETHER/@Git::VersionManager/release snapshot", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v0.31", "tag_format" : "v%V", "tag_message" : "v%v%t" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::ETHER/@Git::VersionManager/Git::Tag", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":InstallModules" ], "global" : 1, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Author::ETHER/@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Author::ETHER/@Git::VersionManager/NextRelease", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::ETHER/@Git::VersionManager/post-release commit", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "x_Dist_Zilla", "type" : "requires" } }, "name" : "@Author::ETHER/@Git::VersionManager/prereqs for @Git::VersionManager", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.31.1", "repo_root" : "." } }, "name" : "@Author::ETHER/Git::Push", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::GitHub::Update", "config" : { "Dist::Zilla::Plugin::GitHub::Update" : { "metacpan" : 1 } }, "name" : "@Author::ETHER/GitHub::Update", "version" : "0.48" }, { "class" : "Dist::Zilla::Plugin::Run::AfterRelease", "config" : { "Dist::Zilla::Plugin::Run::Role::Runner" : { "fatal_errors" : 0, "quiet" : 0, "run" : [ "REDACTED" ], "version" : "0.048" } }, "name" : "@Author::ETHER/install release", "version" : "0.048" }, { "class" : "Dist::Zilla::Plugin::Run::AfterRelease", "config" : { "Dist::Zilla::Plugin::Run::Role::Runner" : { "eval" : [ "print \"release complete!\\xa\"" ], "fatal_errors" : 1, "quiet" : 1, "version" : "0.048" } }, "name" : "@Author::ETHER/release complete", "version" : "0.048" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Author::ETHER/ConfirmRelease", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "x_Dist_Zilla", "type" : "requires" } }, "name" : "@Author::ETHER/prereqs for @Author::ETHER", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "Substitute", "version" : "0.006" }, { "class" : "Dist::Zilla::Plugin::Prereqs::Soften", "config" : { "Dist::Zilla::Plugin::Prereqs::Soften" : { "copy_to" : [ "develop.requires" ], "modules" : [ "Capture::Tiny", "Sub::Name", "Sub::Util" ], "modules_from_features" : null, "to_relationship" : "suggests" } }, "name" : "Prereqs::Soften", "version" : "0.006003" }, { "class" : "Dist::Zilla::Plugin::OnlyCorePrereqs", "config" : { "Dist::Zilla::Plugin::OnlyCorePrereqs" : { "also_disallow" : [], "check_dual_life_versions" : "0", "deprecated_ok" : 0, "phases" : [ "configure", "build", "runtime", "test" ], "skips" : [], "starting_version" : "to be determined from perl prereq" } }, "name" : "OnlyCorePrereqs", "version" : "0.024" }, { "class" : "Dist::Zilla::Plugin::Breaks", "name" : "Breaks", "version" : "0.004" }, { "class" : "Dist::Zilla::Plugin::Test::CheckBreaks", "config" : { "Dist::Zilla::Plugin::Test::CheckBreaks" : { "conflicts_module" : [], "no_forced_deps" : 1 }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "Test::CheckBreaks", "version" : "0.019" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.024" }, { "class" : "Dist::Zilla::Plugin::VerifyPhases", "name" : "@Author::ETHER/PHASE VERIFICATION", "version" : "0.016" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.024" } }, "x_authority" : "cpan:NUFFIN", "x_breaks" : { "Try::Tiny::Except" : "<= 0.01" }, "x_contributors" : [ "Karen Etheridge ", "Peter Rabbitson ", "Ricardo Signes ", "Mark Fowler ", "Graham Knop ", "Aristotle Pagaltzis ", "Dagfinn Ilmari Mannsåker ", "Lukas Mai ", "Alex ", "anaxagoras ", "Andrew Yates ", "awalker ", "chromatic ", "cm-perl ", "David Lowe ", "Glenn Fowler ", "Hans Dieter Pearcey ", "Jens Berthold ", "Jonathan Yu ", "Marc Mims ", "Mark Stosberg ", "Pali ", "Paul Howarth ", "Rudolf Leermakers " ], "x_generated_by_perl" : "v5.35.6", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "MIT", "x_use_unsafe_inc" : 0 } PK!Lit@perl5/x86_64-linux-thread-multi/.meta/IO-HTML-1.004/install.jsonnu6${"target":"IO::HTML","provides":{"IO::HTML":{"version":1.004,"file":"lib/IO/HTML.pm"}},"pathname":"C/CJ/CJM/IO-HTML-1.004.tar.gz","name":"IO::HTML","dist":"IO-HTML-1.004","version":1.004}PK!p]`Z*Z*?perl5/x86_64-linux-thread-multi/.meta/IO-HTML-1.004/MYMETA.jsonnu6${ "abstract" : "Open an HTML file with automatic charset detection", "author" : [ "Christopher J. Madsen " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.015, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "IO-HTML", "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::More" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "requires" : { "Carp" : "0", "Encode" : "2.10", "Exporter" : "5.57", "perl" : "5.008" } }, "test" : { "requires" : { "File::Temp" : "0", "Scalar::Util" : "0", "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "git://github.com/madsen/io-html.git", "web" : "https://github.com/madsen/io-html" } }, "version" : "1.004", "x_Dist_Zilla" : { "perl" : { "version" : "5.030001" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::VersionFromModule", "name" : "CJM/VersionFromModule", "version" : "0.08" }, { "class" : "Dist::Zilla::Plugin::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." } }, "name" : "CJM/GatherDir", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "CJM/PruneCruft", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::ManifestSkip", "name" : "CJM/ManifestSkip", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "CJM/MetaJSON", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "CJM/MetaYAML", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "CJM/License", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::Test::PrereqsFromMeta", "name" : "CJM/Test::PrereqsFromMeta", "version" : "4.23" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "CJM/PodSyntaxTests", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::PodCoverageTests", "name" : "CJM/PodCoverageTests", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::PodLoom", "config" : { "Pod::Loom version" : "0.08" }, "name" : "CJM/PodLoom", "version" : "5.001" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1 } }, "name" : "CJM/MakeMaker", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1 } }, "name" : "CJM/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "CJM/MetaConfig", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::MatchManifest", "name" : "CJM/MatchManifest", "version" : "6.000" }, { "class" : "Dist::Zilla::Plugin::RecommendedPrereqs", "name" : "CJM/RecommendedPrereqs", "version" : "4.21" }, { "class" : "Dist::Zilla::Plugin::CheckPrereqsIndexed", "name" : "CJM/CheckPrereqsIndexed", "version" : "0.020" }, { "class" : "Dist::Zilla::Plugin::GitVersionCheckCJM", "name" : "CJM/GitVersionCheckCJM", "version" : "4.27" }, { "class" : "Dist::Zilla::Plugin::TemplateCJM", "name" : "CJM/TemplateCJM", "version" : "5.002" }, { "class" : "Dist::Zilla::Plugin::Repository", "name" : "CJM/Repository", "version" : "0.24" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.28.0", "repo_root" : "." } }, "name" : "CJM/@Git/Check", "version" : "2.047" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "Updated Changes for %{MMMM d, yyyy}d%{ trial}t release of %v", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.28.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "CJM/@Git/Commit", "version" : "2.047" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "1.004", "tag_format" : "%v%t", "tag_message" : "Tagged %N %v%{ (trial release)}t" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.28.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "CJM/@Git/Tag", "version" : "2.047" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "github master" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.28.0", "repo_root" : "." } }, "name" : "CJM/@Git/Push", "version" : "2.047" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "CJM/TestRelease", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "CJM/UploadToCPAN", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::ArchiveRelease", "name" : "CJM/ArchiveRelease", "version" : "6.000" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "AutoPrereqs", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.015" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.015" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.015" } }, "x_generated_by_perl" : "v5.30.1", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } PK!.Hperl5/x86_64-linux-thread-multi/.meta/Types-Serialiser-1.01/install.jsonnu6${"target":"Types::Serialiser","name":"Types::Serialiser","provides":{"JSON::PP::Boolean":{"file":"Serialiser.pm","version":1.01},"Types::Serialiser::BooleanBase":{"file":"Serialiser.pm","version":1.01},"Types::Serialiser::Error":{"version":1.01,"file":"Serialiser.pm"},"Types::Serialiser":{"version":1.01,"file":"Serialiser.pm"}},"version":1.01,"pathname":"M/ML/MLEHMANN/Types-Serialiser-1.01.tar.gz","dist":"Types-Serialiser-1.01"}PK!VGperl5/x86_64-linux-thread-multi/.meta/Types-Serialiser-1.01/MYMETA.jsonnu6${ "abstract" : "unknown", "author" : [ "unknown" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150001, CPAN::Meta::Converter version 2.150010", "license" : [ "unknown" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Types-Serialiser", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "common::sense" : "0" } } }, "release_status" : "stable", "version" : "1.01", "x_serialization_backend" : "JSON::PP version 2.97001" } PK! Eperl5/x86_64-linux-thread-multi/.meta/local-lib-2.000029/install.jsonnu6${"pathname":"H/HA/HAARG/local-lib-2.000029.tar.gz","dist":"local-lib-2.000029","target":"local::lib","provides":{"local::lib":{"version":2.000029,"file":"lib/local/lib.pm"},"lib::core::only":{"file":"lib/lib/core/only.pm"}},"name":"local::lib","version":2.000029}PK!VJDperl5/x86_64-linux-thread-multi/.meta/local-lib-2.000029/MYMETA.jsonnu6${ "abstract" : "create and use a local lib/ for perl modules with PERL5LIB", "author" : [ "mst - Matt S. Trout (cpan:MSTROUT) " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "local-lib", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : {} }, "configure" : { "requires" : {} }, "develop" : { "requires" : { "Capture::Tiny" : "0", "Module::Build" : "0.36", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::More" : "0.8101", "Test::NoTabs" : "0", "Test::Pod" : "0" } }, "runtime" : { "requires" : { "perl" : "5.006" } }, "test" : { "requires" : { "Test::More" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-local-lib@rt.cpan.org", "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=local-lib" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "url" : "git://github.com/Perl-Toolchain-Gang/local-lib", "web" : "https://github.com/Perl-Toolchain-Gang/local-lib" }, "x_IRC" : "irc://irc.perl.org/#local-lib" }, "version" : "2.000029", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!55Dperl5/x86_64-linux-thread-multi/.meta/HTTP-Message-6.45/install.jsonnu6${"target":"HTTP::Headers","provides":{"HTTP::Response":{"file":"lib/HTTP/Response.pm","version":6.45},"HTTP::Headers::ETag":{"version":6.45,"file":"lib/HTTP/Headers/ETag.pm"},"HTTP::Headers::Util":{"version":6.45,"file":"lib/HTTP/Headers/Util.pm"},"HTTP::Headers::Auth":{"file":"lib/HTTP/Headers/Auth.pm","version":6.45},"HTTP::Request":{"version":6.45,"file":"lib/HTTP/Request.pm"},"HTTP::Message":{"version":6.45,"file":"lib/HTTP/Message.pm"},"HTTP::Headers":{"file":"lib/HTTP/Headers.pm","version":6.45},"HTTP::Config":{"version":6.45,"file":"lib/HTTP/Config.pm"},"HTTP::Status":{"file":"lib/HTTP/Status.pm","version":6.45},"HTTP::Request::Common":{"version":6.45,"file":"lib/HTTP/Request/Common.pm"}},"name":"HTTP::Message","pathname":"O/OA/OALDERS/HTTP-Message-6.45.tar.gz","version":6.45,"dist":"HTTP-Message-6.45"}PK!6zzCperl5/x86_64-linux-thread-multi/.meta/HTTP-Message-6.45/MYMETA.jsonnu6${ "abstract" : "HTTP style message (base class)", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTTP-Message", "no_index" : { "directory" : [ "examples", "t", "xt" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0", "perl" : "5.006" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "requires" : { "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Test::CPAN::Changes" : "0.19", "Test::Mojibake" : "0", "Test::More" : "0.96", "Test::Pod" : "1.41", "Test::Portability::Files" : "0", "Test::Version" : "1", "perl" : "5.006" } }, "runtime" : { "recommends" : { "IO::Compress::Brotli" : "0.004001", "IO::Uncompress::Brotli" : "0.004001" }, "requires" : { "Carp" : "0", "Clone" : "0.46", "Compress::Raw::Bzip2" : "0", "Compress::Raw::Zlib" : "2.062", "Encode" : "3.01", "Encode::Locale" : "1", "Exporter" : "5.57", "File::Spec" : "0", "HTTP::Date" : "6", "IO::Compress::Bzip2" : "2.021", "IO::Compress::Deflate" : "0", "IO::Compress::Gzip" : "0", "IO::HTML" : "0", "IO::Uncompress::Inflate" : "0", "IO::Uncompress::RawInflate" : "0", "LWP::MediaTypes" : "6", "MIME::Base64" : "2.1", "MIME::QuotedPrint" : "0", "URI" : "1.10", "parent" : "0", "perl" : "5.008001", "strict" : "0", "warnings" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900", "IO::Compress::Brotli" : "0.004001", "IO::Uncompress::Brotli" : "0.004001" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "File::Temp" : "0", "PerlIO::encoding" : "0", "Test::More" : "0.88", "Test::Needs" : "0", "Time::Local" : "0", "Try::Tiny" : "0", "URI::URL" : "0", "lib" : "0", "overload" : "0", "perl" : "5.008001" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/HTTP-Message/issues" }, "homepage" : "https://github.com/libwww-perl/HTTP-Message", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/HTTP-Message.git", "web" : "https://github.com/libwww-perl/HTTP-Message" }, "x_IRC" : "irc://irc.perl.org/#lwp", "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.45", "x_Dist_Zilla" : { "perl" : { "version" : "5.038000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "requires" } }, "name" : "Prereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 0, "check_all_prereqs" : 0, "modules" : [ "Dist::Zilla::PluginBundle::Author::OALDERS" ], "phase" : "build", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, build", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 1, "check_all_prereqs" : 1, "modules" : [], "phase" : "release", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, release", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "@Author::OALDERS/AutoPrereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "@Author::OALDERS/CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/MakeMaker", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CPANFile", "name" : "@Author::OALDERS/CPANFile", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::ContributorsFile", "name" : "@Author::OALDERS/ContributorsFile", "version" : "0.3.0" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Author::OALDERS/MetaJSON", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Author::OALDERS/MetaYAML", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Author::OALDERS/Manifest", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Author::OALDERS/MetaNoIndex", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Author::OALDERS/MetaConfig", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "@Author::OALDERS/MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Author::OALDERS/License", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::OALDERS/InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "@Author::OALDERS/ExecDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "@Author::OALDERS/MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Author::OALDERS/PodSyntaxTests", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::CPAN::Changes", "config" : { "Dist::Zilla::Plugin::Test::CPAN::Changes" : { "changelog" : "Changes" } }, "name" : "@Author::OALDERS/Test::CPAN::Changes", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "@Author::OALDERS/Test::Portability", "version" : "2.001001" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Author::OALDERS/TestRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Author::OALDERS/Test::ReportPrereqs", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "@Author::OALDERS/Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::MinimumPerl", "name" : "@Author::OALDERS/MinimumPerl", "version" : "1.006" }, { "class" : "Dist::Zilla::Plugin::PodWeaver", "config" : { "Dist::Zilla::Plugin::PodWeaver" : { "finder" : [ ":InstallModules", ":PerlExecFiles" ], "plugins" : [ { "class" : "Pod::Weaver::Plugin::EnsurePod5", "name" : "@CorePrep/EnsurePod5", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::H1Nester", "name" : "@CorePrep/H1Nester", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::SingleEncoding", "name" : "@Default/SingleEncoding", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Name", "name" : "@Default/Name", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Version", "name" : "@Default/Version", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/prelude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "SYNOPSIS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "DESCRIPTION", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "OVERVIEW", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "ATTRIBUTES", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "METHODS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "FUNCTIONS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Leftovers", "name" : "@Default/Leftovers", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/postlude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Authors", "name" : "@Default/Authors", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Legal", "name" : "@Default/Legal", "version" : "4.019" } ] } }, "name" : "@Author::OALDERS/PodWeaver", "version" : "4.010" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "@Author::OALDERS/PruneCruft", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", "name" : "@Author::OALDERS/CopyFilesFromBuild", "version" : "0.170880" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "@Author::OALDERS/GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "@Author::OALDERS/Git::GatherDir", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Install" ], "match" : [] } }, "name" : "@Author::OALDERS/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.42.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Check", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.42.0", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "@Author::OALDERS/Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "@Author::OALDERS/ReadmeMdInBuild", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::StaticInstall", "config" : { "Dist::Zilla::Plugin::StaticInstall" : { "dry_run" : 0, "mode" : "on" } }, "name" : "@Author::OALDERS/StaticInstall", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::ShareDir", "name" : "@Author::OALDERS/ShareDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CheckIssues", "name" : "@Author::OALDERS/CheckIssues", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Author::OALDERS/ConfirmRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Author::OALDERS/UploadToCPAN", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Author::OALDERS/@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Author::OALDERS/@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.42.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/release snapshot", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.45", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.42.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/Git::Tag", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Author::OALDERS/@Git::VersionManager/NextRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.42.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/post-release commit", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.42.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Push", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Prereqs::Soften", "config" : { "Dist::Zilla::Plugin::Prereqs::Soften" : { "copy_to" : [ "test.recommends" ], "modules" : [ "IO::Compress::Brotli", "IO::Uncompress::Brotli" ], "modules_from_features" : null, "to_relationship" : "recommends" } }, "name" : "Brotli", "version" : "0.006003" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : "1", "fail_on_warning" : "author", "fake_home" : 0, "filename" : "xt/author/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "develop", "script_finder" : [ ":PerlExecFiles" ], "skips" : [], "switch" : [] } }, "name" : "Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::Test::ChangesHasContent", "name" : "Test::ChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "Substitute", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.030" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.030" } }, "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "amire80 ", "Andreas J. Koenig ", "Andrei Grechkin ", "bayashi ", "Bill Mann ", "Brendan Byrd ", "Bron Gondwana ", "Chase Whitener ", "Christopher J. Madsen ", "chromatic ", "Dan Book ", "Daniel Hedlund ", "Daniel Trizen ", "David E. Wheeler ", "DAVIDRW ", "David Steinbrunner ", "Dorian Taylor ", "Eric Wastl ", "Father Chrysostomos ", "Felipe Gasper ", "FWILES ", "Galen Huntington ", "Gavin Peters ", "Gisle Aas ", "Graeme Thompson ", "Graham Knop ", "Håkon Hægland ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "Jakub Skory ", "Jakub Wilk ", "James Raspass ", "jefflee ", "Jerome Eteve ", "john9art ", "jonasbn ", "Julien Fiegehenn ", "Karen Etheridge ", "Lars Dɪᴇᴄᴋᴏᴡ 迪拉斯 ", "Mark Overmeer ", "Mark Stosberg ", "Martin H. Sluka ", "Matthew Chae ", "Max Maischein ", "Michael Schout ", "Michal Josef Špaček ", "Mickey Nasriachi ", "Mike Schilli ", "murphy ", "nanto_vi, TOYAMA Nao ", "Neil Bowers ", "Olaf Alders ", "Olivier Mengué ", "Ondrej Hanak ", "openstrike ", "Peter Rabbitson ", "phrstbrn ", "Robert Rothenberg ", "Robert Rothenberg ", "Robert Stone ", "Rolf Grossmann ", "ruff ", "sasao ", "Saturday Walkers Club ", "Sean M. Burke ", "Slaven Rezic ", "Spiros Denaxas ", "Steve Hay ", "Tatsuhiko Miyagawa ", "Tatsuhiko Miyagawa ", "Theo van Hoesel ", "Tigran ", "Tobias Leich ", "Todd Lipcon ", "tokuhirom ", "Tom Hukins ", "Tony Finch ", "Toru Yamaguchi ", "tzccinct ", "uid39246 ", "Ville Skyttä ", "Vít Strádal ", "William Storey ", "Yuri Karaban ", "Zakariyya Mughal ", "Zefram " ], "x_generated_by_perl" : "v5.38.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_static_install" : 1 } PK!EDperl5/x86_64-linux-thread-multi/.meta/FFI-CheckLib-0.31/install.jsonnu6${"version":0.31,"dist":"FFI-CheckLib-0.31","provides":{"FFI::CheckLib":{"file":"lib/FFI/CheckLib.pm","version":0.31}},"target":"FFI::CheckLib","name":"FFI::CheckLib","pathname":"P/PL/PLICEASE/FFI-CheckLib-0.31.tar.gz"}PK!"͟Cperl5/x86_64-linux-thread-multi/.meta/FFI-CheckLib-0.31/MYMETA.jsonnu6${ "abstract" : "Check that a library is available for FFI", "author" : [ "Graham Ollis " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.025, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "FFI-CheckLib", "no_index" : { "directory" : [ "corpus" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "recommends" : { "Dist::Zilla::Plugin::Author::Plicease::Thanks" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.72", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::Freenode::ArrayAssignAref" : "0", "Perl::Critic::Policy::Freenode::BarewordFilehandles" : "0", "Perl::Critic::Policy::Freenode::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Freenode::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Freenode::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Freenode::DiscouragedModules" : "0", "Perl::Critic::Policy::Freenode::DollarAB" : "0", "Perl::Critic::Policy::Freenode::Each" : "0", "Perl::Critic::Policy::Freenode::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Freenode::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Freenode::LoopOnHash" : "0", "Perl::Critic::Policy::Freenode::ModPerl" : "0", "Perl::Critic::Policy::Freenode::OpenArgs" : "0", "Perl::Critic::Policy::Freenode::OverloadOptions" : "0", "Perl::Critic::Policy::Freenode::POSIXImports" : "0", "Perl::Critic::Policy::Freenode::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Freenode::PreferredAlternatives" : "0", "Perl::Critic::Policy::Freenode::StrictWarnings" : "0", "Perl::Critic::Policy::Freenode::Threads" : "0", "Perl::Critic::Policy::Freenode::WarningsSwitch" : "0", "Perl::Critic::Policy::Freenode::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "FindBin" : "0", "Perl::Critic" : "0", "Test2::Require::EnvVar" : "0.000121", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::LinkCheck::Lite" : "0", "Test::Pod::Spelling::CommonMistakes" : "0", "Test::Spelling" : "0", "Test::Strict" : "0", "YAML" : "0" } }, "runtime" : { "requires" : { "File::Which" : "0", "List::Util" : "1.33", "perl" : "5.006" } }, "test" : { "requires" : { "Test2::API" : "1.302015", "Test2::Require::EnvVar" : "0.000121", "Test2::Require::Module" : "0.000121", "Test2::V0" : "0.000121" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/PerlFFI/FFI-CheckLib/issues" }, "homepage" : "https://metacpan.org/pod/FFI::CheckLib", "repository" : { "type" : "git", "url" : "git://github.com/PerlFFI/FFI-CheckLib.git", "web" : "https://github.com/PerlFFI/FFI-CheckLib" }, "x_IRC" : "irc://irc.perl.org/#native" }, "version" : "0.31", "x_contributors" : [ "Graham Ollis ", "Bakkiaraj Murugesan (bakkiaraj)", "Dan Book (grinnz, DBOOK)", "Ilya Pavlov (Ilya, ILUX)", "Shawn Laffan (SLAFFAN)", "Petr Písař (ppisar)", "Michael R. Davis (MRDVT)", "Shawn Laffan (SLAFFAN)", "Carlos D. Álvaro (cdalvaro)" ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } PK!mmAperl5/x86_64-linux-thread-multi/.meta/YAML-Syck-1.34/install.jsonnu6${"target":"YAML::Syck","provides":{"YAML::Loader::Syck":{"file":"lib/YAML/Loader/Syck.pm"},"YAML::Dumper::Syck":{"file":"lib/YAML/Dumper/Syck.pm"},"JSON::Syck":{"file":"lib/JSON/Syck.pm","version":1.34},"YAML::Syck":{"file":"lib/YAML/Syck.pm","version":1.34}},"name":"YAML::Syck","version":1.34,"pathname":"T/TO/TODDR/YAML-Syck-1.34.tar.gz","dist":"YAML-Syck-1.34"}PK!Q^@perl5/x86_64-linux-thread-multi/.meta/YAML-Syck-1.34/MYMETA.jsonnu6${ "abstract" : "Fast, lightweight YAML loader and dumper", "author" : [ "Todd Rinaldo " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.44, CPAN::Meta::Converter version 2.150010", "license" : [ "mit" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "YAML-Syck", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "Test::More" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.006" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/toddr/YAML-Syck/issues" }, "homepage" : "http://github.com/toddr/YAML-Syck", "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "http://github.com/toddr/YAML-Syck" } }, "version" : "1.34", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!uե=perl5/x86_64-linux-thread-multi/.meta/Clone-0.46/install.jsonnu6${"provides":{"Clone":{"version":0.46,"file":"Clone.pm"}},"target":"Clone","dist":"Clone-0.46","version":0.46,"name":"Clone","pathname":"G/GA/GARU/Clone-0.46.tar.gz"}PK!p5<perl5/x86_64-linux-thread-multi/.meta/Clone-0.46/MYMETA.jsonnu6${ "abstract" : "recursively copy Perl datatypes", "author" : [ "Ray Finch " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Clone", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "test" : { "requires" : { "B::COW" : "0.004", "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/garu/Clone/issues" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "http://github.com/garu/Clone" } }, "version" : "0.46", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!yBperl5/x86_64-linux-thread-multi/.meta/HTTP-Tiny-0.088/install.jsonnu6${"pathname":"D/DA/DAGOLDEN/HTTP-Tiny-0.088.tar.gz","dist":"HTTP-Tiny-0.088","target":"HTTP::Tiny","name":"HTTP::Tiny","provides":{"HTTP::Tiny":{"file":"lib/HTTP/Tiny.pm","version":"0.088"}},"version":"0.088"}PK!1Aperl5/x86_64-linux-thread-multi/.meta/HTTP-Tiny-0.088/MYMETA.jsonnu6${ "abstract" : "A small, simple, correct HTTP/1.1 client", "author" : [ "Christian Hansen ", "David Golden " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTTP-Tiny", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::ReleaseStatus::FromVersion" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::DAGOLDEN" : "0.072", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Perl::Critic::Policy::Lax::ProhibitStringyEval::ExceptForRequire" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Software::License::Perl_5" : "0", "Test::CPAN::Meta" : "0", "Test::MinimumVersion" : "0", "Test::More" : "0", "Test::Perl::Critic" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1", "perl" : "5.006" } }, "runtime" : { "recommends" : { "HTTP::CookieJar" : "0.001", "IO::Socket::IP" : "0.32", "IO::Socket::SSL" : "1.42", "Mozilla::CA" : "20160104", "Net::SSLeay" : "1.49" }, "requires" : { "Carp" : "0", "Fcntl" : "0", "IO::Socket" : "0", "MIME::Base64" : "0", "Socket" : "0", "Time::Local" : "0", "bytes" : "0", "perl" : "5.006", "strict" : "0", "warnings" : "0" }, "suggests" : { "IO::Socket::SSL" : "1.56" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "Data::Dumper" : "0", "Exporter" : "0", "ExtUtils::MakeMaker" : "0", "File::Basename" : "0", "File::Spec" : "0", "File::Temp" : "0", "IO::Dir" : "0", "IO::File" : "0", "IO::Socket::INET" : "0", "IPC::Cmd" : "0", "Test::More" : "0.96", "lib" : "0", "open" : "0" } } }, "provides" : { "HTTP::Tiny" : { "file" : "lib/HTTP/Tiny.pm", "version" : "0.088" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/Perl-Toolchain-Gang/HTTP-Tiny/issues" }, "homepage" : "https://github.com/Perl-Toolchain-Gang/HTTP-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/Perl-Toolchain-Gang/HTTP-Tiny.git", "web" : "https://github.com/Perl-Toolchain-Gang/HTTP-Tiny" } }, "version" : "0.088", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "Alan Gardner ", "Alessandro Ghedini ", "A. Sinan Unur ", "Brad Gilbert ", "brian m. carlson ", "Chris Nehren ", "Chris Weyl ", "Claes Jakobsson ", "Clinton Gormley ", "Craig A. Berry ", "Craig Berry ", "David Golden ", "David Mitchell ", "Dean Pearce ", "Edward Zborowski ", "Felipe Gasper ", "Graham Knop ", "Greg Kennedy ", "James E Keenan ", "James Raspass ", "Jeremy Mates ", "Jess Robinson ", "Karen Etheridge ", "Lukas Eklund ", "Martin J. Evans ", "Martin-Louis Bright ", "Matthew Horsfall ", "Michael R. Davis ", "Mike Doherty ", "Nicolas Rochelemagne ", "Olaf Alders ", "Olivier Mengué ", "Petr Písař ", "sanjay-cpu ", "Serguei Trouchelle ", "Shoichi Kaji ", "SkyMarshal ", "Sören Kornetzki ", "Steve Grazzini ", "Stig Palmquist ", "Syohei YOSHIDA ", "Tatsuhiko Miyagawa ", "Tom Hukins ", "Tony Cook ", "Xavier Guimard " ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } PK!S Operl5/x86_64-linux-thread-multi/.meta/CPAN-Meta-Requirements-2.143/install.jsonnu6${"pathname":"R/RJ/RJBS/CPAN-Meta-Requirements-2.143.tar.gz","dist":"CPAN-Meta-Requirements-2.143","target":"CPAN::Meta::Requirements","provides":{"CPAN::Meta::Requirements::Range":{"version":"2.143","file":"lib/CPAN/Meta/Requirements/Range.pm"},"CPAN::Meta::Requirements":{"version":"2.143","file":"lib/CPAN/Meta/Requirements.pm"}},"name":"CPAN::Meta::Requirements","version":"2.143"}PK!&9~~Nperl5/x86_64-linux-thread-multi/.meta/CPAN-Meta-Requirements-2.143/MYMETA.jsonnu6${ "abstract" : "a set of version requirements for a CPAN dist", "author" : [ "David Golden ", "Ricardo Signes " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "CPAN-Meta-Requirements", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::Authority" : "0", "Dist::Zilla::Plugin::AutoPrereqs" : "0", "Dist::Zilla::Plugin::BumpVersionAfterRelease" : "0", "Dist::Zilla::Plugin::CPANFile" : "0", "Dist::Zilla::Plugin::CheckChangesHasContent" : "0", "Dist::Zilla::Plugin::CheckMetaResources" : "0", "Dist::Zilla::Plugin::CheckPrereqsIndexed" : "0", "Dist::Zilla::Plugin::ConfirmRelease" : "0", "Dist::Zilla::Plugin::CopyFilesFromBuild::Filtered" : "0", "Dist::Zilla::Plugin::ExecDir" : "0", "Dist::Zilla::Plugin::Git::Check" : "0", "Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch" : "0", "Dist::Zilla::Plugin::Git::Commit" : "0", "Dist::Zilla::Plugin::Git::Contributors" : "0", "Dist::Zilla::Plugin::Git::GatherDir" : "0", "Dist::Zilla::Plugin::Git::Push" : "0", "Dist::Zilla::Plugin::Git::Tag" : "0", "Dist::Zilla::Plugin::GithubMeta" : "0", "Dist::Zilla::Plugin::InsertCopyright" : "0", "Dist::Zilla::Plugin::License" : "0", "Dist::Zilla::Plugin::MakeMaker" : "0", "Dist::Zilla::Plugin::MakeMaker::Highlander" : "0.003", "Dist::Zilla::Plugin::Manifest" : "0", "Dist::Zilla::Plugin::ManifestSkip" : "0", "Dist::Zilla::Plugin::MetaJSON" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::MetaProvides::Package" : "0", "Dist::Zilla::Plugin::MetaTests" : "0", "Dist::Zilla::Plugin::MetaYAML" : "0", "Dist::Zilla::Plugin::MinimumPerl" : "0", "Dist::Zilla::Plugin::NextRelease" : "0", "Dist::Zilla::Plugin::OnlyCorePrereqs" : "0.014", "Dist::Zilla::Plugin::Pod2Readme" : "0", "Dist::Zilla::Plugin::PodCoverageTests" : "0", "Dist::Zilla::Plugin::PodSyntaxTests" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::Prereqs::AuthorDeps" : "0", "Dist::Zilla::Plugin::PromptIfStale" : "0", "Dist::Zilla::Plugin::PruneCruft" : "0", "Dist::Zilla::Plugin::RewriteVersion" : "0", "Dist::Zilla::Plugin::RunExtraTests" : "0", "Dist::Zilla::Plugin::ShareDir" : "0", "Dist::Zilla::Plugin::SurgicalPodWeaver" : "0", "Dist::Zilla::Plugin::Test::Compile" : "0", "Dist::Zilla::Plugin::Test::MinimumVersion" : "0", "Dist::Zilla::Plugin::Test::Perl::Critic" : "0", "Dist::Zilla::Plugin::Test::PodSpelling" : "0", "Dist::Zilla::Plugin::Test::Portability" : "0", "Dist::Zilla::Plugin::Test::ReportPrereqs" : "0", "Dist::Zilla::Plugin::Test::Version" : "0", "Dist::Zilla::Plugin::TestRelease" : "0", "Dist::Zilla::Plugin::UploadToCPAN" : "0", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Weaver::PluginBundle::DAGOLDEN" : "0", "Pod::Wordlist" : "0", "Software::License::Perl_5" : "0", "Test::CPAN::Meta" : "0", "Test::MinimumVersion" : "0", "Test::More" : "0", "Test::Perl::Critic" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1" } }, "runtime" : { "requires" : { "B" : "0", "Carp" : "0", "perl" : "5.010000", "strict" : "0", "version" : "0.88", "warnings" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "Test::More" : "0.88", "version" : "0.88" } } }, "provides" : { "CPAN::Meta::Requirements" : { "file" : "lib/CPAN/Meta/Requirements.pm", "version" : "2.143" }, "CPAN::Meta::Requirements::Range" : { "file" : "lib/CPAN/Meta/Requirements/Range.pm", "version" : "2.143" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements/issues" }, "homepage" : "https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements", "repository" : { "type" : "git", "url" : "https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements.git", "web" : "https://github.com/Perl-Toolchain-Gang/CPAN-Meta-Requirements" } }, "version" : "2.143", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "Ed J ", "Graham Knop ", "Karen Etheridge ", "Leon Timmermans ", "Paul Howarth ", "Ricardo Signes ", "robario ", "Tatsuhiko Miyagawa ", "Tatsuhiko Miyagawa " ], "x_generated_by_perl" : "v5.37.10", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } PK!]PmmCperl5/x86_64-linux-thread-multi/.meta/Alien-Build-2.80/install.jsonnu6${"target":"Alien::Base::Wrapper","name":"Alien::Build","pathname":"P/PL/PLICEASE/Alien-Build-2.80.tar.gz","dist":"Alien-Build-2.80","version":"2.80","provides":{"Alien::Build::Log::Abbreviate":{"version":"2.80","file":"lib/Alien/Build/Log/Abbreviate.pm"},"alienfile":{"version":"2.80","file":"lib/alienfile.pm"},"Alien::Build::Log::Default":{"file":"lib/Alien/Build/Log/Default.pm","version":"2.80"},"Alien::Build::Plugin::Build::Copy":{"version":"2.80","file":"lib/Alien/Build/Plugin/Build/Copy.pm"},"Alien::Build::Plugin":{"version":"2.80","file":"lib/Alien/Build/Plugin.pm"},"Alien::Build::Plugin::Fetch::LocalDir":{"file":"lib/Alien/Build/Plugin/Fetch/LocalDir.pm","version":"2.80"},"Test::Alien":{"file":"lib/Test/Alien.pm","version":"2.80"},"Alien::Build::Util":{"file":"lib/Alien/Build/Util.pm","version":"2.80"},"Alien::Build::Plugin::Extract::File":{"version":"2.80","file":"lib/Alien/Build/Plugin/Extract/File.pm"},"Alien::Build::Plugin::Build::CMake":{"version":"2.80","file":"lib/Alien/Build/Plugin/Build/CMake.pm"},"Alien::Build::Plugin::Extract::CommandLine":{"version":"2.80","file":"lib/Alien/Build/Plugin/Extract/CommandLine.pm"},"Alien::Build::Interpolate::Helper":{"version":"2.80","file":"lib/Alien/Build/Interpolate.pm"},"Alien::Build::Plugin::Prefer::GoodVersion":{"version":"2.80","file":"lib/Alien/Build/Plugin/Prefer/GoodVersion.pm"},"Alien::Build::Version::Basic":{"file":"lib/Alien/Build/Version/Basic.pm","version":"2.80"},"Alien::Build::Plugin::Fetch::LWP":{"file":"lib/Alien/Build/Plugin/Fetch/LWP.pm","version":"2.80"},"Alien::Build::Plugin::Core::Tail":{"file":"lib/Alien/Build/Plugin/Core/Tail.pm","version":"2.80"},"Alien::Build::Plugin::Build::Make":{"version":"2.80","file":"lib/Alien/Build/Plugin/Build/Make.pm"},"Alien::Build::rc":{"file":"lib/Alien/Build.pm","version":"2.80"},"Alien::Util":{"file":"lib/Alien/Util.pm","version":"2.80"},"Test::Alien::Synthetic":{"version":"2.80","file":"lib/Test/Alien/Synthetic.pm"},"Alien::Build::TempDir":{"file":"lib/Alien/Build.pm","version":"2.80"},"Alien::Build::Plugin::Extract::Negotiate":{"file":"lib/Alien/Build/Plugin/Extract/Negotiate.pm","version":"2.80"},"Alien::Build::Plugin::Build::MSYS":{"file":"lib/Alien/Build/Plugin/Build/MSYS.pm","version":"2.80"},"Alien::Build::Plugin::Core::Gather":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/Gather.pm"},"Alien::Build::Plugin::Core::FFI":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/FFI.pm"},"Test::Alien::CanCompile":{"file":"lib/Test/Alien/CanCompile.pm","version":"2.80"},"Alien::Build::MM":{"file":"lib/Alien/Build/MM.pm","version":"2.80"},"Alien::Build":{"version":"2.80","file":"lib/Alien/Build.pm"},"Alien::Build::Plugin::Decode::DirListing":{"file":"lib/Alien/Build/Plugin/Decode/DirListing.pm","version":"2.80"},"Alien::Build::Plugin::Fetch::Wget":{"file":"lib/Alien/Build/Plugin/Fetch/Wget.pm","version":"2.80"},"Alien::Build::CommandSequence":{"version":"2.80","file":"lib/Alien/Build/CommandSequence.pm"},"Alien::Build::Temp":{"version":"2.80","file":"lib/Alien/Build/Temp.pm"},"Alien::Build::Plugin::Probe::CBuilder":{"version":"2.80","file":"lib/Alien/Build/Plugin/Probe/CBuilder.pm"},"Alien::Role":{"file":"lib/Alien/Role.pm","version":"2.80"},"Alien::Build::Plugin::Build::SearchDep":{"version":"2.80","file":"lib/Alien/Build/Plugin/Build/SearchDep.pm"},"Alien::Base::Wrapper":{"file":"lib/Alien/Base/Wrapper.pm","version":"2.80"},"Alien::Build::Plugin::Probe::Vcpkg":{"version":"2.80","file":"lib/Alien/Build/Plugin/Probe/Vcpkg.pm"},"Alien::Build::Plugin::Download::Negotiate":{"file":"lib/Alien/Build/Plugin/Download/Negotiate.pm","version":"2.80"},"Alien::Build::Plugin::Decode::DirListingFtpcopy":{"file":"lib/Alien/Build/Plugin/Decode/DirListingFtpcopy.pm","version":"2.80"},"Alien::Build::Plugin::Test::Mock":{"version":"2.80","file":"lib/Alien/Build/Plugin/Test/Mock.pm"},"Alien::Base":{"version":"2.80","file":"lib/Alien/Base.pm"},"Alien::Build::Plugin::Core::Setup":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/Setup.pm"},"Alien::Build::Plugin::PkgConfig::Negotiate":{"file":"lib/Alien/Build/Plugin/PkgConfig/Negotiate.pm","version":"2.80"},"Alien::Build::Plugin::Prefer::BadVersion":{"file":"lib/Alien/Build/Plugin/Prefer/BadVersion.pm","version":"2.80"},"Test::Alien::Run":{"file":"lib/Test/Alien/Run.pm","version":"2.80"},"Alien::Build::Plugin::Decode::Mojo":{"file":"lib/Alien/Build/Plugin/Decode/Mojo.pm","version":"2.80"},"Alien::Build::Plugin::Fetch::Local":{"version":"2.80","file":"lib/Alien/Build/Plugin/Fetch/Local.pm"},"Alien::Build::Plugin::Extract::Directory":{"file":"lib/Alien/Build/Plugin/Extract/Directory.pm","version":"2.80"},"Alien::Build::Interpolate::Default":{"version":"2.80","file":"lib/Alien/Build/Interpolate/Default.pm"},"Alien::Build::Plugin::PkgConfig::CommandLine":{"version":"2.80","file":"lib/Alien/Build/Plugin/PkgConfig/CommandLine.pm"},"Alien::Build::Plugin::PkgConfig::MakeStatic":{"version":"2.80","file":"lib/Alien/Build/Plugin/PkgConfig/MakeStatic.pm"},"Alien::Build::Plugin::Extract::ArchiveTar":{"file":"lib/Alien/Build/Plugin/Extract/ArchiveTar.pm","version":"2.80"},"Alien::Base::PkgConfig":{"version":"2.80","file":"lib/Alien/Base/PkgConfig.pm"},"Test::Alien::CanPlatypus":{"file":"lib/Test/Alien/CanPlatypus.pm","version":"2.80"},"Alien::Build::Plugin::Core::Legacy":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/Legacy.pm"},"Test::Alien::Build":{"file":"lib/Test/Alien/Build.pm","version":"2.80"},"Alien::Build::Plugin::PkgConfig::PP":{"version":"2.80","file":"lib/Alien/Build/Plugin/PkgConfig/PP.pm"},"Alien::Build::Plugin::Gather::IsolateDynamic":{"version":"2.80","file":"lib/Alien/Build/Plugin/Gather/IsolateDynamic.pm"},"Alien::Build::PluginMeta":{"version":"2.80","file":"lib/Alien/Build/Plugin.pm"},"Test::Alien::Diag":{"version":"2.80","file":"lib/Test/Alien/Diag.pm"},"Alien::Build::Meta":{"version":"2.80","file":"lib/Alien/Build.pm"},"Alien::Build::Plugin::Probe::CommandLine":{"file":"lib/Alien/Build/Plugin/Probe/CommandLine.pm","version":"2.80"},"Alien::Build::Plugin::Digest::SHAPP":{"file":"lib/Alien/Build/Plugin/Digest/SHAPP.pm","version":"2.80"},"Alien::Build::Helper":{"version":"2.80","file":"lib/Alien/Build/Interpolate.pm"},"Alien::Build::Plugin::Decode::HTML":{"file":"lib/Alien/Build/Plugin/Decode/HTML.pm","version":"2.80"},"Alien::Build::Plugin::Digest::SHA":{"file":"lib/Alien/Build/Plugin/Digest/SHA.pm","version":"2.80"},"Alien::Build::Plugin::Fetch::CurlCommand":{"version":"2.80","file":"lib/Alien/Build/Plugin/Fetch/CurlCommand.pm"},"Alien::Build::Interpolate":{"file":"lib/Alien/Build/Interpolate.pm","version":"2.80"},"Alien::Build::Plugin::Core::Override":{"file":"lib/Alien/Build/Plugin/Core/Override.pm","version":"2.80"},"Alien::Build::Plugin::Digest::Negotiate":{"version":"2.80","file":"lib/Alien/Build/Plugin/Digest/Negotiate.pm"},"Alien::Build::Plugin::Prefer::SortVersions":{"version":"2.80","file":"lib/Alien/Build/Plugin/Prefer/SortVersions.pm"},"Alien::Build::Plugin::Core::Download":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/Download.pm"},"Alien::Build::Plugin::PkgConfig::LibPkgConf":{"file":"lib/Alien/Build/Plugin/PkgConfig/LibPkgConf.pm","version":"2.80"},"Alien::Build::Plugin::Build::Autoconf":{"file":"lib/Alien/Build/Plugin/Build/Autoconf.pm","version":"2.80"},"Alien::Build::Plugin::Core::CleanInstall":{"version":"2.80","file":"lib/Alien/Build/Plugin/Core/CleanInstall.pm"},"Alien::Build::Plugin::Fetch::HTTPTiny":{"file":"lib/Alien/Build/Plugin/Fetch/HTTPTiny.pm","version":"2.80"},"Alien::Build::Plugin::Extract::ArchiveZip":{"file":"lib/Alien/Build/Plugin/Extract/ArchiveZip.pm","version":"2.80"},"Alien::Build::Plugin::Fetch::NetFTP":{"version":"2.80","file":"lib/Alien/Build/Plugin/Fetch/NetFTP.pm"},"Alien::Build::Log":{"version":"2.80","file":"lib/Alien/Build/Log.pm"}}}PK!A\g"g"Bperl5/x86_64-linux-thread-multi/.meta/Alien-Build-2.80/MYMETA.jsonnu6${ "abstract" : "Build external dependencies for use in CPAN", "author" : [ "Graham Ollis ", "Joel Berger " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Alien-Build", "no_index" : { "directory" : [ "corpus", "example", "maint" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "6.64" } }, "configure" : { "requires" : { "ExtUtils::CBuilder" : "0", "ExtUtils::MakeMaker" : "6.64", "ExtUtils::ParseXS" : "3.30", "File::Which" : "0" } }, "develop" : { "recommends" : { "Dist::Zilla::Plugin::Author::Plicease::Thanks" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::Plugin::DynamicPrereqs" : "0", "Dist::Zilla::Plugin::GatherFile" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::PruneFiles" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.75", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::Community::ArrayAssignAref" : "0", "Perl::Critic::Policy::Community::BarewordFilehandles" : "0", "Perl::Critic::Policy::Community::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Community::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Community::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Community::DiscouragedModules" : "0", "Perl::Critic::Policy::Community::DollarAB" : "0", "Perl::Critic::Policy::Community::Each" : "0", "Perl::Critic::Policy::Community::EmptyReturn" : "0", "Perl::Critic::Policy::Community::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Community::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Community::LoopOnHash" : "0", "Perl::Critic::Policy::Community::ModPerl" : "0", "Perl::Critic::Policy::Community::OpenArgs" : "0", "Perl::Critic::Policy::Community::OverloadOptions" : "0", "Perl::Critic::Policy::Community::POSIXImports" : "0", "Perl::Critic::Policy::Community::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Community::PreferredAlternatives" : "0", "Perl::Critic::Policy::Community::StrictWarnings" : "0", "Perl::Critic::Policy::Community::Threads" : "0", "Perl::Critic::Policy::Community::Wantarray" : "0", "Perl::Critic::Policy::Community::WarningsSwitch" : "0", "Perl::Critic::Policy::Community::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "FindBin" : "0", "Perl::Critic" : "0", "Test2::Require::EnvVar" : "0.000121", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::LinkCheck::Lite" : "0", "Test::Spelling" : "0" } }, "runtime" : { "requires" : { "Capture::Tiny" : "0.17", "Digest::SHA" : "0", "ExtUtils::CBuilder" : "0", "ExtUtils::MakeMaker" : "6.64", "ExtUtils::ParseXS" : "3.30", "FFI::CheckLib" : "0.11", "File::Which" : "1.10", "File::chdir" : "0", "JSON::PP" : "0", "List::Util" : "1.33", "Path::Tiny" : "0.077", "Test2::API" : "1.302096", "Text::ParseWords" : "3.26", "parent" : "0", "perl" : "5.008004" }, "suggests" : { "Archive::Tar" : "0" } }, "test" : { "requires" : { "Test2::API" : "1.302096", "Test2::V0" : "0.000121" }, "suggests" : { "Devel::Hide" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/PerlAlien/Alien-Build/issues" }, "homepage" : "https://metacpan.org/pod/Alien::Build", "repository" : { "type" : "git", "url" : "git://github.com/PerlAlien/Alien-Build.git", "web" : "https://github.com/PerlAlien/Alien-Build" }, "x_IRC" : "irc://irc.perl.org/#native" }, "version" : "2.80", "x_contributors" : [ "Graham Ollis ", "Diab Jerius (DJERIUS)", "Roy Storey (KIWIROY)", "Ilya Pavlov", "David Mertens (run4flat)", "Mark Nunberg (mordy, mnunberg)", "Christian Walde (Mithaldu)", "Brian Wightman (MidLifeXis)", "Zaki Mughal (zmughal)", "mohawk (mohawk2, ETJ)", "Vikas N Kumar (vikasnkumar)", "Flavio Poletti (polettix)", "Salvador Fandiño (salva)", "Gianni Ceccarelli (dakkar)", "Pavel Shaydo (zwon, trinitum)", "Kang-min Liu (劉康民, gugod)", "Nicholas Shipp (nshp)", "Juan Julián Merelo Guervós (JJ)", "Joel Berger (JBERGER)", "Petr Písař (ppisar)", "Lance Wicks (LANCEW)", "Ahmad Fatoum (a3f, ATHREEF)", "José Joaquín Atria (JJATRIA)", "Duke Leto (LETO)", "Shoichi Kaji (SKAJI)", "Shawn Laffan (SLAFFAN)", "Paul Evans (leonerd, PEVANS)", "Håkon Hægland (hakonhagland, HAKONH)", "nick nauwelaerts (INPHOBIA)", "Florian Weimer" ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } PK!`m%Fperl5/x86_64-linux-thread-multi/.meta/LWP-MediaTypes-6.04/install.jsonnu6${"provides":{"LWP::MediaTypes":{"file":"lib/LWP/MediaTypes.pm","version":"6.04"}},"target":"LWP::MediaTypes","dist":"LWP-MediaTypes-6.04","version":"6.04","name":"LWP::MediaTypes","pathname":"O/OA/OALDERS/LWP-MediaTypes-6.04.tar.gz"}PK!;k]]Eperl5/x86_64-linux-thread-multi/.meta/LWP-MediaTypes-6.04/MYMETA.jsonnu6${ "abstract" : "guess media type for a file or a URL", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "LWP-MediaTypes", "no_index" : { "directory" : [ "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "recommends" : { "Dist::Zilla::PluginBundle::Git::VersionManager" : "0.007" }, "requires" : { "Encode" : "0", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Test::EOL" : "0", "Test::MinimumVersion" : "0", "Test::Mojibake" : "0", "Test::More" : "0.94", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1", "perl" : "5.006", "warnings" : "0" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "0", "File::Basename" : "0", "Scalar::Util" : "0", "perl" : "5.006002", "strict" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "Test::Fatal" : "0", "Test::More" : "0", "overload" : "0", "warnings" : "0" } } }, "provides" : { "LWP::MediaTypes" : { "file" : "lib/LWP/MediaTypes.pm", "version" : "6.04" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/lwp-mediatypes/issues" }, "homepage" : "https://github.com/libwww-perl/lwp-mediatypes", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/lwp-mediatypes.git", "web" : "https://github.com/libwww-perl/lwp-mediatypes" }, "x_IRC" : "irc://irc.perl.org/#lwp", "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.04", "x_Dist_Zilla" : { "perl" : { "version" : "5.026001" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "LICENSE", "META.json", "README.md", "cpanfile" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "Git::GatherDir", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "MetaConfig", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Package", "config" : { "Dist::Zilla::Plugin::MetaProvides::Package" : { "finder_objects" : [ { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.012" } ], "include_underscores" : 0 }, "Dist::Zilla::Role::MetaProvider::Provider" : { "$Dist::Zilla::Role::MetaProvider::Provider::VERSION" : "2.002004", "inherit_missing" : 1, "inherit_version" : 1, "meta_noindex" : 1 }, "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000033", "version" : "0.004" } }, "name" : "MetaProvides::Package", "version" : "2.004003" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "MetaNoIndex", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "MetaYAML", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "MetaJSON", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "MetaResources", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.20.1", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "Git::Contributors", "version" : "0.035" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Authority", "name" : "Authority", "version" : "1.009" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "Manifest", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "License", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "name" : "InstallGuide", "version" : "1.200012" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "ExecDir", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "requires" } }, "name" : "Prereqs", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "AutoPrereqs", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::CPANFile", "name" : "CPANFile", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1 } }, "name" : "MakeMaker", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "Test::ReportPrereqs", "version" : "0.027" }, { "class" : "Dist::Zilla::Plugin::Test::Compile", "config" : { "Dist::Zilla::Plugin::Test::Compile" : { "bail_out_on_fail" : "1", "fail_on_warning" : "author", "fake_home" : 0, "filename" : "xt/author/00-compile.t", "module_finder" : [ ":InstallModules" ], "needs_display" : 0, "phase" : "develop", "script_finder" : [ ":PerlExecFiles" ], "skips" : [], "switch" : [] } }, "name" : "Test::Compile", "version" : "2.058" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "00-compile.t", "version" : "0.006" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "Test::Portability", "version" : "2.001000" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":InstallModules", ":TestFiles" ], "trailing_whitespace" : 1 } }, "name" : "Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::Test::ChangesHasContent", "name" : "Test::ChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::Substitute", "name" : "changes_has_content.t", "version" : "0.006" }, { "class" : "Dist::Zilla::Plugin::Test::MinimumVersion", "name" : "Test::MinimumVersion", "version" : "2.000008" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "PodSyntaxTests", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::Test::PodSpelling", "config" : { "Dist::Zilla::Plugin::Test::PodSpelling" : { "directories" : [ "bin", "lib" ], "spell_cmd" : "aspell list", "stopwords" : [ "eg" ], "wordlist" : "Pod::Wordlist" } }, "name" : "Test::PodSpelling", "version" : "2.007005" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.20.1", "repo_root" : "." } }, "name" : "Git::Check", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::CheckStrictVersion", "name" : "CheckStrictVersion", "version" : "0.001" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : 1 } }, "name" : "RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::CheckChangeLog", "name" : "CheckChangeLog", "version" : "0.05" }, { "class" : "Dist::Zilla::Plugin::CheckChangesHasContent", "name" : "CheckChangesHasContent", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "TestRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "UploadToCPAN", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "Markdown_Readme", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "LICENSE", "META.json", "cpanfile" ], "match" : [] } }, "name" : "CopyFilesFromRelease", "version" : "0.006" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "develop", "type" : "recommends" } }, "name" : "@Git::VersionManager/pluginbundle version", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Git::VersionManager/CopyFilesFromRelease", "version" : "0.006" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%v%n%n%c" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "LICENSE", "META.json", "README.md", "cpanfile" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.20.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/release snapshot", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.04", "tag_format" : "v%v", "tag_message" : "v%v" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.20.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/Git::Tag", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Git::VersionManager/NextRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.20.1", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Git::VersionManager/post-release commit", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.20.1", "repo_root" : "." } }, "name" : "Git::Push", "version" : "2.045" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "ConfirmRelease", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : "MetaProvides::Package/AUTOVIV/:InstallModulesPM", "version" : "6.012" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.012" } }, "x_authority" : "cpan:LWWWP", "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "amire80 ", "Andreas J. Koenig ", "Bill Mann ", "Bron Gondwana ", "Daniel Hedlund ", "David E. Wheeler ", "DAVIDRW ", "Father Chrysostomos ", "FWILES ", "Gavin Peters ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Graeme Thompson ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "jefflee ", "john9art ", "Karen Etheridge ", "Mark Stosberg ", "Mark Stosberg ", "Mark Stosberg ", "Mike Schilli ", "mschilli ", "murphy ", "Olaf Alders ", "Ondrej Hanak ", "Peter Rabbitson ", "phrstbrn ", "Robert Stone ", "Rolf Grossmann ", "ruff ", "sasao ", "Sean M. Burke ", "Slaven Rezic ", "Slaven Rezic ", "Spiros Denaxas ", "Steve Hay ", "Todd Lipcon ", "Tom Hukins ", "Tony Finch ", "Toru Yamaguchi ", "uid39246 ", "Ville Skytta ", "Wesley Schwengle ", "Yuri Karaban ", "Zefram " ], "x_generated_by_perl" : "v5.26.1", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!FBperl5/x86_64-linux-thread-multi/.meta/XML-Simple-2.25/install.jsonnu6${"target":"XML::Simple","provides":{"XML::Simple":{"version":2.25,"file":"lib/XML/Simple.pm"}},"name":"XML::Simple","pathname":"G/GR/GRANTM/XML-Simple-2.25.tar.gz","version":2.25,"dist":"XML-Simple-2.25"}PK!hAperl5/x86_64-linux-thread-multi/.meta/XML-Simple-2.25/MYMETA.jsonnu6${ "abstract" : "An API for simple XML files", "author" : [ "Grant McLean " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 5.043, CPAN::Meta::Converter version 2.150005, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-Simple", "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "XML::NamespaceSupport" : "1.04", "XML::SAX" : "0.15", "XML::SAX::Expat" : "0", "perl" : "5.008" } }, "test" : { "requires" : { "File::Temp" : "0", "Test::More" : "0.88" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "git://github.com/grantm/xml-simple.git", "web" : "https://github.com/grantm/xml-simple" } }, "version" : "2.25", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!#ЁEperl5/x86_64-linux-thread-multi/.meta/XML-SAX-Expat-0.51/install.jsonnu6${"name":"XML::SAX::Expat","pathname":"B/BJ/BJOERN/XML-SAX-Expat-0.51.tar.gz","dist":"XML-SAX-Expat-0.51","version":0.51,"target":"XML::SAX::Expat","provides":{"XML::SAX::Expat":{"version":0.51,"file":"Expat.pm"}}}PK! dDperl5/x86_64-linux-thread-multi/.meta/XML-SAX-Expat-0.51/MYMETA.jsonnu6${ "abstract" : "SAX Driver for Expat", "author" : [ "Robin Berjon" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 6.6302, CPAN::Meta::Converter version 2.132830, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-SAX-Expat", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "XML::NamespaceSupport" : "0.03", "XML::Parser" : "2.27", "XML::SAX" : "0.03", "XML::SAX::Base" : "1.00" } } }, "release_status" : "stable", "resources" : { "repository" : { "url" : "https://github.com/hoehrmann/XML-SAX-Expat" } }, "version" : "0.51", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!P944Dperl5/x86_64-linux-thread-multi/.meta/File-Listing-6.16/install.jsonnu6${"provides":{"File::Listing":{"file":"lib/File/Listing.pm","version":6.16},"File::Listing::apache":{"file":"lib/File/Listing.pm","version":6.16},"File::Listing::unix":{"version":6.16,"file":"lib/File/Listing.pm"},"File::Listing::vms":{"file":"lib/File/Listing.pm","version":6.16},"File::Listing::dosftp":{"version":6.16,"file":"lib/File/Listing.pm"},"File::Listing::netware":{"file":"lib/File/Listing.pm","version":6.16}},"target":"File::Listing","dist":"File-Listing-6.16","version":6.16,"name":"File::Listing","pathname":"P/PL/PLICEASE/File-Listing-6.16.tar.gz"}PK!{Cperl5/x86_64-linux-thread-multi/.meta/File-Listing-6.16/MYMETA.jsonnu6${ "abstract" : "Parse directory listing", "author" : [ "Gisle Aas", "Graham Ollis " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "File-Listing", "no_index" : { "directory" : [ "maint" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "recommends" : { "Dist::Zilla::Plugin::Author::Plicease::Thanks" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.69", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::Community::ArrayAssignAref" : "0", "Perl::Critic::Policy::Community::BarewordFilehandles" : "0", "Perl::Critic::Policy::Community::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Community::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Community::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Community::DiscouragedModules" : "0", "Perl::Critic::Policy::Community::DollarAB" : "0", "Perl::Critic::Policy::Community::Each" : "0", "Perl::Critic::Policy::Community::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Community::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Community::LoopOnHash" : "0", "Perl::Critic::Policy::Community::ModPerl" : "0", "Perl::Critic::Policy::Community::OpenArgs" : "0", "Perl::Critic::Policy::Community::OverloadOptions" : "0", "Perl::Critic::Policy::Community::POSIXImports" : "0", "Perl::Critic::Policy::Community::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Community::PreferredAlternatives" : "0", "Perl::Critic::Policy::Community::StrictWarnings" : "0", "Perl::Critic::Policy::Community::Threads" : "0", "Perl::Critic::Policy::Community::Wantarray" : "0", "Perl::Critic::Policy::Community::WarningsSwitch" : "0", "Perl::Critic::Policy::Community::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "File::Spec" : "0", "FindBin" : "0", "Perl::Critic" : "0", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::Spelling::CommonMistakes" : "0", "Test::Spelling" : "0", "Test::Strict" : "0", "YAML" : "0" } }, "runtime" : { "requires" : { "Exporter" : "5.57", "HTTP::Date" : "0", "perl" : "5.006" } }, "test" : { "requires" : { "Test::More" : "0.98" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/PerlAlien/File-Listing/issues" }, "homepage" : "https://metacpan.org/pod/File::Listing", "repository" : { "type" : "git", "url" : "git://github.com/PerlAlien/File-Listing.git", "web" : "https://github.com/PerlAlien/File-Listing" } }, "version" : "6.16", "x_contributors" : [ "Gisle Aas", "Graham Ollis ", "Adam Kennedy", "Adam Sjogren", "Alex Kapranoff", "Alexey Tourbin", "Andreas J. Koenig", "Bill Mann", "Bron Gondwana", "DAVIDRW", "Daniel Hedlund", "David E. Wheeler", "David Steinbrunner", "Erik Esterer", "FWILES", "Father Chrysostomos", "Gavin Peters", "Graeme Thompson", "Grant Street Group", "Hans-H. Froehlich", "Ian Kilgore", "Jacob J", "Mark Stosberg", "Mike Schilli", "Ondrej Hanak", "Peter John Acklam", "Peter Rabbitson", "Robert Stone", "Rolf Grossmann", "Sean M. Burke", "Simon Legner", "Slaven Rezic", "Spiros Denaxas", "Steve Hay", "Todd Lipcon", "Tom Hukins", "Tony Finch", "Toru Yamaguchi", "Ville Skyttä", "Yuri Karaban", "Zefram", "amire80", "jefflee", "john9art", "mschilli", "murphy", "phrstbrn", "ruff", "sasao", "uid39246" ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } PK!N#Eperl5/x86_64-linux-thread-multi/.meta/Alien-Libxml2-0.19/install.jsonnu6${"pathname":"P/PL/PLICEASE/Alien-Libxml2-0.19.tar.gz","dist":"Alien-Libxml2-0.19","name":"Alien::Libxml2","version":0.19,"target":"Alien::Libxml2","provides":{"Alien::Libxml2":{"version":0.19,"file":"lib/Alien/Libxml2.pm"}}}PK!5Dperl5/x86_64-linux-thread-multi/.meta/Alien-Libxml2-0.19/MYMETA.jsonnu6${ "abstract" : "Install the C libxml2 library on your system", "author" : [ "Graham Ollis " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.025, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Alien-Libxml2", "prereqs" : { "build" : { "requires" : { "Alien::Build" : "2.37", "Alien::Build::MM" : "0.32", "ExtUtils::MakeMaker" : "6.52" } }, "configure" : { "requires" : { "Alien::Build" : "2.37", "Alien::Build::MM" : "2.37", "Alien::Build::Plugin::Build::SearchDep" : "0.35", "Alien::Build::Plugin::Download::GitLab" : "0", "Alien::Build::Plugin::Prefer::BadVersion" : "1.05", "Alien::Build::Plugin::Probe::Vcpkg" : "0", "ExtUtils::CBuilder" : "0", "ExtUtils::MakeMaker" : "6.52" } }, "develop" : { "recommends" : { "Alien::Build::Plugin::Probe::Vcpkg" : "0", "Dist::Zilla::Plugin::AlienBase::Doc" : "0", "Dist::Zilla::Plugin::AlienBuild" : "0.11", "Dist::Zilla::Plugin::Author::Plicease::Thanks" : "0", "Dist::Zilla::Plugin::Author::Plicease::Upload" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::PruneFiles" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::Author::Plicease" : "2.69", "Perl::Critic::Policy::BuiltinFunctions::ProhibitBooleanGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep" : "0", "Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA" : "0", "Perl::Critic::Policy::ClassHierarchies::ProhibitOneArgBless" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitHardTabs" : "0", "Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace" : "0", "Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions" : "0", "Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode" : "0", "Perl::Critic::Policy::Freenode::ArrayAssignAref" : "0", "Perl::Critic::Policy::Freenode::BarewordFilehandles" : "0", "Perl::Critic::Policy::Freenode::ConditionalDeclarations" : "0", "Perl::Critic::Policy::Freenode::ConditionalImplicitReturn" : "0", "Perl::Critic::Policy::Freenode::DeprecatedFeatures" : "0", "Perl::Critic::Policy::Freenode::DiscouragedModules" : "0", "Perl::Critic::Policy::Freenode::DollarAB" : "0", "Perl::Critic::Policy::Freenode::Each" : "0", "Perl::Critic::Policy::Freenode::EmptyReturn" : "0", "Perl::Critic::Policy::Freenode::IndirectObjectNotation" : "0", "Perl::Critic::Policy::Freenode::LexicalForeachIterator" : "0", "Perl::Critic::Policy::Freenode::LoopOnHash" : "0", "Perl::Critic::Policy::Freenode::ModPerl" : "0", "Perl::Critic::Policy::Freenode::OpenArgs" : "0", "Perl::Critic::Policy::Freenode::OverloadOptions" : "0", "Perl::Critic::Policy::Freenode::POSIXImports" : "0", "Perl::Critic::Policy::Freenode::PackageMatchesFilename" : "0", "Perl::Critic::Policy::Freenode::PreferredAlternatives" : "0", "Perl::Critic::Policy::Freenode::StrictWarnings" : "0", "Perl::Critic::Policy::Freenode::Threads" : "0", "Perl::Critic::Policy::Freenode::Wantarray" : "0", "Perl::Critic::Policy::Freenode::WarningsSwitch" : "0", "Perl::Critic::Policy::Freenode::WhileDiamondDefaultAssignment" : "0", "Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles" : "0", "Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline" : "0", "Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitFormats" : "0", "Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic" : "0", "Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements" : "0", "Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish" : "0", "Perl::Critic::Policy::Objects::ProhibitIndirectSyntax" : "0", "Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic" : "0", "Perl::Critic::Policy::Subroutines::ProhibitNestedSubs" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators" : "0", "Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator" : "0", "Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator" : "0", "Perl::Critic::Policy::Variables::ProhibitPerl4PackageNames" : "0", "Perl::Critic::Policy::Variables::ProhibitUnusedVariables" : "0", "Software::License::Perl_5" : "0" }, "requires" : { "File::Spec" : "0", "FindBin" : "0", "Perl::Critic" : "0", "Test2::Require::Module" : "0.000121", "Test2::Tools::PerlCritic" : "0", "Test2::V0" : "0.000121", "Test::CPAN::Changes" : "0", "Test::EOL" : "0", "Test::Fixme" : "0.07", "Test::More" : "0.98", "Test::NoTabs" : "0", "Test::Pod" : "0", "Test::Pod::Coverage" : "0", "Test::Pod::Spelling::CommonMistakes" : "0", "Test::Spelling" : "0", "Test::Strict" : "0", "YAML" : "0" } }, "runtime" : { "requires" : { "Alien::Base" : "2.37", "Alien::Build" : "0.25", "perl" : "5.006" } }, "test" : { "requires" : { "Test2::V0" : "0.000121", "Test::Alien" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/PerlAlien/Alien-Libxml2/issues" }, "homepage" : "https://metacpan.org/pod/Alien::Libxml2", "repository" : { "type" : "git", "url" : "git://github.com/PerlAlien/Alien-Libxml2.git", "web" : "https://github.com/PerlAlien/Alien-Libxml2" }, "x_IRC" : "irc://irc.perl.org/#native" }, "version" : "0.19", "x_alienfile" : { "generated_by" : "Dist::Zilla::Plugin::AlienBuild version 0.32", "requires" : { "share" : { "Config" : "0", "HTTP::Tiny" : "0.044", "IO::Socket::SSL" : "1.56", "Mozilla::CA" : "0", "Net::SSLeay" : "1.49", "URI" : "0" }, "system" : {} } }, "x_contributors" : [ "Graham Ollis ", "Shlomi Fish (shlomif)" ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_use_unsafe_inc" : 0 } PK!};o <perl5/x86_64-linux-thread-multi/.meta/CPAN-2.36/install.jsonnu6${"target":"CPAN","name":"CPAN","provides":{"CPAN::Tarzip":{"file":"lib/CPAN/Tarzip.pm","version":5.5013},"CPAN::Exception::yaml_process_error":{"version":5.5,"file":"lib/CPAN/Exception/yaml_process_error.pm"},"CPAN::URL":{"file":"lib/CPAN/URL.pm","version":5.5},"CPAN::Module":{"file":"lib/CPAN/Module.pm","version":5.5003},"CPAN::Bundle":{"file":"lib/CPAN/Bundle.pm","version":5.5005},"CPAN::LWP::UserAgent":{"file":"lib/CPAN/LWP/UserAgent.pm","version":1.9601},"CPAN::Prompt":{"file":"lib/CPAN/Prompt.pm","version":5.5},"CPAN::CacheMgr":{"file":"lib/CPAN/CacheMgr.pm","version":5.5002},"CPAN::Kwalify":{"file":"lib/CPAN/Kwalify.pm","version":"5.50"},"CPAN::Shell":{"version":5.5009,"file":"lib/CPAN/Shell.pm"},"CPAN::Index":{"file":"lib/CPAN/Index.pm","version":2.29},"CPAN::HandleConfig":{"version":5.5012,"file":"lib/CPAN/HandleConfig.pm"},"CPAN::Admin":{"file":"lib/CPAN/Admin.pm","version":5.501},"CPAN::FTP":{"version":5.5016,"file":"lib/CPAN/FTP.pm"},"CPAN::Distroprefs::Iterator":{"version":6.0001,"file":"lib/CPAN/Distroprefs.pm"},"CPAN::Distroprefs::Result::Success":{"file":"lib/CPAN/Distroprefs.pm","version":6.0001},"CPAN::DeferredCode":{"version":"5.50","file":"lib/CPAN/DeferredCode.pm"},"CPAN::Distroprefs::Result::Fatal":{"file":"lib/CPAN/Distroprefs.pm","version":6.0001},"CPAN::Distroprefs::Pref":{"file":"lib/CPAN/Distroprefs.pm","version":6.0001},"CPAN::Distrostatus":{"file":"lib/CPAN/Distrostatus.pm","version":5.5},"App::Cpan":{"file":"lib/App/Cpan.pm","version":1.678},"CPAN::Plugin::Specfile":{"version":0.02,"file":"lib/CPAN/Plugin/Specfile.pm"},"CPAN::Exception::blocked_urllist":{"file":"lib/CPAN/Exception/blocked_urllist.pm","version":1.001},"CPAN::FirstTime":{"file":"lib/CPAN/FirstTime.pm","version":5.5317},"CPAN":{"file":"lib/CPAN.pm","version":2.36},"CPAN::Eval":{"version":6.0001,"file":"lib/CPAN/Distroprefs.pm"},"CPAN::Debug":{"file":"lib/CPAN/Debug.pm","version":5.5001},"CPAN::Author":{"version":5.5002,"file":"lib/CPAN/Author.pm"},"CPAN::Exception::RecursiveDependency":{"version":5.5001,"file":"lib/CPAN/Exception/RecursiveDependency.pm"},"CPAN::Queue::Item":{"version":5.5003,"file":"lib/CPAN/Queue.pm"},"CPAN::Nox":{"file":"lib/CPAN/Nox.pm","version":5.5001},"CPAN::Distroprefs::Result":{"file":"lib/CPAN/Distroprefs.pm","version":6.0001},"CPAN::FTP::netrc":{"version":1.01,"file":"lib/CPAN/FTP/netrc.pm"},"CPAN::Exception::RecursiveDependency::na":{"file":"lib/CPAN/Exception/RecursiveDependency.pm","version":5.5001},"CPAN::Distroprefs":{"version":6.0001,"file":"lib/CPAN/Distroprefs.pm"},"CPAN::HTTP::Credentials":{"version":1.9601,"file":"lib/CPAN/HTTP/Credentials.pm"},"CPAN::Distroprefs::Result::Error":{"version":6.0001,"file":"lib/CPAN/Distroprefs.pm"},"CPAN::Complete":{"file":"lib/CPAN/Complete.pm","version":5.5001},"CPAN::Distroprefs::Result::Warning":{"version":6.0001,"file":"lib/CPAN/Distroprefs.pm"},"CPAN::Distribution":{"version":2.34,"file":"lib/CPAN/Distribution.pm"},"CPAN::Mirrors":{"file":"lib/CPAN/Mirrors.pm","version":2.27},"CPAN::Mirrored::By":{"version":2.27,"file":"lib/CPAN/Mirrors.pm"},"CPAN::Plugin":{"version":0.97,"file":"lib/CPAN/Plugin.pm"},"CPAN::Version":{"file":"lib/CPAN/Version.pm","version":5.5003},"CPAN::InfoObj":{"file":"lib/CPAN/InfoObj.pm","version":5.5},"CPAN::Exception::yaml_not_installed":{"file":"lib/CPAN/Exception/yaml_not_installed.pm","version":5.5},"CPAN::HTTP::Client":{"version":1.9602,"file":"lib/CPAN/HTTP/Client.pm"},"CPAN::Queue":{"file":"lib/CPAN/Queue.pm","version":5.5003}},"version":2.36,"pathname":"A/AN/ANDK/CPAN-2.36.tar.gz","dist":"CPAN-2.36"}PK!qA ;perl5/x86_64-linux-thread-multi/.meta/CPAN-2.36/MYMETA.jsonnu6${ "abstract" : "query, download and build perl modules from CPAN sites", "author" : [ "Andreas Koenig " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "keywords" : [ "CPAN", "module", "module installation" ], "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "CPAN", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Archive::Tar" : "0", "Archive::Zip" : "0", "CPAN::Meta" : "0", "CPAN::Meta::Requirements" : "2.121", "CPAN::Meta::YAML" : "0", "Compress::Bzip2" : "0", "Compress::Zlib" : "0", "Data::Dumper" : "0", "Digest::MD5" : "0", "Digest::SHA" : "0", "Exporter" : "0", "Exporter::Heavy" : "0", "ExtUtils::CBuilder" : "0", "File::Copy" : "0", "File::HomeDir" : "0", "File::Spec" : "0", "File::Temp" : "0", "File::Which" : "0", "HTTP::Tiny" : "0.005", "IO::Compress::Base" : "0", "IO::Zlib" : "0", "JSON::PP" : "0", "MIME::Base64" : "0", "Module::Build" : "0", "Net::FTP" : "0", "Net::Ping" : "0", "Parse::CPAN::Meta" : "0", "Pod::Perldoc" : "0", "Pod::Perldoc::ToMan" : "0", "Scalar::Util" : "0", "Socket" : "0", "Term::ReadKey" : "0", "Test::Harness" : "2.62", "Test::More" : "0", "Text::Glob" : "0", "Text::ParseWords" : "0", "Text::Wrap" : "0", "perl" : "5.006002" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "https://github.com/andk/cpanpm" } }, "version" : "2.36", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!=Dperl5/x86_64-linux-thread-multi/.meta/Capture-Tiny-0.48/install.jsonnu6${"target":"Capture::Tiny","name":"Capture::Tiny","pathname":"D/DA/DAGOLDEN/Capture-Tiny-0.48.tar.gz","version":"0.48","dist":"Capture-Tiny-0.48","provides":{"Capture::Tiny":{"file":"lib/Capture/Tiny.pm","version":"0.48"}}}PK!I;U U Cperl5/x86_64-linux-thread-multi/.meta/Capture-Tiny-0.48/MYMETA.jsonnu6${ "abstract" : "Capture STDOUT and STDERR from Perl, XS or external programs", "author" : [ "David Golden " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "apache_2_0" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Capture-Tiny", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::OSPrereqs" : "0", "Dist::Zilla::Plugin::Prereqs" : "0", "Dist::Zilla::Plugin::ReleaseStatus::FromVersion" : "0", "Dist::Zilla::Plugin::RemovePrereqs" : "0", "Dist::Zilla::PluginBundle::DAGOLDEN" : "0.072", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Pod::Wordlist" : "0", "Software::License::Apache_2_0" : "0", "Test::CPAN::Meta" : "0", "Test::MinimumVersion" : "0", "Test::More" : "0", "Test::Perl::Critic" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Spelling" : "0.12", "Test::Version" : "1" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "0", "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "Scalar::Util" : "0", "perl" : "5.006", "strict" : "0", "warnings" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "IO::File" : "0", "Test::More" : "0.62", "lib" : "0" } } }, "provides" : { "Capture::Tiny" : { "file" : "lib/Capture/Tiny.pm", "version" : "0.48" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/dagolden/Capture-Tiny/issues" }, "homepage" : "https://github.com/dagolden/Capture-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/dagolden/Capture-Tiny.git", "web" : "https://github.com/dagolden/Capture-Tiny" } }, "version" : "0.48", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "Dagfinn Ilmari Mannsåker ", "David E. Wheeler ", "fecundf ", "Graham Knop ", "Peter Rabbitson " ], "x_generated_by_perl" : "v5.26.1", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!{ Dperl5/x86_64-linux-thread-multi/.meta/XML-LibXML-2.0210/install.jsonnu6${"pathname":"S/SH/SHLOMIF/XML-LibXML-2.0210.tar.gz","dist":"XML-LibXML-2.0210","name":"XML::LibXML","version":"2.0210","provides":{"XML::LibXML::RelaxNG":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Devel":{"file":"lib/XML/LibXML/Devel.pm","version":"2.0210"},"XML::LibXML::Common":{"version":"2.0210","file":"lib/XML/LibXML/Common.pm"},"XML::LibXML::SAX::Parser":{"version":"2.0210","file":"lib/XML/LibXML/SAX/Parser.pm"},"XML::LibXML":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::Comment":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::XPathContext":{"version":"2.0210","file":"lib/XML/LibXML/XPathContext.pm"},"XML::LibXML::SAX::Generator":{"file":"lib/XML/LibXML/SAX/Generator.pm","version":"2.0210"},"XML::LibXML::AttributeHash":{"file":"lib/XML/LibXML/AttributeHash.pm","version":"2.0210"},"XML::LibXML::InputCallback":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::Element":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::_SAXParser":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::SAX::Builder":{"file":"lib/XML/LibXML/SAX/Builder.pm","version":"2.0210"},"XML::LibXML::NodeList":{"file":"lib/XML/LibXML/NodeList.pm","version":"2.0210"},"XML::LibXML::SAX":{"file":"lib/XML/LibXML/SAX.pm","version":"2.0210"},"XML::LibXML::Error":{"file":"lib/XML/LibXML/Error.pm","version":"2.0210"},"XML::LibXML::Namespace":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::Node":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::Schema":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Document":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Pattern":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::NamedNodeMap":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Dtd":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::PI":{"version":"2.0210","file":"LibXML.pm"},"XML::LibXML::Boolean":{"version":"2.0210","file":"lib/XML/LibXML/Boolean.pm"},"XML::LibXML::ErrNo":{"version":"2.0210","file":"lib/XML/LibXML/ErrNo.pm"},"XML::LibXML::RegExp":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::DocumentFragment":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Reader":{"file":"lib/XML/LibXML/Reader.pm","version":"2.0210"},"XML::LibXML::CDATASection":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Text":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::SAX::AttributeNode":{"file":"lib/XML/LibXML/SAX/Generator.pm","version":"2.0210"},"XML::LibXML::Literal":{"file":"lib/XML/LibXML/Literal.pm","version":"2.0210"},"XML::LibXML::Number":{"file":"lib/XML/LibXML/Number.pm","version":"2.0210"},"XML::LibXML::XPathExpression":{"file":"LibXML.pm","version":"2.0210"},"XML::LibXML::Attr":{"file":"LibXML.pm","version":"2.0210"}},"target":"XML::LibXML"}PK!|{  Cperl5/x86_64-linux-thread-multi/.meta/XML-LibXML-2.0210/MYMETA.jsonnu6${ "abstract" : "Interface to Gnome libxml2 xml parsing and DOM library", "author" : [ "Petr Pajas " ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "keywords" : [ "dom", "html", "libxml", "object oriented", "oop", "parse", "parser", "parsing", "pullparser", "sax", "sgml", "xml", "xpath", "XPath", "xs" ], "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-LibXML", "no_index" : { "directory" : [ "t", "inc", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "Alien::Base::Wrapper" : "0", "Alien::Libxml2" : "0.14", "Config" : "0", "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Carp" : "0", "DynaLoader" : "0", "Encode" : "0", "Exporter" : "5.57", "IO::Handle" : "0", "Scalar::Util" : "0", "Tie::Hash" : "0", "XML::NamespaceSupport" : "1.07", "XML::SAX" : "0.11", "XML::SAX::Base" : "0", "XML::SAX::DocumentLocator" : "0", "XML::SAX::Exception" : "0", "base" : "0", "constant" : "0", "overload" : "0", "parent" : "0", "perl" : "5.008001", "strict" : "0", "vars" : "0", "warnings" : "0" } }, "test" : { "requires" : { "Config" : "0", "Errno" : "0", "IO::File" : "0", "IO::Handle" : "0", "POSIX" : "0", "Scalar::Util" : "0", "Test::More" : "0", "locale" : "0", "utf8" : "0" } } }, "release_status" : "stable", "resources" : { "repository" : { "type" : "git", "url" : "https://github.com/shlomif/perl-XML-LibXML.git", "web" : "https://github.com/shlomif/perl-XML-LibXML" } }, "version" : "2.0210", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!ֶ}}@perl5/x86_64-linux-thread-multi/.meta/Net-HTTP-6.23/install.jsonnu6${"pathname":"O/OA/OALDERS/Net-HTTP-6.23.tar.gz","name":"Net::HTTP","dist":"Net-HTTP-6.23","version":6.23,"target":"Net::HTTP","provides":{"Net::HTTP::NB":{"version":6.23,"file":"lib/Net/HTTP/NB.pm"},"Net::HTTP":{"version":6.23,"file":"lib/Net/HTTP.pm"},"Net::HTTPS":{"version":6.23,"file":"lib/Net/HTTPS.pm"},"Net::HTTP::Methods":{"file":"lib/Net/HTTP/Methods.pm","version":6.23}}}PK!f/qq?perl5/x86_64-linux-thread-multi/.meta/Net-HTTP-6.23/MYMETA.jsonnu6${ "abstract" : "Low-level HTTP connection (client)", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.030, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Net-HTTP", "no_index" : { "directory" : [ "examples", "t", "xt" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "requires" : { "Pod::Coverage::TrustPod" : "0", "Test::EOL" : "0", "Test::Mojibake" : "0", "Test::More" : "0.88", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Portability::Files" : "0", "Test::Version" : "1" } }, "runtime" : { "requires" : { "Carp" : "0", "Compress::Raw::Zlib" : "0", "IO::Socket::INET" : "0", "IO::Uncompress::Gunzip" : "0", "URI" : "0", "base" : "0", "perl" : "5.006002", "strict" : "0", "warnings" : "0" }, "suggests" : { "IO::Socket" : "0", "IO::Socket::INET6" : "0", "IO::Socket::IP" : "0", "IO::Socket::SSL" : "2.012", "Symbol" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "Data::Dumper" : "0", "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "IO::Select" : "0", "Socket" : "0", "Test::More" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/Net-HTTP/issues" }, "homepage" : "https://github.com/libwww-perl/Net-HTTP", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/Net-HTTP.git", "web" : "https://github.com/libwww-perl/Net-HTTP" }, "x_IRC" : "irc://irc.perl.org/#lwp", "x_MailingList" : "mailto:libwww@perl.org" }, "version" : "6.23", "x_Dist_Zilla" : { "perl" : { "version" : "5.036000" }, "plugins" : [ { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "requires" } }, "name" : "Prereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 0, "check_all_prereqs" : 0, "modules" : [ "Dist::Zilla::PluginBundle::Author::OALDERS" ], "phase" : "build", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, build", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::PromptIfStale", "config" : { "Dist::Zilla::Plugin::PromptIfStale" : { "check_all_plugins" : 1, "check_all_prereqs" : 1, "modules" : [], "phase" : "release", "run_under_travis" : 0, "skip" : [] } }, "name" : "@Author::OALDERS/stale modules, release", "version" : "0.058" }, { "class" : "Dist::Zilla::Plugin::MakeMaker", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/MakeMaker", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CPANFile", "name" : "@Author::OALDERS/CPANFile", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::ContributorsFile", "name" : "@Author::OALDERS/ContributorsFile", "version" : "0.3.0" }, { "class" : "Dist::Zilla::Plugin::MetaJSON", "name" : "@Author::OALDERS/MetaJSON", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaYAML", "name" : "@Author::OALDERS/MetaYAML", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Manifest", "name" : "@Author::OALDERS/Manifest", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaNoIndex", "name" : "@Author::OALDERS/MetaNoIndex", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaConfig", "name" : "@Author::OALDERS/MetaConfig", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MetaResources", "name" : "@Author::OALDERS/MetaResources", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::License", "name" : "@Author::OALDERS/License", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::InstallGuide", "config" : { "Dist::Zilla::Role::ModuleMetadata" : { "Module::Metadata" : "1.000037", "version" : "0.006" } }, "name" : "@Author::OALDERS/InstallGuide", "version" : "1.200014" }, { "class" : "Dist::Zilla::Plugin::ExecDir", "name" : "@Author::OALDERS/ExecDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::MojibakeTests", "name" : "@Author::OALDERS/MojibakeTests", "version" : "0.8" }, { "class" : "Dist::Zilla::Plugin::PodSyntaxTests", "name" : "@Author::OALDERS/PodSyntaxTests", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::EOL", "config" : { "Dist::Zilla::Plugin::Test::EOL" : { "filename" : "xt/author/eol.t", "finder" : [ ":ExecFiles", ":InstallModules", ":TestFiles" ], "trailing_whitespace" : 1 } }, "name" : "@Author::OALDERS/Test::EOL", "version" : "0.19" }, { "class" : "Dist::Zilla::Plugin::Test::Portability", "config" : { "Dist::Zilla::Plugin::Test::Portability" : { "options" : "" } }, "name" : "@Author::OALDERS/Test::Portability", "version" : "2.001001" }, { "class" : "Dist::Zilla::Plugin::TestRelease", "name" : "@Author::OALDERS/TestRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Test::ReportPrereqs", "name" : "@Author::OALDERS/Test::ReportPrereqs", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::Test::Version", "name" : "@Author::OALDERS/Test::Version", "version" : "1.09" }, { "class" : "Dist::Zilla::Plugin::RunExtraTests", "config" : { "Dist::Zilla::Role::TestRunner" : { "default_jobs" : "8" } }, "name" : "@Author::OALDERS/RunExtraTests", "version" : "0.029" }, { "class" : "Dist::Zilla::Plugin::PodWeaver", "config" : { "Dist::Zilla::Plugin::PodWeaver" : { "finder" : [ ":InstallModules", ":PerlExecFiles" ], "plugins" : [ { "class" : "Pod::Weaver::Plugin::EnsurePod5", "name" : "@CorePrep/EnsurePod5", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::H1Nester", "name" : "@CorePrep/H1Nester", "version" : "4.019" }, { "class" : "Pod::Weaver::Plugin::SingleEncoding", "name" : "@Default/SingleEncoding", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Name", "name" : "@Default/Name", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Version", "name" : "@Default/Version", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/prelude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "SYNOPSIS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "DESCRIPTION", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Generic", "name" : "OVERVIEW", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "ATTRIBUTES", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "METHODS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Collect", "name" : "FUNCTIONS", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Leftovers", "name" : "@Default/Leftovers", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Region", "name" : "@Default/postlude", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Authors", "name" : "@Default/Authors", "version" : "4.019" }, { "class" : "Pod::Weaver::Section::Legal", "name" : "@Default/Legal", "version" : "4.019" } ] } }, "name" : "@Author::OALDERS/PodWeaver", "version" : "4.010" }, { "class" : "Dist::Zilla::Plugin::PruneCruft", "name" : "@Author::OALDERS/PruneCruft", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromBuild", "name" : "@Author::OALDERS/CopyFilesFromBuild", "version" : "0.170880" }, { "class" : "Dist::Zilla::Plugin::GithubMeta", "name" : "@Author::OALDERS/GithubMeta", "version" : "0.58" }, { "class" : "Dist::Zilla::Plugin::Git::GatherDir", "config" : { "Dist::Zilla::Plugin::GatherDir" : { "exclude_filename" : [ "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile" ], "exclude_match" : [], "follow_symlinks" : 0, "include_dotfiles" : 0, "prefix" : "", "prune_directory" : [], "root" : "." }, "Dist::Zilla::Plugin::Git::GatherDir" : { "include_untracked" : 0 } }, "name" : "@Author::OALDERS/Git::GatherDir", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Install" ], "match" : [] } }, "name" : "@Author::OALDERS/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Check", "config" : { "Dist::Zilla::Plugin::Git::Check" : { "untracked_files" : "die" }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Check", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Contributors", "config" : { "Dist::Zilla::Plugin::Git::Contributors" : { "git_version" : "2.41.0", "include_authors" : 0, "include_releaser" : 1, "order_by" : "name", "paths" : [] } }, "name" : "@Author::OALDERS/Git::Contributors", "version" : "0.036" }, { "class" : "Dist::Zilla::Plugin::ReadmeAnyFromPod", "config" : { "Dist::Zilla::Role::FileWatcher" : { "version" : "0.006" } }, "name" : "@Author::OALDERS/ReadmeMdInBuild", "version" : "0.163250" }, { "class" : "Dist::Zilla::Plugin::ShareDir", "name" : "@Author::OALDERS/ShareDir", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::CheckIssues", "name" : "@Author::OALDERS/CheckIssues", "version" : "0.011" }, { "class" : "Dist::Zilla::Plugin::ConfirmRelease", "name" : "@Author::OALDERS/ConfirmRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::UploadToCPAN", "name" : "@Author::OALDERS/UploadToCPAN", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::RewriteVersion::Transitional", "config" : { "Dist::Zilla::Plugin::RewriteVersion" : { "add_tarball_name" : 0, "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "skip_version_provider" : 0 }, "Dist::Zilla::Plugin::RewriteVersion::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/RewriteVersion::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::MetaProvides::Update", "name" : "@Author::OALDERS/@Git::VersionManager/MetaProvides::Update", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::CopyFilesFromRelease", "config" : { "Dist::Zilla::Plugin::CopyFilesFromRelease" : { "filename" : [ "Changes" ], "match" : [] } }, "name" : "@Author::OALDERS/@Git::VersionManager/CopyFilesFromRelease", "version" : "0.007" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "v%V%n%n%c", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Changes", "Install", "LICENSE", "META.json", "Makefile.PL", "README.md", "cpanfile", "dist.ini" ], "allow_dirty_match" : [], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/release snapshot", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Tag", "config" : { "Dist::Zilla::Plugin::Git::Tag" : { "branch" : null, "changelog" : "Changes", "signed" : 0, "tag" : "v6.23", "tag_format" : "v%V", "tag_message" : "v%V" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/Git::Tag", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional", "config" : { "Dist::Zilla::Plugin::BumpVersionAfterRelease" : { "finders" : [ ":ExecFiles", ":InstallModules" ], "global" : 0, "munge_makefile_pl" : 1 }, "Dist::Zilla::Plugin::BumpVersionAfterRelease::Transitional" : {} }, "name" : "@Author::OALDERS/@Git::VersionManager/BumpVersionAfterRelease::Transitional", "version" : "0.009" }, { "class" : "Dist::Zilla::Plugin::NextRelease", "name" : "@Author::OALDERS/@Git::VersionManager/NextRelease", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Git::Commit", "config" : { "Dist::Zilla::Plugin::Git::Commit" : { "add_files_in" : [], "commit_msg" : "increment $VERSION after %v release", "signoff" : 0 }, "Dist::Zilla::Role::Git::DirtyFiles" : { "allow_dirty" : [ "Build.PL", "Changes", "Makefile.PL" ], "allow_dirty_match" : [ "(?^:^lib/.*\\.pm$)" ], "changelog" : "Changes" }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." }, "Dist::Zilla::Role::Git::StringFormatter" : { "time_zone" : "local" } }, "name" : "@Author::OALDERS/@Git::VersionManager/post-release commit", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Git::Push", "config" : { "Dist::Zilla::Plugin::Git::Push" : { "push_to" : [ "origin" ], "remotes_must_exist" : 1 }, "Dist::Zilla::Role::Git::Repo" : { "git_version" : "2.41.0", "repo_root" : "." } }, "name" : "@Author::OALDERS/Git::Push", "version" : "2.048" }, { "class" : "Dist::Zilla::Plugin::Test::Pod::Coverage::Configurable", "name" : "Test::Pod::Coverage::Configurable", "version" : "0.07" }, { "class" : "Dist::Zilla::Plugin::AutoPrereqs", "name" : "AutoPrereqs", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs", "config" : { "Dist::Zilla::Plugin::Prereqs" : { "phase" : "runtime", "type" : "suggests" } }, "name" : "RuntimeSuggests", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::Prereqs::Soften", "config" : { "Dist::Zilla::Plugin::Prereqs::Soften" : { "copy_to" : [], "modules" : [ "IO::Socket", "IO::Socket::INET6", "IO::Socket::IP", "IO::Socket::SSL", "Symbol" ], "modules_from_features" : null, "to_relationship" : "suggests" } }, "name" : "Prereqs::Soften", "version" : "0.006003" }, { "class" : "Dist::Zilla::Plugin::StaticInstall", "config" : { "Dist::Zilla::Plugin::StaticInstall" : { "dry_run" : 0, "mode" : "on" } }, "name" : "StaticInstall", "version" : "0.012" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":InstallModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":IncModules", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":TestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExtraTestFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":PerlExecFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":ShareFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":MainModule", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":AllFiles", "version" : "6.030" }, { "class" : "Dist::Zilla::Plugin::FinderCode", "name" : ":NoFiles", "version" : "6.030" } ], "zilla" : { "class" : "Dist::Zilla::Dist::Builder", "config" : { "is_trial" : 0 }, "version" : "6.030" } }, "x_contributors" : [ "Adam Kennedy ", "Adam Sjogren ", "Alexey Tourbin ", "Alex Kapranoff ", "amire80 ", "Andreas J. Koenig ", "Andy Grundman ", "Bill Mann ", "Bron Gondwana ", "Chase Whitener ", "Dagfinn Ilmari Mannsåker ", "Daniel Hedlund ", "Dave Rolsky ", "David E. Wheeler ", "DAVIDRW ", "David Steinbrunner ", "Eric Wong ", "Father Chrysostomos ", "FWILES ", "Gavin Peters ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Gisle Aas ", "Graeme Thompson ", "Hans-H. Froehlich ", "Ian Kilgore ", "Jacob J ", "James Raspass ", "Jason A Fesler ", "Jay Hannah ", "Jean-Louis Martineau ", "jefflee ", "Jesse Luehrs ", "john9art ", "Karen Etheridge ", "Kent Fredric ", "Lasse Makholm ", "Marinos Yannikos ", "Mark Overmeer ", "Mark Stosberg ", "Mark Stosberg ", "Mark Stosberg ", "Mike Schilli ", "Mohammad S Anwar ", "mschilli ", "murphy ", "Olaf Alders ", "Ondrej Hanak ", "Paul Cochrane ", "Peter Rabbitson ", "phrstbrn ", "Robert Stone ", "Rolf Grossmann ", "ruff ", "sasao ", "Sean M. Burke ", "Shoichi Kaji ", "Slaven Rezic ", "Slaven Rezic ", "Spiros Denaxas ", "Steffen Ullrich ", "Steve Hay ", "Todd Lipcon ", "Tom Hukins ", "Tom Wyant ", "Tony Finch ", "Toru Yamaguchi ", "uid39246 ", "Ville Skyttä ", "Yuri Karaban ", "Zefram " ], "x_generated_by_perl" : "v5.36.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later", "x_static_install" : 1 } PK!" ], "dynamic_config" : 0, "generated_by" : "ExtUtils::MakeMaker version 7.70, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "version", "no_index" : { "directory" : [ "t", "inc" ], "package" : [ "charstar" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "perl" : "5.006002" } }, "test" : { "requires" : { "File::Temp" : "0.13", "Test::More" : "0.45", "base" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-version@rt.cpan.org", "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=version" }, "repository" : { "type" : "git", "url" : "git://github.com/Perl/version.pm.git", "web" : "https://github.com/Perl/version.pm" } }, "version" : "0.9930", "x_serialization_backend" : "JSON::PP version 2.97001" } PK!i|Cperl5/x86_64-linux-thread-multi/.meta/HTML-Parser-3.82/install.jsonnu6${"dist":"HTML-Parser-3.82","version":"3.82","pathname":"O/OA/OALDERS/HTML-Parser-3.82.tar.gz","name":"HTML::Parser","provides":{"HTML::TokeParser":{"file":"lib/HTML/TokeParser.pm","version":"3.82"},"HTML::PullParser":{"version":"3.82","file":"lib/HTML/PullParser.pm"},"HTML::Parser":{"file":"lib/HTML/Parser.pm","version":"3.82"},"HTML::Filter":{"x_deprecated":1,"version":"3.82","file":"lib/HTML/Filter.pm"},"HTML::Entities":{"version":"3.82","file":"lib/HTML/Entities.pm"},"HTML::LinkExtor":{"file":"lib/HTML/LinkExtor.pm","version":"3.82"},"HTML::HeadParser":{"version":"3.82","file":"lib/HTML/HeadParser.pm"}},"target":"HTML::Entities"}PK![[Bperl5/x86_64-linux-thread-multi/.meta/HTML-Parser-3.82/MYMETA.jsonnu6${ "abstract" : "HTML parser class", "author" : [ "Gisle Aas " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.031, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "HTML-Parser", "no_index" : { "directory" : [ "eg", "examples", "inc", "share", "t", "xt" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.52" }, "suggests" : { "JSON::PP" : "2.27300" } }, "develop" : { "requires" : { "Dist::Zilla" : "0", "Dist::Zilla::Plugin::MinimumPerl" : "0", "Dist::Zilla::PluginBundle::Starter" : "v4.0.0", "File::Spec" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Test::CPAN::Changes" : "0.4", "Test::CPAN::Meta" : "0", "Test::CheckManifest" : "1.29", "Test::Kwalitee" : "1.22", "Test::More" : "0.88", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08", "Test::Pod::Spelling::CommonMistakes" : "1.000", "Test::Spelling" : "0.12", "Test::Version" : "2.00" } }, "runtime" : { "requires" : { "Carp" : "0", "Exporter" : "0", "HTML::Tagset" : "0", "HTTP::Headers" : "0", "IO::File" : "0", "URI" : "0", "URI::URL" : "0", "XSLoader" : "0", "strict" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "2.120900" }, "requires" : { "Config" : "0", "ExtUtils::MakeMaker" : "0", "File::Spec" : "0", "IO::File" : "0", "SelectSaver" : "0", "Test::More" : "0", "URI" : "0", "strict" : "0" } } }, "provides" : { "HTML::Entities" : { "file" : "lib/HTML/Entities.pm", "version" : "3.82" }, "HTML::Filter" : { "file" : "lib/HTML/Filter.pm", "version" : "3.82", "x_deprecated" : 1 }, "HTML::HeadParser" : { "file" : "lib/HTML/HeadParser.pm", "version" : "3.82" }, "HTML::LinkExtor" : { "file" : "lib/HTML/LinkExtor.pm", "version" : "3.82" }, "HTML::Parser" : { "file" : "lib/HTML/Parser.pm", "version" : "3.82" }, "HTML::PullParser" : { "file" : "lib/HTML/PullParser.pm", "version" : "3.82" }, "HTML::TokeParser" : { "file" : "lib/HTML/TokeParser.pm", "version" : "3.82" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/libwww-perl/HTML-Parser/issues" }, "homepage" : "https://github.com/libwww-perl/HTML-Parser", "repository" : { "type" : "git", "url" : "https://github.com/libwww-perl/HTML-Parser.git", "web" : "https://github.com/libwww-perl/HTML-Parser" } }, "version" : "3.82", "x_contributors" : [ "Antonio Radici ", "Barbie ", "bulk88 ", "Chase Whitener ", "Chip Salzenberg ", "Damyan Ivanov ", "David Steinbrunner ", "dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>", "François Perrad ", "Gisle Aas ", "Graham Knop ", "Jacques Germishuys ", "James Raspass ", "Jess Robinson ", "Jon Jensen ", "Michal Josef Špaček ", "Mike South ", "Nicholas Clark ", "Nicolas R ", "Olaf Alders ", "Salvatore Bonaccorso ", "Todd Rinaldo ", "Ville Skyttä ", "yoshikazusawa <883514+yoshikazusawa@users.noreply.github.com>", "Yves Orton ", "Zefram " ], "x_generated_by_perl" : "v5.34.0", "x_serialization_backend" : "JSON::PP version 2.97001", "x_spdx_expression" : "Artistic-1.0-Perl OR GPL-1.0-or-later" } PK! Lperl5/x86_64-linux-thread-multi/.meta/XML-NamespaceSupport-1.12/install.jsonnu6${"pathname":"P/PE/PERIGRIN/XML-NamespaceSupport-1.12.tar.gz","name":"XML::NamespaceSupport","version":"1.12","dist":"XML-NamespaceSupport-1.12","target":"XML::NamespaceSupport","provides":{"XML::NamespaceSupport":{"version":"1.12","file":"lib/XML/NamespaceSupport.pm"}}}PK!"Kperl5/x86_64-linux-thread-multi/.meta/XML-NamespaceSupport-1.12/MYMETA.jsonnu6${ "abstract" : "A simple generic namespace processor", "author" : [ "Robin Berjon ", "Chris Prather " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.009, CPAN::Meta::Converter version 2.150005, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "XML-NamespaceSupport", "no_index" : { "directory" : [ "corpus", "examples", "t", "xt" ], "package" : [ "DB" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" } }, "develop" : { "requires" : { "Dist::Zilla" : "5", "Dist::Zilla::Plugin::Authority" : "0", "Dist::Zilla::Plugin::AutoPrereqs" : "0", "Dist::Zilla::Plugin::CPANFile" : "0", "Dist::Zilla::Plugin::CheckChangesHasContent" : "0", "Dist::Zilla::Plugin::CheckMetaResources" : "0", "Dist::Zilla::Plugin::CheckPrereqsIndexed" : "0", "Dist::Zilla::Plugin::ConfirmRelease" : "0", "Dist::Zilla::Plugin::CopyFilesFromBuild" : "0", "Dist::Zilla::Plugin::ExecDir" : "0", "Dist::Zilla::Plugin::Git::Check" : "0", "Dist::Zilla::Plugin::Git::CheckFor::CorrectBranch" : "0", "Dist::Zilla::Plugin::Git::Commit" : "0", "Dist::Zilla::Plugin::Git::Contributors" : "0", "Dist::Zilla::Plugin::Git::GatherDir" : "0", "Dist::Zilla::Plugin::Git::NextVersion" : "0", "Dist::Zilla::Plugin::Git::Push" : "0", "Dist::Zilla::Plugin::Git::Tag" : "0", "Dist::Zilla::Plugin::GithubMeta" : "0", "Dist::Zilla::Plugin::InsertCopyright" : "0", "Dist::Zilla::Plugin::License" : "0", "Dist::Zilla::Plugin::MakeMaker" : "0", "Dist::Zilla::Plugin::Manifest" : "0", "Dist::Zilla::Plugin::ManifestSkip" : "0", "Dist::Zilla::Plugin::MetaJSON" : "0", "Dist::Zilla::Plugin::MetaNoIndex" : "0", "Dist::Zilla::Plugin::MetaProvides::Package" : "0", "Dist::Zilla::Plugin::MetaYAML" : "0", "Dist::Zilla::Plugin::MinimumPerl" : "0", "Dist::Zilla::Plugin::NextRelease" : "0", "Dist::Zilla::Plugin::OurPkgVersion" : "0", "Dist::Zilla::Plugin::PodWeaver" : "0", "Dist::Zilla::Plugin::Prereqs::AuthorDeps" : "0", "Dist::Zilla::Plugin::PromptIfStale" : "0", "Dist::Zilla::Plugin::PruneCruft" : "0", "Dist::Zilla::Plugin::RunExtraTests" : "0", "Dist::Zilla::Plugin::ShareDir" : "0", "Dist::Zilla::Plugin::TestRelease" : "0", "Dist::Zilla::Plugin::UploadToCPAN" : "0", "Software::License::Perl_5" : "0" } }, "runtime" : { "requires" : { "constant" : "0", "perl" : "5.006", "strict" : "0", "vars" : "0", "warnings" : "0" } }, "test" : { "requires" : { "Test::More" : "0" } } }, "provides" : { "XML::NamespaceSupport" : { "file" : "lib/XML/NamespaceSupport.pm", "version" : "1.12" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/perigrin/xml-namespacesupport/issues" }, "homepage" : "https://github.com/perigrin/xml-namespacesupport", "repository" : { "type" : "git", "url" : "https://github.com/perigrin/xml-namespacesupport.git", "web" : "https://github.com/perigrin/xml-namespacesupport" } }, "version" : "1.12", "x_authority" : "cpan:PERIGRIN", "x_contributors" : [ "Chris Prather ", "David Steinbrunner ", "Paul Cochrane ", "Paulo Custodio " ], "x_serialization_backend" : "JSON::PP version 2.97001" } PK!Wyperl5/Types/Serialiser/Error.pmnu6$=head1 NAME Types::Serialiser::Error - dummy module for Types::Serialiser =head1 SYNOPSIS # do not "use" yourself =head1 DESCRIPTION This module exists only to provide overload resolution for Storable and similar modules that assume that class name equals module name. See L for more info about this class. =cut use Types::Serialiser (); =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ =cut 1 PK!?##perl5/Types/Serialiser.pmnu6$=head1 NAME Types::Serialiser - simple data types for common serialisation formats =encoding utf-8 =head1 SYNOPSIS =head1 DESCRIPTION This module provides some extra datatypes that are used by common serialisation formats such as JSON or CBOR. The idea is to have a repository of simple/small constants and containers that can be shared by different implementations so they become interoperable between each other. =cut package Types::Serialiser; use common::sense; # required to suppress annoying warnings our $VERSION = '1.01'; =head1 SIMPLE SCALAR CONSTANTS Simple scalar constants are values that are overloaded to act like simple Perl values, but have (class) type to differentiate them from normal Perl scalars. This is necessary because these have different representations in the serialisation formats. In the following, functions with zero or one arguments have a prototype of C<()> and C<($)>, respectively, so act as constants and unary operators. =head2 BOOLEANS (Types::Serialiser::Boolean class) This type has only two instances, true and false. A natural representation for these in Perl is C<1> and C<0>, but serialisation formats need to be able to differentiate between them and mere numbers. =over 4 =item $Types::Serialiser::true, Types::Serialiser::true This value represents the "true" value. In most contexts is acts like the number C<1>. It is up to you whether you use the variable form (C<$Types::Serialiser::true>) or the constant form (C). The constant is represented as a reference to a scalar containing C<1> - implementations are allowed to directly test for this. =item $Types::Serialiser::false, Types::Serialiser::false This value represents the "false" value. In most contexts is acts like the number C<0>. It is up to you whether you use the variable form (C<$Types::Serialiser::false>) or the constant form (C). The constant is represented as a reference to a scalar containing C<0> - implementations are allowed to directly test for this. =item Types::Serialiser::as_bool $value Converts a Perl scalar into a boolean, which is useful syntactic sugar. Strictly equivalent to: $value ? $Types::Serialiser::true : $Types::Serialiser::false =item $is_bool = Types::Serialiser::is_bool $value Returns true iff the C<$value> is either C<$Types::Serialiser::true> or C<$Types::Serialiser::false>. For example, you could differentiate between a perl true value and a C by using this: $value && Types::Serialiser::is_bool $value =item $is_true = Types::Serialiser::is_true $value Returns true iff C<$value> is C<$Types::Serialiser::true>. =item $is_false = Types::Serialiser::is_false $value Returns false iff C<$value> is C<$Types::Serialiser::false>. =back =head2 ERROR (Types::Serialiser::Error class) This class has only a single instance, C. It is used to signal an encoding or decoding error. In CBOR for example, and object that couldn't be encoded will be represented by a CBOR undefined value, which is represented by the error value in Perl. =over 4 =item $Types::Serialiser::error, Types::Serialiser::error This value represents the "error" value. Accessing values of this type will throw an exception. The constant is represented as a reference to a scalar containing C - implementations are allowed to directly test for this. =item $is_error = Types::Serialiser::is_error $value Returns false iff C<$value> is C<$Types::Serialiser::error>. =back =cut BEGIN { # for historical reasons, and to avoid extra dependencies in JSON::PP, # we alias *Types::Serialiser::Boolean with JSON::PP::Boolean. package JSON::PP::Boolean; *Types::Serialiser::Boolean:: = *JSON::PP::Boolean::; } { # this must done before blessing to work around bugs # in perl < 5.18 (it seems to be fixed in 5.18). package Types::Serialiser::BooleanBase; use overload "0+" => sub { ${$_[0]} }, "++" => sub { $_[0] = ${$_[0]} + 1 }, "--" => sub { $_[0] = ${$_[0]} - 1 }, fallback => 1; @Types::Serialiser::Boolean::ISA = Types::Serialiser::BooleanBase::; } our $true = do { bless \(my $dummy = 1), Types::Serialiser::Boolean:: }; our $false = do { bless \(my $dummy = 0), Types::Serialiser::Boolean:: }; our $error = do { bless \(my $dummy ), Types::Serialiser::Error:: }; sub true () { $true } sub false () { $false } sub error () { $error } sub as_bool($) { $_[0] ? $true : $false } sub is_bool ($) { UNIVERSAL::isa $_[0], Types::Serialiser::Boolean:: } sub is_true ($) { $_[0] && UNIVERSAL::isa $_[0], Types::Serialiser::Boolean:: } sub is_false ($) { !$_[0] && UNIVERSAL::isa $_[0], Types::Serialiser::Boolean:: } sub is_error ($) { UNIVERSAL::isa $_[0], Types::Serialiser::Error:: } package Types::Serialiser::Error; sub error { require Carp; Carp::croak ("caught attempt to use the Types::Serialiser::error value"); }; use overload "0+" => \&error, "++" => \&error, "--" => \&error, fallback => 1; =head1 NOTES FOR XS USERS The recommended way to detect whether a scalar is one of these objects is to check whether the stash is the C or C stash, and then follow the scalar reference to see if it's C<1> (true), C<0> (false) or C (error). While it is possible to use an isa test, directly comparing stash pointers is faster and guaranteed to work. For historical reasons, the C stash is just an alias for C. When printed, the classname with usually be C, but isa tests and stash pointer comparison will normally work correctly (i.e. Types::Serialiser::true ISA JSON::PP::Boolean, but also ISA Types::Serialiser::Boolean). =head1 A GENERIC OBJECT SERIALIATION PROTOCOL This section explains the object serialisation protocol used by L. It is meant to be generic enough to support any kind of generic object serialiser. This protocol is called "the Types::Serialiser object serialisation protocol". =head2 ENCODING When the encoder encounters an object that it cannot otherwise encode (for example, L can encode a few special types itself, and will first attempt to use the special C serialisation protocol), it will look up the C method on the object. Note that the C method will normally be called I encoding, and I change the data structure that is being encoded in any way, or it might cause memory corruption or worse. If it exists, it will call it with two arguments: the object to serialise, and a constant string that indicates the name of the data model. For example L uses C, and the L and L modules (or any other JSON serialiser), would use C as second argument. The C method can then return zero or more values to identify the object instance. The serialiser is then supposed to encode the class name and all of these return values (which must be encodable in the format) using the relevant form for Perl objects. In CBOR for example, there is a registered tag number for encoded perl objects. The values that C returns must be serialisable with the serialiser that calls it. Therefore, it is recommended to use simple types such as strings and numbers, and maybe array references and hashes (basically, the JSON data model). You can always use a more complex format for a specific data model by checking the second argument, the data model. The "data model" is not the same as the "data format" - the data model indicates what types and kinds of return values can be returned from C. For example, in C it is permissible to return tagged CBOR values, while JSON does not support these at all, so C would be a valid (but too limited) data model name for C. similarly, a serialising format that supports more or less the same data model as JSON could use C as data model without losing anything. =head2 DECODING When the decoder then encounters such an encoded perl object, it should look up the C method on the stored classname, and invoke it with the classname, the constant string to identify the data model/data format, and all the return values returned by C. =head2 EXAMPLES See the C section in the L manpage for more details, an example implementation, and code examples. Here is an example C/C method pair: sub My::Object::FREEZE { my ($self, $model) = @_; ($self->{type}, $self->{id}, $self->{variant}) } sub My::Object::THAW { my ($class, $model, $type, $id, $variant) = @_; $class->new (type => $type, id => $id, variant => $variant) } =head1 BUGS The use of L makes this module much heavier than it should be (on my system, this module: 4kB RSS, overload: 260kB RSS). =head1 SEE ALSO Currently, L and L use these types. =head1 AUTHOR Marc Lehmann http://home.schmorp.de/ =cut 1 PK!Pperl5/Path/Tiny.pmnu6$use 5.008001; use strict; use warnings; package Path::Tiny; # ABSTRACT: File path utility our $VERSION = '0.144'; # Dependencies use Config; use Exporter 5.57 (qw/import/); use File::Spec 0.86 (); # shipped with 5.8.1 use Carp (); our @EXPORT = qw/path/; our @EXPORT_OK = qw/cwd rootdir tempfile tempdir/; use constant { PATH => 0, CANON => 1, VOL => 2, DIR => 3, FILE => 4, TEMP => 5, IS_WIN32 => ( $^O eq 'MSWin32' ), }; use overload ( q{""} => 'stringify', bool => sub () { 1 }, fallback => 1, ); # FREEZE/THAW per Sereal/CBOR/Types::Serialiser protocol sub THAW { return path( $_[2] ) } { no warnings 'once'; *TO_JSON = *FREEZE = \&stringify }; my $HAS_UU; # has Unicode::UTF8; lazily populated sub _check_UU { local $SIG{__DIE__}; # prevent outer handler from being called !!eval { require Unicode::UTF8; Unicode::UTF8->VERSION(0.58); 1; }; } my $HAS_PU; # has PerlIO::utf8_strict; lazily populated sub _check_PU { local $SIG{__DIE__}; # prevent outer handler from being called !!eval { # MUST preload Encode or $SIG{__DIE__} localization fails # on some Perl 5.8.8 (maybe other 5.8.*) compiled with -O2. require Encode; require PerlIO::utf8_strict; PerlIO::utf8_strict->VERSION(0.003); 1; }; } my $HAS_FLOCK = $Config{d_flock} || $Config{d_fcntl_can_lock} || $Config{d_lockf}; # notions of "root" directories differ on Win32: \\server\dir\ or C:\ or \ my $SLASH = qr{[\\/]}; my $NOTSLASH = qr{[^\\/]}; my $DRV_VOL = qr{[a-z]:}i; my $UNC_VOL = qr{$SLASH $SLASH $NOTSLASH+ $SLASH $NOTSLASH+}x; my $WIN32_ROOT = qr{(?: $UNC_VOL $SLASH | $DRV_VOL $SLASH | $SLASH )}x; sub _win32_vol { my ( $path, $drv ) = @_; require Cwd; my $dcwd = eval { Cwd::getdcwd($drv) }; # C: -> C:\some\cwd # getdcwd on non-existent drive returns empty string # so just use the original drive Z: -> Z: $dcwd = "$drv" unless defined $dcwd && length $dcwd; # normalize dwcd to end with a slash: might be C:\some\cwd or D:\ or Z: $dcwd =~ s{$SLASH?\z}{/}; # make the path absolute with dcwd $path =~ s{^$DRV_VOL}{$dcwd}; return $path; } # This is a string test for before we have the object; see is_rootdir for well-formed # object test sub _is_root { return IS_WIN32() ? ( $_[0] =~ /^$WIN32_ROOT\z/ ) : ( $_[0] eq '/' ); } BEGIN { *_same = IS_WIN32() ? sub { lc( $_[0] ) eq lc( $_[1] ) } : sub { $_[0] eq $_[1] }; } # mode bits encoded for chmod in symbolic mode my %MODEBITS = ( om => 0007, gm => 0070, um => 0700 ); ## no critic { my $m = 0; $MODEBITS{$_} = ( 1 << $m++ ) for qw/ox ow or gx gw gr ux uw ur/ }; sub _symbolic_chmod { my ( $mode, $symbolic ) = @_; for my $clause ( split /,\s*/, $symbolic ) { if ( $clause =~ m{\A([augo]+)([=+-])([rwx]+)\z} ) { my ( $who, $action, $perms ) = ( $1, $2, $3 ); $who =~ s/a/ugo/g; for my $w ( split //, $who ) { my $p = 0; $p |= $MODEBITS{"$w$_"} for split //, $perms; if ( $action eq '=' ) { $mode = ( $mode & ~$MODEBITS{"${w}m"} ) | $p; } else { $mode = $action eq "+" ? ( $mode | $p ) : ( $mode & ~$p ); } } } else { Carp::croak("Invalid mode clause '$clause' for chmod()"); } } return $mode; } # flock doesn't work on NFS on BSD or on some filesystems like lustre. # Since program authors often can't control or detect that, we warn once # instead of being fatal if we can detect it and people who need it strict # can fatalize the 'flock' category #<<< No perltidy { package flock; use warnings::register } #>>> my $WARNED_NO_FLOCK = 0; sub _throw { my ( $self, $function, $file, $msg ) = @_; if ( $function =~ /^flock/ && $! =~ /operation not supported|function not implemented/i && !warnings::fatal_enabled('flock') ) { if ( !$WARNED_NO_FLOCK ) { warnings::warn( flock => "Flock not available: '$!': continuing in unsafe mode" ); $WARNED_NO_FLOCK++; } } else { $msg = $! unless defined $msg; Path::Tiny::Error->throw( $function, ( defined $file ? $file : $self->[PATH] ), $msg ); } return; } # cheapo option validation sub _get_args { my ( $raw, @valid ) = @_; if ( defined($raw) && ref($raw) ne 'HASH' ) { my ( undef, undef, undef, $called_as ) = caller(1); $called_as =~ s{^.*::}{}; Carp::croak("Options for $called_as must be a hash reference"); } my $cooked = {}; for my $k (@valid) { $cooked->{$k} = delete $raw->{$k} if exists $raw->{$k}; } if ( keys %$raw ) { my ( undef, undef, undef, $called_as ) = caller(1); $called_as =~ s{^.*::}{}; Carp::croak( "Invalid option(s) for $called_as: " . join( ", ", keys %$raw ) ); } return $cooked; } #--------------------------------------------------------------------------# # Constructors #--------------------------------------------------------------------------# #pod =construct path #pod #pod $path = path("foo/bar"); #pod $path = path("/tmp", "file.txt"); # list #pod $path = path("."); # cwd #pod #pod Constructs a C object. It doesn't matter if you give a file or #pod directory path. It's still up to you to call directory-like methods only on #pod directories and file-like methods only on files. This function is exported #pod automatically by default. #pod #pod The first argument must be defined and have non-zero length or an exception #pod will be thrown. This prevents subtle, dangerous errors with code like #pod C<< path( maybe_undef() )->remove_tree >>. #pod #pod B: If and only if the B character of the B argument #pod to C is a tilde ('~'), then tilde replacement will be applied to the #pod first path segment. A single tilde will be replaced with C and a #pod tilde followed by a username will be replaced with output of #pod C. B. #pod See L for more. #pod #pod On Windows, if the path consists of a drive identifier without a path component #pod (C or C), it will be expanded to the absolute path of the current #pod directory on that volume using C. #pod #pod If called with a single C argument, the original is returned unless #pod the original is holding a temporary file or directory reference in which case a #pod stringified copy is made. #pod #pod $path = path("foo/bar"); #pod $temp = Path::Tiny->tempfile; #pod #pod $p2 = path($path); # like $p2 = $path #pod $t2 = path($temp); # like $t2 = path( "$temp" ) #pod #pod This optimizes copies without proliferating references unexpectedly if a copy is #pod made by code outside your control. #pod #pod Current API available since 0.017. #pod #pod =cut sub path { my $path = shift; Carp::croak("Path::Tiny paths require defined, positive-length parts") unless 1 + @_ == grep { defined && length } $path, @_; # non-temp Path::Tiny objects are effectively immutable and can be reused if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) { return $path; } # stringify objects $path = "$path"; # do any tilde expansions my ($tilde) = $path =~ m{^(~[^/]*)}; if ( defined $tilde ) { # Escape File::Glob metacharacters (my $escaped = $tilde) =~ s/([\[\{\*\?\\])/\\$1/g; require File::Glob; my ($homedir) = File::Glob::bsd_glob($escaped); if (defined $homedir && ! $File::Glob::ERROR) { $homedir =~ tr[\\][/] if IS_WIN32(); $path =~ s{^\Q$tilde\E}{$homedir}; } } unshift @_, $path; goto &_pathify; } # _path is like path but without tilde expansion sub _path { my $path = shift; Carp::croak("Path::Tiny paths require defined, positive-length parts") unless 1 + @_ == grep { defined && length } $path, @_; # non-temp Path::Tiny objects are effectively immutable and can be reused if ( !@_ && ref($path) eq __PACKAGE__ && !$path->[TEMP] ) { return $path; } # stringify objects $path = "$path"; unshift @_, $path; goto &_pathify; } # _pathify expects one or more string arguments, then joins and canonicalizes # them into an object. sub _pathify { my $path = shift; # expand relative volume paths on windows; put trailing slash on UNC root if ( IS_WIN32() ) { $path = _win32_vol( $path, $1 ) if $path =~ m{^($DRV_VOL)(?:$NOTSLASH|\z)}; $path .= "/" if $path =~ m{^$UNC_VOL\z}; } # concatenations stringifies objects, too if (@_) { $path .= ( _is_root($path) ? "" : "/" ) . join( "/", @_ ); } # canonicalize, but with unix slashes and put back trailing volume slash my $cpath = $path = File::Spec->canonpath($path); $path =~ tr[\\][/] if IS_WIN32(); $path = "/" if $path eq '/..'; # for old File::Spec $path .= "/" if IS_WIN32() && $path =~ m{^$UNC_VOL\z}; # root paths must always have a trailing slash, but other paths must not if ( _is_root($path) ) { $path =~ s{/?\z}{/}; } else { $path =~ s{/\z}{}; } bless [ $path, $cpath ], __PACKAGE__; } #pod =construct new #pod #pod $path = Path::Tiny->new("foo/bar"); #pod #pod This is just like C, but with method call overhead. (Why would you #pod do that?) #pod #pod Current API available since 0.001. #pod #pod =cut sub new { shift; path(@_) } #pod =construct cwd #pod #pod $path = Path::Tiny->cwd; # path( Cwd::getcwd ) #pod $path = cwd; # optional export #pod #pod Gives you the absolute path to the current directory as a C object. #pod This is slightly faster than C<< path(".")->absolute >>. #pod #pod C may be exported on request and used as a function instead of as a #pod method. #pod #pod Current API available since 0.018. #pod #pod =cut sub cwd { require Cwd; return _path( Cwd::getcwd() ); } #pod =construct rootdir #pod #pod $path = Path::Tiny->rootdir; # / #pod $path = rootdir; # optional export #pod #pod Gives you C<< File::Spec->rootdir >> as a C object if you're too #pod picky for C. #pod #pod C may be exported on request and used as a function instead of as a #pod method. #pod #pod Current API available since 0.018. #pod #pod =cut sub rootdir { _path( File::Spec->rootdir ) } #pod =construct tempfile, tempdir #pod #pod $temp = Path::Tiny->tempfile( @options ); #pod $temp = Path::Tiny->tempdir( @options ); #pod $temp = $dirpath->tempfile( @options ); #pod $temp = $dirpath->tempdir( @options ); #pod $temp = tempfile( @options ); # optional export #pod $temp = tempdir( @options ); # optional export #pod #pod C passes the options to C<< File::Temp->new >> and returns a #pod C object with the file name. The C option will be enabled #pod by default, but you can override that by passing C<< TMPDIR => 0 >> along with #pod the options. (If you use an absolute C