Commit | Line | Data |
9e60f0d8 |
1 | package MooseX::Compiler; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
f56affb6 |
6 | use B; |
9e60f0d8 |
7 | use Module::Runtime qw( module_notional_filename ); |
8 | use PPI::Document; |
9 | use Scalar::Util qw( blessed ); |
10 | |
11 | use Moose; |
12 | use Moose::Util::TypeConstraints; |
13 | |
14 | my $moose_class = subtype, as 'ClassName', where { |
15 | $_[0]->can('meta') |
16 | && blessed $_[0]->meta() |
17 | && $_[0]->meta()->isa('Moose::Meta::Class'); |
18 | }; |
19 | |
20 | has class => ( |
21 | is => 'ro', |
22 | isa => $moose_class, |
23 | required => 1, |
24 | ); |
25 | |
26 | has _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 |
34 | has _ppi_document => ( |
35 | is => 'ro', |
36 | isa => 'PPI::Document', |
37 | init_arg => undef, |
38 | lazy => 1, |
39 | builder => '_build_ppi_document', |
40 | ); |
41 | |
42 | has _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 |
50 | sub 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 |
61 | sub _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 | |
72 | sub _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 | |
82 | sub _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 |
116 | sub _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 |
132 | sub _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 | |
143 | sub _inline_roles { |
144 | return; |
145 | } |
146 | |
147 | sub _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 | |
164 | sub _inline_attributes { |
165 | return; |
166 | } |
167 | |
168 | sub _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 | |
191 | sub _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 | |
202 | sub _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 | |
221 | 1; |