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 )