got the basic tests in place, got the types organized how I want this to work. Still...
John Napiorkowski [Fri, 27 Mar 2009 21:44:24 +0000 (21:44 +0000)]
Changes [new file with mode: 0644]
MANIFEST.SKIP [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
lib/MooseX/Meta/TypeCoercion/Dependent.pm [new file with mode: 0644]
lib/MooseX/Meta/TypeConstraint/Dependent.pm [new file with mode: 0644]
lib/MooseX/Types/Dependent.pm
t/00-load.t [new file with mode: 0644]
t/01-basic.t

diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..5ab183e
--- /dev/null
+++ b/Changes
@@ -0,0 +1,6 @@
+Revision history for MooseX-Types-Structured
+        
+0.01    27 March 2009
+        - Completed basic requirements, documentation and tests.
+        - Today my dog, "Sunshine" is one year old.  This release is dedicated
+          to her.
diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP
new file mode 100644 (file)
index 0000000..b055a14
--- /dev/null
@@ -0,0 +1,43 @@
+
+# Avoid version control files.
+\bRCS\b
+\bCVS\b
+,v$
+\B\.svn\b
+
+# Avoid Makemaker generated and utility files.
+\bMakefile$
+\bblib
+\bMakeMaker-\d
+\bpm_to_blib$
+\bblibdirs$
+^MANIFEST\.SKIP$
+
+# for developers only :)
+^TODO$
+^VERSIONING\.SKETCH$
+
+# Avoid Module::Build generated and utility files.
+\bBuild$
+\b_build
+
+# Avoid temp and backup files.
+~$
+\.tmp$
+\.old$
+\.bak$
+\#$
+\b\.#
+
+# avoid OS X finder files
+\.DS_Store$
+
+#skip komodo project files
+\.kpf$
+
+
+# Don't ship the last dist we built :)
+\.tar\.gz$
+
+# Skip maint stuff
+^maint/
\ No newline at end of file
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..c891688
--- /dev/null
@@ -0,0 +1,29 @@
+use inc::Module::Install;
+
+## All the required meta information
+name 'MooseX-Types-Dependent';
+all_from 'lib/MooseX/Types/Dependent.pm';
+abstract 'Moose Type Constraint for creating Dependent Types Constraints';
+author 'John Napiorkowski <jjnapiork@cpan.org>';
+license 'perl';
+
+## Module dependencies
+requires 'Moose' => '0.73';
+requires 'Scalar::Util' => '1.19';
+
+## Testing dependencies
+build_requires 'Test::More' => '0.70';
+build_requires 'Test::Exception' => '0.27';
+build_requires 'Test::Pod' => '1.14';
+build_requires 'Test::Pod::Coverage' => '1.08';
+
+## Build README
+system 'pod2text lib/MooseX/Types/Dependent.pm > README'
+    if -e 'MANIFEST.SKIP';
+    
+## Instructions to Module::Install
+auto_install;
+tests_recursive;
+WriteAll;
+
+1;
diff --git a/lib/MooseX/Meta/TypeCoercion/Dependent.pm b/lib/MooseX/Meta/TypeCoercion/Dependent.pm
new file mode 100644 (file)
index 0000000..991020e
--- /dev/null
@@ -0,0 +1,36 @@
+package ## Hide from PAUSE
+ MooseX::Meta::TypeCoercion::Dependent;
+
+use Moose;
+extends 'Moose::Meta::TypeCoercion';
+
+=head1 NAME
+
+MooseX::Meta::TypeCoercion::Dependent - Coerce structured type constraints.
+
+=head1 DESCRIPTION
+
+TBD
+
+=head1 METHODS
+
+This class defines the following methods.
+
+=head1 SEE ALSO
+
+The following modules or resources may be of interest.
+
+L<Moose>, L<Moose::Meta::TypeCoercion>
+
+=head1 AUTHOR
+
+John Napiorkowski, C<< <jjnapiork@cpan.org> >>
+
+=head1 COPYRIGHT & LICENSE
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
\ No newline at end of file
diff --git a/lib/MooseX/Meta/TypeConstraint/Dependent.pm b/lib/MooseX/Meta/TypeConstraint/Dependent.pm
new file mode 100644 (file)
index 0000000..01853fe
--- /dev/null
@@ -0,0 +1,296 @@
+package ## Hide from PAUSE
+ MooseX::Meta::TypeConstraint::Dependent;
+
+use Moose;
+use Moose::Util::TypeConstraints ();
+use MooseX::Meta::TypeCoercion::Dependent;
+extends 'Moose::Meta::TypeConstraint';
+
+=head1 NAME
+
+MooseX::Meta::TypeConstraint::Dependent - Metaclass for Dependent type constraints.
+
+=head1 DESCRIPTION
+
+see L<MooseX::Types::Dependent> for examples and details of how to use dependent
+types.  This class is a subclass of L<Moose::Meta::TypeConstraint> which
+provides the gut functionality to enable dependent type constraints.
+
+=head1 ATTRIBUTES
+
+This class defines the following attributes.
+
+=head2 dependent_type_constraint
+
+The type constraint whose validity is being made dependent on a value that is a
+L</constraining_type_constraint>
+
+=cut
+
+has 'dependent_type_constraint' => (
+    is=>'ro',
+    predicate=>'has_dependent_type_constraint',
+);
+
+=head2 constraining_type_constraint
+
+This is a type constraint which defines what kind of value is allowed to be the
+constraining value of the depending type.
+
+=cut
+
+has 'constraining_type_constraint' => (
+    is=>'ro',
+    predicate=>'has_constraining_type_constraint',
+);
+
+=head2 comparision_callback
+
+This is a callback which returns a boolean value.  It get's passed the value
+L</constraining_type_constraint> validates as well as the check value.
+
+This callback is executed in addition to anything you put into a 'where' clause.
+However, the 'where' clause only get's the check value.
+
+Exercise some sanity, this should be limited to actual comparision operations,
+not as a sneaky way to mess with the constraining value.
+
+=cut
+
+has 'comparision_callback' => (
+    is=>'ro',
+    isa=>'CodeRef',
+    predicate=>'has_comparision_callback',
+);
+
+=head2 constraint_generator
+
+A subref or closure that contains the way we validate incoming values against
+a set of type constraints.
+
+=cut
+
+has 'constraint_generator' => (
+    is=>'ro',
+    isa=>'CodeRef',
+    predicate=>'has_constraint_generator',
+);
+
+=head1 METHODS
+
+This class defines the following methods.
+
+=head2 new
+
+Initialization stuff.
+
+=cut
+
+around 'new' => sub {
+    my ($new, $class, @args)  = @_;
+    my $self = $class->$new(@args);
+    $self->coercion(MooseX::Meta::TypeCoercion::Structured->new(
+        type_constraint => $self,
+    ));
+    return $self;
+};
+
+=head2 generate_constraint_for ($type_constraints)
+
+Given some type constraints, use them to generate validation rules for an ref
+of values (to be passed at check time)
+
+=cut
+
+sub generate_constraint_for {
+    my ($self, $dependent, $callback, $constraining) = @_;
+    return sub {
+        my (@args) = @_;
+        my $constraint_generator = $self->constraint_generator;
+        return $constraint_generator->($dependent, $callback, $constraining, @args);
+    };
+}
+
+=head2 parameterize (@type_constraints)
+
+Given a ref of type constraints, create a structured type.
+
+=cut
+
+sub parameterize {
+    
+    my ($self, $dependent, $callback, $constraining) = @_;
+    my $class = ref $self;
+    my $name = $self->_generate_subtype_name($dependent, $constraining);
+    my $constraint_generator = $self->__infer_constraint_generator;
+
+    return $class->new(
+        name => $name,
+        parent => $self,
+        dependent_type_constraint=>$dependent,
+        comparision_callback=>$callback,
+        constraint_generator => $constraint_generator,
+    );
+}
+
+=head2 _generate_subtype_name
+
+Returns a name for the dependent type that should be unique
+
+=cut
+
+sub _generate_subtype_name {
+    my ($self, $dependent, $constraining) = @_;
+    return sprintf(
+        "%s_depends_on_%s",
+        $dependent, $constraining
+    );
+}
+
+=head2 __infer_constraint_generator
+
+This returns a CODEREF which generates a suitable constraint generator.  Not
+user servicable, you'll never call this directly.
+
+    TBD, this is definitely going to need some work.
+
+=cut
+
+sub __infer_constraint_generator {
+    my ($self) = @_;
+    if($self->has_constraint_generator) {
+        return $self->constraint_generator;
+    } else {
+        return sub {
+            ## I'm not sure about this stuff but everything seems to work
+            my $tc = shift @_;
+            my $merged_tc = [
+                @$tc,
+                $self->dependent_type_constraint,
+                $self->comparision_callback,
+                $self->constraining_type_constraint,
+            ];
+            
+            $self->constraint->($merged_tc, @_);            
+        };
+    }    
+}
+
+=head2 compile_type_constraint
+
+hook into compile_type_constraint so we can set the correct validation rules.
+
+=cut
+
+around 'compile_type_constraint' => sub {
+    my ($compile_type_constraint, $self, @args) = @_;
+    
+    if($self->has_type_constraints) {
+        my $type_constraints = $self->type_constraints;
+        my $constraint = $self->generate_constraint_for($type_constraints);
+        $self->_set_constraint($constraint);        
+    }
+
+    return $self->$compile_type_constraint(@args);
+};
+
+=head2 create_child_type
+
+modifier to make sure we get the constraint_generator
+
+=cut
+
+around 'create_child_type' => sub {
+    my ($create_child_type, $self, %opts) = @_;
+    return $self->$create_child_type(
+        %opts,
+        constraint_generator => $self->__infer_constraint_generator,
+    );
+};
+
+=head2 is_a_type_of
+
+=head2 is_subtype_of
+
+=head2 equals
+
+Override the base class behavior.
+
+    TBD
+
+sub equals {
+    my ( $self, $type_or_name ) = @_;
+    my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name);
+
+    return unless $other->isa(__PACKAGE__);
+    
+    return (
+        $self->type_constraints_equals($other)
+            and
+        $self->parent->equals( $other->parent )
+    );
+}
+
+=head2 type_constraints_equals
+
+Checks to see if the internal type contraints are equal.
+
+    TBD
+
+sub type_constraints_equals {
+    my ($self, $other) = @_;
+    my @self_type_constraints = @{$self->type_constraints||[]};
+    my @other_type_constraints = @{$other->type_constraints||[]};
+    
+    ## Incoming ay be either arrayref or hashref, need top compare both
+    while(@self_type_constraints) {
+        my $self_type_constraint = shift @self_type_constraints;
+        my $other_type_constraint = shift @other_type_constraints
+         || return; ## $other needs the same number of children.
+        
+        if( ref $self_type_constraint) {
+            $self_type_constraint->equals($other_type_constraint)
+             || return; ## type constraints obviously need top be equal
+        } else {
+            $self_type_constraint eq $other_type_constraint
+             || return; ## strings should be equal
+        }
+
+    }
+    
+    return 1; ##If we get this far, everything is good.
+}
+
+=head2 get_message
+
+Give you a better peek into what's causing the error.  For now we stringify the
+incoming deep value with L<Devel::PartialDump> and pass that on to either your
+custom error message or the default one.  In the future we'll try to provide a
+more complete stack trace of the actual offending elements
+
+    TBD
+
+around 'get_message' => sub {
+    my ($get_message, $self, $value) = @_;
+    my $new_value = Devel::PartialDump::dump($value);
+    return $self->$get_message($new_value);
+};
+
+=head1 SEE ALSO
+
+The following modules or resources may be of interest.
+
+L<Moose>, L<Moose::Meta::TypeConstraint>
+
+=head1 AUTHOR
+
+John Napiorkowski, C<< <jjnapiork@cpan.org> >>
+
+=head1 COPYRIGHT & LICENSE
+
+This program is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
\ No newline at end of file
index ea27fba..b4f10ef 100644 (file)
 package MooseX::Types::Dependent;
 
