File Coverage

blib/lib/App/Fetchware/Fetchwarefile.pm
Criterion Covered Total %
statement 80 93 86.0
branch 35 48 72.9
condition 12 21 57.1
subroutine 10 10 100.0
pod 3 3 100.0
total 140 175 80.0


line stmt bran cond sub pod time code
1             package App::Fetchware::Fetchwarefile;
2             our $VERSION = '1.015'; # VERSION: generated by DZP::OurPkgVersion
3             # ABSTRACT: Helps Fetchware extensions create Fetchwarefiles.
4             ###BUGALERT### Uses die instead of croak. croak is the preferred way of throwing
5             #exceptions in modules. croak says that the caller was the one who caused the
6             #error not the specific code that actually threw the error.
7 35     35   2023 use strict;
  35         55  
  35         820  
8 35     35   109 use warnings;
  35         31  
  35         728  
9              
10             # Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
11             # things in 5.10 were changed in 5.10.1+.
12 35     35   520 use 5.010001;
  35         77  
13              
14 35     35   14378 use Text::Wrap 'wrap';
  35         63825  
  35         1670  
15 35     35   176 use App::Fetchware::Util 'vmsg';
  35         38  
  35         1229  
16 35     35   119 use Carp 'croak';
  35         56  
  35         27518  
