d24a457e08c74a9de34b3a11265154328d2b80d3
[gitmo/Moo.git] / lib / Method / Generate / Constructor.pm
1 package Method::Generate::Constructor;
2
3 use strictures 1;
4 use Sub::Quote;
5 use base qw(Moo::Object);
6 use Sub::Defer;
7 use B 'perlstring';
8
9 sub register_attribute_specs {
10   my ($self, @new_specs) = @_;
11   my $specs = $self->{attribute_specs}||={};
12   while (my ($name, $new_spec) = splice @new_specs, 0, 2) {
13     $new_spec->{index} = scalar keys %$specs
14       unless exists $new_spec->{index};
15     $specs->{$name} = $new_spec;
16   }
17   $self;
18 }
19
20 sub all_attribute_specs {
21   $_[0]->{attribute_specs}
22 }
23
24 sub accessor_generator {
25   $_[0]->{accessor_generator}
26 }
27
28 sub construction_string {
29   my ($self) = @_;
30   $self->{construction_string}
31     or 'bless('
32        .$self->accessor_generator->default_construction_string
33        .', $class);'
34 }
35
36 sub install_delayed {
37   my ($self) = @_;
38   my $package = $self->{package};
39   defer_sub "${package}::new" => sub {
40     unquote_sub $self->generate_method(
41       $package, 'new', $self->{attribute_specs}, { no_install => 1 }
42     )
43   };
44   $self;
45 }
46
47 sub generate_method {
48   my ($self, $into, $name, $spec, $quote_opts) = @_;
49   foreach my $no_init (grep !exists($spec->{$_}{init_arg}), keys %$spec) {
50     $spec->{$no_init}{init_arg} = $no_init;
51   }
52   local $self->{captures} = {};
53   my $body = '    my $class = shift;'."\n"
54             .'    $class = ref($class) if ref($class);'."\n";
55   $body .= $self->_handle_subconstructor($into, $name);
56   my $into_buildargs = $into->can('BUILDARGS');
57   if ( $into_buildargs && $into_buildargs != \&Moo::Object::BUILDARGS ) {
58       $body .= $self->_generate_args_via_buildargs;
59   } else {
60       $body .= $self->_generate_args;
61   }
62   $body .= $self->_check_required($spec);
63   $body .= '    my $new = '.$self->construction_string.";\n";
64   $body .= $self->_assign_new($spec);
65   if ($into->can('BUILD')) {
66     require Method::Generate::BuildAll;
67     $body .= Method::Generate::BuildAll->new->buildall_body_for(
68       $into, '$new', '$args'
69     );
70   }
71   $body .= '    return $new;'."\n";
72   if ($into->can('DEMOLISH')) {
73     require Method::Generate::DemolishAll;
74     Method::Generate::DemolishAll->new->generate_method($into);
75   }
76   quote_sub
77     "${into}::${name}" => $body,
78     $self->{captures}, $quote_opts||{}
79   ;
80 }
81
82 sub _handle_subconstructor {
83   my ($self, $into, $name) = @_;
84   if (my $gen = $self->{subconstructor_handler}) {
85     '    if ($class ne '.perlstring($into).') {'."\n".
86     $gen.
87     '    }'."\n";
88   } else {
89     ''
90   }
91 }
92
93 sub _cap_call {
94   my ($self, $code, $captures) = @_;
95   @{$self->{captures}}{keys %$captures} = values %$captures if $captures;
96   $code;
97 }
98
99 sub _generate_args_via_buildargs {
100   my ($self) = @_;
101   q{    my $args = $class->BUILDARGS(@_);}."\n"
102   .q{    die "BUILDARGS did not return a hashref" unless ref($args) eq 'HASH';}
103   ."\n";
104 }
105
106 # inlined from Moo::Object - update that first.
107 sub _generate_args {
108   my ($self) = @_;
109   return <<'_EOA';
110     my $args;
111     if ( scalar @_ == 1 ) {
112         unless ( defined $_[0] && ref $_[0] eq 'HASH' ) {
113             die "Single parameters to new() must be a HASH ref"
114                 ." data => ". $_[0] ."\n";
115         }
116         $args = { %{ $_[0] } };
117     }
118     elsif ( @_ % 2 ) {
119         die "The new() method for $class expects a hash reference or a key/value list."
120                 . " You passed an odd number of arguments\n";
121     }
122     else {
123         $args = {@_};
124     }
125 _EOA
126
127 }
128
129 sub _assign_new {
130   my ($self, $spec) = @_;
131   my (@init, @slots, %test);
132   my $ag = $self->accessor_generator;
133   NAME: foreach my $name (sort keys %$spec) {
134     my $attr_spec = $spec->{$name};
135     unless ($ag->is_simple_attribute($name, $attr_spec)) {
136       next NAME unless defined($attr_spec->{init_arg})
137                          or $ag->has_eager_default($name, $attr_spec);
138       $test{$name} = $attr_spec->{init_arg};
139       next NAME;
140     }
141     next NAME unless defined(my $i = $attr_spec->{init_arg});
142     push @init, $i;
143     push @slots, $name;
144   }
145   return '' unless @init or %test;
146   join '', (
147     @init
148       ? '    '.$self->_cap_call($ag->generate_multi_set(
149           '$new', [ @slots ], '@{$args}{qw('.join(' ',@init).')}'
150         )).";\n"
151       : ''
152   ), map {
153     my $arg_key = perlstring($test{$_});
154     my $test = "exists \$args->{$arg_key}";
155     my $source = "\$args->{$arg_key}";
156     my $attr_spec = $spec->{$_};
157     $self->_cap_call($ag->generate_populate_set(
158       '$new', $_, $attr_spec, $source, $test
159     ));
160   } sort keys %test;
161 }
162
163 sub _check_required {
164   my ($self, $spec) = @_;
165   my @required_init =
166     map $spec->{$_}{init_arg},
167       grep $spec->{$_}{required},
168         sort keys %$spec;
169   return '' unless @required_init;
170   '    if (my @missing = grep !exists $args->{$_}, qw('
171     .join(' ',@required_init).')) {'."\n"
172     .q{      die "Missing required arguments: ".join(', ', sort @missing);}."\n"
173     ."    }\n";
174 }
175
176 sub _check_isa {
177   my ($self, $spec) = @_;
178   my $acc = $self->accessor_generator;
179   my $captures = $self->{captures};
180   my $check = '';
181   foreach my $name (sort keys %$spec) {
182     my ($init, $isa) = @{$spec->{$name}}{qw(init_arg isa)};
183     next unless $init and $isa;
184     my $init_str = perlstring($init);
185     my ($code, $add_captures) = $acc->generate_isa_check(
186       $name, "\$args->{${init_str}}", $isa
187     );
188     @{$captures}{keys %$add_captures} = values %$add_captures;
189     $check .= "    ${code}".(
190       (not($spec->{lazy}) and ($spec->{default} or $spec->{builder})
191         ? ";\n"
192         : "if exists \$args->{${init_str}};\n"
193       )
194     );
195   }
196   return $check;
197 }
198
199 sub _fire_triggers {
200   my ($self, $spec) = @_;
201   my $acc = $self->accessor_generator;
202   my $captures = $self->{captures};
203   my $fire = '';
204   foreach my $name (sort keys %$spec) {
205     my ($init, $trigger) = @{$spec->{$name}}{qw(init_arg trigger)};
206     next unless $init && $trigger;
207     my ($code, $add_captures) = $acc->generate_trigger(
208       $name, '$new', $acc->generate_simple_get('$new', $name), $trigger
209     );
210     @{$captures}{keys %$add_captures} = values %$add_captures;
211     $fire .= "    ${code} if exists \$args->{${\perlstring $init}};\n";
212   }
213   return $fire;
214 }
215
216 1;