package Scalar::Does;

use 5.008;
use strict;
use warnings;
use if $] < 5.010, 'UNIVERSAL::DOES';

METADATA:
{
	$Scalar::Does::AUTHORITY = 'cpan:TOBYINK';
	$Scalar::Does::VERSION   = '0.202';
}

UTILITY_CLASS:
{
	package Scalar::Does::RoleChecker;
	$Scalar::Does::RoleChecker::AUTHORITY = 'cpan:TOBYINK';
	$Scalar::Does::RoleChecker::VERSION   = '0.202';
	use base "Type::Tiny";
	sub new {
		my $class = shift;
		my ($name, $coderef);
		for my $p (@_)
		{
			if (Scalar::Does::does($p, 'CODE'))  { $coderef = $p }
			if (Scalar::Does::does($p, 'HASH'))  { $coderef = $p->{where} }
			if (Scalar::Does::does($p, 'Regexp')){ $coderef = sub { $_[0] =~ $p } }
			if (not ref $p)                      { $name    = $p }
		}
		Carp::confess("Cannot make role without checker coderef or regexp") unless $coderef;
		$class->SUPER::new(display_name => $name, constraint => $coderef);
	}
	sub code { shift->constraint };
}

PRIVATE_STUFF:
{
	sub _lu {
		require lexical::underscore;
		goto \&lexical::underscore;
	}
	
	use constant MISSING_ROLE_MESSAGE => (
		"Please supply a '-role' argument when exporting custom functions, died"
	);
	
	use Carp 0 qw( confess );
	use Types::Standard 0.004 qw( -types );
}

use namespace::clean 0.19;

DEFINE_CONSTANTS:
{
	our %_CONSTANTS = (
		BOOLEAN    => q[bool],
		STRING     => q[""],
		NUMBER     => q[0+],
		REGEXP     => q[qr],
		SMARTMATCH => q[~~],
		map {; $_ => $_ } qw(
			SCALAR ARRAY HASH CODE REF GLOB
			LVALUE FORMAT IO VSTRING
		)
	);
	require constant;
	constant->import(\%_CONSTANTS);
}

EXPORTER:
{
	use base "Exporter::Tiny";
	
	our %_CONSTANTS;
	our @EXPORT    = ( "does" );
	our @EXPORT_OK = (
		qw( does overloads blessed reftype looks_like_number make_role where custom ),
		keys(%_CONSTANTS),
	);
	our %EXPORT_TAGS = (
		constants      => [ "does", keys(%_CONSTANTS) ],
		only_constants => [ keys(%_CONSTANTS) ],
		make           => [ qw( make_role where ) ],
	);
	
	sub _exporter_validate_opts
	{
		require B;
		my $class = shift;
		$_[0]{exporter} ||= sub {
			my $into = $_[0]{into};
			my ($name, $sym) = @{ $_[1] };
			for (grep ref, $into->can($name))
			{
				B::svref_2object($_)->STASH->NAME eq $into
					and _croak("Refusing to overwrite local sub '$name' with export from $class");
			}
			"namespace::clean"->import(-cleanee => $_[0]{into}, $name);
			no strict qw(refs);
			no warnings qw(redefine prototype);
			*{"$into\::$name"} = $sym;
		}
	}
}

