NAME
    Module::Features - Define features for modules

SPECIFICATION VERSION
    0.1

VERSION
    This document describes version 0.1.7 of Module::Features (from Perl
    distribution Module-Features), released on 2021-08-27.

DESCRIPTION
    This document specifies a very easy and lightweight way to define and
    declare features for modules. A definer module defines some features in
    a feature set, other modules declare these features that they have or
    don't have, and user can easily check and select modules based on
    features he/she wants.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
    "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
    document are to be interpreted as described in RFC 2119.

SPECIFICATION STATUS
    The 0.1.x version series is still unstable.

GLOSSARY
  feature definer module
    A module in ""Module::Features::"*FeatureSetName*" namespace that
    contains "feature set specification". This module describes what each
    feature in the feature set means, what values are valid for the feature,
    and so on. A "feature declarer module" follows this specification and
    declares features.

  feature declarer module
    A regular Perl module that wants to declare some features defined by
    "feature definer module". Module name must not end with
    "::_ModuleFeatures", in which case it is a "feature declarer proxy
    module".

  feature declarer proxy module
    A module that declares features for another module. Module name must end
    with "::_ModuleFeatures" and the name of the module it delares features
    for (the target module) is its own name sans the "::_ModuleFeatures"
    suffix. For example, the module Text::Table::Tiny::_ModuleFeatures
    contains "features declaration" for Text::Table::Tiny.

    The point of proxy module is to allow a different author declare
    features for a target module.

  feature name
    A non-empty string, preferably an identifier matching regex pattern
    /\A\w+\z/.

  feature value
    The value of a feature.

  feature specification
    A DefHash, containing the feature's summary, description, schema for
    value, and other things.

    See "Recommendation for feature name".

  feature set name
    A string following regular Perl namespace name, e.g. "JSON::Encoder" or
    "TextTable".

  feature set specification
    A collection of "feature name"s along with each feature's specification.

  features declaration
    A DefHash containing a list of feature set names and feature values for
    features of those feature sets.

