File Coverage

blib/lib/Mite/Project.pm
Criterion Covered Total %
statement 127 170 74.7
branch 28 56 50.0
condition 11 21 52.3
subroutine 29 30 96.6
pod 0 17 0.0
total 195 294 66.3


line stmt bran cond sub pod time code
1 107     107   5614050 use 5.010001;
  107         2430  
2 107     107   2490 use strict;
  107         1538  
  107         9896  
3 107     107   2393 use warnings;
  107         1921  
  107         23540  
4              
5             package Mite::Project;
6 107     107   59236 use Mite::Miteception -all;
  107         684  
  107         2297  
7              
8             our $AUTHORITY = 'cpan:TOBYINK';
9             our $VERSION = '0.012000';
10              
11             has sources =>
12             is => ro,
13             isa => HashRef[MiteSource],
14             default => sub { {} };
15              
16             has config =>
17             is => ro,
18             isa => MiteConfig,
19             lazy => 1,
20             default => sub {
21             require Mite::Config;
22             state $config = Mite::Config->new;
23             return $config;
24             };
25              
26             has _module_fakeout_namespace =>
27             is => rw,
28             isa => Str | Undef;
29              
30             has debug =>
31             is => rw,
32             isa => Bool,
33             default => false;
34              
35 107     107   53738 use Mite::Source;
  107         453  
  107         5111  
36 107     107   811 use Mite::Class;
  107         257  
  107         322839  
