Modern-Perl-Prelude

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for Modern::Perl::Prelude

0.009  2026-03-23
    - Explicitly document the Perl license in module metadata and docs
    - Lower minimum supported Perl version from 5.30 to 5.26
    - Update module, tests, README and build metadata for Perl 5.26+
    - Extend CI matrix with Perl 5.26 and 5.28
    - Preserve full test and documentation coverage for the public API

0.008  2026-03-22
    - Add optional -always_true / always_true support via the true module
    - Allow modules to omit a trailing 1; on Perl 5.30+ when always_true is enabled
    - Support both flag-style and hash-style always_true imports
    - Support explicit no Modern::Perl::Prelude -always_true / { always_true => 1 }
    - Add true as a dependency and use true::VERSION in Makefile metadata
    - Document always_true in POD and README
    - Add coverage tests for always_true import and unimport behavior
    - Preserve 0.007 work on hash-style imports and Test2::Tools::Spec conversion

0.007  2026-03-22
    - Add documented hash-style import arguments via a single hash reference
    - Support hash-style keys: utf8, class, defer, corinna
    - Make -class and -corinna mutually exclusive for both flag-style and hash-style imports
    - Add tests for hash-style utf8 import and hash-style corinna import

Changes  view on Meta::CPAN

    - Fix UTF-8 option tests to match real lexical behavior
    - Silence once-only package variable warning in args test
    - Finalize test suite for use/no and option validation paths

0.003  2026-03-17
    - Make unimport honest: only undo native pragmata/features
    - Fix no.t to test only reliably reversible native features
    - Clarify POD about import-only compat layers

0.002  2026-03-17
    - Add unimport support: no Modern::Perl::Prelude
    - Add author tests
    - Add GitHub Actions CI matrix for Perl 5.30 .. 5.42
    - Add cpanfile
    - Refresh distribution skeleton

0.001  2026-03-17
    - First version

Makefile.PL  view on Meta::CPAN

use v5.26;
use strict;
use warnings;

use ExtUtils::MakeMaker;

