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