-use strict;
-use warnings;
-
-
-#use Carp::Clan qw( ^MooseX::Types );
-use Moose::Util::TypeConstraints ();
-use Scalar::Util qw(blessed);
-
-use overload(
-    '""' => sub {
-               my $self = shift @_;
-               if(blessed $self) {
-                       return $self->__internal_type_constraint->name;                 
-               } else {
-                       return "$self";
-               }
-    },
-    fallback => 1,
-);
+use 5.008;
+
+use Moose::Util::TypeConstraints;
+use MooseX::Meta::TypeConstraint::Dependent;
+use MooseX::Types -declare => [qw(Depending)];
+
+our $VERSION = '0.01';
+our $AUTHORITY = 'cpan:JJNAPIORK';
 
 =head1 NAME
 
-MooseX::Types::Dependent - Type Constraints that are dependent on others
+MooseX::Types::Dependent - L<MooseX::Types> constraints that depend on values.
 
 =head1 SYNOPSIS
 
-    use MooseX::Types::Dependent;
-    
-    ## Assuming the type constraint 'Set' isa Set::Scalar
-
-    subtype UniqueInt,
-      as Dependent[Int,Set],
-      where {
-        ## ok Set->check($set), 'Good $set';
-        ## ok Int->check($val), 'Already an Int'
-        my ($set, $val) = @_;
-        ## If the $set already has $val, then it's not unique
-        return $set->has($val) ? 0:1
-      };
-      
-    my $set = Set::Scalar->new(1..10);
-    
-    ok UniqueInt->check([1, $set]);  ## Fails, 1 is already in $set;
-    ok UniqueInt->check(['a', $set]); ## Fails, 'a' is not an Int;
-    ok UniqueInt->check([1, $obj]); ## Fails, $obj is not a Set;
-    ok UniqueInt->check([20, $set]); ## PASSES
+        TDB:  Syntax to be determined.  Canonical is:
+        
+        subtype UniqueInt,
+          as Depending[
+            Int,
+            sub {
+              shift->not_exists(shift);
+            },
+            Set,
+          ];
+          
+        possible sugar options
+        
+        Depending 
+        as Depending sub :Set {} Int;
+        depending(Set $set) { $set->exists($Int) } Int;
+        
+        May have some ready to go, such as
+        as isGreaterThan[
+                Int,
+                Int,
+        ];
+        
+        as isMemberOf[
+                Int
+                Set,
+        ]
+        
+        ## using object for comparison
+        
+        as Dependent[Int, CompareCmd, Int];
+
+Please see the test cases for more examples.
+
+=head1 DEFINITIONS
+
+The following is a list of terms used in this documentation.
+
+=head2 Dependent Type Constraint
+
+=head2 Check Value
+
+=head2 Constraining Type Constraint
+
+=head2 Constraining Value
 
 =head1 DESCRIPTION
 
