File Coverage

blib/lib/Dist/Data.pm
Criterion Covered Total %
statement 117 129 90.7
branch 32 44 72.7
condition 14 18 77.7
subroutine 24 27 88.8
pod 0 9 0.0
total 187 227 82.3


line stmt bran cond sub pod time code
1             package Dist::Data;
2             our $AUTHORITY = 'cpan:GETTY';
3             # ABSTRACT: API to access the data of a Perl distribution file or directory
4             $Dist::Data::VERSION = '0.007';
5 3     3   477366 use Moo;
  3         41290  
  3         21  
6 3     3   7614 use Archive::Any;
  3         131721  
  3         179  
7 3     3   2109 use CPAN::Meta;
  3         145206  
  3         154  
8 3     3   2136 use File::Temp;
  3         34911  
  3         463  
9 3     3   2100 use File::Find::Object;
  3         45104  
  3         136  
10 3     3   2086 use Module::Metadata;
  3         26910  
  3         177  
11 3     3   2018 use DateTime::Format::Epoch::Unix;
  3         2327277  
  3         186  
12 3     3   1987 use Dist::Metadata ();
  3         7687  
  3         7398  
13              
14             has filename => (
15             is => 'ro',
16             predicate => 'has_filename',
17             );
18              
19             has archive => (
20             is => 'ro',
21             lazy => 1,
22             builder => '_build_archive',
23             );
24              
25             sub _build_archive {
26 2     2   41 my ( $self ) = @_;
27 2 50       15 die __PACKAGE__.": need a filename" unless $self->has_filename;
28 2         32 return Archive::Any->new($self->filename);
29             }
30              
31             has cpan_meta => (
32             is => 'ro',
33             lazy => 1,
34             builder => '_build_cpan_meta',
35             handles => [qw(
36             abstract
37             description
38             dynamic_config
39             generated_by
40             name
41             release_status
42             version
43             authors
44             keywords
45             licenses
46             meta_spec
47             resources
48             provides
49             no_index
50             prereqs
51             optional_features
52             )]
53             );
54 3     3 0 105515 sub cm { shift->cpan_meta(@_) }
55              
56             # LEGACY
57 0     0 0 0 sub distmeta { shift->cpan_meta(@_) }
58              
59             sub _build_cpan_meta {
60 2     2   1234 my ( $self ) = @_;
61 2 50       106 if ($self->files->{'META.json'}) {
    0          
62 2         84 CPAN::Meta->load_file($self->files->{'META.json'});
63             } elsif ($self->files->{'META.yml'}) {
64 0         0 CPAN::Meta->load_file($self->files->{'META.yml'});
65             }
66             }
67              
68             has dist_metadata => (
69             is => 'ro',
70             lazy => 1,
71             builder => '_build_dist_metadata',
72             );
73              
74             sub _build_dist_metadata {
75 2     2   27 my ( $self ) = @_;
76 2         12 $self->extract_distribution;
77 2         84 Dist::Metadata->new(dir => $self->dist_dir);
78             }
79              
80             has dir => (
81             is => 'ro',
82             predicate => 'has_dir',
83             );
84              
85             sub dir_has_dist {
86 3     3 0 9 my ( $self ) = @_;
87 3         111 my $dir = $self->dist_dir;
88 3 50       1216 return unless -d $dir;
89 3         140 return -f "$dir/Makefile.PL";
90             }
91              
92             has files => (
93             is => 'ro',
94             lazy => 1,
95             builder => '_build_files',
96             );
97              
98             sub _build_files {
99 2     2   805 my ( $self ) = @_;
100 2         16 $self->extract_distribution;
101 2         2251 my %files;
102 2         91 for ($self->get_directory_tree($self->dist_dir)) {
103 36 100       360 $files{join('/',@{$_->full_components})} = $_->path if $_->is_file;
  24         63  
104             }
105 2         138 return \%files;
106             }
107              
108             has dirs => (
109             is => 'ro',
110             lazy => 1,
111             builder => '_build_dirs',
112             );
113              
114             sub _build_dirs {
115 0     0   0 my ( $self ) = @_;
116 0         0 $self->extract_distribution;
117 0         0 my %dirs;
118 0         0 for ($self->get_directory_tree($self->dist_dir)) {
119 0 0       0 $dirs{join('/',@{$_->full_components})} = $_->path if $_->is_dir;
  0         0  
120             }
121 0         0 return \%dirs;
122             }
123              
124             has dist_dir => (
125             is => 'ro',
126             lazy => 1,
127             builder => '_build_dist_dir',
128             );
129              
130             sub _build_dist_dir {
131 3     3   32 my ( $self ) = @_;
132 3 100       38 return $self->has_dir ? $self->dir : File::Temp->newdir;
133             }
134              
135             sub extract_distribution {
136 5     5 0 15 my ( $self ) = @_;
137 5 100       32 return unless $self->has_filename;
138 3 100       12 return if $self->dir_has_dist;
139 2         55 my $ext_dir = File::Temp->newdir;
140 2         1251 $self->archive->extract($ext_dir);
141 2         582230 for ($self->get_directory_tree($ext_dir)) {
142 38         7046 my @components = @{$_->full_components};
  38         167  
143 38         606 shift @components;
144 38 100       151 if ($_->is_dir) {
145 14         612 mkdir $self->dist_dir.'/'.join('/',@components);
146             } else {
147 24         877 rename $_->path, $self->dist_dir.'/'.join('/',@components);
148             }
149             }
150 2         479 return 1;
151             }
152              
153             has packages => (
154             is => 'ro',
155             lazy => 1,
156             builder => '_build_packages',
157             );
158              
159             sub _build_packages {
160 2     2   5151 my ( $self ) = @_;
161 2         63 return $self->dist_metadata->determine_packages($self->cm);
162             # OLD - probably reused later if we introduce behaviour switches
163             # my %packages;
164             # for (keys %{$self->files}) {
165             # my $key = $_;
166             # my @components = split('/',$key);
167             # if ($key =~ /\.pm$/) {
168             # my @namespaces = Module::Extract::Namespaces->from_file($self->files->{$key});
169             # for (@namespaces) {
170             # $packages{$_} = [] unless defined $packages{$_};
171             # push @{$packages{$_}}, $key;
172             # }
173             # } elsif ($key =~ /^lib\// && $key =~ /\.pod$/) {
174             # my $packagename = $key;
175             # $packagename =~ s/^lib\///g;
176             # $packagename =~ s/\.pod$//g;
177             # $packagename =~ s/\//::/g;
178             # $packages{$packagename} = [] unless defined $packages{$packagename};
179             # push @{$packages{$packagename}}, $key;
180             # }
181             # }
182             # return \%packages;
183             }
184              
185             has namespaces => (
186             is => 'ro',
187             lazy => 1,
188             builder => '_build_namespaces',
189             );
190              
191             sub _build_namespaces {
192 1     1   15 my ( $self ) = @_;
193 1         3 my %namespaces;
194 1         3 for (keys %{$self->files}) {
  1         59  
195 12         81 my $key = $_;
196 12 100 66     55 if ($key =~ /\.pm$/ || $key =~ /\.pl$/) {
197 1         31 my $metadata = Module::Metadata->new_from_file($self->files->{$key});
198 1         1589 my @namespaces = $metadata->packages_inside;
199 1         9 for (@namespaces) {
200 2 100 66     22 next unless defined $_ && $_ ne 'main';
201 1 50       6 $namespaces{$_} = [] unless defined $namespaces{$_};
202 1         3 push @{$namespaces{$_}}, $key;
  1         11  
203             }
204             }
205             }
206 1         19 return \%namespaces;
207             }
208              
209             has documentations => (
210             is => 'ro',
211             lazy => 1,
212             builder => '_build_documentations',
213             );
214              
215             sub _build_documentations {
216 1     1   47663 my ( $self ) = @_;
217 1         4 my %docs;
218 1         3 for (keys %{$self->files}) {
  1         59  
219 12         41 my $key = $_;
220 12 100 100     61 if ($key =~ /^lib\// && $key =~ /\.pod$/) {
221 1         3 my $packagename = $key;
222 1         5 $packagename =~ s/^lib\///g;
223 1         11 $packagename =~ s/\.pod$//g;
224 1         8 $packagename =~ s/\//::/g;
225 1         5 $docs{$packagename} = $key;
226             }
227             }
228 1         16 return \%docs;
229             }
230              
231             has scripts => (
232             is => 'ro',
233             lazy => 1,
234             builder => '_build_scripts',
235             );
236              
237             sub _build_scripts {
238 1     1   19 my ( $self ) = @_;
239 1         3 my %scripts;
240 1         2 for (keys %{$self->files}) {
  1         39  
241 12 100 66     126 next unless $_ =~ /^bin\// || $_ =~ /^script\//;
242 1         5 my $key = $_;
243 1         5 my @components = split('/',$key);
244 1         2 shift @components;
245 1         8 $scripts{join('/',@components)} = $key;
246             }
247 1         18 return \%scripts;
248             }
249              
250             sub get_directory_tree {
251 4     4 0 233 my ( $self, @dirs ) = @_;
252 4         136 my $tree = File::Find::Object->new({}, @dirs);
253 4         1971 my @files;
254 4         30 while (my $r = $tree->next_obj()) {
255 74         32703 push @files, $r;
256             }
257 4         1159 return @files;
258             }
259              
260             sub file {
261 4     4 0 105061 my ( $self, $file ) = @_;
262 4         209 return $self->files->{$file};
263             }
264              
265             sub modified {
266 0     0 0 0 my ( $self ) = @_;
267 0 0       0 my $mtime = stat($self->has_filename ? $self->filename : $self->dir )->mtime;
268 0         0 return DateTime::Format::Epoch::Unix->parse_datetime($mtime);
269             }
270              
271             sub BUILD {
272 3     3 0 143 my ( $self ) = @_;
273 3 100 100     55 $self->extract_distribution if $self->has_dir && $self->has_filename;
274             }
275              
276             sub BUILDARGS {
277 3     3 0 15085 my ( $class, @args ) = @_;
278 3 50       20 die __PACKAGE__.": please give filename on new" if !@args;
279 3 100 66     10 my $arg; $arg = shift @args if @args % 2 == 1 && ref $args[0] ne 'HASH';
  3         60  
280 3 100       19 if ($arg) {
281             # should support URL also
282 2 100       73 if (-f $arg) {
    50          
283 1         29 return { filename => $arg, @args };
284             } elsif (-d $arg) {
285 1         30 return { dir => $arg, @args };
286             }
287             }
288 1         9 return $class->SUPER::BUILDARGS(@args);
289             }
290              
291             1;
292              
293             __END__
294              
295             =pod
296              
297             =head1 NAME
298              
299             Dist::Data - API to access the data of a Perl distribution file or directory
300              
301             =head1 VERSION
302              
303             version 0.007
304              
305             =head1 SYNOPSIS
306              
307             use Dist::Data;
308              
309             my $dist = Dist::Data->new('My-Sample-Distribution-0.003.tar.gz');
310              
311             # Extract files into this directory, if it not already contains a distribution
312             my $otherdist = Dist::Data->new({
313             dir => '/storage/extracted-dists/My-Sample-Distribution-0.003',
314             filename => 'My-Sample-Distribution-0.003.tar.gz',
315             });
316              
317             my $otherdist_via_dir = Dist::Data->new({
318             dir => '/storage/extracted-dists/My-Sample-Distribution-0.003',
319             });
320              
321             my %files = %{$dist->files};
322              
323             my $filename_of_distini = $dist->file('dist.ini');
324              
325             # gives back CPAN::Meta if the dist has one
326             my $cpan_meta = $dist->cpan_meta;
327             # alternative $dist->cm;
328              
329             my $version = $dist->version; # handled by CPAN::Meta object
330             my $name = $dist->name; # also
331              
332             my @authors = $dist->authors;
333              
334             my %packages = %{$dist->packages}; # via Dist::Metadata
335             my %namespaces = %{$dist->namespaces}; # via Module::Metadata
336             my %documentations = %{$dist->documentations}; # only .pod inside of lib/ (so far)
337             my %scripts = %{$dist->scripts}; # all files in bin/ and script/
338              
339             =head1 DESCRIPTION
340              
341             This distribution is used to get all information from a CPAN distribution or an extracted CPAN distribution. It tries to combine the power of other modules. Longtime it should be possible to define alternative behaviour (to be more like search.cpan.org or be like metacpan.org or whatever other system that parses CPAN Distributions).
342              
343             =encoding utf8
344              
345             =head1 SUPPORT
346              
347             IRC
348              
349             Join #perl-help on irc.perl.org. Highlight Getty for fast reaction :).
350              
351             Repository
352              
353             http://github.com/Getty/p5-dist-data
354             Pull request and additional contributors are welcome
355              
356             Issue Tracker
357              
358             http://github.com/Getty/p5-dist-data/issues
359              
360             =head1 AUTHOR
361              
362             Torsten Raudssus <torsten@raudssus.de> L<https://raudss.us/>
363              
364             =head1 COPYRIGHT AND LICENSE
365              
366             This software is copyright (c) 2024 by Torsten Raudssus <torsten@raudssus.de> L<https://raudss.us/>.
367              
368             This is free software; you can redistribute it and/or modify it under
369             the same terms as the Perl 5 programming language system itself.
370              
371             =cut