File Coverage

blib/lib/Dancer2/Plugin/Negotiate.pm
Criterion Covered Total %
statement 48 50 96.0
branch 18 26 69.2
condition 4 7 57.1
subroutine 9 10 90.0
pod n/a
total 79 93 84.9


line stmt bran cond sub pod time code
1 3     3   1690484 use strict;
  3         9  
  3         79  
2 3     3   17 use warnings;
  3         5  
  3         115  
3              
4             package Dancer2::Plugin::Negotiate;
5              
6             # ABSTRACT: Content negotiation plugin for Dancer2
7              
8 3     3   2313 use Dancer2::Plugin;
  3         7476  
  3         22  
9              
10 3     3   15185 use HTTP::Negotiate;
  3         92  
  3         2086  
11              
12             our $VERSION = '0.002'; # VERSION
13              
14             sub choose_variant {
15 7     7   113713 my $dsl = shift;
16 7         24 my $app = $dsl->app;
17 7         18 my $variants = [];
18 7         37 while ( my ( $variant, $options ) = ( shift, shift ) ) {
19 24 100 66     136 last unless defined $variant and defined $options;
20             push @$variants => [
21             $variant, $options->{Quality}, $options->{Type},
22             $options->{Encoding}, $options->{Charset}, $options->{Language},
23             $options->{Size}
24 17         106 ];
25             }
26 7         60 HTTP::Negotiate::choose( $variants, $app->request->headers, );
27             }
28              
29             sub apply_variant {
30 5     5   112821 my ( $dsl, %variants ) = @_;
31 5         23 my $app = $dsl->app;
32 5         26 my $variant = scalar choose_variant(@_);
33 5 50       1812 return unless defined $variant;
34 5         7 my %options = %{ $variants{$variant} };
  5         24  
35 5         137 my $R = $app->response;
36 5 100       78 $R->header( 'Content-Type' => $options{Type} ) if defined $options{Type};
37             $R->header( 'Content-Encoding' => $options{Encoding} )
38 5 50       1717 if defined $options{Encoding};
39             $R->header( 'Content-Charset' => $options{Charset} )
40 5 100       62 if defined $options{Charset};
41             $R->header( 'Content-Language' => $options{Language} )
42 5 100       239 if defined $options{Language};
43 5         557 $variant;
44             }
45              
46             sub _langmap {
47 0   50 0   0 my $grep = shift || sub { 1 };
  3     3   16  
48 3   50     15 my $langs = plugin_setting->{languages} || {};
49             return grep defined, map {
50 3 50       550 my $opt = {
  6 50       38  
51             Language => scalar( ref $_ eq 'HASH' ? ( keys %$_ )[0] : $_ ),
52             Quality => scalar( ref $_ eq 'HASH' ? ( values %$_ )[0] : 1 )
53             };
54 6         15 my $id = lc $opt->{Language};
55 6 50       15 $grep->($opt) ? ( $id => $opt ) : undef;
56             } @$langs;
57             }
58              
59             sub negotiate {
60 3     3   124523 my ( $dsl, $tplname, @rest ) = @_;
61 3         12 my $app = $dsl->app;
62 3         13 my $engine = $app->engine('template');
63             my @langmap = _langmap(
64             sub {
65 6     6   14 my $lang = shift->{Language};
66 6         33 my $view = $engine->view_pathname( $tplname . '.' . $lang );
67 6 50       1223 defined $view and -e $view ? 1 : 0;
    50          
68             }
69 3         160 );
70 3         24 my $lang = apply_variant( $dsl, 0, {}, @langmap );
71 3 100       18 $tplname .= '.' . $lang if $lang;
72 3 50       7 if (wantarray) {
73 3         22 return ( $tplname, @rest );
74             }
75             else {
76 0           return $tplname;
77             }
78             }
79              
80             register
81             choose_variant => \&choose_variant,
82             { is_global => 0 };
83             register
84             apply_variant => \&apply_variant,
85             { is_global => 0 };
86             register
87             negotiate => \&negotiate,
88             { is_global => 0 };
89              
90             register_plugin;
91              
92             1;
93              
94             __END__
95              
96             =pod
97              
98             =head1 NAME
99              
100             Dancer2::Plugin::Negotiate - Content negotiation plugin for Dancer2
101              
102             =head1 VERSION
103              
104             version 0.002
105              
106             =head1 SYNOPSIS
107              
108             use Dancer2::Plugin::Negotiate;
109            
110             get '...' => sub {
111             choose_variant(
112             var1 => {
113             Quality => 1.000,
114             Type => 'text/html',
115             Charset => 'iso-8859-1',
116             Language => 'en',
117             Size => 3000
118             },
119             var2 => {
120             Quality => 0.950,
121             Type => 'text/plain',
122             Charset => 'us-ascii',
123             Language => 'no',
124             Size => 400
125             },
126             var3 => {
127             Quality => 0.3,
128             Type => 'image/gif',
129             Size => 43555
130             }
131             ); # returns 'var1' or 'var2' or 'var3' or undef
132             }
133              
134             =head1 DESCRIPTION
135              
136             This module is a wrapper for L<HTTP::Negotiate>.
137              
138             =head1 METHODS
139              
140             =head2 C<< choose_variant(%variants) >>
141              
142             C<%options> is a hash like this:
143              
144             %variants = (
145             $identifier => \%options
146             )
147              
148             The key C<$identifier> is a string that will be returned by C<choose_variant()>.
149              
150             Valid keywords of hashref C<\%options>:
151              
152             =over 4
153              
154             =item Quality
155              
156             A float point value between I<0.000> and I<1.000>, describing the source quality (defaults to 1)
157              
158             =item Type
159              
160             A MIME media type (with no charset attributes, but other attributes like I<version>)
161              
162             =item Encoding
163              
164             An encoding like I<gzip> or I<compress>
165              
166             =item Charset
167              
168             An encoding like I<utf-8> or I<iso-8859-1>
169              
170             =item Language
171              
172             A language tag conforming to RFC 3066
173              
174             =item Size
175              
176             Number of bytes used to represent
177              
178             =back
179              
180             Returns C<undef> if no variant matched.
181              
182             See L<HTTP::Negotiate> for more information.
183              
184             =head2 C<< apply_variant(%options) >>
185              
186             This method behaves like C<choose_variant> but sets the according response headers if a variant matched.
187              
188             =head2 C<< negotiate($template_name) >>
189              
190             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.
191              
192             Language tags must be specified in plugin settings and ordered by priority:
193              
194             plugins:
195             Negotiate:
196             languages:
197             - en
198             - de
199             - fr
200              
201             The result of this method can be propagated to C<template()> in order to render a localized version of the file.
202              
203             get '/index' => sub {
204             return template negotiate 'index';
205             }; # renders index.de.tt or index.en.tt or index.fr.tt or index.tt
206              
207             Falls back to C<$template_name> if negotiaten fails.
208              
209             Hint: additional arguments applies to C<template()>:
210              
211             template negotiate index => { foo => 'bar' };
212             # is the same as
213             template(negotiate('index'), { foo => 'bar' });
214              
215             =head1 BUGS
216              
217             Please report any bugs or feature requests on the bugtracker website
218             https://github.com/zurborg/libdancer2-plugin-negotiate-perl/issues
219              
220             When submitting a bug or request, please include a test-file or a
221             patch to an existing test-file that illustrates the bug or desired
222             feature.
223              
224             =head1 AUTHOR
225              
226             David Zurborg <zurborg@cpan.org>
227              
228             =head1 COPYRIGHT AND LICENSE
229              
230             This software is Copyright (c) 2015 by David Zurborg.
231              
232             This is free software, licensed under:
233              
234             The ISC License
235              
236             =cut