Merged from mwolson@gnu.org--2006 (patch 36-37)
[planner-el.git] / planner-calendar.el
blob29d4bc220262577707fa9b32feb9f17bd9d7c55e
1 ;;; planner-calendar.el --- Create a clickable calendar in published html
3 ;; Copyright (C) 2003, 2004 Gary V. Vaughan (gary AT gnu DOT org)
4 ;; Parts copyright (C) 2005, 2006 Free Software Foundation, Inc.
6 ;; Emacs Lisp Archive Entry
7 ;; Filename: planner-calendar.el
8 ;; Version: 1.1
9 ;; Date: Tue, 1 June 2004
10 ;; Keywords: hypermedia
11 ;; Author: Gary V. Vaughan (gary AT gnu DOT org)
12 ;; Description: Create a clickable calendar in published html
13 ;; Compatibility: Emacs21, XEmacs21
15 ;; This file is part of Planner. It is not part of GNU Emacs.
17 ;; Planner is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by
19 ;; the Free Software Foundation; either version 2, or (at your option)
20 ;; any later version.
22 ;; Planner is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
24 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 ;; General Public License for more details.
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with Planner; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;;; Commentary:
34 ;; You will need to install Emacs Muse before this is of any use to
35 ;; you.
37 ;; To publish calendars in your day pages, it is necessary to do two
38 ;; steps.
40 ;; 1. Add (require 'planner-calendar) to your configuration.
42 ;; 2. Add a <calendar> tag to either your header, footer, or
43 ;; `planner-day-page-template', depending on where you want it to
44 ;; appear.
46 ;; If you decide to create a today link for published planner pages,
47 ;; add a hook function like this:
49 ;; (eval-after-load "muse-publish"
50 ;; (add-hook 'muse-after-publish-hook
51 ;; 'planner-calendar-create-today-link nil t))
53 ;;; Contributors:
55 ;; drkm <darkman_spam@yahoo.fr> contributed a small patch that fixes a
56 ;; planner-calendar boundary case when last day of the month is
57 ;; Sunday.
59 ;;; Code:
61 (require 'calendar)
62 (require 'muse)
63 (require 'planner)
65 (eval-when-compile
66 (require 'planner-publish))
68 (defgroup planner-calendar nil
69 "Options controlling the behaviour of planner calendar publication."
70 :group 'planner)
72 (defcustom planner-calendar-prev-month-button "&laquo;"
73 "*Default html entity to use for previous month buttons."
74 :type 'string
75 :group 'planner-calendar)
77 (defcustom planner-calendar-next-month-button "&raquo;"
78 "*Default html entity to use for next month buttons."
79 :type 'string
80 :group 'planner-calendar)
82 (defcustom planner-calendar-day-header-chars 3
83 "*Default number of characters to use for day column header names."
84 :type 'integer
85 :group 'planner-calendar)
87 (defcustom planner-calendar-html-tag-marker "<div id=\"content\">"
88 "*Default html block element to add calendar HTML to."
89 :type 'string
90 :group 'planner-calendar)
92 (defcustom planner-calendar-today-page-name "today"
93 "*Default base name for published today page link file."
94 :type 'string
95 :group 'planner-calendar)
97 (defcustom planner-calendar-nop-buttons-flag t
98 "Non-nil means add <nop> tags before navigation buttons in the calendar."
99 :type 'boolean
100 :group 'planner-calendar)
102 (defmacro planner-calendar-render (var begin end tag class &rest body)
103 "Generate a row of days for the calendar."
104 `(let (string)
105 (calendar-for-loop ,var from ,begin to ,end do
106 (let ((day (mod (+ calendar-week-start-day i) 7))
107 (wrap-p (and (= 6 (mod ,var 7)) (/= ,var ,end))))
108 (setq string (concat string
109 "<" ,tag " class=\"" ,class " "
110 (calendar-day-name day nil t) "\">"
111 ,@body
112 "</" ,tag ">\n"
113 (and wrap-p "</tr>\n<tr>\n")))))
114 string))
116 (put 'planner-calendar-render 'lisp-indent-function 1)
118 (defun planner-calendar-date-to-filename (date)
119 "See `planner-date-to-filename' except don't choke on nil DATE."
120 (and date (planner-date-to-filename date)))
122 ;; calendar-week-start-day
123 (defun planner-calendar (month year &optional arrows)
124 "Generate a string of html to render a clickable calendar for MONTH YEAR.
125 If ARROWS is non-nil, include prev/next month arrows."
126 (let*
127 ((blank-days ; at start of month
128 (mod (- (calendar-day-of-week (list month 1 year))
129 calendar-week-start-day)
131 (last (calendar-last-day-of-month month year))
132 (pad-days ; at end of month
133 (- 7 (1+ (calendar-day-of-week (list month last year)))))
134 ;; don't use leading whitespace in the generated html, or the
135 ;; other markup rules will add <blockquote> sections!
136 (string
137 (concat
138 "<table class=\"month-calendar\">\n"
139 "<tr class=\"month-calendar-head\">\n"
140 (if arrows
141 (concat
142 "<th>"
143 (planner-calendar-prev-month-href
144 month year
145 planner-calendar-prev-month-button
146 planner-calendar-nop-buttons-flag)
147 "</th>\n"
148 "<th colspan=\"5\">\n")
149 "<th colspan=\"7\">\n")
150 (format "%s %d" (calendar-month-name month) year)
151 "</th>\n"
152 (when arrows
153 (concat "<th>"
154 (planner-calendar-next-month-href
155 month year planner-calendar-next-month-button
156 planner-calendar-nop-buttons-flag)
157 "</th>\n"))
158 "</tr>\n"
159 "<tr>\n"
161 ;; add day name headings
162 (planner-calendar-render i 0 6
163 "th" "month-calendar-day-head"
164 (calendar-day-name day planner-calendar-day-header-chars t))
166 "</tr>\n"
167 "<tr>\n"
169 ;; add blank days before the first of the month
170 (planner-calendar-render i 0 (1- blank-days)
171 "td" "month-calendar-day-noday" "&nbsp;")
173 ;; put in the days of the month
174 (planner-calendar-render i blank-days (+ last blank-days -1)
175 "td" (if (planner-page-file
176 (planner-calendar-date-to-filename
177 (list month (- i blank-days -1) year)))
178 "month-calendar-day-link"
179 "month-calendar-day-nolink")
180 (planner-calendar-published-file-href
181 (planner-calendar-date-to-filename
182 (list month (- i blank-days -1) year))
183 (int-to-string (- i blank-days -1))
184 planner-calendar-nop-buttons-flag))
186 ;; add padding days at end of month to make rule lines neat
187 (unless (zerop (mod (+ blank-days last) 7))
188 (planner-calendar-render i
189 (+ last blank-days) (+ last blank-days pad-days -1)
190 "td" "month-calendar-day-noday" "&nbsp;"))
192 "</tr>\n"
193 "</table>\n")))
194 string))
196 (defun planner-calendar-from-wiki (&optional arrows wiki)
197 "Generate a string of html (possibly with ARROWS) for a calendar for WIKI."
198 (let ((page (or wiki (planner-page-name))))
199 (save-match-data
200 (when (string-match planner-date-regexp page)
201 (let ((year (string-to-number (substring page 0 4)))
202 (month (string-to-number (substring page 5 7))))
203 (planner-calendar month year arrows))))))
205 (defun planner-calendar-published-file-href (wiki &optional name nop)
206 "Return an href anchor string to the published WIKI if WIKI exists."
207 (if (and (planner-page-file wiki)
208 (not (planner-private-p (planner-page-file wiki))))
209 (planner-link-href wiki (or name wiki))
210 (or name wiki)))
212 (defun planner-calendar-yesterday (date)
213 "Return the day before DATE as a (month day year) list."
214 (let* ((year (extract-calendar-year date))
215 (month (extract-calendar-month date))
216 (day (extract-calendar-day date))
217 (prev-year (if (and (= 1 month) (= 1 day)) (1- year) year))
218 (prev-month (if (= 1 day) (1+ (mod (+ month 10) 12)) month))
219 (prev-day (if (= 1 day)
220 (calendar-last-day-of-month prev-month prev-year)
221 (1- day))))
222 (list prev-month prev-day prev-year)))
224 (defun planner-calendar-tomorrow (date)
225 "Return the day after DATE as a (month day year) list."
226 (let* ((year (extract-calendar-year date))
227 (month (extract-calendar-month date))
228 (day (extract-calendar-day date))
229 (last-day (calendar-last-day-of-month month year))
230 (next-year
231 (if (and (= 12 month) (= 31 day))
232 (1+ year)
233 year))
234 (next-month
235 (if (>= day last-day)
236 (1+ (mod month 12))
237 month))
238 (next-day (if (< day last-day) (1+ day) 1)))
239 (list next-month next-day next-year)))
241 (defun planner-calendar-today (&optional max-days)
242 "Return today or the first day before today with a day page."
243 (planner-calendar-prev-date
244 (planner-calendar-tomorrow (calendar-current-date))))
246 (defun planner-calendar-create-today-link (&optional name)
247 "Create a link to the newest published day page.
248 Add this to `muse-after-publish-hook' to create a \"today\" soft
249 link to the newest published planner day page, on operating systems that
250 support POSIX \"ln\"."
251 (let* ((today-name planner-calendar-today-page-name)
252 (target-file (planner-published-file (or name today-name)))
253 (source-file (planner-published-file
254 (planner-calendar-date-to-filename
255 (planner-calendar-today)))))
256 (when (file-exists-p target-file)
257 (funcall planner-delete-file-function target-file))
258 (make-symbolic-link source-file target-file t)))
260 (defun planner-calendar-prev-date (date &optional max-days)
261 "Return the first day before DATE with a day page."
262 (let ((days (or max-days 180))
263 (yesterday date)
264 (done nil))
265 (while (and (not done) (> days 0))
266 (setq yesterday (planner-calendar-yesterday yesterday)
267 days (1- days))
268 (let ((wiki (planner-calendar-date-to-filename yesterday)))
269 (setq done (and (planner-page-file wiki)
270 (not (planner-private-p (planner-page-file wiki)))))))
271 (if done yesterday nil)))
273 (defun planner-calendar-next-date (date &optional max-days)
274 "Return the first day after DATE with a day page."
275 (let ((days (or max-days 180))
276 (tomorrow date)
277 (done nil))
278 (while (and (not done) (> days 0))
279 (setq tomorrow (planner-calendar-tomorrow tomorrow)
280 days (1- days))
281 (let ((wiki (planner-calendar-date-to-filename tomorrow)))
282 (setq done (and (planner-page-file wiki)
283 (not (planner-private-p (planner-page-file wiki)))))))
284 (if done tomorrow nil)))
286 (defun planner-calendar-prev-date-href (date name &optional nop max-days)
287 "Return an href anchor string for the first day page before DATE."
288 (let ((prev-date (planner-calendar-prev-date date max-days)))
289 (planner-calendar-published-file-href
290 (planner-calendar-date-to-filename prev-date) name nop)))
292 (defun planner-calendar-next-date-href (date name &optional nop max-days)
293 "Return an href anchor string for the first day page after DATE."
294 (let ((next-date (planner-calendar-next-date date max-days)))
295 (planner-calendar-published-file-href
296 (planner-calendar-date-to-filename next-date) name nop)))
298 (defun planner-calendar-prev-month-href (month year name &optional nop max-days)
299 "Return an href anchor string for the last day page in the previous month."
300 (let ((prev-date (planner-calendar-prev-date (list month 1 year) max-days))
301 (muse-publish-desc-transforms nil))
302 (planner-calendar-published-file-href
303 (planner-calendar-date-to-filename prev-date) name nop)))
305 (defun planner-calendar-next-month-href (month year name &optional nop max-days)
306 "Return an href anchor string for the first day page in the following month."
307 (let ((next-date
308 (planner-calendar-next-date
309 (list month (calendar-last-day-of-month month year) year)
310 max-days))
311 (muse-publish-desc-transforms nil))
312 (planner-calendar-published-file-href
313 (planner-calendar-date-to-filename next-date) name nop)))
315 (defun planner-calendar-prev-day-wiki (&optional wiki max-days)
316 "Return the first planner day page before this one."
317 (let* ((page (or wiki (planner-page-name)))
318 (date (planner-filename-to-calendar-date page)))
319 (planner-calendar-date-to-filename
320 (planner-calendar-prev-date date max-days))))
322 (defun planner-calendar-next-day-wiki (&optional wiki max-days)
323 "Return the first planner day page after this one."
324 (let* ((page (or wiki (planner-page-name)))
325 (date (planner-filename-to-calendar-date page)))
326 (planner-calendar-date-to-filename
327 (planner-calendar-next-date date max-days))))
329 (defun planner-calendar-prev-date-href-from-wiki (name &optional wiki max-days)
330 "Return an href anchor string for the first day page before this one."
331 (let* ((page (or wiki (planner-page-name)))
332 (date (planner-filename-to-calendar-date page)))
333 (planner-calendar-prev-date-href date name max-days)))
335 (defun planner-calendar-next-date-href-from-wiki (name &optional wiki max-days)
336 "Return an href anchor string for the first day page after this one."
337 (let* ((page (or wiki (planner-page-name)))
338 (date (planner-filename-to-calendar-date page)))
339 (planner-calendar-next-date-href date name max-days)))
341 (defun planner-calendar-prev-month-href-from-wiki (name &optional wiki max-days)
342 "Return a string for the last day page in first month before this one."
343 (let* ((page (or wiki (planner-page-name)))
344 (date (planner-filename-to-calendar-date page)))
345 (planner-calendar-prev-month-href date name max-days)))
347 (defun planner-calendar-next-month-href-from-wiki (name &optional wiki max-days)
348 "Return a string for the first day page in the first month after this one."
349 (let* ((page (or wiki (planner-page-name)))
350 (date (planner-filename-to-calendar-date page)))
351 (planner-calendar-next-month-href date name max-days)))
353 (defun planner-publish-calendar-tag (beg end attrs)
354 (let ((arrows (cdr (assoc "arrows" attrs)))
355 (wiki (cdr (assoc "wiki" attrs))))
356 (delete-region beg end)
357 (planner-insert-markup (planner-calendar-from-wiki arrows wiki))
358 (muse-publish-mark-read-only beg (point))))
360 (eval-after-load "planner-publish"
361 '(progn
362 (add-to-list 'planner-publish-markup-tags
363 '("calendar" nil t planner-publish-calendar-tag)
365 (add-to-list 'planner-publish-finalize-regexps
366 '(200 "<\\(calendar\\)>" 0 muse-publish-markup-tag))))
368 (provide 'planner-calendar)
370 ;;; planner-calendar.el ends here
372 ;; Local Variables:
373 ;; indent-tabs-mode: t
374 ;; tab-width: 8
375 ;; End: