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.02;
13              
14 8     8   104517 use 5.024;
  8         37  
15 8     8   37 use autodie;
  8         14  
  8         43  
16 8     8   33770 use parent qw(App::DocKnot);
  8         30  
  8         49  
17 8     8   357 use warnings;
  8         14  
  8         198  
18              
19 8     8   34 use Carp qw(croak);
  8         15  
  8         546  
20 8     8   40 use File::Spec;
  8         23  
  8         150  
21 8     8   32 use JSON;
  8         12  
  8         52  
22 8     8   685 use Perl6::Slurp;
  8         22  
  8         34  
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   998 my ($self, @path) = @_;
53 620         5303 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   1350 my ($self, @path) = @_;
67 620         1151 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   169 my ($self, @path) = @_;
81 42         136 my $data = $self->_load_metadata(@path);
82 42         6933 my $json = JSON->new;
83 42         229 $json->relaxed;
84 42         1503 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         47 my $metadata = $args_ref->{metadata};
105 22 100       63 if (!defined($metadata)) {
106 6         14 $metadata = 'docs/metadata';
107             }
108 22 100       458 if (!-d $metadata) {
109 1         203 croak("metadata path $metadata does not exist or is not a directory");
110             }
111              
112             # Create and return the object.
113 21         93 my $self = { metadata => $metadata };
114 21         46 bless($self, $class);
115 21         64 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 591 my ($self) = @_;
127              
128             # Load the package metadata from JSON.
129 42         146 my $data_ref = $self->_load_metadata_json('metadata.json');
130              
131             # build.install defaults to true.
132 42 100       163 if (!exists($data_ref->{build}{install})) {
133 31         79 $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         175 for my $section ($data_ref->{readme}{sections}->@*) {
139 52         98 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         104 my $file = lc($title);
144 52         106 $file =~ tr{ }{-};
145              
146             # Load the section content.
147 52         120 $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       6531 if ($file eq 'testing') {
152 7         24 $data_ref->{readme}{testing} = 1;
153             }
154             }
155              
156             # If the package is marked orphaned, load the explanation.
157 42 100       187 if ($data_ref->{orphaned}) {
158 4         37 $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       629 if ($data_ref->{quote}) {
163 18         50 $data_ref->{quote}{text} = $self->_load_metadata('quote');
164             }
165              
166             # Expand the package license into license text.
167 42         2189 my $license = $data_ref->{license};
168 42         212 my $licenses_ref = $self->load_appdata_json('licenses.json');
169 42 50       5885 if (!exists($licenses_ref->{$license})) {
170 0         0 die "Unknown license $license\n";
171             }
172 42         156 my $license_text = slurp($self->appdata_path('licenses', $license));
173 42         5556 $data_ref->{license} = { $licenses_ref->{$license}->%* };
174 42         129 $data_ref->{license}{full} = $license_text;
175              
176             # Load additional license notices if they exist.
177 42         84 eval { $data_ref->{license}{notices} = $self->_load_metadata('notices') };
  42         130  
178              
179             # Load the standard sections.
180 42         8508 $data_ref->{blurb} = $self->_load_metadata('blurb');
181 42         5127 $data_ref->{description} = $self->_load_metadata('description');
182 42         5010 $data_ref->{requirements} = $self->_load_metadata('requirements');
183              
184             # Load optional information if it exists.
185 42         5153 for my $file (@METADATA_FILES) {
186 336         41219 my @file = split(m{/}xms, $file);
187 336 100       822 if (scalar(@file) == 1) {
188 42         82 eval { $data_ref->{$file} = $self->_load_metadata(@file) };
  42         115  
189             } else {
190 294         403 eval {
191 294         603 $data_ref->{ $file[0] }{ $file[1] }
192             = $self->_load_metadata(@file);
193             };
194             }
195             }
196              
197             # Return the resulting configuration.
198 42         5440 return $data_ref;
199             }
200              
201             ##############################################################################
202             # Module return value and documentation
203             ##############################################################################
204              
205             1;
206             __END__