package Class::Template; require 5.000; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(members struct); use strict; # Template.pm --- struct/member template builder # 12mar95 # Dean Roehrich # # changes/bugs fixed since 28nov94 version: # - podified # changes/bugs fixed since 21nov94 version: # - Fixed examples. # changes/bugs fixed since 02sep94 version: # - Moved to Class::Template. # changes/bugs fixed since 20feb94 version: # - Updated to be a more proper module. # - Added "use strict". # - Bug in build_methods, was using @var when @$var needed. # - Now using my() rather than local(). # # Uses perl5 classes to create nested data types. # This is offered as one implementation of Tom Christiansen's "structs.pl" # idea. =head1 NAME Class::Template - struct/member template builder =head1 EXAMPLES =item * Example 1 use Class::Template; struct( rusage => { ru_utime => timeval, ru_stime => timeval, }); struct( timeval => [ tv_secs => '$', tv_usecs => '$', ]); my $s = new rusage; =item * Example 2 package OBJ; use Class::Template; members OBJ { 'a' => '$', 'b' => '$', }; members OBJ2 { 'd' => '@', 'c' => '$', }; package OBJ2; @ISA = (OBJ); sub new { my $r = InitMembers( &OBJ::InitMembers() ); bless $r; } =head1 NOTES Use '%' if the member should point to an anonymous hash. Use '@' if the member should point to an anonymous array. When using % and @ the method requires one argument for the key or index into the hash or array. Prefix the %, @, or $ with '*' to indicate you want to retrieve pointers to the values rather than the values themselves. =cut Var: { $Class::Template::print = 0; sub printem { $Class::Template::print++ } } sub struct { my( $struct, $ref ) = @_; my @methods = (); my %refs = (); my %arrays = (); my %hashes = (); my $out = ''; $out = "{\n package $struct;\n sub new {\n"; parse_fields( $ref, \$out, \@methods, \%refs, \%arrays, \%hashes, 0 ); $out .= " bless \$r;\n }\n"; build_methods( $ref, \$out, \@methods, \%refs, \%arrays, \%hashes ); $out .= "}\n1;\n"; ( $Class::Template::print ) ? print( $out ) : eval $out; } sub members { my( $pkg, $ref ) = @_; my @methods = (); my %refs = (); my %arrays = (); my %hashes = (); my $out = ''; $out = "{\n package $pkg;\n sub InitMembers {\n"; parse_fields( $ref, \$out, \@methods, \%refs, \%arrays, \%hashes, 1 ); $out .= " bless \$r;\n }\n"; build_methods( $ref, \$out, \@methods, \%refs, \%arrays, \%hashes ); $out .= "}\n1;\n"; ( $Class::Template::print ) ? print( $out ) : eval $out; } sub parse_fields { my( $ref, $out, $methods, $refs, $arrays, $hashes, $member ) = @_; my $type = ref $ref; my @keys; my $val; my $cnt = 0; my $idx = 0; my( $cmt, $n ); if( $type eq 'HASH' ){ if( $member ){ $$out .= " my(\$r) = \@_ ? shift : {};\n"; } else{ $$out .= " my(\$r) = {};\n"; } @keys = keys %$ref; foreach (@keys){ $val = $ref->{$_}; if( $val =~ /^\*(.)/ ){ $refs->{$_}++; $val = $1; } if( $val eq '@' ){ $$out .= " \$r->{'$_'} = [];\n"; $arrays->{$_}++; } elsif( $val eq '%' ){ $$out .= " \$r->{'$_'} = {};\n"; $hashes->{$_}++; } elsif( $val ne '$' ){ $$out .= " \$r->{'$_'} = \&${val}::new();\n"; } else{ $$out .= " \$r->{'$_'} = undef;\n"; } push( @$methods, $_ ); } } elsif( $type eq 'ARRAY' ){ if( $member ){ $$out .= " my(\$r) = \@_ ? shift : [];\n"; } else{ $$out .= " my(\$r) = [];\n"; } while( $idx < @$ref ){ $n = $ref->[$idx]; push( @$methods, $n ); $val = $ref->[$idx+1]; $cmt = "# $n"; if( $val =~ /^\*(.)/ ){ $refs->{$n}++; $val = $1; } if( $val eq '@' ){ $$out .= " \$r->[$cnt] = []; $cmt\n"; $arrays->{$n}++; } elsif( $val eq '%' ){ $$out .= " \$r->[$cnt] = {}; $cmt\n"; $hashes->{$n}++; } elsif( $val ne '$' ){ $$out .= " \$r->[$cnt] = \&${val}::new();\n"; } else{ $$out .= " \$r->[$cnt] = undef; $cmt\n"; } ++$cnt; $idx += 2; } } } sub build_methods { my( $ref, $out, $methods, $refs, $arrays, $hashes ) = @_; my $type = ref $ref; my $elem = ''; my $cnt = 0; my( $pre, $pst, $cmt, $idx ); foreach (@$methods){ $pre = $pst = $cmt = $idx = ''; if( defined $refs->{$_} ){ $pre = "\\("; $pst = ")"; $cmt = " # returns ref"; } $$out .= " sub $_ {$cmt\n my \$r = shift;\n"; if( $type eq 'ARRAY' ){ $elem = "[$cnt]"; ++$cnt; } elsif( $type eq 'HASH' ){ $elem = "{'$_'}"; } if( defined $arrays->{$_} ){ $$out .= " my \$i;\n"; $$out .= " \@_ ? (\$i = shift) : return \$r->$elem;\n"; $idx = "->[\$i]"; } elsif( defined $hashes->{$_} ){ $$out .= " my \$i;\n"; $$out .= " \@_ ? (\$i = shift) : return \$r->$elem;\n"; $idx = "->{\$i}"; } $$out .= " \@_ ? (\$r->$elem$idx = shift) : $pre\$r->$elem$idx$pst;\n"; $$out .= " }\n"; } } 1;