Docfix and remove one redundant LOC.
[planner-el.git] / planner-rss.el
blob029a2b1e583973a5ed08ae51548930e954c7bf3b
1 ;;; planner-rss.el --- RSS export for the Emacs Planner (planner.el)
3 ;; Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
4 ;; Parts copyright (C) 2005, 2008 David D. Smith
6 ;; Emacs Lisp Archive Entry
7 ;; Filename: planner-rss.el
8 ;; Keywords: hypermedia
9 ;; Author: Sacha Chua <sacha@free.net.ph>
10 ;; Description: Export planner entries as an RSS feed
11 ;; URL: http://www.wjsullivan.net/PlannerMode.html
12 ;; Compatibility: Emacs20, Emacs21, Emacs22, XEmacs21
14 ;; This file is part of Planner. It is not part of GNU Emacs.
16 ;; Planner is free software; you can redistribute it and/or modify it
17 ;; under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation; either version 3, or (at your option)
19 ;; any later version.
21 ;; Planner is distributed in the hope that it will be useful, but
22 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 ;; General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with Planner; see the file COPYING. If not, write to the
28 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
29 ;; Boston, MA 02110-1301, USA.
31 ;;; Commentary:
33 ;; If you use `remember-to-planner' to keep daily notes, you can
34 ;; automatically publish remembered notes as an RSS feed by adding the
35 ;; following code to your .emacs:
37 ;; (add-to-list 'remember-planner-append-hook 'planner-rss-add-note t)
39 ;; You can also invoke `planner-rss-add-note' on any note you would
40 ;; like added.
42 ;;;_ + Contributors
44 ;; David Smith (davidsmith AT acm DOT org) ported this to work with
45 ;; Muse.
47 ;;; Code:
49 (require 'planner)
50 (require 'planner-publish)
52 (defgroup planner-rss nil
53 "Planner options for RSS feeds."
54 :prefix "planner-rss-"
55 :group 'planner)
57 (defcustom planner-rss-base-url
59 "Base URL for blog entries. Should include trailing /.
60 Example: http://sacha.free.net.ph/notebook/wiki/"
61 :type 'string
62 :group 'planner-rss)
64 ;; On my system, this is set to
65 ;;'(("."
66 ;; "/home/sacha/public_html/notebook/wiki/blog.rdf"
67 ;; "<?xml version=\"1.0\"?><rss version=\"2.0\"><channel>
68 ;;<title>sachachua's blog</title>
69 ;;<link>http://sacha.free.net.ph/notebook/wiki/today.php</link>
70 ;;<description>Random notes</description>
71 ;;</channel></rss>\n"))
72 (defcustom planner-rss-category-feeds
73 nil
74 "List of (CONDITION FILENAME INITIAL-CONTENTS).
76 If CONDITION is a regexp, all entries that match the regexp in
77 either title or body will be included in FILENAME. If CONDITION
78 is a function with one argument, it will be called with the
79 marked-up text, and a non-nil return value means include this
80 entry in FILENAME.
82 If INITIAL-CONTENTS is non-nil, it is used to initialize the file if
83 the file is not found or is corrupted.
85 Example:
86 '((\".\"
87 \"/home/sacha/public_html/notebook/wiki/blog.rdf\"
88 \"<?xml version=\\\"1.0\\\"?><rss version=\\\"2.0\\\"><channel>
89 <title>sachachua's blog</title>
90 <link>http://sacha.free.net.ph/notebook/wiki/today.php</link>
91 <description>Random notes</description>
92 </channel></rss>\n\"))"
93 :type '(repeat (group (choice regexp function) file string))
94 :group 'planner-rss)
96 (defcustom planner-rss-feed-limits nil
97 "A list of (REGEX SIZE-LIMIT ITEM-LIMIT).
99 REGEX is a regular expression that matches the filename.
100 SIZE-LIMIT, if non-nil, is the upper limit in characters.
101 ITEM-LIMIT, if non-nil, is the upper limit in items. If the feed
102 exceeds the stated limits, older items are deleted."
103 :type '(alist :key-type regexp
104 :value-type (group (choice
105 :tag "Characters: "
106 (const :tag "No limit" nil)
107 (integer))
108 (choice
109 :tag "Size: "
110 (const :tag "No limit" nil)
111 (integer))))
112 :group 'planner-rss)
114 ;; Determined from planner-rss-category-feeds.
115 ;; You don't need to set this.
116 (defvar planner-rss-file-name nil "Filename of current RSS feed.")
117 (defvar planner-rss-initial-contents nil "Initial contents.")
119 (defun planner-rss-add-item (item)
120 "Add an item to the top of the items list in `planner-rss-file-name'.
121 It will have TITLE, LINK, DESCRIPTION, PUBDATE and CATEGORIES.
122 `planner-rss-initialize' is called if necessary."
123 (save-excursion
124 (save-window-excursion
125 (find-file planner-rss-file-name)
126 (goto-char (point-min))
127 (unless (re-search-forward "<item>\\|</channel>" nil t)
128 (progn
129 (erase-buffer)
130 (insert planner-rss-initial-contents)
131 (goto-char (point-max))
132 (re-search-backward "</channel>")))
133 (goto-char (match-beginning 0))
134 (insert item)
135 (planner-rss-limit)
136 (save-buffer))))
138 (defun planner-rss-strip-tags (string)
139 "Remove all tags from STRING."
140 (planner-replace-regexp-in-string "<[^>]+>" "" string))
142 ;;;###autoload
143 (defun planner-rss-add-note (&optional feed)
144 "Export the current note using `planner-rss-add-item'.
145 If FEED is non-nil, add the note to the specified feed only.
146 Call with the interactive prefix in order to be prompted for FEED."
147 (interactive (list (when current-prefix-arg
148 (read-file-name "Feed: "))))
149 (save-window-excursion
150 (save-excursion
151 (save-restriction
152 (when (planner-narrow-to-note)
153 (let* ((seen)
154 (text (buffer-substring-no-properties (point-min) (point-max)))
155 (muse-publishing-current-file (buffer-file-name))
156 (entry (with-temp-buffer
157 (insert text)
158 (muse-publish-markup-buffer "*rss*" "planner-rss")
159 (buffer-string))))
160 (dolist (feed planner-rss-category-feeds nil)
161 (let ((condition (elt feed 0))
162 (planner-rss-file-name (elt feed 1))
163 (planner-rss-initial-contents (elt feed 2)))
164 (when (cond ((functionp condition)
165 (funcall condition text))
166 ((stringp condition)
167 (string-match condition text))
168 (t condition))
169 (unless (member planner-rss-file-name seen)
170 (add-to-list 'seen planner-rss-file-name)
171 (planner-rss-add-item entry)))))))))))
173 (defun planner-rss-limit ()
174 "Apply limits specified in `planner-rss-feed-limits'."
175 (let ((filename (expand-file-name (planner-current-file))))
176 (mapcar
177 (lambda (item)
178 (when (string-match (elt item 0) filename)
179 (planner-rss-limit-size (elt item 1))
180 (planner-rss-limit-items (elt item 2))))
181 planner-rss-feed-limits)))
183 (defun planner-rss-limit-size (limit)
184 "Delete RSS items that cause this file to go over LIMIT characters."
185 (when limit
186 (widen)
187 (goto-char limit)
188 (unless (eobp)
189 (re-search-backward "<item>" nil t)
190 (let ((start (match-beginning 0)))
191 (re-search-forward "</channel>" nil t)
192 (delete-region start (match-beginning 0))))))
194 (defun planner-rss-limit-items (limit)
195 "Delete RSS items past the LIMIT-th item."
196 (when limit
197 (widen)
198 (goto-char (point-min))
199 (while (and (> limit -1) (re-search-forward "<item>" nil t))
200 (setq limit (1- limit)))
201 (when (= limit -1)
202 (let ((start (match-beginning 0)))
203 (re-search-forward "</channel>" nil t)
204 (delete-region start (match-beginning 0))))))
206 (defun planner-publish-markup-note-rss ()
207 "Replace note with RSS 2.0 representation of note data. Borrowed
208 heavily from Sacha's personal configs."
209 (save-restriction
210 (narrow-to-region
211 (save-excursion (beginning-of-line) (point))
212 (or (save-excursion (and (re-search-forward "<item>\\|</channel>" nil t)
213 (match-beginning 0)))
214 (point-max)))
215 (let ((info (planner-current-note-info t)))
216 (delete-region (point-min) (point-max))
217 (muse-insert-markup
218 "<item>\n"
219 "<title>"
220 (muse-publish-escape-specials-in-string (planner-note-title info))
221 "</title>\n"
222 "<link>"
223 (concat planner-rss-base-url (muse-page-name) ".html#"
224 (planner-note-anchor info))
225 "</link>\n"
226 "<guid>"
227 (concat planner-rss-base-url (muse-page-name) ".html#"
228 (planner-note-anchor info))
229 "</guid>\n")
230 (when (planner-note-body info)
231 (muse-insert-markup "<description><![CDATA["
232 (with-temp-buffer
233 (insert (planner-note-body info))
234 (muse-publish-markup-buffer "*title*" "planner-rss-info")
235 (buffer-string))
236 "]]></description>\n"))
237 (when (planner-note-date info)
238 (muse-insert-markup "<pubDate>"
239 (let ((system-time-locale "C")
240 (timestamp (planner-note-timestamp info))
241 (date (planner-filename-to-calendar-date
242 (planner-note-date info)))
243 (minutes) (hour) (day) (month) (year))
244 (format-time-string
245 "%a, %d %b %Y %T %Z"
246 (when (string-match "\\([0-9]+\\):\\([0-9]+\\)" timestamp)
247 (let ((hour (string-to-number (match-string 1 timestamp)))
248 (minutes (string-to-number
249 (match-string 2 timestamp)))
250 (month (nth 0 date))
251 (day (nth 1 date))
252 (year (nth 2 date)))
253 (encode-time 0 minutes hour day month year)))))
254 "</pubDate>\n"))
255 (muse-insert-markup "</item>\n"))))
257 (defcustom planner-publish-markup-rss-functions
258 '((note . planner-publish-markup-note-rss))
259 "An alist of style types to custom functions for that kind of text for RSS.
260 For more on the structure of this list, see
261 `muse-publish-markup-functions'."
262 :type '(alist :key-type symbol :value-type function)
263 :group 'planner-publish)
265 (unless (assoc "planner-rss" muse-publishing-styles)
266 (muse-derive-style "planner-rss" "planner-xml"
267 :functions 'planner-publish-markup-rss-functions
268 :header ""
269 :footer ""
270 :prefix planner-rss-base-url)
271 (muse-derive-style "planner-rss-info" "planner-html"
272 :header ""
273 :footer ""
274 :prefix planner-rss-base-url))
276 (provide 'planner-rss)
278 ;;; planner-rss.el ends here