File Coverage

blib/lib/Module/CPANfile.pm
Criterion Covered Total %
statement 119 125 95.2
branch 24 30 80.0
condition 7 11 63.6
subroutine 29 30 96.6
pod 10 18 55.5
total 189 214 88.3


line stmt bran cond sub pod time code
1             package Module::CPANfile;
2 6     6   23051 use strict;
  6         10  
  6         158  
3 6     6   22 use warnings;
  6         8  
  6         142  
4 6     6   22 use Cwd;
  6         8  
  6         371  
5 6     6   27 use Carp ();
  6         7  
  6         98  
6 6     6   2295 use Module::CPANfile::Environment;
  6         16  
  6         212  
7 6     6   2610 use Module::CPANfile::Requirement;
  6         10  
  6         705  
8              
9             our $VERSION = '1.1002';
10              
11             BEGIN {
12 6 50   6   50 if (${^TAINT}) {
13             *untaint = sub {
14 0         0 my $str = shift;
15 0         0 ($str) = $str =~ /^(.+)$/s;
16 0         0 $str;
17 0         0 };
18             } else {
19 6     24   9737 *untaint = sub { $_[0] };
  24         99  
20             }
21             }
22              
23             sub new {
24 14     14 0 24 my($class, $file) = @_;
25 14         32 bless {}, $class;
26             }
27              
28             sub load {
29 13     13 1 20306 my($proto, $file) = @_;
30              
31 13 50       72 my $self = ref $proto ? $proto : $proto->new;
32 13   66     73 $self->parse($file || _default_cpanfile());
33 11         29 $self;
34             }
35              
36             sub save {
37 1     1 1 4 my($self, $path) = @_;
38              
39 1 50       101 open my $out, ">", $path or die "$path: $!";
40 1         2 print {$out} $self->to_string;
  1         5  
41             }
42              
43             sub parse {
44 13     13 0 23 my($self, $file) = @_;
45              
46 13         18 my $code = do {
47 13 100       476 open my $fh, "<", $file or die "$file: $!";
48 12         242 join '', <$fh>;
49             };
50              
51 12         38 $code = untaint $code;
52              
53 12         100 my $env = Module::CPANfile::Environment->new($file);
54 12 50       55 $env->parse($code) or die $@;
55              
56 11         44 $self->{_mirrors} = $env->mirrors;
57 11         34 $self->{_prereqs} = $env->prereqs;
58             }
59              
60             sub from_prereqs {
61 1     1 1 1115 my($proto, $prereqs) = @_;
62              
63 1         2 my $self = $proto->new;
64 1         7 $self->{_prereqs} = Module::CPANfile::Prereqs->from_cpan_meta($prereqs);
65              
66 1         1 $self;
67             }
68              
69             sub mirrors {
70 7     7 0 2690 my $self = shift;
71 7 100       36 $self->{_mirrors} || [];
72             }
73              
74             sub features {
75 8     8 1 19 my $self = shift;
76 8         33 map $self->feature($_), $self->{_prereqs}->identifiers;
77             }
78              
79             sub feature {
80 6     6 0 13 my($self, $identifier) = @_;
81 6         26 $self->{_prereqs}->feature($identifier);
82             }
83              
84 2     2 0 20 sub prereq { shift->prereqs }
85              
86             sub prereqs {
87 22     22 1 6781 my $self = shift;
88 22         98 $self->{_prereqs}->as_cpan_meta;
89             }
90              
91             sub merged_requirements {
92 0     0 0 0 my $self = shift;
93 0         0 $self->{_prereqs}->merged_requirements;
94             }
95              
96             sub effective_prereqs {
97 2     2 1 6945 my($self, $features) = @_;
98 2 100       6 $self->prereqs_with(@{$features || []});
  2         18  
99             }
100              
101             sub prereqs_with {
102 5     5 1 7783 my($self, @feature_identifiers) = @_;
103              
104 5         18 my $prereqs = $self->prereqs;
105 5         13 my @others = map { $self->feature($_)->prereqs } @feature_identifiers;
  3         15  
106              
107 4         1875 $prereqs->with_merged_prereqs(\@others);
108             }
109              
110             sub prereq_specs {
111 11     11 1 30 my $self = shift;
112 11         26 $self->prereqs->as_string_hash;
113             }
114              
115             sub prereq_for_module {
116 4     4 0 7 my($self, $module) = @_;
117 4         17 $self->{_prereqs}->find($module);
118             }
119              
120             sub options_for_module {
121 4     4 0 6426 my($self, $module) = @_;
122 4 50       12 my $prereq = $self->prereq_for_module($module) or return;
123 4         13 $prereq->requirement->options;
124             }
125              
126             sub merge_meta {
127 1     1 1 4 my($self, $file, $version) = @_;
128              
129 1         408 require CPAN::Meta;
130              
131 1 50 33     15561 $version ||= $file =~ /\.yml$/ ? '1.4' : '2';
132              
133 1         5 my $prereq = $self->prereqs;
134              
135 1         956 my $meta = CPAN::Meta->load_file($file);
136 1         31542 my $prereqs_hash = $prereq->with_merged_prereqs($meta->effective_prereqs)->as_string_hash;
137 1         5483 my $struct = { %{$meta->as_struct}, prereqs => $prereqs_hash };
  1         7  
138              
139 1         5023 CPAN::Meta->new($struct)->save($file, { version => $version });
140             }
141              
142             sub _dump {
143 2     2   5 my $str = shift;
144 2         894 require Data::Dumper;
145 2         8369 chomp(my $value = Data::Dumper->new([$str])->Terse(1)->Dump);
146 2         135 $value;
147             }
148              
149             sub _default_cpanfile {
150 12     12   315 my $file = Cwd::abs_path('cpanfile');
151 12         53 untaint $file;
152             }
153              
154             sub to_string {
155 6     6 1 4567 my($self, $include_empty) = @_;
156              
157 6         58 my $mirrors = $self->mirrors;
158 6         17 my $prereqs = $self->prereq_specs;
159              
160 6         2802 my $code = '';
161 6         18 $code .= $self->_dump_mirrors($mirrors);
162 6         16 $code .= $self->_dump_prereqs($prereqs, $include_empty);
163              
164 6         17 for my $feature ($self->features) {
165 1         959 $code .= sprintf "feature %s, %s => sub {\n", _dump($feature->{identifier}), _dump($feature->{description});
166 1         9 $code .= $self->_dump_prereqs($feature->{spec}, $include_empty, 4);
167 1         4 $code .= "}\n\n";
168             }
169              
170 6         32 $code =~ s/\n+$/\n/s;
171 6         78 $code;
172             }
173              
174             sub _dump_mirrors {
175 6     6   8 my($self, $mirrors) = @_;
176              
177 6         9 my $code = "";
178              
179 6         11 for my $url (@$mirrors) {
180 4         9 $code .= "mirror '$url';\n";
181             }
182              
183 6         17 $code =~ s/\n+$/\n/s;
184 6         11 $code;
185             }
186              
187             sub _dump_prereqs {
188 7     7   12 my($self, $prereqs, $include_empty, $base_indent) = @_;
189              
190 7         11 my $code = '';
191 7         15 for my $phase (qw(runtime configure build test develop)) {
192 35 100       104 my $indent = $phase eq 'runtime' ? '' : ' ';
193 35   100     119 $indent = (' ' x ($base_indent || 0)) . $indent;
194              
195 35         36 my($phase_code, $requirements);
196 35 100       75 $phase_code .= "on $phase => sub {\n" unless $phase eq 'runtime';
197              
198 35         39 for my $type (qw(requires recommends suggests conflicts)) {
199 140         91 for my $mod (sort keys %{$prereqs->{$phase}{$type}}) {
  140         364  
200 19         27 my $ver = $prereqs->{$phase}{$type}{$mod};
201 19 100       44 $phase_code .= $ver eq '0'
202             ? "${indent}$type '$mod';\n"
203             : "${indent}$type '$mod', '$ver';\n";
204 19         29 $requirements++;
205             }
206             }
207              
208 35 100       64 $phase_code .= "\n" unless $requirements;
209 35 100       102 $phase_code .= "};\n" unless $phase eq 'runtime';
210              
211 35 100 66     124 $code .= $phase_code . "\n" if $requirements or $include_empty;
212             }
213              
214 7         35 $code =~ s/\n+$/\n/s;
215 7         24 $code;
216             }
217              
218             1;
219              
220             __END__
221              
222             =head1 NAME
223              
224             Module::CPANfile - Parse cpanfile
225              
226             =head1 SYNOPSIS
227              
228             use Module::CPANfile;
229              
230             my $file = Module::CPANfile->load("cpanfile");
231             my $prereqs = $file->prereqs; # CPAN::Meta::Prereqs object
232              
233             my @features = $file->features; # CPAN::Meta::Feature objects
234             my $merged_prereqs = $file->prereqs_with(@identifiers); # CPAN::Meta::Prereqs
235              
236             $file->merge_meta('MYMETA.json');
237              
238             =head1 DESCRIPTION
239              
240             Module::CPANfile is a tool to handle L<cpanfile> format to load application
241             specific dependencies, not just for CPAN distributions.
242              
243             =head1 METHODS
244              
245             =over 4
246              
247             =item load
248              
249             $file = Module::CPANfile->load;
250             $file = Module::CPANfile->load('cpanfile');
251              
252             Load and parse a cpanfile. By default it tries to load C<cpanfile> in
253             the current directory, unless you pass the path to its argument.
254              
255             =item from_prereqs
256              
257             $file = Module::CPANfile->from_prereqs({
258             runtime => { requires => { DBI => '1.000' } },
259             });
260              
261             Creates a new Module::CPANfile object from prereqs hash you can get
262             via L<CPAN::Meta>'s C<prereqs>, or L<CPAN::Meta::Prereqs>'
263             C<as_string_hash>.
264              
265             # read MYMETA, then feed the prereqs to create Module::CPANfile
266             my $meta = CPAN::Meta->load_file('MYMETA.json');
267             my $file = Module::CPANfile->from_prereqs($meta->prereqs);
268              
269             # load cpanfile, then recreate it with round-trip
270             my $file = Module::CPANfile->load('cpanfile');
271             $file = Module::CPANfile->from_prereqs($file->prereq_specs);
272             # or $file->prereqs->as_string_hash
273              
274             =item prereqs
275              
276             Returns L<CPAN::Meta::Prereqs> object out of the parsed cpanfile.
277              
278             =item prereq_specs
279              
280             Returns a hash reference that should be passed to C<< CPAN::Meta::Prereqs->new >>.
281              
282             =item features
283              
284             Returns a list of features available in the cpanfile as L<CPAN::Meta::Feature>.
285              
286             =item prereqs_with(@identifiers), effective_prereqs(\@identifiers)
287              
288             Returns L<CPAN::Meta::Prereqs> object, with merged prereqs for
289             features identified with the C<@identifiers>.
290              
291             =item to_string($include_empty)
292              
293             $file->to_string;
294             $file->to_string(1);
295              
296             Returns a canonical string (code) representation for cpanfile. Useful
297             if you want to convert L<CPAN::Meta::Prereqs> to a new cpanfile.
298              
299             # read MYMETA's prereqs and print cpanfile representation of it
300             my $meta = CPAN::Meta->load_file('MYMETA.json');
301             my $file = Module::CPANfile->from_prereqs($meta->prereqs);
302             print $file->to_string;
303              
304             By default, it omits the phase where there're no modules
305             registered. If you pass the argument of a true value, it will print
306             them as well.
307              
308             =item save
309              
310             $file->save('cpanfile');
311              
312             Saves the currently loaded prereqs as a new C<cpanfile> by calling
313             C<to_string>. Beware B<this method will overwrite the existing
314             cpanfile without any warning or backup>. Taking a backup or giving
315             warnings to users is a caller's responsibility.
316              
317             # Read MYMETA.json and creates a new cpanfile
318             my $meta = CPAN::Meta->load_file('MYMETA.json');
319             my $file = Module::CPANfile->from_prereqs($meta->prereqs);
320             $file->save('cpanfile');
321              
322             =item merge_meta
323              
324             $file->merge_meta('META.yml');
325             $file->merge_meta('MYMETA.json', '2.0');
326              
327             Merge the effective prereqs with Meta specification loaded from the
328             given META file, using CPAN::Meta. You can specify the META spec
329             version in the second argument, which defaults to 1.4 in case the
330             given file is YAML, and 2 if it is JSON.
331              
332             =back
333              
334             =head1 AUTHOR
335              
336             Tatsuhiko Miyagawa
337              
338             =head1 SEE ALSO
339              
340             L<cpanfile>, L<CPAN::Meta>, L<CPAN::Meta::Spec>
341              
342             =cut