37              
38             sub classes {
39 291     291 0 428 my $self = shift;
40              
41 460         588 my %classes = map { %{$_->classes} }
  460         1760  
42 291         410 values %{$self->sources};
  291         880  
43 291         1518 return \%classes;
44             }
45              
46             # Careful not to create a class.
47             sub class {
48 290     290 0 1769 my ( $self, $name ) = ( shift, @_ );
49              
50 290         686 return $self->classes->{$name};
51             }
52              
53             # Careful not to create a source.
54             sub source {
55 2     2 0 1131 my ( $self, $file ) = ( shift, @_ );
56              
57 2         12 return $self->sources->{$file};
58             }
59              
60             sub add_sources {
61 2     2 0 24 my ( $self, @sources ) = ( shift, @_ );
62              
63 2         9 for my $source (@sources) {
64 3         66 $self->sources->{$source->file} = $source;
65             }
66             }
67              
68             sub source_for {
69 153     153 0 5771 my ( $self, $file ) = ( shift, @_ );
70              
71             # Normalize the path.
72 153         597 $file = Path::Tiny::path($file)->realpath;
73              
74 153   66     37812 return $self->sources->{$file} ||= Mite::Source->new(
75             file => $file,
76             project => $self
77             );
78             }
79              
80             # This is the shim Mite.pm uses when compiling.
81             signature_for inject_mite_functions => (
82             named => [
83             package => Any,
84             file => Any,
85             kind => Optional[Str],
86             arg => HashRef, { default => {} },
87             shim => Str,
88             x_source => Optional[Object],
89             x_pkg => Optional[Object],
90             ],
91             named_to_list => true,
92             );
93              
94             sub inject_mite_functions {
95             my ( $self, $package, $file, $kind, $arg, $shim, $source, $pkg ) = @_;
96             $kind //= ( $arg->{'-role'} ? 'role' : 'class' );
97              
98             my $fake_ns = $self->can('_module_fakeout_namespace') && $self->_module_fakeout_namespace;
99             if ( defined( $fake_ns ) and not $package =~ /^\Q$fake_ns/ ) {
100             $package = "$fake_ns\::$package";
101             }
102              
103             warn "Gather: $package\n" if $self->debug;
104              
105             $source //= $self->source_for(
106             Path::Tiny::path( $Mite::REAL_FILENAME // $file )
107             );
108             $pkg //= $source->class_for(
109             $package,
110             $kind eq 'role' ? 'Mite::Role' : 'Mite::Class',
111             );
112             $pkg->shim_name( $shim );
113             $pkg->arg( $arg );
114             $pkg->inject_mite_functions( $file, $arg );
115             }
116              
117             sub write_mites {
118 82     82 0 1513 my $self = shift;
119              
120 82         214 for my $source (values %{$self->sources}) {
  82         676  
121 87 50       6332 warn "Write mite: ${\ $source->compiled->file }\n" if $self->debug;
  0         0  
122 87         797 $source->compiled->write(
123             module_fakeout_namespace => $self->_module_fakeout_namespace,
124             );
125             }
126              
127 82         104792 return;
128             }
129              
130             sub _project_mopper_file {
131 89     89   299 my $self = shift;
132              
133 89         219 my ( $mop_package, $mop_dir );
134 89 50       281 eval {
135 89         459 my $config = $self->config;
136 89         787 $mop_package = $config->data->{mop};
137 11         32 $mop_dir = $config->data->{source_from};
138              
139 11 50       75 $mop_package and $mop_dir;
140             } or return;
141              
142 0         0 my $mop_file = $mop_package;
143 0         0 $mop_file =~ s{::}{/}g;
144 0         0 $mop_file .= ".pm";
145 0         0 return Path::Tiny::path($mop_dir, $mop_file);
146             }
147              
148             sub write_mopper {
149 2     2 0 6 my $self = shift;
150              
151 2 50       8 my $mop_file = $self->_project_mopper_file or return;
152              
153 0         0 my $dir = Path::Tiny::path( $self->config->data->{source_from} );
154              
155 0         0 my $code = $self->_compile_mop_header;
156 0         0 for my $source ( sort { $a->file cmp $b->file } values %{ $self->sources } ) {
  0         0  
  0         0  
157 0         0 my $relative_name = $source->file->relative($dir);
158 0         0 $code .= $source->_compile_mop( $relative_name );
159             }
160 0         0 for my $class ( sort { $a->name cmp $b->name } values %{ $self->classes } ) {
  0         0  
  0         0  
161 0         0 $code .= $class->_compile_mop_postamble;
162             }
163              
164 0 0       0 if ( my $yuck = $self->_module_fakeout_namespace ) {
165 0         0 $code =~ s/$yuck\:://g;
166             }
167              
168 0         0 $code .= "\ntrue;\n\n";
169              
170 0 0       0 warn "Write MOP: $mop_file\n" if $self->debug;
171 0         0 $mop_file->spew( $code );
172              
173 0         0 return;
174             }
175              
176             sub _compile_mop_header {
177 0     0   0 my $self = shift;
178 0         0 return sprintf <<'CODE', ( $self->config->data->{mop} ) x 3;
179             package %s;
180              
181             use Moose ();
182             use Moose::Util ();
183             use Moose::Util::MetaRole ();
184             use Moose::Util::TypeConstraints ();
185             use constant { true => !!1, false => !!0 };
186              
187             my $META_CLASS = do {
188             package %s::Meta::Class;
189             use Moose;
190             extends 'Moose::Meta::Class';
191             around _immutable_options => sub {
192             my ( $next, $self, @args ) = ( shift, shift, @_ );
193             return $self->$next( replace_constructor => 1, @args );
194             };
195             __PACKAGE__->meta->make_immutable;
196              
197             __PACKAGE__;
198             };
199              
200             my $META_ROLE = do {
201             package %s::Meta::Role;
202             use Moose;
203             extends 'Moose::Meta::Role';
204             my $built_ins = qr/\A( DOES | does | __META__ | __FINALIZE_APPLICATION__ |
205             CREATE_CLASS | APPLY_TO )\z/x;
206             around get_method => sub {
207             my ( $next, $self, $method_name ) = ( shift, shift, @_ );
208             return if $method_name =~ $built_ins;
209             return $self->$next( @_ );
210             };
211             around get_method_list => sub {
212             my ( $next, $self ) = ( shift, shift );
213             return grep !/$built_ins/, $self->$next( @_ );
214             };
215             around _get_local_methods => sub {
216             my ( $next, $self ) = ( shift, shift );
217             my %%map = %%{ $self->_full_method_map };
218             return map $map{$_}, $self->get_method_list;
219             };
220             __PACKAGE__->meta->make_immutable;
221              
222             __PACKAGE__;
223             };
224              
225             CODE
226             }
227              
228             signature_for load_files => (
229             pos => [ ArrayRef, 0 ],
230             );
231              
232             sub load_files {
233             my ( $self, $files, $inc_dir ) = @_;
234              
235             local $Mite::COMPILING = eval { $self->config->data->{shim} }
236             // $ENV{TEST_MITE_SHIM}
237             // do { warn 'Attempting to compile, but no shim in config'; exit 1; };
238             local @INC = @INC;
239             unshift @INC, $inc_dir if defined $inc_dir;
240             for my $file (@$files) {
241             $self->_load_file( $file, $inc_dir );
242             }
243              
244             return;
245             }
246              
247             sub _load_file {
248 87     87   310 my ( $self, $file, $inc_dir ) = @_;
249              
250 87 50       577 if ( $self->{_already}{$file}++ ) {
251 0 0       0 warn "Skipping $file: already loaded\n" if $self->debug;
252 0         0 return;
253             }
254              
255 87 50 33     1725 if ( defined $self->_project_mopper_file
256             and $file eq $self->_project_mopper_file ) {
257 0 0       0 warn "Skipping $file: it's the mop\n" if $self->debug;
258 0         0 return;
259             }
260              
261 87 50       3318 warn "Load module: $file\n" if $self->debug;
262              
263 87         560 $file = Path::Tiny::path($file);
264              
265 87 50       9781 if ( defined $self->_module_fakeout_namespace ) {
266 0         0 my $ns = $self->_module_fakeout_namespace;
267              
268 0         0 my $code = $file->slurp;
269 0         0 $code =~ s/package /package $ns\::/;
270              
271 0         0 do {
272 0         0 local $@;
273 0         0 local $Mite::REAL_FILENAME = "$file";
274 0 0       0 eval("$code; 1") or do die($@);
275             };
276              
277 0         0 return;
278             }
279              
280 87 50       559 if ( my $pm_file = eval { $file->relative($inc_dir) } ) {
  87         1524  
281 87         57267 require $pm_file;
282             }
283             else {
284 0         0 local $@;
285 0 0       0 eval( $file->slurp ) or die $@;
286             }
287              
288 87         1047 return;
289             }
290              
291             sub find_pms {
292 6     6 0 3709762 my ( $self, $dir ) = ( shift, @_ );
293 6   66     121 $dir //= $self->config->data->{source_from};
294              
295             return $self->_recurse_directory(
296             $dir,
297             sub {
298 31     31   69 my $path = shift;
299 31 100       154 return false if -d $path;
300 21 100       411 return false unless $path =~ m{\.pm$};
301 17 100       247 return false if $path =~ m{\.mite\.pm$};
302 15         131 return true;
303             }
304 6         493 );
305             }
306              
307             sub load_directory {
308 3     3 0 23 my ( $self, $dir ) = ( shift, @_ );
309 3   33     37 $dir //= $self->config->data->{source_from};
310              
311 3         32 $self->load_files( [$self->find_pms($dir)], $dir );
312              
313 3         40 return;
314             }
315              
316             sub find_mites {
317 7     7 0 7521 my ( $self, $dir ) = ( shift, @_ );
318 7   66     189 $dir //= $self->config->data->{compiled_to};
319              
320             return $self->_recurse_directory(
321             $dir,
322             sub {
323 46     46   77 my $path = shift;
324 46 100       162 return false if -d $path;
325 33 100       625 return true if $path =~ m{\.mite\.pm$};
326 23         212 return false;
327             }
328 7         371 );
329             }
330              
331             sub clean_mites {
332 2     2 0 978 my ( $self, $dir ) = ( shift, @_ );
333 2   33     24 $dir //= $self->config->data->{compiled_to};
334              
335 2         11 for my $file ($self->find_mites($dir)) {
336 4 50       430 warn "Clean mite: $file\n" if $self->debug;
337 4         16 Path::Tiny::path($file)->remove;
338             }
339              
340 2         184 return;
341             }
342              
343             sub clean_shim {
344 1     1 0 5 my $self = shift;
345 1 50       8 warn "Clean shim: ${\ $self->_project_shim_file }\n" if $self->debug;
  0         0  
346 1         11 return $self->_project_shim_file->remove;
347             }
348              
349             # Recursively gather all the pm files in a directory
350             signature_for _recurse_directory => (
351             pos => [ Path, CodeRef ],
352             );
353              
354             sub _recurse_directory {
355             my ( $self, $dir, $check ) = @_;
356              
357             my @pm_files;
358              
359             my $iter = $dir->iterator({ recurse => 1, follow_symlinks => 1 });
360             while( my $path = $iter->() ) {
361             next unless $check->($path);
362             push @pm_files, $path;
363             }
364              
365             return @pm_files;
366             }
367              
368             sub init_project {
369 9     9 0 105 my ( $self, $project_name ) = ( shift, @_ );
370              
371 9 50       52 warn "Init\n" if $self->debug;
372              
373 9         105 $self->config->make_mite_dir;
374              
375 9 50       5168 $self->write_default_config(
376             $project_name
377             ) if !-e $self->config->config_file;
378              
379 9         38 return;
380             }
381              
382             sub add_mite_shim {
383 4     4 0 1720 my $self = shift;
384              
385 4         28 my $shim_file = $self->_project_shim_file;
386 4         295 $shim_file->parent->mkpath;
387              
388 4 50       1342 warn "Write shim: $shim_file\n" if $self->debug;
389              
390 4         20 my $shim_package = $self->config->data->{shim};
391 4 50       25 return $shim_file if $shim_package eq 'Mite::Shim';
392              
393 4         27 my $src_shim = $self->_find_mite_shim;
394 4         113 my $code = $src_shim->slurp;
395 4         1126 $code =~ s/package Mite::Shim;/package $shim_package;/;
396 4         52 $code =~ s/^Mite::Shim\b/$shim_package/ms;
397 4         38 $shim_file->spew( $code );
398              
399 4         3241 return $shim_file;
400             }
401              
402             sub _project_shim_file {
403 6     6   92 my $self = shift;
404              
405 6         40 my $config = $self->config;
406 6         43 my $shim_package = $config->data->{shim};
407 6         27 my $shim_dir = $config->data->{source_from};
408              
409 6         16 my $shim_file = $shim_package;
410 6         70 $shim_file =~ s{::}{/}g;
411 6         27 $shim_file .= ".pm";
412 6         32 return Path::Tiny::path($shim_dir, $shim_file);
413             }
414              
415             sub _find_mite_shim {
416 4     4   11 my $self = shift;
417              
418 4         28 for my $dir (@INC) {
419             # Avoid code refs in @INC
420 12 50       168 next if ref $dir;
421              
422 12         40 my $shim = Path::Tiny::path($dir, "Mite", "Shim.pm");
423 12 100       509 return $shim if -e $shim;
424             }
425              
426 0         0 croak <<"ERROR";
427             Can't locate Mite::Shim in \@INC. \@INC contains:
428 0         0 @{[ map { " $_\n" } grep { !ref($_) } @INC ]}
  0         0  
  0         0  
429             ERROR
430             }
431              
432             sub write_default_config {
433 9     9 0 356 my $self = shift;
434 9         46 my $project_name = Str->(shift);
435 9         559 my %args = @_;
436              
437 9         66 my $libdir = Path::Tiny::path('lib');
438 9         554 $self->config->write_config({
439             project => $project_name,
440             shim => $project_name.'::Mite',
441             source_from => $libdir.'',
442             compiled_to => $libdir.'',
443             %args
444             });
445 9         56 return;
446             }
447              
448             {
449             # Get/set the default for a class
450             my %Defaults;
451             sub default {
452 253     253 0 27002102 my $class = shift;
453 253   66     5521 return $Defaults{$class} ||= $class->new;
454             }
455              
456             sub set_default {
457 1     1 0 654 my ( $class, $new_default ) = ( shift, @_ );
458 1         3 $Defaults{$class} = $new_default;
459 1         3 return;
460             }
461             }
462              
463             1;