copy in and rename AttributeHelpers
Hans Dieter Pearcey [Thu, 25 Jun 2009 06:27:51 +0000 (02:27 -0400)]
52 files changed:
lib/Moose/AttributeHelpers.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Bool.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Collection/Array.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Collection/Bag.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Collection/Hash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Collection/ImmutableHash.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Collection/List.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Counter.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Meta/Method/Curried.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/Meta/Method/Provided.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/Number.pm [new file with mode: 0644]
lib/Moose/AttributeHelpers/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]
t/070_attribute_helpers/000_load.t [new file with mode: 0644]
t/070_attribute_helpers/001_basic_counter.t [new file with mode: 0644]
t/070_attribute_helpers/002_basic_array.t [new file with mode: 0644]
t/070_attribute_helpers/003_basic_hash.t [new file with mode: 0644]
t/070_attribute_helpers/004_basic_number.t [new file with mode: 0644]
t/070_attribute_helpers/005_basic_list.t [new file with mode: 0644]
t/070_attribute_helpers/006_basic_bag.t [new file with mode: 0644]
t/070_attribute_helpers/007_basic_string.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/012_basic_bool.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]

diff --git a/lib/Moose/AttributeHelpers.pm b/lib/Moose/AttributeHelpers.pm
new file mode 100644 (file)
index 0000000..7a9081c
--- /dev/null
@@ -0,0 +1,222 @@
+
+package Moose::AttributeHelpers;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Moose 0.56 ();
+
+use Moose::AttributeHelpers::Meta::Method::Provided;
+use Moose::AttributeHelpers::Meta::Method::Curried;
+
+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;
+
+use Moose::AttributeHelpers::Counter;
+use Moose::AttributeHelpers::Number;
+use Moose::AttributeHelpers::String;
+use Moose::AttributeHelpers::Bool;
+use Moose::AttributeHelpers::Collection::List;
+use Moose::AttributeHelpers::Collection::Array;
+use Moose::AttributeHelpers::Collection::Hash;
+use Moose::AttributeHelpers::Collection::ImmutableHash;
+use Moose::AttributeHelpers::Collection::Bag;
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers - Extend your attribute interfaces
+
+=head1 SYNOPSIS
+
+  package MyClass;
+  use Moose;
+  use Moose::AttributeHelpers;
+
+  has 'mapping' => (
+      metaclass => '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<metaclass> parameter. Available meta classes are:
+
+=head1 PARAMETERS
+
+=head2 handles
+
+This points to a hashref that uses C<method> for the keys and
+C<stuff> for the values.  The method will be added to
+the object itself and do what you want.
+
+=head2 curries
+
+This points to a hashref that uses C<provider> for the keys and
+has two choices for the value:
+
+You can supply C<< {method => [ @args ]} >> for the values.  The method will be
+added to the object itself (always using C<@args> as the beginning arguments).
+
+Another approach to curry a method provider is to supply a coderef instead of an
+arrayref. The code ref takes C<$self>, C<$body>, and any additional arguments
+passed to the final method.
+
+  # ...
+
+  curries => {
+      grep => {
+          times_with_day => sub {
+              my ($self, $body, $datetime) = @_;
+              $body->($self, sub { $_->ymd eq $datetime->ymd });
+          }
+      }
+  }
+
+  # ...
+
+  $obj->times_with_day(DateTime->now); # takes datetime argument, checks day
+
+
+=head1 METHOD PROVIDERS
+
+=over
+
+=item L<Number|Moose::AttributeHelpers::Number>
+
+Common numerical operations.
+
+=item L<String|Moose::AttributeHelpers::String>
+
+Common methods for string operations.
+
+=item L<Counter|Moose::AttributeHelpers::Counter>
+
+Methods for incrementing and decrementing a counter attribute.
+
+=item L<Bool|Moose::AttributeHelpers::Bool>
+
+Common methods for boolean values.
+
+=item L<Collection::Hash|Moose::AttributeHelpers::Collection::Hash>
+
+Common methods for hash references.
+
+=item L<Collection::ImmutableHash|Moose::AttributeHelpers::Collection::ImmutableHash>
+
+Common methods for inspecting hash references.
+
+=item L<Collection::Array|Moose::AttributeHelpers::Collection::Array>
+
+Common methods for array references.
+
+=item L<Collection::List|Moose::AttributeHelpers::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/Bool.pm b/lib/Moose/AttributeHelpers/Bool.pm
new file mode 100644 (file)
index 0000000..0212ec1
--- /dev/null
@@ -0,0 +1,70 @@
+package Moose::AttributeHelpers::Bool;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Bool';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Bool;
+sub register_implementation { 'Moose::AttributeHelpers::Bool' }
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Bool
+
+=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_provides>
+
+Run before its superclass method.
+
+=item B<check_provides_values>
+
+Run after its superclass method.
+
+=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/Collection/Array.pm b/lib/Moose/AttributeHelpers/Collection/Array.pm
new file mode 100644 (file)
index 0000000..fbb1ac7
--- /dev/null
@@ -0,0 +1,86 @@
+
+package Moose::AttributeHelpers::Collection::Array;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Collection::Array';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Collection::Array;
+sub register_implementation { 'Moose::AttributeHelpers::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 { [] },
+      provides  => {
+          'push' => 'add_options',
+          'pop'  => 'remove_last_option',
+      }
+  );
+
+=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/Collection/Bag.pm b/lib/Moose/AttributeHelpers/Collection/Bag.pm
new file mode 100644 (file)
index 0000000..b52f9c0
--- /dev/null
@@ -0,0 +1,89 @@
+
+package Moose::AttributeHelpers::Collection::Bag;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Collection::Bag';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Collection::Bag;
+sub register_implementation { 'Moose::AttributeHelpers::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
+      provides  => {
+          'add'    => 'add_word',
+          'get'    => 'get_count_for',
+          'empty'  => 'has_any_words',
+          'count'  => 'num_words',
+          'delete' => 'delete_word',
+      }
+  );
+
+=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_provides>
+
+=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/Collection/Hash.pm b/lib/Moose/AttributeHelpers/Collection/Hash.pm
new file mode 100644 (file)
index 0000000..2fe28b3
--- /dev/null
@@ -0,0 +1,89 @@
+
+package Moose::AttributeHelpers::Collection::Hash;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Collection::Hash';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Collection::Hash;
+sub register_implementation { 'Moose::AttributeHelpers::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 { {} },
+      provides  => {
+          'set'    => 'set_option',
+          'get'    => 'get_option',
+          'empty'  => 'has_options',
+          'count'  => 'num_options',
+          'delete' => 'delete_option',
+      }
+  );
+
+=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/Collection/ImmutableHash.pm b/lib/Moose/AttributeHelpers/Collection/ImmutableHash.pm
new file mode 100644 (file)
index 0000000..54c4cb4
--- /dev/null
@@ -0,0 +1,87 @@
+
+package Moose::AttributeHelpers::Collection::ImmutableHash;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Collection::ImmutableHash';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Collection::ImmutableHash;
+sub register_implementation { 'Moose::AttributeHelpers::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 { {} },
+      provides  => {
+          'get'    => 'get_option',
+          'empty'  => 'has_options',
+          'keys'   => 'get_option_list',
+      }
+  );
+
+=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/Collection/List.pm b/lib/Moose/AttributeHelpers/Collection/List.pm
new file mode 100644 (file)
index 0000000..29e93c6
--- /dev/null
@@ -0,0 +1,86 @@
+
+package Moose::AttributeHelpers::Collection::List;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Collection::List';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Collection::List;
+sub register_implementation { 'Moose::AttributeHelpers::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 { [] },
+      provides  => {
+          map  => 'map_options',
+          grep => 'filter_options',
+      }
+  );
+
+=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/Counter.pm b/lib/Moose/AttributeHelpers/Counter.pm
new file mode 100644 (file)
index 0000000..fb47f2e
--- /dev/null
@@ -0,0 +1,71 @@
+
+package Moose::AttributeHelpers::Counter;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Counter';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Counter;
+sub register_implementation { 'Moose::AttributeHelpers::Counter' }
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Counter
+
+=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_provides>
+
+Run before its superclass method.
+
+=item B<check_provides_values>
+
+Run after its superclass method.
+
+=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/Meta/Method/Curried.pm b/lib/Moose/AttributeHelpers/Meta/Method/Curried.pm
new file mode 100644 (file)
index 0000000..a576c28
--- /dev/null
@@ -0,0 +1,52 @@
+
+package Moose::AttributeHelpers::Meta::Method::Curried;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Method';
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Meta::Method::Curried
+
+=head1 DESCRIPTION
+
+This is an extension of Moose::Meta::Method to mark I<curried> methods.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=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/Meta/Method/Provided.pm b/lib/Moose/AttributeHelpers/Meta/Method/Provided.pm
new file mode 100644 (file)
index 0000000..2823836
--- /dev/null
@@ -0,0 +1,52 @@
+
+package Moose::AttributeHelpers::Meta::Method::Provided;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Method';
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Moose::AttributeHelpers::Meta::Method::Provided
+
+=head1 DESCRIPTION
+
+This is an extension of Moose::Meta::Method to mark I<provided> methods.
+
+=head1 METHODS
+
+=over 4
+
+=item B<meta>
+
+=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/Array.pm b/lib/Moose/AttributeHelpers/MethodProvider/Array.pm
new file mode 100644 (file)
index 0000000..7496ccb
--- /dev/null
@@ -0,0 +1,281 @@
+package Moose::AttributeHelpers::MethodProvider::Array;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 provied 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..78051d6
--- /dev/null
@@ -0,0 +1,97 @@
+package Moose::AttributeHelpers::MethodProvider::Bag;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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..9736d6f
--- /dev/null
@@ -0,0 +1,85 @@
+
+package Moose::AttributeHelpers::MethodProvider::Bool;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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..0e27345
--- /dev/null
@@ -0,0 +1,85 @@
+
+package Moose::AttributeHelpers::MethodProvider::Counter;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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..d82ef5d
--- /dev/null
@@ -0,0 +1,205 @@
+package Moose::AttributeHelpers::MethodProvider::Hash;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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..6d26a35
--- /dev/null
@@ -0,0 +1,155 @@
+package Moose::AttributeHelpers::MethodProvider::ImmutableHash;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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..a04ff15
--- /dev/null
@@ -0,0 +1,274 @@
+package Moose::AttributeHelpers::MethodProvider::List;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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,
+       provides   => {
+           elements => 'all_options',
+           map      => 'map_options',
+           grep     => 'filter_options',
+           find     => 'find_option',
+           first    => 'first_option',
+           last     => 'last_option',
+           get      => 'get_option',
+           join     => 'join_options',
+           count    => 'count_options',
+           empty    => 'do_i_have_options',
+           sort     => 'sorted_options',
+       }
+   );
+
+   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..f9b54ff
--- /dev/null
@@ -0,0 +1,164 @@
+
+package Moose::AttributeHelpers::MethodProvider::String;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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/Number.pm b/lib/Moose/AttributeHelpers/Number.pm
new file mode 100644 (file)
index 0000000..1f8bc7f
--- /dev/null
@@ -0,0 +1,125 @@
+package Moose::AttributeHelpers::Number;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::Number';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::Number;
+sub register_implementation { 'Moose::AttributeHelpers::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 },
+      provides  => {
+          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/String.pm b/lib/Moose/AttributeHelpers/String.pm
new file mode 100644 (file)
index 0000000..7c2da73
--- /dev/null
@@ -0,0 +1,151 @@
+
+package Moose::AttributeHelpers::String;
+use Moose;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+extends 'Moose::Meta::Attribute';
+with 'Moose::AttributeHelpers::Trait::String';
+
+no Moose;
+
+# register the alias ...
+package # hide me from search.cpan.org
+    Moose::Meta::Attribute::Custom::String;
+sub register_implementation { 'Moose::AttributeHelpers::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 { '' },
+      provides  => {
+          append => "add_text",
+          replace => "replace_text",
+      }
+  );
+
+  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<provides> 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_provides>
+
+Run before its superclass method.
+
+=item B<check_provides_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 analogeous 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
diff --git a/lib/Moose/AttributeHelpers/Trait/Base.pm b/lib/Moose/AttributeHelpers/Trait/Base.pm
new file mode 100644 (file)
index 0000000..b82daeb
--- /dev/null
@@ -0,0 +1,267 @@
+
+package Moose::AttributeHelpers::Trait::Base;
+use Moose::Role;
+use Moose::Util::TypeConstraints;
+
+our $VERSION   = '0.19';
+$VERSION = eval $VERSION;
+our $AUTHORITY = 'cpan:STEVAN';
+
+requires 'helper_type';
+
+# this is the method map you define ...
+has 'provides' => (
+    is      => 'ro',
+    isa     => 'HashRef',
+    default => sub {{}}
+);
+
+has 'curries' => (
+    is      => 'ro',
+    isa     => 'HashRef',
+    default => sub {{}}
+);
+
+# these next two are the possible methods
+# you can use in the 'provides' 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_provides {
+    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_provides($options, $name);
+};
+
+## methods called after instantiation
+
+sub check_provides_values {
+    my $self = shift;
+
+    my $method_constructors = $self->method_constructors;
+
+    foreach my $key (keys %{$self->provides}) {
+        (exists $method_constructors->{$key})
+            || confess "$key is an unsupported method type";
+    }
+
+    foreach my $key (keys %{$self->curries}) {
+        (exists $method_constructors->{$key})
+            || confess "$key is an unsupported method type";
+    }
+}
+
+sub _curry {
+    my $self = shift;
+    my $code = shift;
+
+    my @args = @_;
+    return sub {
+        my $self = shift;
+        $code->($self, @args, @_)
+    };
+}
+
+sub _curry_sub {
+    my $self = shift;
+    my $body = shift;
+    my $code = shift;
+
+    return sub {
+        my $self = shift;
+        $code->($self, $body, @_)
+    };
+}
+
+after 'install_accessors' => sub {
+    my $attr  = shift;
+    my $class = $attr->associated_class;
+
+    # grab the reader and writer methods
+    # as well, this will be useful for
+    # our method provider constructors
+    my $attr_reader = $attr->get_read_method_ref;
+    my $attr_writer = $attr->get_write_method_ref;
+
+
+    # before we install them, lets
+    # make sure they are valid
+    $attr->check_provides_values;
+
+    my $method_constructors = $attr->method_constructors;
+
+    my $class_name = $class->name;
+
+    while (my ($constructor, $constructed) = each %{$attr->curries}) {
+        my $method_code;
+        while (my ($curried_name, $curried_arg) = each(%$constructed)) {
+            if ($class->has_method($curried_name)) {
+                confess
+                    "The method ($curried_name) already ".
+                    "exists in class (" . $class->name . ")";
+            }
+            my $body = $method_constructors->{$constructor}->(
+                       $attr,
+                       $attr_reader,
+                       $attr_writer,
+            );
+
+            if (ref $curried_arg eq 'ARRAY') {
+                $method_code = $attr->_curry($body, @$curried_arg);
+            }
+            elsif (ref $curried_arg eq 'CODE') {
+                $method_code = $attr->_curry_sub($body, $curried_arg);
+            }
+            else {
+                confess "curries parameter must be ref type HASH or CODE";
+            }
+
+            my $method = Moose::AttributeHelpers::Meta::Method::Curried->wrap(
+                $method_code,
+                package_name => $class_name,
+                name => $curried_name,
+            );
+
+            $attr->associate_method($method);
+            $class->add_method($curried_name => $method);
+        }
+    }
+
+    foreach my $key (keys %{$attr->provides}) {
+
+        my $method_name = $attr->provides->{$key};
+
+        if ($class->has_method($method_name)) {
+            confess "The method ($method_name) already exists in class (" . $class->name . ")";
+        }
+
+        my $method = Moose::AttributeHelpers::Meta::Method::Provided->wrap(
+            $method_constructors->{$key}->(
+                $attr,
+                $attr_reader,
+                $attr_writer,
+            ),
+            package_name => $class_name,
+            name => $method_name,
+        );
+
+        $attr->associate_method($method);
+        $class->add_method($method_name => $method);
+    }
+};
+
+after 'remove_accessors' => sub {
+    my $attr  = shift;
+    my $class = $attr->associated_class;
+
+    # provides accessors
+    foreach my $key (keys %{$attr->provides}) {
+        my $method_name = $attr->provides->{$key};
+        my $method = $class->get_method($method_name);
+        $class->remove_method($method_name)
+            if blessed($method) &&
+               $method->isa('Moose::AttributeHelpers::Meta::Method::Provided');
+    }
+
+    # curries accessors
+    foreach my $key (keys %{$attr->curries}) {
+        my $method_name = $attr->curries->{$key};
+        my $method = $class->get_method($method_name);
+        $class->remove_method($method_name)
+            if blessed($method) &&
+               $method->isa('Moose::AttributeHelpers::Meta::Method::Provided');
+    }
+};
+
+no Moose::Role;
+no Moose::Util::TypeConstraints;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Moose::AttributeHelpers::Trait::Base - base role for helpers
+
+=head1 METHODS
+
+=head2 check_provides_values
+
+Confirms that provides (and curries) has all valid possibilities in it.
+
+=head2 process_options_for_provides
+
+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..b8071e0
--- /dev/null
@@ -0,0 +1,140 @@
+package Moose::AttributeHelpers::Trait::Bool;
+use Moose::Role;
+use Moose::AttributeHelpers::MethodProvider::Bool;
+
+our $VERSION   = '0.19';
+$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_provides' => 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 },
+      provides  => {
+          set     => 'illuminate',
+          unset   => 'darken',
+          toggle  => 'flip_switch',
+          not     => 'is_dark'
+      }
+  );
+
+  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..701fdd3
--- /dev/null
@@ -0,0 +1,62 @@
+
+package Moose::AttributeHelpers::Trait::Collection;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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_provides>
+
+=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..f433fc0
--- /dev/null
@@ -0,0 +1,98 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Array;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 { [] },
+      provides  => {
+          'push' => 'add_options',
+          'pop'  => 'remove_last_option',
+      }
+  );
+
+=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..5cd36c7
--- /dev/null
@@ -0,0 +1,116 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Bag;
+use Moose::Role;
+use Moose::Util::TypeConstraints;
+
+our $VERSION   = '0.19';
+$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_provides' => 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
+      provides  => {
+          'add'    => 'add_word',
+          'get'    => 'get_count_for',
+          'empty'  => 'has_any_words',
+          'count'  => 'num_words',
+          'delete' => 'delete_word',
+      }
+  );
+
+=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_provides>
+
+=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..d38146d
--- /dev/null
@@ -0,0 +1,101 @@
+
+package Moose::AttributeHelpers::Trait::Collection::Hash;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 { {} },
+      provides  => {
+          'set'    => 'set_option',
+          'get'    => 'get_option',
+          'empty'  => 'has_options',
+          'count'  => 'num_options',
+          'delete' => 'delete_option',
+      }
+  );
+
+=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..b110a69
--- /dev/null
@@ -0,0 +1,99 @@
+
+package Moose::AttributeHelpers::Trait::Collection::ImmutableHash;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 { {} },
+      provides  => {
+          'get'    => 'get_option',
+          'empty'  => 'has_options',
+          'keys'   => 'get_option_list',
+      }
+  );
+
+=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..fabd029
--- /dev/null
@@ -0,0 +1,98 @@
+
+package Moose::AttributeHelpers::Trait::Collection::List;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 { [] },
+      provides  => {
+          map  => 'map_options',
+          grep => 'filter_options',
+      }
+  );
+
+=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..73712a9
--- /dev/null
@@ -0,0 +1,168 @@
+
+package Moose::AttributeHelpers::Trait::Counter;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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_provides' => 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_provides_values' => sub {
+    my $self     = shift;
+    my $provides = $self->provides;
+
+    unless (scalar keys %$provides) {
+        my $method_constructors = $self->method_constructors;
+        my $attr_name           = $self->name;
+
+        foreach my $method (keys %$method_constructors) {
+            $provides->{$method} = ($method . '_' . $attr_name);
+        }
+    }
+};
+
+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 },
+      provides  => {
+          inc => 'inc_counter',
+          dec => 'dec_counter',
+          reset => 'reset_counter',
+      }
+  );
+
+  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 decremeneted.
+
+If your attribute definition does not include any of I<is>, I<isa>,
+I<default> or I<provides> 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_provides>
+
+Run before its superclass method.
+
+=item B<check_provides_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..80d6759
--- /dev/null
@@ -0,0 +1,170 @@
+package Moose::AttributeHelpers::Trait::Number;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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 },
+      provides  => {
+          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..fe721ae
--- /dev/null
@@ -0,0 +1,187 @@
+
+package Moose::AttributeHelpers::Trait::String;
+use Moose::Role;
+
+our $VERSION   = '0.19';
+$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_provides' => 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_provides_values' => sub {
+    my $self     = shift;
+    my $provides = $self->provides;
+
+    unless (scalar keys %$provides) {
+        my $method_constructors = $self->method_constructors;
+        my $attr_name           = $self->name;
+
+        foreach my $method (keys %$method_constructors) {
+            $provides->{$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 { '' },
+      provides  => {
+          append => "add_text",
+          replace => "replace_text",
+      }
+  );
+
+  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<provides> 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_provides>
+
+Run before its superclass method.
+
+=item B<check_provides_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 analogeous 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
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/001_basic_counter.t b/t/070_attribute_helpers/001_basic_counter.t
new file mode 100644 (file)
index 0000000..b4fb826
--- /dev/null
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 18;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'counter' => (
+        metaclass => 'Counter',
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 0 },
+        provides  => {
+            inc   => 'inc_counter',
+            dec   => 'dec_counter',
+            reset => 'reset_counter',
+            set   => 'set_counter'
+        }
+    );
+}
+
+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');
+isa_ok($counter, 'Moose::AttributeHelpers::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->provides, {
+    inc   => 'inc_counter',
+    dec   => 'dec_counter',
+    reset => 'reset_counter',
+    set   => 'set_counter'
+}, '... got the right provides methods');
+
diff --git a/t/070_attribute_helpers/002_basic_array.t b/t/070_attribute_helpers/002_basic_array.t
new file mode 100644 (file)
index 0000000..5bddc1f
--- /dev/null
@@ -0,0 +1,244 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 69;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+
+    has 'options' => (
+        metaclass => 'Collection::Array',
+        is        => 'ro',
+        isa       => 'ArrayRef[Str]',
+        default   => sub { [] },
+        provides => {
+            'push'          => 'add_options',
+            'pop'           => 'remove_last_option',
+            'shift'         => 'remove_first_option',
+            'unshift'       => 'insert_options',
+            'get'           => 'get_option_at',
+            'set'           => 'set_option_at',
+            'count'         => 'num_options',
+            'empty'         => 'has_options',
+            'clear'         => 'clear_options',
+            'splice'        => 'splice_options',
+            'sort_in_place' => 'sort_options_in_place',
+            'accessor'      => 'option_accessor',
+            },
+        curries   => {
+            'push'    => {
+                add_options_with_speed => ['funrolls', 'funbuns']
+            },
+            'unshift'  => {
+                prepend_prerequisites_along_with => ['first', 'second']
+            },
+            'sort_in_place' => { descending_options => [ 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');
+isa_ok($options, 'Moose::AttributeHelpers::Collection::Array');
+
+is_deeply($options->provides, {
+    'push'    => 'add_options',
+    'pop'     => 'remove_last_option',
+    'shift'   => 'remove_first_option',
+    'unshift' => 'insert_options',
+    'get'     => 'get_option_at',
+    'set'     => 'set_option_at',
+    'count'   => 'num_options',
+    'empty'   => 'has_options',
+    'clear'   => 'clear_options',
+    'splice'  => 'splice_options',
+    'sort_in_place' => 'sort_options_in_place',
+    'accessor' => 'option_accessor',
+}, '... got the right provides mapping');
+
+is($options->type_constraint->type_parameter, 'Str', '... got the right container type');
diff --git a/t/070_attribute_helpers/003_basic_hash.t b/t/070_attribute_helpers/003_basic_hash.t
new file mode 100644 (file)
index 0000000..ae3a6a1
--- /dev/null
@@ -0,0 +1,189 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 50;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+    use Moose::AttributeHelpers;
+
+    has 'options' => (
+        metaclass => 'Collection::Hash',
+        is        => 'ro',
+        isa       => 'HashRef[Str]',
+        default   => sub { {} },
+        provides  => {
+            'set'      => 'set_option',
+            'get'      => 'get_option',
+            'empty'    => 'has_options',
+            'count'    => 'num_options',
+            'clear'    => 'clear_options',
+            'delete'   => 'delete_option',
+            'exists'   => 'has_option',
+            'defined'  => 'is_defined',
+            'accessor' => 'option_accessor',
+            'kv'       => 'key_value',
+            'elements' => 'options_elements',
+        },
+        curries   => {
+            'accessor' => {
+                quantity => ['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';
+
+dies_ok {
+    my $stuff = Stuff->new;
+    $stuff->option_accessor();
+} '... accessor dies on 0 args';
+
+dies_ok {
+    my $stuff = Stuff->new;
+    $stuff->option_accessor(1 => 2, 3);
+} '... accessor dies on 3 args';
+
+dies_ok {
+    my $stuff = Stuff->new;
+    $stuff->option_accessor(1 => 2, 3 => 4);
+} '... accessor dies on 4 args';
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('options');
+isa_ok($options, 'Moose::AttributeHelpers::Collection::Hash');
+
+is_deeply($options->provides, {
+    'set'      => 'set_option',
+    'get'      => 'get_option',
+    'empty'    => 'has_options',
+    'count'    => 'num_options',
+    'clear'    => 'clear_options',
+    'delete'   => 'delete_option',
+    'defined'  => 'is_defined',
+    'exists'   => 'has_option',
+    'accessor' => 'option_accessor',
+    'kv'       => 'key_value',
+    'elements' => 'options_elements',
+}, '... got the right provides 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/004_basic_number.t b/t/070_attribute_helpers/004_basic_number.t
new file mode 100644 (file)
index 0000000..56be613
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+
+
+use strict;
+use warnings;
+
+use Test::More tests => 26;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Real;
+    use Moose;
+
+    has 'integer' => (
+        metaclass => 'Number',
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 5 },
+        provides  => {
+            set       => 'set',
+            add       => 'add',
+            sub       => 'sub',
+            mul       => 'mul',
+            div       => 'div',
+            mod       => 'mod',
+            abs       => 'abs',
+        },
+        curries   => {
+            add       => {inc         => [ 1 ]},
+            sub       => {dec         => [ 1 ]},
+            mod       => {odd         => [ 2 ]},
+            div       => {cut_in_half => [ 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');
+isa_ok($attr, 'Moose::AttributeHelpers::Number');
+
+is_deeply($attr->provides, {
+    set => 'set',
+    add => 'add',
+    sub => 'sub',
+    mul => 'mul',
+    div => 'div',
+    mod => 'mod',
+    abs => 'abs',
+}, '... got the right provides mapping');
+
diff --git a/t/070_attribute_helpers/005_basic_list.t b/t/070_attribute_helpers/005_basic_list.t
new file mode 100644 (file)
index 0000000..dac87a6
--- /dev/null
@@ -0,0 +1,152 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 35;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+
+    has '_options' => (
+        metaclass => 'Collection::List',
+        is        => 'ro',
+        isa       => 'ArrayRef[Int]',
+        init_arg  => 'options',
+        default   => sub { [] },
+        provides  => {
+            'count'    => 'num_options',
+            'empty'    => 'has_options',
+            'map'      => 'map_options',
+            'grep'     => 'filter_options',
+            'find'     => 'find_option',
+            'elements' => 'options',
+            'join'     => 'join_options',
+            'get'      => 'get_option_at',
+            'first'    => 'get_first_option',
+            'last'     => 'get_last_option',
+            'sort'     => 'sorted_options',
+        },
+        curries   => {
+            'grep'     => {less_than_five => [ sub { $_ < 5 } ]},
+            'map'      => {up_by_one      => [ sub { $_ + 1 } ]},
+            'join'     => {dashify        => [ '-' ]},
+            'sort'     => {descending     => [ sub { $_[1] <=> $_[0] } ]},
+        }
+    );
+
+    has animals => (
+        is       => 'rw',
+        isa      => 'ArrayRef[Str]',
+        metaclass => 'Collection::List',
+        curries => {
+            grep =>  {
+                double_length_of => sub {
+                    my ($self, $body, $arg) = @_;
+
+                    $body->($self, sub { length($_) == length($arg) * 2 });
+                }
+            }
+        }
+    )
+}
+
+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');
+
+$stuff->animals([ qw/cat duck horse cattle gorilla elephant flamingo kangaroo/ ]);
+
+# 4 * 2 = 8
+is_deeply(
+        [ sort $stuff->double_length_of('fish') ],
+        [ sort qw/elephant flamingo kangaroo/ ],
+        'returns all elements with double length of string "fish"'
+);
+
+is_deeply([$stuff->descending], [reverse 1 .. 10]);
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('_options');
+isa_ok($options, 'Moose::AttributeHelpers::Collection::List');
+
+is_deeply($options->provides, {
+    'map'      => 'map_options',
+    'grep'     => 'filter_options',
+    'find'     => 'find_option',
+    'count'    => 'num_options',
+    'empty'    => 'has_options',
+    'elements' => 'options',
+    'join'     => 'join_options',
+    'get'      => 'get_option_at',
+    'first'    => 'get_first_option',
+    'last'     => 'get_last_option',
+    'sort'     => 'sorted_options',
+}, '... got the right provides 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/006_basic_bag.t b/t/070_attribute_helpers/006_basic_bag.t
new file mode 100644 (file)
index 0000000..f75048c
--- /dev/null
@@ -0,0 +1,76 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 20;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+    use Moose::AttributeHelpers;
+
+    has 'word_histogram' => (
+        metaclass => 'Collection::Bag',
+        is        => 'ro',
+        provides  => {
+            'add'    => 'add_word',
+            'get'    => 'get_count_for',
+            'empty'  => 'has_any_words',
+            'count'  => 'num_words',
+            'delete' => 'delete_word',
+        }
+    );
+}
+
+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');
+isa_ok($words, 'Moose::AttributeHelpers::Collection::Bag');
+
+is_deeply($words->provides, {
+    'add'    => 'add_word',
+    'get'    => 'get_count_for',
+    'empty'  => 'has_any_words',
+    'count'  => 'num_words',
+    'delete' => 'delete_word',
+}, '... got the right provides mapping');
+
diff --git a/t/070_attribute_helpers/007_basic_string.t b/t/070_attribute_helpers/007_basic_string.t
new file mode 100644 (file)
index 0000000..b7c9d03
--- /dev/null
@@ -0,0 +1,118 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 27;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'string' => (
+        metaclass => 'String',
+        is        => 'rw',
+        isa       => 'Str',
+        default   => sub { '' },
+        provides => {
+            inc     => 'inc_string',
+            append  => 'append_string',
+            prepend => 'prepend_string',
+            match   => 'match_string',
+            replace => 'replace_string',
+            chop    => 'chop_string',
+            chomp   => 'chomp_string',
+            clear   => 'clear_string',
+            substr  => 'sub_string',
+        },
+        curries  => {
+            append  => {exclaim         => [ '!' ]},
+            replace => {capitalize_last => [ qr/(.)$/, sub { uc $1 } ]},
+            match   => {invalid_number  => [ qr/\D/ ]},
+            substr  => {shift_chars     => sub { $_[1]->($_[0], 0, $_[2], '') } },
+        }
+    );
+}
+
+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!');
+
+is($page->sub_string(2), 'rcfo!', 'substr(offset)');
+is($page->sub_string(2, 2), 'rc', 'substr(offset, length)');
+is($page->sub_string(2, 2, ''), 'rc', 'substr(offset, length, replacement)');
+is($page->string, 'bAfo!', 'replacement got inserted');
+
+is($page->shift_chars(2), 'bA', 'curried substr');
+is($page->string, 'fo!', 'replacement got inserted');
+
+$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');
+isa_ok($string, 'Moose::AttributeHelpers::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->provides, {
+    inc     => 'inc_string',
+    append  => 'append_string',
+    prepend => 'prepend_string',
+    match   => 'match_string',
+    replace => 'replace_string',
+    chop    => 'chop_string',
+    chomp   => 'chomp_string',
+    clear   => 'clear_string',
+    substr  => 'sub_string',
+}, '... got the right provides methods');
+
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..667ad16
--- /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' => (
+        metaclass => 'Collection::Array',
+        is        => 'ro',
+        isa       => 'ArrayRef[Foo]',
+    );
+
+    package Bulkie::Role;
+    use Moose::Role;
+
+    has 'stuff' => (
+        metaclass => 'Collection::Array',
+        is        => 'ro',
+        isa       => 'ArrayRef',
+        provides  => {
+            'get' => 'get_stuff'
+        }
+    );
+
+    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..a242cb3
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 14;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'counter' => (metaclass => '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');
+isa_ok($counter, 'Moose::AttributeHelpers::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->provides, {
+    inc   => 'inc_counter',
+    dec   => 'dec_counter',
+    reset => 'reset_counter',
+    set   => 'set_counter',
+}, '... got the right default provides methods');
+
diff --git a/t/070_attribute_helpers/012_basic_bool.t b/t/070_attribute_helpers/012_basic_bool.t
new file mode 100644 (file)
index 0000000..b156b47
--- /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' => (
+        metaclass => 'Bool',
+        is        => 'rw',
+        isa       => 'Bool',
+        default   => sub { 0 },
+        provides  => {
+            set     => 'illuminate',
+            unset   => 'darken',
+            toggle  => 'flip_switch',
+            not     => 'is_dark'
+        }
+    )
+}
+
+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';
+
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..3a96e7a
--- /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' => (
+        metaclass => 'Counter',
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 0 },
+        provides  => {
+            inc   => 'inc_counter',
+            dec   => 'dec_counter',
+            reset => 'reset_counter',
+        }
+    );
+}
+
+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..ae7e9dc
--- /dev/null
@@ -0,0 +1,123 @@
+#!/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 => (
+    metaclass  => 'Collection::Array',
+    is         => 'ro',
+    isa        => 'ArrayRef[Observer]',
+    auto_deref => 1,
+    default    => sub { [] },
+    provides   => { 'push' => 'add_observer', count => 'count_observers' }
+);
+
+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 => (
+    metaclass => 'Counter',
+    is        => 'ro',
+    isa       => 'Int',
+    default   => 0,
+    provides  => {
+        inc => 'inc_counter',
+        dec => 'dec_counter',
+    }
+);
+
+after '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');
\ No newline at end of file
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..dafff01
--- /dev/null
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 14;
+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 },
+        provides  => {
+            inc   => 'inc_counter',
+            dec   => 'dec_counter',
+            reset => 'reset_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, 'Int', '... got the expected type constraint');
+
+is_deeply($counter->provides, {
+    inc   => 'inc_counter',
+    dec   => 'dec_counter',
+    reset => 'reset_counter',
+}, '... got the right provides 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..ceae233
--- /dev/null
@@ -0,0 +1,151 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 51;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+
+    has 'options' => (
+        traits    => [qw/Collection::Array/],
+        is        => 'ro',
+        isa       => 'ArrayRef[Int]',
+        default   => sub { [] },
+        provides  => {
+            'push'    => 'add_options',
+            'pop'     => 'remove_last_option',
+            'shift'   => 'remove_first_option',
+            'unshift' => 'insert_options',
+            'get'     => 'get_option_at',
+            'set'     => 'set_option_at',
+            'count'   => 'num_options',
+            'empty'   => 'has_options',
+            'clear'   => 'clear_options',
+        }
+    );
+}
+
+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
+];
+
+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" );
+
+## check some errors
+
+dies_ok {
+    $stuff->add_options([]);
+} '... could not add an array ref where an int is expected';
+
+dies_ok {
+    $stuff->insert_options(undef);
+} '... could not add an undef where an int is expected';
+
+dies_ok {
+    $stuff->set_option(5, {});
+} '... could not add a hash ref where an int is expected';
+
+dies_ok {
+    Stuff->new(options => [ 'Foo', 10, 'Bar', 20 ]);
+} '... bad constructor params';
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('options');
+does_ok($options, 'Moose::AttributeHelpers::Trait::Collection::Array');
+
+is_deeply($options->provides, {
+    'push'    => 'add_options',
+    'pop'     => 'remove_last_option',
+    'shift'   => 'remove_first_option',
+    'unshift' => 'insert_options',
+    'get'     => 'get_option_at',
+    'set'     => 'set_option_at',
+    'count'   => 'num_options',
+    'empty'   => 'has_options',
+    'clear'   => 'clear_options',
+}, '... got the right provies mapping');
+
+is($options->type_constraint->type_parameter, 'Int', '... 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..5d096ac
--- /dev/null
@@ -0,0 +1,125 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 35;
+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 { {} },
+        provides  => {
+            'set'    => 'set_option',
+            'get'    => 'get_option',
+            'empty'  => 'has_options',
+            'count'  => 'num_options',
+            'clear'  => 'clear_options',
+            'delete' => 'delete_option',
+        }
+    );
+}
+
+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
+];
+
+ok(!$stuff->has_options, '... we have no options');
+is($stuff->num_options, 0, '... we have no options');
+
+is_deeply($stuff->options, {}, '... no options yet');
+
+lives_ok {
+    $stuff->set_option(foo => 'bar');
+} '... set the option okay';
+
+ok($stuff->has_options, '... we have options');
+is($stuff->num_options, 1, '... we have 1 option(s)');
+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->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->provides, {
+    'set'    => 'set_option',
+    'get'    => 'get_option',
+    'empty'  => 'has_options',
+    'count'  => 'num_options',
+    'clear'  => 'clear_options',
+    'delete' => 'delete_option',
+}, '... got the right provies mapping');
+
+is($options->type_constraint->type_parameter, 'Str', '... got the right container type');
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..4179c58
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 20;
+use Test::Moose;
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Real;
+    use Moose;
+
+    has 'integer' => (
+        traits    => [qw/Number/],
+        is        => 'ro',
+        isa       => 'Int',
+        default   => sub { 5 },
+        provides  => {
+            set => 'set',
+            add => 'add',
+            sub => 'sub',
+            mul => 'mul',
+            div => 'div',
+            mod => 'mod',
+            abs => 'abs',
+        }
+    );
+}
+
+my $real = Real->new;
+isa_ok($real, 'Real');
+
+can_ok($real, $_) for qw[
+    set add sub mul div mod abs
+];
+
+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';
+
+## test the meta
+
+my $attr = $real->meta->get_attribute('integer');
+does_ok($attr, 'Moose::AttributeHelpers::Trait::Number');
+
+is_deeply($attr->provides, {
+    set => 'set',
+    add => 'add',
+    sub => 'sub',
+    mul => 'mul',
+    div => 'div',
+    mod => 'mod',
+    abs => 'abs',
+}, '... got the right provides 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..21d3fd7
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 21;
+use Test::Exception;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package Stuff;
+    use Moose;
+
+    has '_options' => (
+        traits    => [qw/Collection::List/],
+        is        => 'ro',
+        isa       => 'ArrayRef[Int]',
+        init_arg  => 'options',
+        default   => sub { [] },
+        provides  => {
+            'count'    => 'num_options',
+            'empty'    => 'has_options',
+            'map'      => 'map_options',
+            'grep'     => 'filter_options',
+            'find'     => 'find_option',
+            'elements' => 'options',
+            'join'     => 'join_options',
+        }
+    );
+}
+
+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
+];
+
+is_deeply($stuff->_options, [1 .. 10], '... got options');
+
+ok($stuff->has_options, '... we have options');
+is($stuff->num_options, 10, '... got 2 options');
+
+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 :');
+
+## test the meta
+
+my $options = $stuff->meta->get_attribute('_options');
+does_ok($options, 'Moose::AttributeHelpers::Trait::Collection::List');
+
+is_deeply($options->provides, {
+    'map'      => 'map_options',
+    'grep'     => 'filter_options',
+    'find'     => 'find_option',
+    'count'    => 'num_options',
+    'empty'    => 'has_options',
+    'elements' => 'options',
+    'join'     => 'join_options',
+}, '... got the right provies mapping');
+
+is($options->type_constraint->type_parameter, 'Int', '... got the right container 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..e694516
--- /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',
+        provides  => {
+            'add'    => 'add_word',
+            'get'    => 'get_count_for',
+            'empty'  => 'has_any_words',
+            'count'  => 'num_words',
+            'delete' => 'delete_word',
+        }
+    );
+}
+
+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->provides, {
+    'add'    => 'add_word',
+    'get'    => 'get_count_for',
+    'empty'  => 'has_any_words',
+    'count'  => 'num_words',
+    'delete' => 'delete_word',
+}, '... got the right provides 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..e3c0075
--- /dev/null
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 17;
+use Test::Moose 'does_ok';
+
+BEGIN {
+    use_ok('Moose::AttributeHelpers');
+}
+
+{
+    package MyHomePage;
+    use Moose;
+
+    has 'string' => (
+        traits    => [qw/String/],
+        is        => 'rw',
+        isa       => 'Str',
+        default   => sub { '' },
+        provides => {
+            inc     => 'inc_string',
+            append  => 'append_string',
+            prepend => 'prepend_string',
+            match   => 'match_string',
+            replace => 'replace_string',
+            chop    => 'chop_string',
+            chomp   => 'chomp_string',
+            clear   => 'clear_string',
+        }
+    );
+}
+
+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->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->provides, {
+    inc     => 'inc_string',
+    append  => 'append_string',
+    prepend => 'prepend_string',
+    match   => 'match_string',
+    replace => 'replace_string',
+    chop    => 'chop_string',
+    chomp   => 'chomp_string',
+    clear   => 'clear_string',
+}, '... got the right provides 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..5b9e7a8
--- /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 },
+        provides  => {
+            set     => 'illuminate',
+            unset   => 'darken',
+            toggle  => 'flip_switch',
+            not     => 'is_dark'
+        }
+    )
+}
+
+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';
+