package MooX::Types::MooseLike::Base;
use strict;
use warnings FATAL => 'all';
use Scalar::Util qw(blessed);
use List::Util;
use MooX::Types::MooseLike qw( exception_message inflate_type );
use Exporter 5.57 'import';
our @EXPORT_OK = ();
our $VERSION = 0.29;
# These types act like those found in Moose::Util::TypeConstraints.
# Generally speaking, the same test is used.
sub some_basic_type_definitions {
return
(
{
name => 'Any',
test => sub { 1 },
message =>
sub { "If you get here you've achieved the impossible, congrats." }
},
{
name => 'Item',
test => sub { 1 },
message =>
sub { "If you get here you've achieved the impossible, congrats" }
},
{
name => 'Bool',
# test => sub { $_[0] == 0 || $_[0] == 1 },
test => sub {
!defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0';
},
message => sub { return exception_message($_[0], 'a Boolean') },
},
# Maybe has no test for itself, rather only the parameter type does
{
name => 'Maybe',
test => sub { 1 },
message => sub { 'Maybe only uses its parameterized type message' },
parameterizable => sub { return if (not defined $_[0]); $_[0] },
},
{
name => 'Undef',
test => sub { !defined($_[0]) },
message => sub { return exception_message($_[0], 'undef') },
},
);
}
sub defined_type_definitions {
return
({
name => 'Defined',
test => sub { defined($_[0]) },
message => sub { return exception_message($_[0], 'defined') },
},
{
name => 'Value',
test => sub { defined $_[0] and not ref($_[0]) },
message => sub { return exception_message($_[0], 'a value') },
},
{
name => 'Str',
test => sub { defined $_[0] and (ref(\$_[0]) eq 'SCALAR') },
message => sub { return exception_message($_[0], 'a string') },
},
{
name => 'Num',
test => sub {
my $val = $_[0];
defined $val and
($val =~ /\A[+-]?[0-9]+\z/) ||
( $val =~ /\A(?:[+-]?) # matches optional +- in the beginning
(?=[0-9]|\.[0-9]) # matches previous +- only if there is something like 3 or .3
[0-9]* # matches 0-9 zero or more times
(?:\.[0-9]+)? # matches optional .89 or nothing
(?:[Ee](?:[+-]?[0-9]+))? # matches E1 or e1 or e-1 or e+1 etc
\z/x );
},
message => sub {
my $nbr = shift;
if (not defined $nbr) {
$nbr = 'undef';
}
elsif (not (length $nbr)) {
$nbr = 'The empty string';
}
return exception_message($nbr, 'a number');
},
},
{
name => 'Int',
test => sub { defined $_[0] and ("$_[0]" =~ /^-?[0-9]+$/x) },
message => sub {
my $nbr = shift;
if (not defined $nbr) {
$nbr = 'undef';
}
elsif (not (length $nbr)) {
$nbr = 'The empty string';
}
return exception_message($nbr, 'an integer');
},
},
);
}
sub ref_type_definitions {
return
(
{
name => 'Ref',
test => sub { defined $_[0] and ref($_[0]) },
message => sub { return exception_message($_[0], 'a reference') },
},
{
name => 'ScalarRef',
test => sub { defined $_[0] and ref($_[0]) eq 'SCALAR' },
message => sub { return exception_message($_[0], 'a ScalarRef') },
parameterizable => sub { ${ $_[0] } },
inflate => sub {
require Moose::Util::TypeConstraints;
if (my $params = shift) {
return Moose::Util::TypeConstraints::_create_parameterized_type_constraint(
Moose::Util::TypeConstraints::find_type_constraint('ScalarRef'),
inflate_type(@$params),
);
}
return Moose::Util::TypeConstraints::find_type_constraint('ScalarRef');
},
},
{
name => 'ArrayRef',
test => sub { defined $_[0] and ref($_[0]) eq 'ARRAY' },
message => sub { return exception_message($_[0], 'an ArrayRef') },
parameterizable => sub { @{ $_[0] } },
inflate => sub {
require Moose::Util::TypeConstraints;
if (my $params = shift) {
return Moose::Util::TypeConstraints::_create_parameterized_type_constraint(
Moose::Util::TypeConstraints::find_type_constraint('ArrayRef'),
inflate_type(@$params),
);
}
return Moose::Util::TypeConstraints::find_type_constraint('ArrayRef');
},
},
{
name => 'HashRef',
test => sub { defined $_[0] and ref($_[0]) eq 'HASH' },
message => sub { return exception_message($_[0], 'a HashRef') },
parameterizable => sub { values %{ $_[0] } },
inflate => sub {
require Moose::Util::TypeConstraints;
if (my $params = shift) {
return Moose::Util::TypeConstraints::_create_parameterized_type_constraint(
Moose::Util::TypeConstraints::find_type_constraint('HashRef'),
inflate_type(@$params),
);
}
return Moose::Util::TypeConstraints::find_type_constraint('HashRef');
},
},
{
name => 'CodeRef',
test => sub { defined $_[0] and ref($_[0]) eq 'CODE' },
message => sub { return exception_message($_[0], 'a CodeRef') },
},
{
name => 'RegexpRef',
test => sub { defined $_[0] and ref($_[0]) eq 'Regexp' },
message => sub { return exception_message($_[0], 'a RegexpRef') },
},
{
name => 'GlobRef',
test => sub { defined $_[0] and ref($_[0]) eq 'GLOB' },
message => sub { return exception_message($_[0], 'a GlobRef') },
},
);
}
sub filehandle_type_definitions {
return
(
{
name => 'FileHandle',
test => sub {
defined $_[0]
and Scalar::Util::openhandle($_[0])
or (blessed($_[0]) && $_[0]->isa("IO::Handle"));
},
message => sub { return exception_message($_[0], 'a FileHandle') },
},
);
}
sub blessed_type_definitions {## no critic qw(Subroutines::ProhibitExcessComplexity)
return
(
{
name => 'Object',
test => sub { defined $_[0] and blessed($_[0]) and blessed($_[0]) ne 'Regexp' },
message => sub { return exception_message($_[0], 'an Object') },
},
{
name => 'InstanceOf',
test => sub {
my ($instance, @classes) = (shift, @_);
return if not defined $instance;
return if not blessed($instance);
my @missing_classes = grep { !$instance->isa($_) } @classes;
return (scalar @missing_classes ? 0 : 1);
},
message => sub {
my $instance = shift;
return "No instance given" if not defined $instance;
return "$instance is not blessed" if not blessed($instance);
my @missing_classes = grep { !$instance->isa($_) } @_;
my $s = (scalar @missing_classes) > 1 ? 'es' : '';
my $missing_classes = join ' ', @missing_classes;
return "$instance is not an instance of the class${s}: $missing_classes";
},
inflate => sub {
require Moose::Meta::TypeConstraint::Class;
if (my $classes = shift) {
if (@$classes == 1) {
return Moose::Meta::TypeConstraint::Class->new(class => @$classes);
}
elsif (@$classes > 1) {
return Moose::Meta::TypeConstraint->new(
parent => Moose::Util::TypeConstraints::find_type_constraint('Object'),
constraint => sub {
my $instance = shift;
my @missing_classes = grep { !$instance->isa($_) } @$classes;
return (scalar @missing_classes ? 0 : 1);
},
);
}
}
return Moose::Util::TypeConstraints::find_type_constraint('Object');
},
},
{
name => 'ConsumerOf',
test => sub {
my ($instance, @roles) = (shift, @_);
return if not defined $instance;
return if not blessed($instance);
return if (!$instance->can('does'));
my @missing_roles = grep { !$instance->does($_) } @roles;
return (scalar @missing_roles ? 0 : 1);
},
message => sub {
my $instance = shift;
return "No instance given" if not defined $instance;
return "$instance is not blessed" if not blessed($instance);
return "$instance is not a consumer of roles" if (!$instance->can('does'));
my @missing_roles = grep { !$instance->does($_) } @_;
my $s = (scalar @missing_roles) > 1 ? 's' : '';
my $missing_roles = join ' ', @missing_roles;
return "$instance does not consume the required role${s}: $missing_roles";
},
inflate => sub {
require Moose::Meta::TypeConstraint::Role;
if (my $roles = shift) {
if (@$roles == 1) {
return Moose::Meta::TypeConstraint::Role->new(role => @$roles);
}
elsif (@$roles > 1) {
return Moose::Meta::TypeConstraint->new(
parent => Moose::Util::TypeConstraints::find_type_constraint('Object'),
constraint => sub {
my $instance = shift;
return if (!$instance->can('does'));
my @missing_roles = grep { !$instance->does($_) } @$roles;
return (scalar @missing_roles ? 0 : 1);
},
);
}
}
return Moose::Util::TypeConstraints::find_type_constraint('Object');
},
},
{
name => 'HasMethods',
test => sub {
my ($instance, @methods) = (shift, @_);
return if not defined $instance;
return if not blessed($instance);
my @missing_methods = grep { !$instance->can($_) } @methods;
return (scalar @missing_methods ? 0 : 1);
},
message => sub {
my $instance = shift;
return "No instance given" if not defined $instance;
return "$instance is not blessed" if not blessed($instance);
my @missing_methods = grep { !$instance->can($_) } @_;
my $s = (scalar @missing_methods) > 1 ? 's' : '';
my $missing_methods = join ' ', @missing_methods;
return "$instance does not have the required method${s}: $missing_methods";
},
inflate => sub {
require Moose::Meta::TypeConstraint::DuckType;
if (my $methods = shift) {
return Moose::Meta::TypeConstraint::DuckType->new(methods => $methods);
}
return Moose::Util::TypeConstraints::find_type_constraint('Object');
},
},
{
name => 'Enum',
test => sub {
my ($value, @possible_values) = @_;
return if not defined $value;
return List::Util::first { $value eq $_ } @possible_values;
},
message => sub {
my ($value, @possible_values) = @_;
my $possible_values = join(', ', @possible_values);
return exception_message($value, "any of the possible values: ${possible_values}");
},
inflate => sub {
require Moose::Meta::TypeConstraint::Enum;
if (my $possible_values = shift) {
return Moose::Meta::TypeConstraint::Enum->new(values => $possible_values);
}
die "Enum cannot be inflated to a Moose type without any possible values";
},
},
);
}
sub logic_type_definitions {
return
(
{
name => 'AnyOf',
test => sub {
my ($value, @types) = @_;
foreach my $type (@types) {
return 1 if (eval {$type->($value); 1;});
}
return;
},
message => sub { return exception_message($_[0], 'any of the types') },
inflate => sub {
require Moose::Meta::TypeConstraint::Union;
if (my $types = shift) {
return Moose::Meta::TypeConstraint::Union->new(
type_constraints => [ map inflate_type($_), @$types ],
);
}
die "AnyOf cannot be inflated to a Moose type without any possible types";
},
},
{
name => 'AllOf',
test => sub { return 1; },
message => sub { 'AllOf only uses its parameterized type messages' },
parameterizable => sub { $_[0] },
inflate => 0,
},
);
}
sub type_definitions {
return
[
some_basic_type_definitions()
,defined_type_definitions()
,ref_type_definitions()
,filehandle_type_definitions()
,blessed_type_definitions()
,logic_type_definitions()
];
}
MooX::Types::MooseLike::register_types(type_definitions(), __PACKAGE__);
# Export an 'all' tag so one can easily import all types like so:
# use MooX::Types::MooseLike::Base qw(:all)
our %EXPORT_TAGS = ('all' => \@EXPORT_OK);
1;
__END__
=head1 NAME
MooX::Types::MooseLike::Base - A set of basic Moose-like types for Moo
=head1 SYNOPSIS
package MyPackage;
use Moo;
use MooX::Types::MooseLike::Base qw(:all);
has "beers_by_day_of_week" => (
isa => HashRef
);
has "current_BAC" => (
isa => Num
);
# Also supporting is_$type. For example, is_Int() can be used as follows
has 'legal_age' => (
is => 'ro',
isa => sub { die "$_[0] is not of legal age"
unless (is_Int($_[0]) && $_[0] > 17) },
);
=head1 DESCRIPTION
Moo attributes (like Moose) have an 'isa' property.
This module provides some basic types for this property.
One can import all types with ':all' tag or import
a list of types like:
use MooX::Types::MooseLike::Base qw/HashRef ArrayRef/;
so one could then declare some attributes like:
has 'contact' => (
is => 'ro',
isa => HashRef,
);
has 'guest_list' => (
is => 'ro',
isa => ArrayRef[HashRef],
);
These types provide a check that the I<contact> attribute is a C<hash> reference,
and that the I<guest_list> is an C<array of hash> references.
=head1 TYPES (1st class functions - return a coderef)
=head2 Any
Any type (test is always true)
=head2 Item
Synonymous with Any type
=head2 Undef
A type that is not defined
=head2 Defined
A type that is defined
=head2 Bool
A boolean 1|0 type
=head2 Value
A non-reference type
=head2 Ref
A reference type
=head2 Str
A non-reference type where a reference to it is a SCALAR
=head2 Num
A number type
=head2 Int
An integer type
=head2 ArrayRef
An ArrayRef (ARRAY) type
=head2 HashRef
A HashRef (HASH) type
=head2 CodeRef
A CodeRef (CODE) type
=head2 RegexpRef
A regular expression reference type
=head2 GlobRef
A glob reference type
=head2 FileHandle
A type that is either a builtin perl filehandle or an IO::Handle object
=head2 Object
A type that is an object (think blessed)
=head1 PARAMETERIZED TYPES
=head2 Parameterizing Types With a Single Type
The following types can be parameterized with other types.
=head3 ArrayRef
For example, ArrayRef[HashRef]
=head3 HashRef
=head3 ScalarRef
=head3 Maybe
For example, Maybe[Int] would be an integer or undef
=head2 Parameterizing Types With Multiple Types
=head3 AnyOf
Check if the attribute is any of the listed types (think union).
Takes a list of types as the argument, for example:
isa => AnyOf[Int, ArrayRef[Int], HashRef[Int]]
Note: AnyOf is passed an ArrayRef[CodeRef]
=head3 AllOf
Check if the attribute is all of the listed types (think intersection).
Takes a list of types as the argument. For example:
isa => AllOf[
InstanceOf['Human'],
ConsumerOf['Air'],
HasMethods['breath', 'dance']
],
=head2 Parameterizing Types With (Multiple) Strings
In addition, we have some parameterized types that take string arguments.
=head3 InstanceOf
Check if the attribute is an object instance of one or more classes.
Uses C<blessed> and C<isa> to do so.
Takes a list of class names as the argument. For example:
isa => InstanceOf['MyClass','MyOtherClass']
Note: InstanceOf is passed an ArrayRef[Str]
=head3 ConsumerOf
Check if the attribute is blessed and consumes one or more roles.
Uses C<blessed> and C<does> to do so.
Takes a list of role names as the arguments. For example:
isa => ConsumerOf['My::Role', 'My::AnotherRole']
=head3 HasMethods
Check if the attribute is blessed and has one or more methods.
Uses C<blessed> and C<can> to do so.
Takes a list of method names as the arguments. For example:
isa => HasMethods[qw/postulate contemplate liberate/]
=head3 Enum
Check if the attribute is one of the enumerated strings.
Takes a list of possible string values. For example:
isa => Enum['rock', 'spock', 'paper', 'lizard', 'scissors']
=head1 SEE ALSO
L<MooX::Types::MooseLike::Numeric> - an example of building subtypes.
L<MooX::Types::SetObject> - an example of building parameterized types.
L<MooX::Types::MooseLike::Email>, L<MooX::Types::MooseLike::DateTime>
=head1 AUTHOR
Mateu Hunter C<hunter@missoula.org>
=head1 THANKS
mst has provided critical guidance on the design
=head1 COPYRIGHT
Copyright 2011-2015 Mateu Hunter
=head1 LICENSE
You may distribute this code under the same terms as Perl itself.
=cut