Merge branch 'master' into attribute_helpers
Hans Dieter Pearcey [Fri, 26 Jun 2009 20:38:55 +0000 (16:38 -0400)]
40 files changed:
Changes
lib/Moose.pm
lib/Moose/AttributeHelpers.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/Array.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/Bag.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/Bool.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/Counter.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/Hash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/ImmutableHash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/List.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/MethodProvider/String.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Base.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Bool.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection/Array.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection/Bag.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection/Hash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection/ImmutableHash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Collection/List.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Counter.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/Number.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Trait/String.pm [new file with mode: 0644]
lib/Moose/Manual/Delta.pod
lib/Moose/Meta/Attribute.pm
lib/Moose/Meta/Method/Delegation.pm
t/020_attributes/010_attribute_delegation.t
t/070_attribute_helpers/000_load.t [new file with mode: 0644]
t/070_attribute_helpers/010_array_from_role.t [new file with mode: 0644]
t/070_attribute_helpers/011_counter_with_defaults.t [new file with mode: 0644]
t/070_attribute_helpers/020_remove_attribute.t [new file with mode: 0644]
t/070_attribute_helpers/100_collection_with_roles.t [new file with mode: 0644]
t/070_attribute_helpers/201_trait_counter.t [new file with mode: 0644]
t/070_attribute_helpers/202_trait_array.t [new file with mode: 0644]
t/070_attribute_helpers/203_trait_hash.t [new file with mode: 0644]
t/070_attribute_helpers/204_trait_number.t [new file with mode: 0644]
t/070_attribute_helpers/205_trait_list.t [new file with mode: 0644]
t/070_attribute_helpers/206_trait_bag.t [new file with mode: 0644]
t/070_attribute_helpers/207_trait_string.t [new file with mode: 0644]
t/070_attribute_helpers/208_trait_bool.t [new file with mode: 0644]
xt/author/pod_spell.t

diff --git a/Changes b/Changes
index 32f804a..835cee0 100644 (file)
--- a/Changes
+++ b/Changes
@@ -6,6 +6,11 @@ for, noteworthy changes.
       - The warning for 'no associated methods' is now split out into the
         check_associated_methods method, so that extensions can safely call
         'after install_accessors => ...'. (hdp)
+      - Move currying syntax for delegation in from AttributeHelpers. (hdp)
+
+    * Moose::AttributeHelpers
+      - Moved in from MooseX with API tweaks. See Moose::Manual::Delta for
+        details. (hdp, jhannah, rbuels)
 
 0.84 Fri, Jun 26, 2009
     * Moose::Role
index 9aa8ce7..b4ddfcc 100644 (file)
@@ -538,6 +538,18 @@ In this example, the Tree package gets C<parent_node> and C<siblings> methods,
 which delegate to the C<node> and C<children> methods (respectively) of the Tree
 instance stored in the C<parent> slot.
 
+You may also use an array reference to curry arguments to the original method.
+
+  has 'thing' => (
+      ...
+      handles => { set_foo => [ set => [ 'foo' ] ] },
+  );
+
+  # $self->set_foo(...) calls $self->thing->set('foo', ...)
+
+The first element of the array reference is the original method name, and the
+second is an array reference of curried arguments.
+
 =item C<REGEXP>
 
 The regexp option works very similar to the ARRAY option, except that it builds