-This is a decorator object that contains an underlying type constraint.  We use
-this to control access to the type constraint and to add some features.
-
-=head1 METHODS
+A dependent type is a type constraint whose validity is dependent on a second
+value.  You defined the dependent type constraint with a primary type constraint
+(such as 'Int') a 'constraining' value type constraint (such as a Set object)
+and a coderef which will compare the incoming value to be checked with a value
+that conforms to the constraining type constraint.  Typically there should be a
+comparision operator between the check value and the constraining value
 
-This class defines the following methods.
+=head2 Subtyping a Dependent type constraints
 
-=head2 new
+        TDB: Need discussion and examples.
 
-Old school instantiation
+=head2 Coercions
 
-=cut
+        TBD: Need discussion and example of coercions working for both the
+        constrainted and dependent type constraint.
 
-sub new {
-    my $class = shift @_;
-    my $attributes = {};
-    if(my $
-    if(my $arg = shift @_) {
-        if(blessed $arg && $arg->isa('Moose::Meta::TypeConstraint')) {
-            return bless {'__type_constraint'=>$arg}, $class;
-        } elsif(
-            blessed $arg &&
-            $arg->isa('MooseX::Types::UndefinedType') 
-          ) {
-            ## stub in case we'll need to handle these types differently
-            return bless {'__type_constraint'=>$arg}, $class;
-        } elsif(blessed $arg) {
-            croak "Argument must be ->isa('Moose::Meta::TypeConstraint') or ->isa('MooseX::Types::UndefinedType'), not ". blessed $arg;
-        } else {
-            croak "Argument cannot be '$arg'";
-        }
-    } else {
-        croak "This method [new] requires a single argument.";        
-    }
-}
-
-=head2 __internal_type_constraint ($type_constraint)
-
-Set/Get the type_constraint we are making dependent.
+=head2 Recursion
 
-=cut
+Newer versions of L<MooseX::Types> support recursive type constraints.  That is
+you can include a type constraint as a contained type constraint of itself.
+Recursion is support in both the dependent and constraining type constraint. For
+example:
 
-sub __internal_type_constraint {
-    my $self = shift @_;    
-    if(blessed $self) {
-        if(defined(my $tc = shift @_)) {
-            $self->{__type_constraint} = $tc;
-        }
-        return $self->{__type_constraint};        
-    } else {
-        croak 'cannot call __internal_type_constraint as a class method';
-    }
-}
+        TBD
 
-=head2 isa
+=head1 TYPE CONSTRAINTS
 
-handle $self->isa since AUTOLOAD can't.
+This type library defines the following constraints.
 
-=cut
+=head2 Depending[$dependent_tc, $codref, $constraining_tc]
 
-sub isa {
-    my ($self, $target) = @_;  
-    if(defined $target) {
-       if(blessed $self) {
-               return $self->__internal_type_constraint->isa($target);
-       } else {
-               return;
-       }
-    } else {
-        return;
-    }
-}
+Create a subtype of $dependent_tc that is constrainted by a value that is a
+valid $constraining_tc using $coderef.  For example;
 
-=head2 can
+    subtype GreaterThanInt,
+      as Depending[
+        Int,
+        sub {
+          my($constraining_value, $check_value) = @_;
+          return $constraining_value > $check_value ? 1:0;
+        },
+        Int,
+      ];
 
-handle $self->can since AUTOLOAD can't.
+This would create a type constraint that takes an integer and checks it against
+a second integer, requiring that the check value is greater.  For example:
 
-=cut
+        GreaterThanInt->check(5,10);  ## Fails, 5 is less than 10
+        GreaterThanInt->check('a',10); ## Fails, 'a' is not an Int.
+        GreaterThanInt->check(5,'b'); ## Fails, 'b' is not an Int either.
+        GreaterThanInt->check(10,5); ## Success, 10 is greater than 5.
 
-sub can {
-    my ($self, $target) = @_;
-    if(defined $target) {
-       if(blessed $self) {
-               return $self->__internal_type_constraint->can($target);
-       } else {
-               return;
-       }
-    } else {
-        return;
-    }
-}
+=head1 EXAMPLES
 
-=head2 meta
+Here are some additional example usage for structured types.  All examples can
+be found also in the 't/examples.t' test.  Your contributions are also welcomed.
 
-have meta examine the underlying type constraints
+        TBD
 
 =cut
 
-sub meta {
-       my $self = shift @_;
-       if(blessed $self) {
-               return $self->__internal_type_constraint->meta;
-       } 
-}
+Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
+       MooseX::Meta::TypeConstraint::Dependent->new(
+               name => "MooseX::Types::Dependent::Depending" ,
+               parent => find_type_constraint('ArrayRef'),
+               constraint_generator=> sub { 
+                my ($callback, $constraining_value, $check_value) = @_;
+                return $callback->($constraining_value, $check_value) ? 1:0;
+               },
+       )
+);
+       
+=head1 SEE ALSO
 
