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