tagging: Allow using titles in for related stories.
[matthijs/upstream/blosxom-plugins.git] / gavinc / storydate
1 # Blosxom Plugin: storydate
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
3 # (based on work by Frank Hecker <hecker@hecker.org>
4 #  and Bob Schumaker <cobblers@pobox.com>)
5 # Version: 0.003000
6 # Documentation: See the bottom of this file or type: perldoc storydate
7
8 package storydate;
9
10 use strict;
11
12 use vars qw(%config);
13 use File::stat;
14
15 # Uncomment next line to enable debug output (don't uncomment debug() lines)
16 #use Blosxom::Debug debug_level => 1;
17
18 # --- Configuration variables -----
19
20 %config = ();
21
22 # Perl modules to use for strftime, in search order
23 $config{strftime_modules} = [ qw(Time::Piece Date::Format POSIX) ];
24
25 # ---------------------------------
26 # __END_CONFIG__
27
28 # Package variables
29 use vars qw(
30   $story_rfc822
31   $story_iso8601
32   $story_mtime_rfc822
33   $story_mtime_iso8601
34   $now_rfc822
35   $now_iso8601
36   $latest_rfc822
37   $latest_iso8601
38   %strftime_formats
39 );
40
41 %strftime_formats = (
42     rfc822      => "%a, %d %b %Y %T %z",
43     # ISO 8601 format (localtime, including time zone offset)
44     # Format is YYYY-MM-DDThh:mm:ssTZD per http://www.w3.org/TR/NOTE-datetime
45     iso8601     => "%Y-%m-%dT%T%z",
46 );
47
48 sub start { 
49     my $now = time();
50     $now_rfc822  = format_date('rfc822', $now);
51     $now_iso8601 = format_date('iso8601', $now);
52     # debug(1, "now_rfc822: $now_rfc822");
53     # debug(1, "now_iso8601: $now_iso8601");
54     1;
55 }
56
57 sub filter {
58     my ($pkg, $files, $others) = @_;
59
60     # Find the latest publish date in our stories
61     my $latest_ts = 0;
62     for (values %$files) {
63           $latest_ts = $_ if $_ > $latest_ts;
64     }
65
66     # Format the 'latest' variables
67     $latest_rfc822  = format_date('rfc822', $latest_ts);
68     $latest_iso8601 = format_date('iso8601', $latest_ts);
69
70     return 1;
71 }
72
73 sub story {
74     my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
75
76     ($story_rfc822, $story_mtime_rfc822) 
77         = get_story_dates('rfc822', $path, $filename);
78     ($story_iso8601, $story_mtime_iso8601) 
79         = get_story_dates('iso8601', $path, $filename);
80
81     return 1;
82 }
83
84 # Get the 'file' (publish/official) and 'mtime' formatted dates for the given story file
85 sub get_story_dates {
86     my ($format, $path, $filename) = @_; 
87     my ($file_dt, $mtime_dt) = ('', '');
88     $path ||= '';
89
90     my $story_file = "$blosxom::datadir$path/$filename.$blosxom::file_extension";
91     my $timestamp = $blosxom::files{ $story_file };
92     # debug(2, "$path/$filename timestamp: $timestamp");
93
94     if (my $stat = stat( $story_file )) {
95         if (my $mtime = $stat->mtime) {
96             # debug(2, "$path/$filename mtime: = $mtime");
97
98             $mtime_dt = format_date($format, $mtime);
99
100             $timestamp ||= $mtime;
101         }
102         else {
103             warn "storydate: cannot get mtime on story file '$story_file'\n";
104         }
105     }
106     else {
107         warn "storydate: cannot stat story file '$story_file'\n";
108     }
109
110     $file_dt = format_date($format, $timestamp) if $timestamp;
111
112     return wantarray ? ( $file_dt, $mtime_dt ) : $file_dt;
113 }
114
115 sub format_date {
116     my ($format, $time, $use_gmtime) = @_;
117     return unless $format;
118
119     my $date = '';
120     if ($format =~ m/%/) {
121         $date = strftime($format, $time, $use_gmtime);
122     }
123     elsif ($strftime_formats{ $format }) {
124         $date = strftime($strftime_formats{ $format }, $time, $use_gmtime);
125     }
126     # Hack to handle the fact that ISO8601 dates require a : in the timezone, which strftime doesn't support
127     if ($format eq 'iso8601') {
128         $date =~ s/(\d)(\d{2})$/$1:$2/;
129     }
130
131     return $date;
132 }
133
134 # strftime wrapper, using the first strftime() it finds in 'strftime_modules'
135 my %cache = ();
136 my $strftime_module = '';
137 sub strftime {
138     my ($format, $time, $use_gmtime) = @_;
139     return unless $format && $time;
140     $use_gmtime = 0 unless defined $use_gmtime;
141   
142     # Check cache
143     my $cache_key = "$time:$use_gmtime:$format";
144     return $cache{ $cache_key } if exists $cache{ $cache_key };
145
146     # For testing
147     $strftime_module = $ENV{BLOSXOM_STORYDATE_STRFTIME_MODULE};
148
149     # Search for a strftime module, and load
150     if (! $strftime_module) {
151         for my $module (@{$config{strftime_modules}}) {
152             if (eval "require $module") {
153                 $strftime_module = $module;
154                 # debug(2, "strftime_module: $strftime_module");
155                 last;
156             }
157         }
158         if (! $strftime_module) {
159             warn "storydate: cannot find any suitable strftime module\n";
160             return '';
161         }
162     }
163
164     my $datetime = '';
165     if ($strftime_module eq 'Time::Piece') {
166         my $t = $use_gmtime ? Time::Piece->gmtime($time) : Time::Piece->localtime($time);
167  
168         # Seems to be a bug in Time::Piece %z handling - workaround
169         my $tz_offset = sprintf("%+03d%02d", int($t->tzoffset / 3600), ($t->tzoffset % 3600) / 60);
170         $format =~ s/%z/$tz_offset/g;
171
172         $datetime = $t->strftime( $format );
173     }
174
175     elsif ($strftime_module eq 'Date::Format') {
176         my @t = $use_gmtime ? gmtime($time) : localtime($time);
177         $datetime = Date::Format::strftime($format, \@t);
178     }
179
180     elsif ($strftime_module eq 'POSIX') {
181         my @t = $use_gmtime ? gmtime($time) : localtime($time);
182         $datetime = POSIX::strftime($format, @t);
183     }
184
185     $cache{ $cache_key } = $datetime if $datetime;
186     return $datetime;
187 }
188
189
190 1;
191
192 __END__
193
194 =head1 NAME
195
196 storydate - blosxom plugin to provide story dates in various formats 
197 for use by other plugins
198
199
200 =head1 DESCRIPTION
201
202 storydate is a blosxom plugin that provides story dates in various 
203 formats for use by other plugins. It defines the following variables:
204
205 =over 4
206
207 =item $story_rfc822
208
209 The official/publication date of the story in RFC822 format.
210
211 =item $story_iso8601
212
213 The official/publication date of the story in ISO8601 format.
214
215 =item $story_mtime_rfc822
216
217 The last modification time of the story in RFC822 format.
218
219 =item $story_mtime_iso8601
220
221 The last modification time of the story in ISO8601 format.
222
223 =item $now_rfc822
224
225 The current time in RFC822 format.
226
227 =item $now_iso8601
228
229 The current time in ISO8601 format.
230
231 =item $latest_rfc822
232
233 The latest story publication date, in RFC822 format.
234
235 =item $latest_iso8601
236
237 The latest story publication date, in ISO8601 format.
238
239 =back
240
241 In addition, storydate defines a few subroutines that might be useful
242 to other plugins:
243
244 =over 4
245
246 =item strftime($format, $time)
247
248 Returns $time, formatted according to the L<strftime(3)> format 
249 $format. Requires one of the following perl modules be available to 
250 provide a strftime() function: Time::Piece, Date::Format, or POSIX.
251
252 =item get_story_dates($format, $path, $filename)
253
254 Returns the publication date and the last modified date of the story 
255 at "$path/$filename.$blosxom::file_extension", formatted according
256 to the L<strftime(3)> format $format.
257
258 =back
259
260
261 =head1 USAGE
262
263 storydate should be loaded early, before any story plugins that want
264 to make use of it.
265
266
267 =head1 SEE ALSO
268
269 lastmodified2
270
271 Blosxom: http://blosxom.sourceforge.net/
272
273
274 =head1 ACKNOWLEDGEMENTS
275
276 storydate is based on Frank Hecker's lastmodified2 plugin, which is 
277 itself based on Bob Schumaker's lastmodified plugin.
278
279 The problem with lastmodified2 is that its HTTP header functionality 
280 requires that it runs very late, which makes it useless for providing
281 dates to story plugins. storydate is basically just all the parts of
282 lastmodified2 that can be run early pulled out into a separate plugin.
283
284
285 =head1 AUTHOR
286
287 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
288
289
290 =head1 LICENSE
291
292 Copyright 2007, Gavin Carr.
293
294 This plugin is licensed under the same terms as blosxom itself i.e.
295
296 Permission is hereby granted, free of charge, to any person obtaining a
297 copy of this software and associated documentation files (the "Software"),
298 to deal in the Software without restriction, including without limitation
299 the rights to use, copy, modify, merge, publish, distribute, sublicense,
300 and/or sell copies of the Software, and to permit persons to whom the
301 Software is furnished to do so, subject to the following conditions:
302
303 The above copyright notice and this permission notice shall be included
304 in all copies or substantial portions of the Software.
305
306 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
307 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
308 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
309 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
310 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
311 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
312 OTHER DEALINGS IN THE SOFTWARE.
313
314 =cut
315
316 # vim:ft=perl:sw=4
317