Coverage Summary

# File Coverage

lib/Devel/PerlySense/Project.pm
Criterion | Covered | Total | %
--- | --- | --- | ---
statement | 165 | 232 | 71.1
branch | 28 | 64 | 43.7
condition | 12 | 20 | 60.0
subroutine | 32 | 36 | 88.8
pod | 16 | 16 | 100.0
total | 253 | 368 | 68.7

line | stmt | bran | cond | sub | pod | time | code
--- | --- | --- | --- | --- | --- | --- | ---
1 |  |  |  |  |  |  | =head1 NAME
2 |  |  |  |  |  |  |
3 |  |  |  |  |  |  | Devel::PerlySense::Project - A Project root
4 |  |  |  |  |  |  |
5 |  |  |  |  |  |  | =head1 SYNOPSIS
6 |  |  |  |  |  |  |
7 |  |  |  |  |  |  |
8 |  |  |  |  |  |  |
9 |  |  |  |  |  |  |
10 |  |  |  |  |  |  | =head1 DESCRIPTION
11 |  |  |  |  |  |  |
12 |  |  |  |  |  |  | A Project describes the root directory of a source tree.
13 |  |  |  |  |  |  |
14 |  |  |  |  |  |  | A Project has configuration settings.
15 |  |  |  |  |  |  |
16 |  |  |  |  |  |  | =cut
17 |  |  |  |  |  |  |
18 |  |  |  |  |  |  |
19 |  |  |  |  |  |  |
20 |  |  |  |  |  |  |
21 |  |  |  |  |  |  |
22 | 63 |  |  | 63 |  | 218 | use strict;
| 63 |  |  |  |  | 81 |
| 63 |  |  |  |  | 1790 |
23 | 63 |  |  | 63 |  | 229 | use warnings;
| 63 |  |  |  |  | 64 |
| 63 |  |  |  |  | 1571 |
24 | 63 |  |  | 63 |  | 1054 | use utf8;
| 63 |  |  |  |  | 110 |
| 63 |  |  |  |  | 315 |
25 |  |  |  |  |  |  |
26 |  |  |  |  |  |  | package Devel::PerlySense::Project;
27 |  |  |  |  |  |  |
28 |  |  |  |  |  |  |
29 |  |  |  |  |  |  |
30 | 63 |  |  | 63 |  | 2579 | use Spiffy -Base;
| 63 |  |  |  |  | 5096 |
| 63 |  |  |  |  | 285 |
31 | 63 |  |  | 63 |  | 158257 | use Carp;
| 63 |  |  | 63 |  | 76 |
| 63 |  |  | 63 |  | 1644 |
| 63 |  |  |  |  | 205 |
| 63 |  |  |  |  | 72 |
| 63 |  |  |  |  | 1624 |
| 63 |  |  |  |  | 220 |
| 63 |  |  |  |  | 62 |
| 63 |  |  |  |  | 3347 |
32 | 63 |  |  | 63 |  | 238 | use Data::Dumper;
| 63 |  |  |  |  | 81 |
| 63 |  |  |  |  | 2317 |
33 | 63 |  |  | 63 |  | 689 | use File::Basename;
| 63 |  |  |  |  | 480 |
| 63 |  |  |  |  | 3238 |
34 | 63 |  |  | 63 |  | 509 | use Path::Class;
| 63 |  |  |  |  | 30928 |
| 63 |  |  |  |  | 2350 |
35 | 63 |  |  | 63 |  | 32893 | use File::Slurp;
| 63 |  |  |  |  | 676819 |
| 63 |  |  |  |  | 4415 |
36 | 63 |  |  | 63 |  | 23352 | use File::chdir;
| 63 |  |  |  |  | 111527 |
| 63 |  |  |  |  | 6345 |
37 | 63 |  |  | 63 |  | 30325 | use Perl::Critic 1.105;
| 63 |  |  |  |  | 85117707 |
| 63 |  |  |  |  | 6406 |
38 | 63 |  |  | 63 |  | 473 | use List::MoreUtils qw/ uniq /;
| 63 |  |  |  |  | 83 |
| 63 |  |  |  |  | 3871 |
39 |  |  |  |  |  |  |
40 | 63 |  |  | 63 |  | 1949 | use Devel::PerlySense;
| 63 |  |  |  |  | 103 |
| 63 |  |  |  |  | 701 |
41 | 63 |  |  | 63 |  | 21755 | use Devel::PerlySense::Util;
| 63 |  |  |  |  | 87 |
| 63 |  |  |  |  | 3500 |
42 | 63 |  |  | 63 |  | 607 | use Devel::PerlySense::Util::Log;
| 63 |  |  |  |  | 120 |
| 63 |  |  |  |  | 2355 |
43 | 63 |  |  | 63 |  | 31731 | use Devel::PerlySense::Config::Project::Default;
| 63 |  |  |  |  | 134 |
| 63 |  |  |  |  | 522 |
44 |  |  |  |  |  |  |
45 |  |  |  |  |  |  |
46 |  |  |  |  |  |  |
47 |  |  |  |  |  |  |
48 |  |  |  |  |  |  |
49 |  |  |  |  |  |  | =head1 PROPERTIES
50 |  |  |  |  |  |  |
51 |  |  |  |  |  |  | =head2 dirProject
52 |  |  |  |  |  |  |
53 |  |  |  |  |  |  | The effective project root dir.
54 |  |  |  |  |  |  |
55 |  |  |  |  |  |  | Readonly.
56 |  |  |  |  |  |  |
57 |  |  |  |  |  |  | =cut
58 | 1964 |  |  | 1964 | 1 | 6109 | sub dirProject {
59 |  |  |  |  |  |  | return
60 | 1964 |  | 66 |  |  | 46795 | $self->dirProjectExplicitDir ||
61 |  |  |  |  |  |  | $self->dirProjectImplicitUse ||
62 |  |  |  |  |  |  | $self->dirProjectImplicitDir;
63 |  |  |  |  |  |  | }
64 |  |  |  |  |  |  |
65 |  |  |  |  |  |  |
66 |  |  |  |  |  |  |
67 |  |  |  |  |  |  |
68 |  |  |  |  |  |  |
69 |  |  |  |  |  |  | =head2 dirProjectExplicitDir
70 |  |  |  |  |  |  |
71 |  |  |  |  |  |  | If known, the root dir made explicit by the existence of a project
72 |  |  |  |  |  |  | directory and config, else ""
73 |  |  |  |  |  |  |
74 |  |  |  |  |  |  | Default: ""
75 |  |  |  |  |  |  |
76 |  |  |  |  |  |  | =cut
77 |  |  |  |  |  |  | field "dirProjectExplicitDir" => "";
78 |  |  |  |  |  |  |
79 |  |  |  |  |  |  |
80 |  |  |  |  |  |  |
81 |  |  |  |  |  |  |
82 |  |  |  |  |  |  |
83 |  |  |  |  |  |  | =head2 dirProjectImplicitUse
84 |  |  |  |  |  |  |
85 |  |  |  |  |  |  | If known, the root dir indicated by a found used module, else "".
86 |  |  |  |  |  |  |
87 |  |  |  |  |  |  | Default: ""
88 |  |  |  |  |  |  |
89 |  |  |  |  |  |  | =cut
90 |  |  |  |  |  |  | field "dirProjectImplicitUse" => "";
91 |  |  |  |  |  |  |
92 |  |  |  |  |  |  |
93 |  |  |  |  |  |  |
94 |  |  |  |  |  |  |
95 |  |  |  |  |  |  |
96 |  |  |  |  |  |  | =head2 dirProjectImplicitDir
97 |  |  |  |  |  |  |
98 |  |  |  |  |  |  | If known, the root dir indicated by the presence of "lib" or "t", else "".
99 |  |  |  |  |  |  |
100 |  |  |  |  |  |  | Default: ""
101 |  |  |  |  |  |  |
102 |  |  |  |  |  |  | =cut
103 |  |  |  |  |  |  | field "dirProjectImplicitDir" => "";
104 |  |  |  |  |  |  |
105 |  |  |  |  |  |  |
106 |  |  |  |  |  |  |
107 |  |  |  |  |  |  |
108 |  |  |  |  |  |  |
109 |  |  |  |  |  |  | =head2 oConfig
110 |  |  |  |  |  |  |
111 |  |  |  |  |  |  | A Config::Project object with the current Project Config. This gets
112 |  |  |  |  |  |  | loaded if there is a config file available when the Project is
113 |  |  |  |  |  |  | identified.
114 |  |  |  |  |  |  |
115 |  |  |  |  |  |  | Default: An Config::Project::Default object
116 |  |  |  |  |  |  |
117 |  |  |  |  |  |  | =cut
118 |  |  |  |  |  |  | field "oConfig" => Devel::PerlySense::Config::Project::Default->new();
119 |  |  |  |  |  |  |
120 |  |  |  |  |  |  |
121 |  |  |  |  |  |  |
122 |  |  |  |  |  |  |
123 |  |  |  |  |  |  |
124 |  |  |  |  |  |  | =head2 rhConfig
125 |  |  |  |  |  |  |
126 |  |  |  |  |  |  | The actual hashref with config values.
127 |  |  |  |  |  |  |
128 |  |  |  |  |  |  | Readonly (that includes the entire data structure).
129 |  |  |  |  |  |  |
130 |  |  |  |  |  |  | =cut
131 | 1300 |  |  | 1300 | 1 | 5052 | sub rhConfig {
132 | 1300 |  |  |  |  | 36701 | return $self->oConfig->rhConfig;
133 |  |  |  |  |  |  | }
134 |  |  |  |  |  |  |
135 |  |  |  |  |  |  |
136 |  |  |  |  |  |  |
137 |  |  |  |  |  |  |
138 |  |  |  |  |  |  |
139 |  |  |  |  |  |  | =head2 oPerlySense
140 |  |  |  |  |  |  |
141 |  |  |  |  |  |  | Devel::PerlySense object.
142 |  |  |  |  |  |  |
143 |  |  |  |  |  |  | =cut
144 |  |  |  |  |  |  | field "oPerlySense" => undef;
145 |  |  |  |  |  |  |
146 |  |  |  |  |  |  |
147 |  |  |  |  |  |  |
148 |  |  |  |  |  |  |
149 |  |  |  |  |  |  |
150 |  |  |  |  |  |  | =head2 nameVcs
151 |  |  |  |  |  |  |
152 |  |  |  |  |  |  | The name of the Version Control system for the project.
153 |  |  |  |  |  |  |
154 |  |  |  |  |  |  | Readonly. Currently supported:
155 |  |  |  |  |  |  |
156 |  |  |  |  |  |  | git
157 |  |  |  |  |  |  | svn
158 |  |  |  |  |  |  | none
159 |  |  |  |  |  |  |
160 |  |  |  |  |  |  | In that order, so that if there's a Git repo on top of a SVN repo, git
161 |  |  |  |  |  |  | is found.
162 |  |  |  |  |  |  |
163 |  |  |  |  |  |  | =cut
164 | 1 |  |  | 1 | 1 | 1 | sub nameVcs {
165 |  |  |  |  |  |  |
166 |  |  |  |  |  |  | #Git
167 | 1 | 50 |  |  |  | 23 | $self->oPerlySense->dirFindLookingAround(
168 |  |  |  |  |  |  | ".git",
169 |  |  |  |  |  |  | $self->dirProject,
170 |  |  |  |  |  |  | ["."],
171 |  |  |  |  |  |  | ) and return("git");
172 |  |  |  |  |  |  |
173 |  |  |  |  |  |  | #Subversion
174 | 0 | 0 |  |  |  | 0 | -d dir($self->dirProject, ".svn") and return("svn");
175 |  |  |  |  |  |  |
176 | 0 |  |  |  |  | 0 | return("none");
177 |  |  |  |  |  |  | }
178 |  |  |  |  |  |  |
179 |  |  |  |  |  |  |
180 |  |  |  |  |  |  |
181 |  |  |  |  |  |  |
182 |  |  |  |  |  |  |
183 |  |  |  |  |  |  | =head1 CLASS METHODS
184 |  |  |  |  |  |  |
185 |  |  |  |  |  |  | =head2 newFromLocation(file => $file, dir => $dir, oPerlySense => $oPs)
186 |  |  |  |  |  |  |
187 |  |  |  |  |  |  | Create new Project given either $file, or $dir.
188 |  |  |  |  |  |  |
189 |  |  |  |  |  |  | First, search for an explicit project root directory, then try to find
190 |  |  |  |  |  |  | any modules used in $file (if passed), then try to find any "lib" or
191 |  |  |  |  |  |  | "t" directory upwards of $file or $dir.
192 |  |  |  |  |  |  |
193 |  |  |  |  |  |  | $file takes precedence over $dir if both are specified.
194 |  |  |  |  |  |  |
195 |  |  |  |  |  |  | If none if this works out, no Project can be created and undef is
196 |  |  |  |  |  |  | returned.
197 |  |  |  |  |  |  |
198 |  |  |  |  |  |  | Return the new object, or undef if no project could be found.
199 |  |  |  |  |  |  |
200 |  |  |  |  |  |  | =cut
201 |  |  |  |  |  |  | sub newFromLocation(@) {
202 | 59 |  |  | 59 | 1 | 112 | my $pkg = shift;
203 | 59 |  |  |  |  | 591 | my ($oPerlySense) = Devel::PerlySense::Util::aNamedArg(["oPerlySense"], @_);
204 | 59 |  |  |  |  | 207 | my %p = @_;
205 | 59 |  |  |  |  | 139 | my $file = $p{file};
206 | 59 |  |  |  |  | 99 | my $dir = $p{dir};
207 | 59 | 100 |  |  |  | 747 | $file and $dir = dirname($file);
208 | 59 | 50 |  |  |  | 400 | $dir and $dir = dir($dir)->absolute;
209 |  |  |  |  |  |  |
210 | 59 |  |  |  |  | 18053 | my $oProject;
211 |  |  |  |  |  |  |
212 | 59 | 100 | 66 |  |  | 193 | $dir and $oProject = $pkg->newFindExplicit(
213 |  |  |  |  |  |  | dir => $dir,
214 |  |  |  |  |  |  | oPerlySense => $oPerlySense,
215 |  |  |  |  |  |  | ) and return $oProject;
216 |  |  |  |  |  |  |
217 |  |  |  |  |  |  |
218 |  |  |  |  |  |  | #If file, look for the dir from where any package in the file can
219 |  |  |  |  |  |  | #be used
220 | 7 | 100 | 66 |  |  | 35 | if($file and my $oDocument = $oPerlySense->oDocumentParseFile($file)) {
221 | 4 |  |  |  |  | 18 | for my $package ($oDocument->aNamePackage) {
222 | 2 |  |  |  |  | 90 | my $filePackage = $oPerlySense->fileFromModule($package);
223 | 2 |  |  |  |  | 563 | my $dirFound = $oPerlySense->dirFindLookingAround($filePackage, $dir);
224 |  |  |  |  |  |  |
225 | 2 | 50 |  |  |  | 270 | if($dirFound) {
226 | 2 |  |  |  |  | 5 | my $dirProject = dir($dirFound)->parent . ""; ###TODO: if lib or bin
227 | 2 |  |  |  |  | 292 | return $pkg->new(
228 |  |  |  |  |  |  | oPerlySense => $oPerlySense,
229 |  |  |  |  |  |  | dirProjectImplicitUse => $dirProject,
230 |  |  |  |  |  |  | );
231 |  |  |  |  |  |  | }
232 |  |  |  |  |  |  | }
233 |  |  |  |  |  |  | }
234 |  |  |  |  |  |  |
235 |  |  |  |  |  |  | #If dir, look for dirs
236 | 5 | 50 |  |  |  | 209 | if($dir) {
237 | 5 |  | 66 |  |  | 39 | my $dirFound =
238 |  |  |  |  |  |  | $oPerlySense->fileFindLookingAround("lib", $dir) ||
239 |  |  |  |  |  |  | $oPerlySense->fileFindLookingAround("t", $dir);
240 |  |  |  |  |  |  |
241 | 5 | 100 |  |  |  | 519 | if($dirFound) {
242 | 3 |  |  |  |  | 9 | my $dirProject = dir($dirFound)->parent . ""; ###TODO: if lib or bin
243 |  |  |  |  |  |  |
244 |  |  |  |  |  |  | #Special case the Unix style root dir. It's never a
245 |  |  |  |  |  |  | #Project dir, but if often contains a lib dir and so will
246 |  |  |  |  |  |  | #get identified as a Project.
247 |  |  |  |  |  |  | #
248 |  |  |  |  |  |  | #If it _is_ a root dir, this can be overriden with an
249 |  |  |  |  |  |  | #explicit .PerlySenseProject dir.
250 |  |  |  |  |  |  | #
251 |  |  |  |  |  |  | #On Windows, the root looks like X:\, and it's not
252 |  |  |  |  |  |  | #entirely unlikely that a secondary drive or a SUBST drive
253 |  |  |  |  |  |  | #letter contains the project root.
254 | 3 | 50 |  |  |  | 400 | $dirProject eq "/" and return undef;
255 |  |  |  |  |  |  |
256 | 3 |  |  |  |  | 27 | return $pkg->new(
257 |  |  |  |  |  |  | oPerlySense => $oPerlySense,
258 |  |  |  |  |  |  | dirProjectImplicitDir => $dirProject,
259 |  |  |  |  |  |  | );
260 |  |  |  |  |  |  | }
261 |  |  |  |  |  |  | }
262 |  |  |  |  |  |  |
263 | 2 |  |  |  |  | 14 | return(undef);
264 |  |  |  |  |  |  | }
265 |  |  |  |  |  |  |
266 |  |  |  |  |  |  |
267 |  |  |  |  |  |  |
268 |  |  |  |  |  |  |
269 |  |  |  |  |  |  |
270 |  |  |  |  |  |  | =head2 newFindExplicit(dir => $dir, oPerlySense => $oPs)
271 |  |  |  |  |  |  |
272 |  |  |  |  |  |  | Create new Project if there is an explicit .PerlySenseProject
273 |  |  |  |  |  |  | directory in the path above $dir.
274 |  |  |  |  |  |  |
275 |  |  |  |  |  |  | Return the new object, or undef if no project could be found.
276 |  |  |  |  |  |  |
277 |  |  |  |  |  |  | =cut
278 |  |  |  |  |  |  | sub newFindExplicit(@) {
279 | 54 |  |  | 54 | 1 | 580 | my $pkg = shift;
280 | 54 |  |  |  |  | 253 | my ($oPerlySense, $dir) = Devel::PerlySense::Util::aNamedArg(["oPerlySense", "dir"], @_);
281 |  |  |  |  |  |  |
282 | 54 | 100 |  |  |  | 313 | if(my $dirProject = $oPerlySense->dirFindLookingAround(
283 |  |  |  |  |  |  | ".PerlySenseProject",
284 |  |  |  |  |  |  | $dir,
285 |  |  |  |  |  |  | ["."],
286 |  |  |  |  |  |  | )) {
287 | 52 |  |  |  |  | 2874 | return $pkg->new(
288 |  |  |  |  |  |  | oPerlySense => $oPerlySense,
289 |  |  |  |  |  |  | dirProjectExplicitDir => $dirProject,
290 |  |  |  |  |  |  | oConfig => Devel::PerlySense::Config::Project->new(dirRoot => $dirProject),
291 |  |  |  |  |  |  | );
292 |  |  |  |  |  |  | }
293 |  |  |  |  |  |  |
294 | 2 |  |  |  |  | 18 | return undef;
295 |  |  |  |  |  |  | }
296 |  |  |  |  |  |  |
297 |  |  |  |  |  |  |
298 |  |  |  |  |  |  |
299 |  |  |  |  |  |  |
300 |  |  |  |  |  |  |
301 |  |  |  |  |  |  | =head1 MEHTODS
302 |  |  |  |  |  |  |
303 |  |  |  |  |  |  | =head2 rhRunFile(file => $fileSource, [rhConfigType = DEDUCED_FROM_FILE], [ keyConfigCommand = "command" ])
304 |  |  |  |  |  |  |
305 |  |  |  |  |  |  | Like rhRunFile0, but with what => "run".
306 |  |  |  |  |  |  |
307 |  |  |  |  |  |  | =cut
308 | 4 |  |  | 4 | 1 | 18 | sub rhRunFile {
309 | 4 |  |  |  |  | 12 | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
310 | 4 |  |  |  |  | 9 | my %p = @_;
311 | 4 |  |  |  |  | 14 | return( $self->rhRunFile0(%p, what => "run") );
312 |  |  |  |  |  |  | }
313 |  |  |  |  |  |  |
314 |  |  |  |  |  |  |
315 |  |  |  |  |  |  |
316 |  |  |  |  |  |  |
317 |  |  |  |  |  |  |
318 |  |  |  |  |  |  | =head2 rhDebugFile(file => $fileSource, [rhConfigType = DEDUCED_FROM_FILE], [ keyConfigCommand = "command" ])
319 |  |  |  |  |  |  |
320 |  |  |  |  |  |  | Like rhRunFile0, but with what => "debug".
321 |  |  |  |  |  |  |
322 |  |  |  |  |  |  | =cut
323 |  |  |  |  |  |  | ### XXX refactor, DRY with rhRunFile
324 | 1 |  |  | 1 | 1 | 5 | sub rhDebugFile {
325 | 1 |  |  |  |  | 5 | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
326 | 1 |  |  |  |  | 3 | my %p = @_;
327 | 1 |  |  |  |  | 6 | return( $self->rhRunFile0(%p, what => "debug") );
328 |  |  |  |  |  |  | }
329 |  |  |  |  |  |  |
330 |  |  |  |  |  |  |
331 |  |  |  |  |  |  |
332 |  |  |  |  |  |  |
333 |  |  |  |  |  |  |
334 |  |  |  |  |  |  | =head2 rhRunFile0(file => $fileSource, what => "run" | "debug", [rhConfigType = DEDUCED_FROM_FILE], [ keyConfigCommand = "command" ])
335 |  |  |  |  |  |  |
336 |  |  |  |  |  |  | Figure out what type of source file $fileSource is, and how it should
337 |  |  |  |  |  |  | be run/debugged.
338 |  |  |  |  |  |  |
339 |  |  |  |  |  |  | The settings in the global config->{$what_file} is used to determine
340 |  |  |  |  |  |  | the details.
341 |  |  |  |  |  |  |
342 |  |  |  |  |  |  | The command to use is taken from
343 |  |  |  |  |  |  | config->{$what_file}->{$keyConfigCommand}, if that is specified,
344 |  |  |  |  |  |  | otherwise for "command".
345 |  |  |  |  |  |  |
346 |  |  |  |  |  |  | Return hash ref with (keys: "dir_$what_from", "command_$what",
347 |  |  |  |  |  |  | "type_source_file"), or die on errors (like if no Project could be
348 |  |  |  |  |  |  | found).
349 |  |  |  |  |  |  |
350 |  |  |  |  |  |  | dir_$what_from is an absolute directory name which should be the cwd
351 |  |  |  |  |  |  | when command_$what is executed.
352 |  |  |  |  |  |  |
353 |  |  |  |  |  |  | type_source_file is something like "Test", "Module".
354 |  |  |  |  |  |  |
355 |  |  |  |  |  |  | =cut
356 | 5 |  |  | 5 | 1 | 5 | sub rhRunFile0 {
357 | 5 |  |  |  |  | 16 | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
358 | 5 |  |  |  |  | 13 | my %p = @_;
359 | 5 |  |  |  |  | 8 | my $rhConfigType = $p{rhConfigType};
360 | 5 |  |  |  |  | 6 | my $what = $p{what};
361 | 5 |  | 100 |  |  | 20 | my $keyConfigCommand = $p{keyConfigCommand} || "command";
362 |  |  |  |  |  |  |
363 | 5 |  |  |  |  | 9 | $file = file($file)->absolute;
364 | 5 |  | 33 |  |  | 1272 | $rhConfigType ||= $self->rhConfigTypeForFile(
365 |  |  |  |  |  |  | file => $file,
366 |  |  |  |  |  |  | keyConfig => "${what}_file",
367 |  |  |  |  |  |  | );
368 |  |  |  |  |  |  |
369 | 5 |  |  |  |  | 199 | my $dirProject = dir($self->dirProject)->absolute;
370 |  |  |  |  |  |  | my %hTypeDirRunFrom = (
371 | 3 |  |  | 3 |  | 5 | source_root_directory => sub { $dirProject },
372 | 2 |  |  | 2 |  | 6 | file_directory => sub { $file->dir },
373 | 5 |  |  |  |  | 486 | );
374 | 5 |  |  |  |  | 13 | my $typeRunFrom = $rhConfigType->{"${what}_from"};
375 | 5 | 50 |  |  |  | 12 | my $rsDirRunFrom = $hTypeDirRunFrom{$typeRunFrom} or die("Invalid ${what}_from value ($typeRunFrom)\n" . Dumper($rhConfigType) . "Allowed values: (" . join(", ", (sort keys %hTypeDirRunFrom)) . ")\n");
376 | 5 |  |  |  |  | 7 | my $dirRunFrom = $rsDirRunFrom->();
377 |  |  |  |  |  |  |
378 | 5 |  |  |  |  | 18 | my @aDirIncProject = $self->aDirIncProject(dirRelativeTo => $dirRunFrom);
379 | 5 |  |  |  |  | 11 | my $optionInc = join(" ", map { qq|"-I$_"| } @aDirIncProject);
| 19 |  |  |  |  | 30 |
380 |  |  |  |  |  |  |
381 | 5 |  |  |  |  | 23 | my $fileSource = $file->relative($dirRunFrom);
382 | 5 | 50 | 33 |  |  | 1584 | my $command
383 |  |  |  |  |  |  | = $rhConfigType->{ $keyConfigCommand }
384 |  |  |  |  |  |  | || $rhConfigType->{command}
385 |  |  |  |  |  |  | or die("Could not find a config value for which 'command' to run\n");
386 | 5 |  |  |  |  | 12 | my $commandRun = textRenderTemplate(
387 |  |  |  |  |  |  | $command,
388 |  |  |  |  |  |  | {
389 |  |  |  |  |  |  | INC => $optionInc,
390 |  |  |  |  |  |  | SOURCE_FILE => $fileSource . "",
391 |  |  |  |  |  |  | },
392 |  |  |  |  |  |  | );
393 |  |  |  |  |  |  |
394 | 5 |  |  |  |  | 23 | my $rhConfigRun = {
395 |  |  |  |  |  |  | "dir_${what}_from" => $dirRunFrom . "",
396 |  |  |  |  |  |  | "command_${what}" => $commandRun,
397 |  |  |  |  |  |  | type_source_file => $rhConfigType->{moniker},
398 |  |  |  |  |  |  | };
399 | 5 |  |  |  |  | 180 | return($rhConfigRun);
400 |  |  |  |  |  |  | }
401 |  |  |  |  |  |  |
402 |  |  |  |  |  |  |
403 |  |  |  |  |  |  |
404 |  |  |  |  |  |  |
405 |  |  |  |  |  |  |
406 |  |  |  |  |  |  | =head2 rhConfigTypeForFile(file => $fileSource, keyConfig => CONFIG_KEY)
407 |  |  |  |  |  |  |
408 |  |  |  |  |  |  | Return the config type hash ref (keys: command, moniker) from the ones
409 |  |  |  |  |  |  | available in the config. Match the $fileSource name against each rex
410 |  |  |  |  |  |  | in the config.
411 |  |  |  |  |  |  |
412 |  |  |  |  |  |  | Die if no configType could be identified.
413 |  |  |  |  |  |  |
414 |  |  |  |  |  |  | =cut
415 | 19 |  |  | 19 | 1 | 20 | sub rhConfigTypeForFile {
416 | 19 |  |  |  |  | 55 | my ($file, $keyConfig) = Devel::PerlySense::Util::aNamedArg(["file", "keyConfig"], @_);
417 |  |  |  |  |  |  |
418 | 19 |  |  |  |  | 483 | my $rhConfig = $self->oPerlySense->rhConfig;
419 | 19 |  |  |  |  | 583 | for my $rhConfigType (@{ $rhConfig->{ $keyConfig } }) {
| 19 |  |  |  |  | 30 |
420 | 33 | 100 |  |  |  | 62 | my $rex = $rhConfigType->{rex}
421 |  |  |  |  |  |  | or die("Missing rex key in config chunk: " . Dumper($rhConfigType));
422 |  |  |  |  |  |  |
423 | 31 | 100 |  |  |  | 23 | eval { $file =~ /$rex/ } and return $rhConfigType;
| 31 |  |  |  |  | 280 |
424 | 18 | 100 |  |  |  | 176 | $@ and die("Invalid rex value in config chunk.\nError: $@\n" . Dumper($rhConfigType));
425 |  |  |  |  |  |  | }
426 |  |  |  |  |  |  |
427 | 2 |  |  |  |  | 14 | die("No run_perl rex matched the file ($file). Please check your config\n");
428 |  |  |  |  |  |  | }
429 |  |  |  |  |  |  |
430 |  |  |  |  |  |  |
431 |  |  |  |  |  |  |
432 |  |  |  |  |  |  |
433 |  |  |  |  |  |  |
434 |  |  |  |  |  |  | =head2 aDirIncProject(dirRelativeTo => $dirRelativeTo)
435 |  |  |  |  |  |  |
436 |  |  |  |  |  |  | Return array with dir objects which are the inc_dir directories in the
437 |  |  |  |  |  |  | config, plus the usual inc directories. They are all relative to
438 |  |  |  |  |  |  | $dirRelativeTo.
439 |  |  |  |  |  |  |
440 |  |  |  |  |  |  | =cut
441 | 717 |  |  | 717 | 1 | 9645 | sub aDirIncProject {
442 | 717 |  |  |  |  | 2319 | my ($dirRelativeTo) = Devel::PerlySense::Util::aNamedArg(["dirRelativeTo"], @_);
443 |  |  |  |  |  |  |
444 | 717 |  |  |  |  | 1796 | my $dirProject = dir($self->dirProject)->absolute;
445 | 717 |  |  |  |  | 54201 | my $dirProjectRelativeTo = $dirProject->relative( $dirRelativeTo );
446 |  |  |  |  |  |  |
447 |  |  |  |  |  |  | ###TODO: extract method for the inc_dir config entry aIncDirConfig
448 | 717 | 50 |  |  |  | 100265 | my @aDirIncProject = @{ $self->oPerlySense->rhConfig->{project}->{inc_dir} || [] };
| 717 |  |  |  |  | 23901 |
449 | 717 |  |  |  |  | 26280 | my @aDirInc = (@aDirIncProject, ".", "lib");
450 |  |  |  |  |  |  |
451 | 1443 |  |  |  |  | 54948 | my @aDirIncRelative =
452 | 717 |  |  |  |  | 1148 | map { dir($dirProjectRelativeTo, $_) . "" }
453 |  |  |  |  |  |  | @aDirInc;
454 |  |  |  |  |  |  |
455 | 717 |  |  |  |  | 55292 | return @aDirIncRelative;
456 |  |  |  |  |  |  | }
457 |  |  |  |  |  |  |
458 |  |  |  |  |  |  |
459 |  |  |  |  |  |  |
460 |  |  |  |  |  |  |
461 |  |  |  |  |  |  |
462 |  |  |  |  |  |  | =head2 aDirIncAbsolute()
463 |  |  |  |  |  |  |
464 |  |  |  |  |  |  | Return array with absolute dir objects which are the inc_dir
465 |  |  |  |  |  |  | directories in the config, plus the usual inc directories.
466 |  |  |  |  |  |  |
467 |  |  |  |  |  |  | =cut
468 | 534 |  |  | 534 | 1 | 3276 | sub aDirIncAbsolute {
469 | 534 |  |  |  |  | 1930 | my $dirProject = dir($self->dirProject)->absolute;
470 |  |  |  |  |  |  |
471 |  |  |  |  |  |  | ###TODO: extract method for the inc_dir config entry aIncDirConfig
472 | 534 | 50 |  |  |  | 14154 | my @aDirIncProject = @{
473 | 534 |  |  |  |  | 39243 | $self->oPerlySense->rhConfig->{project}->{inc_dir} || []
474 |  |  |  |  |  |  | };
475 | 534 |  |  |  |  | 19750 | my @aDirInc = (@aDirIncProject, ".", "lib");
476 |  |  |  |  |  |  |
477 | 1071 |  |  |  |  | 54332 | my @aDirIncAbsolute =
478 | 534 |  |  |  |  | 875 | map { dir($dirProject, $_)->absolute . "" }
479 |  |  |  |  |  |  | @aDirInc;
480 | 534 |  |  |  |  | 56687 | return @aDirIncAbsolute;
481 |  |  |  |  |  |  | }
482 |  |  |  |  |  |  |
483 |  |  |  |  |  |  |
484 |  |  |  |  |  |  |
485 |  |  |  |  |  |  |
486 |  |  |  |  |  |  |
487 |  |  |  |  |  |  | =head2 isFileInProject(file => $file)
488 |  |  |  |  |  |  |
489 |  |  |  |  |  |  | Return true if $file is within the project root, or in any of the INC
490 |  |  |  |  |  |  | directories, else false. Die on errors.
491 |  |  |  |  |  |  |
492 |  |  |  |  |  |  | Test logically/structurally, not whether the file actually exists.
493 |  |  |  |  |  |  |
494 |  |  |  |  |  |  | =cut
495 | 7 |  |  | 7 | 1 | 17 | sub isFileInProject {
496 | 7 |  |  |  |  | 28 | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
497 |  |  |  |  |  |  |
498 | 15 |  |  |  |  | 695 | my @aDirInc =
499 | 7 | 50 |  |  |  | 186 | map { dir($self->dirProject, $_) }
500 | 7 |  |  |  |  | 13 | @{ $self->oPerlySense->rhConfig->{project}->{inc_dir} || [] };
501 | 22 |  |  |  |  | 2758 | my @aDirSourceAbsolute =
502 | 7 |  |  |  |  | 322 | map { dir($_)->absolute }
503 |  |  |  |  |  |  | ($self->dirProject, @aDirInc);
504 |  |  |  |  |  |  |
505 |  |  |  |  |  |  | ###TODO: Comment out debug statements when that failing test in t/PerlySense-Project-file-in-project.t is fixed
506 | 7 |  |  |  |  | 1063 | debug("\nTESTING WHETHER FILE\n($file) IS IN PROJECT\n(" . $self->dirProject . "),\ni.e.\n(" . join(")\n(", @aDirSourceAbsolute) . ")\n");
507 | 7 |  |  |  |  | 20 | my @aDirProjectRegex = map { qr/^ \Q$_\E /x } @aDirSourceAbsolute;
| 22 |  |  |  |  | 529 |
508 |  |  |  |  |  |  |
509 | 7 |  |  |  |  | 249 | my $dirFileAbsolute = dir( filePathNormalize( file($file)->absolute->dir ) );
510 | 7 |  |  |  |  | 1150 | debug("DIR ABSOLUTE ($dirFileAbsolute)\n");
511 |  |  |  |  |  |  |
512 | 7 |  |  |  |  | 15 | for my $dirProject (map { dir(filePathNormalize($_)) } @aDirSourceAbsolute) {
| 22 |  |  |  |  | 2753 |
513 | 16 |  |  |  |  | 3381 | debug("Checking whether\n($dirFileAbsolute) is within\n($dirProject)\n");
514 | 16 | 100 |  |  |  | 47 | $dirProject->subsumes($dirFileAbsolute) and debug("Found it"), return 1;
515 |  |  |  |  |  |  | }
516 |  |  |  |  |  |  |
517 | 3 |  |  |  |  | 987 | debug("FILE ($file) is NOT in the Project (" . $self->dirProject . ")\n");
518 | 3 |  |  |  |  | 720 | return 0;
519 |  |  |  |  |  |  | }
520 |  |  |  |  |  |  |
521 |  |  |  |  |  |  |
522 |  |  |  |  |  |  |
523 |  |  |  |  |  |  |
524 |  |  |  |  |  |  |
525 |  |  |  |  |  |  | =head2 flymakeFile(file => $fileSource)
526 |  |  |  |  |  |  |
527 |  |  |  |  |  |  | Do a flymake run with $fileSource according to the flymake config and
528 |  |  |  |  |  |  | output the result to STDOUT and STDERR.
529 |  |  |  |  |  |  |
530 |  |  |  |  |  |  | Return 1 or die on errors (but that will look like the result of a
531 |  |  |  |  |  |  | failed flymake run).
532 |  |  |  |  |  |  |
533 |  |  |  |  |  |  | =cut
534 |  |  |  |  |  |  |
535 | 0 |  |  | 0 | 1 |  | sub flymakeFile {
536 | 0 |  |  |  |  |  | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
537 |  |  |  |  |  |  |
538 | 0 | 0 |  |  |  |  | if($self->oPerlySense->rhConfig->{external}{editor}{emacs}{flymake}{syntax}) {
539 | 0 |  |  |  |  |  | my $rhConfigRun = $self->rhRunFile(
540 |  |  |  |  |  |  | file => $file,
541 |  |  |  |  |  |  | rhConfigType => {
542 |  |  |  |  |  |  | command => q{perl -c ${INC} "${SOURCE_FILE}" 2>&1 | perl -ne " /Subroutine [\\w:]+ redefined at/ or print"},
543 |  |  |  |  |  |  | moniker => "Flymake",
544 |  |  |  |  |  |  | rex => "",
545 |  |  |  |  |  |  | run_from => "file_directory",
546 |  |  |  |  |  |  | },
547 |  |  |  |  |  |  | );
548 |  |  |  |  |  |  |
549 | 0 |  |  |  |  |  | local $CWD = $rhConfigRun->{dir_run_from};
550 | 0 |  |  |  |  |  | system( $rhConfigRun->{command_run} );
551 |  |  |  |  |  |  | }
552 |  |  |  |  |  |  |
553 | 0 | 0 |  |  |  |  | if($self->oPerlySense->rhConfig->{external}{editor}{emacs}{flymake}{critic}) {
554 |  |  |  |  |  |  | ###TODO: don't run if syntax errors found
555 |  |  |  |  |  |  |
556 | 0 |  |  |  |  |  | my $fileConfigCritic = file(
557 |  |  |  |  |  |  | dir($self->dirProject)->absolute, ".PerlySenseProject", ".perlcritic",
558 |  |  |  |  |  |  | );
559 |  |  |  |  |  |  |
560 | 0 |  |  |  |  |  | my @aOption = (-profile => $fileConfigCritic . "");
561 | 0 | 0 |  |  |  |  | -e $fileConfigCritic or @aOption = ();
562 |  |  |  |  |  |  |
563 | 0 |  |  |  |  |  | my $oCritic = Perl::Critic->new(@aOption);
564 |  |  |  |  |  |  |
565 | 0 |  |  |  |  |  | my @aViolation = $oCritic->critique($file);
566 |  |  |  |  |  |  |
567 | 0 |  |  |  |  |  | Perl::Critic::Violation::set_format( "%m near '%r' (%e, %p) at %f line %l.\n" );
568 | 0 |  |  |  |  |  | for my $violation (@aViolation) {
569 | 0 |  |  |  |  |  | print STDERR "Warning: $violation";
570 |  |  |  |  |  |  | }
571 |  |  |  |  |  |  | }
572 |  |  |  |  |  |  |
573 | 0 |  |  |  |  |  | return 1;
574 |  |  |  |  |  |  | }
575 |  |  |  |  |  |  |
576 |  |  |  |  |  |  |
577 |  |  |  |  |  |  |
578 |  |  |  |  |  |  |
579 |  |  |  |  |  |  |
580 |  |  |  |  |  |  | =head2 rhSubCovered(file => $fileSource)
581 |  |  |  |  |  |  |
582 |  |  |  |  |  |  | Do a "covered subs" call with $fileSource.
583 |  |  |  |  |  |  |
584 |  |  |  |  |  |  | Die if Devel::CoverX::Covered isn't installed.
585 |  |  |  |  |  |  |
586 |  |  |  |  |  |  | Return hash ref with (keys: sub name; values: quality)), or an empty
587 |  |  |  |  |  |  | hash ref if no coverage info was found for $fileSource.
588 |  |  |  |  |  |  |
589 |  |  |  |  |  |  | Any subs with the same name will have their qualities combined.
590 |  |  |  |  |  |  |
591 |  |  |  |  |  |  | =cut
592 | 0 |  |  | 0 | 1 |  | sub rhSubCovered {
593 | 0 |  |  |  |  |  | my ($file) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
594 |  |  |  |  |  |  |
595 | 0 |  |  |  |  |  | local $CWD = $self->dirProject;
596 |  |  |  |  |  |  |
597 | 0 |  |  |  |  |  | eval {
598 | 0 |  |  |  |  |  | require Devel::CoverX::Covered;
599 | 0 |  |  |  |  |  | require Devel::CoverX::Covered::Db;
600 |  |  |  |  |  |  | };
601 | 0 | 0 |  |  |  |  | $@ and die("Devel::CoverX::Covered isn't installed\n");
602 |  |  |  |  |  |  |
603 |  |  |  |  |  |  | ###TODO: use dir specified in config and/or on command line
604 | 0 |  |  |  |  |  | local $CWD = $self->dirProject . "";
605 |  |  |  |  |  |  |
606 |  |  |  |  |  |  | ###TODO: Change Devel::CoverX::Covered to expose this dir properly
607 |  |  |  |  |  |  | ###and get it from there.
608 | 0 |  |  |  |  |  | my $dirDb = "covered";
609 | 0 | 0 |  |  |  |  | -d $dirDb or return( {} );
610 |  |  |  |  |  |  |
611 | 0 |  |  |  |  |  | my $db = Devel::CoverX::Covered::Db->new();
612 | 0 |  |  |  |  |  | my $fileRelative = file($file)->relative( $self->dirProject );
613 | 0 |  |  |  |  |  | my %hSubQuality;
614 | 0 |  |  |  |  |  | for my $raSubQuality ( $db->covered_subs($fileRelative) ) {
615 | 0 |  |  |  |  |  | my ($sub, $quality) = @$raSubQuality;
616 | 0 |  |  |  |  |  | $hSubQuality{$sub} += $quality;
617 |  |  |  |  |  |  | }
618 |  |  |  |  |  |  |
619 | 0 |  |  |  |  |  | return(\%hSubQuality);
620 |  |  |  |  |  |  | }
621 |  |  |  |  |  |  |
622 |  |  |  |  |  |  |
623 |  |  |  |  |  |  |
624 |  |  |  |  |  |  |
625 |  |  |  |  |  |  |
626 |  |  |  |  |  |  | =head2 raFileTestOther(file => $fileSource, [sub => $sub])
627 |  |  |  |  |  |  |
628 |  |  |  |  |  |  | Return array ref with file names of files related to $file and
629 |  |  |  |  |  |  | possibly $sub, i.e. the "other" files related $file.
630 |  |  |  |  |  |  |
631 |  |  |  |  |  |  | If $file is a source file, return test files, and vice verca.
632 |  |  |  |  |  |  |
633 |  |  |  |  |  |  | $sub is only ever active when $fileSource is a source file.
634 |  |  |  |  |  |  |
635 |  |  |  |  |  |  | Die if Devel::CoverX::Covered isn't installed.
636 |  |  |  |  |  |  |
637 |  |  |  |  |  |  | =cut
638 | 0 |  |  | 0 | 1 |  | sub raFileTestOther {
639 | 0 |  |  |  |  |  | my ($file, $sub) = Devel::PerlySense::Util::aNamedArg(["file", "sub"], @_);
640 |  |  |  |  |  |  |
641 | 0 |  |  |  |  |  | eval {
642 | 0 |  |  |  |  |  | require Devel::CoverX::Covered;
643 | 0 |  |  |  |  |  | require Devel::CoverX::Covered::Db;
644 |  |  |  |  |  |  | };
645 | 0 | 0 |  |  |  |  | $@ and die("Devel::CoverX::Covered isn't installed\n");
646 |  |  |  |  |  |  |
647 |  |  |  |  |  |  | ###TODO: verify there is a cover_db database present
648 |  |  |  |  |  |  |
649 | 0 |  |  |  |  |  | local $CWD = $self->dirProject . "";
650 |  |  |  |  |  |  |
651 | 0 |  |  |  |  |  | my $db = Devel::CoverX::Covered::Db->new();
652 |  |  |  |  |  |  |
653 | 0 |  |  |  |  |  | my $method = "source_files_covered_by"; # Default to test file
654 | 0 | 0 |  |  |  |  | $file =~ m|\.pm$| and $method = "test_files_covering"; # If a module, it's source
655 | 0 | 0 |  |  |  |  | $file =~ m|/t/| and $method = "source_files_covered_by"; # If it's in /t/, it's a test
656 | 0 | 0 |  |  |  |  | $file =~ m|\.t$| and $method = "source_files_covered_by"; # If it's a .t file, it's a test
657 |  |  |  |  |  |  | ### TODO: config with list of lib dirs and list of test dirs
658 | 0 |  |  |  |  |  | debug("raFileTestOther with method ($method)");
659 |  |  |  |  |  |  |
660 | 0 |  |  |  |  |  | my $fileRelative = file($file)->relative( $self->dirProject );
661 | 0 |  |  |  |  |  | my $raFileTestOther = [ $db->$method($fileRelative, $sub) ];
662 |  |  |  |  |  |  |
663 | 0 |  |  |  |  |  | return($raFileTestOther);
664 |  |  |  |  |  |  | }
665 |  |  |  |  |  |  |
666 |  |  |  |  |  |  |
667 |  |  |  |  |  |  |
668 |  |  |  |  |  |  |
669 |  |  |  |  |  |  |
670 |  |  |  |  |  |  | =head2 raFileProjectOther(file => $fileSource, [sub => $sub])
671 |  |  |  |  |  |  |
672 |  |  |  |  |  |  | Return array ref with file names of files corresponding to $file.
673 |  |  |  |  |  |  |
674 |  |  |  |  |  |  | Die if there is no config file.
675 |  |  |  |  |  |  |
676 |  |  |  |  |  |  | =cut
677 | 0 |  |  | 0 | 1 |  | sub raFileProjectOther {
678 | 0 |  |  |  |  |  | my ($file, $sub) = Devel::PerlySense::Util::aNamedArg(["file"], @_);
679 |  |  |  |  |  |  |
680 | 0 |  |  |  |  |  | eval {
681 | 0 |  |  |  |  |  | require File::Corresponding;
682 | 0 |  |  |  |  |  | require File::Corresponding::Config::Find;
683 |  |  |  |  |  |  | };
684 | 0 | 0 |  |  |  |  | $@ and die("File::Corresponding isn't installed\n");
685 |  |  |  |  |  |  |
686 |  |  |  |  |  |  |
687 |  |  |  |  |  |  | # Work from the project dir, so the rest of the paths will just work out
688 | 0 |  |  |  |  |  | local $CWD = $self->dirProject . "";
689 |  |  |  |  |  |  |
690 | 0 | 0 |  |  |  |  | my $fileConfig = File::Corresponding::Config::Find->new(
691 |  |  |  |  |  |  | preferred_dirs => [ dir(".PerlySenseProject"), dir(".") ],
692 |  |  |  |  |  |  | )->user_config(".corresponding_file")
693 |  |  |  |  |  |  | or die("Could not find a '.corresponding_file' config file in either 1) the .PerlySenseProject; b) the current directory; c) your home directory. See perldoc File::Corresponding for information on how to create one.\n");
694 |  |  |  |  |  |  |
695 | 0 |  |  |  |  |  | my $corresponding = File::Corresponding->new();
696 | 0 | 0 |  |  |  |  | eval { $corresponding->load_config_file($fileConfig) }
| 0 |  |  |  |  |  |
697 |  |  |  |  |  |  | or die("Could not read config file ($fileConfig): $@\n");
698 |  |  |  |  |  |  |
699 | 0 |  |  |  |  |  | my $fileRelative = file($file)->relative( $self->dirProject );
700 | 0 |  |  |  |  |  | my @aFileProjectOther = uniq(
701 | 0 |  |  |  |  |  | map { $_->file }
702 | 0 |  |  |  |  |  | @{$corresponding->corresponding( $fileRelative )}
703 |  |  |  |  |  |  | );
704 |  |  |  |  |  |  |
705 | 0 |  |  |  |  |  | return(\@aFileProjectOther);
706 |  |  |  |  |  |  | }
707 |  |  |  |  |  |  |
708 |  |  |  |  |  |  |
709 |  |  |  |  |  |  |
710 |  |  |  |  |  |  |
711 |  |  |  |  |  |  |
712 |  |  |  |  |  |  |
713 |  |  |  |  |  |  | 1;
714 |  |  |  |  |  |  |
715 |  |  |  |  |  |  |
716 |  |  |  |  |  |  |
717 |  |  |  |  |  |  |
718 |  |  |  |  |  |  |
719 |  |  |  |  |  |  | __END__
720 |  |  |  |  |  |  |
721 |  |  |  |  |  |  | =encoding utf8
722 |  |  |  |  |  |  |
723 |  |  |  |  |  |  | =head1 AUTHOR
724 |  |  |  |  |  |  |
725 |  |  |  |  |  |  | Johan Lindström, C<< <johanl[ÄT]DarSerMan.com> >>
726 |  |  |  |  |  |  |
727 |  |  |  |  |  |  | =head1 BUGS
728 |  |  |  |  |  |  |
729 |  |  |  |  |  |  | Please report any bugs or feature requests to
730 |  |  |  |  |  |  | C<bug-devel-perlysense@rt.cpan.org>, or through the web interface at
731 |  |  |  |  |  |  | L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense>.
732 |  |  |  |  |  |  | I will be notified, and then you'll automatically be notified of progress on
733 |  |  |  |  |  |  | your bug as I make changes.
734 |  |  |  |  |  |  |
735 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS
736 |  |  |  |  |  |  |
737 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE
738 |  |  |  |  |  |  |
739 |  |  |  |  |  |  | Copyright 2005 Johan Lindström, All Rights Reserved.
740 |  |  |  |  |  |  |
741 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it
742 |  |  |  |  |  |  | under the same terms as Perl itself.
743 |  |  |  |  |  |  |
744 |  |  |  |  |  |  | =cut