ROLES:
{
	no warnings;
	
	my $io = "Type::Tiny"->new(
		display_name => "IO",
		constraint   => sub { require IO::Detect; IO::Detect::is_filehandle($_) },
	);
	
	our %_ROLES = (
		SCALAR   => ( ScalarRef() | Ref->parameterize('SCALAR')  | Overload->parameterize('${}') ),
		ARRAY    => ( ArrayRef()  | Ref->parameterize('ARRAY')   | Overload->parameterize('@{}') ),
		HASH     => ( HashRef()   | Ref->parameterize('HASH')    | Overload->parameterize('%{}') ),
		CODE     => ( CodeRef()   | Ref->parameterize('CODE')    | Overload->parameterize('&{}') ),
		REF      => ( Ref->parameterize('REF') ),
		GLOB     => ( GlobRef()   | Ref->parameterize('GLOB')    | Overload->parameterize('*{}') ),
		LVALUE   => ( Ref->parameterize('LVALUE') ),
		FORMAT   => ( Ref->parameterize('FORMAT') ),
		IO       => $io,
		VSTRING  => ( Ref->parameterize('VSTRING') ),
		Regexp   => ( RegexpRef() | Ref->parameterize('Regexp')  | Overload->parameterize('qr') ),
		bool     => ( Value() | Overload->complementary_type | Overload->parameterize('bool') ),
		q[""]    => ( Value() | Overload->complementary_type | Overload->parameterize('""') ),
		q[0+]    => ( Value() | Overload->complementary_type | Overload->parameterize('0+') ),
		q[<>]    => ( Overload->parameterize('<>') | $io ),
		q[~~]    => ( Overload->parameterize('~~') | Object->complementary_type ),
		q[${}]   => 'SCALAR',
		q[@{}]   => 'ARRAY',
		q[%{}]   => 'HASH',
		q[&{}]   => 'CODE',
		q[*{}]   => 'GLOB',
		q[qr]    => 'Regexp',
	);
	
	while (my ($k, $v) = each %_ROLES) { $_ROLES{$k} = $_ROLES{$v} unless ref $v }
}

PUBLIC_FUNCTIONS:
{
	use Scalar::Util 1.24 qw( blessed reftype looks_like_number );
	
	sub overloads ($;$)
	{
		unshift @_, ${+_lu} if @_ == 1;
		return unless blessed $_[0];
		goto \&overload::Method;
	}
	
	sub does ($;$)
	{
		unshift @_, ${+_lu} if @_ == 1;
		my ($thing, $role) = @_;
		
		no warnings;
		our %_ROLES;
		if (my $test = $_ROLES{$role})
		{
			return !! $test->check($thing);
		}
		
		if (blessed $role and $role->can('check'))
		{
			return !! $role->check($thing);
		}
		
		if (blessed $thing && $thing->can('DOES'))
		{
			return !! 1 if $thing->DOES($role);
		}
		elsif (UNIVERSAL::can($thing, 'can') && $thing->can('DOES'))
		{
			my $class = $thing;
			return '0E0' if $class->DOES($role);
		}
		
		return;
	}
	
	sub _generate_custom
	{
		my ($class, $name, $arg) = @_;
		my $role = $arg->{ -role } or confess MISSING_ROLE_MESSAGE;
		
		return sub (;$) {
			push @_, $role;
			goto \&does;
		}
	}
	
	sub make_role
	{
		return "Scalar::Does::RoleChecker"->new(@_);
	}
	
	sub where (&)
	{
		return +{ where => $_[0] };
	}
}

"it does"
__END__

=pod

=encoding utf8

=for stopwords vstring qr numifies

=head1 NAME

Scalar::Does - like ref() but useful

=head1 SYNOPSIS

  use Scalar::Does qw( -constants );
  
  my $object = bless {}, 'Some::Class';
  
  does($object, 'Some::Class');   # true
  does($object, '%{}');           # true
  does($object, HASH);            # true
  does($object, ARRAY);           # false

=head1 DESCRIPTION

It has long been noted that Perl would benefit from a C<< does() >> built-in.
A check that C<< ref($thing) eq 'ARRAY' >> doesn't allow you to accept an
object that uses overloading to provide an array-like interface.

=head2 Functions

=over

=item C<< does($scalar, $role) >>

Checks if a scalar is capable of performing the given role. The following
(case-sensitive) roles are predefined:

=over

=item * B<SCALAR> or B<< ${} >>

Checks if the scalar can be used as a scalar reference.

Note: this role does not check whether a scalar is a scalar (which is
obviously true) but whether it is a reference to another scalar.

=item * B<ARRAY> or B<< @{} >>

Checks if the scalar can be used as an array reference.

=item * B<HASH> or B<< %{} >>

Checks if the scalar can be used as a hash reference.

