--- /dev/null
+#! /usr/bin/perl
+# Copyright (c) 2006, 2007 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 2.00;
+use Time::Local;
+
+my %cache;
+my %linkcache;
+my @now=localtime();
+
+sub import { #{{{
+ hook(type => "preprocess", id => "calendar", call => \&preprocess);
+} #}}}
+
+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 $pmonth = $params{pmonth};
+ my $nmonth = $params{nmonth};
+ my $pyear = $params{pyear};
+ my $nyear = $params{nyear};
+
+ my @list;
+ 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;
+ if ($year == $now[5]+1900 && $month == $now[4]+1) {
+ $future_dom = $now[3]+1;
+ $today = $now[3];
+ }
+
+ # 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)));
+
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+ $archivebase = $params{archivebase} if defined $params{archivebase};
+
+ # Calculate URL's for monthly archives.
+ my ($url, $purl, $nurl)=("$monthname",'','');
+ if (exists $cache{$pagespec}{"$year/$month"}) {
+ $url = htmllink($params{page}, $params{destpage},
+ "$archivebase/$year/".sprintf("%02d", $month),
+ linktext => " $monthname ");
+ }
+ add_depends($params{page}, "$archivebase/$year/".sprintf("%02d", $month));
+ if (exists $cache{$pagespec}{"$pyear/$pmonth"}) {
+ $purl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$pyear/" . sprintf("%02d", $pmonth),
+ linktext => " $pmonthname ");
+ }
+ add_depends($params{page}, "$archivebase/$pyear/".sprintf("%02d", $pmonth));
+ if (exists $cache{$pagespec}{"$nyear/$nmonth"}) {
+ $nurl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$nyear/" . sprintf("%02d", $nmonth),
+ linktext => " $nmonthname ");
+ }
+ add_depends($params{page}, "$archivebase/$nyear/".sprintf("%02d", $nmonth));
+
+ # 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{\t\t<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{\t<tr>\n} if $wday == $week_start_day;
+ $calendar.=qq{\t\t<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{\t</tr>\n} unless $day == 1;
+ $calendar.=qq{\t<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{\t\t<td class="$tag $downame{$wday}">};
+ $calendar.=htmllink($params{page}, $params{destpage},
+ pagename($linkcache{"$year/$mtag/$day"}),
+ "linktext" => "$day");
+ push @list, pagename($linkcache{"$year/$mtag/$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{\t\t<td class="$tag $downame{$wday}">$day</td>\n};
+ }
+ }
+
+ # finish off the week
+ for (; $wday != $week_start_day; $wday++, $wday %= 7) {
+ $calendar.=qq{\t\t<td class="month-calendar-day-noday $downame{$wday}"> </td>\n};
+ }
+ $calendar.=<<EOF;
+ </tr>
+</table>
+EOF
+
+ # Add dependencies to update the calendar whenever pages
+ # matching the pagespec are added or removed.
+ add_depends($params{page}, $params{pages});
+ # Explicitly add all currently linked pages as dependencies, so
+ # that if they are removed, the calendar will be sure to be updated.
+ add_depends($params{page}, join(" or ", @list));
+
+ return $calendar;
+}
+
+sub format_year (@) {
+ my %params=@_;
+
+ my $pagespec = $params{pages};
+ my $year = $params{year};
+ my $month = $params{month};
+ my $pmonth = $params{pmonth};
+ my $nmonth = $params{nmonth};
+ my $pyear = $params{pyear};
+ my $nyear = $params{nyear};
+
+ my $calendar="\n";
+
+ my $future_month = 0;
+ $future_month = $now[4]+1 if ($year == $now[5]+1900);
+
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+ $archivebase = $params{archivebase} if defined $params{archivebase};
+
+ # calculate URL's for previous and next years
+ my ($url, $purl, $nurl)=("$year",'','');
+ if (exists $cache{$pagespec}{"$year"}) {
+ $url = htmllink($params{page}, $params{destpage},
+ "$archivebase/$year",
+ linktext => "$year");
+ }
+ add_depends($params{page}, "$archivebase/$year");
+ if (exists $cache{$pagespec}{"$pyear"}) {
+ $purl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$pyear",
+ linktext => "\←");
+ }
+ add_depends($params{page}, "$archivebase/$pyear");
+ if (exists $cache{$pagespec}{"$nyear"}) {
+ $nurl = htmllink($params{page}, $params{destpage},
+ "$archivebase/$nyear",
+ linktext => "\→");
+ }
+ add_depends($params{page}, "$archivebase/$nyear");
+
+ # 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{\t<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",
+ linktext => "$monthabbr");
+ $calendar.=qq{\t<td class="$tag">};
+ $calendar.=$murl;
+ $calendar.=qq{\t</td>\n};
+ }
+ else {
+ $calendar.=qq{\t<td class="$tag">$monthabbr</td>\n};
+ }
+ add_depends($params{page}, "$archivebase/$year/$mtag");
+
+ $calendar.=qq{\t</tr>\n} if ($month % $params{months_per_row} == 0);
+ }
+
+ $calendar.=<<EOF;
+</table>
+EOF
+
+ return $calendar;
+}
+
+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};
+
+ # Calculate month names for next month, and previous months
+ my $pmonth = $params{month} - 1;
+ my $nmonth = $params{month} + 1;
+ my $pyear = $params{year} - 1;
+ my $nyear = $params{year} + 1;
+
+ # Adjust for January and December
+ if ($params{month} == 1) {
+ $pmonth = 12;
+ $pyear--;
+ }
+ if ($params{month} == 12) {
+ $nmonth = 1;
+ $nyear++;
+ }
+
+ $params{pmonth}=$pmonth;
+ $params{nmonth}=$nmonth;
+ $params{pyear} =$pyear;
+ $params{nyear} =$nyear;
+
+ my $calendar="\n";
+ my $pagespec=$params{pages};
+ my $page =$params{page};
+
+ if (! defined $cache{$pagespec}) {
+ foreach my $p (keys %pagesources) {
+ next unless pagespec_match($p, $pagespec);
+ my $mtime = $IkiWiki::pagectime{$p};
+ my $src = $pagesources{$p};
+ my @date = localtime($mtime);
+ my $mday = $date[3];
+ my $month = $date[4] + 1;
+ my $year = $date[5] + 1900;
+ my $mtag = sprintf("%02d", $month);
+
+ # Only one posting per day is being linked to.
+ $linkcache{"$year/$mtag/$mday"} = "$src";
+ $cache{$pagespec}{"$year"}++;
+ $cache{$pagespec}{"$year/$mtag"}++;
+ $cache{$pagespec}{"$year/$mtag/$mday"}++;
+ }
+ }
+
+ if ($params{type} =~ /month/i) {
+ $calendar=format_month(%params);
+ }
+ elsif ($params{type} =~ /year/i) {
+ $calendar=format_year(%params);
+ }
+
+ return "\n<div class=\"calendar\">$calendar</div><!-- calendar -->\n";
+}
+
+1
--- /dev/null
+[[template id=plugin name=calendar author="[[ManojSrivastava]]"]]
+[[tag type/chrome]]
+
+This plugin displays a calendar, similar to the typical calendars shown on
+some blogs.
+
+# examples
+
+ \[[calendar ]]
+
+ \[[calendar type="month" pages="blog/* and !*/Discussion"]]
+
+ \[[calendar type="year" year="2005" pages="blog/* and !*/Discussion"]]
+
+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 tricky part of this is that while calendar defaults to showing the
+current day and month, ikiwiki is a wiki compiler, which only rebuilds
+pages if they have changed. So to keep the calendar up-to-date, wikis that
+include it need to be periodically rebuilt, typically by cron at midnight.
+
+The month format calendar simply links to any page posted on each
+day of the month. The year format calendar links to archive pages, with
+names like `archives/2007` (for all of 2007) and `archives/2007/01`
+(for January, 2007). For this to work, you'll need to create these archive
+pages. They typically use [[inline]] to display or list pages created in
+the given time frame.
+
+## usage
+
+* `type` - Used to specify the type of calendar wanted. Can be one of
+ "month" or "year". The default is a month view calendar.
+* `pages` - Specifies the [[PageSpec]] of pages to link to from the
+ month calendar. Defaults to "*".
+* `archivebase` - Configures the base of the archives hierarchy. The
+ default is "archives". Note that this default can also be overridden
+ for the whole wiki by setting `archivebase` in ikiwiki's setup file.
+* `year` - The year for which the calendar is requested. Defaults to the
+ current year.
+* `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.
+* `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.
+* `months_per_row` - In the annual calendar, number of months to place in
+ each row. Defaults to 3.
+
+## CSS
+
+The output is liberally sprinkled with classes, for fine grained CSS
+customization.
+
+* `month-calendar` - The month calendar as a whole.
+* `month-calendar-head` - The head of the month calendar (ie,"March").
+* `month-calendar-day-head` - A column head in the month calendar (ie, a
+ day-of-week abbreviation).
+* `month-calendar-day-noday`, `month-calendar-day-link`,
+ `month-calendar-day-nolink`, `month-calendar-day-future`,
+ `month-calendar-day-this-day` - The day squares on the month calendar,
+ for days that are not in the month (before or after the month itself), that
+ don't have links, that do have links, that are in the future, or are that
+ are the current day, respectively.
+* `Sunday`, `Monday`, `Tuesday`, ... - Each day square is also given a class
+ matching its (localised) day of week, this can be used to highlight
+ weekends.
+* `year-calendar` - The year calendar as a whole.
+* `year-calendar-head` - The head of the year calendar (ie, "2007").
+* `year-calendar-subhead` - For example, "Months".
+* `year-calendar-month-link`, `year-calendar-month-nolink`,
+ `year-calendar-month-future`, `year-calendar-this-month` - The month
+ squares on the year calendar, for months with stories,
+ without, in the future, and currently selected, respectively.