File Coverage

blib/lib/Mojolicious/Plugin/Ical.pm
Criterion Covered Total %
statement 86 86 100.0
branch 7 12 58.3
condition 23 57 40.3
subroutine 12 12 100.0
pod 1 1 100.0
total 129 168 76.7


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::Ical;
2              
3             =head1 NAME
4              
5             Mojolicious::Plugin::Ical - Generate .ical documents
6              
7             =head1 VERSION
8              
9             0.03
10              
11             =head1 SYNOPSIS
12              
13             =head2 Application
14              
15             use Mojolicious::Lite;
16             plugin ical => {
17             properties => {
18             calscale => "GREGORIAN" # default GREGORIAN
19             method => "REQUEST", # default PUBLISH
20             prodid => "-//ABC Corporation//NONSGML My Product//EN",
21             version => "1.0", # default to 2.0
22             x_wr_caldesc => "Some description",
23             x_wr_calname => "My calender",
24             x_wr_timezone => "EDT", # default to timezone for localhost
25             }
26             };
27              
28             get '/calendar' => sub {
29             my $c = shift;
30             $c->reply->ical({
31             events => [
32             {
33             created => $date,
34             description => $str, # http://www.kanzaki.com/docs/ical/description.html
35             dtend => $date,
36             dtstamp => $date, # UTC time format, defaults to "now"
37             dtstart => $date,
38             last_modified => $date, # defaults to "now"
39             location => $str, # http://www.kanzaki.com/docs/ical/location.html
40             sequence => $int, # default 0
41             status => $str, # default CONFIRMED
42             summary => $str, # http://www.kanzaki.com/docs/ical/summary.html
43             transp => $str, # default OPAQUE
44             uid => $str, # default to md5 of the values @hostname
45             },
46             ...
47             ],
48             });
49             };
50              
51             # or using respond_to()
52             get '/events' => sub {
53             my $c = shift;
54             my $ical = { events => [...] };
55             $c->respond_to(
56             ical => {handler => 'ical', ical => $ical},
57             json => {json => $ical}
58             );
59             };
60              
61             =head1 DESCRIPTION
62              
63             L is a L plugin for generating
64             L documents.
65              
66             This plugin will...
67              
68             =over 4
69              
70             =item *
71              
72             Add the helper L.
73              
74             =item *
75              
76             Add ".ical" type to L.
77              
78             =item *
79              
80             Add a handler "ical" to L.
81              
82             =back
83              
84             =cut
85              
86 2     2   1045 use Mojo::Base 'Mojolicious::Plugin';
  2         2  
  2         10  
87 2     2   281 use POSIX ();
  2         2  
  2         20  
88 2     2   877 use Sys::Hostname ();
  2         1434  
  2         32  
89 2     2   772 use Text::vFile::asData;
  2         7218  
  2         10  