+The following modules or resources may be of interest.
 
-=head2 DESTROY
+L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
+L<MooseX::Meta::TypeConstraint::Dependent>
 
-We might need it later
+=head1 TODO
 
-=cut
+Here's a list of stuff I would be happy to get volunteers helping with:
 
-sub DESTROY {
-    return;
-}
+=over 4
 
-=head2 AUTOLOAD
+=item Examples
 
-Delegate to the decorator targe
+Examples of useful code with both POD and ideally a test case to show it
+working.
 
-=cut
+=back
+
+=head1 AUTHOR
 
-sub AUTOLOAD {
-    
-    my ($self, @args) = @_;
-    my ($method) = (our $AUTOLOAD =~ /([^:]+)$/);
-    
-    ## We delegate with this method in an attempt to support a value of
-    ## __type_constraint which is also AUTOLOADing, in particular the class
-    ## MooseX::Types::UndefinedType which AUTOLOADs during autovivication.
-    
-    my $return;
-    
-    eval {
-        $return = $self->__internal_type_constraint->$method(@args);
-    }; if($@) {
-        croak $@;
-    } else {
-        return $return;
-    }
-}
-
-=head1 AUTHOR AND COPYRIGHT
-
-John Napiorkowski (jnapiorkowski) <jjnapiork@cpan.org>
-
-=head1 LICENSE
+John Napiorkowski, C<< <jjnapiork@cpan.org> >>
+
+=head1 COPYRIGHT & LICENSE
 
 This program is free software; you can redistribute it and/or modify
