package Yeti; use strict; use FileHandle; #---------------------------------------------------------------------- # Yet another interpolator sub interpolate { my ($template, $hash, $visible) = @_; if (! ref($template)) { my @tokens = grep (length($_), split (/^(\#[^\n]*)\n/m, $template)); $template = {index => 0, tokens => \@tokens}; $visible = 1; } my $output; my $saved_test; while ($template->{index} < @{$template->{tokens}}) { my $token = $template->{tokens}->[$template->{index}++]; if ($token =~ /^\#(\w+)\s*(\S*)/) { my $cmd = $1; my $name = $2; if ($cmd eq 'include') { if ($visible) { my $fd = FileHandle->new("<$name"); if ($fd) { my $source = do {local $/; <$fd>}; $output .= interpolate ($source, $hash); } } } elsif ($cmd eq 'for') { my $subarray = $hash->{$name}; my $saved_index = $template->{index}; if ($visible && $subarray && @$subarray) { foreach my $subhash (@$subarray) { $template->{index} = $saved_index; my %hash = (%$hash, %$subhash); $output .= interpolate ($template, \%hash, $visible); } } else { $output .= interpolate ($template, {}, 0); } } elsif ($cmd eq 'endfor') { return $output; } elsif ($cmd eq 'if') { my $value = $hash->{$name}; if (ref $value eq 'ARRAY') { $saved_test = @$value ? 1 : 0; } elsif (ref $value eq 'HASH') { $saved_test = %$value ? 1: 0; } else { $saved_test = $value ? 1 : 0; } $output .= interpolate ($template, $hash, $visible && $saved_test); } elsif ($cmd eq 'else') { if (defined $saved_test) { $saved_test = ! $saved_test; $output .= interpolate ($template, $hash, $visible && $saved_test); } else { $template->{index} --; return $output; } } elsif ($cmd eq 'endif') { if (defined $saved_test) { undef $saved_test; } else { $template->{index} --; return $output; } } elsif ($cmd eq 'switch') { $saved_test = $hash->{$name}; } elsif ($cmd eq 'case') { if (defined $saved_test) { $output .= interpolate ($template, $hash, $visible && $saved_test eq $name); $saved_test = '' if $saved_test eq $name; } else { $template->{index} --; return $output; } } elsif ($cmd eq 'default') { if (defined $saved_test) { $output .= interpolate ($template, $hash, $visible && $saved_test); $saved_test = ''; } else { $template->{index} --; return $output; } } elsif ($cmd eq 'endswitch') { if (defined $saved_test) { undef $saved_test; } else { $template->{index} --; return $output; } } else { $output .= "$token\n"; } } elsif ($visible) { $token =~ s/\$\(([^\)]*)\)/$hash->{$1}/ge; $output .= $token; } } return $output; } 1; __END__ =pod =head1 NAME Yeti - Yet another html interpolator =head1 SYNOPSIS use Yeti; my $source = join ('', <$fd>); my $output = Yeti::interpolate ($source, $hash); =head1 DESCRIPTION Yeti implements a text macro preprocessor. The preprocessor was developed for creating html files and use in cgi scripts, but is general purpose and can be used for other tasks. If a line starts with a sharp character (C<#>) it is interpreted as a command. The command name must immediately follow the sharp character and continues up to the first white space character. The text following the initial span of whitespace is the command argument. The argument continues up to the end of the line. Macros start with the two characters dollar sign and left parenthesis, (C<$(>). and continue until the first right parenthesis, C<)>. For example, $(SUMMARY) is an example of a macro. A hash contains values that are substituted for the macro names. This hash is an argument to the interpolator call. If a macro is not found in the hash, it replaces the hash with an empty string instead. In addition to macro substitution, Yeti permits include files and flow control with the C<#if>, C<#for>, and C<#switch> commands. =over 4 =item #include The text in the include file will be processed as part of the template. This command =item #if The text until the matching C<#endif> is included only if the argument to the C<#if> command is true. If false, the text is skipped. The C<#if> command can contain an C<#else>, in which case the text before the C<#else> is included if the variable in the C<#if> command is true and the text after the C<#else> is included if it is false. The argument to the C<#if> command is the name of a value in the hash. The value is false if it is not found in the hash. Or if it is a scalar and its value is the empty string or zero. Or if it is a reference to an empty list or hash. Otherwise it is true. #if HIGHLIGHT $(TEXT) #else $(TEXT) #endif =item #for Expand the text between the C<#for> and <#endfor> commands several times. The for command takes a name in the hash as its argument. The value of this name should be a reference to a list. It will expand the text in the for block once for each element in the list. Within the C<#for> block, the element of the list is treated as if it were the hash. This is especially useful for displaying lists of hashes. For example, suppose the name PHONELIST points to an array. This array is a list of hashes, and each hash has two entries, NAME and PHONE. Then the code #for PHONELIST

$(NAME)
$(PHONE)

#endfor displays the entire phone list. =item switch Choose a section of text based on the value of a variable. Each section of text is proceded by a C<#case> statement. If the value on the case statement matches the value of the variable named in the switch statement, that section of text is used. There is also a C<#default> statement. The text following the default statement is used if none of the case statements were matched. The switch ends with an <#endswitch> statement. #switch WEATHER #case sunny

The picnic is schedlued for this Wednesday

#case rainy

The picnic will be postponed until next Wednesday

#default

The picnic is scheduled for Wednesday, but will be postponed a week if it rains.

#endswitch =back =head1 AUTHOR Bernie Simon