File Coverage

blib/lib/Test/Subunits.pm
Criterion Covered Total %
statement 70 74 94.5
branch 19 22 86.3
condition 7 9 77.7
subroutine 15 15 100.0
pod n/a
total 111 120 92.5


line stmt bran cond sub pod time code
1             package Test::Subunits;
2              
3 11     11   85203 use 5.010;
  11         29  
  11         296  
4 11     11   39 use strict;
  11         17  
  11         279  
5 11     11   34 use warnings;
  11         19  
  11         1852  
6              
7             our $VERSION = '0.000003';
8              
9             sub import {
10             # Client code passes in filename or module name...
11 14     14   99 our (undef, $source) = @_;
12              
13 14 100 66     86 if (!defined $source || !length($source)) {
14 1         4 _croak( q{No argument supplied to 'use Test::Subunits'} );
15             }
16              
17             # Locate file in library path...
18 13         27 $source = _locate($source);
19              
20             # Extract source code...
21 13         61 my $source_code = _slurp($source);
22              
23             # Accumulate code to be extracted, while tracking line numbers...
24 13         15 my $extracted_code = q{};
25 13         32 my $line_number = 1;
26              
27             # Locate and isolate subunits...
28 13         99 $source_code =~ s{
29             \G ^ (?&ws)
30             (?:
31             # A bracketed subunit...
32             (? \#\#\{ )
33             (?: # Optional subroutine wrapper specification...
34             (?&ws) (? (?&ident) )
35             (?&ws) (? (?¶mlist) )
36             (?: (?&ws) --> (? (?&whatever) ) )?
37             )?
38             (?&ws) \n
39             (?
40             .*?
41             ^ (?&ws) \#\#\} (?&ws) (?&eol)
42             )
43             |
44             # A paragraphed subunit...
45             (? \#\#: )
46             (?: # Optional subroutine wrapper specification...
47             (?&ws) (? (?&ident) )
48             (?&ws) (? (?¶mlist) )
49             (?: (?&ws) --> (? (?&whatever) ) )?
50             )?
51             (?&ws) \n
52             (?
53             .*?
54             ^ (?&ws) (?&eol)
55             )
56             |
57             # Catch junk after opening delimiters...
58             (? \#\#[:\{] (?&ws) \S (?&whatever) ) (?&eol)
59             |
60             # Catch unmatched delimiters...
61             (? \#\#[{}] ) (?&whatever) (?&eol)
62             |
63             # Catch unknown delimiters...
64             (? \#\#\S ) (?&whatever) (?&eol)
65             |
66             # One-or-more consecutive single-line subunits...
67             (? \#\# )
68             (?
69             (?&whatever) (?&eol)
70             (?: ^ (?&ws) \#\# (?=\s) (?&whatever) (?&eol) )*+
71             )
72             |
73             # Ignore anything else...
74             (?&whatever) (?&eol)
75             )
76              
77             (?(DEFINE)
78             (?
79             \( (?&ws) (?&var) (?: (?&ws) , (?&ws) (?&var) )*+ (?&ws) \)
80             )
81             (? [\$\@%] (?&ident) )
82             (? \h*+ )
83             (? (?-s: .*+ ) )
84             (? [^\W\d] \w*+ )
85             (? \n | \Z )
86             )
87             }{
88             # Every match consumes at least one line...
89 232         131 $line_number++;
90              
91             # Handle bad delimiters...
92 11 100   11   4557 if (exists $+{UNMATCHED_DELIM}) {
  11 50       4083  
  11 100       6236  
  232 100       2470  
93 3         6 my $where = "at $source line " . ($line_number - 1);
94 3         21 $extracted_code .= qq(BEGIN { die "Unmatched $+{UNMATCHED_DELIM} $where\n" });
95             }
96             elsif (exists $+{UNKNOWN_DELIM}) {
97 0         0 my $where = "at $source line " . ($line_number - 1);
98 0         0 $extracted_code .= qq(BEGIN { die "Unrecognized subunit marker ($+{UNKNOWN_DELIM}) $where\n" });
99             }
100             elsif (exists $+{INVALID_WRAPPER}) {
101 2         5 my $where = "at $source line " . ($line_number - 1);
102 2         13 $extracted_code .= qq(BEGIN { die "Invalid wrapper specification: $+{INVALID_WRAPPER} $where\n" });
103             }
104              
105             # Remember anything that was extracted...
106             elsif (exists $+{EXTRACTED}) {
107 42         122 my $extracted = $+{EXTRACTED};
108              
109             # Track how many lines the extracton covered...
110 42         64 my $extracted_lines = ($extracted =~ tr/\n//);
111              
112             # Wrap in a subroutine, if requested...
113 42 100       126 if (exists $+{SUBNAME}) {
114             # If no return specification, return original parameter list...
115 7   66     31 my $retexpr = $+{RETEXPR} // $+{PARAMS};
116              
117             # Build the wrapper...
118 7         38 $extracted = "sub $+{SUBNAME} { my $+{PARAMS} = \@_;\n"
119             . $extracted
120             . "return $retexpr; }\n";
121             }
122              
123             # Remember the extracted code (and where it was in the original file)...
124 42         115 $extracted_code .= " # line $line_number $source\n" . $extracted;
125              
126             # Track the extra lines that have been matched...
127 42         272 $line_number += $extracted_lines;
128             }
129             }egxms;
130              
131             # Remove any extra internal ## lines...
132 13         297 $extracted_code =~ s{ ^ \h*+ \# \#[{}\s]? }{}gxms;
133              
134             # Compile the code in the caller's namespace...
135 13         33 my $target_package = caller;
136 13 100 100 4   2041 eval qq{
  4 100   5   4961  
  5     12   5444  
  5     6   414  
  5     6   19  
  5     4   30  
  25     3   71  
  10         14  
  15         21  
  5         17  
  12         6933  
  12         63  
  12         43  
  6         7415  
  6         17  
  36         42  
  6         12  
  6         34  
  6         16389  
  6         16  
  12         45  
  4         545  
  4         11520  
137             package $target_package;
138             $extracted_code;
139             1;
140             } or die;
141             }
142              
143             sub _croak {
144 3     1   26 require Carp;
145 1         201 Carp::croak(@_);
146             }
147              
148             sub _slurp {
149 13     13   64 local (@ARGV, $/) = shift;
150 13         487 return readline();
151             }
152              
153             sub _locate {
154 13     13   17 my ($source) = @_;
155 13         16 my $orig_source = $source;
156              
157             # Convert module name to filename if necessary...
158 13 100       92 if ($source =~ m{^\w+(?:::\w+)*$}) {
159 11         21 $source =~ s{::}{/}g ;
160 11         17 $source .= '.pm';
161             }
162              
163             # Try within all the standard inclusion directories...
164 13         21 for my $path (@INC) {
165 23         46 my $file = "$path/$source";
166 23 100       387 return $file if -e $file;
167             }
168              
169             # Finally, try the exact path by itself...
170 0 0       0 return $source if -e $source;
171              
172             # Otherwise give up with extreme prejudice...
173 0         0 _croak( qq{Test::Subunits can't locate requested source file: $orig_source} );
174             }
175              
176              
177             1; # Magic true value required at end of module
178             __END__