90              
91             our $VERSION = '0.03';
92              
93             =head1 HELPERS
94              
95             =head2 reply.ical
96              
97             $c = $c->reply->ical({ events => [...], properties => {...} });
98              
99             Will render a iCal document with the Content-Type "text/calender".
100              
101             C is an array ref of calendar events.
102             C will override the defaults given to L.
103              
104             See L for more details.
105              
106             =head1 METHODS
107              
108             =head2 register
109              
110             plugin ical => {properties => {...}};
111              
112             Register L helper.
113              
114             =cut
115              
116             sub register {
117 2     2 1 60 my ($self, $app, $config) = @_;
118              
119 2   50     13 $config->{handler} //= 'ical';
120              
121 2   50     12 $self->{properties} = $config->{properties} || {};
122 2   50     11 $self->{properties}{calscale} ||= 'GREGORIAN';
123 2   50     8 $self->{properties}{method} ||= 'PUBLISH';
124 2   33     10 $self->{properties}{prodid} ||= sprintf '-//%s//NONSGML %s//EN', Sys::Hostname::hostname, $app->moniker;
125 2   50     38 $self->{properties}{version} ||= '2.0';
126 2   50     4 $self->{properties}{x_wr_caldesc} ||= '';
127 2   33     10 $self->{properties}{x_wr_calname} ||= $app->moniker;
128 2   33     106 $self->{properties}{x_wr_timezone} ||= POSIX::strftime('%Z', localtime);
129              
130 2   33     19 $self->{vfile} ||= Text::vFile::asData->new;
131              
132 2     1   33 $app->helper('reply.ical' => sub { $self->_reply_ical(@_) });
  1         7755  
133 2         58 $app->types->type(ical => 'text/calendar');
134              
135 2 50       114 if ($config->{handler}) {
136             $app->renderer->add_handler(
137             $config->{handler},
138             sub {
139 1     1   15198 my ($renderer, $c, $output, $options) = @_;
140 1 50       3 return undef unless my $ical = $c->stash('ical');
141 1         12 $$output = join '', map {"$_\n"} $self->_render_ical($c, $ical);
  22         216  
142 1         4 return 1;
143             }
144 2         4 );
145             }
146             }
147              
148             sub _event_to_properties {
149 2     2   7 my ($event, $defaults) = @_;
150 2         2 my $properties = {};
151              
152 2         5 for my $k (keys %$event) {
153 14   50     27 my $v = $event->{$k} //= '';
154 14         23 my $p = _vkey($k);
155 14 100       33 if (UNIVERSAL::isa($v, 'Mojo::Date')) {
156 6         11 $v = $v->to_datetime;
157 6         68 $v =~ s![:-]!!g; # 1994-11-06T08:49:37Z => 19941106T084937Z
158             }
159 14         37 $properties->{$p} = [{value => $v}];
160             }
161              
162 2   50     13 $properties->{DTSTAMP} ||= [{value => $defaults->{now}}];
163 2   50     4 $properties->{SEQUENCE} ||= [{value => 0}];
164 2   50     15 $properties->{STATUS} ||= [{value => 'CONFIRMED'}];
165 2   50     11 $properties->{TRANSP} ||= [{value => 'OPAQUE'}];
166 2   50     9 $properties->{UID} ||= [{value => sprintf '%s@%s', _md5($event), $defaults->{hostname}}];
167 2         21 $properties;
168             }
169              
170             sub _render_ical {
171 2     2   2 my ($self, $c, $data) = @_;
172 2 50       2 my %properties = %{$data->{properties} || {}};
  2         14  
173 2         4 my $ical = {};
174 2         3 my %defaults;
175              
176 2         4 $ical->{objects} = [];
177 2         3 $ical->{properties} = {};
178 2         4 $ical->{type} = 'VCALENDAR';
179              
180 2   33     11 $properties{calscale} ||= $self->{properties}{calscale};
181 2   33     13 $properties{method} ||= $self->{properties}{method};
182 2   33     8 $properties{prodid} ||= $self->{properties}{prodid};
183 2   33     10 $properties{version} ||= $self->{properties}{version};
184 2   33     12 $properties{x_wr_caldesc} ||= $self->{properties}{x_wr_caldesc};
185 2   33     14 $properties{x_wr_calname} ||= $self->{properties}{x_wr_calname};
186 2   33     10 $properties{x_wr_timezone} ||= $self->{properties}{x_wr_timezone};
187              
188 2         4 for my $k (keys %properties) {
189 14         18 my $p = _vkey($k);
190 14         32 $ical->{properties}{$p} = [{value => $properties{$k}}];
191             }
192              
193 2         9 $defaults{hostname} = Sys::Hostname::hostname;
194 2         11 $defaults{now} = Mojo::Date->new->to_datetime;
195 2         59 $defaults{now} =~ s![:-]!!g; # 1994-11-06T08:49:37Z => 19941106T084937Z
196              
197 2 50       19 for my $event (@{$data->{events} || []}) {
  2         27  
198 2         2 push @{$ical->{objects}}, {properties => _event_to_properties($event, \%defaults), type => 'VEVENT'};
  2         7  
199             }
200              
201 2         10 return $self->{vfile}->generate_lines($ical);
202             }
203              
204             sub _reply_ical {
205 1     1   2 my ($self, $c, $data) = @_;
206 1         3 $c->res->headers->content_type('text/calendar');
207 1         26 $c->render(text => join '', map {"$_\n"} $self->_render_ical($c, $data));
  22         217  
208             }
209              
210             sub _md5 {
211 2     2   3 my $data = $_[0];
212 2         9 Mojo::Util::md5_sum(join ':', map {"$_=$data->{$_}"} grep { $_ ne 'dtstamp' } sort keys %$data);
  14         77  
  14         15  
213             }
214              
215             sub _vkey {
216 28 50   28   44 return $_[0] if $_[0] =~ /^[A-Z]/;
217 28         26 local $_ = uc $_[0];
218 28         32 s!_!-!g;
219 28         31 $_;
220             }
221              
222             =head1 COPYRIGHT AND LICENSE
223              
224             Copyright (C) 2014, Jan Henning Thorsen
225              
226             This program is free software, you can redistribute it and/or modify it under
227             the terms of the Artistic License version 2.0.
228              
229             =head1 AUTHOR
230              
231             Jan Henning Thorsen - C
232              
233             =cut
234              
235             1;