deferred constructor construction
[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(Class::Tiny::Object);
6 use Sub::Defer;
7
8 sub register_attribute_spec {
9   my ($self, $name, $spec) = @_;
10   $self->{attribute_specs}{$name} = $spec;
11 }
12
13 sub install_delayed {
14   my ($self) = @_;
15   my $package = $self->{package};
16   defer_sub "${package}::new" => sub {
17     unquote_sub $self->generate_method(
18       $package, 'new', $self->{attribute_specs}, { no_install => 1 }
19     )
20   };
21   $self;
22 }
23
24 sub generate_method {
25   my ($self, $into, $name, $spec, $quote_opts) = @_;
26   foreach my $no_init (grep !exists($spec->{$_}{init_arg}), keys %$spec) {
27     $spec->{$no_init}{init_arg} = $no_init;
28   }
29   my $body = '    my $class = shift;'."\n";
30   $body .= $self->_generate_args;
31   $body .= $self->_check_required($spec);
32   $body .= '    my $new = bless({}, $class);'."\n";
33   $body .= $self->_assign_new($spec);
34   $body .= '    return $new;'."\n";
35   quote_sub
36     "${into}::${name}" => $body,
37     (ref($quote_opts) ? ({}, $quote_opts) : ())
38   ;
39 }
40
41 sub _generate_args {
42   my ($self) = @_;
43   q{    my $args = ref($_[0]) eq 'HASH' ? $_[0] : { @_ };}."\n";
44 }
45
46 sub _assign_new {
47   my ($self, $spec) = @_;
48   my (@init, @slots);
49   NAME: foreach my $name (keys %$spec) {
50     my $attr_spec = $spec->{$name};
51     push @init, do {
52       next NAME unless defined(my $i = $attr_spec->{init_arg});
53       $i;
54     };
55     push @slots, $name;
56   }
57   return '' unless @init;
58   '    @{$new}{qw('.join(' ',@slots).')} = @{$args}{qw('.join(' ',@init).')};'
59     ."\n";
60 }
61
62 sub _check_required {
63   my ($self, $spec) = @_;
64   my @required_init =
65     map $spec->{$_}{init_arg},
66       grep $spec->{$_}{required},
67         keys %$spec;
68   return '' unless @required_init;
69   '    if (my @missing = grep !exists $args->{$_}, qw('
70     .join(' ',@required_init).')) {'."\n"
71     .q{      die "Missing required arguments: ".join(', ', sort @missing);}."\n"
72     ."    }\n";
73 }
74
75 1;