diff options
author | Steffen Mueller <smueller@cpan.org> | 2011-05-09 22:39:25 +0200 |
---|---|---|
committer | Steffen Mueller <smueller@cpan.org> | 2011-07-18 23:09:43 +0200 |
commit | e30afae0a3edd83821767f6ac56dbf053c52f468 (patch) | |
tree | 5fc5f9104ca330506680e0bf86e1a856f9a2de3c | |
parent | f11ca51e41e898a77f1fd33b9e0371e69b1be73a (diff) | |
download | perl-smueller/perllocal.tar.gz |
Parser/generator for perllocal.pod filessmueller/perllocal
This adds an interface to parse/append to perllocal.pod files
programmatically. The plan is to patch ExtUtils::Command::MM to use this
module if available and to teach Module::Build to use it, too, so that
M::B can finally append to perllocal.pod.
-rw-r--r-- | MANIFEST | 5 | ||||
-rw-r--r-- | dist/ExtUtils-Install/lib/ExtUtils/Perllocal.pm | 100 | ||||
-rw-r--r-- | dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Entry.pm | 166 | ||||
-rw-r--r-- | dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Parser.pm | 161 | ||||
-rw-r--r-- | dist/ExtUtils-Install/t/Perllocal.t | 62 | ||||
-rw-r--r-- | dist/ExtUtils-Install/t/data/perllocal.pod | 352 |
6 files changed, 846 insertions, 0 deletions
@@ -2959,7 +2959,11 @@ dist/ExtUtils-Install/Changes ExtUtils-Install change log dist/ExtUtils-Install/lib/ExtUtils/Installed.pm Information on installed extensions dist/ExtUtils-Install/lib/ExtUtils/Install.pm Handles 'make install' on extensions dist/ExtUtils-Install/lib/ExtUtils/Packlist.pm Manipulates .packlist files +dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Entry.pm Parse/generate perllocal.pod +dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Parser.pm Parse/generate perllocal.pod +dist/ExtUtils-Install/lib/ExtUtils/Perllocal.pm Parse/generate perllocal.pod dist/ExtUtils-Install/t/can_write_dir.t Does the _can_write_dir function of ExtUtils::Install work properly? +dist/ExtUtils-Install/t/data/perllocal.pod Test data for ExtUtils::Perllocal dist/ExtUtils-Install/t/Installapi2.t See if new api for ExtUtils::Install::install() works dist/ExtUtils-Install/t/Installed.t See if ExtUtils::Installed works dist/ExtUtils-Install/t/Install.t See if ExtUtils::Install works @@ -2968,6 +2972,7 @@ dist/ExtUtils-Install/t/lib/MakeMaker/Test/Setup/BFD.pm MakeMaker test utilities dist/ExtUtils-Install/t/lib/MakeMaker/Test/Utils.pm MakeMaker test utilities dist/ExtUtils-Install/t/lib/TieOut.pm Testing library to capture prints dist/ExtUtils-Install/t/Packlist.t See if Packlist works +dist/ExtUtils-Install/t/Perllocal.t See if Perllocal works dist/ExtUtils-Manifest/lib/ExtUtils/Manifest.pm Utilities to write MANIFEST files dist/ExtUtils-Manifest/lib/ExtUtils/MANIFEST.SKIP The default MANIFEST.SKIP dist/ExtUtils-Manifest/t/Manifest.t See if ExtUtils::Manifest works diff --git a/dist/ExtUtils-Install/lib/ExtUtils/Perllocal.pm b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal.pm new file mode 100644 index 0000000000..e85c02097b --- /dev/null +++ b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal.pm @@ -0,0 +1,100 @@ +package ExtUtils::Perllocal; + +use 5.00503; +use strict; +use Carp qw(); +use vars qw($VERSION); +$VERSION = '1.44_01'; +$VERSION = eval $VERSION; +require ExtUtils::Perllocal::Entry; + +sub new { + my $class = shift; + my %opts = @_; + die "'file' parameter required!" if not defined $opts{file}; + my $self = bless({%opts} => $class); + return $self; +} + +sub append_entry { + my $self = shift; + my $entry = shift; + + my $perllocal = $self->{file}; + my $pod = $entry->as_pod; + open FH, ">>$perllocal" + or die "Cannot open perllocal file '$perllocal' for appending: $!"; + print FH $pod; + close FH + or die "Failed to write to perllocal file '$perllocal': $!"; +} + +sub get_entries { + my $self = shift; + + require ExtUtils::Perllocal::Parser; + my $file = $self->{file}; + + my $parser = ExtUtils::Perllocal::Parser->new; + return $parser->parse_from_file($file); +} + +1; + +__END__ + +=head1 NAME + +ExtUtils::Perllocal - manage perllocal.pod files + +=head1 SYNOPSIS + + use ExtUtils::Perllocal; + my $pl = ExtUtils::Perllocal->new(file => '/path/to/perllocal.pod'); + my $entry = ExtUtils::Perllocal::Entry->new(...); + $pl->append_entry($entry); # writes to file + my @entries = $pl->get_entries(); # parses file + foreach my $entry (@entries) { + # See ExtUtils::Perllocal::Entry + } + +=head1 DESCRIPTION + +C<ExtUtils::Perllocal> provides a standard way to manage F<perllocal.pod> files. + +=head1 METHODS + +=head2 new + +Contstructor. Takes named parameters. + +Mandatory parameter C<file> should point at the F<perllocal.pod> file +that you intend to append to or parse. + +=head2 append_entry + +Appends an entry to the specified F<perllocal.pod> file. Takes +an L<ExtUtils::Perllocal::Entry> object as argument. + +=head2 get_entries + +Parses the file and returns the list of L<ExtUtils::Perllocal::Entry> +objects from the file. + +B<Note:> Requires the C<Pod::Parser> and C<Time::Local> modules +at run-time when C<get_entries> is called. + +=head1 AUTHOR + +Steffen Mueller, C<smueller@cpan.org> + +Inspired by C<ExtUtils::Command::MM> by Randy Kobes. + +=head1 COPRIGHT AND LICENSE + +Copyright (c) 2011 by Steffen Mueller + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut diff --git a/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Entry.pm b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Entry.pm new file mode 100644 index 0000000000..56021256e1 --- /dev/null +++ b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Entry.pm @@ -0,0 +1,166 @@ +package ExtUtils::Perllocal::Entry; + +use 5.00503; +use strict; +use Carp qw(); + +sub new { + my $class = shift; + my %opts = @_; + if (not defined $opts{name}) { + Carp::croak("Need module name as 'name' parameter"); + } + + my $self = bless({ + 'time' => time(), + type => 'Module', + data => {}, + %opts, + } => $class); + + return $self; +} + +sub as_pod { + my $self = shift; + + my $pod; + $pod = sprintf <<POD, scalar localtime($self->{'time'}); +=head2 %s: C<$self->{type}> L<$self->{name}|$self->{name}> + +=over 4 + +POD + + foreach my $key (sort keys %{$self->{data}}) { + if ($key =~ ':') { + die "The keys in the 'data' hash of perllocal.pod entries " + . "must not contain colons, but '$key' does!"; + } + my $value = $self->{data}{$key}; + $pod .= <<POD +=item * + +C<$key: $value> + +POD + } + $pod .= "=back\n\n"; + + return $pod; +} + +sub name { + my $self = shift; + if (@_) { + $self->{name} = shift; + } + return $self->{name}; +} + +sub time { + my $self = shift; + if (@_) { + $self->{time} = shift; + } + return $self->{time}; +} + +sub type { + my $self = shift; + if (@_) { + $self->{type} = shift; + } + return $self->{type}; +} + +sub data { + my $self = shift; + return $self->{data}; +} + + +1; + +__END__ + +=head1 NAME + +ExtUtils::Perllocal::Entry - A single perllocal.pod entry + +=head1 SYNOPSIS + + use ExtUtils::Perllocal; + my $pl = ExtUtils::Perllocal->new(file => '/path/to/perllocal.pod'); + my $entry = ExtUtils::Perllocal::Entry->new( + name => 'The::Module', + type => 'Module', # defaults to 'Module' + 'time' => $seconds_since_epoch, # defaults to running time() + data => { # key/value pairs that will be written as an =item list, no defaults + # These are all conventions: + "installed into" => $path_to_installation, + LINKTYPE => 'dynamic', # static|dynamic + VERSION => The::Module->VERSION, + EXE_FILES => join(' ', @exe_files), + }, + ); + $pl->append_entry($entry); # writes to file + +=head1 DESCRIPTION + +C<ExtUtils::Perllocal::Entry> is the in-memory representation of a single +F<perllocal.pod> entry. + +=head1 METHODS + +=head2 new + +Constructor. Takes named parameters. +Requires the C<name> parameter indicating the module name. + +C<type> is the type of the thing to be installed and defaults to C<Module>. +C<time> is the installation time as seconds since the UNIX epoch. +C<data> can contain key/value pairs of additional data to include +as an itemized list in alphabetical key order. Some conventional +data is shown in the SYNOPSIS. + +Due to the historic output format, the keys of the data hash +cannot contain colons or else, parsing them again would become +impossible. + +=head2 as_pod + +Returns the POD representation of the entry. + +=head2 name + +Read/write accessor for the module name. + +=head2 time + +Read/write accessor for the installation time. + +=head2 type + +Read/write accessor for the installation type. + +=head2 data + +Read accessor for the additional data (see constructor docs). +Returns the actual hash ref. that is contained in the object, +so modifying it modifies the state of the object. + +=head1 AUTHOR + +Steffen Mueller, C<smueller@cpan.org> + +Inspired by C<ExtUtils::Command::MM> by Randy Kobes. + +=head1 COPRIGHT AND LICENSE + +Copyright (c) 2011 by Steffen Mueller + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut diff --git a/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Parser.pm b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Parser.pm new file mode 100644 index 0000000000..404aff4d46 --- /dev/null +++ b/dist/ExtUtils-Install/lib/ExtUtils/Perllocal/Parser.pm @@ -0,0 +1,161 @@ +package ExtUtils::Perllocal::Parser; + +use 5.00503; +use strict; +use Carp qw(); +require ExtUtils::Perllocal::Entry; +require Pod::Simple::SimpleTree; +require Time::Local; + +sub new { + my $class = shift; + my %opt = @_; + my $self = bless({%opt} => $class); + return $self; +} + +sub parse_from_file { + my $self = shift; + my $file = shift; + local $self->{file} = $file; + my $psst = Pod::Simple::SimpleTree->new->parse_file($file)->root; + my @nodes = _subnodes($psst); + + my @entries; + while (@nodes) { + # Parse this: =head2 Wed Nov 3 20:46:45 2010: C<Module> L<Module::CoreList|Module::CoreList> + my $head2 = shift @nodes; + if ($head2->[0] ne 'head2') { + $self->_report_error($head2, "Expected =head2"); + next; + } + my @subn = _subnodes($head2); + if (@subn != 4 or ref($subn[0]) + or not ref($subn[1]) or $subn[1][0] ne 'C' + or not ref($subn[3]) or not $subn[3][0] eq 'L') + { + $self->_report_error($head2, "Expected string of format '[date]: C<[type]> L<[name]>"); + next; + } + my $type = $subn[1][2]; + my $name = $subn[3][2]; + my $epoch = date_str_to_epoch(\$subn[0]); + if (not $epoch or not $subn[0] =~ /^:/) { + $self->_report_error($head2, "Expected string of format '[date]: C<[type]> L<[name]>"); + } + + my $entry = ExtUtils::Perllocal::Entry->new( + 'time' => $epoch, type => $type, name => $name, + ); + my $entry_data = $entry->data; + + # Parse the data section (=over.. =item * C<key: value> =back) + my $over = shift @nodes; + @subn = _subnodes($over); + foreach my $bullet (@subn) { + if (not ref($bullet)) { + $self->_report_error($over, "Expected only =item's in =over"); + next; + } + elsif (not $bullet->[0] eq 'item-bullet') { + $self->_report_error($bullet, "Expected only =item's in =over"); + next; + } + elsif (not ref($bullet->[2]) or $bullet->[2][0] ne 'C') { + $self->_report_error($bullet, "Expected C<> in =item"); + next; + } + elsif (ref($bullet->[2][2])) { + $self->_report_error($bullet, "Expected text in the C<> within each=item"); + next; + } + + my $text = $bullet->[2][2]; + my ($key, $value) = split /\s*:\s*/, $text, 2; + if (not defined $key or not defined $value) { + $self->_report_error($bullet, "Expected text of the form 'C<key: value>'"); + } + $entry_data->{$key} = $value; + } + push @entries, $entry; + } + + return @entries; +} + +sub _subnodes { + my $n = shift; + return @{$n}[2..$#$n]; +} + +sub _report_error { + my $self = shift; + my $node = shift; + my $err = shift; + + return if $self->{silent}; + my $debug = 1; + if (ref($node)) { + my $line = $node->[1]{start_line}; + Carp::carp("Invalid perllocal.pod at $self->{file}:$line: $err"); + if ($debug) { + require Data::Dumper; + warn Data::Dumper->Dump([$node], ['$node']); + } + } + else { + Carp::carp("Invalid perllocal.pod '$self->{file}': $err"); + } +} + + +SCOPE: { + # Low dependency mode... parse manually. Yikes. + my $WeekDayRegexp = qr/(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)/; + my $MonthRegexp = qr/(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/; + my $imonth = 0; + my %MonthToNumber = map {$_ => $imonth++} qw( + Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + ); + + sub date_str_to_epoch { + my $strref = shift; + $$strref =~ s/^$WeekDayRegexp\s+($MonthRegexp) + \s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d\d\d\d)//ox + or return(); + + my $epoch = Time::Local::timelocal($5, $4, $3, $2, $MonthToNumber{$1}, $6); + return $epoch; + } +} + +1; + +__END__ + +=head1 NAME + +ExtUtils::Perllocal::Parser - Internal parser tool for ExtUtils::Perllocal + +=head1 SYNOPSIS + + use ExtUtils::Perllocal; + +=head1 DESCRIPTION + +Internal to L<ExtUtils::Perllocal>. B<Never> use this directly! + +=head1 AUTHOR + +Steffen Mueller, C<smueller@cpan.org> + +Inspired by C<ExtUtils::Command::MM> by Randy Kobes. + +=head1 COPRIGHT AND LICENSE + +Copyright (c) 2011 by Steffen Mueller + +This module is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut diff --git a/dist/ExtUtils-Install/t/Perllocal.t b/dist/ExtUtils-Install/t/Perllocal.t new file mode 100644 index 0000000000..2f8265ecb6 --- /dev/null +++ b/dist/ExtUtils-Install/t/Perllocal.t @@ -0,0 +1,62 @@ +#!/usr/bin/perl -w + +# Test ExtUtils::Install. + +use strict; +use Test::More tests => 10; +use File::Spec; +BEGIN { use_ok('ExtUtils::Perllocal') } + +my $datafile = File::Spec->catdir("t", "data", "perllocal.pod"); +$datafile = File::Spec->catdir("data", "perllocal.pod") if not -f $datafile; +ok(-f $datafile, "test file exists"); + +my $pl = ExtUtils::Perllocal->new(file => $datafile); +isa_ok($pl, 'ExtUtils::Perllocal'); +my @entries = $pl->get_entries(); +is(scalar(@entries), 16, "Found all entries"); + +my $pod = join '', map $_->as_pod, @entries; +is($pod, slurp($datafile), "rountrip okay"); + +my $testfile = $datafile . '.tmp'; +$SIG{INT} = $SIG{HUP} = $SIG{TERM} = sub { + unlink $testfile; + exit(1); +}; + +END {unlink $testfile} + +open OFH, ">$testfile" + or die "Cannot open $testfile for writing: $!"; + +print OFH $pod; +close OFH; + +$pl = ExtUtils::Perllocal->new(file => $testfile); +my %entrydata = ( + type => 'Foo', + name => 'The::Name', + 'time' => 1304973319, + data => {d1 => 'foo', bar => 'baz'}, +); +$pl->append_entry(ExtUtils::Perllocal::Entry->new(%entrydata)); + +$pl = ExtUtils::Perllocal->new(file => $testfile); +@entries = $pl->get_entries(); +is(scalar(@entries), 17, "Found all entries + 1"); +my $e = $entries[-1]; +is($e->type, $entrydata{type}); +is($e->name, $entrydata{name}); +is($e->time, $entrydata{time}); +is_deeply($e->data, $entrydata{data}); +unlink($testfile); + +sub slurp { + my $datafile = shift; + open FH, "<$datafile" or die $!; + local $/; + my $tmp = <FH>; + close FH; + $tmp +} diff --git a/dist/ExtUtils-Install/t/data/perllocal.pod b/dist/ExtUtils-Install/t/data/perllocal.pod new file mode 100644 index 0000000000..08a45f2361 --- /dev/null +++ b/dist/ExtUtils-Install/t/data/perllocal.pod @@ -0,0 +1,352 @@ +=head2 Wed Nov 3 20:46:45 2010: C<Module> L<Module::CoreList|Module::CoreList> + +=over 4 + +=item * + +C<EXE_FILES: corelist> + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 2.40> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Sun Nov 7 16:42:24 2010: C<Module> L<Class::XSAccessor|Class::XSAccessor> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 1.09> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Thu Dec 30 14:35:32 2010: C<Module> L<Parallel::ForkManager|Parallel::ForkManager> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.7.9> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 10:29:08 2010: C<Module> L<Graph::Easy|Graph::Easy> + +=over 4 + +=item * + +C<EXE_FILES: bin/graph-easy> + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.70> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 10:31:32 2010: C<Module> L<Class::XSAccessor|Class::XSAccessor> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 1.11> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 14:18:33 2010: C<Module> L<Statistics::Test::Sequence|Statistics::Test::Sequence> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.01> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 14:18:35 2010: C<Module> L<Math::Random::MT|Math::Random::MT> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 1.12> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 14:18:37 2010: C<Module> L<Statistics::Test::RandomWalk|Statistics::Test::RandomWalk> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.01> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 14:30:35 2010: C<Module> L<Text::FindIndent|Text::FindIndent> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.09> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 17:06:52 2010: C<Module> L<Math::Symbolic|Math::Symbolic> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.606> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Fri Dec 31 17:16:31 2010: C<Module> L<Regexp::Common|Regexp::Common> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 2010010201> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Sat Jan 8 15:03:09 2011: C<Module> L<Algorithm::QuadTree|Algorithm::QuadTree> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.1> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Sat Jan 8 18:23:47 2011: C<Module> L<Devel::NYTProf|Devel::NYTProf> + +=over 4 + +=item * + +C<EXE_FILES: bin/nytprofhtml bin/nytprofcsv bin/nytprofcg bin/nytprofmerge> + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 4.06> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Sat Jan 8 19:08:57 2011: C<Module> L<Algorithm::SpatialIndex|Algorithm::SpatialIndex> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.01> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Wed Jan 19 13:51:49 2011: C<Module> L<ExtUtils::CBuilder|ExtUtils::CBuilder> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 0.2802> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + +=head2 Wed Jan 19 13:51:52 2011: C<Module> L<ExtUtils::ParseXS|ExtUtils::ParseXS> + +=over 4 + +=item * + +C<EXE_FILES: > + +=item * + +C<LINKTYPE: dynamic> + +=item * + +C<VERSION: 2.2206> + +=item * + +C<installed into: /usr/local/share/perl/5.10.1> + +=back + |