Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

View File

@@ -0,0 +1,324 @@
package Specio::Coercion;
use strict;
use warnings;
our $VERSION = '0.46';
use Specio::OO;
use Role::Tiny::With;
use Specio::Role::Inlinable;
with 'Specio::Role::Inlinable';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $role_attrs = Specio::Role::Inlinable::_attrs();
## use critic
my $attrs = {
%{$role_attrs},
from => {
does => 'Specio::Constraint::Role::Interface',
required => 1,
},
to => {
does => 'Specio::Constraint::Role::Interface',
required => 1,
weak_ref => 1,
},
_coercion => {
isa => 'CodeRef',
predicate => '_has_coercion',
init_arg => 'coercion',
},
_optimized_coercion => {
isa => 'CodeRef',
init_arg => undef,
lazy => 1,
builder => '_build_optimized_coercion',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub BUILD {
my $self = shift;
die
'A type coercion should have either a coercion or inline_generator parameter, not both'
if $self->_has_coercion && $self->_has_inline_generator;
die
'A type coercion must have either a coercion or inline_generator parameter'
unless $self->_has_coercion || $self->_has_inline_generator;
return;
}
sub coerce {
my $self = shift;
my $value = shift;
return $self->_optimized_coercion->($value);
}
sub inline_coercion {
my $self = shift;
return $self->_inline_generator->( $self, @_ );
}
sub _build_optimized_coercion {
my $self = shift;
if ( $self->_has_inline_generator ) {
return $self->_generated_inline_sub;
}
else {
return $self->_coercion;
}
}
sub can_be_inlined {
my $self = shift;
return $self->_has_inline_generator && $self->from->can_be_inlined;
}
sub _build_description {
my $self = shift;
my $from_name
= defined $self->from->name ? $self->from->name : 'anonymous type';
my $to_name
= defined $self->to->name ? $self->to->name : 'anonymous type';
my $desc = "coercion from $from_name to $to_name";
$desc .= q{ } . $self->declared_at->description;
return $desc;
}
sub clone_with_new_to {
my $self = shift;
my $new_to = shift;
my $from = $self->from;
local $self->{from} = undef;
local $self->{to} = undef;
my $clone = $self->clone;
$clone->{from} = $from;
$clone->{to} = $new_to;
return $clone;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class representing a coercion from one type to another
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Coercion - A class representing a coercion from one type to another
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $coercion = $type->coercion_from_type('Int');
my $new_value = $coercion->coerce_value(42);
if ( $coercion->can_be_inlined() ) {
my $code = $coercion->inline_coercion('$_[0]');
}
=head1 DESCRIPTION
This class represents a coercion from one type to another. Internally, a
coercion is a piece of code that takes a value of one type returns a new value
of a new type. For example, a coercion from c<Num> to C<Int> might round a
number to its nearest integer and return that integer.
Coercions can be implemented either as a simple subroutine reference or as an
inline generator subroutine. Using an inline generator is faster but more
complicated.
=for Pod::Coverage BUILD clone_with_new_to
=head1 API
This class provides the following methods.
=head2 Specio::Coercion->new( ... )
This method creates a new coercion object. It accepts the following named
parameters:
=over 4
=item * from => $type
The type this coercion is from. The type must be an object which does the
L<Specio::Constraint::Role::Interface> interface.
This parameter is required.
=item * to => $type
The type this coercion is to. The type must be an object which does the
L<Specio::Constraint::Role::Interface> interface.
This parameter is required.
=item * coercion => sub { ... }
A subroutine reference implementing the coercion. It will be called as a
method on the object and passed a single argument, the value to coerce.
It should return the new value.
This parameter is mutually exclusive with C<inline_generator>.
Either this parameter or the C<inline_generator> parameter is required.
You can also pass this option with the key C<using> in the parameter list.
=item * inline_generator => sub { ... }
This should be a subroutine reference which returns a string containing a
single term. This code should I<not> end in a semicolon. This code should
implement the coercion.
The generator will be called as a method on the coercion with a single
argument. That argument is the name of the variable being coerced, something
like C<'$_[0]'> or C<'$var'>.
This parameter is mutually exclusive with C<coercion>.
Either this parameter or the C<coercion> parameter is required.
You can also pass this option with the key C<inline> in the parameter list.
=item * inline_environment => {}
This should be a hash reference of variable names (with sigils) and values for
that variable. The values should be I<references> to the values of the
variables.
This environment will be used when compiling the coercion as part of a
subroutine. The named variables will be captured as closures in the generated
subroutine, using L<Eval::Closure>.
It should be very rare to need to set this in the constructor. It's more
likely that a special coercion subclass would need to provide values that it
generates internally.
This parameter defaults to an empty hash reference.
=item * declared_at => $declared_at
This parameter must be a L<Specio::DeclaredAt> object.
This parameter is required.
=back
=head2 $coercion->from(), $coercion->to(), $coercion->declared_at()
These methods are all read-only attribute accessors for the corresponding
attribute.
=head2 $coercion->description
This returns a string describing the coercion. This includes the names of the
to and from type and where the coercion was declared, so you end up with
something like C<'coercion from Foo to Bar declared in package My::Lib
(lib/My/Lib.pm) at line 42'>.
=head2 $coercion->coerce($value)
Given a value of the right "from" type, returns a new value of the "to" type.
This method does not actually check that the types of given or return values.
=head2 $coercion->inline_coercion($var)
Given a variable name like C<'$_[0]'> this returns a string with code for the
coercion.
Note that this method will die if the coercion does not have an inline
generator.
=head2 $coercion->can_be_inlined()
This returns true if the coercion has an inline generator I<and> the
constraint it is from can be inlined. This exists primarily for the benefit of
the C<inline_coercion_and_check()> method for type constraint object.
=head2 $coercion->inline_environment()
This returns a hash defining the variables that need to be closed over when
inlining the coercion. The keys are full variable names like C<'$foo'> or
C<'@bar'>. The values are I<references> to a variable of the matching type.
=head2 $coercion->clone()
Returns a clone of this object.
=head2 $coercion->clone_with_new_to($new_to_type)
This returns a clone of the coercion, replacing the "to" type with a new
one. This is intended for use when the to type itself is being cloned as part
of importing that type. We need to make sure the newly cloned coercion has the
newly cloned type as well.
=head1 ROLES
This class does the L<Specio::Role::Inlinable> role.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,150 @@
package Specio::Constraint::AnyCan;
use strict;
use warnings;
our $VERSION = '0.46';
use List::Util 1.33 ();
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::CanType;
with 'Specio::Constraint::Role::CanType';
{
my $Defined = t('Defined');
sub _build_parent {$Defined}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
my $methods = join ', ', map { perlstring($_) } @{ $self->methods };
return sprintf( <<'EOF', $val, $methods );
(
do {
# We need to assign this since if it's something like $_[0] then
# inside the all block @_ gets redefined and we can no longer get at
# the value.
my $v = %s;
(
Scalar::Util::blessed($v) || (
defined($v)
&& !ref($v)
&& length($v)
&& $v !~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
# Passing a GLOB from (my $glob = *GLOB) gives us a very weird
# scalar. It's not a ref and it has a length but trying to
# call ->can on it throws an exception
&& ref( \$v ) ne 'GLOB'
)
) && List::Util::all { $v->can($_) } %s;
}
)
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {1}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require a class name or object with a set of methods
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::AnyCan - A class for constraints which require a class name or object with a set of methods
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::AnyCan->new(...);
print $_, "\n" for @{ $type->methods };
=head1 DESCRIPTION
This is a specialized type constraint class for types which require a class
name or object with a defined set of methods.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::AnyCan->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Defined> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<methods>. This must be
an array reference of method names which the constraint requires. You can also
pass a single string and it will be converted to an array reference
internally.
=head2 $any_can->methods
Returns an array reference containing the methods this constraint requires.
=head1 ROLES
This class does the L<Specio::Constraint::Role::IsaType>,
L<Specio::Constraint::Role::Interface>, and L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,139 @@
package Specio::Constraint::AnyDoes;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::DoesType;
with 'Specio::Constraint::Role::DoesType';
{
my $Defined = t('Defined');
sub _build_parent {$Defined}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
return sprintf( <<'EOF', ($val) x 8, perlstring( $self->role ) );
(
(
Scalar::Util::blessed(%s) || (
!ref(%s)
&& defined(%s)
&& length(%s)
&& %s !~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
&& ref( \%s ) ne 'GLOB'
)
)
&& %s->can('does')
&& %s->does(%s)
)
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {1}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require a class name or an object that does a specific role
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::AnyDoes - A class for constraints which require a class name or an object that does a specific role
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::AnyDoes->new(...);
print $type->role;
=head1 DESCRIPTION
This is a specialized type constraint class for types which require a class
name or an object that does a specific role.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::AnyDoes->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Defined> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<role>. This must be a
single role name.
=head2 $any_isa->role
Returns the role name passed to the constructor.
=head1 ROLES
This class does the L<Specio::Constraint::Role::DoesType>,
L<Specio::Constraint::Role::Interface>, L<Specio::Role::Inlinable>, and
L<MooseX::Clone> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,142 @@
package Specio::Constraint::AnyIsa;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::IsaType;
with 'Specio::Constraint::Role::IsaType';
{
my $Defined = t('Defined');
sub _build_parent {$Defined}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
return sprintf( <<'EOF', ($val) x 7, perlstring( $self->class ) );
(
(
Scalar::Util::blessed(%s)
|| (
defined(%s)
&& !ref(%s)
&& length(%s)
&& %s !~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
# Passing a GLOB from (my $glob = *GLOB) gives us a very weird
# scalar. It's not a ref and it has a length but trying to
# call ->can on it throws an exception
&& ref( \%s ) ne 'GLOB'
)
)
&& %s->isa(%s)
)
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {1}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require a class name or an object that inherit from a specific class
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::AnyIsa - A class for constraints which require a class name or an object that inherit from a specific class
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::AnyIsa->new(...);
print $type->class;
=head1 DESCRIPTION
This is a specialized type constraint class for types which require a class
name or an object that inherit from a specific class.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::AnyIsa->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Defined> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<class>. This must be a
single class name.
=head2 $any_isa->class
Returns the class name passed to the constructor.
=head1 ROLES
This class does the L<Specio::Constraint::Role::IsaType>,
L<Specio::Constraint::Role::Interface>, and L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,151 @@
package Specio::Constraint::Enum;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Scalar::Util qw( refaddr );
use Specio::Library::Builtins;
use Specio::OO;
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( parent _inline_generator )) {
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
$attrs->{values} = {
isa => 'ArrayRef',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
{
my $Str = t('Str');
sub _build_parent {$Str}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
return sprintf( <<'EOF', ($val) x 2, $self->_env_var_name, $val );
( !ref( %s ) && defined( %s ) && $%s{ %s } )
EOF
};
sub _build_inline_generator {$_inline_generator}
}
sub _build_inline_environment {
my $self = shift;
my %values = map { $_ => 1 } @{ $self->values };
return { '%' . $self->_env_var_name => \%values };
}
sub _env_var_name {
my $self = shift;
return '_Specio_Constraint_Enum_' . refaddr($self);
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require a string matching one of a set of values
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Enum - A class for constraints which require a string matching one of a set of values
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::Enum->new(...);
print $_, "\n" for @{ $type->values };
=head1 DESCRIPTION
This is a specialized type constraint class for types which require a string
that matches one of a list of values.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::Enum->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Str> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
Finally, this class requires an additional parameter, C<values>. This must be a
an arrayref of valid strings for the type.
=head2 $enum->values
Returns an array reference of valid values for the type.
=head1 ROLES
This class does the L<Specio::Constraint::Role::Interface> and
L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,199 @@
package Specio::Constraint::Intersection;
use strict;
use warnings;
our $VERSION = '0.46';
use List::Util qw( all );
use Role::Tiny::With;
use Specio::OO;
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( _constraint _inline_generator )) {
delete $attrs->{$name}{predicate};
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{lazy} = 1;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
delete $attrs->{parent};
delete $attrs->{name}{predicate};
$attrs->{name}{lazy} = 1;
$attrs->{name}{builder} = '_build_name';
$attrs->{of} = {
isa => 'ArrayRef',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub parent {undef}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _has_parent {0}
sub _has_name {
my $self = shift;
return defined $self->name;
}
sub _build_name {
my $self = shift;
return unless all { $_->_has_name } @{ $self->of };
return join q{ & }, map { $_->name } @{ $self->of };
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _has_constraint {
my $self = shift;
return !$self->_has_inline_generator;
}
## use critic
sub _build_constraint {
return $_[0]->_optimized_constraint;
}
sub _build_optimized_constraint {
my $self = shift;
## no critic (Subroutines::ProtectPrivateSubs)
my @c = map { $_->_optimized_constraint } @{ $self->of };
return sub {
return all { $_->( $_[0] ) } @c;
};
}
sub _has_inline_generator {
my $self = shift;
## no critic (Subroutines::ProtectPrivateSubs)
return all { $_->_has_inline_generator } @{ $self->of };
}
sub _build_inline_generator {
my $self = shift;
return sub {
return '(' . (
join q{ && },
map { sprintf( '( %s )', $_->_inline_generator->( $_, $_[1] ) ) }
@{ $self->of }
) . ')';
}
}
sub _build_inline_environment {
my $self = shift;
my %env;
for my $type ( @{ $self->of } ) {
%env = (
%env,
%{ $type->inline_environment },
);
}
return \%env;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for intersection constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Intersection - A class for intersection constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::Untion->new(...);
=head1 DESCRIPTION
This is a specialized type constraint class for intersections, which will
allow a value which matches each one of several distinct types.
=for Pod::Coverage parent
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::Intersection->new( ... )
The C<parent> parameter is ignored if it passed, as it is always C<undef>
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
Finally, this class requires an additional parameter, C<of>. This must be an
arrayref of type objects.
=head2 $union->of
Returns an array reference of the individual types which makes up this
intersection.
=head1 ROLES
This class does the L<Specio::Constraint::Role::Interface> and
L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,131 @@
package Specio::Constraint::ObjectCan;
use strict;
use warnings;
our $VERSION = '0.46';
use List::Util 1.33 ();
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::CanType;
with 'Specio::Constraint::Role::CanType';
{
my $Object = t('Object');
sub _build_parent {$Object}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
my $methods = join ', ', map { perlstring($_) } @{ $self->methods };
return sprintf( <<'EOF', $val, $methods );
(
do {
my $v = %s;
Scalar::Util::blessed($v)
&& List::Util::all { $v->can($_) } %s;
}
)
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {0}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require an object with a set of methods
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::ObjectCan - A class for constraints which require an object with a set of methods
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::ObjectCan->new(...);
print $_, "\n" for @{ $type->methods };
=head1 DESCRIPTION
This is a specialized type constraint class for types which require an object
with a defined set of methods.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::ObjectCan->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Object> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<methods>. This must be
an array reference of method names which the constraint requires. You can also
pass a single string and it will be converted to an array reference
internally.
=head2 $object_can->methods
Returns an array reference containing the methods this constraint requires.
=head1 ROLES
This class does the L<Specio::Constraint::Role::CanType>,
L<Specio::Constraint::Role::Interface>, and L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,121 @@
package Specio::Constraint::ObjectDoes;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::DoesType;
with 'Specio::Constraint::Role::DoesType';
{
my $Object = t('Object');
sub _build_parent {$Object}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
return sprintf( <<'EOF', ($val) x 3, perlstring( $self->role ) );
( Scalar::Util::blessed(%s) && %s->can('does') && %s->does(%s) )
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {0}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require an object that does a specific role
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::ObjectDoes - A class for constraints which require an object that does a specific role
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::ObjectDoes->new(...);
print $type->role;
=head1 DESCRIPTION
This is a specialized type constraint class for types which require an object
that does a specific role.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::ObjectDoes->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Defined> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<role>. This must be a
single role name.
=head2 $object_isa->role
Returns the role name passed to the constructor.
=head1 ROLES
This class does the L<Specio::Constraint::Role::DoesType>,
L<Specio::Constraint::Role::Interface>, and L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,121 @@
package Specio::Constraint::ObjectIsa;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Scalar::Util ();
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::OO;
use Specio::Constraint::Role::IsaType;
with 'Specio::Constraint::Role::IsaType';
{
my $Object = t('Object');
sub _build_parent {$Object}
}
{
my $_inline_generator = sub {
my $self = shift;
my $val = shift;
return sprintf( <<'EOF', $val, $val, perlstring( $self->class ) );
( Scalar::Util::blessed( %s ) && %s->isa(%s) )
EOF
};
sub _build_inline_generator {$_inline_generator}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _allow_classes {0}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for constraints which require an object that inherits from a specific class
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::ObjectIsa - A class for constraints which require an object that inherits from a specific class
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::ObjectIsa->new(...);
print $type->class;
=head1 DESCRIPTION
This is a specialized type constraint class for types which require an object
that inherits from a specific class.
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::ObjectIsa->new( ... )
The C<parent> parameter is ignored if it passed, as it is always set to the
C<Defined> type.
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
This class overrides the C<message_generator> default if none is provided.
Finally, this class requires an additional parameter, C<class>. This must be a
single class name.
=head2 $object_isa->class
Returns the class name passed to the constructor.
=head1 ROLES
This class does the L<Specio::Constraint::Role::IsaType>,
L<Specio::Constraint::Role::Interface>, and L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,209 @@
package Specio::Constraint::Parameterizable;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use Role::Tiny::With;
use Specio::Constraint::Parameterized;
use Specio::DeclaredAt;
use Specio::OO;
use Specio::TypeChecks qw( does_role isa_class );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $role_attrs = Specio::Constraint::Role::Interface::_attrs();
## use critic
my $attrs = {
%{$role_attrs},
_parameterized_constraint_generator => {
isa => 'CodeRef',
init_arg => 'parameterized_constraint_generator',
predicate => '_has_parameterized_constraint_generator',
},
_parameterized_inline_generator => {
isa => 'CodeRef',
init_arg => 'parameterized_inline_generator',
predicate => '_has_parameterized_inline_generator',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub BUILD {
my $self = shift;
if ( $self->_has_constraint ) {
die
'A parameterizable constraint with a constraint parameter must also have a parameterized_constraint_generator'
unless $self->_has_parameterized_constraint_generator;
}
if ( $self->_has_inline_generator ) {
die
'A parameterizable constraint with an inline_generator parameter must also have a parameterized_inline_generator'
unless $self->_has_parameterized_inline_generator;
}
return;
}
sub parameterize {
my $self = shift;
my %args = @_;
my ( $parameter, $declared_at ) = @args{qw( of declared_at )};
does_role( $parameter, 'Specio::Constraint::Role::Interface' )
or confess
'The "of" parameter passed to ->parameterize must be an object which does the Specio::Constraint::Role::Interface role';
if ($declared_at) {
isa_class( $declared_at, 'Specio::DeclaredAt' )
or confess
'The "declared_at" parameter passed to ->parameterize must be a Specio::DeclaredAt object';
}
$declared_at = Specio::DeclaredAt->new_from_caller(1)
unless defined $declared_at;
my %p = (
parent => $self,
parameter => $parameter,
declared_at => $declared_at,
);
if ( $self->_has_parameterized_constraint_generator ) {
$p{constraint}
= $self->_parameterized_constraint_generator->($parameter);
}
else {
confess
'The "of" parameter passed to ->parameterize must be an inlinable constraint if the parameterizable type has an inline_generator'
unless $parameter->can_be_inlined;
my $ig = $self->_parameterized_inline_generator;
$p{inline_generator} = sub { $ig->( shift, $parameter, @_ ) };
}
return Specio::Constraint::Parameterized->new(%p);
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class which represents parameterizable constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Parameterizable - A class which represents parameterizable constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $arrayref = t('ArrayRef');
my $arrayref_of_int = $arrayref->parameterize( of => t('Int') );
=head1 DESCRIPTION
This class implements the API for parameterizable types like C<ArrayRef> and
C<Maybe>.
=for Pod::Coverage BUILD
=head1 API
This class implements the same API as L<Specio::Constraint::Simple>, with a few
additions.
=head2 Specio::Constraint::Parameterizable->new(...)
This class's constructor accepts two additional parameters:
=over 4
=item * parameterized_constraint_generator
This is a subroutine that generates a new constraint subroutine when the type
is parameterized.
It will be called as a method on the type and will be passed a single
argument, the type object for the type parameter.
This parameter is mutually exclusive with the
C<parameterized_inline_generator> parameter.
=item * parameterized_inline_generator
This is a subroutine that generates a new inline generator subroutine when the
type is parameterized.
It will be called as a method on the L<Specio::Constraint::Parameterized>
object when that object needs to generate an inline constraint. It will
receive the type parameter as the first argument and the variable name as a
string as the second.
This probably seems fairly confusing, so looking at the examples in the
L<Specio::Library::Builtins> code may be helpful.
This parameter is mutually exclusive with the
C<parameterized_constraint_generator> parameter.
=back
=head2 $type->parameterize(...)
This method takes two arguments. The C<of> argument should be an object which
does the L<Specio::Constraint::Role::Interface> role, and is required.
The other argument, C<declared_at>, is optional. If it is not given, then a
new L<Specio::DeclaredAt> object is creating using a call stack depth of 1.
This method returns a new L<Specio::Constraint::Parameterized> object.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,156 @@
package Specio::Constraint::Parameterized;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Specio::OO;
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
$attrs->{parent}{isa} = 'Specio::Constraint::Parameterizable';
$attrs->{parent}{required} = 1;
delete $attrs->{name}{predicate};
$attrs->{name}{lazy} = 1;
$attrs->{name}{builder} = '_build_name';
$attrs->{parameter} = {
does => 'Specio::Constraint::Role::Interface',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub _has_name {
my $self = shift;
return defined $self->name;
}
sub _build_name {
my $self = shift;
## no critic (Subroutines::ProtectPrivateSubs)
return unless $self->parent->_has_name && $self->parameter->_has_name;
return $self->parent->name . '[' . $self->parameter->name . ']';
}
sub can_be_inlined {
my $self = shift;
return $self->_has_inline_generator
&& $self->parameter->can_be_inlined;
}
# Moose compatibility methods - these exist as a temporary hack to make Specio
# work with Moose.
sub type_parameter {
shift->parameter;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class which represents parameterized constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Parameterized - A class which represents parameterized constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $arrayref = t('ArrayRef');
my $arrayref_of_int = $arrayref->parameterize( of => t('Int') );
my $parent = $arrayref_of_int->parent; # returns ArrayRef
my $parameter = $arrayref_of_int->parameter; # returns Int
=head1 DESCRIPTION
This class implements the API for parameterized types.
=for Pod::Coverage can_be_inlined type_parameter
=head1 API
This class implements the same API as L<Specio::Constraint::Simple>, with a few
additions.
=head2 Specio::Constraint::Parameterized->new(...)
This class's constructor accepts two additional parameters:
=over 4
=item * parent
This should be the L<Specio::Constraint::Parameterizable> object from which this
object was created.
This parameter is required.
=item * parameter
This is the type parameter for the parameterized type. This must be an object
which does the L<Specio::Constraint::Role::Interface> role.
This parameter is required.
=back
=head2 $type->parameter
Returns the type that was passed to the constructor.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,178 @@
package Specio::Constraint::Role::CanType;
use strict;
use warnings;
our $VERSION = '0.46';
use Scalar::Util qw( blessed );
use Specio::PartialDump qw( partial_dump );
use Storable qw( dclone );
use Role::Tiny;
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( parent _inline_generator )) {
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
$attrs->{methods} = {
isa => 'ArrayRef',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _wrap_message_generator {
my $self = shift;
my $generator = shift;
my $type = ( split /::/, blessed $self)[-1];
my @methods = @{ $self->methods };
my $all_word_list = _word_list(@methods);
my $allow_classes = $self->_allow_classes;
unless ( defined $generator ) {
$generator = sub {
shift;
my $value = shift;
return
"An undef will never pass an $type check (wants $all_word_list)"
unless defined $value;
my $class = blessed $value;
if ( !defined $class ) {
# If we got here we know that blessed returned undef, so if
# it's a ref then it must not be blessed.
if ( ref $value ) {
my $dump = partial_dump($value);
return
"An unblessed reference ($dump) will never pass an $type check (wants $all_word_list)";
}
# If it's defined and not an unblessed ref it must be a
# string. If we allow classes (vs just objects) then it might
# be a valid class name. But an empty string is never a valid
# class name. We cannot call q{}->can.
return
"An empty string will never pass an $type check (wants $all_word_list)"
unless length $value;
if ( ref \$value eq 'GLOB' ) {
return
"A glob will never pass an $type check (wants $all_word_list)";
}
if (
$value =~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
) {
return
"A number ($value) will never pass an $type check (wants $all_word_list)";
}
$class = $value if $allow_classes;
# At this point we either have undef or a non-empty string in
# $class.
unless ( defined $class ) {
my $dump = partial_dump($value);
return
"A plain scalar ($dump) will never pass an $type check (wants $all_word_list)";
}
}
my @missing = grep { !$value->can($_) } @methods;
my $noun = @missing == 1 ? 'method' : 'methods';
my $list = _word_list( map {qq['$_']} @missing );
return "The $class class is missing the $list $noun";
};
}
return sub { $generator->( undef, @_ ) };
}
## use critic
sub _word_list {
my @items = sort { $a cmp $b } @_;
return $items[0] if @items == 1;
return join ' and ', @items if @items == 2;
my $final = pop @items;
my $list = join ', ', @items;
$list .= ', and ' . $final;
return $list;
}
1;
# ABSTRACT: Provides a common implementation for Specio::Constraint::AnyCan and Specio::Constraint::ObjectCan
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Role::CanType - Provides a common implementation for Specio::Constraint::AnyCan and Specio::Constraint::ObjectCan
=head1 VERSION
version 0.46
=head1 DESCRIPTION
See L<Specio::Constraint::AnyCan> and L<Specio::Constraint::ObjectCan> for details.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,144 @@
package Specio::Constraint::Role::DoesType;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny;
use Scalar::Util qw( blessed );
use Specio::PartialDump qw( partial_dump );
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( parent _inline_generator )) {
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
$attrs->{role} = {
isa => 'Str',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _wrap_message_generator {
my $self = shift;
my $generator = shift;
my $type = ( split /::/, blessed $self)[-1];
my $role = $self->role;
my $allow_classes = $self->_allow_classes;
unless ( defined $generator ) {
$generator = sub {
shift;
my $value = shift;
return "An undef will never pass an $type check (wants $role)"
unless defined $value;
if ( ref $value && !blessed $value ) {
my $dump = partial_dump($value);
return
"An unblessed reference ($dump) will never pass an $type check (wants $role)";
}
if ( !blessed $value) {
return
"An empty string will never pass an $type check (wants $role)"
unless length $value;
if (
$value =~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
) {
return
"A number ($value) will never pass an $type check (wants $role)";
}
if ( !$allow_classes ) {
my $dump = partial_dump($value);
return
"A plain scalar ($dump) will never pass an $type check (wants $role)";
}
}
my $got = blessed $value;
$got ||= $value;
return "The $got class does not consume the $role role";
};
}
return sub { $generator->( undef, @_ ) };
}
## use critic
1;
# ABSTRACT: Provides a common implementation for Specio::Constraint::AnyDoes and Specio::Constraint::ObjectDoes
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Role::DoesType - Provides a common implementation for Specio::Constraint::AnyDoes and Specio::Constraint::ObjectDoes
=head1 VERSION
version 0.46
=head1 DESCRIPTION
See L<Specio::Constraint::AnyDoes> and L<Specio::Constraint::ObjectDoes> for
details.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,667 @@
package Specio::Constraint::Role::Interface;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use Eval::Closure qw( eval_closure );
use List::Util 1.33 qw( all any first );
use Specio::Exception;
use Specio::PartialDump qw( partial_dump );
use Specio::TypeChecks qw( is_CodeRef );
use Role::Tiny 1.003003;
use Specio::Role::Inlinable;
with 'Specio::Role::Inlinable';
use overload(
q{""} => sub { $_[0] },
'&{}' => '_subification',
'bool' => sub {1},
);
{
## no critic (Subroutines::ProtectPrivateSubs)
my $role_attrs = Specio::Role::Inlinable::_attrs();
## use critic
my $attrs = {
%{$role_attrs},
name => {
isa => 'Str',
predicate => '_has_name',
},
parent => {
does => 'Specio::Constraint::Role::Interface',
predicate => '_has_parent',
},
_constraint => {
isa => 'CodeRef',
init_arg => 'constraint',
predicate => '_has_constraint',
},
_optimized_constraint => {
isa => 'CodeRef',
init_arg => undef,
lazy => 1,
builder => '_build_optimized_constraint',
},
_ancestors => {
isa => 'ArrayRef',
init_arg => undef,
lazy => 1,
builder => '_build_ancestors',
},
_message_generator => {
isa => 'CodeRef',
init_arg => undef,
},
_coercions => {
builder => '_build_coercions',
clone => '_clone_coercions',
},
_subification => {
init_arg => undef,
lazy => 1,
builder => '_build_subification',
},
# Because types are cloned on import, we can't directly compare type
# objects. Because type names can be reused between packages (no global
# registry) we can't compare types based on name either.
_signature => {
isa => 'Str',
init_arg => undef,
lazy => 1,
builder => '_build_signature',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
my $NullConstraint = sub {1};
# See Specio::OO to see how this is used.
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _Specio_Constraint_Role_Interface_BUILD {
my $self = shift;
my $p = shift;
unless ( $self->_has_constraint || $self->_has_inline_generator ) {
$self->{_constraint} = $NullConstraint;
}
die
'A type constraint should have either a constraint or inline_generator parameter, not both'
if $self->_has_constraint && $self->_has_inline_generator;
$self->{_message_generator}
= $self->_wrap_message_generator( $p->{message_generator} );
return;
}
## use critic
sub _wrap_message_generator {
my $self = shift;
my $generator = shift;
unless ( defined $generator ) {
$generator = sub {
my $description = shift;
my $value = shift;
return "Validation failed for $description with value "
. partial_dump($value);
};
}
my $d = $self->description;
return sub { $generator->( $d, @_ ) };
}
sub coercions { values %{ $_[0]->{_coercions} } }
sub coercion_from_type { $_[0]->{_coercions}{ $_[1] } }
sub _has_coercion_from_type { exists $_[0]->{_coercions}{ $_[1] } }
sub _add_coercion { $_[0]->{_coercions}{ $_[1] } = $_[2] }
sub has_coercions { scalar keys %{ $_[0]->{_coercions} } }
sub validate_or_die {
my $self = shift;
my $value = shift;
return if $self->value_is_valid($value);
Specio::Exception->throw(
message => $self->_message_generator->($value),
type => $self,
value => $value,
);
}
sub value_is_valid {
my $self = shift;
my $value = shift;
return $self->_optimized_constraint->($value);
}
sub _ancestors_and_self {
my $self = shift;
return ( ( reverse @{ $self->_ancestors } ), $self );
}
sub is_a_type_of {
my $self = shift;
my $type = shift;
return any { $_->_signature eq $type->_signature }
$self->_ancestors_and_self;
}
sub is_same_type_as {
my $self = shift;
my $type = shift;
return $self->_signature eq $type->_signature;
}
sub is_anon {
my $self = shift;
return !$self->_has_name;
}
sub has_real_constraint {
my $self = shift;
return ( $self->_has_constraint && $self->_constraint ne $NullConstraint )
|| $self->_has_inline_generator;
}
sub can_be_inlined {
my $self = shift;
return 1 if $self->_has_inline_generator;
return 0
if $self->_has_constraint && $self->_constraint ne $NullConstraint;
# If this type is an empty subtype of an inlinable parent, then we can
# inline this type as well.
return 1 if $self->_has_parent && $self->parent->can_be_inlined;
return 0;
}
sub _build_generated_inline_sub {
my $self = shift;
my $type = $self->_self_or_first_inlinable_ancestor;
my $source
= 'sub { ' . $type->_inline_generator->( $type, '$_[0]' ) . '}';
return eval_closure(
source => $source,
environment => $type->inline_environment,
description => 'inlined sub for ' . $self->description,
);
}
sub _self_or_first_inlinable_ancestor {
my $self = shift;
my $type = first { $_->_has_inline_generator }
reverse $self->_ancestors_and_self;
# This should never happen because ->can_be_inlined should always be
# checked before this builder is called.
die 'Cannot generate an inline sub' unless $type;
return $type;
}
sub _build_optimized_constraint {
my $self = shift;
if ( $self->can_be_inlined ) {
return $self->_generated_inline_sub;
}
else {
return $self->_constraint_with_parents;
}
}
sub _constraint_with_parents {
my $self = shift;
my @constraints;
for my $type ( $self->_ancestors_and_self ) {
next unless $type->has_real_constraint;
# If a type can be inlined, we can use that and discard all of the
# ancestors we've seen so far, since we can assume that the inlined
# constraint does all of the ancestor checks in addition to its own.
if ( $type->can_be_inlined ) {
@constraints = $type->_generated_inline_sub;
}
else {
push @constraints, $type->_constraint;
}
}
return $NullConstraint unless @constraints;
return sub {
all { $_->( $_[0] ) } @constraints;
};
}
# This is only used for identifying from types as part of coercions, but I
# want to leave open the possibility of using something other than
# _description in the future.
sub id {
my $self = shift;
return $self->description;
}
sub add_coercion {
my $self = shift;
my $coercion = shift;
my $from_id = $coercion->from->id;
confess "Cannot add two coercions fom the same type: $from_id"
if $self->_has_coercion_from_type($from_id);
$self->_add_coercion( $from_id => $coercion );
return;
}
sub has_coercion_from_type {
my $self = shift;
my $type = shift;
return $self->_has_coercion_from_type( $type->id );
}
sub coerce_value {
my $self = shift;
my $value = shift;
for my $coercion ( $self->coercions ) {
next unless $coercion->from->value_is_valid($value);
return $coercion->coerce($value);
}
return $value;
}
sub can_inline_coercion {
my $self = shift;
return all { $_->can_be_inlined } $self->coercions;
}
sub can_inline_coercion_and_check {
my $self = shift;
return all { $_->can_be_inlined } $self, $self->coercions;
}
sub inline_coercion {
my $self = shift;
my $arg_name = shift;
die 'Cannot inline coercion'
unless $self->can_inline_coercion;
my $source = 'do { my $value = ' . $arg_name . ';';
my ( $coerce, $env );
( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
$source .= $coerce . $arg_name . '};';
return ( $source, $env );
}
sub inline_coercion_and_check {
my $self = shift;
my $arg_name = shift;
die 'Cannot inline coercion and check'
unless $self->can_inline_coercion_and_check;
my $source = 'do { my $value = ' . $arg_name . ';';
my ( $coerce, $env );
( $coerce, $arg_name, $env ) = $self->_inline_coercion($arg_name);
my ( $assert, $assert_env ) = $self->inline_assert($arg_name);
$source .= $coerce;
$source .= $assert;
$source .= $arg_name . '};';
return ( $source, { %{$env}, %{$assert_env} } );
}
sub _inline_coercion {
my $self = shift;
my $arg_name = shift;
return ( q{}, $arg_name, {} ) unless $self->has_coercions;
my %env;
$arg_name = '$value';
my $source = $arg_name . ' = ';
for my $coercion ( $self->coercions ) {
$source
.= '('
. $coercion->from->inline_check($arg_name) . ') ? ('
. $coercion->inline_coercion($arg_name) . ') : ';
%env = (
%env,
%{ $coercion->inline_environment },
%{ $coercion->from->inline_environment },
);
}
$source .= $arg_name . ';';
return ( $source, $arg_name, \%env );
}
{
my $counter = 1;
sub inline_assert {
my $self = shift;
my $type_var_name = '$_Specio_Constraint_Interface_type' . $counter;
my $message_generator_var_name
= '$_Specio_Constraint_Interface_message_generator' . $counter;
my %env = (
$type_var_name => \$self,
$message_generator_var_name => \( $self->_message_generator ),
%{ $self->inline_environment },
);
my $source = $self->inline_check( $_[0] );
$source .= ' or ';
$source .= $self->_inline_throw_exception(
$_[0],
$message_generator_var_name,
$type_var_name
);
$source .= ';';
$counter++;
return ( $source, \%env );
}
}
sub inline_check {
my $self = shift;
die 'Cannot inline' unless $self->can_be_inlined;
my $type = $self->_self_or_first_inlinable_ancestor;
return $type->_inline_generator->( $type, @_ );
}
# For some idiotic reason I called $type->_subify directly in Code::TidyAll so
# I'll leave this in here for now.
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _subify { $_[0]->_subification }
## use critic
sub _build_subification {
my $self = shift;
if ( defined &Sub::Quote::quote_sub && $self->can_be_inlined ) {
return Sub::Quote::quote_sub( $self->inline_assert('$_[0]') );
}
else {
return sub { $self->validate_or_die( $_[0] ) };
}
}
sub _inline_throw_exception {
my $self = shift;
my $value_var = shift;
my $message_generator_var_name = shift;
my $type_var_name = shift;
#<<<
return 'Specio::Exception->throw( '
. ' message => ' . $message_generator_var_name . '->(' . $value_var . '),'
. ' type => ' . $type_var_name . ','
. ' value => ' . $value_var . ' )';
#>>>
}
# This exists for the benefit of Moo
sub coercion_sub {
my $self = shift;
if ( defined &Sub::Quote::quote_sub
&& all { $_->can_be_inlined } $self->coercions ) {
my $inline = q{};
my %env;
for my $coercion ( $self->coercions ) {
$inline .= sprintf(
'$_[0] = %s if %s;' . "\n",
$coercion->inline_coercion('$_[0]'),
$coercion->from->inline_check('$_[0]')
);
%env = (
%env,
%{ $coercion->inline_environment },
%{ $coercion->from->inline_environment },
);
}
$inline .= sprintf( "%s;\n", '$_[0]' );
return Sub::Quote::quote_sub( $inline, \%env );
}
else {
return sub { $self->coerce_value(shift) };
}
}
sub _build_ancestors {
my $self = shift;
my @parents;
my $type = $self;
while ( $type = $type->parent ) {
push @parents, $type;
}
return \@parents;
}
sub _build_description {
my $self = shift;
my $desc
= $self->is_anon ? 'anonymous type' : 'type named ' . $self->name;
$desc .= q{ } . $self->declared_at->description;
return $desc;
}
sub _build_coercions { {} }
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _clone_coercions {
my $self = shift;
my $coercions = $self->_coercions;
my %clones;
for my $name ( keys %{$coercions} ) {
my $coercion = $coercions->{$name};
$clones{$name} = $coercion->clone_with_new_to($self);
}
return \%clones;
}
## use critic
sub _build_signature {
my $self = shift;
# This assumes that when a type is cloned, the underlying constraint or
# generator sub is copied by _reference_, so it has the same memory
# address and stringifies to the same value. XXX - will this break under
# threads?
return join "\n",
( $self->_has_parent ? $self->parent->_signature : () ),
(
defined $self->_constraint
? $self->_constraint
: $self->_inline_generator
);
}
# Moose compatibility methods - these exist as a temporary hack to make Specio
# work with Moose.
sub has_coercion {
shift->has_coercions;
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _inline_check {
shift->inline_check(@_);
}
sub _compiled_type_constraint {
shift->_optimized_constraint;
}
## use critic;
# This class implements the methods that Moose expects from coercions as well.
sub coercion {
return shift;
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _compiled_type_coercion {
my $self = shift;
return sub {
return $self->coerce_value(shift);
};
}
## use critic
sub has_message {
1;
}
sub message {
shift->_message_generator;
}
sub get_message {
my $self = shift;
my $value = shift;
return $self->_message_generator->( $self, $value );
}
sub check {
shift->value_is_valid(@_);
}
sub coerce {
shift->coerce_value(@_);
}
1;
# ABSTRACT: The interface all type constraints should provide
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Role::Interface - The interface all type constraints should provide
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This role defines the interface that all type constraints must provide, and
provides most (or all) of the implementation. The L<Specio::Constraint::Simple>
class simply consumes this role and provides no additional code. Other
constraint classes add features or override some of this role's functionality.
=for Pod::Coverage .*
=head1 API
See the L<Specio::Constraint::Simple> documentation for details. See the
internals of various constraint classes to see how this role can be overridden
or expanded upon.
=head1 ROLES
This role does the L<Specio::Role::Inlinable> role.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,144 @@
package Specio::Constraint::Role::IsaType;
use strict;
use warnings;
our $VERSION = '0.46';
use Scalar::Util qw( blessed );
use Specio::PartialDump qw( partial_dump );
use Storable qw( dclone );
use Role::Tiny;
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( parent _inline_generator )) {
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
$attrs->{class} = {
isa => 'ClassName',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _wrap_message_generator {
my $self = shift;
my $generator = shift;
my $type = ( split /::/, blessed $self)[-1];
my $class = $self->class;
my $allow_classes = $self->_allow_classes;
unless ( defined $generator ) {
$generator = sub {
shift;
my $value = shift;
return "An undef will never pass an $type check (wants $class)"
unless defined $value;
if ( ref $value && !blessed $value) {
my $dump = partial_dump($value);
return
"An unblessed reference ($dump) will never pass an $type check (wants $class)";
}
if ( !blessed $value) {
return
"An empty string will never pass an $type check (wants $class)"
unless length $value;
if (
$value =~ /\A
\s*
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\s*
\z/xs
) {
return
"A number ($value) will never pass an $type check (wants $class)";
}
if ( !$allow_classes ) {
my $dump = partial_dump($value);
return
"A plain scalar ($dump) will never pass an $type check (wants $class)";
}
}
my $got = blessed $value;
$got ||= $value;
return "The $got class is not a subclass of the $class class";
};
}
return sub { $generator->( undef, @_ ) };
}
## use critic
1;
# ABSTRACT: Provides a common implementation for Specio::Constraint::AnyIsa and Specio::Constraint::ObjectIsa
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Role::IsaType - Provides a common implementation for Specio::Constraint::AnyIsa and Specio::Constraint::ObjectIsa
=head1 VERSION
version 0.46
=head1 DESCRIPTION
See L<Specio::Constraint::AnyIsa> and L<Specio::Constraint::ObjectIsa> for details.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,351 @@
package Specio::Constraint::Simple;
use strict;
use warnings;
our $VERSION = '0.46';
use Role::Tiny::With;
use Specio::OO;
with 'Specio::Constraint::Role::Interface';
__PACKAGE__->_ooify;
1;
# ABSTRACT: Class for simple (non-parameterized or specialized) types
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Simple - Class for simple (non-parameterized or specialized) types
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $str = t('Str');
print $str->name; # Str
my $parent = $str->parent;
if ( $str->value_is_valid($value) ) { ... }
$str->validate_or_die($value);
my $code = $str->inline_coercion_and_check('$_[0]');
=head1 DESCRIPTION
This class implements simple type constraints, constraints without special
properties or parameterization.
It does not actually contain any real code of its own. The entire
implementation is provided by the L<Specio::Constraint::Role::Interface> role,
but the primary API for type constraints is documented here.
All other type constraint classes in this distribution implement this API,
except where otherwise noted.
=head1 API
This class provides the following methods.
=head2 Specio::Constraint::Simple->new(...)
This creates a new constraint. It accepts the following named parameters:
=over 4
=item * name => $name
This is the type's name. The name is optional, but if provided it must be a
string.
=item * parent => $type
The type's parent type. This must be an object which does the
L<Specio::Constraint::Role::Interface> role.
This parameter is optional.
=item * constraint => sub { ... }
A subroutine reference implementing the constraint. It will be called as a
method on the object and passed a single argument, the value to check.
It should return true or false to indicate whether the value matches the
constraint.
This parameter is mutually exclusive with C<inline_generator>.
You can also pass this option with the key C<where> in the parameter list.
=item * inline_generator => sub { ... }
This should be a subroutine reference which returns a string containing a
single term. This code should I<not> end in a semicolon. This code should
implement the constraint.
The generator will be called as a method on the constraint with a single
argument. That argument is the name of the variable being coerced, something
like C<'$_[0]'> or C<'$var'>.
The inline generator is expected to include code to implement both the current
type and all its parents. Typically, the easiest way to do this is to write a
subroutine something like this:
sub {
my $self = shift;
my $var = shift;
return $_[0]->parent->inline_check( $_[1] )
. ' and more checking code goes here';
}
This parameter is mutually exclusive with C<constraint>.
You can also pass this option with the key C<inline> in the parameter list.
=item * inline_environment => {}
This should be a hash reference of variable names (with sigils) and values for
that variable. The values should be I<references> to the values of the
variables.
This environment will be used when compiling the constraint as part of a
subroutine. The named variables will be captured as closures in the generated
subroutine, using L<Eval::Closure>.
It should be very rare to need to set this in the constructor. It's more
likely that a special type subclass would need to provide values that it
generates internally.
If you do set this, you are responsible for generating variable names that
won't clash with anything else in the inlined code.
This parameter defaults to an empty hash reference.
=item * message_generator => sub { ... }
A subroutine to generate an error message when the type check fails. The
default message says something like "Validation failed for type named Int
declared in package Specio::Library::Builtins
(.../Specio/blib/lib/Specio/Library/Builtins.pm) at line 147 in sub named (eval)
with value 1.1".
You can override this to provide something more specific about the way the
type failed.
The subroutine you provide will be called as a subroutine, I<not as a method>,
with two arguments. The first is the description of the type (the bit in the
message above that starts with "type named Int ..." and ends with "... in sub
named (eval)". This description says what the thing is and where it was
defined.
The second argument is the value that failed the type check, after any
coercions that might have been applied.
You can also pass this option with the key C<message> in the parameter list.
=item * declared_at => $declared_at
This parameter must be a L<Specio::DeclaredAt> object.
This parameter is required.
=back
It is possible to create a type without a constraint of its own.
=head2 $type->name
Returns the name of the type as it was passed the constructor.
=head2 $type->parent
Returns the parent type passed to the constructor. If the type has no parent
this returns C<undef>.
=head2 $type->is_anon
Returns false for named types, true otherwise.
=head2 $type->is_a_type_of($other_type)
Given a type object, this returns true if the type this method is called on is
a descendant of that type or is that type.
=head2 $type->is_same_type_as($other_type)
Given a type object, this returns true if the type this method is called on is
the same as that type.
=head2 $type->coercions
Returns a list of L<Specio::Coercion> objects which belong to this constraint.
=head2 $type->coercion_from_type($name)
Given a type name, this method returns a L<Specio::Coercion> object which
coerces from that type, if such a coercion exists.
=head2 $type->validate_or_die($value)
This method does nothing if the value is valid. If it is not, it throws a
L<Specio::Exception>.
=head2 $type->value_is_valid($value)
Returns true or false depending on whether the C<$value> passes the type
constraint.
=head2 $type->has_real_constraint
This returns true if the type was created with a C<constraint> or
C<inline_generator> parameter. This is used internally to skip type checks for
types that don't actually implement a constraint.
=head2 $type->description
This returns a string describing the type. This includes the type's name and
where it was declared, so you end up with something like C<'type named Foo
declared in package My::Lib (lib/My/Lib.pm) at line 42'>. If the type is
anonymous the name will be "anonymous type".
=head2 $type->id
This is a unique id for the type as a string. This is useful if you need to
make a hash key based on a type, for example. This should be treated as an
essentially arbitrary and opaque string, and could change at any time in the
future. If you want something human-readable, use the C<< $type->description
>> method.
=head2 $type->add_coercion($coercion)
This adds a new L<Specio::Coercion> to the type. If the type already has a
coercion from the same type as the new coercion, it will throw an error.
=head2 $type->has_coercion_from_type($other_type)
This method returns true if the type can coerce from the other type.
=head2 $type->coerce_value($value)
This attempts to coerce a value into a new value that matches the type. It
checks all of the type's coercions. If it finds one which has a "from" type
that accepts the value, it runs the coercion and returns the new value.
If it cannot find a matching coercion it returns the original value.
=head2 $type->inline_coercion_and_check($var)
Given a variable name, this returns a string of code and an environment hash
that implements all of the type's coercions as well as the type check itself.
This will throw an exception unless both the type and all of its coercions are
inlinable.
The generated code will throw a L<Specio::Exception> if the type constraint
fails. If the constraint passes, then the generated code returns the (possibly
coerced) value.
The return value is a two-element list. The first element is the code. The
second is a hash reference containing variables which need to be in scope for
the code to work. This is intended to be passed to L<Eval::Closure>'s
C<eval_closure> subroutine.
The returned code is a single C<do { }> block without a terminating
semicolon.
=head2 $type->inline_assert($var)
Given a variable name, this generates code that implements the constraint and
throws an exception if the variable does not pass the constraint.
The return value is a two-element list. The first element is the code. The
second is a hash reference containing variables which need to be in scope for
the code to work. This is intended to be passed to L<Eval::Closure>'s
C<eval_closure> subroutine.
=head2 $type->inline_check($var)
Given a variable name, this returns a string of code that implements the
constraint. If the type is not inlinable, this method throws an error.
=head2 $type->inline_coercion($var)
Given a variable name, this returns a string of code and an environment hash
that implements all of the type's coercions. I<It does not check that the
resulting value is valid.>
This will throw an exception unless all of the type's coercions are inlinable.
The return value is a two-element list. The first element is the code. The
second is a hash reference containing variables which need to be in scope for
the code to work. This is intended to be passed to L<Eval::Closure>'s
C<eval_closure> subroutine.
The returned code is a single C<do { }> block without a terminating
semicolon.
=head2 $type->inline_environment()
This returns a hash defining the variables that need to be closed over when
inlining the type. The keys are full variable names like C<'$foo'> or
C<'@bar'>. The values are I<references> to a variable of the matching type.
=head2 $type->coercion_sub
This method returns a sub ref that takes a single argument and applied all
relevant coercions to it. This sub ref will use L<Sub::Quote> if all the
type's coercions are inlinable.
This method exists primarily for the benefit of L<Moo>.
=head1 OVERLOADING
All constraints overloading subroutine de-referencing for the benefit of
L<Moo>. The returned subroutine uses L<Sub::Quote> if the type constraint is
inlinable.
=head1 ROLES
This role does the L<Specio::Constraint::Role::Interface> and
L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,267 @@
package Specio::Constraint::Structurable;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use Role::Tiny::With;
use Scalar::Util qw( blessed );
use Specio::DeclaredAt;
use Specio::OO;
use Specio::Constraint::Structured;
use Specio::TypeChecks qw( does_role isa_class );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $role_attrs = Specio::Constraint::Role::Interface::_attrs();
## use critic
my $attrs = {
%{$role_attrs},
_parameterization_args_builder => {
isa => 'CodeRef',
init_arg => 'parameterization_args_builder',
required => 1,
},
_name_builder => {
isa => 'CodeRef',
init_arg => 'name_builder',
required => 1,
},
_structured_constraint_generator => {
isa => 'CodeRef',
init_arg => 'structured_constraint_generator',
predicate => '_has_structured_constraint_generator',
},
_structured_inline_generator => {
isa => 'CodeRef',
init_arg => 'structured_inline_generator',
predicate => '_has_structured_inline_generator',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub BUILD {
my $self = shift;
if ( $self->_has_constraint ) {
die
'A structurable constraint with a constraint parameter must also have a structured_constraint_generator'
unless $self->_has_structured_constraint_generator;
}
if ( $self->_has_inline_generator ) {
die
'A structurable constraint with an inline_generator parameter must also have a structured_inline_generator'
unless $self->_has_structured_inline_generator;
}
return;
}
sub parameterize {
my $self = shift;
my %args = @_;
my $declared_at = $args{declared_at};
if ($declared_at) {
isa_class( $declared_at, 'Specio::DeclaredAt' )
or confess
q{The "declared_at" parameter passed to ->parameterize must be a Specio::DeclaredAt object};
}
my %parameters
= $self->_parameterization_args_builder->( $self, $args{of} );
$declared_at = Specio::DeclaredAt->new_from_caller(1)
unless defined $declared_at;
my %new_p = (
parent => $self,
parameters => \%parameters,
declared_at => $declared_at,
name => $self->_name_builder->( $self, \%parameters ),
);
if ( $self->_has_structured_constraint_generator ) {
$new_p{constraint}
= $self->_structured_constraint_generator->(%parameters);
}
else {
for my $p (
grep {
blessed($_)
&& does_role('Specio::Constraint::Role::Interface')
} values %parameters
) {
confess
q{Any type objects passed to ->parameterize must be inlinable constraints if the structurable type has an inline_generator}
unless $p->can_be_inlined;
}
my $ig = $self->_structured_inline_generator;
$new_p{inline_generator}
= sub { $ig->( shift, shift, %parameters, @_ ) };
}
return Specio::Constraint::Structured->new(%new_p);
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _name_or_anon {
return $_[1]->_has_name ? $_[1]->name : 'ANON';
}
## use critic
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class which represents structurable constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Structurable - A class which represents structurable constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $tuple = t('Tuple');
my $tuple_of_str_int = $tuple->parameterize( of => [ t('Str'), t('Int') ] );
=head1 DESCRIPTION
This class implements the API for structurable types like C<Dict>, C<Map>< and
C<Tuple>.
=for Pod::Coverage BUILD
=head1 API
This class implements the same API as L<Specio::Constraint::Simple>, with a few
additions.
=head2 Specio::Constraint::Structurable->new(...)
This class's constructor accepts two additional parameters:
=over 4
=item * parameterization_args_builder
This is a subroutine that takes the values passed to C<of> and returns a hash
of named arguments. These arguments will then be passed into the
C<structured_constraint_generator> or C<structured_inline_generator>.
This should also do argument checking to make sure that the argument passed
are valid. For example, the C<Tuple> type turns the arrayref passed to C<of>
into a hash, along the way checking that the caller did not do things like
interleave optional and required elements or mix optional and slurpy together
in the definition.
This parameter is required.
=item * name_builder
This is a subroutine that is called to generate a name for the structured type
when it is created. This will be called as a method on the
C<Specio::Constraint::Structurable> object. It will be passed the hash of
arguments returned by the C<parameterization_args_builder>.
This parameter is required.
=item * structured_constraint_generator
This is a subroutine that generates a new constraint subroutine when the type
is structured.
It will be called as a method on the type and will be passed the hash of
arguments returned by the C<parameterization_args_builder>.
This parameter is mutually exclusive with the C<structured_inline_generator>
parameter.
This parameter or the C<structured_inline_generator> parameter is required.
=item * structured_inline_generator
This is a subroutine that generates a new inline generator subroutine when the
type is structured.
It will be called as a method on the L<Specio::Constraint::Structured> object
when that object needs to generate an inline constraint. It will receive the
type parameter as the first argument and the variable name as a string as the
second.
The remaining arguments will be the parameter hash returned by the
C<parameterization_args_builder>.
This probably seems fairly confusing, so looking at the examples in the
L<Specio::Library::Structured::*> code may be helpful.
This parameter is mutually exclusive with the
C<structured_constraint_generator> parameter.
This parameter or the C<structured_constraint_generator> parameter is
required.
=back
=head2 $type->parameterize(...)
This method takes two arguments. The C<of> argument should be an object which
does the L<Specio::Constraint::Role::Interface> role, and is required.
The other argument, C<declared_at>, is optional. If it is not given, then a
new L<Specio::DeclaredAt> object is creating using a call stack depth of 1.
This method returns a new L<Specio::Constraint::Structured> object.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,134 @@
package Specio::Constraint::Structured;
use strict;
use warnings;
our $VERSION = '0.46';
use List::Util qw( all );
use Role::Tiny::With;
use Specio::OO;
use Specio::TypeChecks qw( does_role );
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
$attrs->{parent}{isa} = 'Specio::Constraint::Structurable';
$attrs->{parent}{required} = 1;
$attrs->{parameters} = {
isa => 'HashRef',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub can_be_inlined {
my $self = shift;
return $self->_has_inline_generator;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class which represents structured constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Structured - A class which represents structured constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $tuple = t('Tuple');
my $tuple_of_str_int = $tuple->parameterize( of => [ t('Str'), t('Int') ] );
my $parent = $tuple_of_str_int->parent; # returns Tuple
my $parameters = $arrayref_of_int->parameters; # returns { of => [ t('Str'), t('Int') ] }
=head1 DESCRIPTION
This class implements the API for structured types.
=for Pod::Coverage can_be_inlined type_parameter
=head1 API
This class implements the same API as L<Specio::Constraint::Simple>, with a few
additions.
=head2 Specio::Constraint::Structured->new(...)
This class's constructor accepts two additional parameters:
=over 4
=item * parent
This should be the L<Specio::Constraint::Structurable> object from which this
object was created.
This parameter is required.
=item * parameters
This is the hashref of parameters for the structured type. These are the
parameters returned by the C<Structurable> type's
C<parameterization_args_builder>. The exact form of this hashref will vary for
each structured type.
This parameter is required.
=back
=head2 $type->parameters
Returns the hashref that was passed to the constructor.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,198 @@
package Specio::Constraint::Union;
use strict;
use warnings;
our $VERSION = '0.46';
use List::Util qw( all any );
use Role::Tiny::With;
use Specio::OO;
use Storable qw( dclone );
use Specio::Constraint::Role::Interface;
with 'Specio::Constraint::Role::Interface';
{
## no critic (Subroutines::ProtectPrivateSubs)
my $attrs = dclone( Specio::Constraint::Role::Interface::_attrs() );
## use critic
for my $name (qw( _constraint _inline_generator )) {
delete $attrs->{$name}{predicate};
$attrs->{$name}{init_arg} = undef;
$attrs->{$name}{lazy} = 1;
$attrs->{$name}{builder}
= $name =~ /^_/ ? '_build' . $name : '_build_' . $name;
}
delete $attrs->{parent};
delete $attrs->{name}{predicate};
$attrs->{name}{lazy} = 1;
$attrs->{name}{builder} = '_build_name';
$attrs->{of} = {
isa => 'ArrayRef',
required => 1,
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub parent {undef}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _has_parent {0}
sub _has_name {
my $self = shift;
return defined $self->name;
}
sub _build_name {
my $self = shift;
return unless all { $_->_has_name } @{ $self->of };
return join q{ | }, map { $_->name } @{ $self->of };
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _has_constraint {
my $self = shift;
return !$self->_has_inline_generator;
}
## use critic
sub _build_constraint {
return $_[0]->_optimized_constraint;
}
sub _build_optimized_constraint {
my $self = shift;
## no critic (Subroutines::ProtectPrivateSubs)
my @c = map { $_->_optimized_constraint } @{ $self->of };
return sub {
return any { $_->( $_[0] ) } @c;
};
}
sub _has_inline_generator {
my $self = shift;
## no critic (Subroutines::ProtectPrivateSubs)
return all { $_->_has_inline_generator } @{ $self->of };
}
sub _build_inline_generator {
my $self = shift;
return sub {
return '(' . (
join q{ || },
map { sprintf( '( %s )', $_->_inline_generator->( $_, $_[1] ) ) }
@{ $self->of }
) . ')';
}
}
sub _build_inline_environment {
my $self = shift;
my %env;
for my $type ( @{ $self->of } ) {
%env = (
%env,
%{ $type->inline_environment },
);
}
return \%env;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class for union constraints
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Constraint::Union - A class for union constraints
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $type = Specio::Constraint::Untion->new(...);
=head1 DESCRIPTION
This is a specialized type constraint class for unions, which will allow a
value which matches any one of several distinct types.
=for Pod::Coverage parent
=head1 API
This class provides all of the same methods as L<Specio::Constraint::Simple>,
with a few differences:
=head2 Specio::Constraint::Union->new( ... )
The C<parent> parameter is ignored if it passed, as it is always C<undef>
The C<inline_generator> and C<constraint> parameters are also ignored. This
class provides its own default inline generator subroutine reference.
Finally, this class requires an additional parameter, C<of>. This must be an
arrayref of type objects.
=head2 $union->of
Returns an array reference of the individual types which makes up this union.
=head1 ROLES
This class does the L<Specio::Constraint::Role::Interface> and
L<Specio::Role::Inlinable> roles.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,696 @@
package Specio::Declare;
use strict;
use warnings;
use parent 'Exporter';
our $VERSION = '0.46';
use Carp qw( croak );
use Specio::Coercion;
use Specio::Constraint::Simple;
use Specio::DeclaredAt;
use Specio::Helpers qw( install_t_sub _STRINGLIKE );
use Specio::Registry qw( internal_types_for_package register );
## no critic (Modules::ProhibitAutomaticExportation)
our @EXPORT = qw(
anon
any_can_type
any_does_type
any_isa_type
coerce
declare
enum
intersection
object_can_type
object_does_type
object_isa_type
union
);
## use critic
sub import {
my $package = shift;
# What the heck is this monstrosity?
#
# Moose version 2.0901 included a first pass at support for Specio. This
# was based on Specio c. 0.06 when Specio itself still used
# Moose. Unfortunately, recent changes to Specio broke this support and
# the Moose core needs updating.
#
# However, stable versions of Moose have since shipped with a test that
# attempts to test itself with Specio 0.07+. This was fine until I wanted
# to release a non-TRIAL Specio.
#
# Once that's out, anyone installing Specio will cause future attempts to
# install Moose to fail until Moose includes updated Specio support!
# Breaking Moose is not acceptable, thus this mess.
#
# Note that since Moose 2.1207 this test was renamed and the Specio tests
# actually run (and pass). We still need to leave this in here for quite
# some time. People should be able to install Specio and then install an
# older Moose indefinitely (or at least for a year or two).
if ( $ENV{HARNESS_ACTIVE}
&& $0 =~ m{t[\\/]type_constraints[\\/]specio\.t$} ) {
require Test::More;
Test::More::plan( skip_all =>
'These tests will not pass with this version of Specio' );
exit 0;
}
my $caller = caller();
$package->export_to_level( 1, $package, @_ );
install_t_sub(
$caller,
internal_types_for_package($caller)
);
return;
}
sub declare {
my $name = _STRINGLIKE(shift)
or croak 'You must provide a name for declared types';
my %p = @_;
my $tc = _make_tc( name => $name, %p );
register( scalar caller(), $name, $tc, 'exportable' );
return $tc;
}
sub anon {
return _make_tc(@_);
}
sub enum {
my $name;
$name = shift if @_ % 2;
my %p = @_;
require Specio::Constraint::Enum;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
values => $p{values},
type_class => 'Specio::Constraint::Enum',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub object_can_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
# This cannot be loaded earlier, since it loads Specio::Library::Builtins,
# which in turn wants to load Specio::Declare (the current module).
require Specio::Constraint::ObjectCan;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
methods => $p{methods},
type_class => 'Specio::Constraint::ObjectCan',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub object_does_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
my $caller = scalar caller();
# If we are being called repeatedly with a single argument, then we don't
# want to blow up because the type has already been declared. This would
# force the user to use t() for all calls but the first, making their code
# pointlessly more complicated.
unless ( keys %p ) {
if ( my $exists = internal_types_for_package($caller)->{$name} ) {
return $exists;
}
}
require Specio::Constraint::ObjectDoes;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
role => ( defined $p{role} ? $p{role} : $name ),
type_class => 'Specio::Constraint::ObjectDoes',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub object_isa_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
my $caller = scalar caller();
unless ( keys %p ) {
if ( my $exists = internal_types_for_package($caller)->{$name} ) {
return $exists;
}
}
require Specio::Constraint::ObjectIsa;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
class => ( defined $p{class} ? $p{class} : $name ),
type_class => 'Specio::Constraint::ObjectIsa',
);
register( $caller, $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub any_can_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
# This cannot be loaded earlier, since it loads Specio::Library::Builtins,
# which in turn wants to load Specio::Declare (the current module).
require Specio::Constraint::AnyCan;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
methods => $p{methods},
type_class => 'Specio::Constraint::AnyCan',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub any_does_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
my $caller = scalar caller();
unless ( keys %p ) {
if ( my $exists = internal_types_for_package($caller)->{$name} ) {
return $exists;
}
}
require Specio::Constraint::AnyDoes;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
role => ( defined $p{role} ? $p{role} : $name ),
type_class => 'Specio::Constraint::AnyDoes',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub any_isa_type {
my $name;
$name = shift if @_ % 2;
my %p = @_;
my $caller = scalar caller();
unless ( keys %p ) {
if ( my $exists = internal_types_for_package($caller)->{$name} ) {
return $exists;
}
}
require Specio::Constraint::AnyIsa;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
class => ( defined $p{class} ? $p{class} : $name ),
type_class => 'Specio::Constraint::AnyIsa',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub intersection {
my $name;
$name = shift if @_ % 2;
my %p = @_;
require Specio::Constraint::Intersection;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
%p,
type_class => 'Specio::Constraint::Intersection',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub union {
my $name;
$name = shift if @_ % 2;
my %p = @_;
require Specio::Constraint::Union;
my $tc = _make_tc(
( defined $name ? ( name => $name ) : () ),
%p,
type_class => 'Specio::Constraint::Union',
);
register( scalar caller(), $name, $tc, 'exportable' )
if defined $name;
return $tc;
}
sub _make_tc {
my %p = @_;
my $class = delete $p{type_class} || 'Specio::Constraint::Simple';
$p{constraint} = delete $p{where} if exists $p{where};
$p{message_generator} = delete $p{message} if exists $p{message};
$p{inline_generator} = delete $p{inline} if exists $p{inline};
return $class->new(
%p,
declared_at => Specio::DeclaredAt->new_from_caller(2),
);
}
sub coerce {
my $to = shift;
my %p = @_;
$p{coercion} = delete $p{using} if exists $p{using};
$p{inline_generator} = delete $p{inline} if exists $p{inline};
return $to->add_coercion(
Specio::Coercion->new(
to => $to,
%p,
declared_at => Specio::DeclaredAt->new_from_caller(1),
)
);
}
1;
# ABSTRACT: Specio declaration subroutines
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Declare - Specio declaration subroutines
=head1 VERSION
version 0.46
=head1 SYNOPSIS
package MyApp::Type::Library;
use parent 'Specio::Exporter';
use Specio::Declare;
use Specio::Library::Builtins;
declare(
'Foo',
parent => t('Str'),
where => sub { $_[0] =~ /foo/i },
);
declare(
'ArrayRefOfInt',
parent => t( 'ArrayRef', of => t('Int') ),
);
my $even = anon(
parent => t('Int'),
inline => sub {
my $type = shift;
my $value_var = shift;
return $value_var . ' % 2 == 0';
},
);
coerce(
t('ArrayRef'),
from => t('Foo'),
using => sub { [ $_[0] ] },
);
coerce(
$even,
from => t('Int'),
using => sub { $_[0] % 2 ? $_[0] + 1 : $_[0] },
);
# Specio name is DateTime
any_isa_type('DateTime');
# Specio name is DateTimeObject
object_isa_type( 'DateTimeObject', class => 'DateTime' );
any_can_type(
'Duck',
methods => [ 'duck_walk', 'quack' ],
);
object_can_type(
'DuckObject',
methods => [ 'duck_walk', 'quack' ],
);
enum(
'Colors',
values => [qw( blue green red )],
);
intersection(
'HashRefAndArrayRef',
of => [ t('HashRef'), t('ArrayRef') ],
);
union(
'IntOrArrayRef',
of => [ t('Int'), t('ArrayRef') ],
);
=head1 DESCRIPTION
This package exports a set of type declaration helpers. Importing this package
also causes it to create a C<t> subroutine the caller.
=head1 SUBROUTINES
This module exports the following subroutines.
=head2 t('name')
This subroutine lets you access any types you have declared so far, as well as
any types you imported from another type library.
If you pass an unknown name, it throws an exception.
=head2 declare(...)
This subroutine declares a named type. The first argument is the type name,
followed by a set of key/value parameters:
=over 4
=item * parent => $type
The parent should be another type object. Specifically, it can be anything
which does the L<Specio::Constraint::Role::Interface> role. The parent can be a
named or anonymous type.
=item * where => sub { ... }
This is a subroutine which defines the type constraint. It will be passed a
single argument, the value to check, and it should return true or false to
indicate whether or not the value is valid for the type.
This parameter is mutually exclusive with the C<inline> parameter.
=item * inline => sub { ... }
This is a subroutine that is called to generate inline code to validate the
type. Inlining can be I<much> faster than simply providing a subroutine with
the C<where> parameter, but is often more complicated to get right.
The inline generator is called as a method on the type with one argument. This
argument is a I<string> containing the variable name to use in the generated
code. Typically this is something like C<'$_[0]'> or C<'$value'>.
The inline generator subroutine should return a I<string> of code representing
a single term, and it I<should not> be terminated with a semicolon. This
allows the inlined code to be safely included in an C<if> statement, for
example. You can use C<do { }> blocks and ternaries to get everything into one
term. Do not assign to the variable you are testing. This single term should
evaluate to true or false.
The inline generator is expected to include code to implement both the current
type and all its parents. Typically, the easiest way to do this is to write a
subroutine something like this:
sub {
my $self = shift;
my $var = shift;
return $self->parent->inline_check($var)
. ' and more checking code goes here';
}
Or, more concisely:
sub { $_[0]->parent->inline_check( $_[1] ) . 'more code that checks $_[1]' }
The C<inline> parameter is mutually exclusive with the C<where> parameter.
=item * message_generator => sub { ... }
A subroutine to generate an error message when the type check fails. The
default message says something like "Validation failed for type named Int
declared in package Specio::Library::Builtins
(.../Specio/blib/lib/Specio/Library/Builtins.pm) at line 147 in sub named (eval)
with value 1.1".
You can override this to provide something more specific about the way the
type failed.
The subroutine you provide will be called as a method on the type with two
arguments. The first is the description of the type (the bit in the message
above that starts with "type named Int ..." and ends with "... in sub named
(eval)". This description says what the thing is and where it was defined.
The second argument is the value that failed the type check, after any
coercions that might have been applied.
=back
=head2 anon(...)
This subroutine declares an anonymous type. It is identical to C<declare>
except that it expects a list of key/value parameters without a type name as
the first parameter.
=head2 coerce(...)
This declares a coercion from one type to another. The first argument should
be an object which does the L<Specio::Constraint::Role::Interface> role. This
can be either a named or anonymous type. This type is the type that the
coercion is I<to>.
The remaining arguments are key/value parameters:
=over 4
=item * from => $type
This must be an object which does the L<Specio::Constraint::Role::Interface>
role. This is type that we are coercing I<from>. Again, this can be either a
named or anonymous type.
=item * using => sub { ... }
This is a subroutine which defines the type coercion. It will be passed a
single argument, the value to coerce. It should return a new value of the type
this coercion is to.
This parameter is mutually exclusive with the C<inline> parameter.
=item * inline => sub { ... }
This is a subroutine that is called to generate inline code to perform the
coercion.
The inline generator is called as a method on the type with one argument. This
argument is a I<string> containing the variable name to use in the generated
code. Typically this is something like C<'$_[0]'> or C<'$value'>.
The inline generator subroutine should return a I<string> of code representing
a single term, and it I<should not> be terminated with a semicolon. This
allows the inlined code to be safely included in an C<if> statement, for
example. You can use C<do { }> blocks and ternaries to get everything into one
term. This single term should evaluate to the new value.
=back
=head1 DECLARATION HELPERS
This module also exports some helper subs for declaring certain kinds of types:
=head2 any_isa_type, object_isa_type
The C<any_isa_type> helper creates a type which accepts a class name or
object of the given class. The C<object_isa_type> helper creates a type
which only accepts an object of the given class.
These subroutines take a type name as the first argument. The remaining
arguments are key/value pairs. Currently this is just the C<class> key, which
should be a class name. This is the class that the type requires.
The type name argument can be omitted to create an anonymous type.
You can also pass just a single argument, in which case that will be used as
both the type's name and the class for the constraint to check.
=head2 any_does_type, object_does_type
The C<any_does_type> helper creates a type which accepts a class name or
object which does the given role. The C<object_does_type> helper creates a
type which only accepts an object which does the given role.
These subroutines take a type name as the first argument. The remaining
arguments are key/value pairs. Currently this is just the C<role> key, which
should be a role name. This is the class that the type requires.
This should just work (I hope) with roles created by L<Moose>, L<Mouse>, and
L<Moo> (using L<Role::Tiny>).
The type name argument can be omitted to create an anonymous type.
You can also pass just a single argument, in which case that will be used as
both the type's name and the role for the constraint to check.
=head2 any_can_type, object_can_type
The C<any_can_type> helper creates a type which accepts a class name or
object with the given methods. The C<object_can_type> helper creates a type
which only accepts an object with the given methods.
These subroutines take a type name as the first argument. The remaining
arguments are key/value pairs. Currently this is just the C<methods> key,
which can be either a string or array reference of strings. These strings are
the required methods for the type.
The type name argument can be omitted to create an anonymous type.
=head2 enum
This creates a type which accepts a string matching a given list of acceptable
values.
The first argument is the type name. The remaining arguments are key/value
pairs. Currently this is just the C<values> key. This should an array
reference of acceptable string values.
The type name argument can be omitted to create an anonymous type.
=head2 intersection
This creates a type which is the intersection of two or more other types. A
union only accepts values which match all of its underlying types.
The first argument is the type name. The remaining arguments are key/value
pairs. Currently this is just the C<of> key. This should an array
reference of types.
The type name argument can be omitted to create an anonymous type.
=head2 union
This creates a type which is the union of two or more other types. A union
accepts any of its underlying types.
The first argument is the type name. The remaining arguments are key/value
pairs. Currently this is just the C<of> key. This should an array
reference of types.
The type name argument can be omitted to create an anonymous type.
=head1 PARAMETERIZED TYPES
You can create a parameterized type by calling C<t> with additional
parameters, like this:
my $arrayref_of_int = t( 'ArrayRef', of => t('Int') );
my $arrayref_of_hashref_of_int = t(
'ArrayRef',
of => t(
'HashRef',
of => t('Int'),
),
);
The C<t> subroutine assumes that if it receives more than one argument, it
should look up the named type and call C<< $type->parameterize(...) >> with
the additional arguments.
If the named type cannot be parameterized, it throws an error.
You can also call C<< $type->parameterize >> directly if needed. See
L<Specio::Constraint::Parameterizable> for details.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,148 @@
package Specio::DeclaredAt;
use strict;
use warnings;
our $VERSION = '0.46';
use Specio::OO;
{
my $attrs = {
package => {
isa => 'Str',
required => 1,
},
filename => {
isa => 'Str',
required => 1,
},
line => {
isa => 'Int',
required => 1,
},
subroutine => {
isa => 'Str',
predicate => 'has_subroutine',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub new_from_caller {
my $class = shift;
my $depth = shift;
my %p;
@p{qw( package filename line )} = ( caller($depth) )[ 0, 1, 2 ];
my $sub = ( caller( $depth + 1 ) )[3];
$p{subroutine} = $sub if defined $sub;
return $class->new(%p);
}
sub description {
my $self = shift;
my $package = $self->package;
my $filename = $self->filename;
my $line = $self->line;
my $desc = "declared in package $package ($filename) at line $line";
if ( $self->has_subroutine ) {
$desc .= ' in sub named ' . $self->subroutine;
}
return $desc;
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: A class to represent where a type or coercion was declared
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::DeclaredAt - A class to represent where a type or coercion was declared
=head1 VERSION
version 0.46
=head1 SYNOPSIS
my $declared = Specio::DeclaredAt->new_from_caller(1);
print $declared->description;
=head1 DESCRIPTION
This class provides a thin wrapper around some of the return values from
Perl's C<caller> built-in. It's used internally to identify where types and
coercions are being declared, which is useful when generating error messages.
=head1 API
This class provides the following methods.
=head2 Specio::DeclaredAt->new_from_caller($depth)
Given a call stack depth, this method returns a new C<Specio::DeclaredAt>
object.
=head2 $declared_at->package, $declared_at->filename, $declared_at->line
Returns the call stack information recorded when the object was created. These
values are always populated.
=head2 $declared_at->subroutine
Returns the subroutine from the call stack. This may be an C<udnef>
=head2 $declared_at->has_subroutine
Returns true if there is a subroutine name associated with this object.
=head2 $declared_at->description
Puts all the information together into a single string like "declared in
package Foo::Bar (.../Foo/Bar.pm) at line 42 in sub named blah".
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,162 @@
package Specio::Exception;
use strict;
use warnings;
use overload
q{""} => 'as_string',
fallback => 1;
our $VERSION = '0.46';
use Devel::StackTrace;
use Scalar::Util qw( blessed );
use Specio::OO;
{
my $attrs = {
message => {
isa => 'Str',
required => 1,
},
type => {
does => 'Specio::Constraint::Role::Interface',
required => 1,
},
value => {
required => 1,
},
stack_trace => {
init_arg => undef,
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
sub BUILD {
my $self = shift;
$self->{stack_trace}
= Devel::StackTrace->new( ignore_package => __PACKAGE__ );
return;
}
sub as_string {
my $self = shift;
my $str = $self->message;
$str .= "\n\n" . $self->stack_trace->as_string;
return $str;
}
sub throw {
my $self = shift;
die $self if blessed $self;
die $self->new(@_);
}
__PACKAGE__->_ooify;
1;
# ABSTRACT: An exception class for type constraint failures
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Exception - An exception class for type constraint failures
=head1 VERSION
version 0.46
=head1 SYNOPSIS
use Try::Tiny;
try {
$type->validate_or_die($value);
}
catch {
if ( $_->isa('Specio::Exception') ) {
print $_->message, "\n";
print $_->type->name, "\n";
print $_->value, "\n";
}
};
=head1 DESCRIPTION
This exception class is thrown by Specio when a type check fails. It emulates
the L<Throwable::Error> API, but doesn't use that module to avoid adding a
dependency on L<Moo>.
=for Pod::Coverage BUILD throw
=head1 API
This class provides the following methods:
=head2 $exception->message
The error message associated with the exception.
=head2 $exception->stack_trace
A L<Devel::StackTrace> object for the exception.
=head2 $exception->type
The type constraint object against which the value failed.
=head2 $exception->value
The value that failed the type check.
=head2 $exception->as_string
The exception as a string. This includes the method and the stack trace.
=head1 OVERLOADING
This class overloads stringification to call the C<as_string> method.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,166 @@
package Specio::Exporter;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Exporter';
use Specio::Helpers qw( install_t_sub );
use Specio::Registry
qw( exportable_types_for_package internal_types_for_package register );
my %Exported;
sub import {
my $package = shift;
my $reexport = shift;
my $caller = caller();
return if $Exported{$caller}{$package};
my $exported = exportable_types_for_package($package);
while ( my ( $name, $type ) = each %{$exported} ) {
register( $caller, $name, $type->clone, $reexport );
}
install_t_sub(
$caller,
internal_types_for_package($caller),
);
if ( $package->can('_also_export') ) {
for my $sub ( $package->_also_export ) {
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
*{ $caller . '::' . $sub } = \&{ $package . '::' . $sub };
}
}
$Exported{$caller}{$package} = 1;
return;
}
1;
# ABSTRACT: Base class for type libraries
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Exporter - Base class for type libraries
=head1 VERSION
version 0.46
=head1 SYNOPSIS
package MyApp::Type::Library;
use parent 'Specio::Exporter';
use Specio::Declare;
declare( ... );
# more types here
package MyApp::Foo;
use MyApp::Type::Library
=head1 DESCRIPTION
Inheriting from this package makes your package a type exporter. By default,
types defined in a package are never visible outside of the package. When you
inherit from this package, all the types you define internally become
available via exports.
The exported types are available through the importing package's C<t>
subroutine.
By default, types your package imports are not re-exported:
package MyApp::Type::Library;
use parent 'Specio::Exporter';
use Specio::Declare;
use Specio::Library::Builtins;
In this case, the types provided by L<Specio::Library::Builtins> are not
exported to packages which C<use MyApp::Type::Library>.
You can explicitly ask for types to be re-exported:
package MyApp::Type::Library;
use parent 'Specio::Exporter';
use Specio::Declare;
use Specio::Library::Builtins -reexport;
In this case, packages which C<use MyApp::Type::Library> will get all the
types from L<Specio::Library::Builtins> as well as any types defined in
C<MyApp::Type::Library>.
=head1 ADDITIONAL EXPORTS
If you want to export some additional subroutines from a package which has
C<Specio::Exporter> as its parent, define a sub named C<_also_export>. This
sub should return a I<list> of subroutines defined in your package that should
also be exported. These subs will be exported unconditionally to any package
that uses your package.
=head1 COMBINING LIBRARIES WITH L<Specio::Subs>
You can combine loading libraries with subroutine generation using
L<Specio::Subs> by using C<_also_export> and
C<Specio::Subs::subs_installed_into>:
package My::Library;
use My::Library::Internal -reexport;
use Specio::Library::Builtins -reexport;
use Specio::Subs qw( My::Library::Internal Specio::Library::Builtins );
sub _also_export {
return Specio::Subs::subs_installed_into(__PACKAGE__);
}
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,160 @@
package Specio::Helpers;
use strict;
use warnings;
use Carp qw( croak );
use Exporter 'import';
use overload ();
our $VERSION = '0.46';
use Scalar::Util qw( blessed );
our @EXPORT_OK = qw( install_t_sub is_class_loaded perlstring _STRINGLIKE );
sub install_t_sub {
# Specio::DeclaredAt use Specio::OO, which in turn uses
# Specio::Helpers. If we load this with "use" we get a cirular require and
# a big mess.
require Specio::DeclaredAt;
my $caller = shift;
my $types = shift;
# XXX - check to see if their t() is something else entirely?
return if $caller->can('t');
my $t = sub {
my $name = shift;
croak 'The t subroutine requires a single non-empty string argument'
unless _STRINGLIKE($name);
croak "There is no type named $name available for the $caller package"
unless exists $types->{$name};
my $found = $types->{$name};
return $found unless @_;
my %p = @_;
croak 'Cannot parameterize a non-parameterizable type'
unless $found->can('parameterize');
return $found->parameterize(
declared_at => Specio::DeclaredAt->new_from_caller(1),
%p,
);
};
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
no warnings 'redefine';
*{ $caller . '::t' } = $t;
}
return;
}
## no critic (Subroutines::ProhibitSubroutinePrototypes, Subroutines::ProhibitExplicitReturnUndef)
sub _STRINGLIKE ($) {
return $_[0] if _STRING( $_[0] );
return $_[0]
if blessed $_[0]
&& overload::Method( $_[0], q{""} )
&& length "$_[0]";
return undef;
}
# Borrowed from Params::Util
sub _STRING ($) {
return defined $_[0] && !ref $_[0] && length( $_[0] ) ? $_[0] : undef;
}
BEGIN {
if ( $] >= 5.010 && eval { require XString; 1 } ) {
*perlstring = \&XString::perlstring;
}
else {
require B;
*perlstring = \&B::perlstring;
}
}
# Borrowed from Types::Standard
sub is_class_loaded {
my $stash = do {
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
\%{ $_[0] . '::' };
};
return 1 if exists $stash->{ISA};
return 1 if exists $stash->{VERSION};
foreach my $globref ( values %{$stash} ) {
return 1
if ref \$globref eq 'GLOB'
? *{$globref}{CODE}
: ref $globref; # const or sub ref
}
return 0;
}
1;
# ABSTRACT: Helper subs for the Specio distro
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Helpers - Helper subs for the Specio distro
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There's nothing public here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,600 @@
package Specio::Library::Builtins;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Specio::Exporter';
use List::Util 1.33 ();
use overload ();
use re ();
use Scalar::Util ();
use Specio::Constraint::Parameterizable;
use Specio::Declare;
use Specio::Helpers ();
BEGIN {
local $@ = undef;
my $has_ref_util
= eval { require Ref::Util; Ref::Util->VERSION('0.112'); 1 };
sub _HAS_REF_UTIL () {$has_ref_util}
}
declare(
'Item',
inline => sub {'1'}
);
declare(
'Undef',
parent => t('Item'),
inline => sub {
'!defined(' . $_[1] . ')';
}
);
declare(
'Defined',
parent => t('Item'),
inline => sub {
'defined(' . $_[1] . ')';
}
);
declare(
'Bool',
parent => t('Item'),
inline => sub {
return sprintf( <<'EOF', ( $_[1] ) x 7 );
(
(
!ref( %s )
&& (
!defined( %s )
|| %s eq q{}
|| %s eq '1'
|| %s eq '0'
)
)
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, 'bool' )
)
)
EOF
}
);
declare(
'Value',
parent => t('Defined'),
inline => sub {
$_[0]->parent->inline_check( $_[1] ) . ' && !ref(' . $_[1] . ')';
}
);
declare(
'Ref',
parent => t('Defined'),
# no need to call parent - ref also checks for definedness
inline => sub { 'ref(' . $_[1] . ')' }
);
declare(
'Str',
parent => t('Value'),
inline => sub {
return sprintf( <<'EOF', ( $_[1] ) x 6 );
(
(
defined( %s )
&& !ref( %s )
&& (
( ref( \%s ) eq 'SCALAR' )
|| do { ( ref( \( my $val = %s ) ) eq 'SCALAR' ) }
)
)
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, q{""} )
)
)
EOF
}
);
my $value_type = t('Value');
declare(
'Num',
parent => t('Str'),
inline => sub {
return sprintf( <<'EOF', ( $_[1] ) x 5 );
(
(
defined( %s )
&& !ref( %s )
&& (
do {
( my $val = %s ) =~
/\A
-?[0-9]+(?:\.[0-9]+)?
(?:[Ee][\-+]?[0-9]+)?
\z/x
}
)
)
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '0+' )
)
)
EOF
}
);
declare(
'Int',
parent => t('Num'),
inline => sub {
return sprintf( <<'EOF', ( $_[1] ) x 6 );
(
(
defined( %s )
&& !ref( %s )
&& (
do { ( my $val1 = %s ) =~ /\A-?[0-9]+(?:[Ee]\+?[0-9]+)?\z/ }
)
)
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '0+' )
&& do { ( my $val2 = %s + 0 ) =~ /\A-?[0-9]+(?:[Ee]\+?[0-9]+)?\z/ }
)
)
EOF
}
);
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_coderef(%s)'
: q{ref(%s) eq 'CODE'};
declare(
'CodeRef',
parent => t('Ref'),
inline => sub {
return sprintf( <<"EOF", ( $_[1] ) x 3 );
(
$ref_check
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '&{}' )
)
)
EOF
}
);
}
{
# This is a 5.8 back-compat shim stolen from Type::Tiny's Devel::Perl58Compat
# module.
unless ( exists &re::is_regexp || _HAS_REF_UTIL ) {
require B;
*re::is_regexp = sub {
## no critic (ErrorHandling::RequireCheckingReturnValueOfEval)
eval { B::svref_2object( $_[0] )->MAGIC->TYPE eq 'r' };
};
}
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_regexpref(%s)'
: 're::is_regexp(%s)';
declare(
'RegexpRef',
parent => t('Ref'),
inline => sub {
return sprintf( <<"EOF", ( $_[1] ) x 3 );
(
$ref_check
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, 'qr' )
)
)
EOF
},
);
}
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_globref(%s)'
: q{ref( %s ) eq 'GLOB'};
declare(
'GlobRef',
parent => t('Ref'),
inline => sub {
return sprintf( <<"EOF", ( $_[1] ) x 3 );
(
$ref_check
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '*{}' )
)
)
EOF
}
);
}
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_globref(%s)'
: q{ref( %s ) eq 'GLOB'};
# NOTE: scalar filehandles are GLOB refs, but a GLOB ref is not always a
# filehandle
declare(
'FileHandle',
parent => t('Ref'),
inline => sub {
return sprintf( <<"EOF", ( $_[1] ) x 6 );
(
(
$ref_check
&& Scalar::Util::openhandle( %s )
)
||
(
Scalar::Util::blessed( %s )
&&
(
%s->isa('IO::Handle')
||
(
defined overload::Method( %s, '*{}' )
&& Scalar::Util::openhandle( *{ %s } )
)
)
)
)
EOF
}
);
}
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_blessed_ref(%s)'
: 'Scalar::Util::blessed(%s)';
declare(
'Object',
parent => t('Ref'),
inline => sub { sprintf( $ref_check, $_[1] ) },
);
}
declare(
'ClassName',
parent => t('Str'),
inline => sub {
return
sprintf(
<<'EOF', $_[0]->parent->inline_check( $_[1] ), ( $_[1] ) x 2 );
(
( %s )
&& length "%s"
&& Specio::Helpers::is_class_loaded( "%s" )
)
EOF
},
);
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_scalarref(%s) || Ref::Util::is_plain_refref(%s)'
: q{ref( %s ) eq 'SCALAR' || ref( %s ) eq 'REF'};
my $base_scalarref_check = sub {
return sprintf( <<"EOF", ( $_[0] ) x 4 );
(
(
$ref_check
)
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '\${}' )
)
)
EOF
};
declare(
'ScalarRef',
type_class => 'Specio::Constraint::Parameterizable',
parent => t('Ref'),
inline => sub { $base_scalarref_check->( $_[1] ) },
parameterized_inline_generator => sub {
my $self = shift;
my $parameter = shift;
my $val = shift;
return sprintf(
'( ( %s ) && ( %s ) )',
$base_scalarref_check->($val),
$parameter->inline_check( '${' . $val . '}' ),
);
}
);
}
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_arrayref(%s)'
: q{ref( %s ) eq 'ARRAY'};
my $base_arrayref_check = sub {
return sprintf( <<"EOF", ( $_[0] ) x 3 );
(
$ref_check
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '\@{}' )
)
)
EOF
};
declare(
'ArrayRef',
type_class => 'Specio::Constraint::Parameterizable',
parent => t('Ref'),
inline => sub { $base_arrayref_check->( $_[1] ) },
parameterized_inline_generator => sub {
my $self = shift;
my $parameter = shift;
my $val = shift;
return sprintf(
'( ( %s ) && ( List::Util::all { %s } @{ %s } ) )',
$base_arrayref_check->($val),
$parameter->inline_check('$_'),
$val,
);
}
);
}
{
my $ref_check
= _HAS_REF_UTIL
? 'Ref::Util::is_plain_hashref(%s)'
: q{ref( %s ) eq 'HASH'};
my $base_hashref_check = sub {
return sprintf( <<"EOF", ( $_[0] ) x 3 );
(
$ref_check
||
(
Scalar::Util::blessed( %s )
&& defined overload::Method( %s, '%%{}' )
)
)
EOF
};
declare(
'HashRef',
type_class => 'Specio::Constraint::Parameterizable',
parent => t('Ref'),
inline => sub { $base_hashref_check->( $_[1] ) },
parameterized_inline_generator => sub {
my $self = shift;
my $parameter = shift;
my $val = shift;
return sprintf(
'( ( %s ) && ( List::Util::all { %s } values %%{ %s } ) )',
$base_hashref_check->($val),
$parameter->inline_check('$_'),
$val,
);
}
);
}
declare(
'Maybe',
type_class => 'Specio::Constraint::Parameterizable',
parent => t('Item'),
inline => sub {'1'},
parameterized_inline_generator => sub {
my $self = shift;
my $parameter = shift;
my $val = shift;
return sprintf( <<'EOF', $val, $parameter->inline_check($val) );
( !defined( %s ) || ( %s ) )
EOF
},
);
1;
# ABSTRACT: Implements type constraint objects for Perl's built-in types
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Builtins - Implements type constraint objects for Perl's built-in types
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This library provides a set of types parallel to those provided by Moose.
The types are in the following hierarchy
Item
Bool
Maybe (of `a)
Undef
Defined
Value
Str
Num
Int
ClassName
Ref
ScalarRef (of `a)
ArrayRef (of `a)
HashRef (of `a)
CodeRef
RegexpRef
GlobRef
FileHandle
Object
=head2 Item
Accepts any value
=head2 Bool
Accepts a non-reference that is C<undef>, an empty string, C<0>, or C<1>. It
also accepts any object which overloads boolification.
=head2 Maybe (of `a)
A parameterizable type which accepts C<undef> or the type C<`a>. If not
parameterized this type will accept any value.
=head2 Undef
Only accepts C<undef>.
=head2 Value
Accepts any non-reference value.
=head2 Str
Accepts any non-reference value or an object which overloads stringification.
=head2 Num
Accepts nearly the same values as C<Scalar::Util::looks_like_number>, but does
not accept numbers with leading or trailing spaces, infinities, or NaN. Also
accepts an object which overloads numification.
=head2 Int
Accepts any integer value, or an object which overloads numification and
numifies to an integer.
=head2 ClassName
Accepts any value which passes C<Str> where the string is a loaded package.
=head2 Ref
Accepts any reference.
=head2 ScalarRef (of `a)
Accepts a scalar reference or an object which overloads scalar
dereferencing. If parameterized, the dereferenced value must be of type C<`a>.
=head2 ArrayRef (of `a)
Accepts a array reference or an object which overloads array dereferencing. If
parameterized, the values in the arrayref must be of type C<`a>.
=head2 HashRef (of `a)
Accepts a hash reference or an object which overloads hash dereferencing. If
parameterized, the values in the hashref must be of type C<`a>.
=head2 CodeRef
Accepts a code (sub) reference or an object which overloads code
dereferencing.
=head2 RegexpRef
Accepts a regex object created by C<qr//> or an object which overloads
regex interpolation.
=head2 GlobRef
Accepts a glob reference or an object which overloads glob dereferencing.
=head2 FileHandle
Accepts a glob reference which is an open file handle, any C<IO::Handle>
Object or subclass, or an object which overloads glob dereferencing and
returns a glob reference which is an open file handle.
=head2 Object
Accepts any blessed object.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,218 @@
package Specio::Library::Numeric;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Specio::Exporter';
use Specio::Declare;
use Specio::Library::Builtins;
declare(
'PositiveNum',
parent => t('Num'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s > 0
)
EOF
},
);
declare(
'PositiveOrZeroNum',
parent => t('Num'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s >= 0
)
EOF
},
);
declare(
'PositiveInt',
parent => t('Int'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s > 0
)
EOF
},
);
declare(
'PositiveOrZeroInt',
parent => t('Int'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s >= 0
)
EOF
},
);
declare(
'NegativeNum',
parent => t('Num'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s < 0
)
EOF
},
);
declare(
'NegativeOrZeroNum',
parent => t('Num'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s <= 0
)
EOF
},
);
declare(
'NegativeInt',
parent => t('Int'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s < 0
)
EOF
},
);
declare(
'NegativeOrZeroInt',
parent => t('Int'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s <= 0
)
EOF
},
);
declare(
'SingleDigit',
parent => t('Int'),
inline => sub {
return
sprintf(
<<'EOF', $_[0]->parent->inline_check( $_[1] ), ( $_[1] ) x 2 );
(
%s
&&
%s >= -9
&&
%s <= 9
)
EOF
},
);
1;
# ABSTRACT: Implements type constraint objects for some common numeric types
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Numeric - Implements type constraint objects for some common numeric types
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This library provides some additional string numeric for common cases.
=head2 PositiveNum
=head2 PositiveOrZeroNum
=head2 PositiveInt
=head2 PositiveOrZeroInt
=head2 NegativeNum
=head2 NegativeOrZeroNum
=head2 NegativeInt
=head2 NegativeOrZeroInt
=head2 SingleDigit
A single digit from -9 to 9.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,208 @@
package Specio::Library::Perl;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Specio::Exporter';
use Specio::Library::String;
use version 0.83 ();
use Specio::Declare;
my $package_inline = sub {
return sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s =~ /\A[^\W\d]\w*(?:::\w+)*\z/
)
EOF
};
declare(
'PackageName',
parent => t('NonEmptyStr'),
inline => $package_inline,
);
declare(
'ModuleName',
parent => t('NonEmptyStr'),
inline => $package_inline,
);
declare(
'DistName',
parent => t('NonEmptyStr'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s =~ /\A[^\W\d]\w*(?:-\w+)*\z/
)
EOF
},
);
declare(
'Identifier',
parent => t('NonEmptyStr'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s =~ /\A[^\W\d]\w*\z/
)
EOF
},
);
declare(
'SafeIdentifier',
parent => t('Identifier'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
%s !~ /\A[_ab]\z/
)
EOF
},
);
declare(
'LaxVersionStr',
parent => t('NonEmptyStr'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
version::is_lax(%s)
)
EOF
},
);
declare(
'StrictVersionStr',
parent => t('NonEmptyStr'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
version::is_strict(%s)
)
EOF
},
);
1;
# ABSTRACT: Implements type constraint objects for some common Perl language things
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Perl - Implements type constraint objects for some common Perl language things
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This library provides some additional string types for common cases.
=head2 PackageName
A valid package name. Unlike the C<ClassName> constraint from the
L<Specio::Library::Builtins> library, this package does not need to be loaded.
This type does allow Unicode characters.
=head2 ModuleName
Same as C<PackageName>.
=head2 DistName
A valid distribution name like C<DBD-Pg> Basically this is the same as a
package name with the double-colons replaced by dashes. Note that there are
some historical distribution names that don't fit this pattern, like
C<CGI.pm>.
This type does allow Unicode characters.
=head2 Identifier
An L<Identifier|perldata/Variable names> is something that could be used as a
symbol name or other identifier (filehandle, directory handle, subroutine
name, format name, or label). It's what you put after the sigil (dollar sign,
at sign, percent sign) in a variable name. Generally, it's a bunch of
word characters not starting with a digit.
This type does allow Unicode characters.
=head2 SafeIdentifier
This is just like an C<Identifier> but it excludes the single-character
variables underscore (C<_>), C<a>< and C<b>, as these are special variables to
the Perl interpreter.
=head2 LaxVersionStr and StrictVersionStr
Lax and strict version strings use the L<is_lax|version/is_lax> and
L<is_strict|version/is_strict> methods from C<version> to check if the given
string would be a valid lax or strict version. L<version::Internals> covers
the details but basically: lax versions are everything you may do, and strict
omit many of the usages best avoided.
=head2 CREDITS
Much of the code and docs for this library comes from MooseX::Types::Perl,
written by Ricardo SIGNES <rjbs@cpan.org>.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,127 @@
package Specio::Library::String;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Specio::Exporter';
use Specio::Declare;
use Specio::Library::Builtins;
declare(
'NonEmptySimpleStr',
parent => t('Str'),
inline => sub {
return
sprintf(
<<'EOF', $_[0]->parent->inline_check( $_[1] ), ( $_[1] ) x 3 );
(
%s
&&
length %s > 0
&&
length %s <= 255
&&
%s !~ /[\n\r\x{2028}\x{2029}]/
)
EOF
},
);
declare(
'NonEmptyStr',
parent => t('Str'),
inline => sub {
return
sprintf( <<'EOF', $_[0]->parent->inline_check( $_[1] ), $_[1] );
(
%s
&&
length %s
)
EOF
},
);
declare(
'SimpleStr',
parent => t('Str'),
inline => sub {
return
sprintf(
<<'EOF', $_[0]->parent->inline_check( $_[1] ), ( $_[1] ) x 2 );
(
%s
&&
length %s <= 255
&&
%s !~ /[\n\r\x{2028}\x{2029}]/
)
EOF
},
);
1;
# ABSTRACT: Implements type constraint objects for some common string types
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::String - Implements type constraint objects for some common string types
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This library provides some additional string types for common cases.
=head2 NonEmptyStr
A string which has at least one character.
=head2 SimpleStr
A string that is 255 characters or less with no vertical whitespace
characters.
=head2 NonEmptySimpleStr
A non-empty string that is 255 characters or less with no vertical whitespace
characters.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,250 @@
package Specio::Library::Structured;
use strict;
use warnings;
our $VERSION = '0.46';
use parent 'Specio::Exporter';
use Carp qw( confess );
use List::Util ();
use Scalar::Util qw( blessed );
use Specio::Constraint::Structurable;
use Specio::Declare;
use Specio::Library::Builtins;
use Specio::Library::Structured::Dict;
use Specio::Library::Structured::Map;
use Specio::Library::Structured::Tuple;
use Specio::TypeChecks qw( does_role );
## no critic (Variables::ProtectPrivateVars)
declare(
'Dict',
type_class => 'Specio::Constraint::Structurable',
parent => Specio::Library::Structured::Dict->parent,
inline => \&Specio::Library::Structured::Dict::_inline,
parameterization_args_builder =>
\&Specio::Library::Structured::Dict::_parameterization_args_builder,
name_builder => \&Specio::Library::Structured::Dict::_name_builder,
structured_inline_generator =>
\&Specio::Library::Structured::Dict::_structured_inline_generator,
);
declare(
'Map',
type_class => 'Specio::Constraint::Structurable',
parent => Specio::Library::Structured::Map->parent,
inline => \&Specio::Library::Structured::Map::_inline,
parameterization_args_builder =>
\&Specio::Library::Structured::Map::_parameterization_args_builder,
name_builder => \&Specio::Library::Structured::Map::_name_builder,
structured_inline_generator =>
\&Specio::Library::Structured::Map::_structured_inline_generator,
);
declare(
'Tuple',
type_class => 'Specio::Constraint::Structurable',
parent => Specio::Library::Structured::Tuple->parent,
inline => \&Specio::Library::Structured::Tuple::_inline,
parameterization_args_builder =>
\&Specio::Library::Structured::Tuple::_parameterization_args_builder,
name_builder => \&Specio::Library::Structured::Tuple::_name_builder,
structured_inline_generator =>
\&Specio::Library::Structured::Tuple::_structured_inline_generator,
);
## use critic
sub optional {
return { optional => shift };
}
sub slurpy {
return { slurpy => shift };
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _also_export {qw( optional slurpy )}
## use critic
1;
# ABSTRACT: Structured types for Specio (Dict, Map, Tuple)
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Structured - Structured types for Specio (Dict, Map, Tuple)
=head1 VERSION
version 0.46
=head1 SYNOPSIS
use Specio::Library::Builtins;
use Specio::Library::String;
use Specio::Library::Structured;
my $map = t(
'Map',
of => {
key => t('NonEmptyStr'),
value => t('Int'),
},
);
my $tuple = t(
'Tuple',
of => [ t('Str'), t('Num') ],
);
my $dict = t(
'Dict',
of => {
kv => {
name => t('Str'),
age => t('Int'),
},
},
);
=head1 DESCRIPTION
B<This particular library should be considered in an alpha state. The syntax
for defining structured types may change, as well as some of the internals of
its implementation.>
This library provides a set of structured types for Specio, C<Dict>, C<Map>,
and C<Tuple>. This library also exports two helper subs used for some types,
C<optional> and C<slurpy>.
All structured types are parameterized by calling C<< t( 'Type Name', of =>
... ) >>. The arguments passed after C<of> vary for each type.
=head2 Dict
A C<Dict> is a hashref with a well-defined set of keys and types for those
key.
The argument passed to C<of> should be a single hashref. That hashref must
contain a C<kv> key defining the expected keys and the types for their
values. This C<kv> value is itself a hashref. If a key/value pair is optional,
use C<optional> around the I<type> for that key:
my $person = t(
'Dict',
of => {
kv => {
first => t('NonEmptyStr'),
middle => optional( t('NonEmptyStr') ),
last => t('NonEmptyStr'),
},
},
);
If a key is optional, then it can be omitted entirely, but if it passed then
it's type will be checked, so it cannot just be set to C<undef>.
You can also pass a C<slurpy> key. If this is passed, then the C<Dict> will
allow other, unknown keys, as long as they match the specified type:
my $person = t(
'Dict',
of => {
kv => {
first => t('NonEmptyStr'),
middle => optional( t('NonEmptyStr') ),
last => t('NonEmptyStr'),
},
slurpy => t('Int'),
},
);
=head2 Map
A C<Map> is a hashref with specified types for its keys and values, but no
well-defined key names.
The argument passed to C<of> should be a single hashref with two keys, C<key>
and C<value>. The type for the C<key> will typically be some sort of key, but
if you're using a tied hash or an object with hash overloading it could
conceivably be any sort of value.
=head2 Tuple
A C<Tuple> is an arrayref with a fixed set of members in a specific order.
The argument passed to C<of> should be a single arrayref consisting of
types. You can mark a slot in the C<Tuple> as optional by wrapping the type in
a call to C<optional>:
my $record = t(
'Tuple',
of => [
t('PositiveInt'),
t('Str'),
optional( t('Num') ),
optional( t('Num') ),
],
);
You can have as many C<optional> elements as you want, but they must always
come in sequence at the end of the tuple definition. You cannot interleave
required and optional elements.
You can also make the Tuple accept an arbitrary number of values by wrapping
the last type in a call to C<slurpy>:
my $record = t(
'Tuple',
of => [
t('PositiveInt'),
t('Str'),
slurpy( t('Num') ),
],
);
In this case, the C<Tuple> will require the first two elements and then allow
any number (including zero) of C<Num> elements.
You cannot mix C<optional> and C<slurpy> in a C<Tuple> definition.
=for Pod::Coverage optional slurpy
=head1 LIMITATIONS
Currently all structured types require that the types they are structured with
can be inlined. This may change in the future, but inlining all your types is
a really good idea, so you should do that anyway.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,168 @@
package Specio::Library::Structured::Dict;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use List::Util ();
use Scalar::Util qw( blessed );
use Specio::Helpers qw( perlstring );
use Specio::Library::Builtins;
use Specio::TypeChecks qw( does_role );
my $hashref = t('HashRef');
sub parent {$hashref}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _inline {
$hashref->inline_check( $_[1] );
}
sub _parameterization_args_builder {
my $self = shift;
my $args = shift;
for my $p ( ( $args->{slurpy} || () ), values %{ $args->{kv} } ) {
my $type;
if ( blessed($p) ) {
$type = $p;
}
else {
if ( ref $p eq 'HASH' && $p->{optional} ) {
$type = $p->{optional};
}
else {
confess
'Can only pass types, optional types, and slurpy types when defining a Dict';
}
}
does_role( $type, 'Specio::Constraint::Role::Interface' )
or confess
'All parameters passed to ->parameterize must be objects which do the Specio::Constraint::Role::Interface role';
confess
'All parameters passed to ->parameterize must be inlinable constraints'
unless $type->can_be_inlined;
}
return %{$args};
}
sub _name_builder {
my $self = shift;
my $p = shift;
## no critic (Subroutines::ProtectPrivateSubs)
my @kv;
for my $k ( sort keys %{ $p->{kv} } ) {
my $v = $p->{kv}{$k};
if ( blessed($v) ) {
push @kv, "$k => " . $self->_name_or_anon($v);
}
elsif ( $v->{optional} ) {
push @kv,
"$k => " . $self->_name_or_anon( $v->{optional} ) . '?';
}
}
if ( $p->{slurpy} ) {
push @kv, $self->_name_or_anon( $p->{slurpy} ) . '...';
}
return 'Dict{ ' . ( join ', ', @kv ) . ' }';
}
sub _structured_inline_generator {
my $self = shift;
my $val = shift;
my %args = @_;
my @code = sprintf( '( %s )', $hashref->_inline_check($val) );
for my $k ( sort keys %{ $args{kv} } ) {
my $p = $args{kv}{$k};
my $access = sprintf( '%s->{%s}', $val, perlstring($k) );
if ( !blessed($p) ) {
my $type = $p->{optional};
push @code,
sprintf(
'( exists %s ? ( %s ) : 1 )',
$access, $type->_inline_check($access)
);
}
else {
push @code, sprintf( '( %s )', $p->_inline_check($access) );
}
}
if ( $args{slurpy} ) {
my $check
= '( do { my %%_____known_____ = map { $_ => 1 } ( %s ); List::Util::all { %s } grep { ! $_____known_____{$_} } sort keys %%{ %s } } )';
push @code,
sprintf(
$check,
( join ', ', map { perlstring($_) } keys %{ $args{kv} } ),
$args{slurpy}->_inline_check( sprintf( '%s->{$_}', $val ) ),
$val,
);
}
return '( ' . ( join ' && ', @code ) . ' )';
}
1;
# ABSTRACT: Guts of Dict structured type
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Structured::Dict - Guts of Dict structured type
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There are no user facing parts here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,124 @@
package Specio::Library::Structured::Map;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use List::Util ();
use Specio::Library::Builtins;
use Specio::TypeChecks qw( does_role );
my $hashref = t('HashRef');
sub parent {$hashref}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _inline {
$hashref->inline_check( $_[1] );
}
sub _parameterization_args_builder {
my $self = shift;
my $args = shift;
for my $k (qw( key value )) {
does_role(
$args->{$k},
'Specio::Constraint::Role::Interface'
)
or confess
qq{The "$k" parameter passed to ->parameterize must be one or more objects which do the Specio::Constraint::Role::Interface role};
confess
qq{The "$k" parameter passed to ->parameterize must be an inlinable constraint}
unless $args->{$k}->can_be_inlined;
}
return map { $_ => $args->{$_} } qw( key value );
}
sub _name_builder {
my $self = shift;
my $p = shift;
## no critic (Subroutines::ProtectPrivateSubs)
return
'Map{ '
. $self->_name_or_anon( $p->{key} ) . ' => '
. $self->_name_or_anon( $p->{value} ) . ' }';
}
sub _structured_inline_generator {
my $self = shift;
my $val = shift;
my %args = @_;
my $code = <<'EOF';
(
( %s )
&& ( List::Util::all { %s } keys %%{ %s } )
&& ( List::Util::all { %s } values %%{ %s } )
)
EOF
return sprintf(
$code,
$hashref->_inline_check($val),
$args{key}->inline_check('$_'),
$val,
$args{value}->inline_check('$_'),
$val,
);
}
1;
# ABSTRACT: Guts of Map structured type
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Structured::Map - Guts of Map structured type
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There are no user facing parts here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,218 @@
package Specio::Library::Structured::Tuple;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( confess );
use List::Util ();
use Scalar::Util qw( blessed );
use Specio::Library::Builtins;
use Specio::TypeChecks qw( does_role );
my $arrayref = t('ArrayRef');
sub parent {$arrayref}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _inline {
$arrayref->inline_check( $_[1] );
}
sub _parameterization_args_builder {
my $self = shift;
my $args = shift;
my $saw_slurpy;
my $saw_optional;
for my $p ( @{$args} ) {
if ($saw_slurpy) {
confess
'A Tuple cannot have any parameters after a slurpy parameter';
}
if ( $saw_optional && blessed($p) ) {
confess
'A Tuple cannot have a non-optional parameter after an optional parameter';
}
my $type;
if ( blessed($p) ) {
$type = $p;
}
else {
if ( ref $p eq 'HASH' ) {
if ( $p->{optional} ) {
$saw_optional = 1;
$type = $p->{optional};
}
if ( $p->{slurpy} ) {
$saw_slurpy = 1;
$type = $p->{slurpy};
}
}
else {
confess
'Can only pass types, optional types, and slurpy types when defining a Tuple';
}
}
if ( $saw_optional && $saw_slurpy ) {
confess
'Cannot defined a slurpy Tuple with optional slots as well';
}
does_role( $type, 'Specio::Constraint::Role::Interface' )
or confess
'All parameters passed to ->parameterize must be objects which do the Specio::Constraint::Role::Interface role';
confess
'All parameters passed to ->parameterize must be inlinable constraints'
unless $type->can_be_inlined;
}
return ( of => $args );
}
sub _name_builder {
my $self = shift;
my $p = shift;
my @names;
for my $m ( @{ $p->{of} } ) {
## no critic (Subroutines::ProtectPrivateSubs)
if ( blessed($m) ) {
push @names, $self->_name_or_anon($m);
}
elsif ( $m->{optional} ) {
push @names, $self->_name_or_anon( $m->{optional} ) . '?';
}
elsif ( $m->{slurpy} ) {
push @names, $self->_name_or_anon( $m->{slurpy} ) . '...';
}
}
return 'Tuple[ ' . ( join ', ', @names ) . ' ]';
}
sub _structured_inline_generator {
my $self = shift;
my $val = shift;
my %args = @_;
my @of = @{ $args{of} };
my $slurpy;
$slurpy = ( pop @of )->{slurpy}
if !blessed( $of[-1] ) && $of[-1]->{slurpy};
my @code = sprintf( '( %s )', $arrayref->_inline_check($val) );
unless ($slurpy) {
my $min = 0;
my $max = 0;
for my $p (@of) {
# Unblessed values are optional.
if ( blessed($p) ) {
$min++;
$max++;
}
else {
$max++;
}
}
if ($min) {
push @code,
sprintf(
'( @{ %s } >= %d && @{ %s } <= %d )',
$val, $min, $val, $max
);
}
}
for my $i ( 0 .. $#of ) {
my $p = $of[$i];
my $access = sprintf( '%s->[%d]', $val, $i );
if ( !blessed($p) ) {
my $type = $p->{optional};
push @code,
sprintf(
'( @{%s} >= %d ? ( %s ) : 1 )', $val, $i + 1,
$type->_inline_check($access)
);
}
else {
push @code,
sprintf( '( %s )', $p->_inline_check($access) );
}
}
if ($slurpy) {
my $non_slurpy = scalar @of;
my $check
= '( @{%s} > %d ? ( List::Util::all { %s } @{%s}[%d .. $#{%s}] ) : 1 )';
push @code,
sprintf(
$check,
$val, $non_slurpy, $slurpy->_inline_check('$_'),
$val, $non_slurpy, $val,
);
}
return '( ' . ( join ' && ', @code ) . ' )';
}
1;
# ABSTRACT: Guts of Tuple structured type
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Library::Structured::Tuple - Guts of Tuple structured type
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There are no user facing parts here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

412
database/perl/vendor/lib/Specio/OO.pm vendored Normal file
View File

@@ -0,0 +1,412 @@
package Specio::OO;
use strict;
use warnings;
use Carp qw( confess );
use List::Util qw( all );
use MRO::Compat;
use Role::Tiny;
use Scalar::Util qw( weaken );
use Specio::Helpers qw( perlstring );
use Specio::PartialDump qw( partial_dump );
use Specio::TypeChecks;
use Storable qw( dclone );
our $VERSION = '0.46';
use Exporter qw( import );
## no critic (Modules::ProhibitAutomaticExportation)
our @EXPORT = qw(
clone
_ooify
);
## use critic
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _ooify {
my $class = shift;
my $attrs = $class->_attrs;
for my $name ( sort keys %{$attrs} ) {
my $attr = $attrs->{$name};
_inline_reader( $class, $name, $attr );
_inline_predicate( $class, $name, $attr );
}
_inline_constructor($class);
}
## use critic
sub _inline_reader {
my $class = shift;
my $name = shift;
my $attr = shift;
my $reader;
if ( $attr->{lazy} && ( my $builder = $attr->{builder} ) ) {
my $source = <<'EOF';
sub {
unless ( exists $_[0]->{%s} ) {
$_[0]->{%s} = $_[0]->%s;
Scalar::Util::weaken( $_[0]->{%s} ) if %s && ref $_[0]->{%s};
}
$_[0]->{%s};
}
EOF
$reader = sprintf(
$source,
$name,
$name,
$builder,
$name,
( $attr->{weak_ref} ? 1 : 0 ),
$name,
$name,
);
}
else {
$reader = sprintf( 'sub { $_[0]->{%s} }', $name );
}
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
*{ $class . '::' . $name } = _eval_or_die(
$reader, $class . '->' . $name,
);
}
}
sub _inline_predicate {
my $class = shift;
my $name = shift;
my $attr = shift;
return unless $attr->{predicate};
my $predicate = "sub { exists \$_[0]->{$name} }";
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
*{ $class . '::' . $attr->{predicate} } = _eval_or_die(
$predicate, $class . '->' . $attr->{predicate},
);
}
}
my @RolesWithBUILD = qw( Specio::Constraint::Role::Interface );
# This is an optimization to avoid calling this many times over:
#
# Specio::TypeChecks->can( 'is_' . $attr->{isa} )
my %TypeChecks;
BEGIN {
for my $sub (@Specio::TypeChecks::EXPORT_OK) {
my ($type) = $sub =~ /^is_(.+)$/
or next;
$TypeChecks{$type} = Specio::TypeChecks->can($sub);
}
}
sub _inline_constructor {
my $class = shift;
my @build_subs;
for my $parent ( @{ mro::get_linear_isa($class) } ) {
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
push @build_subs, $parent . '::BUILD'
if defined &{ $parent . '::BUILD' };
}
}
# This is all a hack to avoid needing Class::Method::Modifiers to add a
# BUILD from a role. We can't just call the method in the role "BUILD" or
# it will be shadowed by a class's BUILD. So we give it a wacky unique
# name. We need to explicitly know which roles have a _X_BUILD method
# because Role::Tiny doesn't provide a way to list all the roles applied
# to a class.
for my $role (@RolesWithBUILD) {
if ( Role::Tiny::does_role( $class, $role ) ) {
( my $build_name = $role ) =~ s/::/_/g;
$build_name = q{_} . $build_name . '_BUILD';
push @build_subs, $role . '::' . $build_name;
}
}
my $constructor = <<'EOF';
sub {
my $class = shift;
my %p = do {
if ( @_ == 1 ) {
if ( ref $_[0] eq 'HASH' ) {
%{ shift() };
}
else {
Specio::OO::_constructor_confess(
Specio::OO::_bad_args_message( $class, @_ ) );
}
}
else {
Specio::OO::_constructor_confess(
Specio::OO::_bad_args_message( $class, @_ ) )
if @_ % 2;
@_;
}
};
my $self = bless {}, $class;
EOF
my $attrs = $class->_attrs;
for my $name ( sort keys %{$attrs} ) {
my $attr = $attrs->{$name};
my $key_name = defined $attr->{init_arg} ? $attr->{init_arg} : $name;
if ( $attr->{required} ) {
$constructor .= <<"EOF";
Specio::OO::_constructor_confess(
"$class->new requires a $key_name argument.")
unless exists \$p{$key_name};
EOF
}
if ( $attr->{builder} && !$attr->{lazy} ) {
my $builder = $attr->{builder};
$constructor .= <<"EOF";
\$p{$key_name} = $class->$builder unless exists \$p{$key_name};
EOF
}
if ( $attr->{isa} ) {
my $validator;
if ( $TypeChecks{ $attr->{isa} } ) {
$validator
= 'Specio::TypeChecks::is_'
. $attr->{isa}
. "( \$p{$key_name} )";
}
else {
my $quoted_class = perlstring( $attr->{isa} );
$validator
= "Specio::TypeChecks::isa_class( \$p{$key_name}, $quoted_class )";
}
$constructor .= <<"EOF";
if ( exists \$p{$key_name} && !$validator ) {
Carp::confess(
Specio::OO::_bad_value_message(
"The value you provided to $class->new for $key_name is not a valid $attr->{isa}.",
\$p{$key_name},
)
);
}
EOF
}
if ( $attr->{does} ) {
my $quoted_role = perlstring( $attr->{does} );
$constructor .= <<"EOF";
if ( exists \$p{$key_name} && !Specio::TypeChecks::does_role( \$p{$key_name}, $quoted_role ) ) {
Carp::confess(
Specio::OO::_bad_value_message(
"The value you provided to $class->new for $key_name does not do the $attr->{does} role.",
\$p{$key_name},
)
);
}
EOF
}
if ( $attr->{weak_ref} ) {
$constructor .= " Scalar::Util::weaken( \$p{$key_name} );\n";
}
$constructor
.= " \$self->{$name} = \$p{$key_name} if exists \$p{$key_name};\n";
$constructor .= "\n";
}
$constructor .= ' $self->' . $_ . "(\\%p);\n" for @build_subs;
$constructor .= <<'EOF';
return $self;
}
EOF
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
*{ $class . '::new' } = _eval_or_die(
$constructor, $class . '->new',
);
}
}
# This used to be done with Eval::Closure but that added a lot of unneeded
# overhead. We're never actually eval'ing a closure, just plain source, so
# doing it by hand is a worthwhile optimization.
sub _eval_or_die {
local $@ = undef;
## no critic (Variables::RequireInitializationForLocalVars)
# $SIG{__DIE__} = undef causes warnings with 5.8.x
local $SIG{__DIE__};
## no critic (BuiltinFunctions::ProhibitStringyEval)
my $sub = eval <<"EOF";
#line 1 "$_[1]"
$_[0];
EOF
my $e = $@;
die $e if $e;
return $sub;
}
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _constructor_confess {
local $Carp::CarpLevel = $Carp::CarpLevel + 1;
confess shift;
}
sub _bad_args_message {
my $class = shift;
return
"$class->new requires either a hashref or hash as arguments. You passed "
. partial_dump(@_);
}
sub _bad_value_message {
my $message = shift;
my $value = shift;
return $message . ' You passed ' . partial_dump($value);
}
## use critic
my %BuiltinTypes = map { $_ => 1 } qw(
SCALAR
ARRAY
HASH
CODE
REF
GLOB
LVALUE
FORMAT
IO
VSTRING
Regexp
);
sub clone {
my $self = shift;
# Attributes which provide a clone method are cloned by calling that
# method on the _clone_ (not the original). This is primarily to allow us
# to clone the coercions contained by a type in a way that doesn't lead to
# circular clone (type clones coercions which in turn need to clone their
# to/from types which in turn ...).
my $attrs = $self->_attrs;
my %special = map { $_ => $attrs->{$_}{clone} }
grep { $attrs->{$_}{clone} } keys %{$attrs};
my $new;
for my $key ( keys %{$self} ) {
my $value = $self->{$key};
if ( $special{$key} ) {
$new->{$key} = $value;
next;
}
# We need to special case arrays of Specio objects, as they may
# contain code refs which cannot be cloned with dclone. Not using
# blessed is a small optimization.
if ( ( ref $value eq 'ARRAY' )
&& all { ( ref($_) || q{} ) =~ /Specio/ } @{$value} ) {
$new->{$key} = [ map { $_->clone } @{$value} ];
next;
}
# This is a weird hacky way of trying to avoid calling
# Scalar::Util::blessed, which showed up as a hotspot in profiling of
# loading DateTime. That's because we call ->clone a _lot_ (it's
# called every time a type is exported).
my $ref = ref $value;
$new->{$key}
= !$ref ? $value
: $ref eq 'CODE' ? $value
: $BuiltinTypes{$ref} ? dclone($value)
: $value->clone;
}
bless $new, ( ref $self );
for my $key ( keys %special ) {
my $method = $special{$key};
$new->{$key} = $new->$method;
}
return $new;
}
1;
# ABSTRACT: A painfully poor reimplementation of Moo(se)
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::OO - A painfully poor reimplementation of Moo(se)
=head1 VERSION
version 0.46
=head1 DESCRIPTION
Specio can't depend on Moo or Moose, so this module provides a terrible
reimplementation of a small slice of their features.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,261 @@
package Specio::PartialDump;
use strict;
use warnings;
our $VERSION = '0.46';
use Scalar::Util qw( looks_like_number reftype blessed );
use Exporter qw( import );
our @EXPORT_OK = qw( partial_dump );
my $MaxLength = 100;
my $MaxElements = 6;
my $MaxDepth = 2;
sub partial_dump {
my (@args) = @_;
my $dump
= _should_dump_as_pairs(@args)
? _dump_as_pairs( 1, @args )
: _dump_as_list( 1, @args );
if ( length($dump) > $MaxLength ) {
my $max_length = $MaxLength - 3;
$max_length = 0 if $max_length < 0;
substr( $dump, $max_length, length($dump) - $max_length ) = '...';
}
return $dump;
}
sub _should_dump_as_pairs {
my (@what) = @_;
return if @what % 2 != 0; # must be an even list
for ( my $i = 0; $i < @what; $i += 2 ) {
return if ref $what[$i]; # plain strings are keys
}
return 1;
}
sub _dump_as_pairs {
my ( $depth, @what ) = @_;
my $truncated;
if ( defined $MaxElements and ( @what / 2 ) > $MaxElements ) {
$truncated = 1;
@what = splice( @what, 0, $MaxElements * 2 );
}
return join(
', ', _dump_as_pairs_recursive( $depth, @what ),
( $truncated ? "..." : () )
);
}
sub _dump_as_pairs_recursive {
my ( $depth, @what ) = @_;
return unless @what;
my ( $key, $value, @rest ) = @what;
return (
( _format_key( $depth, $key ) . ': ' . _format( $depth, $value ) ),
_dump_as_pairs_recursive( $depth, @rest ),
);
}
sub _dump_as_list {
my ( $depth, @what ) = @_;
my $truncated;
if ( @what > $MaxElements ) {
$truncated = 1;
@what = splice( @what, 0, $MaxElements );
}
return join(
', ', ( map { _format( $depth, $_ ) } @what ),
( $truncated ? "..." : () )
);
}
sub _format {
my ( $depth, $value ) = @_;
defined($value)
? (
ref($value)
? (
blessed($value)
? _format_object( $depth, $value )
: _format_ref( $depth, $value )
)
: (
looks_like_number($value)
? _format_number( $depth, $value )
: _format_string( $depth, $value )
)
)
: _format_undef( $depth, $value ),
}
sub _format_key {
my ( undef, $key ) = @_;
return $key;
}
sub _format_ref {
my ( $depth, $ref ) = @_;
if ( $depth > $MaxDepth ) {
return overload::StrVal($ref);
}
else {
my $reftype = reftype($ref);
$reftype = 'SCALAR'
if $reftype eq 'REF' || $reftype eq 'LVALUE';
my $method = "_format_" . lc $reftype;
if ( my $sub = __PACKAGE__->can($method) ) {
return $sub->( $depth, $ref );
}
else {
return overload::StrVal($ref);
}
}
}
sub _format_array {
my ( $depth, $array ) = @_;
my $class = blessed($array) || '';
$class .= "=" if $class;
return $class . "[ " . _dump_as_list( $depth + 1, @$array ) . " ]";
}
sub _format_hash {
my ( $depth, $hash ) = @_;
my $class = blessed($hash) || '';
$class .= "=" if $class;
return $class . "{ " . _dump_as_pairs(
$depth + 1,
map { $_ => $hash->{$_} } sort keys %$hash
) . " }";
}
sub _format_scalar {
my ( $depth, $scalar ) = @_;
my $class = blessed($scalar) || '';
$class .= "=" if $class;
return $class . "\\" . _format( $depth + 1, $$scalar );
}
sub _format_object {
my ( $depth, $object ) = @_;
return _format_ref( $depth, $object );
}
sub _format_string {
my ( undef, $str ) = @_;
# FIXME use String::Escape ?
# remove vertical whitespace
$str =~ s/\n/\\n/g;
$str =~ s/\r/\\r/g;
# reformat nonprintables
$str =~ s/(\P{IsPrint})/"\\x{" . sprintf("%x", ord($1)) . "}"/ge;
_quote($str);
}
sub _quote {
my ($str) = @_;
qq{"$str"};
}
sub _format_undef {"undef"}
sub _format_number {
my ( undef, $value ) = @_;
return "$value";
}
# ABSTRACT: A partially rear-ended copy of Devel::PartialDump without prereqs
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::PartialDump - A partially rear-ended copy of Devel::PartialDump without prereqs
=head1 VERSION
version 0.46
=head1 SYNOPSIS
use Specio::PartialDump qw( partial_dump );
partial_dump( { foo => 42 } );
partial_dump(qw( a b c d e f g ));
partial_dump( foo => 42, bar => [ 1, 2, 3 ], );
=head1 DESCRIPTION
This is a copy of Devel::PartialDump with all the OO bits and prereqs
removed. You may want to use this module in your own code to generate nicely
formatted messages when a type constraint fails.
This module optionally exports one sub, C<partial_dump>. This sub accepts any
number of arguments. If given more than one, it will assume that it's either
been given a list of key/value pairs (to build a hash) or a list of values (to
build an array) and dump them appropriately. Objects and references are
stringified in a sane way.
=for Pod::Coverage partial_dump
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2008 by יובל קוג'מן (Yuval Kogman).
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut

View File

@@ -0,0 +1,103 @@
package Specio::Registry;
use strict;
use warnings;
use parent 'Exporter';
our $VERSION = '0.46';
use Carp qw( confess croak );
our @EXPORT_OK
= qw( exportable_types_for_package internal_types_for_package register );
my %Registry;
sub register {
confess
'register requires three or four arguments (package, name, type, [exportable])'
unless @_ == 3 || @_ == 4;
my $package = shift;
my $name = shift;
my $type = shift;
my $exportable = shift;
croak "The $package package already has a type named $name"
if $Registry{$package}{internal}{$name};
# This is structured so that we can always return a _reference_ for
# *_types_for_package. This means that the generated t sub sees any
# changes to the registry as they happen. This is important inside a
# package that is declaring new types. It needs to be able to see types it
# has declared.
$Registry{$package}{internal}{$name} = $type;
$Registry{$package}{exportable}{$name} = $type
if $exportable;
return;
}
sub exportable_types_for_package {
my $package = shift;
return $Registry{$package}{exportable} ||= {};
}
sub internal_types_for_package {
my $package = shift;
return $Registry{$package}{internal} ||= {};
}
1;
# ABSTRACT: Implements the per-package type registry
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Registry - Implements the per-package type registry
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There's nothing public here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,137 @@
package Specio::Role::Inlinable;
use strict;
use warnings;
our $VERSION = '0.46';
use Eval::Closure qw( eval_closure );
use Role::Tiny;
requires '_build_description';
{
my $attrs = {
_inline_generator => {
is => 'ro',
isa => 'CodeRef',
predicate => '_has_inline_generator',
init_arg => 'inline_generator',
},
inline_environment => {
is => 'ro',
isa => 'HashRef',
lazy => 1,
init_arg => 'inline_environment',
builder => '_build_inline_environment',
},
_generated_inline_sub => {
is => 'ro',
isa => 'CodeRef',
init_arg => undef,
lazy => 1,
builder => '_build_generated_inline_sub',
},
declared_at => {
is => 'ro',
isa => 'Specio::DeclaredAt',
required => 1,
},
description => {
is => 'ro',
isa => 'Str',
init_arg => undef,
lazy => 1,
builder => '_build_description',
},
};
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _attrs {
return $attrs;
}
}
# These are here for backwards compatibility. Some other packages that I wrote
# may call the private methods.
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
sub _description { $_[0]->description }
sub _inline_environment { $_[0]->inline_environment }
## use critic
sub can_be_inlined {
my $self = shift;
return $self->_has_inline_generator;
}
sub _build_generated_inline_sub {
my $self = shift;
my $source
= 'sub { ' . $self->_inline_generator->( $self, '$_[0]' ) . '}';
return eval_closure(
source => $source,
environment => $self->inline_environment,
description => 'inlined sub for ' . $self->description,
);
}
sub _build_inline_environment {
return {};
}
1;
# ABSTRACT: A role for things which can be inlined (type constraints and coercions)
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Role::Inlinable - A role for things which can be inlined (type constraints and coercions)
=head1 VERSION
version 0.46
=head1 DESCRIPTION
This role implements a common API for inlinable things, type constraints and
coercions. It is fully documented in the relevant classes.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

282
database/perl/vendor/lib/Specio/Subs.pm vendored Normal file
View File

@@ -0,0 +1,282 @@
package Specio::Subs;
use strict;
use warnings;
our $VERSION = '0.46';
use Carp qw( croak );
use Eval::Closure qw( eval_closure );
use Module::Runtime qw( use_package_optimistically );
use Specio::Library::Perl;
use Specio::Registry qw( exportable_types_for_package );
my $counter = 0;
sub import {
shift;
my @libs = @_;
my $caller = caller();
my $ident = t('Identifier');
use_package_optimistically($_) for @libs;
for my $types ( map { exportable_types_for_package($_) } @libs ) {
for my $name ( keys %{$types} ) {
croak
qq{Cannot use '$name' type to create a check sub. It results in an invalid Perl subroutine name}
unless $ident->check( 'is_' . $name );
_export_subs( $name, $types->{$name}, $caller );
}
}
}
sub _export_subs {
my $name = shift;
my $type = shift;
my $caller = shift;
_export_validation_subs( $name, $type, $caller );
return unless $type->has_coercions;
_export_coercion_subs( $name, $type, $caller );
}
sub _export_validation_subs {
my $name = shift;
my $type = shift;
my $caller = shift;
my $is_name = 'is_' . $name;
my $assert_name = 'assert_' . $name;
if ( $type->can_be_inlined ) {
_make_sub(
$caller, $is_name,
$type->inline_check('$_[0]')
);
_make_sub(
$caller, $assert_name,
$type->inline_assert('$_[0]')
);
}
else {
_install_sub(
$caller, $is_name,
sub { $type->value_is_valid( $_[0] ) }
);
_install_sub(
$caller, $assert_name,
sub { $type->validate_or_die( $_[0] ) }
);
}
}
sub _export_coercion_subs {
my $name = shift;
my $type = shift;
my $caller = shift;
my $to_name = 'to_' . $name;
if ( $type->can_inline_coercion ) {
_make_sub(
$caller, $to_name,
$type->inline_coercion('$_[0]')
);
}
else {
_install_sub(
$caller, $to_name,
sub { $type->coerce_value( $_[0] ) }
);
}
my $force_name = 'force_' . $name;
if ( $type->can_inline_coercion_and_check ) {
_make_sub(
$caller, $force_name,
$type->inline_coercion_and_check('$_[0]')
);
}
else {
_install_sub(
$caller, $force_name,
sub {
my $val = $type->coerce_value( $_[0] );
$type->validate_or_die($val);
return $val;
}
);
}
}
sub _make_sub {
my $caller = shift;
my $sub_name = shift;
my $source = shift;
my $env = shift;
my $sub = eval_closure(
source => 'sub { ' . $source . ' }',
environment => $env,
description => $caller . '::'
. $sub_name
. ' generated by '
. __PACKAGE__,
);
_install_sub( $caller, $sub_name, $sub );
return;
}
my $sub_namer = do {
eval {
require Sub::Util;
Sub::Util->VERSION(1.40);
Sub::Util->can('set_subname');
} or eval {
require Sub::Name;
Sub::Name->can('subname');
}
or sub { return $_[1] };
};
my %Installed;
sub _install_sub {
my $caller = shift;
my $sub_name = shift;
my $sub = shift;
my $fq_name = $caller . '::' . $sub_name;
{
## no critic (TestingAndDebugging::ProhibitNoStrict)
no strict 'refs';
*{$fq_name} = $sub_namer->( $fq_name, $sub );
}
$Installed{$caller} ||= [];
push @{ $Installed{$caller} }, $sub_name;
return;
}
sub subs_installed_into {
my $package = shift;
return @{ $Installed{$package} || [] };
}
1;
# ABSTRACT: Make validation and coercion subs from Specio types
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::Subs - Make validation and coercion subs from Specio types
=head1 VERSION
version 0.46
=head1 SYNOPSIS
use Specio::Subs qw( Specio::Library::Builtins Specio::Library::Perl My::Lib );
if ( is_PackageName($var) ) { ... }
assert_Str($var);
my $person1 = to_Person($var);
my $person2 = force_Person($var);
=head1 DESCRIPTION
This module generates a set of helpful validation and coercion subroutines for
all of the types defined in one or more libraries.
To use it, simply import C<Specio::Subs> passing a list of one or more library
names. This module will load those libraries as needed.
If any of the types in any libraries have names that do not work as part of a
Perl subroutine name, this module will throw an exception.
If you have L<Sub::Util> or L<Sub::Name> installed, one of those will be used
to name the generated subroutines.
=head1 "EXPORTS"
The following subs are created in the importing package:
=head2 is_$type($value)
This subroutine returns a boolean indicating whether or not the C<$value> is
valid for the type.
=head2 assert_$type($value)
This subroutine dies if the C<$value> is not valid for the type.
=head2 to_$type($value)
This subroutine attempts to coerce C<$value> into the given type. If it cannot
be coerced it returns the original C<$value>.
This is only created if the type has coercions.
=head2 force_$type($value)
This subroutine attempts to coerce C<$value> into the given type, and dies if
it cannot do so.
This is only created if the type has coercions.
=head1 ADDITIONAL API
=for Pod::Coverage subs_installed_into
This module has a subroutine named C<subs_installed_into>. It is not exported
but it can be called by its fully qualified name. It accepts a single
argument, a package name. It returns a list of subs that it generated and
installed in the given package, if any.
This exists to make it easy to write a type library that combines other
library and generates helper subs for export all at once.
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut

View File

@@ -0,0 +1,107 @@
package Specio::TypeChecks;
use strict;
use warnings;
our $VERSION = '0.46';
use Exporter qw( import );
use Specio::Helpers qw( is_class_loaded );
use Scalar::Util qw( blessed );
our @EXPORT_OK = qw(
does_role
is_ArrayRef
is_ClassName
is_CodeRef
is_HashRef
is_Int
is_Str
isa_class
);
sub is_ArrayRef {
return ref $_[0] eq 'ARRAY';
}
sub is_CodeRef {
return ref $_[0] eq 'CODE';
}
sub is_HashRef {
return ref $_[0] eq 'HASH';
}
sub is_Str {
defined( $_[0] ) && !ref( $_[0] ) && ref( \$_[0] ) eq 'SCALAR'
|| ref( \( my $val = $_[0] ) eq 'SCALAR' );
}
sub is_Int {
( defined( $_[0] ) && !ref( $_[0] ) && ref( \$_[0] ) eq 'SCALAR'
|| ref( \( my $val = $_[0] ) eq 'SCALAR' ) )
&& $_[0] =~ /^[0-9]+$/;
}
sub is_ClassName {
is_class_loaded( $_[0] );
}
sub isa_class {
blessed( $_[0] ) && $_[0]->isa( $_[1] );
}
sub does_role {
blessed( $_[0] ) && $_[0]->can('does') && $_[0]->does( $_[1] );
}
1;
# ABSTRACT: Type checks used internally for Specio classes (it's not self-bootstrapping (yet?))
__END__
=pod
=encoding UTF-8
=head1 NAME
Specio::TypeChecks - Type checks used internally for Specio classes (it's not self-bootstrapping (yet?))
=head1 VERSION
version 0.46
=head1 DESCRIPTION
There's nothing public here.
=for Pod::Coverage .*
=head1 SUPPORT
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
=head1 SOURCE
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
=head1 AUTHOR
Dave Rolsky <autarch@urth.org>
=head1 COPYRIGHT AND LICENSE
This software is Copyright (c) 2012 - 2020 by Dave Rolsky.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
The full text of the license can be found in the
F<LICENSE> file included with this distribution.
=cut