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