File Coverage

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


line stmt bran cond sub pod time code
1             package Code::TidyAll::Plugin::YAMLFrontMatter;
2              
3 1     1   33054 use strict;
  1         3  
  1         30  
4 1     1   8 use warnings;
  1         6  
  1         30  
5 1     1   483 use namespace::autoclean;
  1         10246  
  1         6  
6              
7             our $VERSION = '1.000003';
8              
9 1     1   93 use Moo;
  1         3  
  1         8  
10              
11 1     1   1027 use Encode qw( decode encode FB_CROAK );
  1         9832  
  1         78  
12 1     1   9 use Path::Tiny qw( path );
  1         2  
  1         43  
13 1     1   6 use Try::Tiny qw( catch try );
  1         3  
  1         53  
14 1     1   488 use YAML::PP 0.006 ();
  1         48381  
  1         558  
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   92 my $self = shift;
54             return +{
55              
56             # note use of magical split on space to do automatic trimming
57 7         50 map { $_ => 1 } split q{ }, $self->required_top_level_keys
  4         31  
58             };
59             }
60              
61             sub validate_file {
62 11     11 1 133400 my ( $self, $filename ) = @_;
63              
64 11         48 my $src = path($filename)->slurp_raw;
65              
66             # YAML::PP always expects things to be in UTF-8 bytes
67 11         2024 my $encoding = $self->encoding;
68             try {
69 11     11   588 $src = decode( $encoding, $src, FB_CROAK );
70 10         4260 $src = encode( 'UTF-8', $src, FB_CROAK );
71             }
72             catch {
73 1     1   95 die "File does not match encoding '$encoding': $_";
74 11         106 };
75              
76             # is there a BOM? There's not meant to be a BOM!
77 10 100       619 if ( $src =~ /\A\x{EF}\x{BB}\x{BF}/ ) {
78 1         8 die "Starting document with UTF-8 BOM is not allowed\n";
79             }
80              
81             # match the YAML front matter.
82 9         22 my $yaml;
83 9 100       106 unless ( ($yaml) = $src =~ $YAML_REGEX ) {
84 1         11 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   387 my $yp = YAML::PP->new(
90              
91             # Insist on YAML 1.1. Jekyl uses SafeYAML to parse YAML
92             # which will either use Syck (via "YAML") or LibYAML
93             # (via "Psych") to parse the YAML, so what YAML it can
94             # use is a matter of debate. However, since this module
95             # is a linter, we can make up our own rules, and they are
96             # that you have to write your frontmatter only in the
97             # YAML 1.1 spec. Todo: Make this insist in the most
98             # compatible YAML (i.e. freak out if someone uses "y"
99             # instead of "true") if YAML::PP ever supports such
100             # an option.
101             schema => ['YAML1_1'],
102              
103             # we do not want to create circular refs
104             cyclic_refs => 'fatal',
105              
106             );
107 8         29585 return $yp->load_string($yaml);
108             }
109             catch {
110 1     1   1816 die "Problem parsing YAML: $_";
111 8         58 };
112              
113             # check for required keys
114 7         11114 my $errors = q{};
115 7         18 for ( sort keys %{ $self->_req_keys_hash } ) {
  7         196  
116 4 100       18 next if $ds->{$_};
117 2         7 $errors .= "Missing required YAML Front Matter key: '$_'\n";
118             }
119 7 100       42 die $errors if $errors;
120              
121 5         28 return;
122             }
123              
124             1;
125              
126             # ABSTRACT: TidyAll plugin for validating YAML Front Matter
127              
128             __END__
129              
130             =pod
131              
132             =encoding UTF-8
133              
134             =head1 NAME
135              
136             Code::TidyAll::Plugin::YAMLFrontMatter - TidyAll plugin for validating YAML Front Matter
137              
138             =head1 VERSION
139              
140             version 1.000003
141              
142             =head1 SYNOPSIS
143              
144             In your .tidyallrc file:
145              
146             [YAMLFrontMatter]
147             select = **/*.md
148             required_top_level_keys = title layout
149              
150             =head1 DESCRIPTION
151              
152             This is a validator plugin for L<Code::TidyAll> that can be used to check
153             that files have valid YAML Front Matter, like Jekyll et al use.
154              
155             It will complain if:
156              
157             =over
158              
159             =item There's no YAML Front Matter
160              
161             =item The YAML Front Matter isn't valid YAML
162              
163             =item There's a UTF-8 BOM at the start of the file
164              
165             =item The file isn't encoded in the configured encoding (UTF-8 by default)
166              
167             =item The YAML Front Matter is missing one or more configured top level keys
168              
169             =item The YAML Front Matter contains circular references
170              
171             =back
172              
173             =head2 Options
174              
175             =over
176              
177             =item C<required_top_level_keys>
178              
179             Keys that must be present at the top level of the YAML Front Matter.
180              
181             =item C<encoding>
182              
183             The encoding the file is in. Defaults to UTF-8 (just like Jekyll 2.0 and
184             later.)
185              
186             =back
187              
188             =head1 SEE ALSO
189              
190             L<Jekyll's Front Matter Documentation|https://jekyllrb.com/docs/frontmatter/>
191              
192             =head1 SUPPORT
193              
194             Please report all issues with this code using the GitHub issue tracker at
195             L<https://github.com/maxmind/Code-TidyAll-Plugin-YAMLFrontMatter/issues>.
196              
197             Bugs may be submitted through L<https://github.com/maxmind/Code-Tidyall-Plugin-YAMLFrontMatter/issues>.
198              
199             =head1 AUTHOR
200              
201             Mark Fowler <mfowler@maxmind.com>
202              
203             =head1 CONTRIBUTORS
204              
205             =for stopwords Dave Rolsky Greg Oschwald
206              
207             =over 4
208              
209             =item *
210              
211             Dave Rolsky <autarch@urth.org>
212              
213             =item *
214              
215             Greg Oschwald <goschwald@maxmind.com>
216              
217             =back
218              
219             =head1 COPYRIGHT AND LICENSE
220              
221             This software is copyright (c) 2019 by MaxMind, Inc.
222              
223             This is free software; you can redistribute it and/or modify it under
224             the same terms as the Perl 5 programming language system itself.
225              
226             =cut