-it under the same terms as perl itself.
+it under the same terms as Perl itself.
 
 =cut
-
+       
 1;
-
diff --git a/t/00-load.t b/t/00-load.t
new file mode 100644 (file)
index 0000000..062b92b
--- /dev/null
@@ -0,0 +1,12 @@
+
+use Test::More tests=>3; {
+    
+    use strict;
+    use warnings;
+    
+    ## List all the modules we want to make sure can at least compile
+    use_ok 'MooseX::Types::Dependent';
+    use_ok 'MooseX::Meta::TypeConstraint::Dependent';
+    use_ok 'MooseX::Meta::TypeCoercion::Dependent';
+}
+
index 3327e2a..136928b 100644 (file)
@@ -1,35 +1,38 @@
-use Test::More tests=>5;
-use MooseX::Types::Structured qw(Tuple slurpy);
-use MooseX::Types qw(Str Object);
 
-use_ok 'MooseX::Meta::TypeConstraint::Structured';
-use_ok 'Moose::Util::TypeConstraints';
-
-ok my $int = find_type_constraint('Int') => 'Got Int';
-ok my $str = find_type_constraint('Str') => 'Got Str';
-ok my $obj = find_type_constraint('Object') => 'Got Object';
-ok my $arrayref = find_type_constraint('ArrayRef') => 'Got ArrayRef';
-
-my $a = [1,2,3,4];
-
-
-package Dependent;
-
-use overload(
-       '&{}' => sub {
-               warn 'sdfsdfsdfsdfsdf';
-               return sub {};
-       },
-);
-
-sub new {
-       my $class = shift @_;
-       return bless {a=>1}, $class;
+use Test::More tests=>8; {
+       
+       use strict;
+       use warnings;
+       
+       use_ok 'MooseX::Meta::TypeConstraint::Dependent';
+       use_ok 'Moose::Util::TypeConstraints';
+
+       ## A sample dependent type constraint the requires two ints and see which
+       ## is the greater.
+       
+       ok my $int = find_type_constraint('Int') => 'Got Int';
+       
+       my $dep_tc = MooseX::Meta::TypeConstraint::Dependent->new(
+               name => "MooseX::Types::Dependent::Depending" ,
+               parent => find_type_constraint('ArrayRef'),
+               dependent_type_constraint=>$int,
+               comparision_callback=>sub {
+                       my ($constraining_value, $check_value) = @_;
+                       return $constraining_value > $check_value ? 0:1;
+               },
+               constraint_generator =>$int,
+               constraint_generator=> sub { 
+                       my ($callback, $constraining_value, $check_value) = @_;
+                       return $callback->($constraining_value, $check_value) ? 1:0;
+               },
+       );
+       
+       ## Does this work at all?
+
+       isa_ok $dep_tc, 'MooseX::Meta::TypeConstraint::Dependent';
+
+       ok !$dep_tc->check([5,10]), "Fails, 5 is less than 10";
+       ok !$dep_tc->check(['a',10]), "Fails, 'a' is not an Int.";
+       ok !$dep_tc->check([5,'b']), "Fails, 'b' is not an Int either.";
+       ok $dep_tc->check([10,5]), "Success, 10 is greater than 5.";
 }
-
-1;
-
-my $dependent = Dependent->new($int);
-
-$dependent->();
-