Added code to fixup line numbers, inline constructor, and load modules that construct...
[gitmo/MooseX-Compiler.git] / lib / MooseX / Compiler.pm
CommitLineData
9e60f0d8 1package MooseX::Compiler;
2
3use strict;
4use warnings;
5
f56affb6 6use B;
9e60f0d8 7use Module::Runtime qw( module_notional_filename );
8use PPI::Document;
9use Scalar::Util qw( blessed );
10
11use Moose;
12use Moose::Util::TypeConstraints;
13
14my $moose_class = subtype, as 'ClassName', where {
15 $_[0]->can('meta')
16 && blessed $_[0]->meta()
17 && $_[0]->meta()->isa('Moose::Meta::Class');
18};
19
20has class => (
21 is => 'ro',
22 isa => $moose_class,
23 required => 1,
24);
25
26has _class_meta => (
27 is => 'ro',
28 isa => 'Moose::Meta::Class',
29 init_arg => undef,
30 lazy => 1,
31 default => sub { $_[0]->class()->meta() },
32);
33
f56affb6 34has _ppi_document => (
35 is => 'ro',
36 isa => 'PPI::Document',
37 init_arg => undef,
38 lazy => 1,
39 builder => '_build_ppi_document',
40);
41
42has _package_statement => (
43 is => 'ro',
44 isa => 'PPI::Statement::Package',
45 init_arg => undef,
46 lazy => 1,
47 builder => '_build_package_statement',
48);
49
9e60f0d8 50sub compile_class {
f56affb6 51 my $self = shift;
9e60f0d8 52
f56affb6 53 $self->_modify_class_content();
54 $self->_inline_roles();
55 $self->_inline_constructor();
56 $self->_inline_attributes();
9e60f0d8 57
f56affb6 58 return $self->_ppi_document()->content();
9e60f0d8 59}
60
f56affb6 61sub _modify_class_content {
9e60f0d8 62 my $self = shift;
63
f56affb6 64 $self->_fixup_line_numbers();
65 $self->_do_not_use_moose();
66 $self->_inline_parents();
67 $self->_load_required_modules();
9e60f0d8 68
f56affb6 69 return;
70}
71
72sub _fixup_line_numbers {
73 my $self = shift;
74
75 my $code = "#line 3\n";
76 $self->_package_statement()->snext_sibling()->insert_before( $_->clone() )
77 for PPI::Document->new( \$code )->children();
78
79 return;
80}
81
82sub _do_not_use_moose {
83 my $self = shift;
9e60f0d8 84
f56affb6 85 my $use_nodes = $self->_ppi_document()->find(
9e60f0d8 86 sub {
87 my $node = $_[1];
88 return undef
89 if $node->isa('PPI::Statement')
90 && !$node->isa('PPI::Statement::Include');
91 return undef if $node->isa('PPI::Structure');
92 return 1
93 if $node->isa('PPI::Statement::Include')
94 && $node->module() =~ /^Moose/;
95 return 0;
96 }
97 );
98
99 for my $node ( @{$use_nodes} ) {
100 my $replacement_code .=
101 defined $node->module_version()
102 ? join(
103 q{ },
104 'use', $node->module(), $node->module_version(), '()', ';',
105 )
106 : '# ' . $node->content();
107
108 $node->insert_before( $_->clone() )
109 for PPI::Document->new( \$replacement_code )->children();
110 $node->remove();
111 }
112
f56affb6 113 return;
9e60f0d8 114}
115
f56affb6 116sub _inline_parents {
117 my $self = shift;
118
119 my @supers = $self->_class_meta()->superclasses();
120 return unless @supers;
121
122 my $code = 'use parent ';
123 $code .= join ', ', map { B::perlstring($_) } @supers;
124 $code .= ";\n";
125
126 $self->_package_statement()->insert_after( $_->clone() )
127 for PPI::Document->new( \$code )->children();
128
129 return;
9e60f0d8 130}
131
f56affb6 132sub _load_required_modules {
133 my $self = shift;
134
135 my $code = "use Scalar::Util ();\nuse Moose::Error::Util ();\nuse Carp ();\n";
136
137 $self->_package_statement()->insert_after( $_->clone() )
138 for PPI::Document->new( \$code )->children();
139
140 return;
141}
142
143sub _inline_roles {
144 return;
145}
146
147sub _inline_constructor {
148 my $self = shift;
149
150 my $constructor = join "\n",
151 (
152 'sub new {',
153 $self->_class_meta()->_inline_new_object(),
154 '}'
155 );
156
157 $constructor .= "\n\n";
158
159 $self->_insert_before_end($constructor);
160
161 return;
162}
163
164sub _inline_attributes {
165 return;
166}
167
168sub _insert_before_end {
169 my $self = shift;
170 my $code = shift;
171
172 my $end_node = $self->_ppi_document()->find_first(
173 sub {
174 my $node = $_[1];
175
176 return 1
177 if $node->isa('PPI::Statement') && $node->content() =~ /^1;/;
178 return 0;
179 }
180 );
181
182 die 'Cannot find the end of the class (looking for a line match /^1;/)'
183 unless $end_node;
184
185 $end_node->insert_before( $_->clone() )
186 for PPI::Document->new( \$code )->children();
187
188 return;
189}
190
191sub _build_ppi_document {
192 my $self = shift;
193
194 my $pm_file = module_notional_filename( $self->class() );
195 my $path_to_class = $INC{$pm_file}
196 or die "Cannot find $pm_file in %INC!";
197
198 return PPI::Document->new( $path_to_class->stringify() )
199 or die PPI::Document->errstr();
200}
201
202sub _build_package_statement {
203 my $self = shift;
204
205 my $package_stmt = $self->_ppi_document()->find_first(
206 sub {
207 my $node = $_[1];
208 return 1 if $_[1]->isa('PPI::Statement::Package');
209 return 0;
210 }
211 );
212
213 die 'Cannot find a package statement in this code'
214 unless $package_stmt;
215
216 return $package_stmt;
9e60f0d8 217}
218
219__PACKAGE__->meta()->make_immutable();
220
2211;