diff --git a/lisp/org-feed.el b/lisp/org-feed.el
index 6fc7ace1a..87c9a1590 100644
--- a/lisp/org-feed.el
+++ b/lisp/org-feed.el
@@ -37,35 +37,36 @@
;; (setq org-feed-alist
;; '(("ReQall"
;; "http://www.reqall.com/user/feeds/rss/a1b2c3....."
-;; "~/org/feeds.org" "ReQall Entries" nil)
+;; "~/org/feeds.org" "ReQall Entries")
;;
;; With this setup, the command `M-x org-feed-update-all' will
;; collect new entries in the feed at the given URL and create
-;; entries as subheading under the "ReQall Entries" heading in the
-;; file "~/org.feeds.org". The final element in the alist entry in
-;; this list can be a filter function to further process the parsed
-;; information. For example, here we turn entries with
+;; entries as subheadings under the "ReQall Entries" heading in the
+;; file "~/org.feeds.org".
+;; In addition to these standard arguments, additional keyword-value
+;; pairs are possible. For example, here we turn entries with
;; "Task" into TODO entries by adding the
-;; keyword to the title:
+;; keyword to the title, usinf the `:filter' argument:
;;
;; (setq org-feed-alist
;; '(("ReQall"
;; "http://www.reqall.com/user/feeds/rss/a1b2c3....."
;; "~/org/feeds.org" "ReQall Entries"
-;; my-raquall-filter)))
+;; :filter my-reqall-filter)))
;;
-;; (defun my-requall-filter (e)
+;; (defun my-reqall-filter (e)
;; (when (equal (plist-get e :category) "Task")
;; (setq e (plist-put e :title
;; (concat "TODO " (plist-get e :title)))))
;; e)
;;
-;; Another possibility for the filter function would be to format
-;; the entire Org node for the feed item, by adding the formatted
-;; entry as a `:formatted-for-org' property:
+;; A `:template' entry in the alist would override the template
+;; in `org-feed-default-template' for the construction of the outline
+;; node to be inserted. Another possibility would be for the filter
+;; function to create the Org node for the feed item, by adding the
+;; formatted entry as a `:formatted-for-org' property:
;;
-;;
-;; (defun my-requall-filter (e)
+;; (defun my-reqall-filter (e)
;; (setq e (plist-put
;; e :formatted-for-org
;; (format "* %s\n%s"
@@ -84,7 +85,7 @@
;; org-feed.el needs to keep track of GUIDs in the feed it has
;; already processed. It does so by listing them in a special
;; drawer, FEEDGUIDS, under the heading that received the input of
-;; te feed. You should add FEEDGUIDS to your list of drawers
+;; the feed. You should add FEEDGUIDS to your list of drawers
;; in the files that receive feed input:
;;
;; #+DRAWERS: PROPERTIES LOGBOOK FEEDGUIDS
@@ -92,10 +93,14 @@
;; Acknowledgements
;; ----------------
;;
-;; org-feed.el is based on ideas by Brad Bozarth who implemented it
-;; using shell and awk scripts, and who in this way made me for the
-;; first time look into an RSS feed, showing me how simple this really
-;; was.
+;; org-feed.el is based on ideas by Brad Bozarth who implemented a
+;; similar mechanism using shell and awk scripts, and who in this
+;; way made me for the first time look into an RSS feed, showing
+;; how simple this really was. Because I wanted to include a
+;; solution into Org with as few dependencies as possible, I
+;; reimplemented his ideas in Emacs Lisp.
+
+;;; Code:
(require 'org)
@@ -116,33 +121,66 @@ name a custom name for this feed
URL the Feed URL
file the target Org file where entries should be listed
headline the headline under which entries should be listed
-filter a filter function to modify the property list before
- an Org entry is created from it.
+
+Additional argumetns can be given using keyword-value pairs:
+
+:template template-string
+ The template to create an Org node from a feed item
+
+:filter filter-function
+ A function to filter entries before Org nodes are
+ created from them.
+
+If no template is given, the one in `org-feed-default-template' is used.
+See the docstring of that variable for information on the syntax of this
+template. If creating the node required more logic than a template can
+provide, this task can be delegated to the filter function.
The filter function gets as a argument a property list describing the item.
That list has a property for each field, for example `:title' for the
`
' field and `:pubDate' for the publication date. In addition,
it contains the following properties:
-`:item-full-text' the full text in the tag.
+`:item-full-text' the full text in the tag
`:guid-permalink' t when the guid property is a permalink
The filter function can modify the existing fields before an item
-is constructed from the `:title', `:pubDate', `:link', `:guid', and
-`:description' fields. For more control, the filter can construct
-the Org item itself, by adding a `:formatted-for-org' property that
-specifies the complete outline node that should be added.
+is constructed using the template. Or it can construct the node directly,
+by adding a `:formatted-for-org' property that specifies the complete
+outline node that should be added.
-If the filter returns nil for some entries, these will be marked as seen
-but *not* inserted into the inbox."
+The filter should return the modified entry property list. It may also
+return nil to indicate that this entry should not be added to the Org file
+at all."
:group 'org-feed
:type '(repeat
- (list :value ("" "http://" "" "" nil)
+ (list :value ("" "http://" "" "")
(string :tag "Name")
(string :tag "Feed URL")
(file :tag "File for inbox")
(string :tag "Headline for inbox")
- (symbol :tag "Filter Function"))))
+ (repeat :inline t
+ (choice
+ (list :inline t :tag "Template"
+ (const :template) (string :tag "Template"))
+ (list :inline t :tag "Filter"
+ (const :filter) (symbol :tag "Filter Function")))))))
+
+(defcustom org-feed-default-template "* %h\n %U\n %description\n %a\n"
+ "Template for the Org node created from RSS feed items.
+This is just the default, each feed can specify its own.
+Any fields from the feed item can be interpolated into the template with
+%name, for example %title, %description, %pubDate etc. In addition, the
+following special escapes are valid as well:
+
+%h the title, or the first line of the description
+%t the date as a stamp, either from (if present), or
+ the current date.
+%T date and time
+%u,%U like %t,%T, but inactive time stamps
+%a A link, from if that is a permalink, else from "
+ :group 'org-feed
+ :type '(string :tag "Template"))
(defcustom org-feed-save-after-adding t
"Non-nil means, save buffer after adding new feed items."
@@ -165,11 +203,12 @@ of the file pointed to by the URL."
"Non-nil means, assume feeds to be stable.
A stable feed is one which only adds and removes items, but never removes
an item with a given GUID and then later adds it back in. So if the feed
-is stable, this means we can simple remember the GUIDs present as the ones
-we have seen, and we can forget GUIDs that used to be in the feed but no
-longer are. So for stable feeds, we only need to remember a limited
-number of GUIDs. For unstable ones, we need to remember all GUIDs we have
-ever seen, which can be a very long list indeed."
+is stable, this means we can simple remember the GUIDs present in the feed
+at any given time, as the ones we have seen and precessed. So we can
+forget GUIDs that used to be in the feed but no longer are.
+Thus, for stable feeds, we only need to remember a limited number of GUIDs.
+For unstable ones, we need to remember all GUIDs we have ever seen, which
+can be a very long list indeed."
:group 'org-feed
:type 'boolean)
@@ -217,7 +256,9 @@ it can be a list structured like an entry in `org-feed-alist'."
(feed-url (nth 1 feed))
(feed-file (nth 2 feed))
(feed-headline (nth 3 feed))
- (feed-formatter (nth 4 feed))
+ (feed-filter (nth 1 (memq :filter feed)))
+ (feed-template (or (nth 1 (memq :template feed))
+ org-feed-default-template))
feed-buffer feed-pos
entries entries2 old-guids current-guids new new-selected e)
(setq feed-buffer (org-feed-get-feed feed-url))
@@ -238,10 +279,13 @@ it can be a list structured like an entry in `org-feed-alist'."
;; Format the new entries
(run-hooks 'org-feed-before-adding-hook)
(setq new-selected new)
- (when feed-formatter
- (setq new-selected (mapcar feed-formatter new-selected)))
- (setq new-selected (mapcar 'org-feed-format new-selected))
- (setq new-selected (delq nil new-selected))
+ (when feed-filter
+ (setq new-selected (mapcar feed-filter new-selected)))
+ (setq new-selected
+ (delq nil
+ (mapcar
+ (lambda (e) (org-feed-format-entry e feed-template))
+ new-selected)))
;; Insert the new items
(apply 'org-feed-add-items feed-pos new-selected)
;; Update the list of seen GUIDs in a drawer
@@ -337,38 +381,57 @@ When REPLACE is non-nil, replace all GUIDs by the new ones."
(org-paste-subtree level (plist-get entry :formatted-for-org) 'yank))
(org-mark-ring-push pos))))
-(defun org-feed-format (entry)
+(defun org-feed-format-entry (entry template)
"Format ENTRY so that it can be inserted into an Org file.
ENTRY is a property list. This function adds a `:formatted-for-org' property
and returns the full property list.
If that property is already present, nothing changes."
(unless (or (not entry) ; not an entry at all
(plist-get entry :formatted-for-org)) ; already formatted
- (let (lines fmt tmp indent)
- (setq lines (org-split-string (or (plist-get entry :description) "???")
+ (let (dlines fmt tmp indent)
+ (setq dlines (org-split-string (or (plist-get entry :description) "???")
"\n")
- indent " ")
- (setq fmt
- (concat
- "* " (or (plist-get entry :title) (car lines)) "\n"
- (if (setq tmp (plist-get entry :pubDate))
- (concat
- " ["
- (substring
- (format-time-string (cdr org-time-stamp-formats)
- (org-read-date t t tmp))
- 1 -1)
- "]\n"))
- (concat " :PROPERTIES:\n :FEED-GUID: "
- (plist-get entry :guid)
- "\n :END:\n")
- (mapconcat (lambda (x) (concat indent x)) lines "\n") "\n"
- (if (setq tmp (or (and (plist-get entry :guid-permalink)
- (plist-get entry :guid))
- (plist-get entry :link)))
- (concat " [[" tmp "]]\n")))
- entry (plist-put entry :formatted-for-org fmt))))
- entry)
+ v-h (or (plist-get entry :title) (car dlines) "???")
+ time (or (if (plist-get entry :pubDate)
+ (org-read-date t t (plist-get entry :pubDate)))
+ (current-time))
+ v-t (format-time-string (org-time-stamp-format nil nil) time)
+ v-T (format-time-string (org-time-stamp-format t nil) time)
+ v-u (format-time-string (org-time-stamp-format nil t) time)
+ v-U (format-time-string (org-time-stamp-format t t) time)
+ v-a (if (setq tmp (or (and (plist-get entry :guid-permalink)
+ (plist-get entry :guid))
+ (plist-get entry :link)))
+ (concat "[[" tmp "]]\n")
+ ""))
+ (with-temp-buffer
+ (insert template)
+ (debug)
+ (goto-char (point-min))
+ (while (re-search-forward "%\\([a-zA-Z]+\\)" nil t)
+ (setq name (match-string 1))
+ (cond
+ ((member name '("h" "t" "T" "u" "U" "a"))
+ (replace-match (symbol-value (intern (concat "v-" name))) t t))
+ ((setq tmp (plist-get entry (intern (concat ":" name))))
+ (save-excursion
+ (save-match-data
+ (beginning-of-line 1)
+ (when (looking-at (concat "^\\([ \t]*\\)%" name "[ \t]*$"))
+ (setq tmp (org-feed-make-indented-block
+ tmp (org-get-indentation))))))
+ (replace-match tmp t t))
+ t))
+ (setq entry (plist-put entry :formatted-for-org (buffer-string))))))
+ entry)
+
+(defun org-feed-make-indented-block (s n)
+ "Add indentaton of N spaces to a multiline string S."
+ (if (not (string-match "\n" s))
+ s
+ (mapconcat 'identity
+ (org-split-string s "\n")
+ (concat "\n" (make-string n ?\ )))))
(defun org-feed-get-feed (url)
"Get the RSS feed file at URL and return the buffer."