diff --git a/lib/Moose/AttributeHelpers.pm b/lib/Moose/AttributeHelpers.pm
new file mode 100644 (file)
index 0000000..8b05ce7
--- /dev/null
@@ -0,0 +1,181 @@
+
+package Moose::AttributeHelpers;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose 0.56 ();
+
+use Moose::AttributeHelpers::Trait::Bool;
+use Moose::AttributeHelpers::Trait::Counter;
+use Moose::AttributeHelpers::Trait::Number;
+use Moose::AttributeHelpers::Trait::String;
+use Moose::AttributeHelpers::Trait::Collection::List;
+use Moose::AttributeHelpers::Trait::Collection::Array;
+use Moose::AttributeHelpers::Trait::Collection::Hash;
+use Moose::AttributeHelpers::Trait::Collection::ImmutableHash;
+use Moose::AttributeHelpers::Trait::Collection::Bag;
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers - Extend your attribute interfaces
+
+=head1 SYNOPSIS
+
+  package MyClass;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'mapping' => (
+      traits    => [ 'Collection::Hash' ],
+      is        => 'rw',
+      isa       => 'HashRef[Str]',
+      default   => sub { {} },
+      handles   => {
+          exists_in_mapping => 'exists',
+          ids_in_mapping    => 'keys',
+          get_mapping       => 'get',
+          set_mapping       => 'set',
+          set_quantity      => [ set => [ 'quantity' ] ],
+      },
+  );
+
+
+  # ...
+
+  my $obj = MyClass->new;
+  $obj->set_quantity(10);      # quantity => 10
+  $obj->set_mapping(4, 'foo'); # 4 => 'foo'
+  $obj->set_mapping(5, 'bar'); # 5 => 'bar'
+  $obj->set_mapping(6, 'baz'); # 6 => 'baz'
+
+
+  # prints 'bar'
+  print $obj->get_mapping(5) if $obj->exists_in_mapping(5);
+
+  # prints '4, 5, 6'
+  print join ', ', $obj->ids_in_mapping;
+
+=head1 DESCRIPTION
+
+While L<Moose> attributes provide you with a way to name your accessors,
+readers, writers, clearers and predicates, this library provides commonly
+used attribute helper methods for more specific types of data.
+
+As seen in the L</SYNOPSIS>, you specify the extension via the
+C<trait> parameter. Available meta classes are below; see L</METHOD PROVIDERS>.
+
+=head1 PARAMETERS
+
+=head2 handles
+
+This is like C<< handles >> in L<Moose/has>, but only HASH references are
+allowed.  Keys are method names that you want installed locally, and values are
+methods from the method providers (below).  Currying with delegated methods works normally for C<< handles >>.
+
+=head1 METHOD PROVIDERS
+
+=over
+
+=item L<Number|Moose::AttributeHelpers::Trait::Number>
+
+Common numerical operations.
+
+=item L<String|Moose::AttributeHelpers::Trait::String>
+
+Common methods for string operations.
+
+=item L<Counter|Moose::AttributeHelpers::Trait::Counter>
+
+Methods for incrementing and decrementing a counter attribute.
+
+=item L<Bool|Moose::AttributeHelpers::Trait::Bool>
+
+Common methods for boolean values.
+
+=item L<Collection::Hash|Moose::AttributeHelpers::Trait::Collection::Hash>
+
+Common methods for hash references.
+
+=item L<Collection::ImmutableHash|Moose::AttributeHelpers::Trait::Collection::ImmutableHash>
+
+Common methods for inspecting hash references.
+
+=item L<Collection::Array|Moose::AttributeHelpers::Trait::Collection::Array>
+
+Common methods for array references.
+
+=item L<Collection::List|Moose::AttributeHelpers::Trait::Collection::List>
+
+Common list methods for array references.
+
+=back
+
+=head1 CAVEAT
+
+This is an early release of this module. Right now it is in great need
+of documentation and tests in the test suite. However, we have used this
+module to great success at C<$work> where it has been tested very thoroughly
+and deployed into a major production site.
+
+I plan on getting better docs and tests in the next few releases, but until
+then please refer to the few tests we do have and feel free email and/or
+message me on irc.perl.org if you have any questions.
+
+=head1 TODO
+
+We need tests and docs badly.
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+B<with contributions from:>
+
+Robert (rlb3) Boone
+
+Paul (frodwith) Driver
+
+Shawn (Sartak) Moore
+
+Chris (perigrin) Prather
+
+Robert (phaylon) Sedlacek
+
+Tom (dec) Lanyon
+
+Yuval Kogman
+
+Jason May
+
+Cory (gphat) Watson
+
+Florian (rafl) Ragwitz
+
+Evan Carroll
+
+Jesse (doy) Luehrs
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/Array.pm b/lib/Moose/AttributeHelpers/MethodProvider/Array.pm
new file mode 100644 (file)
index 0000000..9fc17bb
--- /dev/null
@@ -0,0 +1,281 @@
+package Moose::AttributeHelpers::MethodProvider::Array;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::MethodProvider::List';
+
+sub push : method {
+    my ($attr, $reader, $writer) = @_;
+
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my $instance = CORE::shift;
+            $container_type_constraint->check($_)
+                || confess "Value " . ($_||'undef') . " did not pass container type constraint '$container_type_constraint'"
+                    foreach @_;
+            CORE::push @{$reader->($instance)} => @_;
+        };
+    }
+    else {
+        return sub {
+            my $instance = CORE::shift;
+            CORE::push @{$reader->($instance)} => @_;
+        };
+    }
+}
+
+sub pop : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        CORE::pop @{$reader->($_[0])}
+    };
+}
+
+sub unshift : method {
+    my ($attr, $reader, $writer) = @_;
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my $instance = CORE::shift;
+            $container_type_constraint->check($_)
+                || confess "Value " . ($_||'undef') . " did not pass container type constraint '$container_type_constraint'"
+                    foreach @_;
+            CORE::unshift @{$reader->($instance)} => @_;
+        };
+    }
+    else {
+        return sub {
+            my $instance = CORE::shift;
+            CORE::unshift @{$reader->($instance)} => @_;
+        };
+    }
+}
+
+sub shift : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        CORE::shift @{$reader->($_[0])}
+    };
+}
+
+sub get : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        $reader->($_[0])->[$_[1]]
+    };
+}
+
+sub set : method {
+    my ($attr, $reader, $writer) = @_;
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            ($container_type_constraint->check($_[2]))
+                || confess "Value " . ($_[2]||'undef') . " did not pass container type constraint '$container_type_constraint'";
+            $reader->($_[0])->[$_[1]] = $_[2]
+        };
+    }
+    else {
+        return sub {
+            $reader->($_[0])->[$_[1]] = $_[2]
+        };
+    }
+}
+
+sub accessor : method {
+    my ($attr, $reader, $writer) = @_;
+
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my $self = shift;
+
+            if (@_ == 1) { # reader
+                return $reader->($self)->[$_[0]];
+            }
+            elsif (@_ == 2) { # writer
+                ($container_type_constraint->check($_[1]))
+                    || confess "Value " . ($_[1]||'undef') . " did not pass container type constraint '$container_type_constraint'";
+                $reader->($self)->[$_[0]] = $_[1];
+            }
+            else {
+                confess "One or two arguments expected, not " . @_;
+            }
+        };
+    }
+    else {
+        return sub {
+            my $self = shift;
+
+            if (@_ == 1) { # reader
+                return $reader->($self)->[$_[0]];
+            }
+            elsif (@_ == 2) { # writer
+                $reader->($self)->[$_[0]] = $_[1];
+            }
+            else {
+                confess "One or two arguments expected, not " . @_;
+            }
+        };
+    }
+}
+
+sub clear : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        @{$reader->($_[0])} = ()
+    };
+}
+
+sub delete : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        CORE::splice @{$reader->($_[0])}, $_[1], 1;
+    }
+}
+
+sub insert : method {
+    my ($attr, $reader, $writer) = @_;
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            ($container_type_constraint->check($_[2]))
+                || confess "Value " . ($_[2]||'undef') . " did not pass container type constraint '$container_type_constraint'";
+            CORE::splice @{$reader->($_[0])}, $_[1], 0, $_[2];
+        };
+    }
+    else {
+        return sub {
+            CORE::splice @{$reader->($_[0])}, $_[1], 0, $_[2];
+        };
+    }
+}
+
+sub splice : method {
+    my ($attr, $reader, $writer) = @_;
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my ( $self, $i, $j, @elems ) = @_;
+            ($container_type_constraint->check($_))
+                || confess "Value " . (defined($_) ? $_ : 'undef') . " did not pass container type constraint '$container_type_constraint'" for @elems;
+            CORE::splice @{$reader->($self)}, $i, $j, @elems;
+        };
+    }
+    else {
+        return sub {
+            my ( $self, $i, $j, @elems ) = @_;
+            CORE::splice @{$reader->($self)}, $i, $j, @elems;
+        };
+    }
+}
+
+sub sort_in_place : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $predicate) = @_;
+
+        die "Argument must be a code reference"
+            if $predicate && ref $predicate ne 'CODE';
+
+        my @sorted;
+        if ($predicate) {
+            @sorted = CORE::sort { $predicate->($a, $b) } @{$reader->($instance)};
+        }
+        else {
+            @sorted = CORE::sort @{$reader->($instance)};
+        }
+
+        $writer->($instance, \@sorted);
+    };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::Array
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Collection::Array>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+This module also consumes the B<List> method providers, to
+see those provided methods, refer to that documentation.
+
+=over 4
+
+=item B<get>
+
+=item B<pop>
+
+=item B<push>
+
+=item B<set>
+
+=item B<shift>
+
+=item B<unshift>
+
+=item B<clear>
+
+=item B<delete>
+
+=item B<insert>
+
+=item B<splice>
+
+=item B<sort_in_place>
+
+Sorts the array I<in place>, modifying the value of the attribute.
+
+You can provide an optional subroutine reference to sort with (as you
+can with the core C<sort> function). However, instead of using C<$a>
+and C<$b>, you will need to use C<$_[0]> and C<$_[1]> instead.
+
+=item B<accessor>
+
+If passed one argument, returns the value of the requested element.
+If passed two arguments, sets the value of the requested element.
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/Bag.pm b/lib/Moose/AttributeHelpers/MethodProvider/Bag.pm
new file mode 100644 (file)
index 0000000..1738bdb
--- /dev/null
@@ -0,0 +1,97 @@
+package Moose::AttributeHelpers::MethodProvider::Bag;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::MethodProvider::ImmutableHash';
+
+sub add : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $reader->($_[0])->{$_[1]}++ };
+}
+
+sub delete : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { CORE::delete $reader->($_[0])->{$_[1]} };
+}
+
+sub reset : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $reader->($_[0])->{$_[1]} = 0 };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::Bag
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Collection::Bag>.
+
+This role is composed from the
+L<Moose::AttributeHelpers::Collection::ImmutableHash> role.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<count>
+
+=item B<delete>
+
+=item B<empty>
+
+=item B<exists>
+
+=item B<get>
+
+=item B<keys>
+
+=item B<add>
+
+=item B<reset>
+
+=item B<values>
+
+=item B<kv>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/Bool.pm b/lib/Moose/AttributeHelpers/MethodProvider/Bool.pm
new file mode 100644 (file)
index 0000000..b84cfa1
--- /dev/null
@@ -0,0 +1,85 @@
+
+package Moose::AttributeHelpers::MethodProvider::Bool;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+sub set : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], 1) };
+}
+
+sub unset : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], 0) };
+}
+
+sub toggle : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], !$reader->($_[0])) };
+}
+
+sub not : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { !$reader->($_[0]) };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::Bool
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Bool>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<set>
+
+=item B<unset>
+
+=item B<toggle>
+
+=item B<not>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Jason May E<lt>jason.a.may@gmail.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/Counter.pm b/lib/Moose/AttributeHelpers/MethodProvider/Counter.pm
new file mode 100644 (file)
index 0000000..78edffc
--- /dev/null
@@ -0,0 +1,85 @@
+
+package Moose::AttributeHelpers::MethodProvider::Counter;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+sub reset : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], $attr->default($_[0])) };
+}
+
+sub set : method {
+    my ($attr, $reader, $writer, $value) = @_;
+    return sub { $writer->($_[0], $_[1]) };
+}
+
+sub inc {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], $reader->($_[0]) + (defined($_[1]) ? $_[1] : 1) ) };
+}
+
+sub dec {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->($_[0], $reader->($_[0]) - (defined($_[1]) ? $_[1] : 1) ) };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::Counter
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Counter>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<set>
+
+=item B<inc>
+
+=item B<dec>
+
+=item B<reset>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/Hash.pm b/lib/Moose/AttributeHelpers/MethodProvider/Hash.pm
new file mode 100644 (file)
index 0000000..416e1f6
--- /dev/null
@@ -0,0 +1,205 @@
+package Moose::AttributeHelpers::MethodProvider::Hash;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::MethodProvider::ImmutableHash';
+
+sub set : method {
+    my ($attr, $reader, $writer) = @_;
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my ( $self, @kvp ) = @_;
+
+            my ( @keys, @values );
+
+            while ( @kvp ) {
+                my ( $key, $value ) = ( shift(@kvp), shift(@kvp) );
+                ($container_type_constraint->check($value))
+                    || confess "Value " . ($value||'undef') . " did not pass container type constraint '$container_type_constraint'";
+                push @keys, $key;
+                push @values, $value;
+            }
+
+            if ( @values > 1 ) {
+                @{ $reader->($self) }{@keys} = @values;
+            } else {
+                $reader->($self)->{$keys[0]} = $values[0];
+            }
+        };
+    }
+    else {
+        return sub {
+            if ( @_ == 3 ) {
+                $reader->($_[0])->{$_[1]} = $_[2]
+            } else {
+                my ( $self, @kvp ) = @_;
+                my ( @keys, @values );
+
+                while ( @kvp ) {
+                    push @keys, shift @kvp;
+                    push @values, shift @kvp;
+                }
+
+                @{ $reader->($_[0]) }{@keys} = @values;
+            }
+        };
+    }
+}
+
+sub accessor : method {
+    my ($attr, $reader, $writer) = @_;
+
+    if ($attr->has_type_constraint && $attr->type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
+        my $container_type_constraint = $attr->type_constraint->type_parameter;
+        return sub {
+            my $self = shift;
+
+            if (@_ == 1) { # reader
+                return $reader->($self)->{$_[0]};
+            }
+            elsif (@_ == 2) { # writer
+                ($container_type_constraint->check($_[1]))
+                    || confess "Value " . ($_[1]||'undef') . " did not pass container type constraint '$container_type_constraint'";
+                $reader->($self)->{$_[0]} = $_[1];
+            }
+            else {
+                confess "One or two arguments expected, not " . @_;
+            }
+        };
+    }
+    else {
+        return sub {
+            my $self = shift;
+
+            if (@_ == 1) { # reader
+                return $reader->($self)->{$_[0]};
+            }
+            elsif (@_ == 2) { # writer
+                $reader->($self)->{$_[0]} = $_[1];
+            }
+            else {
+                confess "One or two arguments expected, not " . @_;
+            }
+        };
+    }
+}
+
+sub clear : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { %{$reader->($_[0])} = () };
+}
+
+sub delete : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $hashref = $reader->(shift);
+        CORE::delete @{$hashref}{@_};
+    };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::Hash
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Collection::Hash>.
+
+This role is composed from the
+L<Moose::AttributeHelpers::Collection::ImmutableHash> role.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<count>
+
+Returns the number of elements in the hash.
+
+=item B<delete>
+
+Removes the element with the given key
+
+=item B<defined>
+
+Returns true if the value of a given key is defined
+
+=item B<empty>
+
+If the list is populated, returns true. Otherwise, returns false.
+
+=item B<clear>
+
+Unsets the hash entirely.
+
+=item B<exists>
+
+Returns true if the given key is present in the hash
+
+=item B<get>
+
+Returns an element of the hash by its key.
+
+=item B<keys>
+
+Returns the list of keys in the hash.
+
+=item B<set>
+
+Sets the element in the hash at the given key to the given value.
+
+=item B<values>
+
+Returns the list of values in the hash.
+
+=item B<kv>
+
+Returns the  key, value pairs in the hash
+
+=item B<accessor>
+
+If passed one argument, returns the value of the requested key. If passed two
+arguments, sets the value of the requested key.
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/ImmutableHash.pm b/lib/Moose/AttributeHelpers/MethodProvider/ImmutableHash.pm
new file mode 100644 (file)
index 0000000..c56fc82
--- /dev/null
@@ -0,0 +1,155 @@
+package Moose::AttributeHelpers::MethodProvider::ImmutableHash;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+sub exists : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { CORE::exists $reader->($_[0])->{$_[1]} ? 1 : 0 };
+}
+
+sub defined : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { CORE::defined $reader->($_[0])->{$_[1]} ? 1 : 0 };
+}
+
+sub get : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        if ( @_ == 2 ) {
+            $reader->($_[0])->{$_[1]}
+        } else {
+            my ( $self, @keys ) = @_;
+            @{ $reader->($self) }{@keys}
+        }
+    };
+}
+
+sub keys : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { CORE::keys %{$reader->($_[0])} };
+}
+
+sub values : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { CORE::values %{$reader->($_[0])} };
+}
+
+sub kv : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $h = $reader->($_[0]);
+        map {
+            [ $_, $h->{$_} ]
+        } CORE::keys %{$h}
+    };
+}
+
+sub elements : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $h = $reader->($_[0]);
+        map {
+            $_, $h->{$_}
+        } CORE::keys %{$h}
+    };
+}
+
+sub count : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { scalar CORE::keys %{$reader->($_[0])} };
+}
+
+sub empty : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { scalar CORE::keys %{$reader->($_[0])} ? 1 : 0 };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::ImmutableHash
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Collection::ImmutableHash>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<count>
+
+Returns the number of elements in the list.
+
+=item B<empty>
+
+If the list is populated, returns true. Otherwise, returns false.
+
+=item B<exists>
+
+Returns true if the given key is present in the hash
+
+=item B<defined>
+
+Returns true if the value of a given key is defined
+
+=item B<get>
+
+Returns an element of the hash by its key.
+
+=item B<keys>
+
+Returns the list of keys in the hash.
+
+=item B<values>
+
+Returns the list of values in the hash.
+
+=item B<kv>
+
+Returns the key, value pairs in the hash as array references
+
+=item B<elements>
+
+Returns the key, value pairs in the hash as a flattened list
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/List.pm b/lib/Moose/AttributeHelpers/MethodProvider/List.pm
new file mode 100644 (file)
index 0000000..c1d425c
--- /dev/null
@@ -0,0 +1,274 @@
+package Moose::AttributeHelpers::MethodProvider::List;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+sub count : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        scalar @{$reader->($_[0])}
+    };
+}
+
+sub empty : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        scalar @{$reader->($_[0])} ? 1 : 0
+    };
+}
+
+sub find : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $predicate) = @_;
+        foreach my $val (@{$reader->($instance)}) {
+            return $val if $predicate->($val);
+        }
+        return;
+    };
+}
+
+sub map : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $f) = @_;
+        CORE::map { $f->($_) } @{$reader->($instance)}
+    };
+}
+
+sub sort : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $predicate) = @_;
+        die "Argument must be a code reference"
+            if $predicate && ref $predicate ne 'CODE';
+
+        if ($predicate) {
+            CORE::sort { $predicate->($a, $b) } @{$reader->($instance)};
+        }
+        else {
+            CORE::sort @{$reader->($instance)};
+        }
+    };
+}
+
+sub grep : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $predicate) = @_;
+        CORE::grep { $predicate->($_) } @{$reader->($instance)}
+    };
+}
+
+sub elements : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance) = @_;
+        @{$reader->($instance)}
+    };
+}
+
+sub join : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my ($instance, $separator) = @_;
+        join $separator, @{$reader->($instance)}
+    };
+}
+
+sub get : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        $reader->($_[0])->[$_[1]]
+    };
+}
+
+sub first : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        $reader->($_[0])->[0]
+    };
+}
+
+sub last : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        $reader->($_[0])->[-1]
+    };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::List
+
+=head1 SYNOPSIS
+
+   package Stuff;
+   use Moose;
+   use Moose::AttributeHelpers;
+
+   has 'options' => (
+       metaclass  => 'Collection::List',
+       is         => 'rw',
+       isa        => 'ArrayRef[Str]',
+       default    => sub { [] },
+       auto_deref => 1,
+       handles   => {
+           all_options       => 'elements',
+           map_options       => 'map',
+           filter_options    => 'grep',
+           find_option       => 'find',
+           first_option      => 'first',
+           last_option       => 'last',
+           get_option        => 'get',
+           join_options      => 'join',
+           count_options     => 'count',
+           do_i_have_options => 'empty',
+           sorted_options    => 'sort',
+       }
+   );
+
+   no Moose;
+   1;
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::Collection::List>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<count>
+
+Returns the number of elements in the list.
+
+   $stuff = Stuff->new;
+   $stuff->options(["foo", "bar", "baz", "boo"]);
+
+   my $count = $stuff->count_options;
+   print "$count\n"; # prints 4
+
+=item B<empty>
+
+If the list is populated, returns true. Otherwise, returns false.
+
+   $stuff->do_i_have_options ? print "Good boy.\n" : die "No options!\n" ;
+
+=item B<find>
+
+This method accepts a subroutine reference as its argument. That sub
+will receive each element of the list in turn. If it returns true for
+an element, that element will be returned by the C<find> method.
+
+   my $found = $stuff->find_option( sub { $_[0] =~ /^b/ } );
+   print "$found\n"; # prints "bar"
+
+=item B<grep>
+
+This method accepts a subroutine reference as its argument. This
+method returns every element for which that subroutine reference
+returns a true value.
+
+   my @found = $stuff->filter_options( sub { $_[0] =~ /^b/ } );
+   print "@found\n"; # prints "bar baz boo"
+
+=item B<map>
+
+This method accepts a subroutine reference as its argument. The
+subroutine will be executed for each element of the list. It is
+expected to return a modified version of that element. The return
+value of the method is a list of the modified options.
+
+   my @mod_options = $stuff->map_options( sub { $_[0] . "-tag" } );
+   print "@mod_options\n"; # prints "foo-tag bar-tag baz-tag boo-tag"
+
+=item B<sort>
+
+Sorts and returns the elements of the list.
+
+You can provide an optional subroutine reference to sort with (as you
+can with the core C<sort> function). However, instead of using C<$a>
+and C<$b>, you will need to use C<$_[0]> and C<$_[1]> instead.
+
+   # ascending ASCIIbetical
+   my @sorted = $stuff->sort_options();
+
+   # Descending alphabetical order
+   my @sorted_options = $stuff->sort_options( sub { lc $_[1] cmp lc $_[0] } );
+   print "@sorted_options\n"; # prints "foo boo baz bar"
+
+=item B<elements>
+
+Returns all of the elements of the list
+
+   my @option = $stuff->all_options;
+   print "@options\n"; # prints "foo bar baz boo"
+
+=item B<join>
+
+Joins every element of the list using the separator given as argument.
+
+   my $joined = $stuff->join_options( ':' );
+   print "$joined\n"; # prints "foo:bar:baz:boo"
+
+=item B<get>
+
+Returns an element of the list by its index.
+
+   my $option = $stuff->get_option(1);
+   print "$option\n"; # prints "bar"
+
+=item B<first>
+
+Returns the first element of the list.
+
+   my $first = $stuff->first_option;
+   print "$first\n"; # prints "foo"
+
+=item B<last>
+
+Returns the last element of the list.
+
+   my $last = $stuff->last_option;
+   print "$last\n"; # prints "boo"
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/MethodProvider/String.pm b/lib/Moose/AttributeHelpers/MethodProvider/String.pm
new file mode 100644 (file)
index 0000000..a99bf70
--- /dev/null
@@ -0,0 +1,164 @@
+
+package Moose::AttributeHelpers::MethodProvider::String;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+sub append : method {
+    my ($attr, $reader, $writer) = @_;
+
+    return sub { $writer->( $_[0],  $reader->($_[0]) . $_[1] ) };
+}
+
+sub prepend : method {
+    my ($attr, $reader, $writer) = @_;
+
+    return sub { $writer->( $_[0],  $_[1] . $reader->($_[0]) ) };
+}
+
+sub replace : method {
+    my ($attr, $reader, $writer) = @_;
+
+    return sub {
+        my ( $self, $regex, $replacement ) = @_;
+        my $v = $reader->($_[0]);
+
+        if ( (ref($replacement)||'') eq 'CODE' ) {
+            $v =~ s/$regex/$replacement->()/e;
+        } else {
+            $v =~ s/$regex/$replacement/;
+        }
+
+        $writer->( $_[0], $v);
+    };
+}
+
+sub match : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $reader->($_[0]) =~ $_[1] };
+}
+
+sub chop : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $v = $reader->($_[0]);
+        CORE::chop($v);
+        $writer->( $_[0], $v);
+    };
+}
+
+sub chomp : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $v = $reader->($_[0]);
+        chomp($v);
+        $writer->( $_[0], $v);
+    };
+}
+
+sub inc : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $v = $reader->($_[0]);
+        $v++;
+        $writer->( $_[0], $v);
+    };
+}
+
+sub clear : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub { $writer->( $_[0], '' ) }
+}
+
+sub substr : method {
+    my ($attr, $reader, $writer) = @_;
+    return sub {
+        my $self = shift;
+        my $v = $reader->($self);
+
+        my $offset      = defined $_[0] ? shift : 0;
+        my $length      = defined $_[0] ? shift : CORE::length($v);
+        my $replacement = defined $_[0] ? shift : undef;
+
+        my $ret;
+        if (defined $replacement) {
+            $ret = CORE::substr($v, $offset, $length, $replacement);
+            $writer->($self, $v);
+        }
+        else {
+            $ret = CORE::substr($v, $offset, $length);
+        }
+
+        return $ret;
+    };
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::MethodProvider::String
+
+=head1 DESCRIPTION
+
+This is a role which provides the method generators for
+L<Moose::AttributeHelpers::String>.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=back
+
+=head1 PROVIDED METHODS
+
+=over 4
+
+=item B<append>
+
+=item B<prepend>
+
+=item B<replace>
+
+=item B<match>
+
+=item B<chomp>
+
+=item B<chop>
+
+=item B<inc>
+
+=item B<clear>
+
+=item B<substr>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Base.pm b/lib/Moose/AttributeHelpers/Trait/Base.pm
new file mode 100644 (file)
index 0000000..42c98a1
--- /dev/null
@@ -0,0 +1,176 @@
+
+package Moose::AttributeHelpers::Trait::Base;
+use Moose::Role;
+use Moose::Util::TypeConstraints;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+requires 'helper_type';
+
+# these next two are the possible methods
+# you can use in the 'handles' map.
+
+# provide a Class or Role which we can
+# collect the method providers from
+
+# requires_attr 'method_provider'
+
+# or you can provide a HASH ref of anon subs
+# yourself. This will also collect and store
+# the methods from a method_provider as well
+has 'method_constructors' => (
+    is      => 'ro',
+    isa     => 'HashRef',
+    lazy    => 1,
+    default => sub {
+        my $self = shift;
+        return +{} unless $self->has_method_provider;
+        # or grab them from the role/class
+        my $method_provider = $self->method_provider->meta;
+        return +{
+            map {
+                $_ => $method_provider->get_method($_)
+            } $method_provider->get_method_list
+        };
+    },
+);
+
+# extend the parents stuff to make sure
+# certain bits are now required ...
+has '+default'         => (required => 1);
+has '+type_constraint' => (required => 1);
+
+## Methods called prior to instantiation
+
+sub process_options_for_handles {
+    my ($self, $options) = @_;
+
+    if (my $type = $self->helper_type) {
+        (exists $options->{isa})
+            || confess "You must define a type with the $type metaclass";
+
+        my $isa = $options->{isa};
+
+        unless (blessed($isa) && $isa->isa('Moose::Meta::TypeConstraint')) {
+            $isa = Moose::Util::TypeConstraints::find_or_create_type_constraint($isa);
+        }
+
+        ($isa->is_a_type_of($type))
+            || confess "The type constraint for a $type ($options->{isa}) must be a subtype of $type";
+    }
+}
+
+before '_process_options' => sub {
+    my ($self, $name, $options) = @_;
+    $self->process_options_for_handles($options, $name);
+};
+
+around '_canonicalize_handles' => sub {
+    my $next    = shift;
+    my $self    = shift;
+    my $handles = $self->handles;
+    return unless $handles;
+    unless ('HASH' eq ref $handles) {
+        $self->throw_error(
+            "The 'handles' option must be a HASH reference, not $handles"
+        );
+    }
+    return map {
+        my $to = $handles->{$_};
+        $to = [ $to ] unless ref $to;
+        $_ => $to
+    } keys %$handles;
+};
+
+## methods called after instantiation
+
+before 'install_accessors' => sub { (shift)->check_handles_values };
+
+sub check_handles_values {
+    my $self = shift;
+
+    my $method_constructors = $self->method_constructors;
+
+    my %handles = $self->_canonicalize_handles;
+
+    for my $original_method (values %handles) {
+        my $name = $original_method->[0];
+        (exists $method_constructors->{$name})
+            || confess "$name is an unsupported method type";
+    }
+
+}
+
+around '_make_delegation_method' => sub {
+    my $next = shift;
+    my ($self, $handle_name, $method_to_call) = @_;
+
+    my ($name, $curried_args) = @$method_to_call;
+
+    $curried_args ||= [];
+
+    my $method_constructors = $self->method_constructors;
+
+    my $code = $method_constructors->{$name}->(
+        $self,
+        $self->get_read_method_ref,
+        $self->get_write_method_ref,
+    );
+
+    return $next->(
+        $self,
+        $handle_name,
+        sub {
+            my $instance = shift;
+            return $code->($instance, @$curried_args, @_);
+        },
+    );
+};
+
+no Moose::Role;
+no Moose::Util::TypeConstraints;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Moose::AttributeHelpers::Trait::Base - base role for helpers
+
+=head1 METHODS
+
+=head2 check_handles_values
+
+Confirms that handles has all valid possibilities in it.
+
+=head2 process_options_for_handles
+
+Ensures that the type constraint (C<isa>) matches the helper type.
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHORS
+
+Yuval Kogman
+
+Shawn M Moore
+
+Jesse Luehrs
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Bool.pm b/lib/Moose/AttributeHelpers/Trait/Bool.pm
new file mode 100644 (file)
index 0000000..b41d734
--- /dev/null
@@ -0,0 +1,140 @@
+package Moose::AttributeHelpers::Trait::Bool;
+use Moose::Role;
+use Moose::AttributeHelpers::MethodProvider::Bool;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::Trait::Base';
+
+sub helper_type { 'Bool' }
+
+# NOTE:
+# we don't use the method provider for this
+# module since many of the names of the provied
+# methods would conflict with keywords
+# - SL
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::Bool'
+);
+
+before 'process_options_for_handles' => sub {
+    my ($self, $options, $name) = @_;
+
+    # Set some default attribute options here unless already defined
+    if ((my $type = $self->helper_type) && !exists $options->{isa}){
+        $options->{isa} = $type;
+    }
+};
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Bool;
+sub register_implementation { 'Moose::AttributeHelpers::Trait::Bool' }
+
+1;
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Bool
+
+=head1 SYNOPSIS
+
+  package Room;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'is_lit' => (
+      metaclass => 'Bool',
+      is        => 'rw',
+      isa       => 'Bool',
+      default   => sub { 0 },
+      handles   => {
+          illuminate  => 'set',
+          darken      => 'unset',
+          flip_switch => 'toggle',
+          is_dark     => 'not',
+      }
+  );
+
+  my $room = Room->new();
+  $room->illuminate;     # same as $room->is_lit(1);
+  $room->darken;         # same as $room->is_lit(0);
+  $room->flip_switch;    # same as $room->is_lit(not $room->is_lit);
+  return $room->is_dark; # same as !$room->is_lit
+
+=head1 DESCRIPTION
+
+This provides a simple boolean attribute, which supports most of the
+basic math operations.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<helper_type>
+
+=item B<method_constructors>
+
+=item B<has_method_provider>
+
+=item B<method_provider>
+
+=back
+
+=head1 PROVIDED METHODS
+
+It is important to note that all those methods do in place
+modification of the value stored in the attribute.
+
+=over 4
+
+=item I<set>
+
+Sets the value to C<1>.
+
+=item I<unset>
+
+Set the value to C<0>.
+
+=item I<toggle>
+
+Toggle the value. If it's true, set to false, and vice versa.
+
+=item I<not>
+
+Equivalent of 'not C<$value>'.
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Jason May
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection.pm b/lib/Moose/AttributeHelpers/Trait/Collection.pm
new file mode 100644 (file)
index 0000000..14bd873
--- /dev/null
@@ -0,0 +1,62 @@
+
+package Moose::AttributeHelpers::Trait::Collection;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::Trait::Base';
+
+no Moose::Role;
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection - Base class for all collection type helpers
+
+=head1 DESCRIPTION
+
+Documentation to come.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<container_type>
+
+=item B<container_type_constraint>
+
+=item B<has_container_type>
+
+=item B<process_options_for_handles>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection/Array.pm b/lib/Moose/AttributeHelpers/Trait/Collection/Array.pm
new file mode 100644 (file)
index 0000000..53e36c6
--- /dev/null
@@ -0,0 +1,98 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Array;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::Array;
+
+with 'Moose::AttributeHelpers::Trait::Collection';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::Array'
+);
+
+sub helper_type { 'ArrayRef' }
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Collection::Array;
+sub register_implementation {
+    'Moose::AttributeHelpers::Trait::Collection::Array'
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection::Array
+
+=head1 SYNOPSIS
+
+  package Stuff;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'options' => (
+      metaclass => 'Collection::Array',
+      is        => 'ro',
+      isa       => 'ArrayRef[Int]',
+      default   => sub { [] },
+      handles   => {
+          add_options        => 'push',
+          remove_last_option => 'pop',
+      }
+  );
+
+=head1 DESCRIPTION
+
+This module provides an Array attribute which provides a number of
+array operations. See L<Moose::AttributeHelpers::MethodProvider::Array>
+for more details.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection/Bag.pm b/lib/Moose/AttributeHelpers/Trait/Collection/Bag.pm
new file mode 100644 (file)
index 0000000..5fb342b
--- /dev/null
@@ -0,0 +1,116 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Bag;
+use Moose::Role;
+use Moose::Util::TypeConstraints;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::Bag;
+
+with 'Moose::AttributeHelpers::Trait::Collection';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::Bag'
+);
+
+subtype 'Bag' => as 'HashRef[Int]';
+
+sub helper_type { 'Bag' }
+
+before 'process_options_for_handles' => sub {
+    my ($self, $options, $name) = @_;
+
+    # Set some default attribute options here unless already defined
+    if ((my $type = $self->helper_type) && !exists $options->{isa}){
+        $options->{isa} = $type;
+    }
+
+    $options->{default} = sub { +{} } unless exists $options->{default};
+};
+
+no Moose::Role;
+no Moose::Util::TypeConstraints;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Collection::Bag;
+sub register_implementation {
+    'Moose::AttributeHelpers::Trait::Collection::Bag'
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection::Bag
+
+=head1 SYNOPSIS
+
+  package Stuff;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'word_histogram' => (
+      metaclass => 'Collection::Bag',
+      is        => 'ro',
+      isa       => 'Bag', # optional ... as is defalt
+      handles   => {
+          add_word      => 'add',
+          get_count_for => 'get',
+          has_any_words => 'empty',
+          num_words     => 'count',
+          delete_word   => 'delete',
+      }
+  );
+
+=head1 DESCRIPTION
+
+This module provides a Bag attribute which provides a number of
+bag-like operations. See L<Moose::AttributeHelpers::MethodProvider::Bag>
+for more details.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=item B<process_options_for_handles>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection/Hash.pm b/lib/Moose/AttributeHelpers/Trait/Collection/Hash.pm
new file mode 100644 (file)
index 0000000..b0d7c2b
--- /dev/null
@@ -0,0 +1,101 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Hash;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::Hash;
+
+with 'Moose::AttributeHelpers::Trait::Collection';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::Hash'
+);
+
+sub helper_type { 'HashRef' }
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Collection::Hash;
+sub register_implementation {
+    'Moose::AttributeHelpers::Trait::Collection::Hash'
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection::Hash
+
+=head1 SYNOPSIS
+
+  package Stuff;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'options' => (
+      metaclass => 'Collection::Hash',
+      is        => 'ro',
+      isa       => 'HashRef[Str]',
+      default   => sub { {} },
+      handles   => {
+          set_option    => 'set',
+          get_option    => 'get',
+          has_options   => 'empty',
+          num_options   => 'count',
+          delete_option => 'delete',
+      }
+  );
+
+=head1 DESCRIPTION
+
+This module provides a Hash attribute which provides a number of
+hash-like operations. See L<Moose::AttributeHelpers::MethodProvider::Hash>
+for more details.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection/ImmutableHash.pm b/lib/Moose/AttributeHelpers/Trait/Collection/ImmutableHash.pm
new file mode 100644 (file)
index 0000000..c12a4bb
--- /dev/null
@@ -0,0 +1,99 @@
+
+package Moose::AttributeHelpers::Trait::Collection::ImmutableHash;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::ImmutableHash;
+
+with 'Moose::AttributeHelpers::Trait::Collection';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::ImmutableHash'
+);
+
+sub helper_type { 'HashRef' }
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Collection::ImmutableHash;
+sub register_implementation {
+    'Moose::AttributeHelpers::Trait::Collection::ImmutableHash'
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection::ImmutableHash
+
+=head1 SYNOPSIS
+
+  package Stuff;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'options' => (
+      metaclass => 'Collection::ImmutableHash',
+      is        => 'ro',
+      isa       => 'HashRef[Str]',
+      default   => sub { {} },
+      handles  => {
+          get_option      => 'get',
+          has_options     => 'empty',
+          get_option_list => 'keys',
+      }
+  );
+
+=head1 DESCRIPTION
+
+This module provides a immutable HashRef attribute which provides a number of
+hash-line operations. See L<Moose::AttributeHelpers::MethodProvider::ImmutableHash>
+for more details.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Collection/List.pm b/lib/Moose/AttributeHelpers/Trait/Collection/List.pm
new file mode 100644 (file)
index 0000000..1f1a309
--- /dev/null
@@ -0,0 +1,98 @@
+
+package Moose::AttributeHelpers::Trait::Collection::List;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::List;
+
+with 'Moose::AttributeHelpers::Trait::Collection';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::List'
+);
+
+sub helper_type { 'ArrayRef' }
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Collection::List;
+sub register_implementation {
+    'Moose::AttributeHelpers::Trait::Collection::List'
+}
+
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Collection::List
+
+=head1 SYNOPSIS
+
+  package Stuff;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'options' => (
+      metaclass => 'Collection::List',
+      is        => 'ro',
+      isa       => 'ArrayRef[Int]',
+      default   => sub { [] },
+      handles   => {
+          map_options    => 'map',
+          filter_options => 'grep',
+      }
+  );
+
+=head1 DESCRIPTION
+
+This module provides an List attribute which provides a number of
+list operations. See L<Moose::AttributeHelpers::MethodProvider::List>
+for more details.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Counter.pm b/lib/Moose/AttributeHelpers/Trait/Counter.pm
new file mode 100644 (file)
index 0000000..2cbb835
--- /dev/null
@@ -0,0 +1,170 @@
+
+package Moose::AttributeHelpers::Trait::Counter;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::Counter;
+
+with 'Moose::AttributeHelpers::Trait::Base';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::Counter',
+);
+
+sub helper_type { 'Num' }
+
+before 'process_options_for_handles' => sub {
+    my ($self, $options, $name) = @_;
+
+    # Set some default attribute options here unless already defined
+    if ((my $type = $self->helper_type) && !exists $options->{isa}){
+        $options->{isa} = $type;
+    }
+
+    $options->{is}      = 'ro' unless exists $options->{is};
+    $options->{default} = 0    unless exists $options->{default};
+};
+
+after 'check_handles_values' => sub {
+    my $self     = shift;
+    my $handles = $self->handles;
+
+    unless (scalar keys %$handles) {
+        my $method_constructors = $self->method_constructors;
+        my $attr_name           = $self->name;
+
+        foreach my $method (keys %$method_constructors) {
+            $handles->{$method . '_' . $attr_name} = $method;
+        }
+
+        $self->_set_handles($handles);
+    }
+};
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Counter;
+sub register_implementation { 'Moose::AttributeHelpers::Trait::Counter' }
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Counter
+
+=head1 SYNOPSIS
+
+  package MyHomePage;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'counter' => (
+      metaclass => 'Counter',
+      is        => 'ro',
+      isa       => 'Num',
+      default   => sub { 0 },
+      handles   => {
+          inc_counter   => 'inc',
+          dec_counter   => 'dec',
+          reset_counter => 'reset',
+      }
+  );
+
+  my $page = MyHomePage->new();
+  $page->inc_counter; # same as $page->counter($page->counter + 1);
+  $page->dec_counter; # same as $page->counter($page->counter - 1);
+
+=head1 DESCRIPTION
+
+This module provides a simple counter attribute, which can be
+incremented and decremented.
+
+If your attribute definition does not include any of I<is>, I<isa>,
+I<default> or I<handles> but does use the C<Counter> metaclass,
+then this module applies defaults as in the L</SYNOPSIS>
+above. This allows for a very basic counter definition:
+
+  has 'foo' => (metaclass => 'Counter');
+  $obj->inc_foo;
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=item B<process_options_for_handles>
+
+Run before its superclass method.
+
+=item B<check_handles_values>
+
+Run after its superclass method.
+
+=back
+
+=head1 PROVIDED METHODS
+
+It is important to note that all those methods do in place
+modification of the value stored in the attribute.
+
+=over 4
+
+=item I<set>
+
+Set the counter to the specified value.
+
+=item I<inc>
+
+Increments the value stored in this slot by 1. Providing an argument will
+cause the counter to be increased by specified amount.
+
+=item I<dec>
+
+Decrements the value stored in this slot by 1. Providing an argument will
+cause the counter to be increased by specified amount.
+
+=item I<reset>
+
+Resets the value stored in this slot to it's default value.
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/Number.pm b/lib/Moose/AttributeHelpers/Trait/Number.pm
new file mode 100644 (file)
index 0000000..8e666ad
--- /dev/null
@@ -0,0 +1,170 @@
+package Moose::AttributeHelpers::Trait::Number;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+with 'Moose::AttributeHelpers::Trait::Base';
+
+sub helper_type { 'Num' }
+
+# NOTE:
+# we don't use the method provider for this
+# module since many of the names of the provied
+# methods would conflict with keywords
+# - SL
+
+has 'method_constructors' => (
+    is      => 'ro',
+    isa     => 'HashRef',
+    lazy    => 1,
+    default => sub {
+        return +{
+            set => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $_[1]) };
+            },
+            add => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $reader->($_[0]) + $_[1]) };
+            },
+            sub => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $reader->($_[0]) - $_[1]) };
+            },
+            mul => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $reader->($_[0]) * $_[1]) };
+            },
+            div => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $reader->($_[0]) / $_[1]) };
+            },
+            mod => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], $reader->($_[0]) % $_[1]) };
+            },
+            abs => sub {
+                my ($attr, $reader, $writer) = @_;
+                return sub { $writer->($_[0], abs($reader->($_[0])) ) };
+            },
+        }
+    }
+);
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::Number;
+sub register_implementation { 'Moose::AttributeHelpers::Trait::Number' }
+
+1;
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Number
+
+=head1 SYNOPSIS
+
+  package Real;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'integer' => (
+      metaclass => 'Number',
+      is        => 'ro',
+      isa       => 'Int',
+      default   => sub { 5 },
+      handles  => {
+          set => 'set',
+          add => 'add',
+          sub => 'sub',
+          mul => 'mul',
+          div => 'div',
+          mod => 'mod',
+          abs => 'abs',
+      }
+  );
+
+  my $real = Real->new();
+  $real->add(5); # same as $real->integer($real->integer + 5);
+  $real->sub(2); # same as $real->integer($real->integer - 2);
+
+=head1 DESCRIPTION
+
+This provides a simple numeric attribute, which supports most of the
+basic math operations.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<helper_type>
+
+=item B<method_constructors>
+
+=back
+
+=head1 PROVIDED METHODS
+
+It is important to note that all those methods do in place
+modification of the value stored in the attribute.
+
+=over 4
+
+=item I<set ($value)>
+
+Alternate way to set the value.
+
+=item I<add ($value)>
+
+Adds the current value of the attribute to C<$value>.
+
+=item I<sub ($value)>
+
+Subtracts the current value of the attribute to C<$value>.
+
+=item I<mul ($value)>
+
+Multiplies the current value of the attribute to C<$value>.
+
+=item I<div ($value)>
+
+Divides the current value of the attribute to C<$value>.
+
+=item I<mod ($value)>
+
+Modulus the current value of the attribute to C<$value>.
+
+=item I<abs>
+
+Sets the current value of the attribute to its absolute value.
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Robert Boone
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/AttributeHelpers/Trait/String.pm b/lib/Moose/AttributeHelpers/Trait/String.pm
new file mode 100644 (file)
index 0000000..1e62902
--- /dev/null
@@ -0,0 +1,187 @@
+
+package Moose::AttributeHelpers::Trait::String;
+use Moose::Role;
+
+our $VERSION   = '0.83';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose::AttributeHelpers::MethodProvider::String;
+
+with 'Moose::AttributeHelpers::Trait::Base';
+
+has 'method_provider' => (
+    is        => 'ro',
+    isa       => 'ClassName',
+    predicate => 'has_method_provider',
+    default   => 'Moose::AttributeHelpers::MethodProvider::String',
+);
+
+sub helper_type { 'Str' }
+
+before 'process_options_for_handles' => sub {
+    my ($self, $options, $name) = @_;
+
+    # Set some default attribute options here unless already defined
+    if ((my $type = $self->helper_type) && !exists $options->{isa}){
+        $options->{isa} = $type;
+    }
+
+    $options->{is}      = 'rw' unless exists $options->{is};
+    $options->{default} = ''   unless exists $options->{default};
+};
+
+after 'check_handles_values' => sub {
+    my $self     = shift;
+    my $handles = $self->handles;
+
+    unless (scalar keys %$handles) {
+        my $method_constructors = $self->method_constructors;
+        my $attr_name           = $self->name;
+
+        foreach my $method (keys %$method_constructors) {
+            $handles->{$method} = ($method . '_' . $attr_name);
+        }
+    }
+};
+
+no Moose::Role;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Trait::String;
+sub register_implementation { 'Moose::AttributeHelpers::Trait::String' }
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::String
+
+=head1 SYNOPSIS
+
+  package MyHomePage;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'text' => (
+      metaclass => 'String',
+      is        => 'rw',
+      isa       => 'Str',
+      default   => sub { '' },
+      handles  => {
+          add_text     => 'append',
+          replace_text => 'replace',
+      }
+  );
+
+  my $page = MyHomePage->new();
+  $page->add_text("foo"); # same as $page->text($page->text . "foo");
+
+=head1 DESCRIPTION
+
+This module provides a simple string attribute, to which mutating string
+operations can be applied more easily (no need to make an lvalue attribute
+metaclass or use temporary variables). Additional methods are provided for
+completion.
+
+If your attribute definition does not include any of I<is>, I<isa>,
+I<default> or I<handles> but does use the C<String> metaclass,
+then this module applies defaults as in the L</SYNOPSIS>
+above. This allows for a very basic counter definition:
+
+  has 'foo' => (metaclass => 'String');
+  $obj->append_foo;
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=item B<method_provider>
+
+=item B<has_method_provider>
+
+=item B<helper_type>
+
+=item B<process_options_for_handles>
+
+Run before its superclass method.
+
+=item B<check_handles_values>
+
+Run after its superclass method.
+
+=back
+
+=head1 PROVIDED METHODS
+
+It is important to note that all those methods do in place
+modification of the value stored in the attribute.
+
+=over 4
+
+=item I<inc>
+
+Increments the value stored in this slot using the magical string autoincrement
+operator. Note that Perl doesn't provide analogous behavior in C<-->, so
+C<dec> is not available.
+
+=item I<append> C<$string>
+
+Append a string, like C<.=>.
+
+=item I<prepend> C<$string>
+
+Prepend a string.
+
+=item I<replace> C<$pattern> C<$replacement>
+
+Performs a regexp substitution (L<perlop/s>). There is no way to provide the
+C<g> flag, but code references will be accepted for the replacement, causing
+the regex to be modified with a single C<e>. C</smxi> can be applied using the
+C<qr> operator.
+
+=item I<match> C<$pattern>
+
+Like I<replace> but without the replacement. Provided mostly for completeness.
+
+=item C<chop>
+
+L<perlfunc/chop>
+
+=item C<chomp>
+
+L<perlfunc/chomp>
+
+=item C<clear>
+
+Sets the string to the empty string (not the value passed to C<default>).
+
+=back
+
+=head1 BUGS
+
+All complex software has bugs lurking in it, and this module is no
+exception. If you find a bug please either email me, or add the bug
+to cpan-RT.
+
+=head1 AUTHOR
+
+Stevan Little E<lt>stevan@iinteractive.comE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007-2009 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
index 5cfee58..1e1d712 100644 (file)
@@ -16,6 +16,33 @@ feature.  If you encounter a problem and have a solution but don't see
 it documented here, or think we missed an important feature, please
 send us a patch.
 
