File Coverage

blib/lib/Code/TidyAll/Plugin/YAMLFrontMatter.pm
Criterion Covered Total %
statement 49 49 100.0
branch 8 8 100.0
condition n/a
subroutine 14 14 100.0
pod 1 1 100.0
total 72 72 100.0


line stmt bran cond sub pod time code
1             package Code::TidyAll::Plugin::YAMLFrontMatter;
2              
3 1     1   24671 use strict;
  1         3  
  1         29  
4 1     1   5 use warnings;
  1         2  
  1         26  
5 1     1   419 use namespace::autoclean;
  1         8742  
  1         5  
6              
7             our $VERSION = '1.000001';
8              
9 1     1   89 use Moo;
  1         2  
  1         8  
10              
11 1     1   826 use Encode qw( decode encode FB_CROAK );
  1         7681  
  1         70  
12 1     1   8 use Path::Tiny qw( path );
  1         2  
  1         38  
13 1     1   6 use Try::Tiny qw( catch try );
  1         3  
  1         44  
14 1     1   341 use YAML::XS qw( Load );
  1         2103  
  1         469  
15              
16             extends 'Code::TidyAll::Plugin';
17              
18             # This regular expression is based on the regex
19             # \A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?) (with the m flag)
20             # from the Jekyll source code here:
21             # https://github.com/jekyll/jekyll/blob/c7d98cae2652b2df7ebd3c60b4f8c87950760e47/lib/jekyll/document.rb#L13
22             # note - The 'm' modifier in ruby is essentially the same as 's' in Perl
23             # so we need to enable the 's' modifier not 'm'
24             # - Ruby essentially always treats '^' and '$' the way Perl does when the
25             # 'm' modifier is enabled, so we need to turn that on too
26             # - We need to enable the 'x' modifier and space things out so that
27             # Perl treats '$\n' as '$' and '\n' and not the variable '$\' and 'n'
28             my $YAML_REGEX = qr{
29             \A
30             # the starting ---, and anything up until...
31             (---\s*\n.*?\n?)
32              
33             # ...the first --- or ... on their own line
34             ^ (?:---|\.\.\.) \s* $ \n?
35             }msx;
36              
37             has encoding => (
38             is => 'ro',
39              
40             # By default Jekyll 2.0 and later defaults to utf-8, so this seems
41             # like a sensible default for us
42             default => 'UTF-8',
43             );
44              
45             has required_top_level_keys => (
46             is => 'ro',
47             default => q{},
48             );
49              
50             has _req_keys_hash => ( is => 'lazy' );
51              
52             sub _build__req_keys_hash {
53 7     7   78 my $self = shift;
54             return +{
55              
56             # note use of magical split on space to do automatic trimming
57 7         45 map { $_ => 1 } split q{ }, $self->required_top_level_keys
  4         29  
58             };
59             }
60              
61             sub validate_file {
62 11     11 1 98577 my ( $self, $filename ) = @_;
63              
64 11         47 my $src = path($filename)->slurp_raw;
65              
66             # YAML::XS always expects things to be in UTF-8 bytes
67 11         1638 my $encoding = $self->encoding;
68             try {
69 11     11   540 $src = decode( $encoding, $src, FB_CROAK );
70 10         3439 $src = encode( 'UTF-8', $src, FB_CROAK );
71             }
72             catch {
73 1     1   103 die "File does not match encoding '$encoding': $_";
74 11         93 };
75              
76             # is there a BOM? There's not meant to be a BOM!
77 10 100       621 if ( $src =~ /\A\x{EF}\x{BB}\x{BF}/ ) {
78 1         19 die "Starting document with UTF-8 BOM is not allowed\n";
79             }
80              
81             # match the YAML front matter.
82 9         19 my $yaml;
83 9 100       96 unless ( ($yaml) = $src =~ $YAML_REGEX ) {
84 1         5 die "'$filename' does not start with valid YAML Front Matter\n";
85             }
86              
87             # parse the YAML front matter.
88             my $ds = try {
89 8     8   635 Load($yaml);
90             }
91             catch {
92 1     1   24 die "Problem parsing YAML: $_";
93 8         55 };
94              
95             # check for required keys
96 7         104 my $errors = q{};
97 7         17 for ( sort keys %{ $self->_req_keys_hash } ) {
  7         165  
98 4 100       15 next if $ds->{$_};
99 2         8 $errors .= "Missing required YAML Front Matter key: '$_'\n";
100             }
101 7 100       38 die $errors if $errors;
102              
103 5         22 return;
104             }
105              
106             1;
107              
108             # ABSTRACT: TidyAll plugin for validating YAML Front Matter
109              
110             __END__
111              
112             =pod
113              
114             =encoding UTF-8
115              
116             =head1 NAME
117              
118             Code::TidyAll::Plugin::YAMLFrontMatter - TidyAll plugin for validating YAML Front Matter
119              
120             =head1 VERSION
121              
122             version 1.000001
123              
124             =head1 SYNOPSIS
125              
126             In your .tidyallrc file:
127              
128             [YAMLFrontMatter]
129             select = **/*.md
130             required_top_level_keys = title layout
131              
132             =head1 DESCRIPTION
133              
134             This is a validator plugin for L<Code::TidyAll> that can be used to check
135             that files have valid YAML Front Matter, like Jekyll et al use.
136              
137             It will complain if:
138              
139             =over
140              
141             =item There's no YAML Front Matter
142              
143             =item The YAML Front Matter isn't valid YAML
144              
145             =item There's a UTF-8 BOM at the start of the file
146              
147             =item The file isn't encoded in the configured encoding (UTF-8 by default)
148              
149             =item The YAML Front Matter is missing one or more configured top level keys
150              
151             =back
152              
153             =head2 Options
154              
155             =over
156              
157             =item C<required_top_level_keys>
158              
159             Keys that must be present at the top level of the YAML Front Matter.
160              
161             =item C<encoding>
162              
163             The encoding the file is in. Defaults to UTF-8 (just like Jekyll 2.0 and
164             later.)
165              
166             =back
167              
168             =head1 SEE ALSO
169              
170             L<Jekyll's Front Matter Documentation|https://jekyllrb.com/docs/frontmatter/>
171              
172             =head1 SUPPORT
173              
174             Please report all issues with this code using the GitHub issue tracker at
175             L<https://github.com/maxmind/Code-TidyAll-Plugin-YAMLFrontMatter/issues>.
176              
177             Bugs may be submitted through L<https://github.com/maxmind/Code-Tidyall-Plugin-YAMLFrontMatter/issues>.
178              
179             =head1 AUTHOR
180              
181             Mark Fowler <mfowler@maxmind.com>
182              
183             =head1 COPYRIGHT AND LICENSE
184              
185             This software is copyright (c) 2017 by MaxMind, Inc..
186              
187             This is free software; you can redistribute it and/or modify it under
188             the same terms as the Perl 5 programming language system itself.
189              
190             =cut