Dist-Conformance
view release on metacpan or search on metacpan
lib/Dist/Conformance/Check.pm view on Meta::CPAN
package Dist::Conformance::Check 0.003;
# ABSTRACT: The interface of a check, and what the checks share
use 5.040;
use warnings FATAL => 'all';
use re '/aasx';
use Mojo::Base -base, -signatures;
use List::Util();
use Dist::Conformance::Schema();
has 'name';
has config => sub { {} };
has 'source';
has variables => sub { {} };
our $MODULE = { type => 'string', pattern => '\A[A-Za-z_]\w*(?:::\w+)*\z' };
sub config_schema ($) { return { properties => {} } }
sub schema ($class) {
my $own = $class->config_schema;
return {
type => 'object',
additionalProperties => 0,
required => $own->{required} // [],
properties => {
module => $MODULE,
level => { type => 'string', enum => [qw{fail info}], default => 'fail' },
%{ $own->{properties} },
},
};
}
sub from_config ( $class, %arguments ) {
my $where = delete $arguments{where} // "the check $arguments{name}";
my $config = Dist::Conformance::Schema::validate( $arguments{config} // {}, $class->schema, $where );
return $class->new( %arguments, config => $config );
}
sub check ( $self, $ ) { die( ref($self) . " does not implement check\n" ) }
sub update ( $self, $update ) {
my ( $status, $detail ) = $self->check( $update->snapshot );
$update->by_hand( "$detail: " . $self->name . ' has no automatic fix' ) if $status eq 'fail';
return;
}
sub template ( $self, $path ) {
my $text = $self->source->file($path);
my $variables = $self->variables;
$text =~ s/\{\{ ([[:upper:]][[:upper:][:digit:]_]*) \}\}/exists $variables->{$1} ? $variables->{$1} : "{{$1}}"/ge;
my @unfilled = List::Util::uniq( sort $text =~ m/\{\{ ([[:upper:]][[:upper:][:digit:]_]*) \}\}/g );
return ( $text, @unfilled );
}
sub write_template ( $self, $update, $path, $template, $mode = oct 644 ) {
my ( $text, @unfilled ) = $self->template($template);
if (@unfilled) {
$update->by_hand( "Write $path from $template by hand, or set the variables " . join( ', ', @unfilled ) . ' in .conformance.yml: nothing sets them' );
return 0;
}
return $update->write( $path, $text, $mode );
}
sub missing ( $, $snapshot, @paths ) {
my @missing = grep { !$snapshot->contains($_) } @paths;
return @missing;
}
__END__
=pod
=encoding UTF-8
=head1 NAME
Dist::Conformance::Check - The interface of a check, and what the checks share
=head1 VERSION
version 0.003
=head1 SYNOPSIS
use 5.040;
use Dist::Conformance::Check::ClaudeMd;
use Dist::Conformance::Snapshot::Directory;
use Dist::Conformance::Source::Directory;
my $check = Dist::Conformance::Check::ClaudeMd->from_config(
name => 'ClaudeMd',
config => { file => 'CLAUDE.md', mentions => ['perl-slop:reading-perl'] },
source => Dist::Conformance::Source::Directory->new( directory => '/bogus/standards' ),
where => 'the check ClaudeMd',
);
my ( $status, $detail ) = $check->check( Dist::Conformance::Snapshot::Directory->new( root => '/bogus/checkout' ) );
=head1 DESCRIPTION
lib/Dist/Conformance/Check.pm view on Meta::CPAN
=over
=item L<Dist::Conformance::Check::ClaudeMd>
F<CLAUDE.md> exists and names the skills that it must.
=item L<Dist::Conformance::Check::CriticProfile>
The perlcritic profile enables or turns off every policy that its template
enables.
=item L<Dist::Conformance::Check::ToolConfiguration>
The configuration files of the tools exist.
=item L<Dist::Conformance::Check::GitHooks>
The hooks are tracked, match their templates, and are installed.
=item L<Dist::Conformance::Check::DistFiles>
A distribution has the files that its build needs.
=item L<Dist::Conformance::Check::DataSchema>
A JSON or YAML file, such as F<.perl-slop.json>, matches a schema.
=item L<Dist::Conformance::Check::ClaudeSettings>
F<.claude/settings.json> enables the plugins.
=item L<Dist::Conformance::Check::Workflows>
The CI workflows exist.
=item L<Dist::Conformance::Check::SystemPackages>
The system packages and archives that F<metamerge.yml> declares are valid.
=back
=head1 WRITING A CHECK
Subclass this module, and name the subclass in a flavor:
package My::Check::HasReadme;
use 5.040;
use Mojo::Base 'Dist::Conformance::Check', -signatures;
sub config_schema ($class) {
return { required => ['file'], properties => { file => $Dist::Conformance::Schema::PATH, template => $Dist::Conformance::Schema::PATH } };
}
sub check ( $self, $snapshot ) {
my $file = $self->config->{file};
return $snapshot->contains($file) ? ( 'pass', "$file exists" ) : ( 'fail', "$file is missing" );
}
sub update ( $self, $update ) {
my $config = $self->config;
return if $update->snapshot->contains( $config->{file} );
return $self->write_template( $update, $config->{file}, $config->{template} ) if defined $config->{template};
return $update->by_hand("Write $config->{file}");
}
And in the flavor:
checks:
Readme:
module: My::Check::HasReadme
file: README.md
template: templates/README.md
A check must not keep state between calls: one object checks, and then can
update, and the snapshot can have changed in between. A check reads only
through the snapshot, so that it runs against a repository that is not on this
machine, and writes only through the update.
=head1 SEE ALSO
Please see those modules/websites for more information related to this module.
=over 4
=item *
L<Dist::Conformance|Dist::Conformance>
=item *
L<Dist::Conformance>
=item *
L<Dist::Conformance::Flavor>
=item *
L<Dist::Conformance::Update>
=back
=head1 BUGS
Please report any bugs or feature requests on the bugtracker website
L<https://github.com/Troglodyne-Internet-Widgets/Dist-Conformance/issues>
When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.
=head1 AUTHORS
Current Maintainers:
=over 4
=item *
( run in 1.931 second using v1.01-cache-2.11-cpan-1236ad508bc )