File Coverage

blib/lib/MooseX/Runnable/Invocation.pm
Criterion Covered Total %
statement 83 88 94.3
branch 15 24 62.5
condition 2 8 25.0
subroutine 15 15 100.0
pod 0 7 0.0
total 115 142 80.9


line stmt bran cond sub pod time code
1             package MooseX::Runnable::Invocation;
2              
3             our $VERSION = '0.10';
4              
5 3     3   110385 use Moose;
  3         574081  
  3         16  
6 3     3   15273 use MooseX::Types -declare => ['RunnableClass'];
  3         80342  
  3         15  
7 3     3   9349 use MooseX::Types::Moose qw(Str HashRef ArrayRef);
  3         27799  
  3         16  
8 3     3   10801 use List::SomeUtils qw(uniq);
  3         12780  
  3         223  
9 3     3   17 use Params::Util qw(_CLASS);
  3         3  
  3         104  
10 3     3   12 use Class::Load;
  3         4  
  3         73  
11 3     3   17 use namespace::autoclean;
  3         4  
  3         14  
12              
13             # we can't load the class until plugins are loaded,
14             # so we have to handle this outside of coerce
15              
16             subtype RunnableClass,
17             as Str,
18             where { _CLASS($_) };
19              
20              
21             with 'MooseX::Runnable'; # this class technically follows
22             # MX::Runnable's protocol
23              
24             has 'class' => (
25             is => 'ro',
26             isa => RunnableClass,
27             required => 1,
28             );
29              
30             has 'plugins' => (
31             is => 'ro',
32             isa => HashRef[ArrayRef[Str]],
33             default => sub { +{} },
34             required => 1,
35             auto_deref => 1,
36             );
37              
38             sub BUILD {
39 8     8 0 5869 my $self = shift;
40              
41             # it would be nice to use MX::Object::Pluggable, but our plugins
42             # are too configurable
43              
44 8         13 my $plugin_ns = 'MooseX::Runnable::Invocation::Plugin::';
45 8         10 for my $plugin (keys %{$self->plugins}){
  8         224  
46 6         155 my $orig = $plugin;
47 6 50       23 $plugin = "$plugin_ns$plugin" unless $plugin =~ /^[+]/;
48 6         33 $plugin =~ s/^[+]//g;
49              
50 6         20 Class::Load::load_class( $plugin );
51              
52 6         163 my $does_cmdline = $plugin->meta->
53             does_role('MooseX::Runnable::Invocation::Plugin::Role::CmdlineArgs');
54              
55 6         297 my $args;
56 6 100 66     19 if($does_cmdline){
    100          
57 3         4 $args = eval {
58             $plugin->_build_initargs_from_cmdline(
59 3         4 @{$self->plugins->{$orig}},
  3         72  
60             );
61             };
62              
63 3 50       24 if($@) {
64 0         0 confess "Error building initargs for $plugin: $@";
65             }
66             }
67 3         69 elsif(!$does_cmdline && scalar @{$self->plugins->{$orig}} > 0){
68 1         103 confess "You supplied arguments to the $orig plugin, but it".
69             " does not know how to accept them. Perhaps the plugin".
70             " should consume the".
71             " 'MooseX::Runnable::Invocation::Plugin::Role::CmdlineArgs'".
72             " role?";
73             }
74              
75             $plugin->meta->apply(
76 5 100       13 $self,
77             defined $args ? (rebless_params => $args) : (),
78             );
79             }
80             }
81              
82             sub load_class {
83 5     5 0 7 my $self = shift;
84 5         163 my $class = $self->class;
85              
86 5         23 Class::Load::load_class( $class );
87              
88 5 50       168 confess 'We can only work with Moose classes with "meta" methods'
89             if !$class->can('meta');
90              
91 5         21 my $meta = $class->meta;
92              
93 5 50       101 confess "The metaclass of $class is not a Moose::Meta::Class, it's $meta"
94             unless $meta->isa('Moose::Meta::Class');
95              
96 5 50       18 confess 'MooseX::Runnable can only run classes tagged with '.
97             'the MooseX::Runnable role'
98             unless $meta->does_role('MooseX::Runnable');
99              
100 5         778 return $meta;
101             }
102              
103             sub apply_scheme {
104 5     5 0 9 my ($self, $class) = @_;
105              
106 2         705 my @schemes = grep { defined } map {
107 15         2467 eval { $self->_convert_role_to_scheme($_) }
  15         38  
108             } map {
109 5         16 eval { $_->meta->calculate_all_roles };
  11         868  
  11         32  
110             } $class->linearized_isa;
111              
112 5         2020 eval {
113 5         51 foreach my $scheme (uniq @schemes) {
114 2         6 $scheme->apply($self);
115             }
116             };
117             }
118              
119              
120             sub _convert_role_to_scheme {
121 15     15   15 my ($self, $role) = @_;
122              
123 15         70 my $name = $role->name;
124 15 100       45 return if $name =~ /\|/;
125 13         25 $name = "MooseX::Runnable::Invocation::Scheme::$name";
126              
127 13         11 return eval {
128 13         29 Class::Load::load_class($name);
129 2 50 0     45 warn "$name was loaded OK, but it's not a role!" and return
130             unless $name->meta->isa('Moose::Meta::Role');
131 2         30 return $name->meta;
132             };
133             }
134              
135             sub validate_class {
136 3     3 0 4 my ($self, $class) = @_;
137              
138 0         0 my @bad_attributes = map { $_->name } grep {
139 3 0 0     13 $_->is_required && !($_->has_default || $_->has_builder)
  0         0  
140             } $class->get_all_attributes;
141              
142             confess
143             'By default, MooseX::Runnable calls the constructor with no'.
144             ' args, but that will result in an error for your class. You'.
145             ' need to provide a MooseX::Runnable::Invocation::Plugin or'.
146             ' ::Scheme for this class that will satisfy the requirements.'.
147             "\n".
148 0         0 "The class is @{[$class->name]}, and the required attributes are ".
149 3 50       82 join ', ', map { "'$_'" } @bad_attributes
  0         0  
150             if @bad_attributes;
151              
152 3         6 return; # return value is meaningless
153             }
154              
155             sub create_instance {
156 3     3 0 6 my ($self, $class, @args) = @_;
157 3         18 return ($class->name->new, @args);
158             }
159              
160             sub start_application {
161 5     5 0 5 my $self = shift;
162 5         5 my $instance = shift;
163 5         8 my @args = @_;
164              
165 5         18 return $instance->run(@args);
166             }
167              
168             sub run {
169 5     5 0 4389 my $self = shift;
170 5         10 my @args = @_;
171              
172 5         16 my $class = $self->load_class;
173 5         21 $self->apply_scheme($class);
174 5         864 $self->validate_class($class);
175 5         14 my ($instance, @more_args) = $self->create_instance($class, @args);
176 5         476 my $exit_code = $self->start_application($instance, @more_args);
177 5         67 return $exit_code;
178             }
179              
180             1;