1
+ package GPH::Dependency::AliceFixturesPlugin ;
2
+
3
+ use strict;
4
+ use warnings FATAL => ' all' ;
5
+
6
+ use File::Basename;
7
+ use lib dirname(__FILE__ );
8
+
9
+ use File::Find::Rule;
10
+ use GPH::Dependency::Fixture;
11
+ use GPH::Dependency::File;
12
+
13
+ sub new {
14
+ my ($proto , %args ) = @_ ;
15
+ (exists ($args {fixture_directories }) && exists ($args {strip }) && exists ($args {directories })) or die " $! " ;
16
+
17
+ my $self = bless {
18
+ fixture_directories => $args {fixture_directories },
19
+ fixture_excludes => $args {fixture_excludes } // undef ,
20
+ directories => $args {directories },
21
+ excludes => $args {excludes } // undef ,
22
+ strip => $args {strip },
23
+ type => $args {type } // 1,
24
+ fixtures => $args {fixtures } // {},
25
+ classes => $args {classes } // {},
26
+ usages => {},
27
+ inheritance => {},
28
+ }, $proto ;
29
+
30
+ return ($self );
31
+ }
32
+
33
+ sub files {
34
+ my ($self ) = @_ ;
35
+
36
+ $self
37
+ -> yaml()
38
+ -> php()
39
+ -> inheritance()
40
+ -> resolve()
41
+ -> dependencies()
42
+ ;
43
+
44
+ return ($self -> {' inheritance' });
45
+ };
46
+
47
+ sub yaml {
48
+ my ($self ) = @_ ;
49
+
50
+ my $rule = File::Find::Rule-> new;
51
+
52
+ if (defined $self -> {fixture_excludes }) {
53
+ $rule -> or (
54
+ $rule -> new-> exec (sub {
55
+ my ($shortname , $path , $fullname ) = @_ ;
56
+ foreach my $exclude (@{$self -> {fixture_excludes }}) {
57
+ return 1 if $fullname =~ $exclude ;
58
+ }
59
+ return 0;
60
+ })-> prune-> discard,
61
+ $rule -> new
62
+ );
63
+ }
64
+
65
+ my @files = $rule -> name(' *.yml' , ' *.yaml' )-> in(@{$self -> {fixture_directories }});
66
+
67
+ foreach my $path (@files ) {
68
+ $self -> parseYaml($path )
69
+ }
70
+
71
+ return ($self );
72
+ };
73
+
74
+ sub parseYaml {
75
+ my ($self , $path ) = @_ ;
76
+ my ($fh , $in_import );
77
+
78
+ return $self unless $path =~ ' [/]{0,}([^/]+)\.[yml|yaml]{3,4}$' ;
79
+
80
+ open ($fh , ' <' , $path ) or die " unable to open file $path : $! " ;
81
+
82
+ $path =~ s / $self->{strip}// g ;
83
+
84
+ $self -> {fixtures }{$path } = GPH::Dependency::Fixture-> new((file => $path ));
85
+ $in_import = 0;
86
+
87
+ while (<$fh >) {
88
+ chomp $_ ;
89
+ next unless $_ =~ / ^\\ ?([^\s #]+):$ / ;
90
+ my $dependency = $1 ;
91
+
92
+ $in_import = $dependency eq ' include' ? 1 : 0;
93
+
94
+ while ($in_import == 1) {
95
+ my $import = <$fh >;
96
+ chomp $import ;
97
+
98
+ if ($import =~ / ^\s *-\s *([^\s ]+)$ / ) {
99
+ my $realpath = $self -> realpath(dirname($path ) . " /" . $1 );
100
+ $self -> {fixtures }{$path }{includes }{$realpath } = 1;
101
+ }
102
+ else {
103
+ $in_import = 0;
104
+ }
105
+ }
106
+
107
+ $self -> {fixtures }{$path }{dependencies }{$1 } = 1 unless $dependency eq ' include' ;
108
+ }
109
+ };
110
+
111
+ sub php {
112
+ my ($self ) = @_ ;
113
+
114
+ my $rule = File::Find::Rule-> new;
115
+
116
+ if (defined $self -> {excludes }) {
117
+ $rule -> or (
118
+ $rule -> new-> exec (sub {
119
+ my ($shortname , $path , $fullname ) = @_ ;
120
+ foreach my $exclude (@{$self -> {excludes }}) {
121
+ return 1 if $fullname =~ $exclude ;
122
+ }
123
+ return 0;
124
+ })-> prune-> discard,
125
+ $rule -> new
126
+ );
127
+ }
128
+
129
+ my @files = $rule -> name(' *.php' )-> in(@{$self -> {directories }});
130
+
131
+ foreach my $file (@files ) {
132
+ $self -> parsePhp($file );
133
+ }
134
+
135
+ return ($self );
136
+ };
137
+
138
+ sub parsePhp {
139
+ my ($self , $path ) = @_ ;
140
+ my ($fh , $namespace , $fqcn , $class , $type );
141
+
142
+ return $self unless $path =~ ' [/]{0,}([^/]+)\.php$' ;
143
+
144
+ open ($fh , ' <' , $path ) or die " unable to open file $path : $! " ;
145
+
146
+ $class = $1 ;
147
+
148
+ $path =~ s / $self->{strip}// g ;
149
+
150
+ while (<$fh >) {
151
+ chomp $_ ;
152
+
153
+ next if $_ =~ / ^[\s ]{0,}[\/ ]{0,1}[\* ]{1,2}/ or $_ eq ' ' or $_ =~ / ^[\s ]*\/\/ / ;
154
+
155
+ # get namespace
156
+ if ($_ =~ / ^namespace (.*);$ / ) {
157
+ $namespace = $1 ;
158
+
159
+ next ;
160
+ }
161
+
162
+ if ($_ =~ " [ ]{0,}([^ ]+) $class (?:[ :]|\$ ){1,}" ) {
163
+ $type = $self -> {type } == 1 ? $1 : undef ;
164
+ $fqcn = $namespace . ' \\ ' . $class ;
165
+
166
+ next ;
167
+ }
168
+
169
+ next unless $_ =~ / '([^']+\. [yaml|yml]{3,4})'/ ;
170
+
171
+ $self -> {classes }{$path } = GPH::Dependency::File-> new((fqcn => $fqcn , file => $path , type => $type )) unless defined $self -> {classes }{$path };
172
+
173
+ $self -> {classes }{$path }{fixtures }{$self -> realpath($1 )} = 1;
174
+ }
175
+ };
176
+
177
+ sub inheritance {
178
+ my ($self ) = @_ ;
179
+
180
+ foreach my $key (keys %{$self -> {fixtures }}) {
181
+ $self -> {fixtures }{$key }-> inheritance($self -> processInheritance($key , {}));
182
+ }
183
+
184
+ return ($self );
185
+ };
186
+
187
+ sub processInheritance {
188
+ my ($self , $key , $seen ) = @_ ;
189
+ my (%result , $include );
190
+
191
+ return \%result unless defined $self -> {fixtures }-> {$key };
192
+
193
+ %result = (%result , %{$self -> {fixtures }-> {$key }-> {dependencies }});
194
+
195
+ foreach $include (keys %{$self -> {fixtures }-> {$key }-> {includes }}) {
196
+ next if $seen -> {$include };
197
+ $seen -> {$include } = 1;
198
+ %result = (%result , %{$self -> processInheritance($include , $seen )});
199
+ }
200
+
201
+ return (\%result );
202
+ };
203
+
204
+ sub resolve {
205
+ my ($self ) = @_ ;
206
+ my ($class , $fixture , $file );
207
+
208
+ foreach $class (keys %{$self -> {classes }}) {
209
+ foreach $fixture (keys %{$self -> {classes }{$class }{fixtures }}) {
210
+ foreach $file (keys %{$self -> {fixtures }}) {
211
+ next unless $file =~ $fixture ;
212
+
213
+ $self -> {fixtures }{$file }{files }{$class } = 1;
214
+ }
215
+ }
216
+ }
217
+
218
+ return ($self );
219
+ };
220
+
221
+ sub dependencies {
222
+ my ($self ) = @_ ;
223
+ my ($fixture , $file , $class );
224
+
225
+ foreach $fixture (keys %{$self -> {fixtures }}) {
226
+ foreach $file (keys %{$self -> {fixtures }{$fixture }{files }}) {
227
+ $class = $self -> {classes }{$file };
228
+ $self -> {' inheritance' }{$file } = $class -> merge(GPH::Dependency::File-> new((
229
+ file => $file ,
230
+ dependencies => \%{$self -> {fixtures }{$fixture }{inheritance }}))
231
+ );
232
+ }
233
+ }
234
+
235
+ return ($self );
236
+ };
237
+
238
+ sub realpath {
239
+ my ($self , $path ) = @_ ;
240
+ my @c = reverse split (m @ /@ , $path );
241
+ my @c_new ;
242
+
243
+ while (@c ) {
244
+ my $component = shift @c ;
245
+ next unless length ($component );
246
+ if ($component eq " ." ) {next ;}
247
+ if ($component eq " .." ) {
248
+ shift @c ;
249
+ next ;
250
+ }
251
+ push (@c_new , $component );
252
+ }
253
+
254
+ return join (" /" , reverse @c_new );
255
+ };
256
+
257
+ 1;
258
+
259
+ __END__
260
+
261
+ =head1 NAME
262
+
263
+ GPH::Dependency::AliceFixturesPlugin - a GPH::Dependency::DependencyMapBuilder plugin which extracts (alice data) fixture
264
+ dependencies and assigns them to php classes using them.
265
+
266
+ =head1 SYNOPSIS
267
+
268
+ use GPH::Dependency::AliceFixturesPlugin;
269
+
270
+ my $fixtures = GPH::Dependency::AliceFixturesPlugin->new((
271
+ type => 0,
272
+ strip => '/Users/foo/',
273
+ fixtures => [ '/Users/foo/tests/fixtures' ],
274
+ directories => [ '/Users/foo/tests/Functional' ],
275
+ excludes => [ '/Users/foo/tests/Functional/Bar' ]
276
+ ));
277
+
278
+ my $files = $fixtures->files();
279
+
280
+ =head1 METHODS
281
+
282
+ =over 4
283
+
284
+ =item C<< -E<gt> new(%args) >>
285
+
286
+ the C<new > method creates a new GPH::Dependency::AliceFixturesPlugin. it takes a hash of options, valid option keys include:
287
+
288
+ =over
289
+
290
+ =item fixtures B<(required) >
291
+
292
+ array of paths of directories containing fixture files.
293
+
294
+ =item directories B<(required) >
295
+
296
+ array of paths of directories containing php files using fixtures.
297
+
298
+ =item strip B<(required) >
299
+
300
+ string defining which bit should be stripped of the filepath
301
+
302
+ =item excludes
303
+
304
+ array of paths of directories containing php files which to ignore.
305
+
306
+ =item type
307
+
308
+ boolean value defining whether or not to resolve the php type (e.g. class, interface) during parsing.
309
+
310
+ =back
311
+
312
+ =item C<< -E<gt> files() >>
313
+
314
+ parse fixture and php files, define and resolve dependencies and return a collection of GPH::Dependency::File objects.
315
+
316
+ =item C<< -E<gt> yaml() >> B<(internal) >
317
+
318
+ iterate over yaml files contained in the C<$self- > {fixture_directories}> property and parse them through the parseYaml method.
319
+
320
+ =item C<< -E<gt> parseYaml() >> B<(internal) >
321
+
322
+ parse fixture files, define and resolve their dependencies.
323
+
324
+ =item C<< -E<gt> php() >> B<(internal) >
325
+
326
+ iterate over php files contained in the C<$self- > {directories}> property and parse them through the parsePhp method.
327
+
328
+ =item C<< -E<gt> parsePhp($file) >> B<(internal) >
329
+
330
+ parse php file located at filepath $file, define and resolve it's fixture dependencies.
331
+
332
+ =item C<< -E<gt> inheritance() >> B<(internal) >
333
+
334
+ iterate over fixtures defined in the C<$self- > {fixtures}> property.
335
+
336
+ =item C<< -E<gt> processInheritance($key, %seen) >> B<(internal) >
337
+
338
+ process inheritance for the GPH::Dependency::Fixture file defined in the C<$self- > {fixtures}{$key}> property.
339
+ the C<%seen > argument usually is an empty hash which is used internally to bypass circular references.
340
+
341
+ =item C<< -E<gt> resolve() >> B<(internal) >
342
+
343
+ assign php classes to GPH::Dependency::Fixture objects
344
+
345
+ =item C<< -E<gt> dependencies() >> B<(internal) >
346
+
347
+ assign fixture dependencies to GPH::Dependency::File php files
348
+
349
+ =item C<< -E<gt> realpath() >> B<(internal) >
350
+
351
+ resolve directory traversal
352
+
353
+ =back
354
+
355
+ =head1 AUTHOR
356
+
357
+ the GPH::Dependency::AliceFixturesPlugin module was written by wicliff wolda <[email protected] >
358
+
359
+ =head1 COPYRIGHT AND LICENSE
360
+
361
+ this library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
362
+
363
+ =cut
0 commit comments