+=head1 Version 0.85
+
+L<Moose::AttributeHelpers> has been moved into the Moose core from
+L<MooseX::AttributeHelpers>.  Major changes include:
+
+=over
+
+=item C<traits>, not C<metaclass>
+
+All of the method providers are available via traits only.  The custom
+metaclasses were strictly inferior to applying attribute metaclass traits.
+
+=item C<handles>, not C<provides> or C<curries>
+
+C<provides> syntax was like core Moose C<< handles => HASHREF >> syntax, but
+with the keys and values reversed.  This was confusing, and
+AttributeHelpers now uses C<< handles => HASHREF >> in a way that
+should be intuitive to anyone already familiar with it for normal attributes.
+
+The C<curries> functionality provided by AttributeHelpers has been generalized
+to apply to all cases of C<< handles => HASHREF >>, though not every piece of
+functionality has been ported (currying with a CODEREF is not supported).
+
+=back
+
+See L<Moose::AttributeHelpers> for the new documentation.
+
 =head1 Version 0.84
 
 The C<Role> type has been deprecated. On its own, it was useless,
index b734854..5dce132 100644 (file)
@@ -40,6 +40,7 @@ __PACKAGE__->meta->add_attribute('trigger' => (
 ));
 __PACKAGE__->meta->add_attribute('handles' => (
     reader    => 'handles',
+    writer    => '_set_handles',
     predicate => 'has_handles',
 ));
 __PACKAGE__->meta->add_attribute('documentation' => (
@@ -730,11 +731,17 @@ sub _make_delegation_method {
     $method_body = $method_to_call
         if 'CODE' eq ref($method_to_call);
 
+    my $curried_arguments = [];
+
+    ($method_to_call, $curried_arguments) = @$method_to_call
+        if 'ARRAY' eq ref($method_to_call);
+
     return $self->delegation_metaclass->new(
         name               => $handle_name,
         package_name       => $self->associated_class->name,
         attribute          => $self,
         delegate_to_method => $method_to_call,
+        curried_arguments  => $curried_arguments || [],
     );
 }
 
index 2b1900a..5e64190 100644 (file)
@@ -36,6 +36,13 @@ sub new {
         || confess
         'You must supply a delegate_to_method which is a method name or a CODE reference';
 
+    exists $options{curried_arguments}
+        || ( $options{curried_arguments} = [] );
+
+    ( $options{curried_arguments} &&
+        ( 'ARRAY' eq ref $options{curried_arguments} ) )
+        || confess 'You must supply a curried_arguments which is an ARRAY reference';
+
     my $self = $class->_new( \%options );
 
     weaken( $self->{'attribute'} );
@@ -52,6 +59,8 @@ sub _new {
     return bless $options, $class;
 }
 
+sub curried_arguments { (shift)->{'curried_arguments'} }
+
 sub associated_attribute { (shift)->{'attribute'} }
 
 sub delegate_to_method { (shift)->{'delegate_to_method'} }
@@ -88,7 +97,8 @@ sub _initialize_body {
             method_name => $method_to_call,
             object      => $instance
             );
-        $proxy->$method_to_call(@_);
+        my @args = (@{ $self->curried_arguments }, @_);
+        $proxy->$method_to_call(@args);
     };
 }
 
@@ -137,12 +147,21 @@ accessor is being generated for. This options is B<required>.
 The method in the associated attribute's value to which we
 delegate. This can be either a method name or a code reference.
 
+=item I<curried_arguments>
+
+An array reference of arguments that will be prepended to the argument list for
+any call to the delegating method.
+
 =back
 
 =item B<< $metamethod->associated_attribute >>
 
 Returns the attribute associated with this method.
 
+=item B<< $metamethod->curried_arguments >>
+
+Return any curried arguments that will be passed to the delegated method.
+
 =item B<< $metamethod->delegate_to_method >>
 
 Returns the method to which this method delegates, as passed to the
index 45505a1..7e44c45 100644 (file)
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use Test::More tests => 88;
+use Test::More tests => 89;
 use Test::Exception;
 
 
@@ -27,7 +27,10 @@ use Test::Exception;
     has 'foo' => (
         is      => 'rw',
         default => sub { Foo->new },
-        handles => { 'foo_bar' => 'bar' }
+        handles => {
+            'foo_bar' => 'bar',
+            'foo_bar_to_20' => [ bar => [ 20 ] ],
+        }
     );
 }
 
