* Add a calendar plugin, contributed by Manoj Srivastava.
authorjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Tue, 23 Oct 2007 00:02:53 +0000 (00:02 +0000)
committerjoey <joey@0fa5a96a-9a0e-0410-b3b2-a0fd24251071>
Tue, 23 Oct 2007 00:02:53 +0000 (00:02 +0000)
* Reformat calendar plugin to ikiwiki conventions.
* The calendar plugin made *every* page depend on every other page,
  which seemed a wee tiny little bit overkill. Fixed the dependency
  calculations (I hope.)
* Removed manual ctime statting code, and just have the calendar plugin use
  %pagectime.

IkiWiki/Plugin/calendar.pm [new file with mode: 0644]
debian/changelog
debian/copyright
doc/plugins/calendar.mdwn [new file with mode: 0644]
doc/todo/calendar_--_archive_browsing_via_a_calendar_frontend.mdwn
doc/todo/plugin_data_storage.mdwn

diff --git a/IkiWiki/Plugin/calendar.pm b/IkiWiki/Plugin/calendar.pm
new file mode 100644 (file)
index 0000000..30e2f26
--- /dev/null
@@ -0,0 +1,368 @@
+#! /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}">&nbsp;</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}">&nbsp;</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 => "\&larr;");
+       }
+       add_depends($params{page}, "$archivebase/$pyear");
+       if (exists $cache{$pagespec}{"$nyear"}) {
+               $nurl = htmllink($params{page}, $params{destpage}, 
+                       "$archivebase/$nyear",
+                       linktext => "\&rarr;");
+       }
+       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
index 196de0b7a1d03a8e264e4aa93fd6460682d05144..493fc7728587c06ebd97b4b7ae119d8a78a4f51e 100644 (file)
@@ -3,8 +3,15 @@ ikiwiki (2.11) UNRELEASED; urgency=low
   * Correct a pair of logic errors that triggered if svnpath was empty.
   * If gitorigin_branch is set to the empty string, don't push or pull.
     Useful for laptop clones of remote wikis.
-
- -- Joey Hess <joeyh@debian.org>  Sun, 21 Oct 2007 20:28:16 -0400
+  * Add a calendar plugin, contributed by Manoj Srivastava.
+  * Reformat calendar plugin to ikiwiki conventions.
+  * The calendar plugin made *every* page depend on every other page,
+    which seemed a wee tiny little bit overkill. Fixed the dependency
+    calculations (I hope.)
+  * Removed manual ctime statting code, and just have the calendar plugin use
+    %pagectime.
+
+ -- Joey Hess <joeyh@debian.org>  Mon, 22 Oct 2007 18:24:00 -0400
 
 ikiwiki (2.10) unstable; urgency=low
 
index 2681da80746b63e1959c459bd5bdc7f0e1e4a41f..5ccbf9bf9e581d50dabe059cafbaee2a390111e5 100644 (file)
@@ -78,6 +78,10 @@ Files: plugins/rst
 Copyright: © martin f. krafft <madduck@madduck.net>
 License: GPL-2
 
+Files: calendar.pm
+Copyright: (c) 2006, 2007 Manoj Srivastava <srivasta@debian.org>
+License: GPL-2+
+
 Files: doc/logo/*
 Copyright: © 2006 Recai Oktaş <roktas@debian.org>
 License: GPL-2+
diff --git a/doc/plugins/calendar.mdwn b/doc/plugins/calendar.mdwn
new file mode 100644 (file)
index 0000000..99e2ab1
--- /dev/null
@@ -0,0 +1,76 @@
+[[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.
index 6fd9fb1cf8e1c95a33548c2026628febe5cb0255..caff547808afe9000a60b6f2db4ebe2f10f46162 100644 (file)
@@ -119,4 +119,6 @@ And that's all I've heard so far. Hoping I didn't miss another patch?
 >> I think I have solved the ""Need to look at all pages that match the spec"" issue; but the nightly rebuild to handle the current day changing still remain. I use cron. It is now, however, richly documented :)
 
 
---ManojSrivastava
\ No newline at end of file
+--ManojSrivastava
+
+> Finally reviewed and applied this. [[done]]! --[[Joey]]
index 44888327e3cd02f1dfb92e23b1ef26d506f7ea0c..7078a6ed35ce3f8d7bfdb3db46826b249240ebfa 100644 (file)
@@ -58,3 +58,11 @@ If I do this, I might as well also:
 * Change the link= link= stuff to just links=link+link etc.
 * Change the delimiter from space to comma; commas are rare in index files,
   so less ugly escaped delimiters to deal with.
+
+--- 
+
+The [[plugins/calendar]] plugin could use plugin data storage to record
+which pages have a calendar for the current time. Then ensure they are
+rebuilt at least once a day. Currently, it needs a cron job to rebuild
+the *whole* wiki every day; with this enhancement, the cron job would only
+rebuild the few pages that really need it.