=item * B<CODE> or B<< &{} >>

Checks if the scalar can be used as a code reference.

=item * B<GLOB> or B<< *{} >>

Checks if the scalar can be used as a glob reference.

=item * B<REF>

Checks if the scalar can be used as a ref reference (i.e. a reference to
another reference).

=item * B<LVALUE>

Checks if the scalar is a reference to a special lvalue (e.g. the result
of C<< substr >> or C<< splice >>).

=item * B<IO> or B<< <> >>

Uses L<IO::Detect> to check if the scalar is a filehandle or file-handle-like
object.

(The C<< <> >> check is slightly looser, allowing objects which overload
C<< <> >>, though overloading C<< <> >> well can be a little tricky.)

=item * B<VSTRING>

Checks if the scalar is a vstring reference.

=item * B<FORMAT>

Checks if the scalar is a format reference.

=item * B<Regexp> or B<< qr >>

Checks if the scalar can be used as a quoted regular expression.

=item * B<bool>

Checks if the scalar can be used as a boolean. (It's pretty rare for this
to not be true.)

=item * B<< "" >>

Checks if the scalar can be used as a string. (It's pretty rare for this
to not be true.)

=item * B<< 0+ >>

Checks if the scalar can be used as a number. (It's pretty rare for this
to not be true.)

Note that this is far looser than C<looks_like_number> from L<Scalar::Util>.
For example, an unblessed arrayref can be used as a number (it numifies to
its reference address); the string "Hello World" can be used as a number (it
numifies to 0).

=item * B<< ~~ >>

Checks if the scalar can be used on the right hand side of a smart match.

=back

If the given I<role> is blessed, and provides a C<check> method, then
C<< does >> delegates to that.

Otherwise, if the scalar being tested is blessed, then
C<< $scalar->DOES($role) >> is called, and C<does> returns true if
the method call returned true.

If the scalar being tested looks like a Perl class name, then 
C<< $scalar->DOES($role) >> is also called, and the string "0E0" is
returned for success, which evaluates to 0 in a numeric context but
true in a boolean context.

=item C<< does($role) >>

Called with a single argument, tests C<< $_ >>. Yes, this works with lexical
C<< $_ >>.

  given ($object) {
     when(does ARRAY)  { ... }
     when(does HASH)   { ... }
  }

Note: in Scalar::Does 0.007 and below the single-argument form of C<does>
returned a curried coderef. This was changed in Scalar::Does 0.008.

=item C<< overloads($scalar, $role) >>

A function C<overloads> (which just checks overloading) is also available.

=item C<< overloads($role) >>

Called with a single argument, tests C<< $_ >>. Yes, this works with lexical
C<< $_ >>.

Note: in Scalar::Does 0.007 and below the single-argument form of C<overloads>
returned a curried coderef. This was changed in Scalar::Does 0.008.

=item C<< blessed($scalar) >>, C<< reftype($scalar) >>, C<< looks_like_number($scalar) >>

For convenience, this module can also re-export these functions from
L<Scalar::Util>. C<looks_like_number> is generally more useful than
C<< does($scalar, q[0+]) >>.

=item C<< make_role $name, where { BLOCK } >>

Returns an anonymous role object which can be used as a parameter to
C<does>. The block is arbitrary code which should check whether $_[0]
does the role.

=item C<< where { BLOCK } >>

Syntactic sugar for C<make_role>. Compatible with the C<where> function
from L<Moose::Util::TypeConstraints>, so don't worry about conflicts.

=back

=head2 Constants

The following constants may be exported for convenience:

=over

=item C<SCALAR>

=item C<ARRAY>

=item C<HASH>

=item C<CODE>

=item C<GLOB>

=item C<REF>

=item C<LVALUE>

=item C<IO>

=item C<VSTRING>

=item C<FORMAT>

=item C<REGEXP>

=item C<BOOLEAN>

=item C<STRING>

=item C<NUMBER>

=item C<SMARTMATCH>

=back

=head2 Export