@@ -81,6 +84,10 @@ is($bar->foo, $foo, '... assigned bar->foo with the new Foo');
 is($bar->foo->bar, 25, '... bar->foo->bar returned the right result');
 is($bar->foo_bar, 25, '... and bar->foo_bar delegated correctly again');
 
+# curried handles
+$bar->foo_bar_to_20;
+is($bar->foo_bar, 20, '... correctly curried a single argument');
+
 # -------------------------------------------------------------------
 # ARRAY handles
 # -------------------------------------------------------------------
diff --git a/t/070_attribute_helpers/000_load.t b/t/070_attribute_helpers/000_load.t
new file mode 100644 (file)
index 0000000..d876957
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 1;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
\ No newline at end of file
diff --git a/t/070_attribute_helpers/010_array_from_role.t b/t/070_attribute_helpers/010_array_from_role.t
new file mode 100644 (file)
index 0000000..b72ec51
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Foo;
+    use Moose;
+
+    has 'bar' => (is => 'rw');
+
+    package Stuffed::Role;
+    use Moose::Role;
+
+    has 'options' => (
+        traits    => [ 'Collection::Array' ],
+        is        => 'ro',
+        isa       => 'ArrayRef[Foo]',
+    );
+
+    package Bulkie::Role;
+    use Moose::Role;
+
+    has 'stuff' => (
+        traits    => [ 'Collection::Array' ],
+        is        => 'ro',
+        isa       => 'ArrayRef',
+        handles   => {
+            get_stuff => 'get',
+        }
+    );
+
+    package Stuff;
+    use Moose;
+
+    ::lives_ok {
+        with 'Stuffed::Role';
+    } '... this should work correctly';
+
+    ::lives_ok {
+        with 'Bulkie::Role';
+    } '... this should work correctly';
+
+}
diff --git a/t/070_attribute_helpers/011_counter_with_defaults.t b/t/070_attribute_helpers/011_counter_with_defaults.t
new file mode 100644 (file)
index 0000000..09fc69a
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 14;
+use Test::Moose;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'counter' => (traits => ['Counter']);
+}
+
+my $page = MyHomePage->new();
+isa_ok($page, 'MyHomePage');
+
+can_ok($page, $_) for qw[
+    dec_counter
+    inc_counter
+    reset_counter
+];
+
+is($page->counter, 0, '... got the default value');
+
+$page->inc_counter;
+is($page->counter, 1, '... got the incremented value');
+
+$page->inc_counter;
+is($page->counter, 2, '... got the incremented value (again)');
+
+$page->dec_counter;
+is($page->counter, 1, '... got the decremented value');
+
+$page->reset_counter;
+is($page->counter, 0, '... got the original value');
+
+# check the meta ..
+
+my $counter = $page->meta->get_attribute('counter');
+does_ok($counter, 'Moose::AttributeHelpers::Trait::Counter');
+
+is($counter->helper_type, 'Num', '... got the expected helper type');
+
+is($counter->type_constraint->name, 'Num', '... got the expected default type constraint');
+
+is_deeply($counter->handles, {
+    'inc_counter'   => 'inc',
+    'dec_counter'   => 'dec',
+    'reset_counter' => 'reset',
+    'set_counter'   => 'set',
+}, '... got the right default handles methods');
+
diff --git a/t/070_attribute_helpers/020_remove_attribute.t b/t/070_attribute_helpers/020_remove_attribute.t
new file mode 100644 (file)
index 0000000..08f4662
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 12;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'counter' => (
+        traits    => [ 'Counter' ],
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 0 },
+        handles   => {
+            inc_counter   => 'inc',
+            dec_counter   => 'dec',
+            reset_counter => 'reset',
+        }
+    );
+}
+
+my $page = MyHomePage->new();
+isa_ok($page, 'MyHomePage');
+
+can_ok($page, $_) for qw[
+    counter
+    dec_counter
+    inc_counter
+    reset_counter
+];
+
+lives_ok {
+    $page->meta->remove_attribute('counter')
+} '... removed the counter attribute okay';
+
+ok(!$page->meta->has_attribute('counter'), '... no longer has the attribute');
+
+ok(!$page->can($_), "... our class no longer has the $_ method") for qw[
+    counter
+    dec_counter
+    inc_counter
+    reset_counter
+];
+
+
+
diff --git a/t/070_attribute_helpers/100_collection_with_roles.t b/t/070_attribute_helpers/100_collection_with_roles.t
new file mode 100644 (file)
index 0000000..93b05cf
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 29;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+package Subject;
+
+use Moose::Role;
+use Moose::AttributeHelpers;
+
+has observers => (
+    traits     => [ 'Collection::Array' ],
+    is         => 'ro',
+    isa        => 'ArrayRef[Observer]',
+    auto_deref => 1,
+    default    => sub { [] },
+    handles    => {
+        'add_observer'    => 'push',
+        'count_observers' => 'count',
+    },
+);
+
+sub notify {
+    my ($self) = @_;
+    foreach my $observer ( $self->observers() ) {
+        $observer->update($self);
+    }
+}
+
+###############################################################################
+
+package Observer;
+
+use Moose::Role;
+
+requires 'update';
+
+###############################################################################
+
+package Counter;
+
+use Moose;
+use Moose::AttributeHelpers;
+
+with 'Subject';
+
+has count => (
+    traits    => [ 'Counter' ],
+    is        => 'ro',
+    isa       => 'Int',
+    default   => 0,
+    handles  => {
+        inc_counter => 'inc',
+        dec_counter => 'dec',
+    },
+);
+
+after qw(inc_counter dec_counter) => sub {
+    my ($self) = @_;
+    $self->notify();
+};
+
+###############################################################################
+
+package Display;
+
+use Test::More;
+
+use Moose;
+
+with 'Observer';
+
+sub update {
+    my ( $self, $subject ) = @_;
+    like $subject->count, qr{^-?\d+$}, 'Observed number ' . $subject->count;
+}
+
+###############################################################################
+
+package main;
+
+my $count = Counter->new();
+
+ok($count->can('add_observer'), 'add_observer method added');
+
+ok($count->can('count_observers'), 'count_observers method added');
+
+ok($count->can('inc_counter'), 'inc_counter method added');
+
+ok($count->can('dec_counter'), 'dec_counter method added');
+
+$count->add_observer( Display->new() );
+
+is($count->count_observers, 1, 'Only one observer');
+
+is($count->count, 0, 'Default to zero');
+
+$count->inc_counter;
+
+is($count->count, 1, 'Increment to one ');
+
+$count->inc_counter for (1 .. 6);
+
+is($count->count, 7, 'Increment up to seven');
+
+$count->dec_counter;
+
+is($count->count, 6, 'Decrement to 6');
+
+$count->dec_counter for (1 .. 5);
+
+is($count->count, 1, 'Decrement to 1');
+
+$count->dec_counter for (1 .. 2);
+
+is($count->count, -1, 'Negative numbers');
+
+$count->inc_counter;
+
+is($count->count, 0, 'Back to zero');
diff --git a/t/070_attribute_helpers/201_trait_counter.t b/t/070_attribute_helpers/201_trait_counter.t
new file mode 100644 (file)
index 0000000..0fa544f
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 18;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'counter' => (
+        traits    => [qw/Counter/],
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 0 },
+        handles  => {
+            inc_counter   => 'inc',
+            dec_counter   => 'dec',
+            reset_counter => 'reset',
+            set_counter   => 'set'
+        }
+    );
+}
+
+my $page = MyHomePage->new();
+isa_ok($page, 'MyHomePage');
+
+can_ok($page, $_) for qw[
+    dec_counter
+    inc_counter
+    reset_counter
+    set_counter
+];
+
+is($page->counter, 0, '... got the default value');
+
+$page->inc_counter;
+is($page->counter, 1, '... got the incremented value');
+
+$page->inc_counter;
+is($page->counter, 2, '... got the incremented value (again)');
+
+$page->dec_counter;
+is($page->counter, 1, '... got the decremented value');
+
+$page->reset_counter;
+is($page->counter, 0, '... got the original value');
+
+$page->set_counter(5);
+is($page->counter, 5, '... set the value');
+
+$page->inc_counter(2);
+is($page->counter, 7, '... increment by arg');
+
+$page->dec_counter(5);
+is($page->counter, 2, '... decrement by arg');
+
+# check the meta ..
+
+my $counter = $page->meta->get_attribute('counter');
+does_ok($counter, 'Moose::AttributeHelpers::Trait::Counter');
+
+is($counter->helper_type, 'Num', '... got the expected helper type');
+
+is($counter->type_constraint->name, 'Int', '... got the expected type constraint');
+
+is_deeply($counter->handles, {
+    inc_counter   => 'inc',
+    dec_counter   => 'dec',
+    reset_counter => 'reset',
+    set_counter   => 'set'
+}, '... got the right handles methods');
+
diff --git a/t/070_attribute_helpers/202_trait_array.t b/t/070_attribute_helpers/202_trait_array.t
new file mode 100644 (file)
index 0000000..e4d7fc5
--- /dev/null
@@ -0,0 +1,248 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 69;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+my $sort;
+{
+    package Stuff;
+    use Moose;
+
+    has 'options' => (
+        traits    => [qw/Collection::Array/],
+        is        => 'ro',
+        isa       => 'ArrayRef[Str]',
+        default   => sub { [] },
+        handles => {
+            'add_options'           => 'push',
+            'remove_last_option'    => 'pop',
+            'remove_first_option'   => 'shift',
+            'insert_options'        => 'unshift',
+            'get_option_at'         => 'get',
+            'set_option_at'         => 'set',
+            'num_options'           => 'count',
+            'has_options'           => 'empty',
+            'clear_options'         => 'clear',
+            'splice_options'        => 'splice',
+            'sort_options_in_place' => 'sort_in_place',
+            'option_accessor'       => 'accessor',
+            'add_options_with_speed' =>
+               [ 'push' => ['funrolls', 'funbuns'] ],
+            'prepend_prerequisites_along_with' =>
+               [ 'unshift' => ['first', 'second'] ],
+            'descending_options' =>
+               [ 'sort_in_place' => [ $sort = sub { $_[1] <=> $_[0] } ] ],
+        }
+    );
+}
+
+my $stuff = Stuff->new(options => [ 10, 12 ]);
+isa_ok($stuff, 'Stuff');
+
+can_ok($stuff, $_) for qw[
+    add_options
+    remove_last_option
+    remove_first_option
+    insert_options
+    get_option_at
+    set_option_at
+    num_options
+    clear_options
+    has_options
+    sort_options_in_place
+    option_accessor
+];
+
+is_deeply($stuff->options, [10, 12], '... got options');
+
+ok($stuff->has_options, '... we have options');
+is($stuff->num_options, 2, '... got 2 options');
+
+is($stuff->remove_last_option, 12, '... removed the last option');
+is($stuff->remove_first_option, 10, '... removed the last option');
+
+is_deeply($stuff->options, [], '... no options anymore');
+
+ok(!$stuff->has_options, '... no options');
+is($stuff->num_options, 0, '... got no options');
+
+lives_ok {
+    $stuff->add_options(1, 2, 3);
+} '... set the option okay';
+
+is_deeply($stuff->options, [1, 2, 3], '... got options now');
+
+ok($stuff->has_options, '... no options');
+is($stuff->num_options, 3, '... got 3 options');
+
+is($stuff->get_option_at(0), 1, '... get option at index 0');
+is($stuff->get_option_at(1), 2, '... get option at index 1');
+is($stuff->get_option_at(2), 3, '... get option at index 2');
+
+lives_ok {
+    $stuff->set_option_at(1, 100);
+} '... set the option okay';
+
+is($stuff->get_option_at(1), 100, '... get option at index 1');
+
+lives_ok {
+    $stuff->add_options(10, 15);
+} '... set the option okay';
+
+is_deeply($stuff->options, [1, 100, 3, 10, 15], '... got more options now');
+
+is($stuff->num_options, 5, '... got 5 options');
+
+is($stuff->remove_last_option, 15, '... removed the last option');
+
+is($stuff->num_options, 4, '... got 4 options');
+is_deeply($stuff->options, [1, 100, 3, 10], '... got diff options now');
+
+lives_ok {
+    $stuff->insert_options(10, 20);
+} '... set the option okay';
+
+is($stuff->num_options, 6, '... got 6 options');
+is_deeply($stuff->options, [10, 20, 1, 100, 3, 10], '... got diff options now');
+
+is($stuff->get_option_at(0), 10, '... get option at index 0');
+is($stuff->get_option_at(1), 20, '... get option at index 1');
+is($stuff->get_option_at(3), 100, '... get option at index 3');
+
+is($stuff->remove_first_option, 10, '... getting the first option');
+
+is($stuff->num_options, 5, '... got 5 options');
+is($stuff->get_option_at(0), 20, '... get option at index 0');
+
+$stuff->clear_options;
+is_deeply( $stuff->options, [], "... clear options" );
+
+$stuff->add_options(5, 1, 2, 3);
+$stuff->sort_options_in_place;
+is_deeply( $stuff->options, [1, 2, 3, 5], "... sort options in place (default sort order)" );
+
+$stuff->sort_options_in_place( sub { $_[1] <=> $_[0] } );
+is_deeply( $stuff->options, [5, 3, 2, 1], "... sort options in place (descending order)" );
+
+$stuff->clear_options();
+$stuff->add_options(5, 1, 2, 3);
+lives_ok {
+   $stuff->descending_options();
+} '... curried sort in place lives ok';
+
+is_deeply( $stuff->options, [5, 3, 2, 1], "... sort currying" );
+
+throws_ok { $stuff->sort_options_in_place('foo') } qr/Argument must be a code reference/,
+    'error when sort_in_place receives a non-coderef argument';
+
+$stuff->clear_options;
+
+lives_ok {
+    $stuff->add_options('tree');
+} '... set the options okay';
+
+lives_ok {
+    $stuff->add_options_with_speed('compatible', 'safe');
+} '... add options with speed okay';
+
+is_deeply($stuff->options, [qw/tree funrolls funbuns compatible safe/],
+          'check options after add_options_with_speed');
+
+lives_ok {
+    $stuff->prepend_prerequisites_along_with();
+} '... add prerequisite options okay';
+
+$stuff->clear_options;
+$stuff->add_options( 1, 2 );
+
+lives_ok {
+    $stuff->splice_options( 1, 0, 'foo' );
+} '... splice_options works';
+
+is_deeply(
+    $stuff->options, [ 1, 'foo', 2 ],
+    'splice added expected option'
+);
+
+is($stuff->option_accessor(1 => 'foo++'), 'foo++');
+is($stuff->option_accessor(1), 'foo++');
+
+## check some errors
+
+#dies_ok {
+#    $stuff->insert_options(undef);
+#} '... could not add an undef where a string is expected';
+#
+#dies_ok {
+#    $stuff->set_option(5, {});
+#} '... could not add a hash ref where a string is expected';
+
+dies_ok {
+    Stuff->new(options => [ undef, 10, undef, 20 ]);
+} '... bad constructor params';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->add_options(undef);
+} '... rejects push of an invalid type';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->insert_options(undef);
+} '... rejects unshift of an invalid type';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->set_option_at( 0, undef );
+} '... rejects set of an invalid type';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->sort_in_place_options( undef );
+} '... sort rejects arg of invalid type';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->option_accessor();
+} '... accessor rejects 0 args';
+
+dies_ok {
+    my $stuff = Stuff->new();
+    $stuff->option_accessor(1, 2, 3);
+} '... accessor rejects 3 args';
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('options');
+does_ok($options, 'Moose::AttributeHelpers::Trait::Collection::Array');
+
+is_deeply($options->handles, {
+    'add_options'           => 'push',
+    'remove_last_option'    => 'pop',
+    'remove_first_option'   => 'shift',
+    'insert_options'        => 'unshift',
+    'get_option_at'         => 'get',
+    'set_option_at'         => 'set',
+    'num_options'           => 'count',
+    'has_options'           => 'empty',
+    'clear_options'         => 'clear',
+    'splice_options'        => 'splice',
+    'sort_options_in_place' => 'sort_in_place',
+    'option_accessor'       => 'accessor',
+    'add_options_with_speed' =>
+       [ 'push' => ['funrolls', 'funbuns'] ],
+    'prepend_prerequisites_along_with' =>
+       [ 'unshift' => ['first', 'second'] ],
+    'descending_options' =>
+       [ 'sort_in_place' => [ $sort ] ],
+}, '... got the right handles mapping');
+
+is($options->type_constraint->type_parameter, 'Str', '... got the right container type');
diff --git a/t/070_attribute_helpers/203_trait_hash.t b/t/070_attribute_helpers/203_trait_hash.t
new file mode 100644 (file)
index 0000000..b6d8553
--- /dev/null
@@ -0,0 +1,172 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 47;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+    use Moose::AttributeHelpers;
+
+    has 'options' => (
+        traits    => [qw/Collection::Hash/],
+        is        => 'ro',
+        isa       => 'HashRef[Str]',
+        default   => sub { {} },
+        handles  => {
+            'set_option'       => 'set',
+            'get_option'       => 'get',
+            'has_options'      => 'empty',
+            'num_options'      => 'count',
+            'clear_options'    => 'clear',
+            'delete_option'    => 'delete',
+            'has_option'       => 'exists',
+            'is_defined'       => 'defined',
+            'option_accessor'  => 'accessor',
+            'key_value'        => 'kv',
+            'options_elements' => 'elements',
+            'quantity' => [ accessor => ['quantity'] ],
+        },
+    );
+}
+
+my $stuff = Stuff->new();
+isa_ok($stuff, 'Stuff');
+
+can_ok($stuff, $_) for qw[
+    set_option
+    get_option
+    has_options
+    num_options
+    delete_option
+    clear_options
+    is_defined
+    has_option
+    quantity
+    option_accessor
+];
+
+ok(!$stuff->has_options, '... we have no options');
+is($stuff->num_options, 0, '... we have no options');
+
+is_deeply($stuff->options, {}, '... no options yet');
+ok(!$stuff->has_option('foo'), '... we have no foo option');
+
+lives_ok {
+    $stuff->set_option(foo => 'bar');
+} '... set the option okay';
+
+ok($stuff->is_defined('foo'), '... foo is defined');
+
+ok($stuff->has_options, '... we have options');
+is($stuff->num_options, 1, '... we have 1 option(s)');
+ok($stuff->has_option('foo'), '... we have a foo option');
+is_deeply($stuff->options, { foo => 'bar' }, '... got options now');
+
+lives_ok {
+    $stuff->set_option(bar => 'baz');
+} '... set the option okay';
+
+is($stuff->num_options, 2, '... we have 2 option(s)');
+is_deeply($stuff->options, { foo => 'bar', bar => 'baz' }, '... got more options now');
+
+is($stuff->get_option('foo'), 'bar', '... got the right option');
+
+is_deeply([ $stuff->get_option(qw(foo bar)) ], [qw(bar baz)], "get multiple options at once");
+
+lives_ok {
+    $stuff->set_option(oink => "blah", xxy => "flop");
+} '... set the option okay';
+
+is($stuff->num_options, 4, "4 options");
+is_deeply([ $stuff->get_option(qw(foo bar oink xxy)) ], [qw(bar baz blah flop)], "get multiple options at once");
+
+lives_ok {
+    $stuff->delete_option('bar');
+} '... deleted the option okay';
+
+lives_ok {
+    $stuff->delete_option('oink');
+} '... deleted the option okay';
+
+lives_ok {
+    $stuff->delete_option('xxy');
+} '... deleted the option okay';
+
+is($stuff->num_options, 1, '... we have 1 option(s)');
+is_deeply($stuff->options, { foo => 'bar' }, '... got more options now');
+
+$stuff->clear_options;
+
+is_deeply($stuff->options, { }, "... cleared options" );
+
+lives_ok {
+    $stuff->quantity(4);
+} '... options added okay with defaults';
+
+is($stuff->quantity, 4, 'reader part of curried accessor works');
+
+is_deeply($stuff->options, {quantity => 4}, '... returns what we expect');
+
+lives_ok {
+    Stuff->new(options => { foo => 'BAR' });
+} '... good constructor params';
+
+## check some errors
+
+dies_ok {
+    $stuff->set_option(bar => {});
+} '... could not add a hash ref where an string is expected';
+
+dies_ok {
+    Stuff->new(options => { foo => [] });
+} '... bad constructor params';
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('options');
+does_ok($options, 'Moose::AttributeHelpers::Trait::Collection::Hash');
+
+is_deeply($options->handles, {
+    'set_option'       => 'set',
+    'get_option'       => 'get',
+    'has_options'      => 'empty',
+    'num_options'      => 'count',
+    'clear_options'    => 'clear',
+    'delete_option'    => 'delete',
+    'has_option'       => 'exists',
+    'is_defined'       => 'defined',
+    'option_accessor'  => 'accessor',
+    'key_value'        => 'kv',
+    'options_elements' => 'elements',
+    'quantity' => [ accessor => ['quantity'] ],
+}, '... got the right handles mapping');
+
+is($options->type_constraint->type_parameter, 'Str', '... got the right container type');
+
+$stuff->set_option( oink => "blah", xxy => "flop" );
+my @key_value = $stuff->key_value;
+is_deeply(
+    \@key_value,
+    [ [ 'xxy', 'flop' ], [ 'quantity', 4 ], [ 'oink', 'blah' ] ],
+    '... got the right key value pairs'
+);
+
+my %options_elements = $stuff->options_elements;
+is_deeply(
+    \%options_elements,
+    {
+        'oink'     => 'blah',
+        'quantity' => 4,
+        'xxy'      => 'flop'
+    },
+    '... got the right hash elements'
+);
diff --git a/t/070_attribute_helpers/204_trait_number.t b/t/070_attribute_helpers/204_trait_number.t
new file mode 100644 (file)
index 0000000..4ad077c
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 26;
+use Test::Moose;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Real;
+    use Moose;
+
+    has 'integer' => (
+        traits    => [qw/Number/],
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 5 },
+        handles  => {
+            set       => 'set',
+            add       => 'add',
+            sub       => 'sub',
+            mul       => 'mul',
+            div       => 'div',
+            mod       => 'mod',
+            abs       => 'abs',
+            inc         => [ add => [ 1 ] ],
+            dec         => [ sub => [ 1 ] ],
+            odd         => [ mod => [ 2 ] ],
+            cut_in_half => [ div => [ 2 ] ],
+
+        },
+    );
+}
+
+my $real = Real->new;
+isa_ok($real, 'Real');
+
+can_ok($real, $_) for qw[
+    set add sub mul div mod abs inc dec odd cut_in_half
+];
+
+is $real->integer, 5, 'Default to five';
+
+$real->add(10);
+
+is $real->integer, 15, 'Add ten for fithteen';
+
+$real->sub(3);
+
+is $real->integer, 12, 'Subtract three for 12';
+
+$real->set(10);
+
+is $real->integer, 10, 'Set to ten';
+
+$real->div(2);
+
+is $real->integer, 5, 'divide by 2';
+
+$real->mul(2);
+
+is $real->integer, 10, 'multiplied by 2';
+
+$real->mod(2);
+
+is $real->integer, 0, 'Mod by 2';
+
+$real->set(7);
+
+$real->mod(5);
+
+is $real->integer, 2, 'Mod by 5';
+
+$real->set(-1);
+
+$real->abs;
+
+is $real->integer, 1, 'abs 1';
+
+$real->set(12);
+
+$real->inc;
+
+is $real->integer, 13, 'inc 12';
+
+$real->dec;
+
+is $real->integer, 12, 'dec 13';
+
+## test the meta
+
+my $attr = $real->meta->get_attribute('integer');
+does_ok($attr, 'Moose::AttributeHelpers::Trait::Number');
+
+is_deeply($attr->handles, {
+    set => 'set',
+    add => 'add',
+    sub => 'sub',
+    mul => 'mul',
+    div => 'div',
+    mod => 'mod',
+    abs => 'abs',
+    inc         => [ add => [ 1 ] ],
+    dec         => [ sub => [ 1 ] ],
+    odd         => [ mod => [ 2 ] ],
+    cut_in_half => [ div => [ 2 ] ],
+}, '... got the right handles mapping');
+
diff --git a/t/070_attribute_helpers/205_trait_list.t b/t/070_attribute_helpers/205_trait_list.t
new file mode 100644 (file)
index 0000000..c126b74
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 34;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+my $sort;
+my $less;
+my $up;
+{
+    package Stuff;
+    use Moose;
+
+    has '_options' => (
+        traits    => [qw/Collection::List/],
+        is        => 'ro',
+        isa       => 'ArrayRef[Int]',
+        init_arg  => 'options',
+        default   => sub { [] },
+        handles  => {
+            'num_options'      => 'count',
+            'has_options'      => 'empty',
+            'map_options',     => 'map',
+            'filter_options'   => 'grep',
+            'find_option'      => 'find',
+            'options'          => 'elements',
+            'join_options'     => 'join',
+            'get_option_at'    => 'get',
+            'get_first_option' => 'first',
+            'get_last_option'  => 'last',
+            'sorted_options'   => 'sort',
+            'less_than_five' => [ grep => [ $less = sub { $_ < 5 } ] ],
+            'up_by_one'      => [ map  => [ $up   = sub { $_ + 1 } ] ],
+            'dashify'        => [ join => [ '-'            ] ],
+            'descending'     => [ sort => [ $sort = sub { $_[1] <=> $_[0] } ] ],
+        },
+    );
+
+}
+
+my $stuff = Stuff->new(options => [ 1 .. 10 ]);
+isa_ok($stuff, 'Stuff');
+
+can_ok($stuff, $_) for qw[
+    _options
+    num_options
+    has_options
+    map_options
+    filter_options
+    find_option
+    options
+    join_options
+    get_option_at
+    sorted_options
+];
+
+is_deeply($stuff->_options, [1 .. 10], '... got options');
+
+ok($stuff->has_options, '... we have options');
+is($stuff->num_options, 10, '... got 2 options');
+cmp_ok($stuff->get_option_at(0), '==', 1, '... get option 0');
+cmp_ok($stuff->get_first_option, '==', 1, '... get first');
+cmp_ok($stuff->get_last_option, '==', 10, '... get last');
+
+is_deeply(
+[ $stuff->filter_options(sub { $_[0] % 2 == 0 }) ],
+[ 2, 4, 6, 8, 10 ],
+'... got the right filtered values'
+);
+
+is_deeply(
+[ $stuff->map_options(sub { $_[0] * 2 }) ],
+[ 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ],
+'... got the right mapped values'
+);
+
+is($stuff->find_option(sub { $_[0] % 2 == 0 }), 2, '.. found the right option');
+
+is_deeply([ $stuff->options ], [1 .. 10], '... got the list of options');
+
+is($stuff->join_options(':'), '1:2:3:4:5:6:7:8:9:10', '... joined the list of options by :');
+
+is_deeply([ $stuff->sorted_options ], [sort (1..10)],
+          '... got sorted options (default sort order)');
+is_deeply([ $stuff->sorted_options( sub { $_[1] <=> $_[0] } ) ], [sort { $b <=> $a } (1..10)],
+          '... got sorted options (descending sort order) ');
+
+throws_ok { $stuff->sorted_options('foo') } qr/Argument must be a code reference/,
+    'error when sort receives a non-coderef argument';
+
+# test the currying
+is_deeply([ $stuff->less_than_five() ], [1 .. 4]);
+
+is_deeply([ $stuff->up_by_one() ], [2 .. 11]);
+
+is($stuff->dashify, '1-2-3-4-5-6-7-8-9-10');
+
+is_deeply([$stuff->descending], [reverse 1 .. 10]);
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('_options');
+does_ok($options, 'Moose::AttributeHelpers::Trait::Collection::List');
+
+is_deeply($options->handles, {
+    'num_options'      => 'count',
+    'has_options'      => 'empty',
+    'map_options',     => 'map',
+    'filter_options'   => 'grep',
+    'find_option'      => 'find',
+    'options'          => 'elements',
+    'join_options'     => 'join',
+    'get_option_at'    => 'get',
+    'get_first_option' => 'first',
+    'get_last_option'  => 'last',
+    'sorted_options'   => 'sort',
+    'less_than_five' => [ grep => [ $less ] ],
+    'up_by_one'      => [ map  => [ $up ] ],
+    'dashify'        => [ join => [ '-'            ] ],
+    'descending'     => [ sort => [ $sort ] ],
+}, '... got the right handles mapping');
+
+is($options->type_constraint->type_parameter, 'Int', '... got the right container type');
+
+dies_ok {
+    $stuff->sort_in_place_options( undef );
+} '... sort rejects arg of invalid type';
+
diff --git a/t/070_attribute_helpers/206_trait_bag.t b/t/070_attribute_helpers/206_trait_bag.t
new file mode 100644 (file)
index 0000000..fba4959
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 20;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+    use Moose::AttributeHelpers;
+
+    has 'word_histogram' => (
+        traits    => [qw/Collection::Bag/],
+        is        => 'ro',
+        handles  => {
+           'add_word'      => 'add',
+           'get_count_for' => 'get',
+           'has_any_words' => 'empty', 
+           'num_words'     => 'count',
+           'delete_word'   => 'delete',
+        }
+    );
+}
+
+my $stuff = Stuff->new();
+isa_ok($stuff, 'Stuff');
+
+can_ok($stuff, $_) for qw[
+    add_word
+    get_count_for
+    has_any_words
+    num_words
+    delete_word
+];
+
+ok(!$stuff->has_any_words, '... we have no words');
+is($stuff->num_words, 0, '... we have no words');
+
+lives_ok {
+    $stuff->add_word('bar');
+} '... set the words okay';
+
+ok($stuff->has_any_words, '... we have words');
+is($stuff->num_words, 1, '... we have 1 word(s)');
+is($stuff->get_count_for('bar'), 1, '... got words now');
+
+lives_ok {
+    $stuff->add_word('foo');
+    $stuff->add_word('bar') for 0 .. 3;
+    $stuff->add_word('baz') for 0 .. 10;
+} '... set the words okay';
+
+is($stuff->num_words, 3, '... we still have 1 word(s)');
+is($stuff->get_count_for('foo'), 1, '... got words now');
+is($stuff->get_count_for('bar'), 5, '... got words now');
+is($stuff->get_count_for('baz'), 11, '... got words now');
+
+## test the meta
+
+my $words = $stuff->meta->get_attribute('word_histogram');
+does_ok($words, 'Moose::AttributeHelpers::Trait::Collection::Bag');
+
+is_deeply($words->handles, {
+   'add_word'      => 'add',
+   'get_count_for' => 'get',
+   'has_any_words' => 'empty', 
+   'num_words'     => 'count',
+   'delete_word'   => 'delete',
+}, '... got the right handles mapping');
+
diff --git a/t/070_attribute_helpers/207_trait_string.t b/t/070_attribute_helpers/207_trait_string.t
new file mode 100644 (file)
index 0000000..69646dc
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 21;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+my $uc;
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'string' => (
+        traits    => [qw/String/],
+        is        => 'rw',
+        isa       => 'Str',
+        default   => sub { '' },
+        handles => {
+                    inc_string     => 'inc',
+                    append_string  => 'append',
+                    prepend_string => 'prepend',
+                    match_string   => 'match',
+                    replace_string => 'replace',
+                    chop_string    => 'chop',
+                    chomp_string   => 'chomp',
+                    clear_string   => 'clear',
+                    exclaim         => [ append  => [ '!' ] ],
+                    capitalize_last => [ replace => [ qr/(.)$/, $uc = sub { uc $1 } ] ],
+                    invalid_number  => [ match   => [ qr/\D/ ] ],
+                   },
+    );
+}
+
+my $page = MyHomePage->new();
+isa_ok($page, 'MyHomePage');
+
+is($page->string, '', '... got the default value');
+
+$page->string('a');
+
+$page->inc_string;
+is($page->string, 'b', '... got the incremented value');
+
+$page->inc_string;
+is($page->string, 'c', '... got the incremented value (again)');
+
+$page->append_string("foo$/");
+is($page->string, "cfoo$/", 'appended to string');
+
+$page->chomp_string;
+is($page->string, "cfoo", 'chomped string');
+
+$page->chomp_string;
+is($page->string, "cfoo", 'chomped is noop');
+
+$page->chop_string;
+is($page->string, "cfo", 'chopped string');
+
+$page->prepend_string("bar");
+is($page->string, 'barcfo', 'prepended to string');
+
+is_deeply( [ $page->match_string(qr/([ao])/) ], [ "a" ], "match" );
+
+$page->replace_string(qr/([ao])/, sub { uc($1) });
+is($page->string, 'bArcfo', "substitution");
+
+$page->exclaim;
+is($page->string, 'bArcfo!', 'exclaim!');
+
+$page->string('Moosex');
+$page->capitalize_last;
+is($page->string, 'MooseX', 'capitalize last');
+
+$page->string('1234');
+ok(!$page->invalid_number, 'string "isn\'t an invalid number');
+
+$page->string('one two three four');
+ok($page->invalid_number, 'string an invalid number');
+
+$page->clear_string;
+is($page->string, '', "clear");
+
+# check the meta ..
+
+my $string = $page->meta->get_attribute('string');
+does_ok($string, 'Moose::AttributeHelpers::Trait::String');
+
+is($string->helper_type, 'Str', '... got the expected helper type');
+
+is($string->type_constraint->name, 'Str', '... got the expected type constraint');
+
+is_deeply($string->handles, {
+    inc_string     => 'inc',
+    append_string  => 'append',
+    prepend_string => 'prepend',
+    match_string   => 'match',
+    replace_string => 'replace',
+    chop_string    => 'chop',
+    chomp_string   => 'chomp',
+    clear_string   => 'clear',
+    exclaim         => [ append  => [ '!' ] ],
+    capitalize_last => [ replace => [ qr/(.)$/, $uc ] ],
+    invalid_number  => [ match   => [ qr/\D/ ] ],
+}, '... got the right handles methods');
+
diff --git a/t/070_attribute_helpers/208_trait_bool.t b/t/070_attribute_helpers/208_trait_bool.t
new file mode 100644 (file)
index 0000000..67df111
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 8;
+use Moose::AttributeHelpers;
+
+{
+    package Room;
+    use Moose;
+    has 'is_lit' => (
+        traits    => ['Bool'],
+        is        => 'rw',
+        isa       => 'Bool',
+        default   => sub { 0 },
+        handles  => {
+                     illuminate => 'set',
+                     darken => 'unset',
+                     flip_switch => 'toggle',
+                     is_dark => 'not',
+                    },
+    )
+}
+
+my $room = Room->new;
+$room->illuminate;
+ok $room->is_lit, 'set is_lit to 1 using ->illuminate';
+ok !$room->is_dark, 'check if is_dark does the right thing';
+
+$room->darken;
+ok !$room->is_lit, 'set is_lit to 0 using ->darken';
+ok $room->is_dark, 'check if is_dark does the right thing';
+
+$room->flip_switch;
+ok $room->is_lit, 'toggle is_lit back to 1 using ->flip_switch';
+ok !$room->is_dark, 'check if is_dark does the right thing';
+
+$room->flip_switch;
+ok !$room->is_lit, 'toggle is_lit back to 0 again using ->flip_switch';
+ok $room->is_dark, 'check if is_dark does the right thing';
+
index 036638a..5851ce3 100644 (file)
@@ -24,7 +24,9 @@ chromatic's
 Debolaz
 Deltac
 dexter
+doy
 ewilhelm
+frodwith
 Goulah
 gphat
 groditi
@@ -34,7 +36,9 @@ Kinyon's
 Kogman
 kolibrie
 konobi
+Lanyon
 lbr
+Luehrs
 McWhirter
 merlyn
 mst
@@ -45,6 +49,7 @@ phaylon
 Prather
 Ragwitz
 Reis
+rafl
 rindolf
 rlb
 Rockway
@@ -113,6 +118,7 @@ Baz
 Changelog
 compat
 datetimes
+dec
 definedness
 destructor
 destructors
@@ -130,7 +136,9 @@ invocant's
 irc
 IRC
 isa
+kv
 login
+mul
 namespace
 namespaced
 namespaces