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