+I am serving notice that I am starting work on a calendar plugin inspired by Blosxom's calendar plugin. The current plan is to create a plugin that looks through all the source files matching a certain pagespec, and optionally spit out a month view for the specified month (default to current), or spit out a year view for a given year (defaulting to the current year), of a list of year with posts in them. The output would be a table, with the same CSS directives that the Blosxom plugin used to use (so that I can just reuse my css file). The links would be created to a $config{archivedir}/$year or $config{archivedir}/$year-$month file, which can just have
+
+ \[[inline pages="blog/* and !*/Discussion and creation_year($year) and creation_month($month)" rss="no" atom="no" show="0"]]
+
+or some thing to generate a archive of postings.
+
+Roland Mas suggested a separate cron job to generate these archive indices automatically, but that is another thread.
+
+ManojSrivastava
+
This plugin is inspired by the calendar plugin for Blosxom, but derivesno code from it. This plugin is essentially a fancy front end to archives of previous pages, usually used for blogs. It can produce a calendar for a given month, or a list of months for a given year. To invoke the calendar, just use the preprocessor directive:
\[[calendar ]]
\[[meta title="Archives for 2006/01"]]
\[[inline rootpage="blog" atom="no" rss="no" show="0" pages="blog/* and !*/Discussion and creation_year(2006) and creation_month(01)" ]]
+
+I'll send in the patch via email.
+
+
+ManojSrivastava
+
+------
+
+Since this is a little bit er, stalled, I'll post here the stuff Manoj
+mailed me, and my response to it. --[[Joey]]
+
+<pre>
+#! /usr/bin/perl
+# -*- Mode: Cperl -*-
+# calendar.pm ---
+# Author : Manoj Srivastava ( srivasta@glaurung.internal.golden-gryphon.com )
+# Created On : Fri Dec 8 16:05:48 2006
+# Created On Node : glaurung.internal.golden-gryphon.com
+# Last Modified By : Manoj Srivastava
+# Last Modified On : Sun Dec 10 01:53:22 2006
+# Last Machine Used: glaurung.internal.golden-gryphon.com
+# Update Count : 139
+# Status : Unknown, Use with caution!
+# HISTORY :
+# Description :
+#
+# arch-tag: 2aa737c7-3d62-4918-aaeb-fd85b4b1384c
+#
+# Copyright (c) 2006 Manoj Srivastava <srivasta@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+require 5.002;
+package IkiWiki::Plugin::calendar;
+
+use warnings;
+use strict;
+use IkiWiki '1.00';
+use Time::Local;
+
+our $VERSION = "0.1";
+my $file = __FILE__;
+
+my %calpages;
+my %cache;
+my %linkcache;
+
+my $index=1;
+my @now=localtime();
+
+=head1 NAME
+
+calendar - Add links for the current month's, current year's, and older archived postings
+
+=cut
+
+=head1 SYNOPSIS
+
+To invoke the calendar, just use the preprocessor directive (options
+and variations are detailed below):
+
+ [[calendar ]]
+
+or
+
+ [[calendar type="month" pages="blog/* and !*/Discussion"]]
+
+or
+
+ [[calendar type="year" year="2005" pages="blog/* and !*/Discussion"]]
+
+=cut
+
+
+=head1 DESCRIPTION
+
+This plugin is inspired by the calendar plugin for Blosxom, but
+derives no code from it. This plugin is essentially a fancy front end
+to archives of previous pages, usually used for blogs. It can produce
+a calendar for a given month, or a list of months for a given year.
+
+The year and month entities in the out put have links to archive index
+pages, which are supposed to exist already. The idea is to create an
+archives hierarchy, rooted in the subdirectory specified in the
+site wide customization variable, I<archivebase>. I<archivebase>
+defaults to C<archives>. Links are created to pages
+C<$archivebase/$year> and C<$archivebase/$year/$month>. If one creates
+annual and monthly indices, for example, by using something like this
+sample from my I<archives/2006/01.mdwn> (warning: line split for
+readability):
+
+ \[[meta title="Archives for 2006/01"]]
+ \[[inline rootpage="blog" atom="no" rss="no" show="0"
+ pages="blog/* and !*/Discussion and creation_year(2006)
+ and creation_month(01)"
+ ]]
+
+=cut
+
+=head1 OPTIONS
+
+=over
+
+=item B<type>
+
+Used to specify the type of calendar wanted. Can be one of C<month> or
+C<year>. The default is a month view calendar.
+
+=item B<pages>
+
+Specifies the C<pagespec> used to get pages to match for
+linking. Usually this should be something like C<blog/* and !*/Discussion>.
+Defaults to C<*>.
+
+=item B<year>
+
+The year for which the calendar is requested. Defaults to the current year.
+
+=item B<month>
+
+The numeric month for which the calendar is requested, in the range
+1..12. Used only for the month view calendar, and defaults to the
+current month.
+
+=item B<week_start_day>
+
+A number, in the range 0..6, which represents the day of the week that
+the month calendar starts with. 0 is Sunday, 1 is Monday, and so
+on. Defaults to 0, which is Sunday.
+
+=item B<months_per_row>
+
+In the annual calendar, number of months to place in each row. Defaults to 3.
+
+=back
+
+=cut
+
+=head1 Classes for CSS control
+
+The output is liberally sprinkled with classes, for fine grained CSS
+customization.
+
+=over
+
+=item C<month-calendar>
+
+The month calendar as a whole
+
+=item C<month-calendar-head>
+
+The head of the month calendar (ie,"March"), localized to the environment.
+
+=item C<month-calendar-day-head>
+
+A column head in the month calendar (ie, a day-of-week abbreviation),
+localized.
+
+=item C<month-calendar-day-noday>, C<month-calendar-day-link>,
+ C<month-calendar-day-nolink>, C<month-calendar-day-future>,
+ C<month-calendar-day-this-day>
+
+The day squares on the month calendar, for days that don't exist
+(before or after the month itself), that don't have stories, that do
+have stories, that are in the future, or are that currently selected,
+respectively (today).
+
+=item Day-of-week-name
+
+Each day square is also given a class matching its day of week, this
+can be used to high light weekends. This is also localized.
+
+=item C<year-calendar>
+
+The year calendar as a whole
+
+=item C<year-calendar-head>
+
+The head of the year calendar (ie, "2006")
+
+=item C<year-calendar-subhead>
+
+For example, "Months"
+
+=item C<year-calendar-month-link>, C<year-calendar-month-nolink>,
+ C<year-calendar-month-future>, C<year-calendar-this-month>
+
+The month squares on the year calendar, for months with stories,
+without, in the future, and currently selected, respectively.
+
+=back
+
+=cut
+
+
+sub import {
+ hook(type => "preprocess", id => "calendar", call => \&preprocess);
+ hook(type => "format", id => "calendar", call => \&format);
+}
+
+sub preprocess (@) {
+ my %params=@_;
+ $params{pages} = "*" unless defined $params{pages};
+ $params{type} = "month" unless defined $params{type};
+ $params{year} = 1900 + $now[5] unless defined $params{year};
+ $params{month} = sprintf("%02d", $params{month}) if defined $params{month};
+ $params{month} = 1 + $now[4] unless defined $params{month};
+ $params{week_start_day} = 0 unless defined $params{week_start_day};
+ $params{months_per_row} = 3 unless defined $params{months_per_row};
+
+ # Store parameters (could be multiple calls per page)
+ $calpages{$params{destpage}}{$index} = \%params;
+
+ return "\n<div class=\"calendar\">" . $index++ . "</div><!-- calendar -->\n";
+}
+
+sub is_leap_year (@) {
+ my %params=@_;
+ return ($params{year} % 4 == 0 && (($params{year} % 100 != 0) || $params{year} % 400 ==0)) ;
+}
+
+
+sub month_days {
+ my %params=@_;
+ my $days_in_month = (31,28,31,30,31,30,31,31,30,31,30,31)[$params{month}-1];
+ if ($params{month} == 2 && is_leap_year(%params)) {
+ $days_in_month++;
+ }
+ return $days_in_month;
+}
+
+
+sub format_month (@) {
+ my %params=@_;
+ my $pagespec = $params{pages};
+ my $year = $params{year};
+ my $month = $params{month};
+
+ my $calendar="\n";
+
+ # When did this month start?
+ my @monthstart = localtime(timelocal(0,0,0,1,$month-1,$year-1900));
+
+ my $future_dom = 0;
+ my $today = 0;
+ $future_dom = $now[3]+1 if ($year == $now[5]+1900 && $month == $now[4]+1);
+ $today = $now[3] if ($year == $now[5]+1900 && $month == $now[4]+1);
+
+ # Calculate month names for next month, and previous months
+ my $pmonth = $month - 1;
+ my $nmonth = $month + 1;
+ my $pyear = $year;
+ my $nyear = $year;
+
+ # Adjust for January and December
+ if ($month == 1) { $pmonth = 12; $pyear--; }
+ if ($month == 12) { $nmonth = 1; $nyear++; }
+
+ # Find out month names for this, next, and previous months
+ my $monthname=POSIX::strftime("%B", @monthstart);
+ my $pmonthname=
+ POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$pmonth-1,$pyear-1900)));
+ my $nmonthname=
+ POSIX::strftime("%B", localtime(timelocal(0,0,0,1,$nmonth-1,$nyear-1900)));
+
+ # Calculate URL's for monthly archives, and article counts
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+
+ my ($url, $purl, $nurl)=("$monthname",'','');
+ my ($count, $pcount, $ncount) = (0,0,0);
+
+ if (exists $cache{$pagespec}{"$year/$month"}) {
+ $url = htmllink($params{page}, $params{destpage},
+ "$archivebase/$year/" . sprintf("%02d", $month),
+ 0,0," $monthname ");
+ }
+
+ if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
+ $purl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$pyear/" . sprintf("%02d", $pmonth),
+ 0,0," $pmonthname ");
+ }
+ if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
+ $nurl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$nyear/" . sprintf("%02d", $nmonth),
+ 0,0," $nmonthname ");
+ }
+
+ # Start producing the month calendar
+ $calendar=<<EOF;
+<table class="month-calendar">
+ <caption class="month-calendar-head">
+ $purl
+ $url
+ $nurl
+ </caption>
+ <tr>
+EOF
+ # Suppose we want to start the week with day $week_start_day
+ # If $monthstart[6] == 1
+ my $week_start_day = $params{week_start_day};
+
+ my $start_day = 1 + (7 - $monthstart[6] + $week_start_day) % 7;
+ my %downame;
+ my %dowabbr;
+ for my $dow ($week_start_day..$week_start_day+6) {
+ my @day=localtime(timelocal(0,0,0,$start_day++,$month-1,$year-1900));
+ my $downame = POSIX::strftime("%A", @day);
+ my $dowabbr = POSIX::strftime("%a", @day);
+ $downame{$dow % 7}=$downame;
+ $dowabbr{$dow % 7}=$dowabbr;
+ $calendar.=
+ qq{ <th class="month-calendar-day-head $downame">$dowabbr</th>\n};
+ }
+
+ $calendar.=<<EOF;
+ </tr>
+EOF
+
+ my $wday;
+ # we start with a week_start_day, and skip until we get to the first
+ for ($wday=$week_start_day; $wday != $monthstart[6]; $wday++, $wday %= 7) {
+ $calendar.=qq{ <tr>\n} if $wday == $week_start_day;
+ $calendar.=
+ qq{ <td class="month-calendar-day-noday $downame{$wday}"> </td>\n};
+ }
+
+ # At this point, either the first is a week_start_day, in which case nothing
+ # has been printed, or else we are in the middle of a row.
+ for (my $day = 1; $day <= month_days(year => $year, month => $month);
+ $day++, $wday++, $wday %= 7) {
+ # At tihs point, on a week_start_day, we close out a row, and start a new
+ # one -- unless it is week_start_day on the first, where we do not close a
+ # row -- since none was started.
+ if ($wday == $week_start_day) {
+ $calendar.=qq{ </tr>\n} unless $day == 1;
+ $calendar.=qq{ <tr>\n};
+ }
+ my $tag;
+ my $mtag = sprintf("%02d", $month);
+ if (defined $cache{$pagespec}{"$year/$mtag/$day"}) {
+ if ($day == $today) { $tag='month-calendar-day-this-day'; }
+ else { $tag='month-calendar-day-link'; }
+ $calendar.=qq{ <td class="$tag $downame{$wday}">};
+ $calendar.=
+ htmllink($params{page}, $params{destpage},
+ pagename($linkcache{"$year/$mtag/$day"}),
+ 0,0,"$day");
+ $calendar.=qq{</td>\n};
+ }
+ else {
+ if ($day == $today) { $tag='month-calendar-day-this-day'; }
+ elsif ($day == $future_dom) { $tag='month-calendar-day-future'; }
+ else { $tag='month-calendar-day-nolink'; }
+ $calendar.=qq{ <td class="$tag $downame{$wday}">$day</td>\n};
+ }
+ }
+ # finish off the week
+ for (; $wday != $week_start_day; $wday++, $wday %= 7) {
+ $calendar.=qq{ <td class="month-calendar-day-noday $downame{$wday}"> </td>\n};
+ }
+ $calendar.=<<EOF;
+ </tr>
+</table>
+EOF
+
+ return $calendar;
+}
+
+sub format_year (@) {
+ my %params=@_;
+ my $pagespec = $params{pages};
+ my $year = $params{year};
+ my $month = $params{month};
+ my $calendar="\n";
+ my $pyear = $year - 1;
+ my $nyear = $year + 1;
+ my $future_month = 0;
+ $future_month = $now[4]+1 if ($year == $now[5]+1900);
+
+ # calculate URL's for previous and next years
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+ my ($url, $purl, $nurl)=("$year",'','');
+ if (exists $cache{$pagespec}{"$year"}) {
+ $url = htmllink($params{page}, $params{destpage},
+ "$archivebase/$year",
+ 0,0,"$year");
+ }
+
+ if (exists $cache{$pagespec}{"$pyear"}) {
+ $purl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$pyear",
+ 0,0,"\←");
+ }
+ if (exists $cache{$pagespec}{"$nyear"}) {
+ $nurl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$nyear",
+ 0,0,"\→");
+ }
+ # Start producing the year calendar
+ $calendar=<<EOF;
+<table class="year-calendar">
+ <caption class="year-calendar-head">
+ $purl
+ $url
+ $nurl
+ </caption>
+ <tr>
+ <th class="year-calendar-subhead" colspan="$params{months_per_row}">Months</th>
+ </tr>
+EOF
+
+ for ($month = 1; $month <= 12; $month++) {
+ my @day=localtime(timelocal(0,0,0,15,$month-1,$year-1900));
+ my $murl;
+ my $monthname = POSIX::strftime("%B", @day);
+ my $monthabbr = POSIX::strftime("%b", @day);
+ $calendar.=qq{ <tr>\n} if ($month % $params{months_per_row} == 1);
+ my $tag;
+ my $mtag=sprintf("%02d", $month);
+ if ($month == $params{month}) {
+ if ($cache{$pagespec}{"$year/$mtag"}) {$tag = 'this_month_link'}
+ else {$tag = 'this_month_nolink'}
+ }
+ elsif ($cache{$pagespec}{"$year/$mtag"}) {$tag = 'month_link'}
+ elsif ($future_month && $month >=$future_month){$tag = 'month_future'}
+ else {$tag = 'month_nolink'}
+ if ($cache{$pagespec}{"$year/$mtag"}) {
+ $murl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$year/$mtag",
+ 0,0,"$monthabbr");
+ $calendar.=qq{ <td class="$tag">};
+ $calendar.=$murl;
+ $calendar.=qq{</td>\n};
+ }
+ else {
+ $calendar.=qq{ <td class="$tag">$monthabbr</td>\n};
+ }
+ $calendar.=qq{ </tr>\n} if ($month % $params{months_per_row} == 0);
+ }
+ $calendar.=<<EOF;
+</table>
+EOF
+
+ return $calendar;
+}
+
+
+sub format (@) {
+ my %params=@_;
+ my $content=$params{content};
+ return $content unless exists $calpages{$params{page}};
+
+ # Restore parameters for each invocation
+ foreach my $index (keys %{$calpages{$params{page}}}) {
+ my $calendar="\n";
+ my %saved = %{$calpages{$params{page}}{$index}};
+ my $pagespec=$saved{pages};
+
+ if (! defined $cache{$pagespec}) {
+ for my $page (sort keys %pagesources) {
+ next unless pagespec_match($page,$pagespec);
+ my $mtime;
+ my $src = $pagesources{$page};
+ if (! exists $IkiWiki::pagectime{$page}) {
+ $mtime=(stat(srcfile($src)))[9];
+ }
+ else {
+ $mtime=$IkiWiki::pagectime{$page}
+ }
+ my @date = localtime($mtime);
+ my $mday = $date[3];
+ my $month = $date[4] + 1;
+ my $year = $date[5] + 1900;
+ my $mtag = sprintf("%02d", $month);
+ $linkcache{"$year/$mtag/$mday"} = "$src";
+ $cache{$pagespec}{"$year"}++;
+ $cache{$pagespec}{"$year/$mtag"}++;
+ $cache{$pagespec}{"$year/$mtag/$mday"}++;
+ }
+ }
+ # So, we have cached data for the current pagespec at this point
+ if ($saved{type} =~ /month/i) {
+ $calendar=format_month(%saved);
+ }
+ elsif ($saved{type} =~ /year/i) {
+ $calendar=format_year(%saved);
+ }
+ $content =~ s/(<div class=\"calendar\">\s*.?\s*$index\b)/<div class=\"calendar\">$calendar/ms;
+ }
+ return $content;
+}
+
+
+
+=head1 CAVEATS
+
+In the month calendar, for days in which there is more than one
+posting, the link created randomly selects one of them. Since there is
+no easy way in B<IkiWiki> to automatically generate index pages, and
+pregenerating daily index pages seems too much of an overhead, we have
+to live with this. All postings can still be viewed in the monthly or
+annual indices, of course. This can be an issue for very prolific
+scriveners.
+
+=cut
+
+=head1 BUGS
+
+None Known so far.
+
+=head1 BUGS
+
+Since B<IkiWiki> eval's the configuration file, the values have to all
+on a single physical line. This is the reason we need to use strings
+and eval, instead of just passing in real anonymous sub references,
+since the eval pass converts the coderef into a string of the form
+"(CODE 12de345657)" which can't be dereferenced.
+
+=cut
+
+=head1 AUTHOR
+
+Manoj Srivastava <srivasta@debian.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This script is a part of the Devotee package, and is
+
+Copyright (c) 2002 Manoj Srivastava <srivasta@debian.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+=cut
+
+1;
+
+__END__
+</pre>
+
+------
+
+I've been looking over the calendar plugin. Some items:
+
+* Why did you need to use a two-stage generation with a format hook?
+ That approach should only be needed if adding something to a page that
+ would be removed by the htmlscrubber, and as far as I can tell, the
+ calendars don't involve anything that would be a problem. It seems
+ that emitting the whole calendar in the preprocess hook would simplify
+ things and you'd not need to save state about calendars.
+
+> I am scared of the html scrubber, and have never turned it on,
+> and did not look too deeply into what would be scrubbed out --ManojSrivastava
+>> Unless you're using javascript, a few annoyances link <blink>, or inline
+>> css, it's unlikly to object to any html you might write. The list of
+>> allowed tags and attributes is easy to find near the top of the plugin.
+
+> In case the option that gets the ctime of the pages from the
+> SCM itself, %IkiWiki::pagectime is not populated that early,
+> is it? So I waited until the last possible moment to look at
+> the time information.
+>
+>> Actually, since my big rewrite of the rendering path a few months ago,
+>> ikiwiki scans and populates almost all page information before starting
+>> to render any page. This includes %pagectime, and even %links. So you
+>> shouldn't need to worry about running it late.
+
+* The way that it defaults to the current year and current month
+ is a little bit tricky, because of course the wiki might not get
+ updated in a particular time period, and even if it is updated, only
+ iff a page containing a calendar is rebuilt for some other reason will
+ the calendar get updated, and change what year or month it shows. This
+ is essentially the same problem described in
+ [[todo/tagging_with_a_publication_date]],
+ although I don't think it will affect the calendar plugin very badly.
+ Still, the docs probably need to be clear about this.
+
+> I use it on the sidebar; and the blog pages are almost always
+> rebuilt, which is where the calendar is looked at most often. Oh,
+> and I also cheat, I have ikiwiki --setup foo as a @daily cronjob, so
+> my wiki is always built daily from scratch.
+>
+> I think it should be mentioned, yes.
+
+* There seems to be something a bit wrong with the year-to-year
+ navigation in the calendar, based on the example in your blog. If I'm
+ on the page for 2006, there's an arrow pointing left which takes me to
+ 2005. If I'm on 2005, the arrow points left, but goes to 2006, not
+ 2004.
+
+> I need to look into this.
+
+* AIUI, the archivebase setting makes a directory rooted at the top of
+ the wiki, so you can have only one set of archives per wiki, in
+ /archives/. It would be good if it were possible to have multiple
+ archived for different blogs in the same wiki at multiple locations.
+ Though since the archives contain calendars, the archive location
+ can't just be relative to the page with the calendar. But perhaps
+ archivebase could be a configurable parameter that can be specified in
+ the directive for the calendar? (It would be fine to keep the global
+ location as a default.)
+
+> OK, this is simple enough to implement. I'll do that (well,
+> perhaps not before Xmas, I have a family dinner to cook) and send in
+> another patch.
+
+
+----
+
+And that's all I've heard so far. Hoping I didn't miss another patch?
+
+--[[Joey]]