NAME

Code::Style::Kit - build composable bulk exporters

VERSION

version 1.0.3

SYNOPSIS

To build a "part":

package My::Kit::Part;
use strict;
use warnings;

sub feature_trytiny_default { 1 }
sub feature_trytiny_export_list { 'Try::Tiny' }

1;

To build the kit:

package My::Kit;
use parent qw(Code::Style::Kit My::Kit::Part My::Kit::OtherPart);
1;

To use the kit:

package My::App;
use My::Kit;

# you now have Try::Tiny imported, plus whatever OtherPart did

DESCRIPTION

This package simplifies writing "code style kits". A kit (also known as a "policy") is a module that encapsulates the common pragmas and modules that every package in a project should use. For instance, it might be a good idea to always use strict, enable method signatures, and use true, but it's cumbersome to put that boilerplate in every single file in your project. Now you can do that with a single line of code.

Code::Style::Kit is not to be used directly: you must write a package that inherits from it. Your package can (and probably should) also inherit from one or more "parts". See Code::Style::Kit::Parts for information about the parts included in this distribution.

Please don't use this for libraries you intend to distribute on CPAN: you'd be forcing a bunch of dependencies on every user. These kits are intended for applications, or "internal" libraries that don't get released publicly.

Features

A kit provides a set of "features" (like "tags" in Exporter or "groups" in Sub::Exporter). Feature names must match ^\w+$. Some features may be exported by default.

A simple example of a feature, from the synopsis:

sub feature_trytiny_default { 1 }
sub feature_trytiny_export_list { 'Try::Tiny' }

or, equivalently:

sub feature_trytiny_default { 1 }
sub feature_trytiny_export {
    my ($self, $caller) = @_;
    require Try::Tiny;
    Try::Tiny->import::into($caller);
}

The feature_*_default method says that this feature should always be exported (unless the user explicitly asks us not to). The feature_*_export_list is a shortcut for the simple case of re-exporting one or more entire packages. Alternatively, the feature_*_export sub provides full flexibility, when you need it.

Feature ordering

Sometimes you need features to be exported in a certain order:

package My::Kit;
use parent qw(Code::Style::Kit Code::Style::Kit::Parts::Common);

sub feature_class_export_list { 'Moo' }
sub feature_class_order { 200 }

sub feature_singleton_export {
    require Role::Tiny;
    Role::Tiny->apply_roles_to_package($_[1], 'MooX::Singleton');
}
sub feature_singleton_order { 210 }

If someone says either:

package My::Class;
use My::Kit 'class', 'singleton';

or:

package My::Class;
use My::Kit 'singleton', 'class';

then Moo will be imported first, then MooX::Singleton will be applied.

All features that don't have a feature_*_order sub are assumed to have order 100.

Dependencies

Sometimes you want to make sure that a certain feature is exported whenever another one is.

sub feature_class_export_list { 'Moo' }

sub feature_singleton_export {
    my ($self, $caller) = @_;
    $self->also_export('class');
    require Role::Tiny;
    Role::Tiny->apply_roles_to_package($caller, 'MooX::Singleton');
}

Now:

package My::Class;
use My::Kit 'singleton';

will work. Notice that you don't have to worry whether the feature was defined via "export" or "export_list": it just works.

Also, a feature can be imported only once, so

package My::Class;
use My::Kit 'class', 'singleton';

will not create problems.

Optional dependencies

Maybe you'd like for another feature to be exported, but you're not sure if it's provided by the kit. This can happen when writing reusable parts.

sub feature_class_export {
    my ($self, $caller) = @_;
    require Moo; Moo->import::into($caller);
    $self->maybe_also_export('types');
}

now, if the final kit provides a "types" feature, it will be exported whenever the "class" feature is requested.

Extending features

Different "parts" can provide the same feature. Their export functions will be invoked in method resolution order (usually, the order they appear in @ISA).

So, having:

package My::Kit::Part;

sub feature_test_export_list { 'Test::Most' }

and:

package My::Kit::OtherPart;

sub feature_test_export_list { 'Test::Failure' }

this kit:

package My::Kit;
use parent qw(Code::Style::Kit My::Kit::Part My::Kit::OtherPart);
1;

will export Test::Most first, then Test::Failure, when used as use My::Kit 'test'.

Defaults are also affected by this, and the last one wins: if My::Kit::OtherPart::feature_test_default returned 1, the feature would be exported by default.

Mutually exclusive features

You may want to prevent two features from being exported at the same time:

sub feature_class_export {
    my ($self, $caller) = @_;
    croak "can't be a class and a role" if $self->is_feature_requested('role');
    ...
}

sub feature_role_export {
    my ($self, $caller) = @_;
    croak "can't be a class and a role" if $self->is_feature_requested('class');
    ...
}

Arguments to features

Sometimes you need to have a bit more information than just "import this feature". For example, Mojo::Base needs a superclass name on its import list. In that case you can do:

sub feature_mojo_takes_arguments { 1 }
sub feature_mojo_export {
    my ($self, $caller, @arguments) = @_;
    require Mojo::Base;
    Mojo::Base->import::into(
        $caller,
        @arguments ? @arguments : '-base',
    );
}

and the user can do:

use My::Kit mojo => [ 'Some::Base::Class' ];

(the arrayref is needed to distinguish argument lists from feature names).

METHODS

import

use My::Kit;
use My::Kit 'feature_i_want', '-feature_i_dont_want';

When a package inheriting Code::Style::Kit get used, this method:

  • collects all the features that the kit exports by default

  • adds the features listed in the arguments

  • removes the features listed in the arguments with a - in front

  • exports the resulting set of features

is_feature_requested

if ($self->is_feature_requested($name)) { ... }

Returns true if the named feature is being exported (either because it's exported by default and not removed, or because it was asked for explicitly).

also_export

$self->also_export($name);
$self->also_export($name, \@arguments);

Export the named feature to the caller (optionally with arguments). Dies if the feature is not provided by the kit.

maybe_also_export

$self->maybe_also_export($name);
$self->maybe_also_export($name, \@arguments);

Export the named feature to the caller, same as "also_export", but if the feature is not provided by the kit, this method just returns.

AUTHOR

Gianni Ceccarelli <gianni.ceccarelli@broadbean.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2019 by BroadBean UK, a CareerBuilder Company.

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