WriteMakefile(
    NAME              => 'Modern::Perl::Prelude',
    VERSION_FROM      => 'lib/Modern/Perl/Prelude.pm',
    ABSTRACT_FROM     => 'lib/Modern/Perl/Prelude.pm',
    AUTHOR            => 'Sergey Kovalev <skov@cpan.org>, Kirill Dmitriev <zaika.k1007@gmail.com>',
    LICENSE           => 'perl',
    MIN_PERL_VERSION  => '5.26.0',

    CONFIGURE_REQUIRES => {
        'ExtUtils::MakeMaker' => 0,
    },

README.md  view on Meta::CPAN

# Modern::Perl::Prelude

[![License](https://img.shields.io/badge/license-Perl%205-blue.svg)](https://dev.perl.org/licenses/)
[![Perl](https://img.shields.io/badge/perl-5.30%2B-blue.svg)](https://www.perl.org/)
[![CI](https://github.com/neo1ite/Modern-Perl-Prelude/actions/workflows/ci.yml/badge.svg)](https://github.com/neo1ite/Modern-Perl-Prelude/actions/workflows/ci.yml)

A small lexical prelude for writing modern-style Perl on Perl 5.26+.

## What it enables by default

- `strict`

README.md  view on Meta::CPAN


- `-utf8`
- `-class`
- `-defer`
- `-corinna`
- `-always_true`

Examples:

```perl
use Modern::Perl::Prelude '-utf8';

use Modern::Perl::Prelude qw(
    -class
    -defer
);

use Modern::Perl::Prelude qw(
    -class
    -utf8
    -always_true
);
```

### Hash-style

Hash-style arguments are supported as a **single hash reference**:

```perl
use Modern::Perl::Prelude {
    utf8        => 1,
    defer       => 1,
    always_true => 1,
};
```

Supported hash keys:

- `utf8`
- `class`

README.md  view on Meta::CPAN


For compatibility-layer options (`class`, `defer`, `corinna`), a true scalar enables the option. A hash reference also enables it and is passed through to the underlying module's `import`.

For `always_true`, use a boolean value.

### `-utf8` / `utf8`

Enables source-level UTF-8, like:

```perl
use Modern::Perl::Prelude '-utf8';
```

or:

```perl
use Modern::Perl::Prelude {
    utf8 => 1,
};
```

### `-class` / `class`

Enables `Feature::Compat::Class` on demand:

```perl
use Modern::Perl::Prelude '-class';

class Point {
    field $x :param = 0;
    field $y :param = 0;

    method sum {
        return $x + $y;
    }
}
```

### `-defer` / `defer`

Enables `Feature::Compat::Defer` on demand:

```perl
use Modern::Perl::Prelude '-defer';

{
    defer { warn "leaving scope\n" };
    ...
}
```

### `-corinna` / `corinna`

Enables direct `Object::Pad` / Corinna-like syntax on demand:

```perl
use Modern::Perl::Prelude '-corinna';

class Person {
    field $name :param;
    field $age  :param = 0;

    method greet {
        return "Hello, I'm $name and I'm $age years old";
    }
}
```

Hash-style example:

```perl
use Modern::Perl::Prelude {
    corinna => {},
    utf8    => 1,
};
```

### `-always_true` / `always_true`

Enables automatic true return for the currently-compiling file, so a module can omit the trailing:

```perl
1;
```

Example module without `1;`:

```perl
use Modern::Perl::Prelude qw(
    -class
    -utf8
    -always_true
);

class My::App::Person {
    field $name :param;
    field $age  :param = 0;

    method greet {
        return "Hello, I'm $name and I'm $age years old";
    }
}
```

Hash-style example:

```perl
use Modern::Perl::Prelude {
    class       => 1,
    utf8        => 1,
    always_true => 1,
};

class My::App::Person {
    field $name :param;
}
```

## Option compatibility rules

Any non-conflicting combination is allowed.

`-class` and `-corinna` are intentionally mutually exclusive.

The same rule applies to hash-style arguments:

```perl
use Modern::Perl::Prelude {
    class   => 1,
    corinna => 1,
}; # dies
```

## Usage

Basic usage:

```perl
use Modern::Perl::Prelude;

state $counter = 0;

my $s = trim("  hello  ");
my $folded = fc("Straße");

try {
    die "boom\n";
}
catch ($e) {
    warn $e;
}
```

With `Feature::Compat::Class`:

```perl
use Modern::Perl::Prelude qw(
    -class
    -defer
);

class Example {
    field $value :param = 0;

    method value {
        return $value;
    }

README.md  view on Meta::CPAN

{
    defer { warn "scope ended\n" };
    my $obj = Example->new(value => 42);
    say $obj->value;
}
```

With `Object::Pad`:

```perl
use Modern::Perl::Prelude {
    corinna => {},
    utf8    => 1,
};

class Person {
    field $name :param;
    field $age  :param = 0;

    method greet {
        return "Hello, I'm $name and I'm $age years old";
    }
}

my $p = Person->new(name => 'José');
say $p->greet;
```

With `always_true` for a module file:

```perl
use Modern::Perl::Prelude qw(
    -class
    -utf8
    -always_true
);

class My::App::Person {
    field $name :param;
}

# no trailing 1;
```

## Lexical disabling

You can disable native pragmata/features lexically again:

```perl
no Modern::Perl::Prelude;
```

This reliably disables native pragmata/features managed directly by the module, such as:

- `strict`
- `warnings`
- `say`
- `state`
- `fc`
- `utf8`

Compatibility layers are treated as import-only for cross-version use on Perl 5.30+, so they are not guaranteed to be symmetrically undone by:

```perl
no Modern::Perl::Prelude;
```

This applies to:

- `Feature::Compat::Try`
- `Feature::Compat::Class`
- `Feature::Compat::Defer`
- `Object::Pad`
- `builtin::compat`

`always_true` is different: it is file-scoped, and you can explicitly disable it for the current file with:

```perl
no Modern::Perl::Prelude '-always_true';
```

or:

```perl
no Modern::Perl::Prelude { always_true => 1 };
```

## Design goals

This module is intended as a small project prelude for codebases that want a more modern Perl style while keeping runtime compatibility with Perl 5.30+.

It is implemented as a lexical wrapper using `Import::Into`, so pragmata and lexical features affect the caller's scope rather than the wrapper module itself.

Optional compatibility layers are loaded lazily and only when explicitly requested.

lib/Modern/Perl/Prelude.pm  view on Meta::CPAN

package Modern::Perl::Prelude;

use v5.26;
use strict;
use warnings;

# ABSTRACT: Project prelude for modern Perl style on Perl 5.26+
our $VERSION = '0.009';

use Import::Into ();
use strict   ();

lib/Modern/Perl/Prelude.pm  view on Meta::CPAN

}

1;

__END__

=pod

=head1 NAME

Modern::Perl::Prelude - Project prelude for modern Perl style on Perl 5.26+

=head1 SYNOPSIS

    use Modern::Perl::Prelude;

    state $counter = 0;
    my $s = trim("  hello  ");

    try {
        die "boom\n";
    }
    catch ($e) {
        warn $e;
    }

Flag-style optional imports:

    use Modern::Perl::Prelude '-utf8';
    use Modern::Perl::Prelude qw/-class -defer/;
    use Modern::Perl::Prelude qw(-corinna -always_true);


Hash-style optional imports:

    use Modern::Perl::Prelude {
        utf8        => 1,
        defer       => 1,
        always_true => 1,
    };

Disable native pragmata/features lexically again:

    no Modern::Perl::Prelude;
    no Modern::Perl::Prelude '-utf8';
    no Modern::Perl::Prelude { utf8 => 1 };
    no Modern::Perl::Prelude '-always_true';

=head1 DESCRIPTION

This module bundles a small, opinionated set of pragmata, features, and
compatibility layers for writing Perl in a Perl 5.40+-style while staying
runnable on Perl 5.26+.

It enables:

=over 4

lib/Modern/Perl/Prelude.pm  view on Meta::CPAN

Supported flags:

    -utf8
    -class
    -defer
    -corinna
    -always_true

Examples:

    use Modern::Perl::Prelude '-utf8';

    use Modern::Perl::Prelude qw(
        -class
        -defer
    );

    use Modern::Perl::Prelude qw(
        -class
        -utf8
        -always_true
    );

=head2 Hash-style

Hash-style arguments must be passed as a single hash reference:

    use Modern::Perl::Prelude {
        utf8        => 1,
        defer       => 1,
        always_true => 1,
    };

Supported hash keys:

=over 4

=item * C<utf8>

lib/Modern/Perl/Prelude.pm  view on Meta::CPAN

=item * C<-defer> / C<defer> enables C<defer>

=item * C<-corinna> / C<corinna> enables class syntax via C<Object::Pad>

=item * C<-always_true> / C<always_true> enables automatic true return for the current file

=back

=head1 UNIMPORT

C<no Modern::Perl::Prelude> reliably disables native pragmata/features
managed by this module:

    strict
    warnings
    say
    state
    fc
    utf8

Compatibility layers such as C<Feature::Compat::Try>,
C<Feature::Compat::Class>, C<Feature::Compat::Defer>, C<Object::Pad>, and
C<builtin::compat> are treated as import-only for cross-version use on
Perl 5.26+ and are not guaranteed to be symmetrically undone by
C<no Modern::Perl::Prelude>.

C<always_true> is an exception: C<no Modern::Perl::Prelude '-always_true'> or

    no Modern::Perl::Prelude { always_true => 1 };

disables the automatic true-return behavior for the current file.

=head1 DESIGN NOTES

This is a lexical prelude module. It is implemented via C<Import::Into> so
that pragmata and lexical functions affect the caller's scope, not the scope
of this wrapper module itself.

Optional compatibility layers are loaded lazily, only when explicitly

t/00-load.t  view on Meta::CPAN

use v5.26;
use strict;
use warnings;

use Test::More;

BEGIN {
    use_ok('Modern::Perl::Prelude');
}

done_testing;

t/01-import.t  view on Meta::CPAN

use v5.26;
use strict;
use warnings;
use utf8;

use Test::More;

my $ok = eval <<'PERL';
    package Local::Prelude::Smoke;

    use Modern::Perl::Prelude;

    our %RESULT;

    sub run {
        state $counter = 0;
        $counter++;

        my $out = '';
        {
            open my $fh, '>', \$out or die "open scalar fh failed: $!";

t/02-no.t  view on Meta::CPAN

use v5.26;
use strict;
use warnings;

use Test::More;

my $ok_say = eval <<'PERL';
    package Local::Prelude::No::Say;

    use Modern::Perl::Prelude;
    no Modern::Perl::Prelude;

    say 'hello';
    1;
PERL

ok(!$ok_say, 'say unavailable after no Modern::Perl::Prelude');

my $ok_fc = eval <<'PERL';
    package Local::Prelude::No::Fc;

    use Modern::Perl::Prelude;
    no Modern::Perl::Prelude;

    my $x = fc("Straße");
    1;
PERL

ok(!$ok_fc, 'fc unavailable after no Modern::Perl::Prelude');

my $ok_state = eval <<'PERL';
    package Local::Prelude::No::State;

    use Modern::Perl::Prelude;
    no Modern::Perl::Prelude;

    {
        state $v = 1;
    }

    1;
PERL

ok(!$ok_state, 'state unavailable after no Modern::Perl::Prelude');

done_testing;

t/03-args.t  view on Meta::CPAN

use v5.26;
use strict;
use warnings;
use utf8;

use Test::More;

my $ok_utf8 = eval <<'PERL';
    package Local::Prelude::Args::Utf8;

    use Modern::Perl::Prelude '-utf8';

    our $OK;

    my $ёж = "ёж";
    $OK = ($ёж eq "ёж") ? 1 : 0;

    1;
PERL

ok($ok_utf8, 'use Modern::Perl::Prelude -utf8 compiles')
    or diag $@;

{
    no warnings 'once';
    ok($Local::Prelude::Args::Utf8::OK, '-utf8 enables utf8 source semantics');
}

my $ok_no_utf8 = eval <<'PERL';
    package Local::Prelude::Args::NoUtf8;

    use Modern::Perl::Prelude '-utf8';
    no Modern::Perl::Prelude '-utf8';

    1;
PERL

ok($ok_no_utf8, 'no Modern::Perl::Prelude -utf8 accepts known option')
    or diag $@;

my $ok_hash_utf8 = eval <<'PERL';
    package Local::Prelude::Args::HashUtf8;

    use Modern::Perl::Prelude { utf8 => 1 };

    our $OK;

    my $ёж = "ёж";
    $OK = ($ёж eq "ёж") ? 1 : 0;

    1;
PERL

ok($ok_hash_utf8, 'hash-style utf8 option compiles')
    or diag $@;

{
    no warnings 'once';
    ok($Local::Prelude::Args::HashUtf8::OK, 'hash-style utf8 enables utf8 source semantics');
}

my $ok_hash_no_utf8 = eval <<'PERL';
    package Local::Prelude::Args::HashNoUtf8;

    use Modern::Perl::Prelude { utf8 => 1 };
    no Modern::Perl::Prelude { utf8 => 1 };

    1;
PERL

ok($ok_hash_no_utf8, 'hash-style no accepts known option')
    or diag $@;

my $ok_always_true_flag = eval <<'PERL';
    package Local::Prelude::Args::AlwaysTrueFlag;

    use Modern::Perl::Prelude '-always_true';

    1;
PERL

ok($ok_always_true_flag, 'flag-style always_true option compiles')
    or diag $@;

my $ok_always_true_hash = eval <<'PERL';
    package Local::Prelude::Args::AlwaysTrueHash;

    use Modern::Perl::Prelude { always_true => 1 };

    1;
PERL

ok($ok_always_true_hash, 'hash-style always_true option compiles')
    or diag $@;

my $ok_bad_use = eval <<'PERL';
    package Local::Prelude::Args::BadUse;

    use Modern::Perl::Prelude '-bogus';

    1;
PERL

ok(!$ok_bad_use, 'unknown option in use dies');
like(
    $@,
    qr/^Modern::Perl::Prelude: unknown import option "-bogus"/,
    'unknown option in use gives expected error',
);

my $ok_bad_no = eval <<'PERL';
    package Local::Prelude::Args::BadNo;

    use Modern::Perl::Prelude;
    no Modern::Perl::Prelude '-bogus';

    1;
PERL

ok(!$ok_bad_no, 'unknown option in no dies');
like(
    $@,
    qr/^Modern::Perl::Prelude: unknown import option "-bogus"/,
    'unknown option in no gives expected error',
);

my $ok_bad_hash_key = eval <<'PERL';
    package Local::Prelude::Args::BadHashKey;

    use Modern::Perl::Prelude { bogus => 1 };

    1;
PERL

ok(!$ok_bad_hash_key, 'unknown hash-style key dies');
like(
    $@,
    qr/^Modern::Perl::Prelude: unknown import key "bogus"/,
    'unknown hash-style key gives expected error',
);

my $ok_mixed = eval <<'PERL';
    package Local::Prelude::Args::Mixed;

    use Modern::Perl::Prelude(
        '-utf8',
        {},
    );

    1;
PERL

ok(!$ok_mixed, 'mixed flag-style and hash-style args die');
like(
    $@,
    qr/^Modern::Perl::Prelude: hash-style arguments must be passed as a single hash reference/,
    'mixed args give expected error',
);

my $ok_conflict_flags = eval <<'PERL';
    package Local::Prelude::Args::ConflictFlags;

    use Modern::Perl::Prelude qw(
        -class
        -corinna
    );

    1;
PERL

ok(!$ok_conflict_flags, 'conflicting -class and -corinna flags die');
like(
    $@,
    qr/^Modern::Perl::Prelude: options "-class" and "-corinna" are mutually exclusive/,
    'flag conflict gives expected error',
);

my $ok_conflict_hash = eval <<'PERL';
    package Local::Prelude::Args::ConflictHash;

    use Modern::Perl::Prelude {
        class   => 1,
        corinna => 1,
    };

    1;
PERL

ok(!$ok_conflict_hash, 'conflicting class/corinna hash-style args die');
like(
    $@,
    qr/^Modern::Perl::Prelude: options "-class" and "-corinna" are mutually exclusive/,
    'hash-style conflict gives expected error',
);

done_testing;

t/04-class-defer.t  view on Meta::CPAN

use Test2::Tools::Spec;

sub _run_eval {
    my ($code) = @_;
    local $@;
    my $ret = eval $code;
    my $err = $@;
    return ($ret, $err);
}

describe 'Modern::Perl::Prelude optional -class/-defer imports' => sub {
    it 'compiles and runs class and defer via flag-style imports' => sub {
        my ($ret, $err) = _run_eval(<<'PERL');
package Local::Prelude::Compat::Smoke;

use Modern::Perl::Prelude qw(
    -class
    -defer
);

class Local::Prelude::Compat::Point {
    field $x :param = 0;
    field $y :param = 0;

    method sum {
        return $x + $y;

t/04-class-defer.t  view on Meta::CPAN

];
PERL

        ok($ret, 'optional -class and -defer imports compile and run')
            or diag $err;

        is($ret->[0], 5, 'class syntax works via -class');
        is($ret->[1], 'enter,leave,defer', 'defer syntax works via -defer');
    };

    it 'accepts no Modern::Perl::Prelude with -class and -defer options' => sub {
        my ($ok, $err) = _run_eval(<<'PERL');
package Local::Prelude::Compat::No;

use Modern::Perl::Prelude qw(
    -class
    -defer
);

no Modern::Perl::Prelude qw(
    -class
    -defer
);

1;
PERL

        ok($ok, 'no Modern::Perl::Prelude accepts -class and -defer options')
            or diag $err;
    };
};

done_testing;

t/05-corinna.t  view on Meta::CPAN

use Test2::Tools::Spec;

sub _run_eval {
    my ($code) = @_;
    local $@;
    my $ret = eval $code;
    my $err = $@;
    return ($ret, $err);
}

describe 'Modern::Perl::Prelude optional corinna import' => sub {
    it 'compiles and runs hash-style corinna import with utf8' => sub {
        my ($ret, $err) = _run_eval(<<'PERL');
package Local::Prelude::Corinna::Smoke;

use Modern::Perl::Prelude {
    corinna => {},
    utf8    => 1,
};

class Local::Prelude::Corinna::Person {
    field $name :param;
    field $age  :param = 0;

    method greet {
        return "Hello, I'm $name and I'm $age years old";

t/05-corinna.t  view on Meta::CPAN

        is($ret->[1], 31, 'Object::Pad field mutation works');
        is($ret->[2], "Hello, I'm Bob and I'm 0 years old", 'default field value works');
        is($ret->[3], 'Developer', 'subclass field reader method works');
        is($ret->[4], 50_000, 'initial subclass state works');
        is($ret->[5], 55_000, 'subclass method with argument works');
        is($ret->[6], 29, 'inherited method works');
        is($ret->[7], "Hello, I'm Charlie and I'm 29 years old", 'inherited greet works');
        ok($ret->[8], 'hash-style corinna works together with utf8');
    };

    it 'accepts no Modern::Perl::Prelude with hash-style corinna option' => sub {
        my ($ok, $err) = _run_eval(<<'PERL');
package Local::Prelude::Corinna::No;

use Modern::Perl::Prelude { corinna => {} };
no Modern::Perl::Prelude { corinna => {} };

1;
PERL

        ok($ok, 'no Modern::Perl::Prelude accepts hash-style corinna option')
            or diag $err;
    };
};

done_testing;

t/06-always-true.t  view on Meta::CPAN

    return ($ok, $err);
}

my $tmp = tempdir(CLEANUP => 1);
local @INC = ($tmp, @INC);

_write_module(
    $tmp,
    'Local::AlwaysTrue::Flag',
    <<'PERL',
use Modern::Perl::Prelude qw(
    -class
    -utf8
    -always_true
);

class Local::AlwaysTrue::Flag {
    field $name :param;

    method greet {
        return "Hello, $name";

t/06-always-true.t  view on Meta::CPAN

        Local::AlwaysTrue::Flag->new(name => 'José')->greet,
        'Hello, José',
        'flag-style always_true works for a class module',
    );
}

_write_module(
    $tmp,
    'Local::AlwaysTrue::Hash',
    <<'PERL',
use Modern::Perl::Prelude {
    class       => 1,
    utf8        => 1,
    always_true => 1,
};

class Local::AlwaysTrue::Hash {
    field $name :param;

    method greet {
        return "Hi, $name";

t/06-always-true.t  view on Meta::CPAN

        Local::AlwaysTrue::Hash->new(name => 'José')->greet,
        'Hi, José',
        'hash-style always_true works for a class module',
    );
}

_write_module(
    $tmp,
    'Local::AlwaysTrue::Disabled',
    <<'PERL',
use Modern::Perl::Prelude {
    class       => 1,
    always_true => 1,
};

no Modern::Perl::Prelude { always_true => 1 };

class Local::AlwaysTrue::Disabled {
    field $name :param;
}

0;
PERL
);

my ($ok_disabled, $err_disabled) = _require_module('Local::AlwaysTrue::Disabled');
ok(!$ok_disabled, 'no Modern::Perl::Prelude { always_true => 1 } restores normal require behavior');
like(
    $err_disabled,
    qr/did not return a true value/,
    'disabled always_true makes the module fail without trailing 1',
);

done_testing;



( run in 1.814 second using v1.01-cache-2.11-cpan-98e64b0badf )