SPECIFICATION
  Defining feature set
    A "feature definer module" specifies feature set by putting the "feature
    set specification" in %FEATURES_DEF package variable. Specifying feature
    set should not require any module dependency.

    For example, in Module::Features::TextTable:

     # a DefHash
     our %FEATURES_DEF = (

         # version number of the feature set. positive integer, begins at 1.
         # optional, default is 1 if unspecified. should be increased whenever
         # there's a backward-incompatible change in the feature set, i.e. when one
         # or more features are renamed, deleted, change meaning, or change the
         # schema in a backward-incompatible way (e.g. become more restricted or
         # change type). when a feature set changes in a backward-compatible wa
         # (e.g. a new feature is added, just the summary is revised, etc) then the
         # version number need not be increased.
         v => 1,

         summary => 'Features of a text table generator',

         description => <<'_',
     This feature set defines features of a text table generator. By declaring these
     features, the module author makes it easier for module users to choose an
     appropriate module.
     _

         features => {

             # each key is a feature name. each value is the feature's
             # specification. see recommendation on feature name in this
             # specification.

             can_align_cell_containing_color_code => {
                 # a regular DefHash with common properties like 'summary',
                 # 'description', 'tags', etc. can also contain these properties:
                 # 'schema', 'req' (whether the feature must be declared by user
                 # module).

                 summary => 'Whether the module can align cells that contain ANSI color codes',
                 # schema => 'bool', # Sah schema. if not specified, the default is 'bool'

                 tags => ['category:alignment'],
             },
             can_align_cell_containing_newline => {
                 summary => 'Whether the module can align cells that contain multiple lines of text',
                 tags => ['category:alignment'],
             },
             can_align_cell_containing_wide_character => {
                 summary => 'Whether the module can align cells that contain wide Unicode characters',
                 tags => ['category:alignment', 'category:unicode'],
             },
             speed => {
                 summary => 'The speed of the module, according to the author',
                 schema => ['str', in=>['slow', 'medium', 'fast']],
             },
         },
     );

  Recommendations for feature name
    Features should be written in lower case and words are separated by
    underscores, e.g. "can_color", "max_colors". The name should be
    self-explanatory when possible and should use English.

    Singular noun is preferred (e.g
    "can_align_cell_containing_wide_character" instead of
    "can_align_cells_containing_wide_characters") unless when is is
    grammatically required to be plurals, e.g. "max_colors".

    Abbreviations should be avoided unless when an abbrevation is common,
    e.g. "require_filesystem" is preferred over "req_fs", but "max_colors"
    is okay.

    Infinitive form of verb is preferred, e.g. "require_filesystem_access"
    instead of "requires_filesystem_access".

    Features that refer to whether a module has a specific ability should be
    named with "can_" prefix. Examples:
    "can_align_cell_containing_wide_character", "can_color". These features
    have a bool value ("yes" or "no"). "have_" or "able_to_" prefix is not
    preferred.

    Features that refer to whether a module needs (requires) a specific
    feature/resource to function should be named with "require_" prefix.
    "need_" prefix is not preferred. Examples: "require_filesystem_access".

    Features that refer to whether a module can optionally use or prefers
    something should be named with "can_use_" prefix. "prefer_" or "want_"
    prefix is not preferred.

    Features that specify an upper or lower limit of something should be
    named with "max_" or "min_" prefix. They typically have int/float/num
    schemas.

  Recommendations for feature definer module
    The distribution that ships a feature definer module should add a
    dependency (phase=develop, rel=x_spec) to "Module::Features", to express
    that it follows the Module::Features specification.

  Declaring features
    A "feature declarer module" declares features that it supports (or does
    not support) via putting the "features declaration" in %FEATURES package
    variable. Declaring features should not require any module dependency,
    but a helper module can be written to help check that declared feature
    sets and features are known and the feature values conform to defined
    schemas.

    Not all features from a feature set need to be declared by the feature
    declarer module. The undeclared features will have "undef" as their
    values for the declarer module. However, features defined as required
    ("req => 1" in the specification) MUST be declared.

    For example, in Text::Table::More:

     # a DefHash
     our %FEATURES = (

         # optional. specify the versions of the feature sets this declaration uses.
         # the version of feature set must match. versions defaults to 1 if
         # unspecified.
         #set_v => {TextTable => 1},

         # optional, specifies which module version this declaration pertains to
         #module_v => "0.002",

         # optional, a numeric value to be compared against other declarations for
         # the same module. recommended form is YYYYMMDD. for multiple serials in a
         # single day, you can use YYYYMMDD.1, YYYYMMDD.2, YYYYMMDD.91, and so on.
         #serial => 20210223,

         features => {
             # each key is a feature set name.
             TextTable => {
                 # each key is a feature name defined in the feature set. each value
                 # is either a feature value, or a DefHash that contains the feature
                 # value in the 'value' property, and notes in 'summary', and other
                 # things.
                 can_align_cell_containing_color_code     => 1,
                 can_align_cell_containing_wide_character => 1,
                 can_align_cell_containing_newline        => 1,
                 speed => {
                     value => 'slow', # if unspecified, value will become undef (which means N/A [not available])
                     summary => "It's certainly slower than Text::Table::Tiny, etc; and it can still be made faster after some optimization",
                 },
             },
         },
     );

    While in Text::Table::Sprintf:

     our %FEATURES = (
         features => {
             TextTable => {
                 can_align_cell_containing_color_code     => 0,
                 can_align_cell_containing_wide_character => 0,
                 can_align_cell_containing_newline        => 0,
                 speed                                 => 'fast',
             },
         },
     );

    and in Text::Table::Any:

     our %FEATURES = (
         features => {
             TextTable => {
                 can_align_cell_containing_color_code     => {value => undef, summary => 'Depends on the backend used'},
                 can_align_cell_containing_wide_character => {value => undef, summary => 'Depends on the backend used'},
                 can_align_cell_containing_newline        => {value => undef, summary => 'Depends on the backend used'},
                 speed                                    => {value => undef, summary => 'Depends on the backend used'},
             },
         },
     );

    Features declaration can also be put in other places:

    *   %FEATURES package variable in the "feature declarer proxy module"

    *   database

    *   others

    The %FEATURES package variable in the feature declarer module itself is
    considered to be authoritative, but other places can be checked first to
    avoid having to load the feature declarer module. When multiple features
    declaration exist, the "module_v" and/or "serial" can be used to find
    out which declaration is the most recent or suitable.

  Recommendations for feature declarer module
    The distribution that ships a feature declarer module should add a
    dependency (phase=develop, rel=x_spec) to "Module::Features", to express
    that it follows the Module::Features specification. It should also add a
    dependency (phase=develop, x=features_from> to associated feature
    definer module(s), to express that it declares features defined in the
    associated feature definer module(s).

  Checking whether a module has a certain feature
    The user of a "feature declarer module" can check whether the module has
    a certain feature simply by checking the module's "features declaration"
    (%FEATURES). Checking features of a module should not require any module
    dependency.

    For example, to check whether Text::Table::Sprintf supports aligning
    cells that contain multiple lines:

     if (do { my $val = $Text::Table::Sprintf::FEATURES{features}{TextTable}{align_cell_containing_multiple_lines}; ref $val eq 'HASH' ? $val->{value} : $val }) {
         ...
     }

    A utility module can be written to help make this more convenient.

FAQ
  Why not roles?
    Role frameworks like Role::Tiny allow you to require a module to have
    certain subroutines, i.e. to follow some kind of interface. This can be
    used to achieve the same goal of defining and declaring features, by
    representing features as required subroutines and feature sets as roles.
    However, Module::Features wants declaring features to have negligible
    overhead, including no extra runtime dependency.

HOMEPAGE
    Please visit the project's homepage at
    <https://metacpan.org/release/Module-Features>.

SOURCE
    Source repository is at
    <https://github.com/perlancar/perl-Module-Features>.

SEE ALSO
    DefHash

    Sah

AUTHOR
    perlancar <perlancar@cpan.org>

CONTRIBUTING
    To contribute, you can send patches by email/via RT, or send pull
    requests on GitHub.

    Most of the time, you don't need to build the distribution yourself. You
    can simply modify the code, then test via:

     % prove -l

    If you want to build the distribution (e.g. to try to install it locally
    on your system), you can install Dist::Zilla,
    Dist::Zilla::PluginBundle::Author::PERLANCAR, and sometimes one or two
    other Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional
    steps required beyond that are considered a bug and can be reported to
    me.

COPYRIGHT AND LICENSE
    This software is copyright (c) 2021 by perlancar <perlancar@cpan.org>.

    This is free software; you can redistribute it and/or modify it under
    the same terms as the Perl 5 programming language system itself.

BUGS
    Please report any bugs or feature requests on the bugtracker website
    <https://rt.cpan.org/Public/Dist/Display.html?Name=Module-Features>

    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.