17              
18              
19              
20             sub new {
21 9     9 1 2915 my ($class, %options) = @_;
22              
23 9 100 66     32 if (not exists $options{header}
24             and not defined $options{header}) {
25 1         153 croak <
26             Fetchwarefile: you failed to include a header option in your call to
27             App::Fetchware::Fetchwarefile's new() constructor. Please add the required
28             header and try again.
29             EOC
30             # Above tests if $options{header} does not exist and is not defined, so this
31             # else means that it does indeed exist and is defined.
32             } else {
33 8 100       36 if ($options{header} !~ /^use\s+App::FetchwareX?/m) {
34 1         6 die <
35             Fetchwarefile: Your header does not have a App::Fetchware or App::FetchwareX::*
36             extension declaration. This line is manadatory, and Fetchware requires it,
37             because it needs it to load its or its extensions's configuration subroutines.
38             The erroneous header you provided is:
39             [
40             $options{header}
41             ]
42             EOD
43             }
44             }
45 7 50 66     13 if (not exists $options{descriptions}
46             and not defined $options{descriptions}) {
47 1         86 croak <
48             Fetchwarefile: you failed to include a descriptions hash option in your call to
49             App::Fetchware::Fetchwarefile's new() constructor. Please add the required
50             header and try again.
51             EOC
52             }
53 6 100       14 if (ref $options{descriptions} ne 'HASH') {
54 1         83 croak <
55             Fetchwarefile: the descriptions hash value must be a hash ref whoose keys are
56             configuration options, and whoose values are descriptions to insert into the
57             generated Fetchwarefile when those options are added to your Fetchwarefile.
58             EOC
59             }
60              
61              
62             # Initialize order as instance data. This variable is used by generate() to
63             # track the order as config options are added to the $fetchwarefile object.
64 5         5 $options{order} = 1;
65              
66 5         11 return bless \%options, $class;
67             }
68              
69              
70              
71             sub config_options {
72 18     18 1 2581 my $self = shift;
73              
74             # If only one option is provided, then config_options() is a getter, and
75             # should return that one value back to the caller.
76              
77 18 100       30 if (@_ == 1) {
78             # If the requested key is a arrayref deref it, and return it...
79 8 100       16 if (ref $self->{config_options_value}->{$_[0]} eq 'ARRAY') {
80 4         5 return @{$self->{config_options_value}->{$_[0]}};
  4         15  
81             #...otherwise just return the one scalar.
82             } else {
83 4         13 return $self->{config_options_value}->{$_[0]};
84             }
85             # Otherwise config_options() is a setter, and should set the rest of its
86             # objects (@_) as the
87             } else {
88             # Store the %options inside $self's under $self's config_options_value hash key,
89             # and be sure to use an array to support 'MANY' and 'ARRREF' types.
90 10         12 my %options = @_;
91              
92 10         13 for my $config_key (keys %options) {
93              
94 12 100       19 if (ref $self->{config_options_value}->{$config_key} eq 'ARRAY') {
95 3 50       5 if (ref $options{$config_key} eq 'ARRAY') {
96 0         0 push @{$self->{config_options_value}->{$config_key}},
97 0         0 @{$options{$config_key}};
  0         0  
98             } else {
99 3         6 push @{$self->{config_options_value}->{$config_key}},
100 3         2 $options{$config_key};
101             }
102             } else {
103 9 100 66     25 if (exists $self->{config_options_value}->{$config_key}
      66        
104             and
105             defined $self->{config_options_value}->{$config_key}
106             and
107             ref $self->{config_options_value}->{$config_key} eq ''
108             ) {
109 1 50       2 if (ref $options{$config_key} eq 'ARRAY') {
110 0         0 push @{$self->{config_options_value}->{$config_key}},
111             # Prepend existing arrayref...
112 0         0 @{$self->{config_options_value}->{$config_key}},
113             # ...and the new array ref, but remember to deref it.
114 0         0 @{$options{$config_key}};
  0         0  
115             } else {
116             # Set the hash directly to the value, because if it has
117             # a scalar value, then it is not undef, and push will
118             # only autovivify the array ref if its undef; therefore,
119             # I must set the hash value to an array ref directly
120             # instead.
121             $self->{config_options_value}->{$config_key} =
122             [
123             # Prepend existing scalar...
124             $self->{config_options_value}->{$config_key},
125             # ...and the new scalar too.
126 1         3 $options{$config_key}
127             ];
128             }
129             } else {
130 8 100       10 if (ref $options{$config_key} eq 'ARRAY') {
131             $self->{config_options_value}->{$config_key} = [
132 3         2 @{$options{$config_key}}
  3         9  
133             ];
134             } else {
135 5         7 $self->{config_options_value}->{$config_key} = $options{$config_key};
136             }
137             }
138             }
139             }
140              
141             # Store the order that this $config_key was stored in
142             # config_options_value in it's parallel hash config_options_order...
143             # Copied and pasted from code by brian d foy from Stack Overflow:
144             # http://stackoverflow.com/questions/569772
145 10         19 for (my $i = 0; $i < @_; $i += 2) {
146 12         15 my ($option_name, $option_value) = @_[ $i, $i+1 ];
147              
148             $self->{config_options_order}->{$option_name} = $self->{order}++
149 12 100       42 unless exists $self->{config_options_order}->{$option_name};
150             }
151             }
152             }
153              
154              
155              
156              
157             sub generate {
158 1     1 1 4 my $self = shift;
159              
160             # Stores the Fetchwarefile that we're generating for our caller.
161 1         2 my $fetchwarefile;
162              
163             # First add the header to the $fetchwarefile.
164 1         2 $fetchwarefile .= $self->{header};
165              
166             # Add a newline or 2 if needed.
167 1 50       4 unless ($fetchwarefile =~ /(\n)(\n)$/) {
168 1 50 33     3 $fetchwarefile .= "\n" if defined($1) and $1 eq "\n";
169 1 50 33     5 $fetchwarefile .= "\n" if defined($2) and $2 eq "\n";
170             }
171              
172             # Ensure that $self->{config_options_values} and
173             # $self->{config_options_order} parallel hashes have the same number of
174             # keys.
175              
176 1 50       1 unless (
177 1         3 keys %{$self->{config_options_value}}
178             ==
179 1         3 keys %{$self->{config_options_order}}
180             ) {
181 0         0 die <
182             App-Fetchware-Fetchwarefile: your call to generate() failed, because the data
183             that generate() uses internally is somehow screwed up. This is probably a bug,
184             because App::Fetchware::Fetchwarefile's internals are not supposed to be messed
185             with except by itself of course.
186             EOD
187             }
188              
189             # Tracks how many times each Fetchwarefile configuration option is used, so
190             # that each options description is only put in the Fetchwarefile only once.
191 1         1 my %description_seen;
192              
193             # Loop over all the keys that were added with config_options(), which are
194             # stored in config_options_value, but use config_options_order to sort them,
195             # which stores the order in which the first value was added for each like
196             # key in config_options_value.
197 1         1 for my $option_key (sort {
198             $self->{config_options_order}->{$a}
199             <=>
200 3         5 $self->{config_options_order}->{$b}
201             }
202 1         3 keys %{$self->{config_options_value}}
203             ) {
204             # Due to Fetchwarefile storing each option as an array, and
205             # config_option() returning that array, which may consist of only one
206             # value, I need to loop through them just in case a 'MANY' or
207             # 'ONEARRREF' type configuration option is used.
208 3         7 for my $option_value ($self->config_options($option_key)) {
209 4 50       8 if (defined $self->{descriptions}->{$option_key}) {
210             # If the description has not been written to the $fetchwarefile yet,
211             # then include it.
212 4 100 66     16 unless (exists $description_seen{$option_key}
213             and defined $description_seen{$option_key}
214             and $description_seen{$option_key} > 0
215             ) {
216             _append_to_fetchwarefile(\$fetchwarefile, $option_key,
217             $option_value,
218 3         4 $self->{descriptions}->{$option_key});
219             # Otherwise avoid duplicating the description.
220             } else {
221 1         2 _append_to_fetchwarefile(\$fetchwarefile, $option_key,
222             $option_value);
223             }
224 4         14 vmsg <
225             Appended [$option_key] configuration option [$option_value] to Fetchwarefile.
226             EOM
227             } else {
228 0         0 die <
229             fetchware: fetchwarefile() was called to generate the Fetchwarefile you have
230             created using append_options_to_fetchwarefile(), but it has options in it that
231             do not have a description to add to the Fetchwarefile. Please add a description
232             to your call to fetchwarefile_config_options() for the option [$option_key].
233             EOD
234             }
235             # Increment this for each time each $option_key is written to the
236             # $fetchwarefile to ensure that only on the very first time the
237             # $option_key is written to the $fetchwarefile that its
238             # description is also written.
239 4         6 $description_seen{$option_key}++;
240             }
241             }
242 1         5 return $fetchwarefile;
243             }
244              
245              
246             # It's an "_" internal subroutine, so don't publish its POD.
247             #=head3 _append_to_fetchwarefile()
248             #
249             # _append_to_fetchwarefile(\$fetchwarefile, $config_file_option, $config_file_value, $description)
250             #
251             #Turns $description into a comment as described below, and then appends it to the
252             #$fetchwarefile. Then $config_file_option and $config_file_value are also
253             #appended inside proper Fetchwarefile syntax.
254             #
255             #$description is split into strings 78 characters long, and printed with C<# >
256             #prepended to make it a proper comment so fetchware skips parsing it.
257             #
258             #$description is optional. If you do not include it when you call
259             #_append_to_fetchwarefile(), then _append_to_fetchwarefile() will not add the
260             #provided description.
261             #
262             #=over
263             #
264             #=item NOTE
265             #Notice the backslash infront of the $fetchwarefile argument above. It is there,
266             #because the argument $fetchwarefile must be a reference to a scalar.
267             #
268             #=back
269             #
270             #=cut
271              
272             sub _append_to_fetchwarefile {
273 7     7   1113 my ($fetchwarefile,
274             $config_file_option,
275             $config_file_value,
276             $description) = @_;
277              
278 7 100       21 die <
279             fetchware: run-time error. You called _append_to_fetchwarefile() with a
280             fetchwarefile argument that is not a scalar reference. Please add the need
281             backslash reference operator to your call to _append_to_fetchwarefile() and try
282             again.
283             EOD
284              
285              
286             # Only add a $description if we were called with one.
287 6 100       9 if (defined $description) {
288             # Append a double newline for easier reading, but only when we print a
289             # new $description, which implies we're switching to a new configuration
290             # option.
291 5         6 $$fetchwarefile .= "\n\n";
292              
293             # Append a newline to $description if it doesn't have one already.
294 5 100       14 $description .= "\n" unless $description =~ /\n$/;
295             # Change wrap() to wrap at 80 columns instead of 76.
296 5         5 local $Text::Wrap::columns = 81;
297             # Use Text::Wrap's wrap() to split $description up
298 5         8 $$fetchwarefile .= wrap('# ', '# ', $description);
299             }
300              
301             # This simple chunk of regexes provide trivial and buggy support for
302             # ONEARRREFs. This support simply causes fetchware to avoid adding any
303             # characters that are needed for proper Perl syntax if the user has provided
304             # those characters for us.
305 6 50       916 if ($config_file_value =~ /('|")/) {
306 0         0 $$fetchwarefile .= "$config_file_option $config_file_value";
307              
308 0 0       0 if ($config_file_value =~ /[^;]$/) {
    0          
309 0         0 $$fetchwarefile .= ";";
310             } elsif ($config_file_value =~ /[^\n]$/) {
311 0         0 $$fetchwarefile .= "\n";
312             }
313             } else {
314 6         14 $$fetchwarefile .= "$config_file_option '$config_file_value';\n";
315             }
316             }
317              
318              
319             1;
320              
321             __END__