sub verify_args { my $defaults = shift; # leave the rest of @_ intact. # Verify that args came in pairs. croak "Expecting key => value pairs, but got odd number of args" if @_ % 2; # Verify keys, ignore values. while (@_) { my ($var, undef) = (shift, shift); croak "Invalid parameter: '$var'" unless exists $defaults->{$var}; } } #### my %convert_defaults = ( from => 'here' to => 'eternity' thing => undef, ); sub convert { verify_args(\%convert_defaults, @_); my %args = (%convert_defaults, @_); ... } #### package KinoSearch::Util::Class; use KinoSearch::Util::ToolSet; # strict, warnings, Carp, etc. use Clone 'clone'; use KinoSearch::Util::VerifyArgs qw( verify_args ); our %instance_vars = (); sub new { my $class = shift; # leave the rest of @_ intact. # Clone the instance_vars hash and bless it. $class = ref($class) || $class; my $defaults; { no strict 'refs'; $defaults = \%{ $class . '::instance_vars' }; } my $self = clone($defaults); bless $self, $class; # Verify argument labels. eval { verify_args( $defaults, @_ ) }; if ($@) { my ( $package, $filename, $line ) = caller; die "ERROR during attempt to create object of class '$class' \n" . " at $filename line $line:\n" . " $@\n"; } # Merge var => val pairs into object. %$self = ( %$self, @_ ); $self->init_instance; return $self; } sub init_instance { } # Class method to build the %instance_vars hash sub init_instance_vars { my $package = shift; no strict 'refs'; my $first_isa = ${ $package . '::ISA' }[0]; return ( %{ $first_isa . '::instance_vars' }, @_ ); } # use to define an abstract method sub abstract_death { my ( undef, $filename, $line, $methodname ) = caller(1); die "ERROR: $methodname', called at $filename line $line, is an " . "abstract method and must be defined in a subclass."; } #### package KinoSearch::Analysis::Analyzer; use KinoSearch::Util::ToolSet; use base qw( KinoSearch::Util::Class ); our %instance_vars = __PACKAGE__->init_instance_vars( language => '', ); sub analyze { shift->abstract_death } 1; #### package KinoSearch::Analysis::PolyAnalyzer; use KinoSearch::Util::ToolSet; use base qw( KinoSearch::Analysis::Analyzer ); use KinoSearch::Analysis::LCNormalizer; use KinoSearch::Analysis::Tokenizer; use KinoSearch::Analysis::Stemmer; our %instance_vars = __PACKAGE__->init_instance_vars( analyzers => undef, ); sub init_instance { my $self = shift; my $language = $self->{language} = lc( $self->{language} ); if ( !defined $self->{analyzers} ) { croak("Must specify either 'language' or 'analyzers'") unless $language; $self->{analyzers} = [ KinoSearch::Analysis::LCNormalizer->new( language => $language ), KinoSearch::Analysis::Tokenizer->new( language => $language ), KinoSearch::Analysis::Stemmer->new( language => $language ), ]; } } sub analyze { my ( $self, $field ) = @_; $_->analyze($field) for @{ $self->{analyzers} }; } 1;