=pod =head1 StuwerCal =head2 META =head3 Author Thomas Klausner =head3 Email domm AT plix.at =head3 URL http://domm.plix.at =head3 URL_slides http://domm.plix.at/talks/stuwercal =head3 Date 2011-03-05 =head3 Location Den Haag =head3 Event Dutch Perl Workshop 2011 =head2 Stuwerviertel =for newslide =for img wien.jpg =for newslide =for img wien2.jpg =for newslide nice area central green BUT bad rep =for newslide street prostitution B street prostitution cheap poor people (=immigrants) vs old people plus a tiny bit of gentrification =for newslide City of Vienna started some projects to improve quality of life Intercultural dancing lessions Exhibitions of local artists Neighborhood garden =for newslide =for img garten.jpg =for newslide and I volunteered to hack some software because that's what I do... =for newslide =for img hacking.jpg =head2 StuwerCal Basic Idea: Make activities by the various actors in the Stuwerviertel visible by presenting their events on a single webpage without need for human interaction B =head2 iCal iCal is a standard to exchange information about events lots of calendar software supports it lots of blog software can export it http://calendar.google.com on the inside it looks disgusting =for newslide BEGIN:VEVENT DTSTART:20110110T170000Z DTEND:20110110T190000Z DTSTAMP:20110201T084057Z UID:5kt3l7baskmhbl6l0fb6or9oa4@google.com ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE STS=0:mailto:pcds0caordl4h0dug15q1hnmng@group.calendar.google.com CREATED:20101014T183904Z DESCRIPTION:Arbeitskreis im Rahmen des Stadtteilmanagements Stuwerviertels\ , gemeinsame Ideen\, gemeinsame Projekte für das Stuwerviertel werden hier entwickelt. Interessenten jederzeit willkommen. LAST-MODIFIED:20101227T025818Z LOCATION:Grätzelzentrum\, Max Winter Platz 23\, 1020 Wien SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Arbeitskreis Kultur\, Image\, Markt und Wirtschaft TRANSP:OPAQUE END:VEVENT =for newslide **BEGIN**:VEVENT DTSTART:20110110T170000Z DTEND:20110110T190000Z DTSTAMP:20110201T084057Z UID:5kt3l7baskmhbl6l0fb6or9oa4@google.com ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE STS=0:mailto:pcds0caordl4h0dug15q1hnmng@group.calendar.google.com CREATED:20101014T183904Z DESCRIPTION:Arbeitskreis im Rahmen des Stadtteilmanagements Stuwerviertels\ , gemeinsame Ideen\, gemeinsame Projekte für das Stuwerviertel werden hier entwickelt. Interessenten jederzeit willkommen. LAST-MODIFIED:20101227T025818Z LOCATION:Grätzelzentrum\, Max Winter Platz 23\, 1020 Wien SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Arbeitskreis Kultur\, Image\, Markt und Wirtschaft TRANSP:OPAQUE **END**:VEVENT =for newslide BEGIN:VEVENT DTSTART:20110110T170000Z DTEND:20110110T190000Z DTSTAMP:20110201T084057Z UID:5kt3l7baskmhbl6l0fb6or9oa4@google.com ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;X-NUM-GUE STS=0:mailto:pcds0caordl4h0dug15q1hnmng@group.calendar.google.com CREATED:20101014T183904Z DESCRIPTION:Arbeitskreis im Rahmen des Stadtteilmanagements Stuwerviertels**\ ,** gemeinsame Ideen**\,** gemeinsame Projekte für das Stuwerviertel werden hier entwickelt. Interessenten jederzeit willkommen. LAST-MODIFIED:20101227T025818Z LOCATION:Grätzelzentrum**\,** Max Winter Platz 23**\,** 1020 Wien SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Arbeitskreis Kultur**\,** Image**\,** Markt und Wirtschaft TRANSP:OPAQUE END:VEVENT =for newslide BEGIN:VEVENT DTSTART;TZID=Europe/Vienna:20110216T190000 DTEND;TZID=Europe/Vienna:20110216T230000 **RRULE:FREQ=MONTHLY;BYDAY=3WE;WKST=MO** DTSTAMP:20110201T084057Z UID:km1dlnl4sg44h9vag064llv4gs@google.com CREATED:20100128T120505Z DESCRIPTION:Jeden dritten Mittwoch im Monat gemeinsames Musizieren im... LAST-MODIFIED:20110127T120724Z SEQUENCE:4 STATUS:CONFIRMED SUMMARY:Musiksession TRANSP:OPAQUE END:VEVENT Every third wednesday =for newslide This makes parsing iCal a little bit annoying. Luckily we have this thing called CPAN and B by Rick Frankel =head2 iCal::Parser use iCal::Parser; my $parser = iCal::Parser->new( start => '20110101', end => DateTime->now, ); =for newslide use iCal::Parser; my $parser = iCal::Parser->new( **start** => '20110101', **end** => DateTime->now, ); =for newslide use iCal::Parser; my $parser = iCal::Parser->new( start => %%'20110101'%%, end => DateTime->now, ); =for newslide use iCal::Parser; my $parser = iCal::Parser->new( start => '20110101', end => %%DateTime->now%%, ); =for newslide $parser->parse( '/some/calendar.ical' ); =for newslide $parser->parse( '/some/calendar.ical' ); $parser->parse_strings( $ical_as_string ); The two calendars are automatically merged. =for newslide my $data = $parser->calendar; C<< $data >> is a HASHREF containing C<< calendars >>, C<< events >> and C<< todos >> C<< calenders >> contains references to the different iCal-Files we parsed. I don't care about C<< todos >> But let's take a closer look at C<< events >>: =for newslide C<< events >> is a deep HASHREF, keyed by C<< year >>, C<< month >> and C<< day >>. $events->{2011}{3}{5} At the C<< day >> level we find an ARRAYREF of events, sorted by time. The events themselves are rather plain HASHREFs that mimic the name of the iCal keys: =for newslide { 'UID' => '95CCBF98-3685-11D9-8CA5-000D93C45D90', 'idref' => '7CCE8555-3516-11D9-8A43-000D93C45D90', 'DTSTAMP' => \%DateTime, 'DTEND' => \%DateTime, 'DTSTART' => \%DateTime, ... }, =for newslide The really nice thing about C<< iCal::Parser >> is that it handles recurring events for you. So if you specifiy a recurring event like 'every third Monday' this event takes up only one slot in your iCal file. But C<< iCal::Parser >> copies the data into all relevant days. This makes processing very easy, because you don't have to think about recurring events, events spanning severl days, exceptions to recurring events, etc. =for newslide So one half of my aggregator was basically already finished. All I had to do was to write a smallish script that =over =item * fetches the iCal-files from the various websites, =item * let C<< iCal::Parser >> to all the hard work, =item * go through the C<< events >> hash and store the data somewhere. =back =for newslide foreach my $year ( keys %{ $data->{events} } ) { foreach my $month ( keys %{ $data->{events}{$year} } ) { foreach my $day ( keys %{ $data->{events}{$year}{$month} } ) { my $events = $data->{events}{$year}{$month}{$day}; foreach my $event ( values %$events ) { my $cal = $rs->find_or_create({ uid => join('',$year,$month,$day,$event->{UID}), calendar => $self->idref2cal->{ $event->{idref} }->id, }, { key => 'uid_unique' } ); $cal->update({ start => $event->{DTSTART}, stop => $event->{DTEND}, description => _val( $event->{DESCRIPTION} ), location => _val( $event->{LOCATION} ), summary => _val( $event->{SUMMARY} ), url => _val( $event->{URL} ), }); } } } } =head2 The Frontend Now I needed to create a small website. My initial version (which took 6 hours to build from scratch, including discovering C<< iCal::Parser >>) wrote an HTML snippet per event to the filesystem and had some JQuery assemble those. But this wasn't flexible enough, so I decided this project to be a good excuse to take a look at C<< Dancer >> =head2 Dancer Dancer calls itself a B It's similar to Mojolicous or Sinatra (which seems to have started the microframework craze..) A very minimal app looks like this: =for newslide #!/usr/bin/perl use Dancer; get '/hello/:name' => sub { return "Why, hello there " . params->{name}; }; dance; That's just one plain C script, and it's all you need. This setup is of course much simpler than e.g. Catalyst. =for newslide #!/usr/bin/perl use Dancer; **get** '/hello/:name' => sub { return "Why, hello there " . params->{name}; }; dance; =for newslide #!/usr/bin/perl use Dancer; get '**/hello/:name**' => sub { return "Why, hello there " . params->{name}; }; dance; =for newslide #!/usr/bin/perl use Dancer; get '/hello/:name' => **sub** { return "Why, hello there " . params->{name}; }; dance; =for newslide #!/usr/bin/perl use Dancer; get '/hello/%%:name%%' => sub { return "Why, hello there " . **params**->{%%name%%}; }; dance; =for newslide I was a bit irritated by the lack of objects Catalyst requires B objects my ( $self, $c ) = @_; But Dancer none?? Everything is "just" a function. =for newslide params->{name}; =for newslide config->{appname} =for newslide content_type 'text/plain'; =for newslide template 'some/template.tt' { foo => 'bar' }; =head2 StuwerCal::Web 169 lines in total (including blank lines etc) 74 lines are needed for the calendar navigation widget that leaves us with 95 lines of proper code... =for newslide package StuwerCal::Web; use strict; use warnings; use 5.010; use Dancer ':syntax'; use StuwerCal::ConnectDB; use DateTime; use DateTime::Format::Strptime; use Calendar::Simple; my $dp = DateTime::Format::Strptime->new( pattern=>'%Y-%m-%d', locale=>'de_AT', time_zone => 'Europe/Vienna', ); my $schema = StuwerCal::ConnectDB->connect; DateTime->DefaultLocale('de_AT'); =for newslide get '/' => sub { show_day(now()) }; get '/day' => sub { show_day(now()) }; get '/day/:day' => sub { show_day($dp->parse_datetime(params->{'day'})) }; =for newslide sub show_day { my $day = shift; my $stash = { events => $schema->resultset('Event')->per_day($day), }; make_daynav($stash,$day); make_kalender($stash,$day); make_upcoming($stash); template 'events_per_day', $stash ; } =for newslide get '/event/:id' => sub { my $stash = { event => $schema->resultset('Event')->find(params->{'id'}) }; my $event_day = $stash->{event}->start; make_daynav($stash,$event_day); make_kalender($stash,$event_day); make_upcoming($stash); template 'event', $stash; }; =for newslide get '/source' => sub { my $stash = { sources => $schema->resultset('Calendar')->list, }; make_kalender($stash); make_upcoming($stash); template 'source', $stash; }; =for newslide get '/source/:id' => sub { my $stash = { source => $schema->resultset('Calendar')->find(params->{'id'}), }; make_kalender($stash); make_upcoming($stash); template 'events_per_source', $stash; }; =for newslide get '/about' => sub { template 'about' ; }; get '/kontakt' => sub { template 'kontakt' ; }; =for newslide sub make_daynav { my ($stash, $day) = @_; $stash->{prev_day} = uri_for('/day/'.$day->clone->subtract(days=>1)->ymd('-')); $stash->{next_day} = uri_for('/day/'.$day->clone->add(days=>1)->ymd('-')); $stash->{this_day} = uri_for('/day/'.$day->ymd('-')); $stash->{this_day_label} = $day->strftime("%A, %d. %B %Y"); } =for newslide sub make_upcoming { my ($stash) = @_; $stash->{upcoming} = $schema->resultset('Event')->upcoming(now()); } =for newslide Dancer comes with it's own mini-server but can also be deployed using Plack (which I didn't try yet..) perl -Ilib bin/app.pl The script has to be named F<< app.pl >> =for newslide Another nice Dancer feature: B A bit like the WRAPPER concept of Template::Toolkit =for newslide Currently I'm quite happy with Dancer. It only took me a few hours to learn the basics and code my app. But I'm not sure I'm not going to switch to Catalyst if I need a lot more controllers / actions. =head2 Questions?