our
$VERSION
=
'0.50'
;
our
@EXPORT
=
qw(
clone
_ooify
)
;
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
);
}
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
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
strict
'refs'
;
*{
$class
.
'::'
.
$attr
->{predicate} } = _eval_or_die(
$predicate
,
$class
.
'->'
.
$attr
->{predicate},
);
}
}
my
@RolesWithBUILD
=
qw( Specio::Constraint::Role::Interface )
;
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
strict
'refs'
;
push
@build_subs
,
$parent
.
'::BUILD'
if
defined
&{
$parent
.
'::BUILD'
};
}
}
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
strict
'refs'
;
*{
$class
.
'::new'
} = _eval_or_die(
$constructor
,
$class
.
'->new'
,
);
}
}
sub
_eval_or_die {
local
$@ =
undef
;
local
$SIG
{__DIE__};
my
$sub
=
eval
<<"EOF";
#line 1 "$_[1]"
$_[0];
EOF
my
$e
= $@;
die
$e
if
$e
;
return
$sub
;
}
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
);
}
my
%BuiltinTypes
=
map
{
$_
=> 1 }
qw(
SCALAR
ARRAY
HASH
CODE
REF
GLOB
LVALUE
FORMAT
IO
VSTRING
Regexp
)
;
sub
clone {
my
$self
=
shift
;
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
;
}
my
$ref
=
ref
$value
;
$new
->{
$key
}
= !
$ref
?
$value
:
$ref
eq
'CODE'
?
$value
:
$BuiltinTypes
{
$ref
} ? Clone::clone(
$value
)
:
$value
->clone;
}
bless
$new
, (
ref
$self
);
for
my
$key
(
keys
%special
) {
my
$method
=
$special
{
$key
};
$new
->{
$key
} =
$new
->
$method
;
}
return
$new
;
}
1;