File Coverage

lib/File/Corresponding.pm
Criterion Covered Total %
statement 34 36 94.4
branch 3 6 50.0
condition 1 2 50.0
subroutine 9 10 90.0
pod 2 2 100.0
total 49 56 87.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             File::Corresponding - Find corresponding files in the directory tree
5              
6              
7              
8             =head1 PREAMBLE
9              
10             In a source tree it is common to have files with the same name, but in
11             different places in the directory tree. A typical MVC application
12             could look something like this:
13              
14             Book lib/Controller/Book.pm
15             Borrower lib/Controller/Borrower.pm
16             Book lib/Model/Schema/Book.pm
17             Borrower lib/Model/Schema/Borrower.pm
18             Book lib/root/templates/Book.t
19             Borrower lib/root/templates/Borrower.t
20             Book lib/t/controller-book.t
21             Borrower lib/t/controller-borrower.t
22             Book lib/t/model-schema-book.t
23             Borrower lib/t/model-schema-borrower.t
24              
25             Here we clearly have two types of things; a Book and a Borrower.
26              
27             The C<Controller/Book.pm>, C<Model/Schema/Book.pm>,
28             C<root/templates/Book.t>, C<t/controller-book.t>, and
29             C<t/model-schema-book.t> represent different aspects of the same Book
30             entity; they I<correspond> to each other.
31              
32             Since the files belong to each other it is useful for the programmer
33             to easily navigate between them.
34              
35             This module helps with that.
36              
37              
38              
39             =head1 SYNOPSIS
40              
41             =head2 Config file
42              
43             Given a config file C<.corresponding_file> in the current directory or
44             your $HOME directory:
45              
46             ---
47             file_groups:
48             -
49             name: All MyApp classes
50             file_profiles:
51             -
52             name: Cat Controller
53             regex: / Controller . (\w+) \.pm $ /x
54             sprintf: Controller/%s.pm
55             -
56             name: DBIC Schema
57             regex: "|Model/Schema.(\w+)\.pm$|"
58             sprintf: Model/Schema/%s.pm
59             -
60             name: Template
61             regex: /root.template.(\w+)\.pm$/
62             sprintf: root/template/%s.pm
63              
64              
65             =head2 From the command line
66              
67             $ corresponding_file Controller/Book.pm
68             Model/Schema/Book.pm
69             $ cd ..
70             $ corresponding_file lib/Controller/Book.pm
71             lib/Model/Schema/Book.pm
72              
73              
74             =head2 From your editor
75              
76             =over 2
77              
78             =item Emacs
79              
80             L<Devel::PerlySense> has a feature "Go to Project's Other Files" for
81             navigating to related files.
82              
83             Actually, it doesn't yet. But it will.
84              
85             =back
86              
87              
88             =head2 From your program
89              
90             By using C<File::Corresponding> as a library, you can use the
91             resulting L<File::Corresponding::File::Found> objects to display more
92             information than just the file name.
93              
94              
95              
96             =head1 DESCRIPTION
97              
98             C<File::Corresponding> uses a configuration of groups of File Profiles to
99             identify corresponding files.
100              
101             Using a C<.corresponding_file> config file, and the command line
102             script C<corresponding_file>, you can easily look up corresponding
103             files.
104              
105             It's obviously better if you let your editor do the tedious bits for
106             you, like passing the file name to the script, letting you choose
107             which of the corresponding files you meant, and opening the file in
108             the editor.
109              
110             That's left as an exercise for the reader (well you I<are> a
111             programmer, aren't you?).
112              
113              
114              
115             =head1 THE CONFIG FORMAT
116              
117             Study the SYNOPSIS example.
118              
119             A File Profile for e.g. "Controller" files includes a C<regex> to
120             match a Controller file name with e.g. "Book" in it, and a C<sprintf>
121             string template to render any found files with "Book" in them as a
122             Controller file.
123              
124             Regex definitions are whatever comes after "qr" in
125             e.g. C<qr/file.pm/i>, i.e. "/file.pm/i". As you can see, you can use
126             regex modifiers, and even use other delimiters (which is handy, since
127             you're likely to match "/").
128              
129             The C<regex> should match the intended file. The first capturing
130             parenthesis must contain the entity file fragmen that is common to all
131             files in the group.
132              
133             The C<sprintf> string should contain a C<%s> to fill in the captured
134             file fragment from any other File Profile in the Group.
135              
136             Only existing files are reported.
137              
138              
139              
140             =head1 SEE ALSO
141              
142              
143              
144             =head1 AUTHOR
145              
146             Johan Lindstrom, C<< <johanl[AT]cpan.org> >>
147              
148              
149              
150             =head1 BUGS AND CAVEATS
151              
152             =head2 BUG REPORTS
153              
154             Please report any bugs or feature requests to
155             C<bug-file-corresponding@rt.cpan.org>, or through the web interface at
156             L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=File-Corresponding>.
157             I will be notified, and then you'll automatically be notified of progress on
158             your bug as I make changes.
159              
160              
161             =head2 CAVEATS
162              
163             Currently C<File::Corresponding> supports the simple case in the
164             SYNOPSIS above, where the Controller/Book.pm can easily be translated
165             to Model/Schema/Book.pm. It does not yet support the more complicated
166             translation from Controller/Book.pm to t/controller-book.t and back.
167              
168              
169             =head2 KNOWN BUGS
170              
171              
172             =head1 COPYRIGHT & LICENSE
173              
174             Copyright 2007- Johan Lindstrom, All Rights Reserved.
175              
176             This program is free software; you can redistribute it and/or modify it
177             under the same terms as Perl itself.
178              
179              
180              
181             =head1 *** DEVELOPER API DOCS ***
182              
183             =head1 ERROR HANDLING MODEL
184              
185             Failures will result in a die.
186              
187              
188              
189             =cut
190              
191             package File::Corresponding;
192             $File::Corresponding::VERSION = '0.004';
193 3     3   73351 use Moose;
  3         1028540  
  3         21  
194              
195              
196              
197 3     3   18751 use Moose::Autobox;
  3         428243  
  3         18  
198 3     3   3411 use YAML::Tiny;
  3         13605  
  3         189  
199 3     3   1168 use Data::Dumper;
  3         10019  
  3         163  
200              
201 3     3   1119 use File::Corresponding::Group;
  3         9  
  3         1049  
202              
203              
204             =head1 ATTRIBUTES
205              
206             =head2 profile_groups : ArrayRef[File::Corresponding::Group]
207              
208             Group config objects.
209              
210             =cut
211             has profile_groups => (
212             is => "rw",
213             isa => "ArrayRef[File::Corresponding::Group]",
214             default => sub { [] },
215             );
216              
217              
218              
219             =head1 METHODS
220              
221             =head2 corresponding($file) : ArrayRef[File::Corresponding::File::Found]
222              
223             Find files corresponding to $file (given the config in ->profile_groups)
224             and return found @files.
225              
226             If the same file is found via many Groups, it will be reported once
227             per group (so if you only use this to display the file name, make sure
228             to unique the file names).
229              
230             =cut
231             sub corresponding {
232 3     3 1 7119 my $self = shift;
233 3         7 my ($file) = @_;
234              
235 3     5   70 my $found_files = $self->profile_groups ->map(sub { $_->corresponding($file)->flatten });
  5         35  
236              
237 3         36 return $found_files;
238             }
239              
240              
241              
242             =head2 load_config_file($config_file) : 1
243              
244             Load yaml $config_file, or die with an error message.
245              
246             =cut
247             sub load_config_file {
248 2     2 1 187 my $self = shift;
249 2         2 my ($file) = @_;
250              
251 2 50       11 my $yaml = YAML::Tiny->read($file)
252             or die("Could not read config file ($file):\n" . YAML::Tiny->errstr);
253 1         16110 my $config = $yaml->[0];
254              
255             my $die = sub {
256 0     0   0 my $element = shift;
257 0         0 die("Missing element '$element' in config file ($file)\n" . Dumper($config));
258 1         6 };
259              
260 1 50       6 my $file_groups = $config->{file_groups} or $die->("file_groups");
261              
262             $self->profile_groups(
263             $file_groups->map( sub {
264 1     1   7 my $group = $_;
265 1   50     6 my $name = $group->{name} || "";
266              
267 1 50       4 my $file_profiles = $group->{file_profiles} or $die->("file_profiles");
268             my $profiles = $file_profiles->map( sub {
269 3         883 File::Corresponding::File::Profile->new($_),
270 1         6 });
271              
272 1         634 File::Corresponding::Group->new({
273             name => $name,
274             file_profiles => $profiles,
275             });
276 1         17 }),
277             );
278              
279              
280             #print Dumper($config); use Data::Dumper;
281              
282              
283              
284 1         20 return 1;
285             }
286              
287              
288              
289             1;
290              
291              
292              
293             __END__