File Coverage

blib/lib/Dancer/Plugin/Negotiate.pm
Criterion Covered Total %
statement 47 48 97.9
branch 18 26 69.2
condition 4 7 57.1
subroutine 10 11 90.9
pod n/a
total 79 92 85.8


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Negotiate;
2              
3 5     5   1088021 use Modern::Perl;
  5         40496  
  5         31  
4 5     5   879 use Carp 'croak';
  5         12  
  5         266  
5 5     5   2223 use Dancer ':syntax';
  5         734174  
  5         34  
6 5     5   6347 use Dancer::Plugin;
  5         6850  
  5         354  
7 5     5   14045 use HTTP::Negotiate ();
  5         167  
  5         3803  
8              
9             =head1 NAME
10              
11             Dancer::Plugin::Negotiate - Content negotiation plugin for Dancer
12              
13             =head1 VERSION
14              
15             Version 0.031
16              
17             =cut
18              
19             our $VERSION = '0.031';
20              
21             =head1 SYNOPSIS
22              
23             use Dancer::Plugin::Negotiate;
24              
25             $variant = choose_variant(
26             var1 => {
27             Quality => 1.000,
28             Type => 'text/html',
29             Charset => 'iso-8859-1',
30             Language => 'en',
31             Size => 3000
32             },
33             var2 => {
34             Quality => 0.950,
35             Type => 'text/plain',
36             Charset => 'us-ascii',
37             Language => 'no',
38             Size => 400
39             },
40             var3 => {
41             Quality => 0.3,
42             Type => 'image/gif',
43             Size => 43555
44             }
45             ); # returns 'var1' or 'var2' or 'var3' or undef
46              
47             =head1 DESCRIPTION
48              
49             This module is a wrapper for L.
50              
51             =head1 KEYWORDS
52              
53             =head2 C<< choose_variant(%variants) >>
54              
55             C<%options> is a hash like this:
56              
57             %variants = (
58             $identifier => \%options
59             )
60            
61             The key C<$identifier> is a string that will be returned by C.
62              
63             Valid keywords of hashref C<\%options>:
64              
65             =over 4
66              
67             =item Quality
68              
69             A float point value between I<0.000> and I<1.000>, describing the source quality (defaults to 1)
70              
71             =item Type
72              
73             A MIME media type (with no charset attributes, but other attributes like I)
74              
75             =item Encoding
76              
77             An encoding like I or I
78              
79             =item Charset
80              
81             An encoding like I or I
82              
83             =item Language
84              
85             A language tag conforming to RFC 3066
86              
87             =item Size
88              
89             Number of bytes used to represent
90              
91             =back
92              
93             Returns C if no variant matched.
94              
95             See L for more information.
96              
97             =cut
98              
99             sub choose_variant {
100 7     7   4746 my $variants = [];
101 7         33 while (my ($variant, $options) = (shift, shift)) {
102 24 100 66     112 last unless defined $variant and defined $options;
103 17         91 push @$variants => [
104             $variant,
105             $options->{Quality},
106             $options->{Type},
107             $options->{Encoding},
108             $options->{Charset},
109             $options->{Language},
110             $options->{Size}
111             ];
112             }
113 7         32 return HTTP::Negotiate::choose($variants, Dancer::SharedData->request->headers);
114             }
115              
116             =head2 C<< apply_variant(%options) >>
117              
118             This method behaves like C but sets the according response headers if a variant matched.
119              
120             =cut
121              
122             sub apply_variant {
123 5     5   8216 local %_ = @_;
124 5         19 my $variant = scalar choose_variant(@_);
125 5 50       865 return undef unless defined $variant;
126 5         11 my %options = %{$_{$variant}};
  5         25  
127 5         21 my $R = Dancer::SharedData->response;
128 5 100       46 $R->header('Content-Type' => $options{Type} ) if defined $options{Type};
129 5 50       115 $R->header('Content-Encoding' => $options{Encoding}) if defined $options{Encoding};
130 5 100       20 $R->header('Content-Charset' => $options{Charset} ) if defined $options{Charset};
131 5 100       138 $R->header('Content-Language' => $options{Language}) if defined $options{Language};
132 5         169 return $variant;
133             }
134              
135             =head2 C<< negotiate($template_name) >>
136              
137             This method returns C<$template_name> with a suffixed language tag. The file needs to exist. This method behaves similiary to mod_negotiate of apache httpd's.
138              
139             Language tags must be specified in plugin settings and ordered by priority:
140              
141             plugin:
142             Negotiate:
143             languages:
144             - en
145             - de
146             - fr
147              
148             The result of this method can be propagated to C in order to render a localized version of the file.
149              
150             get '/index' => sub {
151             return template negotiate 'index';
152             }; # renders index.de.tt or index.en.tt or index.fr.tt or index.tt
153              
154             Falls back to C<$template_name> if negotiaten fails.
155              
156             Hint: additional arguments applies to C:
157              
158             template negotiate index => { foo => 'bar' };
159             # is the same as
160             template(negotiate('index'), { foo => 'bar' });
161              
162             =cut
163              
164             sub _langmap {
165 0   50 0   0 my $grep = shift || sub { 1 };
  3     3   10  
166 3   50     16 my $langs = plugin_setting->{languages} || {};
167 6 50       80 return grep defined, map {
    50          
168 3         94 my $opt = {
169             Language => scalar(ref $_ eq 'HASH' ? (keys %$_)[0] : $_ ),
170             Quality => scalar(ref $_ eq 'HASH' ? (values %$_)[0] : 1 )
171             };
172 6         12 my $id = lc $opt->{Language};
173 6 50       10 $grep->($opt) ? ( $id => $opt ) : undef;
174             } @$langs;
175             }
176              
177             sub negotiate($;) {
178 3     3   12384 my ($tplname, @rest) = @_;
179 3         11 my $engine = engine('template');
180             my @langmap = _langmap(sub {
181 6     6   9 my $lang = shift->{Language};
182 6         30 my $view = $engine->view($tplname.'.'.$lang);
183 6 50       605 defined $view and $engine->view_exists($view) ? 1 : 0
    50          
184 3         78 });
185 3         67 my $lang = apply_variant(0, {}, @langmap);
186 3 50       17 return ($tplname, @rest) unless defined $lang;
187 3 100       16 return ($tplname, @rest) unless $lang;
188 2         12 return ($tplname.'.'.$lang, @rest);
189             }
190              
191             register choose_variant => \&choose_variant;
192             register apply_variant => \&apply_variant;
193             register negotiate => \&negotiate;
194              
195             register_plugin;
196             1;
197              
198             =head1 AUTHOR
199              
200             David Zurborg, C<< >>
201              
202             =head1 BUGS
203              
204             Please report any bugs or feature requests through my project management tool
205             at L. I
206             will be notified, and then you'll automatically be notified of progress on
207             your bug as I make changes.
208              
209             =head1 SUPPORT
210              
211             You can find documentation for this module with the perldoc command.
212              
213             perldoc Dancer::Plugin::Negotiate
214              
215             You can also look for information at:
216              
217             =over 4
218              
219             =item * Redmine: Homepage of this module
220              
221             L
222              
223             =item * RT: CPAN's request tracker
224              
225             L
226              
227             =item * AnnoCPAN: Annotated CPAN documentation
228              
229             L
230              
231             =item * CPAN Ratings
232              
233             L
234              
235             =item * Search CPAN
236              
237             L
238              
239             =back
240              
241             =head1 COPYRIGHT & LICENSE
242              
243             Copyright 2014 David Zurborg, all rights reserved.
244              
245             This program is released under the following license: open-source
246              
247             =cut
248              
249             1;