Class-Tiny-0.012/000755 000765 000024 00000000000 12235010327 013727 5ustar00davidstaff000000 000000 Class-Tiny-0.012/Changes000644 000765 000024 00000005373 12235010327 015232 0ustar00davidstaff000000 000000 Revision history for Class-Tiny 0.012 2013-11-01 16:36:35 America/New_York [DOCUMENTED] - added documentation notes about multiple inheritance and attribute defaults under subclassing [INTERNAL] - added tests for multiple inheritance 0.011 2013-09-25 11:08:07 America/New_York [FIXED] - compile test could hang on Windows [PREREQS] - ExtUtils::MakeMaker configure_requires dropped to 6.17 0.010 2013-09-18 13:23:15 America/New_York [FIXED] - suppressed 'used only once' warnings (GH #9) 0.009 2013-09-17 06:54:47 America/New_York [FIXED] - "won't stay shared" bug on older perls 0.008 2013-09-08 09:49:46 America/New_York [FIXED] - META.yml encoding problems [DOCUMENTED] - revised CONTRIBUTING [INTERNAL] - refactored precaching 0.007 2013-09-07 16:48:56 America/New_York [OPTIMIZED] - accessors without defaults are now much faster (comparable to Class::Accessor::Fast) - constructor and destructors are slightly faster when there are no superclasses except Class::Tiny::Object - linearized @ISA and other items are cached for speed when the first object is created 0.006 2013-09-05 11:56:48 America/New_York [ADDED] - added introspection method: get_all_attribute_defaults_for($class) [DOCUMENTED] - Fixed TOBYINK email address for contributors list - Revised rationale for why Class::Tiny vs other modules 0.005 2013-08-28 11:51:37 America/New_York [ADDED] - Attributes now support lazy defaults passed as a hash reference to the class declaration 0.004 2013-08-21 16:38:01 America/New_York [CHANGED] - Base class is now Class::Tiny::Object; Class::Tiny is now only the class builder - BUILD and DEMOLISH now have Moo(se) like semantics: BUILD gets original constructor arguments. DEMOLISH is now passed a global destruction flag (requires Devel::GlobalDestruction on Perls before v5.14) - Constructor argument validation now happens after BUILD. - Constructor argument validation has been softened to a heuristic: argument names must match a method name [ADDED] - added introspection method: get_all_attributes_for($class) [INTERNAL] - Refactored import() for easier subclassing of Class::Tiny should anyone be crazy enough to do so 0.003 2013-08-19 19:43:36 America/New_York [FIXED] - Multiple invocations of "use Class::Tiny" in a package accumulate attributes instead of overwriting them 0.002 2013-08-19 17:17:24 America/New_York [CHANGED] - Slight reduction in memory usage tracking attributes 0.001 2013-08-16 10:48:33 America/New_York - First release Class-Tiny-0.012/CONTRIBUTING000644 000765 000024 00000004234 12235010327 015564 0ustar00davidstaff000000 000000 ## HOW TO CONTRIBUTE Thank you for considering contributing to this distribution. This file contains instructions that will help you work with the source code. The distribution is managed with Dist::Zilla. This means than many of the usual files you might expect are not in the repository, but are generated at release time (e.g. Makefile.PL). ### Getting dependencies See the included `cpanfile` file for a list of dependencies. If you have App::cpanminus 1.6 or later installed, you can use `cpanm` to satisfy dependencies like this: $ cpanm --installdeps . Otherwise, you can install Module::CPANfile 1.0002 or later and then satisfy dependencies with the regular `cpan` client and `cpanfile-dump`: $ cpan `cpanfile-dump` ### Running tests You can run tests directly using the `prove` tool: $ prove -l $ prove -lv t/some_test_file.t For most distributions, `prove` is entirely sufficent for you to test any patches you have. ### Code style and tidying Please try to match any existing coding style. If there is a `.perltidyrc` file, please install Perl::Tidy and use perltidy before submitting patches. If there is a `tidyall.ini` file, you can also install Code::TidyAll and run `tidyall` on a file or `tidyall -a` to tidy all files. ### Patching documentation Much of the documentation Pod is generated at release time. Depending on the distribution, some documentation may be written in a Pod dialect called WikiDoc. (See Pod::WikiDoc on CPAN.) If you would like to submit a documentation edit, please limit yourself to the documentation you see. If you see typos or documentation issues in the generated docs, please email or open a bug ticket instead of patching. ### Learning Dist::Zilla Dist::Zilla is a very powerful authoring tool, but requires a number of author-specific plugins. If you would like to use it for contributing, install it from CPAN, then run one of the following commands, depending on your CPAN client: $ cpan `dzil authordeps` $ dzil authordeps | cpanm Once installed, here are some dzil commands you might try: $ dzil build $ dzil test $ dzil xtest You can learn more about Dist::Zilla at http://dzil.org/ Class-Tiny-0.012/cpanfile000644 000765 000024 00000001674 12235010327 015443 0ustar00davidstaff000000 000000 requires "Carp" => "0"; requires "perl" => "5.008001"; requires "strict" => "0"; requires "warnings" => "0"; on 'test' => sub { requires "Exporter" => "0"; requires "ExtUtils::MakeMaker" => "0"; requires "File::Spec::Functions" => "0"; requires "List::Util" => "0"; requires "Test::More" => "0.96"; requires "base" => "0"; requires "lib" => "0"; requires "subs" => "0"; }; on 'test' => sub { recommends "CPAN::Meta" => "0"; recommends "CPAN::Meta::Requirements" => "0"; recommends "Test::FailWarnings" => "0"; }; on 'configure' => sub { requires "ExtUtils::MakeMaker" => "6.17"; }; on 'develop' => sub { requires "File::Spec" => "0"; requires "File::Temp" => "0"; requires "IO::Handle" => "0"; requires "IPC::Open3" => "0"; requires "Pod::Coverage::TrustPod" => "0"; requires "Test::CPAN::Meta" => "0"; requires "Test::More" => "0"; requires "Test::Pod" => "1.41"; requires "Test::Pod::Coverage" => "1.08"; }; Class-Tiny-0.012/dist.ini000644 000765 000024 00000001132 12235010327 015370 0ustar00davidstaff000000 000000 name = Class-Tiny author = David Golden license = Apache_2_0 copyright_holder = David Golden copyright_year = 2013 [@DAGOLDEN] :version = 0.052 stopwords = destructor stopwords = fatpacking stopwords = interoperability stopwords = linearized [RemovePrereqs] remove = Devel::GlobalDestruction remove = MRO::Compat remove = Test::FailWarnings remove = mro [Prereqs / TestRecommends ] Test::FailWarnings = 0 [PerlVersionPrereqs / 5.010] MRO::Compat = 0 [PerlVersionPrereqs / 5.014] Devel::GlobalDestruction = 0 [OnlyCorePrereqs] :version = 0.003 starting_version = current Class-Tiny-0.012/lib/000755 000765 000024 00000000000 12235010327 014475 5ustar00davidstaff000000 000000 Class-Tiny-0.012/LICENSE000644 000765 000024 00000026354 12235010327 014746 0ustar00davidstaff000000 000000 This software is Copyright (c) 2013 by David Golden. This is free software, licensed under: The Apache License, Version 2.0, January 2004 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Class-Tiny-0.012/Makefile.PL000644 000765 000024 00000003165 12235010327 015706 0ustar00davidstaff000000 000000 use strict; use warnings; use 5.008001; use ExtUtils::MakeMaker 6.17; my %WriteMakefileArgs = ( "ABSTRACT" => "Minimalist class construction", "AUTHOR" => "David Golden ", "BUILD_REQUIRES" => {}, "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => "6.17" }, "DISTNAME" => "Class-Tiny", "EXE_FILES" => [], "LICENSE" => "apache", "NAME" => "Class::Tiny", "PREREQ_PM" => { "Carp" => 0, "strict" => 0, "warnings" => 0 }, "TEST_REQUIRES" => { "Exporter" => 0, "ExtUtils::MakeMaker" => 0, "File::Spec::Functions" => 0, "List::Util" => 0, "Test::More" => "0.96", "base" => 0, "lib" => 0, "subs" => 0 }, "VERSION" => "0.012", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Carp" => 0, "Exporter" => 0, "ExtUtils::MakeMaker" => 0, "File::Spec::Functions" => 0, "List::Util" => 0, "Test::More" => "0.96", "base" => 0, "lib" => 0, "strict" => 0, "subs" => 0, "warnings" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; if ($] < 5.010) { $WriteMakefileArgs{PREREQ_PM} = { %{ $WriteMakefileArgs{PREREQ_PM} }, "MRO::Compat" => "0", }; } if ($] < 5.014) { $WriteMakefileArgs{PREREQ_PM} = { %{ $WriteMakefileArgs{PREREQ_PM} }, "Devel::GlobalDestruction" => "0", }; } WriteMakefile(%WriteMakefileArgs); Class-Tiny-0.012/MANIFEST000644 000765 000024 00000001201 12235010327 015052 0ustar00davidstaff000000 000000 CONTRIBUTING Changes LICENSE MANIFEST META.json META.yml Makefile.PL README cpanfile dist.ini lib/Class/Tiny.pm perlcritic.rc t/00-report-prereqs.t t/alfa.t t/baker.t t/charlie.t t/delta.t t/echo.t t/foxtrot.t t/golf.t t/hotel.t t/juliett.t t/lib/Alfa.pm t/lib/Baker.pm t/lib/Charlie.pm t/lib/Delta.pm t/lib/Echo.pm t/lib/Foxtrot.pm t/lib/Golf.pm t/lib/Hotel.pm t/lib/India.pm t/lib/Juliett.pm t/lib/TestUtils.pm tidyall.ini xt/author/00-compile.t xt/author/critic.t xt/author/pod-spell.t xt/release/distmeta.t xt/release/minimum-version.t xt/release/pod-coverage.t xt/release/pod-syntax.t xt/release/portability.t xt/release/test-version.t Class-Tiny-0.012/META.json000644 000765 000024 00000005152 12235010327 015353 0ustar00davidstaff000000 000000 { "abstract" : "Minimalist class construction", "author" : [ "David Golden " ], "dynamic_config" : 1, "generated_by" : "Dist::Zilla version 5.003, CPAN::Meta::Converter version 2.132830", "license" : [ "apache_2_0" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Class-Tiny", "no_index" : { "directory" : [ "t", "xt", "examples", "corpus" ], "package" : [ "DB" ] }, "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "6.17" } }, "develop" : { "requires" : { "File::Spec" : "0", "File::Temp" : "0", "IO::Handle" : "0", "IPC::Open3" : "0", "Pod::Coverage::TrustPod" : "0", "Test::CPAN::Meta" : "0", "Test::More" : "0", "Test::Pod" : "1.41", "Test::Pod::Coverage" : "1.08" } }, "runtime" : { "requires" : { "Carp" : "0", "perl" : "5.008001", "strict" : "0", "warnings" : "0" } }, "test" : { "recommends" : { "CPAN::Meta" : "0", "CPAN::Meta::Requirements" : "0", "Test::FailWarnings" : "0" }, "requires" : { "Exporter" : "0", "ExtUtils::MakeMaker" : "0", "File::Spec::Functions" : "0", "List::Util" : "0", "Test::More" : "0.96", "base" : "0", "lib" : "0", "subs" : "0" } } }, "provides" : { "Class::Tiny" : { "file" : "lib/Class/Tiny.pm", "version" : "0.012" }, "Class::Tiny::Object" : { "file" : "lib/Class/Tiny.pm", "version" : "0.012" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/dagolden/Class-Tiny/issues" }, "homepage" : "https://github.com/dagolden/Class-Tiny", "repository" : { "type" : "git", "url" : "https://github.com/dagolden/Class-Tiny.git", "web" : "https://github.com/dagolden/Class-Tiny" } }, "version" : "0.012", "x_authority" : "cpan:DAGOLDEN", "x_contributors" : [ "Gelu Lupas ", "Karen Etheridge ", "Matt S Trout ", "Olivier Mengu\u00e9 ", "Toby Inkster " ] } Class-Tiny-0.012/META.yml000644 000765 000024 00000002315 12235010327 015201 0ustar00davidstaff000000 000000 --- abstract: 'Minimalist class construction' author: - 'David Golden ' build_requires: Exporter: 0 ExtUtils::MakeMaker: 0 File::Spec::Functions: 0 List::Util: 0 Test::More: 0.96 base: 0 lib: 0 subs: 0 configure_requires: ExtUtils::MakeMaker: 6.17 dynamic_config: 1 generated_by: 'Dist::Zilla version 5.003, CPAN::Meta::Converter version 2.132830' license: apache meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: Class-Tiny no_index: directory: - t - xt - examples - corpus package: - DB provides: Class::Tiny: file: lib/Class/Tiny.pm version: 0.012 Class::Tiny::Object: file: lib/Class/Tiny.pm version: 0.012 requires: Carp: 0 perl: 5.008001 strict: 0 warnings: 0 resources: bugtracker: https://github.com/dagolden/Class-Tiny/issues homepage: https://github.com/dagolden/Class-Tiny repository: https://github.com/dagolden/Class-Tiny.git version: 0.012 x_authority: cpan:DAGOLDEN x_contributors: - 'Gelu Lupas ' - 'Karen Etheridge ' - 'Matt S Trout ' - 'Olivier Mengué ' - 'Toby Inkster ' Class-Tiny-0.012/perlcritic.rc000644 000765 000024 00000001072 12235010327 016415 0ustar00davidstaff000000 000000 severity = 5 verbose = 8 [Variables::ProhibitPunctuationVars] allow = $@ $! [TestingAndDebugging::ProhibitNoStrict] allow = refs # Turn these off [-BuiltinFunctions::ProhibitStringyEval] [-ControlStructures::ProhibitPostfixControls] [-ControlStructures::ProhibitUnlessBlocks] [-Documentation::RequirePodSections] [-InputOutput::ProhibitInteractiveTest] [-References::ProhibitDoubleSigils] [-RegularExpressions::RequireExtendedFormatting] [-InputOutput::ProhibitTwoArgOpen] [-Modules::ProhibitEvilModules] # Turn this on [Lax::ProhibitStringyEval::ExceptForRequire] Class-Tiny-0.012/README000644 000765 000024 00000024455 12235010327 014621 0ustar00davidstaff000000 000000 NAME Class::Tiny - Minimalist class construction VERSION version 0.012 SYNOPSIS In Person.pm: package Person; use Class::Tiny qw( name ); 1; In Employee.pm: package Employee; use parent 'Person'; use Class::Tiny qw( ssn ), { timestamp => sub { time } # attribute with default }; 1; In example.pl: use Employee; my $obj = Employee->new( name => "Larry", ssn => "111-22-3333" ); # unknown attributes are fatal: eval { Employee->new( name => "Larry", OS => "Linux" ) }; die "Error creating Employee: $@" if $@; DESCRIPTION This module offers a minimalist class construction kit in around 120 lines of code. Here is a list of features: * defines attributes via import arguments * generates read-write accessors * supports lazy attribute defaults * supports custom accessors * superclass provides a standard "new" constructor * "new" takes a hash reference or list of key/value pairs * "new" has heuristics to catch constructor attribute typos * "new" calls "BUILD" for each class from parent to child * superclass provides a "DESTROY" method * "DESTROY" calls "DEMOLISH" for each class from child to parent Multiple-inheritance is possible, with superclass order determined via mro::get_linear_isa. It uses no non-core modules for any recent Perl. On Perls older than v5.10 it requires MRO::Compat. On Perls older than v5.14, it requires Devel::GlobalDestruction. USAGE Defining attributes Define attributes as a list of import arguments: package Foo::Bar; use Class::Tiny qw( name id height weight ); For each attribute, a read-write accessor is created unless a subroutine of that name already exists: $obj->name; # getter $obj->name( "John Doe" ); # setter Attribute names must be valid subroutine identifiers or an exception will be thrown. You can specify lazy defaults by defining attributes with a hash reference. Keys define attribute names and values are constants or code references that will be evaluated when the attribute is first accessed if no value has been set. The object is passed as an argument to a code reference. package Foo::WithDefaults; use Class::Tiny qw/name id/, { title => 'Peon', skills => sub { [] }, hire_date => sub { $_[0]->_build_hire_date }, }; When subclassing, if multiple accessors of the same name exist in different classes, any default (or lack of default) is determined by standard method resolution order. To make your own custom accessors, just pre-declare the method name before loading Class::Tiny: package Foo::Bar; use subs 'id'; use Class::Tiny qw( name id ); sub id { ... } By declaring "id" also with Class::Tiny, you include it in the list of known attributes for introspection. Default values will not be set for custom accessors unless you handle that yourself. Class::Tiny::Object is your base class If your class does not already inherit from some class, then Class::Tiny::Object will be added to your @ISA to provide "new" and "DESTROY". If your class does inherit from something, then no additional inheritance is set up. If the parent subclasses Class::Tiny::Object, then all is well. If not, then you'll get accessors set up but no constructor or destructor. Don't do that unless you really have a special need for it. Define subclasses as normal. It's best to define them with base, parent or superclass before defining attributes with Class::Tiny so the @ISA array is already populated at compile-time: package Foo::Bar::More; use parent 'Foo::Bar'; use Class::Tiny qw( shoe_size ); Object construction If your class inherits from Class::Tiny::Object (as it should if you followed the advice above), it provides the "new" constructor for you. Objects can be created with attributes given as a hash reference or as a list of key/value pairs: $obj = Foo::Bar->new( name => "David" ); $obj = Foo::Bar->new( { name => "David" } ); If a reference is passed as a single argument, it must be able to be dereferenced as a hash or an exception is thrown. A shallow copy is made of the reference provided. In order to help catch typos in constructor arguments, any argument that it is not also a valid method (e.g. an accessor or other method) will result in a fatal exception. This is not perfect, but should catch typical transposition typos. Also see "BUILD" for how to explicitly hide non-attribute, non-method arguments if desired. BUILD If your class or any superclass defines a "BUILD" method, it will be called by the constructor from the furthest parent class down to the child class after the object has been created. It is passed the constructor arguments as a hash reference. The return value is ignored. Use "BUILD" for validation or setting default values. sub BUILD { my ($self, $args) = @_; $self->foo(42) unless defined $self->foo; croak "Foo must be non-negative" if $self->foo < 0; } If you want to hide a non-attribute constructor argument from validation, delete it from the passed-in argument hash reference. sub BUILD { my ($self, $args) = @_; if ( delete $args->{do_something_special} ) { ... } } The argument reference is a copy, so deleting elements won't affect data in the object. You have to delete it from both if that's what you want. sub BUILD { my ($self, $args) = @_; if ( delete $args->{do_something_special} ) { delete $self->{do_something_special}; ... } } DEMOLISH Class::Tiny provides a "DESTROY" method. If your class or any superclass defines a "DEMOLISH" method, they will be called from the child class to the furthest parent class during object destruction. It is provided a single boolean argument indicating whether Perl is in global destruction. Return values and errors are ignored. sub DEMOLISH { my ($self, $global_destruct) = @_; $self->cleanup(); } Introspection and internals You can retrieve an unsorted list of valid attributes known to Class::Tiny for a class and its superclasses with the "get_all_attributes_for" class method. my @attrs = Class::Tiny->get_all_attributes_for("Employee"); # returns qw/name ssn timestamp/ Likewise, a hash reference of all valid attributes and default values (or code references) may be retrieved with the "get_all_attribute_defaults_for" class method. Any attributes without a default will be "undef". my $def = Class::Tiny->get_all_attribute_defaults_for("Employee"); # returns { # name => undef, # ssn => undef # timestamp => $coderef # } The "import" method uses two class methods, "prepare_class" and "create_attributes" to set up the @ISA array and attributes. Anyone attempting to extend Class::Tiny itself should use these instead of mocking up a call to "import". When the first object is created, linearized @ISA and various subroutines references are cached for speed. Ensure that all inheritance and methods are in place before creating objects. (You don't want to be changing that once you create objects anyway, right?) RATIONALE Why this instead of Object::Tiny or Class::Accessor or something else? I wanted something so simple that it could potentially be used by core Perl modules I help maintain (or hope to write), most of which either use Class::Struct or roll-their-own OO framework each time. Object::Tiny and Object::Tiny::RW were close to what I wanted, but lacking some features I deemed necessary, and their maintainers have an even more strict philosophy against feature creep than I have. I looked for something like it on CPAN, but after checking a dozen class creators I realized I could implement it exactly how I wanted faster than I could search CPAN for something merely sufficient. Compared to everything else, this is smaller in implementation and simpler in API. Why this instead of Moose or Moo? Moose and Moo are both excellent OO frameworks. Moose offers a powerful meta-object protocol (MOP), but is slow to start up and has about 30 non-core dependencies including XS modules. Moo is faster to start up and has about 10 pure Perl dependencies but provides no true MOP, relying instead on its ability to transparently upgrade Moo to Moose when Moose's full feature set is required. By contrast, Class::Tiny has no MOP and has zero non-core dependencies for Perls in the support window. It has far less code, less complexity and no learning curve. If you don't need or can't afford what Moo or Moose offer, this is a intended to be a reasonable fallback. That said, Class::Tiny offers Moose-like conventions for things like "BUILD" and "DEMOLISH" for some minimal interoperability and an easier upgrade path. SUPPORT Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at . You will be notified automatically of any progress on your issue. Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. git clone https://github.com/dagolden/Class-Tiny.git AUTHOR David Golden CONTRIBUTORS * Gelu Lupas * Karen Etheridge * Matt S Trout * Olivier Mengué * Toby Inkster COPYRIGHT AND LICENSE This software is Copyright (c) 2013 by David Golden. This is free software, licensed under: The Apache License, Version 2.0, January 2004 Class-Tiny-0.012/t/000755 000765 000024 00000000000 12235010327 014172 5ustar00davidstaff000000 000000 Class-Tiny-0.012/tidyall.ini000644 000765 000024 00000000240 12235010327 016066 0ustar00davidstaff000000 000000 ; Install Code::TidyAll ; run "tidyall -a" to tidy all files ; run "tidyall -g" to tidy only files modified from git [PerlTidy] select = {lib,t}/**/*.{pl,pm,t} Class-Tiny-0.012/xt/000755 000765 000024 00000000000 12235010327 014362 5ustar00davidstaff000000 000000 Class-Tiny-0.012/xt/author/000755 000765 000024 00000000000 12235010327 015664 5ustar00davidstaff000000 000000 Class-Tiny-0.012/xt/release/000755 000765 000024 00000000000 12235010327 016002 5ustar00davidstaff000000 000000 Class-Tiny-0.012/xt/release/distmeta.t000644 000765 000024 00000000217 12235010327 020001 0ustar00davidstaff000000 000000 #!perl use Test::More; eval "use Test::CPAN::Meta"; plan skip_all => "Test::CPAN::Meta required for testing META.yml" if $@; meta_yaml_ok(); Class-Tiny-0.012/xt/release/minimum-version.t000644 000765 000024 00000000266 12235010327 021331 0ustar00davidstaff000000 000000 #!perl use Test::More; eval "use Test::MinimumVersion"; plan skip_all => "Test::MinimumVersion required for testing minimum versions" if $@; all_minimum_version_ok( qq{5.010} ); Class-Tiny-0.012/xt/release/pod-coverage.t000644 000765 000024 00000000527 12235010327 020546 0ustar00davidstaff000000 000000 #!perl use Test::More; eval "use Test::Pod::Coverage 1.08"; plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@; eval "use Pod::Coverage::TrustPod"; plan skip_all => "Pod::Coverage::TrustPod required for testing POD coverage" if $@; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); Class-Tiny-0.012/xt/release/pod-syntax.t000644 000765 000024 00000000212 12235010327 020270 0ustar00davidstaff000000 000000 #!perl use Test::More; eval "use Test::Pod 1.41"; plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; all_pod_files_ok(); Class-Tiny-0.012/xt/release/portability.t000644 000765 000024 00000000332 12235010327 020527 0ustar00davidstaff000000 000000 #!perl use strict; use warnings; use Test::More; eval 'use Test::Portability::Files'; plan skip_all => 'Test::Portability::Files required for testing portability' if $@; options(test_one_dot => 0); run_tests(); Class-Tiny-0.012/xt/release/test-version.t000644 000765 000024 00000000643 12235010327 020634 0ustar00davidstaff000000 000000 use strict; use warnings; use Test::More; # generated by Dist::Zilla::Plugin::Test::Version 0.002004 BEGIN { eval "use Test::Version; 1;" or die $@; } my @imports = ( 'version_all_ok' ); my $params = { is_strict => 0, has_version => 1, }; push @imports, $params if version->parse( $Test::Version::VERSION ) >= version->parse('1.002'); Test::Version->import(@imports); version_all_ok; done_testing; Class-Tiny-0.012/xt/author/00-compile.t000644 000765 000024 00000002024 12235010327 017714 0ustar00davidstaff000000 000000 use strict; use warnings; # this test was generated with Dist::Zilla::Plugin::Test::Compile 2.037 use Test::More tests => 1 + ($ENV{AUTHOR_TESTING} ? 1 : 0); my @module_files = ( 'Class/Tiny.pm' ); # fake home for cpan-testers use File::Temp; local $ENV{HOME} = File::Temp::tempdir( CLEANUP => 1 ); my $inc_switch = -d 'blib' ? '-Mblib' : '-Ilib'; use File::Spec; use IPC::Open3; use IO::Handle; my @warnings; for my $lib (@module_files) { # see L open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!"; my $stderr = IO::Handle->new; my $pid = open3($stdin, '>&STDERR', $stderr, $^X, $inc_switch, '-e', "require q[$lib]"); binmode $stderr, ':crlf' if $^O eq 'MSWin32'; my @_warnings = <$stderr>; waitpid($pid, 0); is($?, 0, "$lib loaded ok"); if (@_warnings) { warn @_warnings; push @warnings, @_warnings; } } is(scalar(@warnings), 0, 'no warnings found') if $ENV{AUTHOR_TESTING}; Class-Tiny-0.012/xt/author/critic.t000644 000765 000024 00000000435 12235010327 017330 0ustar00davidstaff000000 000000 #!perl use strict; use warnings; use Test::More; use English qw(-no_match_vars); eval "use Test::Perl::Critic"; plan skip_all => 'Test::Perl::Critic required to criticise code' if $@; Test::Perl::Critic->import( -profile => "perlcritic.rc" ) if -e "perlcritic.rc"; all_critic_ok(); Class-Tiny-0.012/xt/author/pod-spell.t000644 000765 000024 00000000641 12235010327 017751 0ustar00davidstaff000000 000000 use strict; use warnings; use Test::More; # generated by Dist::Zilla::Plugin::Test::PodSpelling 2.006001 use Test::Spelling 0.12; use Pod::Wordlist; add_stopwords(); all_pod_files_spelling_ok( qw( bin lib ) ); __DATA__ destructor fatpacking interoperability linearized David Golden dagolden Gelu Lupas gelu Karen Etheridge ether Matt Trout mstrout Olivier Mengué dolmen Toby Inkster tobyink lib Class Tiny Class-Tiny-0.012/t/00-report-prereqs.t000644 000765 000024 00000006643 12235010327 017577 0ustar00davidstaff000000 000000 #!perl use strict; use warnings; # This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs 0.010 use Test::More tests => 1; use ExtUtils::MakeMaker; use File::Spec::Functions; use List::Util qw/max/; my @modules = qw( CPAN::Meta CPAN::Meta::Requirements Carp Exporter ExtUtils::MakeMaker File::Spec::Functions List::Util Test::FailWarnings Test::More base lib perl strict subs warnings ); my %exclude = map {; $_ => 1 } qw( ); my ($source) = grep { -f $_ } qw/MYMETA.json MYMETA.yml META.json/; $source = "META.yml" unless defined $source; # replace modules with dynamic results from MYMETA.json if we can # (hide CPAN::Meta from prereq scanner) my $cpan_meta = "CPAN::Meta"; my $cpan_meta_req = "CPAN::Meta::Requirements"; my $all_requires; if ( -f $source && eval "require $cpan_meta" ) { ## no critic if ( my $meta = eval { CPAN::Meta->load_file($source) } ) { # Get ALL modules mentioned in META (any phase/type) my $prereqs = $meta->prereqs; delete $prereqs->{develop} if not $ENV{AUTHOR_TESTING}; my %uniq = map {$_ => 1} map { keys %$_ } map { values %$_ } values %$prereqs; $uniq{$_} = 1 for @modules; # don't lose any static ones @modules = sort grep { ! $exclude{$_} } keys %uniq; # If verifying, merge 'requires' only for major phases if ( 1 ) { $prereqs = $meta->effective_prereqs; # get the object, not the hash if (eval "require $cpan_meta_req; 1") { ## no critic $all_requires = $cpan_meta_req->new; for my $phase ( qw/configure build test runtime/ ) { $all_requires->add_requirements( $prereqs->requirements_for($phase, 'requires') ); } } } } } my @reports = [qw/Version Module/]; my @dep_errors; my $req_hash = defined($all_requires) ? $all_requires->as_string_hash : {}; for my $mod ( @modules ) { next if $mod eq 'perl'; my $file = $mod; $file =~ s{::}{/}g; $file .= ".pm"; my ($prefix) = grep { -e catfile($_, $file) } @INC; if ( $prefix ) { my $ver = MM->parse_version( catfile($prefix, $file) ); $ver = "undef" unless defined $ver; # Newer MM should do this anyway push @reports, [$ver, $mod]; if ( 1 && $all_requires ) { my $req = $req_hash->{$mod}; if ( defined $req && length $req ) { if ( ! defined eval { version->parse($ver) } ) { push @dep_errors, "$mod version '$ver' cannot be parsed (version '$req' required)"; } elsif ( ! $all_requires->accepts_module( $mod => $ver ) ) { push @dep_errors, "$mod version '$ver' is not in required range '$req'"; } } } } else { push @reports, ["missing", $mod]; if ( 1 && $all_requires ) { my $req = $req_hash->{$mod}; if ( defined $req && length $req ) { push @dep_errors, "$mod is not installed (version '$req' required)"; } } } } if ( @reports ) { my $vl = max map { length $_->[0] } @reports; my $ml = max map { length $_->[1] } @reports; splice @reports, 1, 0, ["-" x $vl, "-" x $ml]; diag "\nVersions for all modules listed in $source (including optional ones):\n", map {sprintf(" %*s %*s\n",$vl,$_->[0],-$ml,$_->[1])} @reports; } if ( @dep_errors ) { diag join("\n", "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n", "The following REQUIRED prerequisites were not satisfied:\n", @dep_errors, "\n" ); } pass; # vim: ts=2 sts=2 sw=2 et: Class-Tiny-0.012/t/alfa.t000644 000765 000024 00000004776 12235010327 015300 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Alfa"); subtest "empty list constructor" => sub { my $obj = new_ok("Alfa"); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); }; subtest "empty hash object constructor" => sub { my $obj = new_ok( "Alfa", [ {} ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); }; subtest "one attribute set as list" => sub { my $obj = new_ok( "Alfa", [ foo => 23 ] ); is( $obj->foo, 23, "foo is set" ); is( $obj->bar, undef, "bar is undef" ); }; subtest "one attribute set as hash ref" => sub { my $obj = new_ok( "Alfa", [ { foo => 23 } ] ); is( $obj->foo, 23, "foo is set" ); is( $obj->bar, undef, "bar is undef" ); }; subtest "both attributes set as list" => sub { my $obj = new_ok( "Alfa", [ foo => 23, bar => 42 ] ); is( $obj->foo, 23, "foo is set" ); is( $obj->bar, 42, "bar is set" ); }; subtest "both attributes set as hash ref" => sub { my $obj = new_ok( "Alfa", [ { foo => 23, bar => 42 } ] ); is( $obj->foo, 23, "foo is set" ); is( $obj->bar, 42, "bar is set" ); }; subtest "constructor makes shallow copy" => sub { my $fake = bless { foo => 23, bar => 42 }, "Fake"; my $obj = new_ok( "Alfa", [$fake] ); is( ref $fake, "Fake", "object passed to constructor is original class" ); is( $obj->foo, 23, "foo is set" ); is( $obj->bar, 42, "bar is set" ); }; subtest "attributes are RW" => sub { my $obj = new_ok( "Alfa", [ { foo => 23, bar => 42 } ] ); is( $obj->foo(24), 24, "changing foo returns new value" ); is( $obj->foo, 24, "accessing foo returns changed value" ); }; subtest "exceptions" => sub { like( exception { Alfa->new( foo => 23, bar => 42, baz => 13 ) }, qr/Invalid attributes for Alfa: baz/, "creating object with 'baz' dies", ); like( exception { Alfa->new(qw/ foo bar baz/) }, qr/Alfa->new\(\) got an odd number of elements/, "creating object with odd elements dies", ); like( exception { Alfa->new( [] ) }, qr/Argument to Alfa->new\(\) could not be dereferenced as a hash/, "creating object with array ref dies", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/baker.t000644 000765 000024 00000004355 12235010327 015452 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Baker"); subtest "attribute list" => sub { is_deeply( [ sort Class::Tiny->get_all_attributes_for("Baker") ], [ sort qw/foo bar baz/ ], "attribute list correct", ); }; subtest "empty list constructor" => sub { my $obj = new_ok("Baker"); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->baz, undef, "baz is undef" ); }; subtest "empty hash object constructor" => sub { my $obj = new_ok( "Baker", [ {} ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->baz, undef, "baz is undef" ); }; subtest "subclass attribute set as list" => sub { my $obj = new_ok( "Baker", [ baz => 23 ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->baz, 23, "baz is set " ); }; subtest "superclass attribute set as list" => sub { my $obj = new_ok( "Baker", [ bar => 42, baz => 23 ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, 42, "bar is set" ); is( $obj->baz, 23, "baz is set " ); }; subtest "all attributes set as list" => sub { my $obj = new_ok( "Baker", [ foo => 13, bar => 42, baz => 23 ] ); is( $obj->foo, 13, "foo is set" ); is( $obj->bar, 42, "bar is set" ); is( $obj->baz, 23, "baz is set " ); }; subtest "attributes are RW" => sub { my $obj = new_ok( "Baker", [ { foo => 23, bar => 42 } ] ); is( $obj->foo(24), 24, "changing foo returns new value" ); is( $obj->foo, 24, "accessing foo returns changed value" ); is( $obj->baz(42), 42, "changing baz returns new value" ); is( $obj->baz, 42, "accessing baz returns changed value" ); }; subtest "exceptions" => sub { like( exception { Baker->new( foo => 23, bar => 42, baz => 13, wibble => 0 ) }, qr/Invalid attributes for Baker: wibble/, "creating object with 'wibble' dies", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/charlie.t000644 000765 000024 00000001326 12235010327 015770 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Charlie"); subtest "all attributes set as list" => sub { my $obj = new_ok( "Charlie", [ foo => 13, bar => [42] ] ); is( $obj->foo, 13, "foo is set" ); is_deeply( $obj->bar, [42], "bar is set" ); }; subtest "custom accessor" => sub { my $obj = new_ok( "Charlie", [ foo => 13, bar => [42] ] ); is_deeply( $obj->bar(qw/1 1 2 3 5/), [qw/1 1 2 3 5/], "bar is set" ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/delta.t000644 000765 000024 00000002314 12235010327 015450 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Delta"); subtest "attribute set as list" => sub { my $obj = new_ok( "Delta", [ foo => 42, bar => 23 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); }; subtest "hiding constructor argument" => sub { my $obj = new_ok( "Delta", [ foo => 42, bar => 23, hide_me => 1 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); is( $obj->{hide_me}, 1, "hidden constructor argument still in object" ); }; subtest "destructor" => sub { my @objs = map { new_ok( "Delta", [ foo => 42, bar => 23 ] ) } 1 .. 3; is( $Delta::counter, 3, "BUILD incremented counter" ); @objs = (); is( $Delta::counter, 0, "DEMOLISH decremented counter" ); }; subtest "exceptions" => sub { like( exception { Delta->new( foo => 0 ) }, qr/foo must be positive/, "BUILD validation throws error", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/echo.t000644 000765 000024 00000002542 12235010327 015300 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Echo"); subtest "attribute set as list" => sub { my $obj = new_ok( "Echo", [ foo => 42, bar => 23 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); is( $obj->baz, 24, "baz is set" ); }; subtest "destructor" => sub { no warnings 'once'; my @objs = map { new_ok( "Echo", [ foo => 42, bar => 23 ] ) } 1 .. 3; is( $Delta::counter, 3, "BUILD incremented counter" ); @objs = (); is( $Delta::counter, 0, "DEMOLISH decremented counter" ); is( $Delta::exception, 0, "cleanup worked in correct order" ); }; subtest "constructor argument heuristic hiding" => sub { my $obj = new_ok( "Echo", [ foo => 42, bar => 23, a_method => 1 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); is( $obj->{a_method}, 1, "hidden constructor argument still in object" ); }; subtest "exceptions" => sub { like( exception { Echo->new( foo => 0, bar => 23 ) }, qr/foo must be positive/, "BUILD validation throws error", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/foxtrot.t000644 000765 000024 00000002130 12235010327 016060 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Foxtrot"); subtest "attribute list" => sub { is_deeply( [ sort Class::Tiny->get_all_attributes_for("Foxtrot") ], [ sort qw/foo bar baz/ ], "attribute list correct", ); }; subtest "attribute defaults" => sub { my $def = Class::Tiny->get_all_attribute_defaults_for("Foxtrot"); is( keys %$def, 3, "defaults hashref size" ); is( $def->{foo}, undef, "foo default is undef" ); is( $def->{bar}, 42, "bar default is 42" ); is( ref $def->{baz}, 'CODE', "baz default is a coderef" ); }; subtest "attribute set as list" => sub { my $obj = new_ok( "Foxtrot", [ foo => 42, bar => 23 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); ok( $obj->baz, "baz is set" ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/golf.t000644 000765 000024 00000002270 12235010327 015307 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Golf"); subtest "lazy defaults" => sub { my $obj = new_ok("Golf"); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); ok( !exists( $obj->{wibble} ), "lazy wibble doesn't exist" ); ok( !exists( $obj->{wobble} ), "lazy wobble doesn't exist" ); is( $obj->wibble, 42, "wibble access gives default" ); is( ref $obj->wobble, 'ARRAY', "wobble access gives default" ); ok( exists( $obj->{wibble} ), "lazy wibble does exist" ); ok( exists( $obj->{wobble} ), "lazy wobble does exist" ); my $obj2 = new_ok("Golf"); isnt( $obj->wobble, $obj2->wobble, "coderefs run for each object" ); }; subtest "exceptions" => sub { like( exception { Golf->new( foo => 23, bar => 42, zoom => 13 ) }, qr/Invalid attributes for Golf: zoom/, "creating object with 'baz' dies", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/hotel.t000644 000765 000024 00000002372 12235010327 015476 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Hotel"); subtest "attribute list" => sub { my $attributes = [ sort Class::Tiny->get_all_attributes_for("Hotel") ]; is_deeply( $attributes, [ sort qw/foo bar wibble wobble zig zag/ ], "attribute list correct", ) or diag explain $attributes; }; subtest "attribute defaults" => sub { my $def = Class::Tiny->get_all_attribute_defaults_for("Hotel"); is( keys %$def, 6, "defaults hashref size" ); is( $def->{foo}, undef, "foo default is undef" ); is( $def->{bar}, undef, "bar default is undef" ); is( $def->{wibble}, 23, "wibble default overrides" ); }; subtest "attribute set as list" => sub { my $obj = new_ok( "Hotel", [ foo => 42, bar => 23 ] ); is( $obj->foo, 42, "foo is set" ); is( $obj->bar, 23, "bar is set" ); is( $obj->wibble, 23, "wibble is set" ); is( ref $obj->wobble, 'HASH', "wobble default overrides" ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/juliett.t000644 000765 000024 00000006023 12235010327 016040 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; use lib 't/lib'; use Test::More 0.96; use TestUtils; require_ok("Juliett"); subtest "attribute list" => sub { is_deeply( [ sort Class::Tiny->get_all_attributes_for("Juliett") ], [ sort qw/foo bar baz qux kit/ ], "attribute list correct", ); }; subtest "empty list constructor" => sub { my $obj = new_ok("Juliett"); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->baz, undef, "baz is undef" ); is( $obj->qux, undef, "qux is undef" ); is( $obj->kit, undef, "kit is undef" ); }; subtest "empty hash object constructor" => sub { my $obj = new_ok( "Juliett", [ {} ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->baz, undef, "baz is undef" ); is( $obj->qux, undef, "qux is undef" ); is( $obj->kit, undef, "kit is undef" ); }; subtest "subclass attribute set as list" => sub { my $obj = new_ok( "Juliett", [ kit => 23 ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, undef, "bar is undef" ); is( $obj->qux, undef, "baz is undef" ); is( $obj->qux, undef, "qux is undef" ); is( $obj->kit, 23, "kit is set" ); }; subtest "superclass attribute set as list" => sub { my $obj = new_ok( "Juliett", [ bar => 42, baz => 23, qux => 13, kit => 31 ] ); is( $obj->foo, undef, "foo is undef" ); is( $obj->bar, 42, "bar is set" ); is( $obj->baz, 23, "baz is set" ); is( $obj->qux, 13, "qux is set" ); is( $obj->kit, 31, "kit is set" ); }; subtest "all attributes set as list" => sub { my $obj = new_ok( "Juliett", [ foo => 13, bar => 42, baz => 23, qux => 11, kit => 31 ] ); is( $obj->foo, 13, "foo is set" ); is( $obj->bar, 42, "bar is set" ); is( $obj->baz, 23, "baz is set" ); is( $obj->qux, 11, "qux is set" ); is( $obj->kit, 31, "kit is set" ); }; subtest "attributes are RW" => sub { my $obj = new_ok( "Juliett", [ { foo => 23, bar => 42 } ] ); is( $obj->foo(24), 24, "changing foo returns new value" ); is( $obj->foo, 24, "accessing foo returns changed value" ); is( $obj->baz(42), 42, "changing baz returns new value" ); is( $obj->baz, 42, "accessing baz returns changed value" ); is( $obj->qux(11), 11, "changing qux returns new value" ); is( $obj->qux, 11, "accessing qux returns changed value" ); is( $obj->kit(31), 31, "changing kit returns new value" ); is( $obj->kit, 31, "accessing kit rerutns changed value" ); }; subtest "exceptions" => sub { like( exception { Juliett->new( foo => 23, bar => 42, baz => 13, qux => 11, kit => 31, wibble => 0 ); }, qr/Invalid attributes for Juliett: wibble/, "creating object with 'wibble' dies", ); }; done_testing; # # This file is part of Class-Tiny # # This software is Copyright (c) 2013 by David Golden. # # This is free software, licensed under: # # The Apache License, Version 2.0, January 2004 # # vim: ts=4 sts=4 sw=4 et: Class-Tiny-0.012/t/lib/000755 000765 000024 00000000000 12235010327 014740 5ustar00davidstaff000000 000000 Class-Tiny-0.012/t/lib/Alfa.pm000644 000765 000024 00000000131 12235010327 016134 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Alfa; use Class::Tiny qw/foo bar/; 1; Class-Tiny-0.012/t/lib/Baker.pm000644 000765 000024 00000000147 12235010327 016324 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Baker; use base 'Alfa'; use Class::Tiny qw/baz/; 1; Class-Tiny-0.012/t/lib/Charlie.pm000644 000765 000024 00000000334 12235010327 016645 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Charlie; use subs qw/bar/; use Class::Tiny qw/foo bar/; sub bar { my $self = shift; if (@_) { $self->{bar} = [@_]; } return $self->{bar}; } 1; Class-Tiny-0.012/t/lib/Delta.pm000644 000765 000024 00000000770 12235010327 016333 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Delta; our $counter = 0; our $exception = 0; use Carp (); use Class::Tiny qw/foo bar/; sub BUILD { my $self = shift; my $args = shift; Carp::croak("foo must be positive") unless defined $self->foo && $self->foo > 0; $self->bar(42) unless defined $self->bar; $counter++; delete $args->{hide_me}; } sub DEMOLISH { my $self = shift; $counter--; $exception++ if keys %$self > 2; # Echo will delete first } 1; Class-Tiny-0.012/t/lib/Echo.pm000644 000765 000024 00000000442 12235010327 016154 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Echo; use base 'Delta'; use Class::Tiny qw/baz/; sub BUILD { my $self = shift; $self->baz( $self->bar + 1 ); } sub DEMOLISH { my $self = shift; delete $self->{baz}; # or else Delta::DEMOLISH dies } sub a_method { 1 } 1; Class-Tiny-0.012/t/lib/Foxtrot.pm000644 000765 000024 00000000212 12235010327 016736 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Foxtrot; use Class::Tiny 'foo'; use Class::Tiny { bar => 42, baz => sub { time } }; 1; Class-Tiny-0.012/t/lib/Golf.pm000644 000765 000024 00000000227 12235010327 016166 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Golf; use Class::Tiny qw/foo bar/, { wibble => 42, wobble => sub { [] }, }, qw/zig zag/; 1; Class-Tiny-0.012/t/lib/Hotel.pm000644 000765 000024 00000000220 12235010327 016343 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Hotel; use base 'Golf'; use Class::Tiny { wibble => 23, wobble => sub { {} }, }; 1; Class-Tiny-0.012/t/lib/India.pm000644 000765 000024 00000000147 12235010327 016324 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package India; use base 'Alfa'; use Class::Tiny qw/qux/; 1; Class-Tiny-0.012/t/lib/Juliett.pm000644 000765 000024 00000000163 12235010327 016716 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package Juliett; use base 'Baker', 'India'; use Class::Tiny qw/kit/; 1; Class-Tiny-0.012/t/lib/TestUtils.pm000644 000765 000024 00000000751 12235010327 017241 0ustar00davidstaff000000 000000 use 5.008001; use strict; use warnings; package TestUtils; use Carp; use Exporter; our @ISA = qw/Exporter/; our @EXPORT = qw( exception ); # If we have Test::FailWarnings, use it BEGIN { eval { require Test::FailWarnings; 1 } and do { Test::FailWarnings->import }; } sub exception(&) { my $code = shift; my $success = eval { $code->(); 1 }; my $err = $@; return '' if $success; croak "Execution died, but the error was lost" unless $@; return $@; } 1; Class-Tiny-0.012/lib/Class/000755 000765 000024 00000000000 12235010327 015542 5ustar00davidstaff000000 000000 Class-Tiny-0.012/lib/Class/Tiny.pm000644 000765 000024 00000035443 12235010327 017034 0ustar00davidstaff000000 000000 use 5.008001; use strict; no strict 'refs'; use warnings; no warnings 'once'; package Class::Tiny; # ABSTRACT: Minimalist class construction our $VERSION = '0.012'; # VERSION use Carp (); # load as .pm to hide from min version scanners require( $] >= 5.010 ? "mro.pm" : "MRO/Compat.pm" ); ## no critic: my %CLASS_ATTRIBUTES; sub import { my $class = shift; my $pkg = caller; $class->prepare_class($pkg); $class->create_attributes( $pkg, @_ ) if @_; } sub prepare_class { my ( $class, $pkg ) = @_; @{"${pkg}::ISA"} = "Class::Tiny::Object" unless @{"${pkg}::ISA"}; } # adapted from Object::Tiny and Object::Tiny::RW sub create_attributes { my ( $class, $pkg, @spec ) = @_; my %defaults = map { ref $_ eq 'HASH' ? %$_ : ( $_ => undef ) } @spec; my @attr = grep { defined and !ref and /^[^\W\d]\w*$/s or Carp::croak "Invalid accessor name '$_'" } keys %defaults; $CLASS_ATTRIBUTES{$pkg}{$_} = $defaults{$_} for @attr; _gen_accessor( $pkg, $_ ) for grep { !*{"$pkg\::$_"}{CODE} } @attr; Carp::croak("Failed to generate attributes for $pkg: $@\n") if $@; } sub _gen_accessor { my ( $pkg, $name ) = @_; my $outer_default = $CLASS_ATTRIBUTES{$pkg}{$name}; my $sub = "sub $name { if (\@_ == 1) {"; if ( defined $outer_default && ref $outer_default eq 'CODE' ) { $sub .= "if ( !exists \$_[0]{$name} ) { \$_[0]{$name} = \$default->(\$_[0]) }"; } elsif ( defined $outer_default ) { $sub .= "if ( !exists \$_[0]{$name} ) { \$_[0]{$name} = \$default }"; } $sub .= "return \$_[0]{$name} } else { return \$_[0]{$name}=\$_[1] } }"; # default = outer_default avoids "won't stay shared" bug eval "package $pkg; my \$default=\$outer_default; $sub"; ## no critic Carp::croak("Failed to generate attributes for $pkg: $@\n") if $@; } sub get_all_attributes_for { my ( $class, $pkg ) = @_; my %attr = map { $_ => undef } map { keys %{ $CLASS_ATTRIBUTES{$_} || {} } } @{ mro::get_linear_isa($pkg) }; return keys %attr; } sub get_all_attribute_defaults_for { my ( $class, $pkg ) = @_; my $defaults = {}; for my $p ( reverse @{ mro::get_linear_isa($pkg) } ) { while ( my ( $k, $v ) = each %{ $CLASS_ATTRIBUTES{$p} || {} } ) { $defaults->{$k} = $v; } } return $defaults; } package Class::Tiny::Object; # ABSTRACT: Base class for classes built with Class::Tiny our $VERSION = '0.012'; # VERSION my ( %LINEAR_ISA_CACHE, %BUILD_CACHE, %DEMOLISH_CACHE, %CAN_CACHE ); my $_PRECACHE = sub { my ($class) = @_; $LINEAR_ISA_CACHE{$class} = @{"$class\::ISA"} == 1 && ${"$class\::ISA"}[0] eq "Class::Tiny::Object" ? [$class] : mro::get_linear_isa($class); for my $s ( @{ $LINEAR_ISA_CACHE{$class} } ) { $BUILD_CACHE{$s} = *{"$s\::BUILD"}{CODE}; $DEMOLISH_CACHE{$s} = *{"$s\::DEMOLISH"}{CODE}; } return $LINEAR_ISA_CACHE{$class}; }; sub new { my $class = shift; my $linear_isa = $LINEAR_ISA_CACHE{$class} || $_PRECACHE->($class); # handle hash ref or key/value arguments my $args; if ( @_ == 1 && ref $_[0] ) { my %copy = eval { %{ $_[0] } }; # try shallow copy Carp::croak("Argument to $class->new() could not be dereferenced as a hash") if $@; $args = \%copy; } elsif ( @_ % 2 == 0 ) { $args = {@_}; } else { Carp::croak("$class->new() got an odd number of elements"); } # create object and invoke BUILD my $self = bless {%$args}, $class; for my $s ( reverse @$linear_isa ) { next unless my $builder = $BUILD_CACHE{$s}; $builder->( $self, $args ); } # unknown attributes still in $args are fatal my @bad; for my $k ( keys %$args ) { push( @bad, $k ) unless $CAN_CACHE{$class}{$k} ||= $self->can($k); # a heuristic to catch typos } Carp::croak("Invalid attributes for $class: @bad") if @bad; return $self; } # Adapted from Moo and its dependencies require Devel::GlobalDestruction unless defined ${^GLOBAL_PHASE}; sub DESTROY { my $self = shift; my $class = ref $self; my $in_global_destruction = defined ${^GLOBAL_PHASE} ? ${^GLOBAL_PHASE} eq 'DESTRUCT' : Devel::GlobalDestruction::in_global_destruction(); for my $s ( @{ $LINEAR_ISA_CACHE{$class} } ) { next unless my $demolisher = $DEMOLISH_CACHE{$s}; my $e = do { local ( $?, $@ ); eval { $demolisher->( $self, $in_global_destruction ) }; $@; }; no warnings 'misc'; # avoid (in cleanup) warnings die $e if $e; # rethrow } } 1; # vim: ts=4 sts=4 sw=4 et: __END__ =pod =encoding UTF-8 =head1 NAME Class::Tiny - Minimalist class construction =head1 VERSION version 0.012 =head1 SYNOPSIS In F: package Person; use Class::Tiny qw( name ); 1; In F: package Employee; use parent 'Person'; use Class::Tiny qw( ssn ), { timestamp => sub { time } # attribute with default }; 1; In F: use Employee; my $obj = Employee->new( name => "Larry", ssn => "111-22-3333" ); # unknown attributes are fatal: eval { Employee->new( name => "Larry", OS => "Linux" ) }; die "Error creating Employee: $@" if $@; =head1 DESCRIPTION This module offers a minimalist class construction kit in around 120 lines of code. Here is a list of features: =over 4 =item * defines attributes via import arguments =item * generates read-write accessors =item * supports lazy attribute defaults =item * supports custom accessors =item * superclass provides a standard C constructor =item * C takes a hash reference or list of key/value pairs =item * C has heuristics to catch constructor attribute typos =item * C calls C for each class from parent to child =item * superclass provides a C method =item * C calls C for each class from child to parent =back Multiple-inheritance is possible, with superclass order determined via L. It uses no non-core modules for any recent Perl. On Perls older than v5.10 it requires L. On Perls older than v5.14, it requires L. =head1 USAGE =head2 Defining attributes Define attributes as a list of import arguments: package Foo::Bar; use Class::Tiny qw( name id height weight ); For each attribute, a read-write accessor is created unless a subroutine of that name already exists: $obj->name; # getter $obj->name( "John Doe" ); # setter Attribute names must be valid subroutine identifiers or an exception will be thrown. You can specify lazy defaults by defining attributes with a hash reference. Keys define attribute names and values are constants or code references that will be evaluated when the attribute is first accessed if no value has been set. The object is passed as an argument to a code reference. package Foo::WithDefaults; use Class::Tiny qw/name id/, { title => 'Peon', skills => sub { [] }, hire_date => sub { $_[0]->_build_hire_date }, }; When subclassing, if multiple accessors of the same name exist in different classes, any default (or lack of default) is determined by standard method resolution order. To make your own custom accessors, just pre-declare the method name before loading Class::Tiny: package Foo::Bar; use subs 'id'; use Class::Tiny qw( name id ); sub id { ... } By declaring C also with Class::Tiny, you include it in the list of known attributes for introspection. Default values will not be set for custom accessors unless you handle that yourself. =head2 Class::Tiny::Object is your base class If your class B already inherit from some class, then Class::Tiny::Object will be added to your C<@ISA> to provide C and C. If your class B inherit from something, then no additional inheritance is set up. If the parent subclasses Class::Tiny::Object, then all is well. If not, then you'll get accessors set up but no constructor or destructor. Don't do that unless you really have a special need for it. Define subclasses as normal. It's best to define them with L, L or L before defining attributes with Class::Tiny so the C<@ISA> array is already populated at compile-time: package Foo::Bar::More; use parent 'Foo::Bar'; use Class::Tiny qw( shoe_size ); =head2 Object construction If your class inherits from Class::Tiny::Object (as it should if you followed the advice above), it provides the C constructor for you. Objects can be created with attributes given as a hash reference or as a list of key/value pairs: $obj = Foo::Bar->new( name => "David" ); $obj = Foo::Bar->new( { name => "David" } ); If a reference is passed as a single argument, it must be able to be dereferenced as a hash or an exception is thrown. A shallow copy is made of the reference provided. In order to help catch typos in constructor arguments, any argument that it is not also a valid method (e.g. an accessor or other method) will result in a fatal exception. This is not perfect, but should catch typical transposition typos. Also see L for how to explicitly hide non-attribute, non-method arguments if desired. =head2 BUILD If your class or any superclass defines a C method, it will be called by the constructor from the furthest parent class down to the child class after the object has been created. It is passed the constructor arguments as a hash reference. The return value is ignored. Use C for validation or setting default values. sub BUILD { my ($self, $args) = @_; $self->foo(42) unless defined $self->foo; croak "Foo must be non-negative" if $self->foo < 0; } If you want to hide a non-attribute constructor argument from validation, delete it from the passed-in argument hash reference. sub BUILD { my ($self, $args) = @_; if ( delete $args->{do_something_special} ) { ... } } The argument reference is a copy, so deleting elements won't affect data in the object. You have to delete it from both if that's what you want. sub BUILD { my ($self, $args) = @_; if ( delete $args->{do_something_special} ) { delete $self->{do_something_special}; ... } } =head2 DEMOLISH Class::Tiny provides a C method. If your class or any superclass defines a C method, they will be called from the child class to the furthest parent class during object destruction. It is provided a single boolean argument indicating whether Perl is in global destruction. Return values and errors are ignored. sub DEMOLISH { my ($self, $global_destruct) = @_; $self->cleanup(); } =head2 Introspection and internals You can retrieve an unsorted list of valid attributes known to Class::Tiny for a class and its superclasses with the C class method. my @attrs = Class::Tiny->get_all_attributes_for("Employee"); # returns qw/name ssn timestamp/ Likewise, a hash reference of all valid attributes and default values (or code references) may be retrieved with the C class method. Any attributes without a default will be C. my $def = Class::Tiny->get_all_attribute_defaults_for("Employee"); # returns { # name => undef, # ssn => undef # timestamp => $coderef # } The C method uses two class methods, C and C to set up the C<@ISA> array and attributes. Anyone attempting to extend Class::Tiny itself should use these instead of mocking up a call to C. When the first object is created, linearized C<@ISA> and various subroutines references are cached for speed. Ensure that all inheritance and methods are in place before creating objects. (You don't want to be changing that once you create objects anyway, right?) =for Pod::Coverage new get_all_attributes_for get_all_attribute_defaults_for prepare_class create_attributes =head1 RATIONALE =head2 Why this instead of Object::Tiny or Class::Accessor or something else? I wanted something so simple that it could potentially be used by core Perl modules I help maintain (or hope to write), most of which either use L or roll-their-own OO framework each time. L and L were close to what I wanted, but lacking some features I deemed necessary, and their maintainers have an even more strict philosophy against feature creep than I have. I looked for something like it on CPAN, but after checking a dozen class creators I realized I could implement it exactly how I wanted faster than I could search CPAN for something merely sufficient. Compared to everything else, this is smaller in implementation and simpler in API. =head2 Why this instead of Moose or Moo? L and L are both excellent OO frameworks. Moose offers a powerful meta-object protocol (MOP), but is slow to start up and has about 30 non-core dependencies including XS modules. Moo is faster to start up and has about 10 pure Perl dependencies but provides no true MOP, relying instead on its ability to transparently upgrade Moo to Moose when Moose's full feature set is required. By contrast, Class::Tiny has no MOP and has B non-core dependencies for Perls in the L. It has far less code, less complexity and no learning curve. If you don't need or can't afford what Moo or Moose offer, this is a intended to be a reasonable fallback. That said, Class::Tiny offers Moose-like conventions for things like C and C for some minimal interoperability and an easier upgrade path. =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan =head1 SUPPORT =head2 Bugs / Feature Requests Please report any bugs or feature requests through the issue tracker at L. You will be notified automatically of any progress on your issue. =head2 Source Code This is open source software. The code repository is available for public review and contribution under the terms of the license. L git clone https://github.com/dagolden/Class-Tiny.git =head1 AUTHOR David Golden =head1 CONTRIBUTORS =over 4 =item * Gelu Lupas =item * Karen Etheridge =item * Matt S Trout =item * Olivier Mengué =item * Toby Inkster =back =head1 COPYRIGHT AND LICENSE This software is Copyright (c) 2013 by David Golden. This is free software, licensed under: The Apache License, Version 2.0, January 2004 =cut