By default, only C<does> is exported. This module uses L<Exporter::Tiny>, so
functions can be renamed:

  use Scalar::Does does => { -as => 'performs_role' };

Scalar::Does also plays some tricks with L<namespace::clean> to ensure that
any functions it exports to your namespace are cleaned up when you're finished
with them. This ensures that if you're writing object-oriented code C<does>
and C<overloads> will not be left hanging around as methods of your classes.
L<Moose::Object> provides a C<does> method, and you should be able to use
Scalar::Does without interfering with that.

You can import the constants (plus C<does>) using:

  use Scalar::Does -constants;

The C<make_role> and C<where> functions can be exported like this:

  use Scalar::Does -make;

Or list specific functions/constants that you wish to import:

  use Scalar::Does qw( does ARRAY HASH STRING NUMBER );

=head2 Custom Role Checks

  use Scalar::Does
    custom => { -as => 'does_array', -role => 'ARRAY' },
    custom => { -as => 'does_hash',  -role => 'HASH'  };
  
  does_array($thing);
  does_hash($thing);

=head1 BUGS

Please report any bugs to
L<http://rt.cpan.org/Dist/Display.html?Queue=Scalar-Does>.

=head1 SEE ALSO

L<Scalar::Util>.

L<http://perldoc.perl.org/5.10.0/perltodo.html#A-does()-built-in>.

=head2 Relationship to Moose roles

Scalar::Does is not dependent on Moose, and its role-checking is not specific
to Moose's idea of roles, but it does work well with Moose roles.

Moose::Object overrides C<DOES>, so Moose objects and Moose roles should
"just work" with Scalar::Does.

  {
    package Transport;
    use Moose::Role;
  }
  
  {
    package Train;
    use Moose;
    with qw(Transport);
  }
  
  my $thomas = Train->new;
  does($thomas, 'Train');          # true
  does($thomas, 'Transport');      # true
  does($thomas, Transport->meta);  # not yet supported!

L<Mouse::Object> should be compatible enough to work as well.

See also:
L<Moose::Role>,
L<Moose::Object>,
L<UNIVERSAL>.

=head2 Relationship to Moose type constraints

L<Moose::Meta::TypeConstraint> objects, plus the constants exported by
L<MooseX::Types> libraries all provide a C<check> method, so again, should
"just work" with Scalar::Does. Type constraint strings are not supported
however.

  use Moose::Util::TypeConstraints qw(find_type_constraint);
  use MooseX::Types qw(Int);
  use Scalar::Does qw(does);
  
  my $int = find_type_constraint("Int");
  
  does( "123", $int );     # true
  does( "123", Int );      # true
  does( "123", "Int" );    # false

L<Mouse::Meta::TypeConstraint>s and L<MouseX::Types> should be compatible
enough to work as well.

See also:
L<Moose::Meta::TypeConstraint>,
L<Moose::Util::TypeConstraints>,
L<MooseX::Types>,
L<Scalar::Does::MooseTypes>.

=head2 Relationship to Type::Tiny type constraints

Types built with L<Type::Tiny> and L<Type::Library> can be used exactly as
Moose type constraint objects above.

  use Types::Standard qw(Int);
  use Scalar::Does qw(does);
  
  does(123, Int);   # true

In fact, L<Type::Tiny> and related libraries are used extensively in the
internals of Scalar::Does 0.200+.

See also:
L<Type::Tiny>,
L<Types::Standard>.

=head2 Relationship to Role::Tiny and Moo roles

Roles using Role::Tiny 1.002000 and above provide a C<DOES> method, so
should work with Scalar::Does just like Moose roles. Prior to that release,
Role::Tiny did not provide C<DOES>.

Moo's role system is based on Role::Tiny.

See also:
L<Role::Tiny>,
L<Moo::Role>.

=head1 AUTHOR

Toby Inkster E<lt>tobyink@cpan.orgE<gt>.

=head1 COPYRIGHT AND LICENCE

This software is copyright (c) 2012-2014 by Toby Inkster.

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

=head1 DISCLAIMER OF WARRANTIES

THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.

