NAME

Dist::Conformance::Flavor - A named set of checks and their configuration, read from a standards source

VERSION

version 0.002

SYNOPSIS

use 5.040;
use Dist::Conformance::Flavor;
use Dist::Conformance::Source::Directory;

my $source = Dist::Conformance::Source::Directory->new( directory => '/bogus/standards' );
my $flavor = Dist::Conformance::Flavor->load( $source, 'troglodyne-5.040' );
say for $flavor->check_names;                 # ClaudeMd, CriticProfile, ...
say $flavor->checks->{CriticProfile}{template};
say join ' then ', @{ $flavor->lineage };     # troglodyne then troglodyne-5.040

DESCRIPTION

A flavor is the answer to "what does a conforming repository look like" for one kind of repository. It names the checks to run and gives each its configuration: which files, which templates, which skills, and whether a miss is a failure or only worth knowing. A check module has no expectations of its own; everything it compares against comes from here.

Flavors build on each other with extends, so a standard is written once and varied where it differs: by the perl it runs on, by whether it is a CPAN distribution, by whether it runs CI, or by anything else that a check can see.

ATTRIBUTES

name

The name of the flavor that was loaded.

description

Its description, or undef. Never inherited.

checks

The merged configuration: a hash reference from each check name to its configuration.

variables

The merged variables.

lineage

The names of the flavors that were merged, in the order of the merge, ending with "name".

METHODS

load

($class, $source, $name). Reads the flavor $name and everything it extends from $source, a Dist::Conformance::Source, and returns the merged flavor. Dies when a file is missing, does not parse or does not match the schema, when a check has a name that is not a "$NAME" in Dist::Conformance::Schema, and on a cycle in extends. Each message names the file, and the key where there is one.

check_names

The names of the checks, in the order that they run.

FUNCTIONS

linearize

($source, $name). The validated contents of the flavor files that make up the flavor $name, in the order of "THE MERGE RULE". Dies on a cycle.

merge_checks

(\%checks, \%over). Lays %over, a mapping of check names to configurations, over %checks in place, by "THE MERGE RULE". A configuration in %checks is replaced, never changed, so that a caller can share one with another flavor.

read_file

($source, $name, $extended_by). The validated content of conformance/$name.yml. $extended_by is the flavor whose extends named it, for the message when it is missing.

THE FLAVOR FILE

A flavor named NAME is the YAML file conformance/NAME.yml of a standards source. These are its keys:

name

Required. The name of the flavor, the same as the file name without .yml. A letter or a digit, then letters, digits, ., _ and -.

description

One line that says what kind of repository the flavor is for.

extends

A list of the flavors that this one builds on, in order. See "THE MERGE RULE".

variables

A mapping from a placeholder name to its value, a string. A template that contains {{NAME}} gets the value of NAME when an update writes it. Quote a value that looks like a number, such as '5.040'. YAML::XS keeps the text of an unquoted 5.040, but YAML itself calls it the number 5.04, and another reader of the file can take it at its word.

checks

A mapping from the name of a check to its configuration, itself a mapping. The name picks the module, Dist::Conformance::Check::NAME, unless the configuration says module. So one module can run twice under two names, with two configurations. The keys that every check takes, and the module of each check that this distribution ships, are documented in Dist::Conformance::Check.

A check whose configuration is ~, the YAML null, is removed: the flavor does not run a check that a flavor it extends runs.

The checks run in the order of their names.

This is troglodyne-5.040.yml from the fixtures of this distribution:

name: troglodyne-5.040
description: A troglodyne distribution that declares use 5.040 or later
extends:
  - troglodyne
variables:
  PERL_FLOOR: '5.040'
checks:
  CriticProfile:
    file: .perlcriticrc
    template: templates/perlcriticrc
    replace: false

And this is a flavor that differs for a reason other than the perl: a service, which is deployed and not released to CPAN. It runs the 5.040 standards and the CI of a second flavor, drops the dist files, and requires the Claude Code settings:

name: troglodyne-service
extends:
  - troglodyne-5.040
  - github-ci
checks:
  DistFiles: ~
  ToolConfiguration:
    files:
      .perltidyrc: templates/perltidyrc
      .preferred_modules.ini: templates/preferred_modules.ini
      .tests-covering-map.pl: templates/tests-covering-map
  ClaudeSettings:
    missing: fail

THE MERGE RULE

A flavor is merged from itself and every flavor that it extends, directly or not. First the flavors are put in order:

  1. Each flavor comes after every flavor that it extends.

  2. The flavors that one flavor extends come in the order of its extends list, each with its own ancestors before it.

  3. A flavor that two paths reach is used once, at its first place. So in a diamond, where service extends perl and ci, and both extend base, the order is base, perl, ci, service.

Then each flavor in that order is laid over the result of the ones before it:

  • A variable replaces the variable of the same name.

  • A check that the result does not have yet is added.

  • A check that it has already is merged key by key: each key of the later configuration replaces the same key of the earlier one, and the keys that the later one does not name stay. The value of a key is replaced whole, never merged: a later files mapping is the whole list of files, not an addition to the earlier one.

  • A key whose value is ~ is removed from the configuration of the check, so the check sees it as not set.

  • A check whose configuration is ~ is removed altogether. A later flavor can add it again.

So the last word goes to the flavor named last, and to the flavor itself over everything that it extends. .conformance.yml is laid over the result by the same rule; see Dist::Conformance::Config.

A flavor that extends itself, directly or through others, is an error, and so is an extends that names a flavor that the source does not have.

THE STANDARDS REPOSITORY

A standards source is a directory tree, in a git repository or on disk. Its path is the directory inside the tree that the flavors are read from, and every path that a flavor names is relative to that directory too:

PATH/
    conformance/
        NAME.yml          one file for each flavor
        ...
    templates/            the files that the flavors name, where you like
        perlcriticrc
        pre-commit
        ...

Only conformance/ has a fixed name. The templates can live anywhere below PATH, because each flavor names them by path, and a path that leaves PATH, absolute or with .., is refused. Keep the flavors in the repository of the templates that they name, so that one commit changes a template and the flavor that expects it, and every repository that fetches the standards sees both or neither.

SEE ALSO

Please see those modules/websites for more information related to this module.

BUGS

Please report any bugs or feature requests on the bugtracker website 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.

AUTHORS

Current Maintainers:

  • George S. Baugh <george@troglodyne.net>

COPYRIGHT AND LICENSE

Copyright (c) 2026 Troglodyne LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.