From ca716232b1edda365eca4ff9758fc55b73df341f Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Wed, 15 Jul 2015 08:07:59 +0200 Subject: [PATCH] new version of elfeed. --- bba.org | 4 +- elpa/elfeed-20150101.1457/elfeed-pkg.el | 5 - .../elfeed-autoloads.el | 11 +- .../elfeed-db.el | 52 ++++- .../elfeed-lib.el | 64 +++++- elpa/elfeed-20150608.1302/elfeed-pkg.el | 5 + .../elfeed-search.el | 162 +++++++------- .../elfeed-show.el | 135 ++++++++++++ .../elfeed.el | 135 ++++++------ .../xml-query.el | 4 +- .../elfeed-org-pkg.el | 1 - elpa/elfeed-org-20150108.1406/elfeed-org.el | 156 -------------- .../elfeed-org-autoloads.el | 15 +- .../elfeed-org-20150412.145/elfeed-org-pkg.el | 1 + elpa/elfeed-org-20150412.145/elfeed-org.el | 201 ++++++++++++++++++ 15 files changed, 619 insertions(+), 332 deletions(-) delete mode 100644 elpa/elfeed-20150101.1457/elfeed-pkg.el rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed-autoloads.el (75%) rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed-db.el (90%) rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed-lib.el (73%) create mode 100644 elpa/elfeed-20150608.1302/elfeed-pkg.el rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed-search.el (77%) rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed-show.el (56%) rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/elfeed.el (83%) rename elpa/{elfeed-20150101.1457 => elfeed-20150608.1302}/xml-query.el (96%) delete mode 100644 elpa/elfeed-org-20150108.1406/elfeed-org-pkg.el delete mode 100644 elpa/elfeed-org-20150108.1406/elfeed-org.el rename elpa/{elfeed-org-20150108.1406 => elfeed-org-20150412.145}/elfeed-org-autoloads.el (63%) create mode 100644 elpa/elfeed-org-20150412.145/elfeed-org-pkg.el create mode 100644 elpa/elfeed-org-20150412.145/elfeed-org.el diff --git a/bba.org b/bba.org index f948744b..cdbb877d 100644 --- a/bba.org +++ b/bba.org @@ -1001,8 +1001,8 @@ With this you can do /sudo:ssh-host:file-on-ssh-host #+BEGIN_SRC emacs-lisp (if (>= emacs-major-version 24) (progn - (add-to-list 'load-path "~/.emacs.d/elpa/elfeed-20150101.1457") - (add-to-list 'load-path "~/.emacs.d/elpa/elfeed-org-20150108.1406") + (add-to-list 'load-path "~/.emacs.d/elpa/elfeed-20150608.13027") + (add-to-list 'load-path "~/.emacs.d/elpa/elfeed-org-20150412.145") (require 'elfeed-org) (elfeed-org) (setq rmh-elfeed-org-files (list "~/org/elfeed.org")))) diff --git a/elpa/elfeed-20150101.1457/elfeed-pkg.el b/elpa/elfeed-20150101.1457/elfeed-pkg.el deleted file mode 100644 index b30bc8f9..00000000 --- a/elpa/elfeed-20150101.1457/elfeed-pkg.el +++ /dev/null @@ -1,5 +0,0 @@ -(define-package "elfeed" "20150101.1457" "an Emacs Atom/RSS feed reader" - '((emacs "24.1"))) -;; Local Variables: -;; no-byte-compile: t -;; End: diff --git a/elpa/elfeed-20150101.1457/elfeed-autoloads.el b/elpa/elfeed-20150608.1302/elfeed-autoloads.el similarity index 75% rename from elpa/elfeed-20150101.1457/elfeed-autoloads.el rename to elpa/elfeed-20150608.1302/elfeed-autoloads.el index cc432a91..ca7dada2 100644 --- a/elpa/elfeed-20150101.1457/elfeed-autoloads.el +++ b/elpa/elfeed-20150608.1302/elfeed-autoloads.el @@ -1,10 +1,9 @@ ;;; elfeed-autoloads.el --- automatically extracted autoloads ;; ;;; Code: - +(add-to-list 'load-path (or (file-name-directory #$) (car load-path))) -;;;### (autoloads (elfeed-export-opml elfeed-load-opml elfeed elfeed-update) -;;;;;; "elfeed" "elfeed.el" (21691 56987 0 0)) +;;;### (autoloads nil "elfeed" "elfeed.el" (21908 14285 0 0)) ;;; Generated autoloads from elfeed.el (autoload 'elfeed-update "elfeed" "\ @@ -32,16 +31,14 @@ Export the current feed listing to OPML-formatted FILE. ;;;*** ;;;### (autoloads nil nil ("elfeed-db.el" "elfeed-lib.el" "elfeed-pkg.el" -;;;;;; "elfeed-search.el" "elfeed-show.el" "xml-query.el") (21691 -;;;;;; 56987 496165 0)) +;;;;;; "elfeed-search.el" "elfeed-show.el" "xml-query.el") (21908 +;;;;;; 14285 676992 0)) ;;;*** -(provide 'elfeed-autoloads) ;; Local Variables: ;; version-control: never ;; no-byte-compile: t ;; no-update-autoloads: t -;; coding: utf-8 ;; End: ;;; elfeed-autoloads.el ends here diff --git a/elpa/elfeed-20150101.1457/elfeed-db.el b/elpa/elfeed-20150608.1302/elfeed-db.el similarity index 90% rename from elpa/elfeed-20150101.1457/elfeed-db.el rename to elpa/elfeed-20150608.1302/elfeed-db.el index 637f6181..5fcdca3b 100644 --- a/elpa/elfeed-20150101.1457/elfeed-db.el +++ b/elpa/elfeed-20150608.1302/elfeed-db.el @@ -91,7 +91,8 @@ the :last-update time is updated.") "Merge B into A, preserving A's tags. Return true if an actual update occurred, not counting content." (setf (elfeed-entry-tags b) (elfeed-entry-tags a) - (elfeed-entry-content a) (elfeed-entry-content b)) + (elfeed-entry-content a) (elfeed-entry-content b) + (elfeed-entry-meta b) (elfeed-entry-meta a)) (not (zerop (cl-loop for i from 0 below (length a) @@ -207,6 +208,18 @@ Use `elfeed-db-return' to exit early and optionally return data. ,@body)) elfeed-db-index)))) +(defun elfeed-feed-entries (feed-or-id) + "Return a list of all entries for a particular feed. +The FEED-OR-ID may be a feed struct or a feed ID (url)." + (let ((feed-id (if (elfeed-feed-p feed-or-id) + (elfeed-feed-id feed-or-id) + feed-or-id))) + (let ((entries)) + (with-elfeed-db-visit (entry feed) + (when (equal (elfeed-feed-id feed) feed-id) + (push entry entries))) + (nreverse entries)))) + (defun elfeed-apply-hooks-now () "Apply `elfeed-new-entry-hook' to all entries in the database." (interactive) @@ -218,6 +231,16 @@ Use `elfeed-db-return' to exit early and optionally return data. "Use this to exit early and return VALUE from `with-elfeed-db-visit'." `(throw 'elfeed-db-done ,value)) +(defun elfeed-db-get-all-tags () + "Return a list of all tags currently in the database." + (let ((table (make-hash-table :test 'eq))) + (with-elfeed-db-visit (e _) + (dolist (tag (elfeed-entry-tags e)) + (setf (gethash tag table) tag))) + (let ((tags ())) + (maphash (lambda (k _) (push k tags)) table) + (cl-sort tags #'string< :key #'symbol-name)))) + ;; Saving and Loading: (defun elfeed-db-save () @@ -301,18 +324,26 @@ This function increases the size of the structs in the database." (elfeed-entry (setf (elfeed-entry-meta thing) plist)) (otherwise (error "Don't know how to access metadata on %S" thing)))) -(defun elfeed-meta (thing key) +(defun elfeed-db--plist-fixup (plist) + "Remove nil values from PLIST." + (cl-loop for (k v) on plist by #'cddr + when (not (null v)) + collect k and collect v)) + +(defun elfeed-meta (thing key &optional default) "Access metadata for THING (entry, feed) under KEY." - (plist-get (elfeed-meta--plist thing) key)) + (or (plist-get (elfeed-meta--plist thing) key) + default)) (defun elfeed-meta--put (thing key value) "Set metadata to VALUE on THING under KEY." (when (not (elfeed-readable-p value)) (error "New value must be readable.")) (let ((new-plist (plist-put (elfeed-meta--plist thing) key value))) (prog1 value - (elfeed-meta--set-plist thing new-plist)))) + (elfeed-meta--set-plist thing (elfeed-db--plist-fixup new-plist))))) -(gv-define-simple-setter elfeed-meta elfeed-meta--put) +(gv-define-setter elfeed-meta (value thing key &optional _default) + `(elfeed-meta--put ,thing ,key ,value)) ;; Filesystem storage: @@ -408,9 +439,20 @@ This function increases the size of the structs in the database." (ignore-errors (delete-file (elfeed-ref--file ref)))) +(defun elfeed-db-gc-empty-feeds () + "Remove feeds with no entries from the database." + (let ((seen (make-hash-table :test 'equal))) + (with-elfeed-db-visit (entry feed) + (setf (gethash (elfeed-feed-id feed) seen) feed)) + (maphash (lambda (id _) + (unless (gethash id seen) + (remhash id elfeed-db-feeds))) + elfeed-db-feeds))) + (defun elfeed-db-gc (&optional stats-p) "Clean up unused content from the content database. If STATS is true, return the space cleared in bytes." + (elfeed-db-gc-empty-feeds) (let* ((data (expand-file-name "data" elfeed-db-directory)) (dirs (directory-files data t "^[0-9a-z]\\{2\\}$")) (ids (cl-mapcan (lambda (d) (directory-files d nil nil t)) dirs)) diff --git a/elpa/elfeed-20150101.1457/elfeed-lib.el b/elpa/elfeed-20150608.1302/elfeed-lib.el similarity index 73% rename from elpa/elfeed-20150101.1457/elfeed-lib.el rename to elpa/elfeed-20150608.1302/elfeed-lib.el index 8615e1f8..848f3ae5 100644 --- a/elpa/elfeed-20150101.1457/elfeed-lib.el +++ b/elpa/elfeed-20150608.1302/elfeed-lib.el @@ -39,7 +39,9 @@ `timer-duration' but allows a bit more flair." (if (numberp time) time - (timer-duration (replace-regexp-in-string "\\(ago\\|old\\|-\\)" "" time)))) + (when (string-match-p "[[:alpha:]]" time) + (let ((clean (replace-regexp-in-string "\\(ago\\|old\\|-\\)" " " time))) + (timer-duration clean))))) (defun elfeed-looks-like-url-p (string) "Return true if STRING looks like it could be a URL." @@ -70,19 +72,35 @@ Align should be a keyword :left or :right." (let ((trim (replace-regexp-in-string "[\n\t]+" " " (or name "")))) (replace-regexp-in-string "^ +\\| +$" "" trim))) +(defun elfeed-parse-simple-iso-8601 (string) + "Attempt to parse STRING as a simply formatted ISO 8601 date. +Examples: 2015-02-22, 2015-02, 20150222" + (save-match-data + (let ((re "^\\([0-9]\\{4\\}\\)-?\\([0-9]\\{2\\}\\)-?\\([0-9]\\{2\\}\\)?$") + (clean (elfeed-cleanup string))) + (when (string-match re clean) + (let ((year (string-to-number (match-string 1 clean))) + (month (string-to-number (match-string 2 clean))) + (day (string-to-number (or (match-string 3 clean) "1")))) + (when (and (>= year 1900) (< year 2200)) + (float-time (encode-time 0 0 0 day month year t)))))))) + (defun elfeed-float-time (&optional date) "Like `float-time' but accept anything reasonable for DATE, defaulting to the current time if DATE could not be parsed. Date is allowed to be relative to now (`elfeed-time-duration')." (cl-typecase date (string - (let ((duration (elfeed-time-duration date))) - (if duration - (- (float-time) duration) - (let ((time (ignore-errors (date-to-time date)))) - (if (equal time '(14445 17280)) ; date-to-time silently failed - (float-time) - (float-time time)))))) + (let ((iso-8601 (elfeed-parse-simple-iso-8601 date))) + (if iso-8601 + iso-8601 + (let ((duration (elfeed-time-duration date))) + (if duration + (- (float-time) duration) + (let ((time (ignore-errors (date-to-time date)))) + (if (equal time '(14445 17280)) ; date-to-time silently failed + (float-time) + (float-time time)))))))) (integer date) (list (float-time date)) (otherwise (float-time)))) @@ -205,6 +223,36 @@ If no such line exists, point is left in place." (unless (search-forward-regexp "^$" nil t) (setf (point) start)))) +(defun elfeed--shuffle (seq) + "Destructively shuffle SEQ." + (let ((n (length seq))) + (prog1 seq ; don't use dotimes result (bug#16206) + (dotimes (i n) + (cl-rotatef (elt seq i) (elt seq (+ i (cl-random (- n i))))))))) + +(defun elfeed-split-ranges-to-numbers (str n) + "Convert STR containing enclosure numbers into a list of numbers. +STR is a string; N is the highest possible number in the list. +This includes expanding e.g. 3-5 into 3,4,5. If the letter +\"a\" ('all')) is given, that is expanded to a list with numbers [1..n]." + (let ((str-split (split-string str)) + beg end list) + (dolist (elem str-split list) + ;; special number "a" converts into all enclosures 1-N. + (when (equal elem "a") + (setf elem (concat "1-" (int-to-string n)))) + (if (string-match "\\([0-9]+\\)-\\([0-9]+\\)" elem) + ;; we have found a range A-B, which needs converting + ;; into the numbers A, A+1, A+2, ... B. + (progn + (setf beg (string-to-number (match-string 1 elem)) + end (string-to-number (match-string 2 elem))) + (while (<= beg end) + (setf list (nconc list (list beg)) + beg (1+ beg)))) + ;; else just a number + (push (string-to-number elem) list))))) + (provide 'elfeed-lib) ;;; elfeed-lib.el ends here diff --git a/elpa/elfeed-20150608.1302/elfeed-pkg.el b/elpa/elfeed-20150608.1302/elfeed-pkg.el new file mode 100644 index 00000000..738732a5 --- /dev/null +++ b/elpa/elfeed-20150608.1302/elfeed-pkg.el @@ -0,0 +1,5 @@ +(define-package "elfeed" "20150608.1302" "an Emacs Atom/RSS feed reader" + '((emacs "24.3"))) +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/elpa/elfeed-20150101.1457/elfeed-search.el b/elpa/elfeed-20150608.1302/elfeed-search.el similarity index 77% rename from elpa/elfeed-20150101.1457/elfeed-search.el rename to elpa/elfeed-20150608.1302/elfeed-search.el index 7d741d99..52eea4ab 100644 --- a/elpa/elfeed-20150101.1457/elfeed-search.el +++ b/elpa/elfeed-20150608.1302/elfeed-search.el @@ -24,9 +24,6 @@ (defvar elfeed-search-last-update 0 "The last time the buffer was redrawn in epoch seconds.") -(defvar elfeed-search-refresh-timer nil - "The timer used to keep things updated as the database updates.") - (defcustom elfeed-search-filter "@6-months-ago +unread" "Query string filtering shown entries." :group 'elfeed @@ -37,34 +34,30 @@ :group 'elfeed :type '(choice (const descending) (const ascending))) -(defcustom elfeed-search-refresh-rate 3 - "How often the buffer should update against the datebase in seconds." - :group 'elfeed - :type 'number) - (defcustom elfeed-search-clipboard-type 'PRIMARY "Selects the clipboard `elfeed-search-yank' should use. Choices are the symbols PRIMARY, SECONDARY, or CLIPBOARD." :group 'elfeed :type '(choice (const PRIMARY) (const SECONDARY) (const CLIPBOARD))) -(defvar elfeed-search-live nil - "When true, Elfeed is currently reading a filter from the minibuffer.") - -(defvar elfeed-search-cache (make-hash-table :test 'equal) - "Cache the generated entry buffer lines and such.") +(defvar elfeed-search-filter-active nil + "When non-nil, Elfeed is currently reading a filter from the minibuffer. +When live editing the filter, it is bound to :live.") (defvar elfeed-search--offset 2 "Offset between line numbers and entry list position.") (defalias 'elfeed-search-tag-all-unread - (elfeed-expose #'elfeed-search-tag-all 'unread)) + (elfeed-expose #'elfeed-search-tag-all 'unread) + "Add the `unread' tag to all selected entries.") (defalias 'elfeed-search-untag-all-unread - (elfeed-expose #'elfeed-search-untag-all 'unread)) + (elfeed-expose #'elfeed-search-untag-all 'unread) + "Remove the `unread' tag from all selected entries.") (defalias 'elfeed-search-update--force - (elfeed-expose #'elfeed-search-update :force)) + (elfeed-expose #'elfeed-search-update :force) + "Force refresh view of the feed listing.") (defvar elfeed-search-mode-map (let ((map (make-sparse-keymap))) @@ -100,16 +93,8 @@ Choices are the symbols PRIMARY, SECONDARY, or CLIPBOARD." (hl-line-mode) (make-local-variable 'elfeed-search-entries) (make-local-variable 'elfeed-search-filter) - (when (null elfeed-search-refresh-timer) - (setf elfeed-search-refresh-timer - (run-at-time elfeed-search-refresh-rate elfeed-search-refresh-rate - #'elfeed-search-update))) + (add-hook 'elfeed-update-hooks #'elfeed-search-update) (add-hook 'kill-buffer-hook #'elfeed-db-save t t) - (add-hook 'kill-buffer-hook - (lambda () - (ignore-errors (cancel-timer elfeed-search-refresh-timer)) - (setf elfeed-search-refresh-timer nil)) - t t) (elfeed-search-update :force) (run-hooks 'elfeed-search-mode-hook)) @@ -133,6 +118,11 @@ Choices are the symbols PRIMARY, SECONDARY, or CLIPBOARD." "Face used in search mode for titles." :group 'elfeed) +(defface elfeed-search-unread-title-face + '((t :inherit elfeed-search-title-face :weight bold)) + "Face used in search mode for unread entry titles." + :group 'elfeed) + (defface elfeed-search-feed-face '((((class color) (background light)) (:foreground "#aa0")) (((class color) (background dark)) (:foreground "#ff0"))) @@ -146,30 +136,31 @@ Choices are the symbols PRIMARY, SECONDARY, or CLIPBOARD." :group 'elfeed) (defcustom elfeed-search-title-max-width 70 - "Maximum column width for titles in the elfeed-search buffer. -Clear `elfeed-search-cache' (or restart Emacs) after setting." + "Maximum column width for titles in the elfeed-search buffer." :group 'elfeed :type 'integer) (defcustom elfeed-search-title-min-width 16 - "Minimum column width for titles in the elfeed-search buffer. -Clear `elfeed-search-cache' (or restart Emacs) after setting." + "Minimum column width for titles in the elfeed-search buffer." :group 'elfeed :type 'integer) (defcustom elfeed-search-trailing-width 30 - "Space reserved for displaying the feed and tag information. -Clear `elfeed-search-cache' (or restart Emacs) after setting." + "Space reserved for displaying the feed and tag information." :group 'elfeed :type 'integer) (defun elfeed-search-print (entry) "Print ENTRY to the buffer." (let* ((date (elfeed-search-format-date (elfeed-entry-date entry))) - (title (or (elfeed-entry-title entry) "")) - (title-faces '(elfeed-search-title-face)) + (title (or (elfeed-meta entry :title) (elfeed-entry-title entry) "")) + (title-faces (if (elfeed-tagged-p 'unread entry) + '(elfeed-search-unread-title-face) + '(elfeed-search-title-face))) (feed (elfeed-entry-feed entry)) - (feed-title (if feed (elfeed-feed-title feed))) + (feed-title + (when feed + (or (elfeed-meta feed :title) (elfeed-feed-title feed)))) (tags (mapcar #'symbol-name (elfeed-entry-tags entry))) (tags-str (mapconcat (lambda (s) (propertize s 'face 'elfeed-search-tag-face)) @@ -181,8 +172,6 @@ Clear `elfeed-search-cache' (or restart Emacs) after setting." title-width elfeed-search-title-max-width) :left))) - (when (elfeed-tagged-p 'unread entry) - (push 'bold title-faces)) (insert (propertize date 'face 'elfeed-search-date-face) " ") (insert (propertize title-column 'face title-faces) " ") (when feed-title @@ -200,8 +189,14 @@ Clear `elfeed-search-cache' (or restart Emacs) after setting." (cl-loop for element in (split-string filter) for type = (aref element 0) do (cl-case type - (?+ (push (intern (substring element 1)) must-have)) - (?- (push (intern (substring element 1)) must-not-have)) + (?+ + (let ((symbol (intern (substring element 1)))) + (unless (eq '## symbol) + (push symbol must-have)))) + (?- + (let ((symbol (intern (substring element 1)))) + (unless (eq '## symbol) + (push symbol must-not-have)))) (?@ (setf after (elfeed-time-duration (substring element 1)))) (?! (let ((re (substring element 1))) (when (elfeed-valid-regexp-p re) @@ -217,9 +212,10 @@ Clear `elfeed-search-cache' (or restart Emacs) after setting." (let* ((tags (elfeed-entry-tags entry)) (date (elfeed-entry-date entry)) (age (- (float-time) date)) - (title (elfeed-entry-title entry)) + (title (or (elfeed-meta entry :title) (elfeed-entry-title entry))) (link (elfeed-entry-link entry)) - (feed-title (or (elfeed-feed-title feed) ""))) + (feed-title + (or (elfeed-meta feed :title) (elfeed-feed-title feed) ""))) (when (and after (> age after)) (elfeed-db-return)) (and (cl-every (lambda (tag) (member tag tags)) must-have) @@ -250,6 +246,8 @@ Clear `elfeed-search-cache' (or restart Emacs) after setting." (defun elfeed-search-set-filter (new-filter) "Set a new search filter for the elfeed-search buffer. +When NEW-FILTER is nil, reset the filter to the default value. + When given a prefix argument, the current filter is not displayed in the minibuffer when prompting for a new filter. @@ -262,10 +260,13 @@ than this are allowed. Ex. \"@3-days-ago\" or \"@1-year-old\". Every other space-seperated element is treated like a regular expression, matching against entry link, title, and feed title." - (interactive (list (elfeed-search--prompt - (if current-prefix-arg "" elfeed-search-filter)))) + (interactive + (let ((elfeed-search-filter-active :non-interactive)) + (list (elfeed-search--prompt + (if current-prefix-arg "" elfeed-search-filter))))) (with-current-buffer (elfeed-search-buffer) - (setf elfeed-search-filter new-filter) + (setf elfeed-search-filter + (or new-filter (default-value 'elfeed-search-filter))) (elfeed-search-update :force))) (defun elfeed-search-insert-header-text (text) @@ -288,10 +289,12 @@ expression, matching against entry link, title, and feed title." (defun elfeed-search-insert-header () "Insert a one-line status header." - (if (or elfeed-waiting elfeed-connections) - (elfeed-search-insert-header-text - (format "%d feeds pending, %d in process ..." - (length elfeed-waiting) (length elfeed-connections))) + (if url-queue + (let ((total (length url-queue)) + (in-process (cl-count-if #'url-queue-buffer url-queue))) + (elfeed-search-insert-header-text + (format "%d feeds pending, %d in process ..." + (- total in-process) in-process))) (let ((time (seconds-to-time (elfeed-db-last-update)))) (if (zerop (float-time time)) (elfeed-search-insert-intro-header) @@ -328,10 +331,11 @@ expression, matching against entry link, title, and feed title." (move-to-column column))))) (defun elfeed-search-update (&optional force) - "Update the display to match the database." + "Update the elfeed-search buffer listing to match the database. +When FORCE is non-nil, redraw even when the database hasn't changed." (interactive) (with-current-buffer (elfeed-search-buffer) - (if (or force (and (not elfeed-search-live) + (if (or force (and (not elfeed-search-filter-active) (< elfeed-search-last-update (elfeed-db-last-update)))) (elfeed-save-excursion (let ((inhibit-read-only t) @@ -340,19 +344,9 @@ expression, matching against entry link, title, and feed title." (elfeed-search-insert-header) (insert "\n") (elfeed-search--update-list) - (cl-loop for entry in elfeed-search-entries - when (gethash (list (window-width) entry) - elfeed-search-cache) - do (insert it) - else - do (insert - (with-temp-buffer - (elfeed-search-print entry) - (setf (gethash (list (window-width) - (copy-sequence entry)) - elfeed-search-cache) - (buffer-string)))) - do (insert "\n")) + (dolist (entry elfeed-search-entries) + (elfeed-search-print entry) + (insert "\n")) (insert "End of entries.\n") (setf elfeed-search-last-update (float-time)))) (let ((inhibit-read-only t)) @@ -388,24 +382,29 @@ expression, matching against entry link, title, and feed title." collect it into selected finally (return (if ignore-region (car selected) selected)))))) -(defun elfeed-search-browse-url () - "Visit the current entry in your browser using `browse-url'." - (interactive) +(defun elfeed-search-browse-url (&optional use-generic-p) + "Visit the current entry in your browser using `browse-url'. +If there is a prefix argument, visit the current entry in the +browser defined by `browse-url-generic-program'." + (interactive "P") (let ((entries (elfeed-search-selected))) (cl-loop for entry in entries do (elfeed-untag entry 'unread) when (elfeed-entry-link entry) - do (browse-url it)) + do (if use-generic-p + (browse-url-generic it) + (browse-url it))) (mapc #'elfeed-search-update-entry entries) (unless (use-region-p) (forward-line)))) (defun elfeed-search-yank () - "Copy the selected feed item to " + "Copy the selected feed item to clipboard and kill-ring." (interactive) (let* ((entry (elfeed-search-selected :ignore-region)) (link (and entry (elfeed-entry-link entry)))) (when entry (elfeed-untag entry 'unread) + (kill-new link) (x-set-selection elfeed-search-clipboard-type link) (message "Copied: %s" link) (elfeed-search-update-line) @@ -427,6 +426,17 @@ expression, matching against entry link, title, and feed title." (mapc #'elfeed-search-update-entry entries) (unless (use-region-p) (forward-line)))) +(defun elfeed-search-toggle-all (tag) + "Toggle TAG on all selected entries." + (interactive (list (intern (read-from-minibuffer "Tag: ")))) + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + when (elfeed-tagged-p tag entry) + do (elfeed-untag entry tag) + else do (elfeed-tag entry tag)) + (mapc #'elfeed-search-update-entry entries) + (unless (use-region-p) (forward-line)))) + (defun elfeed-search-show-entry (entry) "Display the currently selected item in a buffer." (interactive (list (elfeed-search-selected :ignore-region))) @@ -438,16 +448,26 @@ expression, matching against entry link, title, and feed title." ;; Live Filters +(defvar elfeed-search-filter-syntax-table + (let ((table (make-syntax-table))) + (prog1 table + (modify-syntax-entry ?+ "w" table) + (modify-syntax-entry ?- "w" table) + (modify-syntax-entry ?@ "w" table))) + "Syntax table active when editing the filter in the minibuffer.") + (defun elfeed-search--minibuffer-setup () "Set up the minibuffer for live filtering." - (when elfeed-search-live - (add-hook 'post-command-hook 'elfeed-search--live-update nil :local))) + (when elfeed-search-filter-active + (set-syntax-table elfeed-search-filter-syntax-table) + (when (eq :live elfeed-search-filter-active) + (add-hook 'post-command-hook 'elfeed-search--live-update nil :local)))) (add-hook 'minibuffer-setup-hook 'elfeed-search--minibuffer-setup) (defun elfeed-search--live-update () "Update the elfeed-search buffer based on the contents of the minibuffer." - (when elfeed-search-live + (when (eq :live elfeed-search-filter-active) (let ((buffer (elfeed-search-buffer)) (current-filter (minibuffer-contents-no-properties))) (when buffer @@ -459,7 +479,7 @@ expression, matching against entry link, title, and feed title." "Filter the elfeed-search buffer as the filter is written." (interactive) (unwind-protect - (let ((elfeed-search-live t)) + (let ((elfeed-search-filter-active :live)) (setq elfeed-search-filter (read-from-minibuffer "Filter: " elfeed-search-filter))) (elfeed-search-update :force))) diff --git a/elpa/elfeed-20150101.1457/elfeed-show.el b/elpa/elfeed-20150608.1302/elfeed-show.el similarity index 56% rename from elpa/elfeed-20150101.1457/elfeed-show.el rename to elpa/elfeed-20150608.1302/elfeed-show.el index e431d27a..3cf704bd 100644 --- a/elpa/elfeed-20150101.1457/elfeed-show.el +++ b/elpa/elfeed-20150608.1302/elfeed-show.el @@ -25,6 +25,7 @@ (let ((map (make-sparse-keymap))) (prog1 map (suppress-keymap map) + (define-key map "d" 'elfeed-show-save-enclosure) (define-key map "q" 'elfeed-kill-buffer) (define-key map "g" 'elfeed-show-refresh) (define-key map "n" 'elfeed-show-next) @@ -170,6 +171,7 @@ (interactive) (let ((link (elfeed-entry-link elfeed-show-entry))) (when link + (kill-new link) (x-set-selection 'PRIMARY link) (message "Yanked: %s" link)))) @@ -194,6 +196,139 @@ (elfeed-search-update-entry entry)) (elfeed-show-refresh))) +;; Enclosures: + +(defcustom elfeed-enclosure-default-dir (expand-file-name "~") + "Default directory for saving enclosures. +This can be either a string (a file system path), or a function +that takes a filename and the mime-type as arguments, and returns +the enclosure dir." + :type 'directory + :group 'elfeed + :safe 'stringp) + +(defcustom elfeed-save-multiple-enclosures-without-asking nil + "If non-nil, saving multiple enclosures asks once for a +directory and saves all attachments in the chosen directory." + :type 'boolean + :group 'elfeed) + +(defun elfeed--download-enclosure (url path) + "Download asynchronously the enclosure from URL to PATH." + (if (fboundp 'async-start) + ;; If async is available, don't hang emacs ! + (async-start + (lambda () + (url-copy-file url path t)) + (lambda (_) + (message (format "%s downloaded" url)))) + (url-copy-file url path t))) + +(defun elfeed--get-enclosure-num (prompt entry &optional multi) + "Ask the user with PROMPT for an enclosure number for ENTRY. +The number is [1..n] for enclosures \[0..(n-1)] in the entry. If +MULTI is nil, return the number for the enclosure; +otherwise (MULTI is non-nil), accept ranges of enclosure numbers, +as per `elfeed-split-ranges-to-numbers', and return the +corresponding string." + (let* ((count (length (elfeed-entry-enclosures entry))) + def) + (when (zerop count) + (error "No enclosures to this entry")) + (if (not multi) + (if (= count 1) + (read-number (format "%s: " prompt) 1) + (read-number (format "%s (1-%d): " prompt count))) + (progn + (setq def (if (= count 1) "1" (format "1-%d" count))) + (read-string (format "%s (default %s): " prompt def) + nil nil def))))) + +(defun elfeed--request-enclosure-path (fname path) + "Ask the user where to save FNAME (default is PATH/FNAME)." + (let ((fpath (expand-file-name + (read-file-name "Save as: " path nil nil fname) path))) + (if (file-directory-p fpath) + (expand-file-name fname fpath) + fpath))) + +(defun elfeed--request-enclosures-dir (path) + "Ask the user where to save multiple enclosures (default is PATH)." + (let ((fpath (expand-file-name + (read-directory-name + (format "Save in directory: ") path nil nil nil) path))) + (if (file-directory-p fpath) + fpath))) + +(defun elfeed-show-save-enclosure-single (&optional entry enclosure-index) + "Save enclosure number ENCLOSURE-INDEX from ENTRY. +If ENTRY is nil use the elfeed-show-entry variable. +If ENCLOSURE-INDEX is nil ask for the enclosure number." + (interactive) + (let* ((path elfeed-enclosure-default-dir) + (entry (or entry elfeed-show-entry)) + (enclosure-index (or enclosure-index + (elfeed--get-enclosure-num + "Enclosure to save" entry))) + (url-enclosure (car (elt (elfeed-entry-enclosures entry) + (- enclosure-index 1)))) + (fname (file-name-nondirectory + (url-unhex-string + (car (url-path-and-query (url-generic-parse-url + url-enclosure)))))) + (retry t) + (fpath)) + (while retry + (setf fpath (elfeed--request-enclosure-path fname path) + retry (and (file-exists-p fpath) + (not (y-or-n-p (format "Overwrite '%s'?" fpath)))))) + (elfeed--download-enclosure url-enclosure fpath))) + +(defun elfeed-show-save-enclosure-multi (&optional entry) + "Offer to save multiple entry enclosures from the current entry. +Default is to save all enclosures, [1..n], where n is the number of +enclosures. You can type multiple values separated by space, e.g. + 1 3-6 8 +will save enclosures 1,3,4,5,6 and 8. + +Furthermore, there is a shortcut \"a\" which so means all +enclosures, but as this is the default, you may not need it." + (interactive) + (let* ((entry (or entry elfeed-show-entry)) + (attachstr (elfeed--get-enclosure-num + "Enclosure number range (or 'a' for 'all')" entry t)) + (count (length (elfeed-entry-enclosures entry))) + (attachnums (elfeed-split-ranges-to-numbers attachstr count)) + (path elfeed-enclosure-default-dir) + (fpath)) + (if elfeed-save-multiple-enclosures-without-asking + (let ((attachdir (elfeed--request-enclosures-dir path))) + (dolist (enclosure-index attachnums) + (let* ((url-enclosure + (aref (elfeed-entry-enclosures entry) enclosure-index)) + (fname (file-name-nondirectory + (url-unhex-string + (car (url-path-and-query + (url-generic-parse-url url-enclosure)))))) + (retry t)) + (while retry + (setf fpath (expand-file-name (concat attachdir fname) path) + retry + (and (file-exists-p fpath) + (not (y-or-n-p (format "Overwrite '%s'?" fpath)))))) + (elfeed--download-enclosure url-enclosure fpath)))) + (dolist (enclosure-index attachnums) + (elfeed-show-save-enclosure-single entry enclosure-index))))) + +(defun elfeed-show-save-enclosure (&optional multi) + "Offer to save enclosure(s). +If MULTI (prefix-argument) is nil, save a single one, otherwise, +offer to save a range of enclosures." + (interactive "P") + (if multi + (elfeed-show-save-enclosure-multi) + (elfeed-show-save-enclosure-single))) + (provide 'elfeed-show) ;;; elfeed-show.el ends here diff --git a/elpa/elfeed-20150101.1457/elfeed.el b/elpa/elfeed-20150608.1302/elfeed.el similarity index 83% rename from elpa/elfeed-20150101.1457/elfeed.el rename to elpa/elfeed-20150608.1302/elfeed.el index 71b6eed0..eb2649ff 100644 --- a/elpa/elfeed-20150101.1457/elfeed.el +++ b/elpa/elfeed-20150608.1302/elfeed.el @@ -56,11 +56,14 @@ (require 'xml) (require 'xml-query) (require 'url-parse) +(require 'url-queue) (defgroup elfeed nil "An Emacs web feed reader." :group 'comm) +(defconst elfeed-version "1.1.2") + (defcustom elfeed-feeds () "List of all feeds that Elfeed should follow. You must add your feeds to this list. @@ -77,7 +80,8 @@ entries as tags (\"autotags\"). For example, All entries from the \"baz\" feed will be tagged as \"comic\" when they are first discovered." :group 'elfeed - :type 'list) + :type '(repeat (choice string + (cons string (repeat symbol))))) (provide 'elfeed) @@ -88,71 +92,51 @@ when they are first discovered." (defcustom elfeed-initial-tags '(unread) "Initial tags for new entries." :group 'elfeed - :type 'list) + :type '(repeat symbol)) ;; Fetching: -(defcustom elfeed-max-connections - ;; Windows Emacs cannot open many sockets at once. - (if (or (not (and (fboundp 'gnutls-available-p) (gnutls-available-p))) - (eq system-type 'windows-nt)) - 1 - 4) - "The maximum number of feeds to be fetched in parallel." - :group 'elfeed - :type 'integer) - -(defvar elfeed-connections nil - "List of callbacks awaiting responses.") - -(defvar elfeed-waiting nil - "List of requests awaiting connections.") - -(defvar elfeed--connection-counter 0 - "Provides unique connection identifiers.") - -(defun elfeed--check-queue () - "Start waiting connections if connection slots are available." - (while (and elfeed-waiting - (< (length elfeed-connections) elfeed-max-connections)) - (let ((request (pop elfeed-waiting))) - (cl-destructuring-bind (_ url cb) request - (push request elfeed-connections) - (condition-case error - (url-retrieve url cb nil :silent) - (error (with-temp-buffer (funcall cb (list :error error))))))))) - -(defun elfeed--wrap-callback (id cb) - "Return a function that manages the elfeed queue." - (lambda (status) - (unwind-protect - (funcall cb status) - (setf elfeed-connections (cl-delete id elfeed-connections :key #'car)) - (elfeed--check-queue)))) - -(defun elfeed-fetch (url callback) - "Basically wraps `url-retrieve' but uses the connection limiter." - (let* ((id (cl-incf elfeed--connection-counter)) - (cb (elfeed--wrap-callback id callback))) - (push (list id url cb) elfeed-waiting) - (elfeed--check-queue))) - -(defmacro with-elfeed-fetch (url &rest body) +(define-obsolete-variable-alias + 'elfeed-max-connections 'url-queue-parallel-processes nil) + +(defvar elfeed-http-error-hooks () + "Hooks to run when an http connection error occurs. +It is called with 2 arguments. The first argument is the url of +the failing feed. The second argument is the http status code.") + +(defvar elfeed-parse-error-hooks () + "Hooks to run when an error occurs during the parsing of a feed. +It is called with 2 arguments. The first argument is the url of +the failing feed. The second argument is the error message .") + +(defvar elfeed-update-hooks () + "Hooks to run any time a feed update has completed a request. +It is called with 1 argument: the URL of the feed that was just +updated. The hook is called even when no new entries were +found.") + +(define-obsolete-variable-alias + 'elfeed-connections 'url-queue nil) + +(define-obsolete-variable-alias + 'elfeed-waiting 'url-queue nil) + +(defmacro elfeed-with-fetch (url &rest body) "Asynchronously run BODY in a buffer with the contents from URL. This macro is anaphoric, with STATUS referring to the status from `url-retrieve'." (declare (indent defun)) - `(elfeed-fetch ,url (lambda (status) ,@body (kill-buffer)))) + `(url-queue-retrieve ,url (lambda (status) ,@body) () t t)) (defun elfeed-unjam () "Manually clear the connection pool when connections fail to timeout. -This is a short-term workaround for connection handling issues." +This is a workaround for issues in `url-queue-retrieve'." (interactive) - (let ((fails (mapcar #'cl-second elfeed-connections))) + (let ((fails (mapcar #'url-queue-url elfeed-connections))) (when fails (message "Elfeed aborted feeds: %s" (mapconcat #'identity fails " "))) - (setf elfeed-connections nil) - (elfeed--check-queue))) + (setf url-queue nil)) + (elfeed-search-update :force)) ;; Parsing: @@ -184,8 +168,12 @@ NIL for unknown." (date (or (xml-query '(published *) entry) (xml-query '(updated *) entry) (xml-query '(date *) entry))) - (content (or (xml-query '(content *) entry) - (xml-query '(summary *) entry))) + (content + (let ((all-content + (or (xml-query-all '(content *) entry) + (xml-query-all '(summary *) entry)))) + (when all-content + (apply #'concat all-content)))) (id (or (xml-query '(id *) entry) link (elfeed-generate-id content))) (type (or (xml-query '(content :type) entry) @@ -224,7 +212,9 @@ NIL for unknown." (link (or (xml-query '(link *) item) guid)) (date (or (xml-query '(pubDate *) item) (xml-query '(date *) item))) - (description (xml-query '(description *) item)) + (content (or (xml-query-all '(encoded *) item) + (xml-query-all '(description *) item))) + (description (apply #'concat content)) (id (or guid link (elfeed-generate-id description))) (full-id (cons feed-id (elfeed-cleanup id))) (original (elfeed-db-get-entry full-id)) @@ -262,7 +252,8 @@ NIL for unknown." (link (xml-query '(link *) item)) (date (or (xml-query '(pubDate *) item) (xml-query '(date *) item))) - (description (xml-query '(description *) item)) + (description + (apply #'concat (xml-query-all '(description *) item))) (id (or link (elfeed-generate-id description))) (full-id (cons feed-id (elfeed-cleanup id))) (original (elfeed-db-get-entry full-id))) @@ -293,13 +284,25 @@ Only a list of strings will be returned." url-or-feed))) (mapcar #'elfeed-keyword->symbol (cdr (assoc url elfeed-feeds))))) +(defun elfeed-handle-http-error (url status) + "Handle an http error during retrieval of URL with STATUS code." + (cl-incf (elfeed-meta (elfeed-db-get-feed url) :failures 0)) + (run-hook-with-args 'elfeed-http-error-hooks url status) + (message "Elfeed fetch failed for %s: %S" url status)) + +(defun elfeed-handle-parse-error (url error) + "Handle parse error during parsing of URL with ERROR message." + (cl-incf (elfeed-meta (elfeed-db-get-feed url) :failures 0)) + (run-hook-with-args 'elfeed-parse-error-hooks url error) + (message "Elfeed parse failed for %s: %s" url error)) + (defun elfeed-update-feed (url) "Update a specific feed." (interactive (list (completing-read "Feed: " (elfeed-feed-list)))) - (with-elfeed-fetch url + (elfeed-with-fetch url (if (and status (eq (car status) :error)) (let ((print-escape-newlines t)) - (message "Elfeed update failed for %s: %S" url status)) + (elfeed-handle-http-error url status)) (condition-case error (progn (elfeed-move-to-first-empty-line) @@ -309,9 +312,13 @@ Only a list of strings will be returned." (:atom (elfeed-entries-from-atom url xml)) (:rss (elfeed-entries-from-rss url xml)) (:rss1.0 (elfeed-entries-from-rss1.0 url xml)) - (otherwise (error "Unknown feed type."))))) + (otherwise + (error (elfeed-handle-parse-error + url "Unknown feed type.")))))) (elfeed-db-add entries))) - (error (message "Elfeed update failed for %s: %s" url error)))))) + (error (elfeed-handle-parse-error url error)))) + (kill-buffer) + (run-hook-with-args 'elfeed-update-hooks url))) (defun elfeed-add-feed (url) "Manually add a feed to the database." @@ -323,14 +330,16 @@ Only a list of strings will be returned." (cl-pushnew url elfeed-feeds) (when (called-interactively-p 'any) (customize-save-variable 'elfeed-feeds elfeed-feeds)) - (elfeed-update-feed url)) + (elfeed-update-feed url) + (elfeed-search-update :force)) ;;;###autoload (defun elfeed-update () "Update all the feeds in `elfeed-feeds'." (interactive) (message "Elfeed update: %s" (format-time-string "%B %e %Y %H:%M:%S %Z")) - (mapc #'elfeed-update-feed (elfeed-feed-list)) + (mapc #'elfeed-update-feed (elfeed--shuffle (elfeed-feed-list))) + (elfeed-search-update :force) (elfeed-db-save)) ;;;###autoload diff --git a/elpa/elfeed-20150101.1457/xml-query.el b/elpa/elfeed-20150608.1302/xml-query.el similarity index 96% rename from elpa/elfeed-20150101.1457/xml-query.el rename to elpa/elfeed-20150608.1302/xml-query.el index 990d5ff9..74adf490 100644 --- a/elpa/elfeed-20150101.1457/xml-query.el +++ b/elpa/elfeed-20150608.1302/xml-query.el @@ -88,9 +88,7 @@ Atom feed: (cond ((keywordp matcher) (xml-query--keyword matcher xml)) ((eq matcher '*) - (let ((strings (cl-remove-if-not #'stringp (xml-query--append xml)))) - (when strings - (mapconcat #'identity strings " ")))) + (cl-remove-if-not #'stringp (xml-query--append xml))) (:else (let ((matches (cl-etypecase matcher diff --git a/elpa/elfeed-org-20150108.1406/elfeed-org-pkg.el b/elpa/elfeed-org-20150108.1406/elfeed-org-pkg.el deleted file mode 100644 index 73a5703a..00000000 --- a/elpa/elfeed-org-20150108.1406/elfeed-org-pkg.el +++ /dev/null @@ -1 +0,0 @@ -(define-package "elfeed-org" "20150108.1406" "Configure elfeed with one or more org-mode files" (quote ((elfeed "1.1.1") (org "7")))) diff --git a/elpa/elfeed-org-20150108.1406/elfeed-org.el b/elpa/elfeed-org-20150108.1406/elfeed-org.el deleted file mode 100644 index 02da7cde..00000000 --- a/elpa/elfeed-org-20150108.1406/elfeed-org.el +++ /dev/null @@ -1,156 +0,0 @@ -;;; elfeed-org.el --- Configure elfeed with one or more org-mode files - -;; Copyright (C) 2014 Remy Honig - -;; Author : Remy Honig -;; Package-Requires : ((elfeed "1.1.1") (org "7")) -;; URL : https://github.com/remyhonig/elfeed-org -;; Version: 20150108.1406 -;; X-Original-Version : 20141003.1 -;; Keywords : news - -;; 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 3 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, see . - -;;; Commentary: -;; Maintaining tags for all rss feeds is cumbersome using the regular -;; flat list where there is no hierarchy and tag names are duplicated -;; a lot. Org-mode makes the book keeping of tags and feeds much -;; easier. Tags get inherited from parent headlines. Multiple files -;; can be specified to separate your private from your work feeds for -;; example. You may also use tagging rules to tag feeds by entry-title -;; keywords. See https://github.com/remyhonig/elfeed-org for usage. - -;;; Code: - -(require 'elfeed) -(require 'cl) -(require 'org) - - -(defgroup elfeed-org nil - "Configure the Elfeed RSS reader with an Orgmode file" - :group 'comm) - - -(defcustom rmh-elfeed-org-tree-id "elfeed" - "The ID property of the tree containing the RSS feeds." - :group 'elfeed-org - :type 'string) - - -(defcustom rmh-elfeed-org-files (list "~/.emacs.d/elfeed.org") - "The files where we look to find the tree with the `rmh-elfeed-org-tree-id'." - :group 'elfeed-org - :type '(repeat (file :tag "org-mode file"))) - - -(defun rmh-elfeed-org-read-tree (tree-id match) - "Convert org tree with TREE-ID into a feed configuration structure for Elfeed. -Filter out headlines that contain MATCH." - (let* ((m (org-id-find tree-id 'marker)) - (buf (marker-buffer m))) - (save-excursion - (with-current-buffer buf - (goto-char m) - (move-marker m nil) - (remove-if-not - (lambda (x) - (and (string-match match (car x)) x)) - (rmh-elfeed-org-tags-inherited - (lambda () - (org-map-entries - '(let ((url (substring-no-properties (org-get-heading t))) - (tags (mapcar 'intern (org-get-tags-at)))) - (append (list url) tags)) - nil - rmh-elfeed-org-files)))))))) - - -(defun rmh-elfeed-org-tags-inherited (func) - "Call FUNC while ensuring tags are inherited." - (let ((original org-use-tag-inheritance)) - (setq org-use-tag-inheritance 't) - (let ((feeds (funcall func))) - (setq org-use-tag-inheritance original) feeds))) - - -(defun rmh-elfeed-org-add-new-entry-hooks (keywords) - "Add new entry hooks for tagging KEYWORDS." - (mapc - (lambda (x) - (let ((term (car (cdr (split-string (car x) ": ")))) - (tags (cdr x))) - (add-hook 'elfeed-new-entry-hook (elfeed-make-tagger :entry-title term :add (cdr x))))) - keywords)) - - -(defun rmh-elfeed-org-check-configuration-file (file) - "Make sure FILE exists. If not, ask user what to do." - (when (not (file-exists-p file)) - (error "Elfeed-org cannot open %s. Make sure it exists customize the variable \'rmh-elfeed-org-files\'" - (abbreviate-file-name file)))) - - -(defun rmh-elfeed-org-configure () - "Clear and reload the elfeed feeds- and tagging configuration." - - ;; To regenerate the elfeed config after the org mode tree has - ;; changed we want this function to be interactive - (interactive) - - ;; Make sure all files really exists - (mapc (lambda (file) (rmh-elfeed-org-check-configuration-file file)) rmh-elfeed-org-files) - - ;; Clear hooks for auto tagging - (setq elfeed-new-entry-hook (list)) - - ;; Clear registered feeds - (setq elfeed-feeds (list)) - - ;; Add feeds and auto tagging rules to elfeed configuration - (mapc (lambda (file) (with-current-buffer (find-file-noselect file) - - ;; Extract tagging rules and feeds from the found file - (let* ((tree-id rmh-elfeed-org-tree-id) - (tagging-rules (rmh-elfeed-org-read-tree tree-id "entry-title")) - (feeds (rmh-elfeed-org-read-tree tree-id "http"))) - - ;; Add feeds to elfeed configuration - (setq elfeed-feeds (append elfeed-feeds feeds)) - - ;; Add auto tagging rules to elfeed configuration - (rmh-elfeed-org-add-new-entry-hooks tagging-rules)))) - - ;; The files we are operating on - rmh-elfeed-org-files) - - ;; Tell user what we did - (message "elfeed-org loaded %i feeds, %i rules" - (length elfeed-feeds) - (length elfeed-new-entry-hook))) - - -;;;###autoload -(defun elfeed-org () - "Hook up rmh-elfeed-org to read the `org-mode' configuration when elfeed starts." - (interactive) - (message "elfeed-org is set up to handle elfeed configuration.") - ;; Use an advice to load the configuration. - (defadvice elfeed (before configure-elfeed activate) - "Load all feed settings before elfeed is started." - (rmh-elfeed-org-configure))) - - -(provide 'elfeed-org) -;;; elfeed-org.el ends here diff --git a/elpa/elfeed-org-20150108.1406/elfeed-org-autoloads.el b/elpa/elfeed-org-20150412.145/elfeed-org-autoloads.el similarity index 63% rename from elpa/elfeed-org-20150108.1406/elfeed-org-autoloads.el rename to elpa/elfeed-org-20150412.145/elfeed-org-autoloads.el index 40322503..680a723e 100644 --- a/elpa/elfeed-org-20150108.1406/elfeed-org-autoloads.el +++ b/elpa/elfeed-org-20150412.145/elfeed-org-autoloads.el @@ -1,29 +1,22 @@ ;;; elfeed-org-autoloads.el --- automatically extracted autoloads ;; ;;; Code: - +(add-to-list 'load-path (or (file-name-directory #$) (car load-path))) -;;;### (autoloads (elfeed-org) "elfeed-org" "elfeed-org.el" (21691 -;;;;;; 57075 0 0)) +;;;### (autoloads nil "elfeed-org" "elfeed-org.el" (21908 14284 0 +;;;;;; 0)) ;;; Generated autoloads from elfeed-org.el (autoload 'elfeed-org "elfeed-org" "\ -Hook up rmh-elfeed-org to read the `org-mode' configuration when elfeed starts. +Hook up rmh-elfeed-org to read the `org-mode' configuration when elfeed is run. \(fn)" t nil) ;;;*** -;;;### (autoloads nil nil ("elfeed-org-pkg.el") (21691 57075 182397 -;;;;;; 0)) - -;;;*** - -(provide 'elfeed-org-autoloads) ;; Local Variables: ;; version-control: never ;; no-byte-compile: t ;; no-update-autoloads: t -;; coding: utf-8 ;; End: ;;; elfeed-org-autoloads.el ends here diff --git a/elpa/elfeed-org-20150412.145/elfeed-org-pkg.el b/elpa/elfeed-org-20150412.145/elfeed-org-pkg.el new file mode 100644 index 00000000..ae046fa9 --- /dev/null +++ b/elpa/elfeed-org-20150412.145/elfeed-org-pkg.el @@ -0,0 +1 @@ +(define-package "elfeed-org" "20150412.145" "Configure elfeed with one or more org-mode files" '((elfeed "1.1.1") (org "8.2.7") (dash "2.10.0") (s "1.9.0")) :url "https://github.com/remyhonig/elfeed-org" :keywords '("news")) diff --git a/elpa/elfeed-org-20150412.145/elfeed-org.el b/elpa/elfeed-org-20150412.145/elfeed-org.el new file mode 100644 index 00000000..41746909 --- /dev/null +++ b/elpa/elfeed-org-20150412.145/elfeed-org.el @@ -0,0 +1,201 @@ +;;; elfeed-org.el --- Configure elfeed with one or more org-mode files + +;; Copyright (C) 2014 Remy Honig + +;; Author : Remy Honig +;; Package-Requires : ((elfeed "1.1.1") (org "8.2.7") (dash "2.10.0") (s "1.9.0")) +;; URL : https://github.com/remyhonig/elfeed-org +;; Package-Version: 20150412.145 +;; Version : 20150412.1 +;; Keywords : news + +;; 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 3 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, see . + +;;; Commentary: +;; Maintaining tags for all rss feeds is cumbersome using the regular +;; flat list where there is no hierarchy and tag names are duplicated +;; a lot. Org-mode makes the book keeping of tags and feeds much +;; easier. Tags get inherited from parent headlines. Multiple files +;; can be specified to separate your private from your work feeds for +;; example. You may also use tagging rules to tag feeds by entry-title +;; keywords. See https://github.com/remyhonig/elfeed-org for usage. + +;;; Code: + +(require 'elfeed) +(require 'org) +(require 'dash) +(require 's) + + +(defgroup elfeed-org nil + "Configure the Elfeed RSS reader with an Orgmode file" + :group 'comm) + + +(defcustom rmh-elfeed-org-tree-id "elfeed" + "The tag or ID property on the trees containing the RSS feeds." + :group 'elfeed-org + :type 'string) + + +(defcustom rmh-elfeed-org-files (list "~/.emacs.d/elfeed.org") + "The files where we look to find trees with the `rmh-elfeed-org-tree-id'." + :group 'elfeed-org + :type '(repeat (file :tag "org-mode file"))) + + +(defun rmh-elfeed-org-check-configuration-file (file) + "Make sure FILE exists." + (when (not (file-exists-p file)) + (error "Elfeed-org cannot open %s. Make sure it exists customize the variable \'rmh-elfeed-org-files\'" + (abbreviate-file-name file)))) + + +(defun rmh-elfeed-org-import-trees (tree-id) + "Get trees with \":ID:\" property or tag of value TREE-ID. +Return trees with TREE-ID as the value of the id property or +with a tag of the same value. Setting an \":ID:\" property is not +recommended but I support it for backward compatibility of +current users." + (org-element-map + (org-element-parse-buffer) + 'headline + (lambda (h) + (when (or (member tree-id (org-element-property :tags h)) + (equal tree-id (org-element-property :ID h))) h)))) + + +(defun rmh-elfeed-org-convert-tree-to-headlines (parsed-org) + "Get the inherited tags from PARSED-ORG structure if MATCH-FUNC is t. +The algorithm to gather inherited tags depends on the tree being +visited depth first by `org-element-map'. The reason I don't use +`org-get-tags-at' for this is that I can reuse the parsed org +structure and I am not dependent on the setting of +`org-use-tag-inheritance' or an org buffer being present at +all. Which in my opinion makes the process more traceable." + (let* ((tags '()) + (level 1)) + (org-element-map parsed-org 'headline + (lambda (h) + (let* ((current-level (org-element-property :level h)) + (delta-level (- current-level level)) + (delta-tags (mapcar 'intern (org-element-property :tags h))) + (heading (org-element-property :raw-value h))) + ;; update the tags stack when we visit a parent or sibling + (unless (> delta-level 0) + (let ((drop-num (+ 1 (- delta-level)))) + (setq tags (-drop drop-num tags)))) + ;; save current level to compare with next heading that will be visited + (setq level current-level) + ;; save the tags that might apply to potential children of the current heading + (push (-concat (-first-item tags) delta-tags) tags) + ;; return the heading and inherited tags + (-concat (list heading) + (-first-item tags))))))) + +;; TODO: mark wrongly formatted feeds (PoC for unretrievable feeds) +(defun rmh-elfeed-org-flag-headlines (parsed-org) + "Flag headlines in PARSED-ORG if they don't have a valid value." + (org-element-map parsed-org 'headline + (lambda (h) + (let ((tags (org-element-property :tags h))) + (org-element-put-property h :tags (push "_flag_" tags)))))) + + +(defun rmh-elfeed-org-filter-relevant (list) + "Filter relevant entries from the LIST." + (-filter + (lambda (entry) + (string-match "\\(http\\|entry-title\\)" (car entry))) + list)) + + +(defun rmh-elfeed-org-cleanup-headlines (headlines tree-id) + "In all HEADLINES given remove the TREE-ID." + (mapcar (lambda (e) (delete tree-id e)) headlines)) + + +(defun rmh-elfeed-org-import-headlines-from-files (files tree-id) + "Visit all FILES and return the headlines stored under tree tagged TREE-ID or with the \":ID:\" TREE-ID in one list, filter headlines on MATCH." + (-distinct (-mapcat (lambda (file) + (with-current-buffer (find-file-noselect (expand-file-name file)) + (org-mode) + (rmh-elfeed-org-cleanup-headlines + (rmh-elfeed-org-filter-relevant + (rmh-elfeed-org-convert-tree-to-headlines + (rmh-elfeed-org-import-trees tree-id))) + (intern tree-id)))) + files))) + + +(defun rmh-elfeed-org-convert-headline-to-tagger-params (tagger-headline) + "Add new entry hooks for tagging configured with the found headline in TAGGER-HEADLINE." + (list + (s-trim (s-chop-prefix "entry-title:" (car tagger-headline))) + (cdr tagger-headline))) + + +(defun rmh-elfeed-org-export-entry-hook (tagger-params) + "Export TAGGER-PARAMS to the proper `elfeed' structure." + (add-hook 'elfeed-new-entry-hook + (elfeed-make-tagger + :entry-title (car tagger-params) + :add (cdr tagger-params)))) + + +(defun rmh-elfeed-org-export-feed (headline) + "Export HEADLINE to the proper `elfeed' structure." + (add-to-list 'elfeed-feeds headline)) + + +(defun rmh-elfeed-org-process (files tree-id) + "Process headlines and taggers from FILES with org headlines with TREE-ID." + + ;; Warn if needed + (-each files 'rmh-elfeed-org-check-configuration-file) + + ;; Clear elfeed structures + (setq elfeed-feeds nil) + (setq elfeed-new-entry-hook nil) + + ;; Convert org structure to elfeed structure + (-each (rmh-elfeed-org-import-headlines-from-files files tree-id) + (lambda (headline) + (let ((text (car headline))) + (when (s-starts-with? "http" text) + (rmh-elfeed-org-export-feed headline)) + (when (s-starts-with? "entry-title" text) + (rmh-elfeed-org-export-entry-hook + (rmh-elfeed-org-convert-headline-to-tagger-params headline)))))) + + ;; Tell user what we did + (message "elfeed-org loaded %i feeds, %i rules" + (length elfeed-feeds) + (length elfeed-new-entry-hook))) + + +;;;###autoload +(defun elfeed-org () + "Hook up rmh-elfeed-org to read the `org-mode' configuration when elfeed is run." + (interactive) + (message "elfeed-org is set up to handle elfeed configuration.") + ;; Use an advice to load the configuration. + (defadvice elfeed (before configure-elfeed activate) + "Load all feed settings before elfeed is started." + (rmh-elfeed-org-process rmh-elfeed-org-files rmh-elfeed-org-tree-id))) + + +(provide 'elfeed-org) +;;; elfeed-org.el ends here -- 2.47.3