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 ]] or \[[calendar type="month" pages="blog/* and !*/Discussion"]] or \[[calendar type="year" year="2005" pages="blog/* and !*/Discussion"]] 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, archivebase. archivebase defaults to "archives". Links are created to pages "$archivebase/$year" and "$archivebase/$year/$month". The idea is to create annual and monthly indices, for example, by using something like this sample from my archives/2006/01.mdwn \[[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]] [[tag patch]]
#! /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 
#
# 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. I
defaults to C.  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 (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

Used to specify the type of calendar wanted. Can be one of C or
C. The default is a month view calendar.

=item B

Specifies the C used to get pages to match for
linking. Usually this should be something like C.
Defaults to C<*>.

=item B

The year for which the calendar is requested. Defaults to the current year.

=item B

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

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

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 

The month calendar as a whole

=item C

The head of the month calendar (ie,"March"), localized to the environment.

=item C

A column head in the month calendar (ie, a day-of-week abbreviation),
localized.

=item C, C,
  C, C,
  C 

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

The year calendar as a whole

=item C

The head of the year calendar (ie, "2006")

=item C

For example, "Months"

=item C, C,
  C, C

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
" . $index++ . "
\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=< $purl $url $nurl 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{ $dowabbr\n}; } $calendar.=< 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{ \n} if $wday == $week_start_day; $calendar.= qq{  \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{ \n} unless $day == 1; $calendar.=qq{ \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{ }; $calendar.= htmllink($params{page}, $params{destpage}, pagename($linkcache{"$year/$mtag/$day"}), 0,0,"$day"); $calendar.=qq{\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{ $day\n}; } } # finish off the week for (; $wday != $week_start_day; $wday++, $wday %= 7) { $calendar.=qq{  \n}; } $calendar.=< 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=< $purl $url $nurl Months 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{ \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{ }; $calendar.=$murl; $calendar.=qq{\n}; } else { $calendar.=qq{ $monthabbr\n}; } $calendar.=qq{ \n} if ($month % $params{months_per_row} == 0); } $calendar.=< 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/(
\s*.?\s*$index\b)/
$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 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 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 =head1 COPYRIGHT AND LICENSE This script is a part of the Devotee package, and is Copyright (c) 2002 Manoj Srivastava 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__
------ 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 , 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]]