]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/Text.pm
assume unknown encodings are UTF-8
[debbugs.git] / Debbugs / Text.pm
1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later
3 # version at your option.
4 # See the file README and COPYING for more information.
5 #
6 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
7
8 package Debbugs::Text;
9
10 use warnings;
11 use strict;
12
13 =head1 NAME
14
15 Debbugs::Text -- General routines for text templates
16
17 =head1 SYNOPSIS
18
19  use Debbugs::Text qw(:templates);
20  print fill_in_template(template => 'cgi/foo');
21
22 =head1 DESCRIPTION
23
24 This module is a replacement for parts of common.pl; subroutines in
25 common.pl will be gradually phased out and replaced with equivalent
26 (or better) functionality here.
27
28 =head1 BUGS
29
30 None known.
31
32 =cut
33
34
35 use vars qw($DEBUG $VERSION @EXPORT_OK %EXPORT_TAGS @EXPORT @ISA);
36 use Exporter qw(import);
37
38 BEGIN {
39      $VERSION = 1.00;
40      $DEBUG = 0 unless defined $DEBUG;
41
42      @EXPORT = ();
43      %EXPORT_TAGS = (templates => [qw(fill_in_template)],
44                     );
45      @EXPORT_OK = ();
46      Exporter::export_ok_tags(qw(templates));
47      $EXPORT_TAGS{all} = [@EXPORT_OK];
48 }
49
50 use Text::Template;
51
52 use Storable qw(dclone);
53
54 use Debbugs::Config qw(:config);
55
56 use Params::Validate qw(:types validate_with);
57 use Carp;
58 use IO::File;
59 use Data::Dumper;
60
61 our %tt_templates;
62 our %filled_templates;
63 our $language;
64
65 # This function is what is called when someone does include('foo/bar')
66 # {include('foo/bar')}
67
68 sub include {
69      my $template = shift;
70      $filled_templates{$template}++;
71      print STDERR "include template $template language $language\n" if $DEBUG;
72      # Die if we're in a template loop
73      die "Template loop with $template" if $filled_templates{$template} > 10;
74      my $filled_tmpl = '';
75      eval {
76           $filled_tmpl = fill_in_template(template  => $template,
77                                           variables => {},
78                                           language  => $language,
79                                          );
80      };
81      if ($@) {
82           print STDERR "failed to fill template $template: $@";
83      }
84      print STDERR "failed to fill template $template\n" if $filled_tmpl eq '' and $DEBUG;
85      print STDERR "template $template '$filled_tmpl'\n" if $DEBUG;
86      $filled_templates{$template}--;
87      return $filled_tmpl;
88 };
89
90
91 =head2 fill_in_template
92
93      print fill_in_template(template => 'template_name',
94                             variables => \%variables,
95                             language  => '..'
96                            );
97
98 Reads a template from disk (if it hasn't already been read in) and
99 fills the template in.
100
101 =cut
102
103
104 sub fill_in_template{
105      my %param = validate_with(params => \@_,
106                                spec   => {template => SCALAR|HANDLE|SCALARREF,
107                                           variables => {type => HASHREF,
108                                                         default => {},
109                                                        },
110                                           language  => {type => SCALAR,
111                                                         default => 'en_US',
112                                                        },
113                                           output    => {type => HANDLE,
114                                                         optional => 1,
115                                                        },
116                                           safe      => {type => OBJECT|UNDEF,
117                                                         optional => 1,
118                                                        },
119                                           hole_var  => {type => HASHREF,
120                                                         optional => 1,
121                                                        },
122                                          },
123                               );
124      if ($DEBUG) {
125           print STDERR "fill_in_template ";
126           print STDERR join(" ",map {exists $param{$_}?"$_:$param{$_}":()} keys %param);
127           print STDERR "\n";
128      }
129
130      # Get the text
131      my $tt_type = '';
132      my $tt_source;
133      if (ref($param{template}) eq 'GLOB' or
134          ref(\$param{template}) eq 'GLOB') {
135           $tt_type = 'FILE_HANDLE';
136           $tt_source = $param{template};
137           binmode($tt_source,":encoding(UTF-8)");
138      }
139      elsif (ref($param{template}) eq 'SCALAR') {
140           $tt_type = 'STRING';
141           $tt_source = ${$param{template}};
142      }
143      else {
144           $tt_type = 'FILE';
145           $tt_source = _locate_text($param{template},$param{language});
146      }
147      if (not defined $tt_source) {
148           die "Unable to find template $param{template} with language $param{language}";
149      }
150
151      $language = $param{language};
152      my $tt;
153      if ($tt_type eq 'FILE' and
154          defined $tt_templates{$tt_source} and
155          ($tt_templates{$tt_source}{mtime} + 60) < time and
156          (stat $tt_source)[9] <= $tt_templates{$tt_source}{mtime}
157         ) {
158           $tt = $tt_templates{$tt_source}{template};
159      }
160      else {
161          my $passed_source = $tt_source;
162          my $passed_type = $tt_type;
163           if ($tt_type eq 'FILE') {
164                $tt_templates{$tt_source}{mtime} =
165                     (stat $tt_source)[9];
166                $passed_source = IO::File->new($tt_source,'r');
167                binmode($passed_source,":encoding(UTF-8)");
168                $passed_type = 'FILEHANDLE';
169           }
170           $tt = Text::Template->new(TYPE => $passed_type,
171                                     SOURCE => $passed_source,
172                                     UNTAINT => 1,
173                                    );
174           if ($tt_type eq 'FILE') {
175                $tt_templates{$tt_source}{template} = $tt;
176           }
177      }
178      if (not defined $tt) {
179           die "Unable to create Text::Template for $tt_type:$tt_source";
180      }
181      my $ret = $tt->fill_in(PACKAGE => 'DTT',
182                             HASH => {%{$param{variables}//{}},
183                                      (map {my $t = $_; $t =~ s/^\&//; ($t => $param{hole_var}{$_})}
184                                       keys %{$param{hole_var}//{}}),
185                                      include => \&Debbugs::Text::include,
186                                      config  => \%config,
187                                     },
188                             defined $param{output}?(OUTPUT=>$param{output}):(),
189                            );
190      if (not defined $ret) {
191           print STDERR $Text::Template::ERROR;
192           return '';
193      }
194      if ($DEBUG) {
195           no strict 'refs';
196           no warnings 'uninitialized';
197           print STDERR "Variables for $param{template}\n";
198      }
199
200      return $ret;
201 }
202
203 sub _locate_text{
204      my ($template,$language) = @_;
205      $template =~ s/\.tmpl$//g;
206      # if a language doesn't exist, use the en_US template
207      if (not -e $config{template_dir}.'/'.$language.'/'.$template.'.tmpl') {
208           $language = 'en_US';
209      }
210      my $loc = $config{template_dir}.'/'.$language.'/'.$template.'.tmpl';
211      if (not -e $loc) {
212           print STDERR "Unable to locate template $loc\n";
213           return undef;
214      }
215      return $loc;
216 }
217
218 1;