File Coverage

blib/lib/App/DocKnot/Config.pm
Criterion Covered Total %
statement 75 76 98.6
branch 15 16 93.7
condition n/a
subroutine 13 13 100.0
pod 2 2 100.0
total 105 107 98.1


line stmt bran cond sub pod time code
1             # Read and return DocKnot package configuration.
2             #
3             # Parses the DocKnot package configuration and provides it to other DocKnot
4             # commands.
5             #
6             # SPDX-License-Identifier: MIT
7              
8             ##############################################################################
9             # Modules and declarations
10             ##############################################################################
11              
12             package App::DocKnot::Config 3.01;
13              
14 8     8   111456 use 5.024;
  8         44  
15 8     8   35 use autodie;
  8         21  
  8         43  
16 8     8   34409 use parent qw(App::DocKnot);
  8         27  
  8         49  
17 8     8   363 use warnings;
  8         13  
  8         199  
18              
19 8     8   35 use Carp qw(croak);
  8         12  
  8         335  
20 8     8   40 use File::Spec;
  8         13  
  8         148  
21 8     8   30 use JSON;
  8         12  
  8         55  
22 8     8   693 use Perl6::Slurp;
  8         14  
  8         32  
23              
24             # Additional files to load from the metadata directory if they exist. The
25             # contents of these files will be added to the configuration in a key of the
26             # same name. If the key contains a slash, like foo/bar, it will be stored as
27             # a nested hash, as $data{foo}{bar}.
28             our @METADATA_FILES = qw(
29             bootstrap
30             build/middle
31             build/suffix
32             debian/summary
33             packaging/extra
34             support/extra
35             test/prefix
36             test/suffix
37             );
38              
39             ##############################################################################
40             # Helper methods
41             ##############################################################################
42              
43             # Internal helper routine to return the path of a file or directory from the
44             # package metadata directory. The resulting file or directory path is not
45             # checked for existence.
46             #
47             # $self - The App::DocKnot::Generate object
48             # @path - The relative path of the file as a list of components
49             #
50             # Returns: The absolute path in the metadata directory
51             sub _metadata_path {
52 620     620   1034 my ($self, @path) = @_;
53 620         5398 return File::Spec->catdir($self->{metadata}, @path);
54             }
55              
56             # Internal helper routine to read a file from the package metadata directory
57             # and return the contents. The file is specified as a list of path
58             # components.
59             #
60             # $self - The App::DocKnot::Generate object
61             # @path - The path of the file to load, as a list of components
62             #
63             # Returns: The contents of the file as a string
64             # Throws: slurp exception on failure to read the file
65             sub _load_metadata {
66 620     620   1333 my ($self, @path) = @_;
67 620         1170 return slurp($self->_metadata_path(@path));
68             }
69              
70             # Like _load_metadata, but interprets the contents of the metadata file as
71             # JSON and decodes it, returning the resulting object. This uses the relaxed
72             # parsing mode, so comments and commas after data elements are supported.
73             #
74             # $self - The App::DocKnot::Generate object
75             # @path - The path of the file to load, as a list of components
76             #
77             # Returns: Anonymous hash or array resulting from decoding the JSON object
78             # Throws: slurp or JSON exception on failure to load or decode the object
79             sub _load_metadata_json {
80 42     42   122 my ($self, @path) = @_;
81 42         150 my $data = $self->_load_metadata(@path);
82 42         7027 my $json = JSON->new;
83 42         263 $json->relaxed;
84 42         1661 return $json->decode($data);
85             }
86              
87             ##############################################################################
88             # Public Interface
89             ##############################################################################
90              
91             # Create a new App::DocKnot::Config object, which will be used for subsequent
92             # calls.
93             #
94             # $class - Class of object to create
95             # $args - Anonymous hash of arguments with the following keys:
96             # metadata - Path to the directory containing package metadata
97             #
98             # Returns: Newly created object
99             # Throws: Text exceptions on invalid metadata directory path
100             sub new {
101 22     22 1 169 my ($class, $args_ref) = @_;
102              
103             # Ensure we were given a valid metadata argument.
104 22         49 my $metadata = $args_ref->{metadata};
105 22 100       67 if (!defined($metadata)) {
106 6         11 $metadata = 'docs/metadata';
107             }
108 22 100       444 if (!-d $metadata) {
109 1         202 croak("metadata path $metadata does not exist or is not a directory");
110             }
111              
112             # Create and return the object.
113 21         87 my $self = { metadata => $metadata };
114 21         53 bless($self, $class);
115 21         67 return $self;
116             }
117              
118             # Load the DocKnot package configuration.
119             #
120             # $self - The App::DocKnot::Config object
121             #
122             # Returns: The package configuration as a dict
123             # Throws: autodie exception on failure to read metadata
124             # Text exception on inconsistencies in the package data
125             sub config {
126 42     42 1 621 my ($self) = @_;
127              
128             # Load the package metadata from JSON.
129 42         137 my $data_ref = $self->_load_metadata_json('metadata.json');
130              
131             # build.install defaults to true.
132 42 100       188 if (!exists($data_ref->{build}{install})) {
133 31         88 $data_ref->{build}{install} = 1;
134             }
135              
136             # Load supplemental README sections. readme.sections will contain a list
137             # of sections to add to the README file.
138 42         168 for my $section ($data_ref->{readme}{sections}->@*) {
139 52         86 my $title = $section->{title};
140              
141             # The file containing the section data will match the title, converted
142             # to lowercase and with spaces changed to dashes.
143 52         130 my $file = lc($title);
144 52         110 $file =~ tr{ }{-};
145              
146             # Load the section content.
147 52         126 $section->{body} = $self->_load_metadata('sections', $file);
148              
149             # If this contains a testing section, that overrides our default. Set
150             # a flag so that the templates know this has happened.
151 52 100       6470 if ($file eq 'testing') {
152 7         27 $data_ref->{readme}{testing} = 1;
153             }
154             }
155              
156             # If the package is marked orphaned, load the explanation.
157 42 100       217 if ($data_ref->{orphaned}) {
158 4         40 $data_ref->{orphaned} = $self->_load_metadata('orphaned');
159             }
160              
161             # If the package has a quote, load the text of the quote.
162 42 100       619 if ($data_ref->{quote}) {
163 18         61 $data_ref->{quote}{text} = $self->_load_metadata('quote');
164             }
165              
166             # Expand the package license into license text.
167 42         2194 my $license = $data_ref->{license};
168 42         228 my $licenses_ref = $self->load_appdata_json('licenses.json');
169 42 50       6119 if (!exists($licenses_ref->{$license})) {
170 0         0 die "Unknown license $license\n";
171             }
172 42         173 my $license_text = slurp($self->appdata_path('licenses', $license));
173 42         5398 $data_ref->{license} = { $licenses_ref->{$license}->%* };
174 42         128 $data_ref->{license}{full} = $license_text;
175              
176             # Load additional license notices if they exist.
177 42         99 eval { $data_ref->{license}{notices} = $self->_load_metadata('notices') };
  42         125  
178              
179             # Load the standard sections.
180 42         9089 $data_ref->{blurb} = $self->_load_metadata('blurb');
181 42         5252 $data_ref->{description} = $self->_load_metadata('description');
182 42         4912 $data_ref->{requirements} = $self->_load_metadata('requirements');
183              
184             # Load optional information if it exists.
185 42         4991 for my $file (@METADATA_FILES) {
186 336         41668 my @file = split(m{/}xms, $file);
187 336 100       785 if (scalar(@file) == 1) {
188 42         75 eval { $data_ref->{$file} = $self->_load_metadata(@file) };
  42         120  
189             } else {
190 294         403 eval {
191 294         646 $data_ref->{ $file[0] }{ $file[1] }
192             = $self->_load_metadata(@file);
193             };
194             }
195             }
196              
197             # Return the resulting configuration.
198 42         5427 return $data_ref;
199             }
200              
201             ##############################################################################
202             # Module return value and documentation
203             ##############################################################################
204              
205             1;
206             __END__