diff -Nru elfeed-3.1.0/debian/changelog elfeed-3.2.0/debian/changelog --- elfeed-3.1.0/debian/changelog 2019-08-25 17:16:57.000000000 +0000 +++ elfeed-3.2.0/debian/changelog 2019-09-03 13:08:09.000000000 +0000 @@ -1,3 +1,14 @@ +elfeed (3.2.0-1) unstable; urgency=medium + + * New upstream version 3.2.0 + * Migrate to dh 12 without d/compat + * Run tests via the main test file only and exclude others to prevent errors + * d/control: Declare Standards-Version 4.4.0 (no changes needed) + * d/control: Drop emacs25 from Enhances + * d/copyright: Bump copyright years + + -- Lev Lamberov Tue, 03 Sep 2019 18:08:09 +0500 + elfeed (3.1.0-3) unstable; urgency=medium * Team upload. diff -Nru elfeed-3.1.0/debian/compat elfeed-3.2.0/debian/compat --- elfeed-3.1.0/debian/compat 2019-08-25 17:16:57.000000000 +0000 +++ elfeed-3.2.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru elfeed-3.1.0/debian/control elfeed-3.2.0/debian/control --- elfeed-3.1.0/debian/control 2019-08-25 17:16:57.000000000 +0000 +++ elfeed-3.2.0/debian/control 2019-09-03 13:08:09.000000000 +0000 @@ -3,9 +3,9 @@ Priority: optional Maintainer: Debian Emacsen team Uploaders: Lev Lamberov -Build-Depends: debhelper (>= 11), +Build-Depends: debhelper-compat (= 12), dh-elpa -Standards-Version: 4.2.1 +Standards-Version: 4.4.0 Testsuite: autopkgtest-pkg-elpa Rules-Requires-Root: no Homepage: https://github.com/skeeto/elfeed @@ -29,8 +29,7 @@ Depends: ${elpa:Depends}, ${misc:Depends} Recommends: emacs (>= 46.0) -Enhances: emacs, - emacs25 +Enhances: emacs Description: Emacs Atom/RSS feed reader - web interface Elfeed is an extensible web feed reader for Emacs, supporting both Atom and RSS. Elfeed was inspired by notmuch. diff -Nru elfeed-3.1.0/debian/copyright elfeed-3.2.0/debian/copyright --- elfeed-3.1.0/debian/copyright 2019-08-25 17:16:57.000000000 +0000 +++ elfeed-3.2.0/debian/copyright 2019-09-03 13:08:09.000000000 +0000 @@ -8,7 +8,7 @@ License: Unlicense Files: debian/* -Copyright: (C) 2016-2018 Lev Lamberov +Copyright: (C) 2016-2019 Lev Lamberov License: GPL-3+ License: Unlicense diff -Nru elfeed-3.1.0/debian/elpa-test elfeed-3.2.0/debian/elpa-test --- elfeed-3.1.0/debian/elpa-test 1970-01-01 00:00:00.000000000 +0000 +++ elfeed-3.2.0/debian/elpa-test 2019-09-03 13:08:09.000000000 +0000 @@ -0,0 +1 @@ +ert_exclude = tests/elfeed-*-tests.el, tests/xml-query-tests.el diff -Nru elfeed-3.1.0/elfeed-curl.el elfeed-3.2.0/elfeed-curl.el --- elfeed-3.1.0/elfeed-curl.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed-curl.el 2019-08-24 12:13:25.000000000 +0000 @@ -110,6 +110,7 @@ (13 . "FTP weird PASV reply.") (14 . "FTP weird 227 format.") (15 . "FTP can't get host.") + (16 . "A problem was detected in the HTTP2 framing layer.") (17 . "FTP couldn't set binary.") (18 . "Partial file. Only a part of the file was transferred.") (19 . "FTP couldn't download/access the given file, the RETR (or similar) command failed.") @@ -286,10 +287,8 @@ "Build an argument list for curl for URL. URL can be a string or a list of URL strings." (let* ((args ()) - (capabilities (elfeed-curl-get-capabilities)) - (version (plist-get capabilities :version))) - (unless (version< version "7.33.0") - (push "--http1.1" args)) ; too many broken HTTP/2 servers + (capabilities (elfeed-curl-get-capabilities))) + (push "--disable" args) (when (plist-get capabilities :compression) (push "--compressed" args)) (push "--silent" args) diff -Nru elfeed-3.1.0/elfeed.el elfeed-3.2.0/elfeed.el --- elfeed-3.1.0/elfeed.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed.el 2019-08-24 12:13:25.000000000 +0000 @@ -39,7 +39,7 @@ "An Emacs web feed reader." :group 'comm) -(defconst elfeed-version "3.1.0") +(defconst elfeed-version "3.2.0") (defcustom elfeed-feeds () "List of all feeds that Elfeed should follow. @@ -243,6 +243,28 @@ (concat protocol ":" url) url)) +(defsubst elfeed--atom-authors-to-plist (authors) + "Parse list of author XML tags into list of plists." + (let ((result ())) + (dolist (author authors) + (let ((plist ()) + (name (xml-query* (name *) author)) + (uri (xml-query* (uri *) author)) + (email (xml-query* (email *) author))) + (when email + (setf plist (list :email (elfeed-cleanup email)))) + (when uri + (setf plist (nconc (list :uri (elfeed-cleanup uri)) plist))) + (when name + (setf plist (nconc (list :name (elfeed-cleanup name)) plist))) + (push plist result))) + (nreverse result))) + +(defsubst elfeed--creators-to-plist (creators) + "Convert Dublin Core list of creators into an authors plist." + (cl-loop for creator in creators + collect (list :name creator))) + (defun elfeed-entries-from-atom (url xml) "Turn parsed Atom content into a list of elfeed-entry structs." (let* ((feed-id url) @@ -250,12 +272,12 @@ (namespace (elfeed-url-to-namespace url)) (feed (elfeed-db-get-feed feed-id)) (title (elfeed-cleanup (xml-query* (feed title *) xml))) - (author (elfeed-cleanup (xml-query* (feed author name *) xml))) + (authors (xml-query-all* (feed author) xml)) (xml-base (or (xml-query* (feed :base) xml) url)) (autotags (elfeed-feed-autotags url))) (setf (elfeed-feed-url feed) url (elfeed-feed-title feed) title - (elfeed-feed-author feed) author) + (elfeed-feed-author feed) (elfeed--atom-authors-to-plist authors)) (cl-loop for entry in (xml-query-all* (feed entry) xml) collect (let* ((title (or (xml-query* (title *) entry) "")) (xml-base (elfeed-update-location @@ -271,13 +293,11 @@ (xml-query* (date *) entry) (xml-query* (modified *) entry) ; Atom 0.3 (xml-query* (issued *) entry))) ; Atom 0.3 - (author-name (or (xml-query* (author name *) entry) - ;; Dublin Core - (xml-query* (creator *) entry))) - (author-email (xml-query* (author email *) entry)) - (author (cond ((and author-name author-email) - (format "%s <%s>" author-name author-email)) - (author-name))) + (authors (nconc (elfeed--atom-authors-to-plist + (xml-query-all* (author) entry)) + ;; Dublin Core + (elfeed--creators-to-plist + (xml-query-all* (creator *) entry)))) (categories (xml-query-all* (category :term) entry)) (content (elfeed--atom-content entry)) (id (or (xml-query* (id *) entry) link @@ -305,14 +325,23 @@ :content content :enclosures enclosures :content-type content-type - :meta `(,@(when author - (list :author author)) + :meta `(,@(when authors + (list :authors authors)) ,@(when categories (list :categories categories)))))) (dolist (hook elfeed-new-entry-parse-hook) (funcall hook :atom entry db-entry)) db-entry)))) +(defsubst elfeed--rss-author-to-plist (author) + "Parse an RSS author element into an authors plist." + (when author + (let ((clean (elfeed-cleanup author))) + (if (string-match "^\\(.*\\) (\\([^)]+\\))$" clean) + (list (list :name (match-string 2 clean) + :email (match-string 1 clean))) + (list (list :email clean)))))) + (defun elfeed-entries-from-rss (url xml) "Turn parsed RSS content into a list of elfeed-entry structs." (let* ((feed-id url) @@ -331,9 +360,11 @@ (or (xml-query* (link *) item) guid))) (date (or (xml-query* (pubDate *) item) (xml-query* (date *) item))) - (author (or (xml-query* (author *) item) - ;; Dublin Core - (xml-query* (creator *) item))) + (authors (nconc (elfeed--rss-author-to-plist + (xml-query* (author *) item)) + ;; Dublin Core + (elfeed--creators-to-plist + (xml-query-all* (creator *) item)))) (categories (xml-query-all* (category *) item)) (content (or (xml-query-all* (encoded *) item) (xml-query-all* (description *) item))) @@ -362,8 +393,8 @@ :enclosures enclosures :content description :content-type 'html - :meta `(,@(when author - (list :author author)) + :meta `(,@(when authors + (list :authors authors)) ,@(when categories (list :categories categories)))))) (dolist (hook elfeed-new-entry-parse-hook) diff -Nru elfeed-3.1.0/elfeed-lib.el elfeed-3.2.0/elfeed-lib.el --- elfeed-3.1.0/elfeed-lib.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed-lib.el 2019-08-24 12:13:25.000000000 +0000 @@ -36,14 +36,21 @@ (end-of-line) (delete-region start (point)))) -(defun elfeed-time-duration (time) +(defun elfeed-time-duration (time &optional now) "Turn a time expression into a number of seconds. Uses -`timer-duration' but allows a bit more flair." - (if (numberp time) - time - (when (string-match-p "[[:alpha:]]" time) - (let ((clean (replace-regexp-in-string "\\(ago\\|old\\|-\\)" " " time))) - (timer-duration clean))))) +`timer-duration' but allows a bit more flair. + +If `now' is non-nil, use it as the current time (`float-time'). This +is mostly useful for testing." + (cond + ((numberp time) time) + ((let ((iso-time (elfeed-parse-simple-iso-8601 time))) + (when iso-time (- (or now (float-time)) iso-time)))) + ((string-match-p "[[:alpha:]]" time) + (let* ((clean (replace-regexp-in-string "\\(ago\\|old\\|-\\)" " " time)) + (duration (timer-duration clean))) + ;; convert to float since float-time is used elsewhere + (when duration (float duration)))))) (defun elfeed-looks-like-url-p (string) "Return true if STRING looks like it could be a URL." @@ -71,21 +78,29 @@ (defun elfeed-cleanup (name) "Trim trailing and leading spaces and collapse multiple spaces." - (let ((trim (replace-regexp-in-string "[\n\t]+" " " (or name "")))) + (let ((trim (replace-regexp-in-string "[\f\n\r\t\v ]+" " " (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)))))))) + (let* ((re (cl-flet ((re-numbers (num) (format "\\([0-9]\\{%s\\}\\)" num))) + (format "^%s-?%s-?%s?\\(T%s:%s:?%s?\\)?" + (re-numbers 4) + (re-numbers 2) + (re-numbers 2) + (re-numbers 2) + (re-numbers 2) + (re-numbers 2)))) + (matches (save-match-data + (when (string-match re string) + (cl-loop for i from 1 to 7 + collect (let ((match (match-string i string))) + (and match (string-to-number match)))))))) + (when matches + (cl-multiple-value-bind (year month day _ hour min sec) matches + (float-time (encode-time (or sec 0) (or min 0) (or hour 0) + (or day 1) month year t)))))) (defun elfeed-new-date-for-entry (old-date new-date) "Decide entry date, given an existing date (nil for new) and a new date. @@ -242,7 +257,8 @@ (funcall interprogram-paste-function)) (and (fboundp 'w32-get-clipboard-data) (funcall 'w32-get-clipboard-data)) - (current-kill 0 :non-destructively)))) + (ignore-errors + (current-kill 0 :non-destructively))))) (defun elfeed-get-link-at-point () "Try to a link at point and return its URL." diff -Nru elfeed-3.1.0/elfeed-pkg.el elfeed-3.2.0/elfeed-pkg.el --- elfeed-3.1.0/elfeed-pkg.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed-pkg.el 2019-08-24 12:13:25.000000000 +0000 @@ -1,3 +1,3 @@ -(define-package "elfeed" "3.1.0" +(define-package "elfeed" "3.2.0" "an Emacs Atom/RSS feed reader" '((emacs "24.3"))) diff -Nru elfeed-3.1.0/elfeed-search.el elfeed-3.2.0/elfeed-search.el --- elfeed-3.1.0/elfeed-search.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed-search.el 2019-08-24 12:13:25.000000000 +0000 @@ -107,24 +107,39 @@ (elfeed-db-save) (quit-window)) +(defun elfeed-search-last-entry () + "Place point on first entry." + (interactive) + (setf (point) (point-max)) + (forward-line -2)) + +(defun elfeed-search-first-entry () + "Place point on last entry." + (interactive) + (setf (point) (point-min))) + (defvar elfeed-search-mode-map (let ((map (make-sparse-keymap))) (prog1 map (suppress-keymap map) - (define-key map "q" 'elfeed-search-quit-window) - (define-key map "g" 'elfeed-search-update--force) - (define-key map "G" 'elfeed-search-fetch) - (define-key map (kbd "RET") 'elfeed-search-show-entry) - (define-key map "s" 'elfeed-search-live-filter) - (define-key map "S" 'elfeed-search-set-filter) - (define-key map "b" 'elfeed-search-browse-url) - (define-key map "y" 'elfeed-search-yank) - (define-key map "u" 'elfeed-search-tag-all-unread) - (define-key map "r" 'elfeed-search-untag-all-unread) - (define-key map "n" 'next-line) - (define-key map "p" 'previous-line) - (define-key map "+" 'elfeed-search-tag-all) - (define-key map "-" 'elfeed-search-untag-all))) + (define-key map "h" #'describe-mode) + (define-key map "q" #'elfeed-search-quit-window) + (define-key map "g" #'elfeed-search-update--force) + (define-key map "G" #'elfeed-search-fetch) + (define-key map (kbd "RET") #'elfeed-search-show-entry) + (define-key map "s" #'elfeed-search-live-filter) + (define-key map "S" #'elfeed-search-set-filter) + (define-key map "c" #'elfeed-search-clear-filter) + (define-key map "b" #'elfeed-search-browse-url) + (define-key map "y" #'elfeed-search-yank) + (define-key map "u" #'elfeed-search-tag-all-unread) + (define-key map "r" #'elfeed-search-untag-all-unread) + (define-key map "n" #'next-line) + (define-key map "p" #'previous-line) + (define-key map "+" #'elfeed-search-tag-all) + (define-key map "-" #'elfeed-search-untag-all) + (define-key map "<" #'elfeed-search-first-entry) + (define-key map ">" #'elfeed-search-last-entry))) "Keymap for elfeed-search-mode.") (defun elfeed-search--intro-header () @@ -180,7 +195,7 @@ ((> (elfeed-queue-count-total) 0) (let ((total (elfeed-queue-count-total)) (in-process (elfeed-queue-count-active))) - (format "%d feeds pending, %d in process ..." + (format "%d jobs pending, %d active..." (- total in-process) in-process))) ((let* ((db-time (seconds-to-time (elfeed-db-last-update))) (update (format-time-string "%Y-%m-%d %H:%M" db-time)) @@ -352,6 +367,7 @@ "Parse the elements of a search filter into a plist." (let ((must-have ()) (must-not-have ()) + (before nil) (after nil) (matches ()) (not-matches ()) @@ -368,7 +384,14 @@ (let ((symbol (intern (substring element 1)))) (unless (eq '## symbol) (push symbol must-not-have)))) - (?@ (setf after (elfeed-time-duration (substring element 1)))) + (?@ (cl-multiple-value-bind (a b) + (split-string (substring element 1) "--") + (let ((duration-a (elfeed-time-duration a)) + (duration-b (and b (elfeed-time-duration b)))) + (when (and duration-b (> duration-b duration-a)) + (cl-rotatef duration-a duration-b)) + (when duration-b (setf before duration-b)) + (setf after duration-a)))) (?! (let ((re (substring element 1))) (when (elfeed-valid-regexp-p re) (push re not-matches)))) @@ -377,7 +400,9 @@ (push url feeds))) (otherwise (when (elfeed-valid-regexp-p element) (push element matches))))) - `(,@(when after + `(,@(when before + (list :before before)) + ,@(when after (list :after after)) ,@(when must-have (list :must-have must-have)) @@ -392,7 +417,7 @@ ,@(when feeds (list :feeds feeds))))) -(defun elfeed-search--recover-units (seconds) +(defun elfeed-search--recover-time (seconds) "Pick a reasonable filter representation for SECONDS." (let ((units '((60 1 "minute") (60 1 "hour") @@ -409,7 +434,15 @@ do (setf name unit value next-value)) (let ((count (format "%.4g" value))) - (format "@%s-%s%s-ago" count name (if (equal count "1") "" "s"))))) + (format "%s-%s%s-ago" count name (if (equal count "1") "" "s"))))) + +(defun elfeed-search--recover-units (after-seconds &optional before-seconds) + "Stringify the age or optionally the date range specified by +AFTER-SECONDS and BEFORE-SECONDS." + (apply 'concat "@" + (elfeed-search--recover-time after-seconds) + (when before-seconds + (list "--"(elfeed-search--recover-time before-seconds))))) (defun elfeed-search-unparse-filter (filter) "Inverse of `elfeed-search-parse-filter', returning a string. @@ -418,6 +451,7 @@ original, but will be equal in its effect." (let ((output ())) (let ((after (plist-get filter :after)) + (before (plist-get filter :before)) (must-have (plist-get filter :must-have)) (must-not-have (plist-get filter :must-not-have)) (matches (plist-get filter :matches)) @@ -425,7 +459,7 @@ (limit (plist-get filter :limit)) (feeds (plist-get filter :feeds))) (when after - (push (elfeed-search--recover-units after) output)) + (push (elfeed-search--recover-units after before) output)) (dolist (tag must-have) (push (format "+%S" tag) output)) (dolist (tag must-not-have) @@ -492,6 +526,7 @@ Executing a filter in bytecode form is generally faster than \"interpreting\" the filter with `elfeed-search-filter'." (let ((after (plist-get filter :after)) + (before (plist-get filter :before)) (must-have (plist-get filter :must-have)) (must-not-have (plist-get filter :must-not-have)) (matches (plist-get filter :matches)) @@ -541,7 +576,9 @@ `((or ,@(cl-loop for regex in feeds collect `(string-match-p ,regex feed-id) - collect `(string-match-p ,regex feed-title)))))))))) + collect `(string-match-p ,regex feed-title))))) + ,@(when before + `((> age ,before)))))))) (defun elfeed-search--prompt (current) "Prompt for a new filter, starting with CURRENT." @@ -553,6 +590,12 @@ (concat current " ")) nil nil 'elfeed-search-filter-history)) +(defun elfeed-search-clear-filter () + "Reset the search filter to the default value of `elfeed-search-filter'." + (interactive) + (setf elfeed-search-filter (default-value 'elfeed-search-filter)) + (elfeed-search-update--force)) + (defun elfeed-search-set-filter (new-filter) "Set a new search filter for the elfeed-search buffer. @@ -565,8 +608,15 @@ the tag must be present on the entry. If - the tag must *not* be present on the entry. Ex. \"+unread\" or \"+unread -comic\". -Any component beginning with an @ is an age limit. No posts older -than this are allowed. Ex. \"@3-days-ago\" or \"@1-year-old\". +Any component beginning with an @ is an age limit or an age +range. If a limit, no posts older than this are allowed. If a +range, posts dates have to be inbetween the specified date +range. Examples: +- \"@3-days-ago\" +- \"@1-year-old\" +- \"@2019-06-24\" +- \"@2019-06-24--2019-06-24\" +- \"@5-days-ago--1-day-ago\" Any component beginning with a # is an entry count maximum. The number following # determines the maxiumum number of entries diff -Nru elfeed-3.1.0/elfeed-show.el elfeed-3.2.0/elfeed-show.el --- elfeed-3.1.0/elfeed-show.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/elfeed-show.el 2019-08-24 12:13:25.000000000 +0000 @@ -45,26 +45,28 @@ (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) - (define-key map "p" 'elfeed-show-prev) - (define-key map "s" 'elfeed-show-new-live-search) - (define-key map "b" 'elfeed-show-visit) - (define-key map "y" 'elfeed-show-yank) - (define-key map "u" (elfeed-expose #'elfeed-show-tag 'unread)) - (define-key map "+" 'elfeed-show-tag) - (define-key map "-" 'elfeed-show-untag) - (define-key map (kbd "SPC") 'scroll-up-command) - (define-key map (kbd "DEL") 'scroll-down-command) - (define-key map "\t" 'shr-next-link) - (define-key map [tab] 'shr-next-link) - (define-key map "\e\t" 'shr-previous-link) - (define-key map [backtab] 'shr-previous-link) - (define-key map [mouse-2] 'shr-browse-url) - (define-key map "A" 'elfeed-show-add-enclosure-to-playlist) - (define-key map "P" 'elfeed-show-play-enclosure))) + (define-key map "h" #'describe-mode) + (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) + (define-key map "p" #'elfeed-show-prev) + (define-key map "s" #'elfeed-show-new-live-search) + (define-key map "b" #'elfeed-show-visit) + (define-key map "y" #'elfeed-show-yank) + (define-key map "u" #'elfeed-show-tag--unread) + (define-key map "+" #'elfeed-show-tag) + (define-key map "-" #'elfeed-show-untag) + (define-key map "<" #'beginning-of-buffer) + (define-key map ">" #'end-of-buffer) + (define-key map (kbd "SPC") #'scroll-up-command) + (define-key map (kbd "DEL") #'scroll-down-command) + (define-key map (kbd "TAB") #'elfeed-show-next-link) + (define-key map "\e\t" #'shr-previous-link) + (define-key map [backtab] #'shr-previous-link) + (define-key map [mouse-2] #'shr-browse-url) + (define-key map "A" #'elfeed-show-add-enclosure-to-playlist) + (define-key map "P" #'elfeed-show-play-enclosure))) "Keymap for `elfeed-show-mode'.") (defun elfeed-show-mode () @@ -80,6 +82,10 @@ (make-local-variable 'elfeed-show-entry) (run-mode-hooks 'elfeed-show-mode-hook)) +(defalias 'elfeed-show-tag--unread + (elfeed-expose #'elfeed-show-tag 'unread) + "Mark the current entry unread.") + (defun elfeed-insert-html (html &optional base-url) "Converted HTML markup to a propertized string." (shr-insert-document @@ -110,13 +116,29 @@ (setf (url-target obj) nil) (url-recreate-url obj))) +(defun elfeed--show-format-author (author) + "Format author plist for the header." + (let ((name (plist-get author :name)) + (uri (plist-get author :uri)) + (email (plist-get author :email))) + (cond ((and name uri email) + (format "%s <%s> (%s)" name email uri)) + ((and name email) + (format "%s <%s>" name email)) + ((and name uri) + (format "%s (%s)" name uri)) + (name name) + (email email) + (uri uri) + ("[unknown]")))) + (defun elfeed-show-refresh--mail-style () "Update the buffer to match the selected entry, using a mail-style." (interactive) (let* ((inhibit-read-only t) (title (elfeed-entry-title elfeed-show-entry)) (date (seconds-to-time (elfeed-entry-date elfeed-show-entry))) - (author (elfeed-meta elfeed-show-entry :author)) + (authors (elfeed-meta elfeed-show-entry :authors)) (link (elfeed-entry-link elfeed-show-entry)) (tags (elfeed-entry-tags elfeed-show-entry)) (tagsstr (mapconcat #'symbol-name tags ", ")) @@ -129,9 +151,12 @@ (erase-buffer) (insert (format (propertize "Title: %s\n" 'face 'message-header-name) (propertize title 'face 'message-header-subject))) - (when (and author elfeed-show-entry-author) - (insert (format (propertize "Author: %s\n" 'face 'message-header-name) - (propertize author 'face 'message-header-to))) ) + (when elfeed-show-entry-author + (dolist (author authors) + (let ((formatted (elfeed--show-format-author author))) + (insert + (format (propertize "Author: %s\n" 'face 'message-header-name) + (propertize formatted 'face 'message-header-to)))))) (insert (format (propertize "Date: %s\n" 'face 'message-header-name) (propertize nicedate 'face 'message-header-other))) (insert (format (propertize "Feed: %s\n" 'face 'message-header-name) @@ -159,9 +184,25 @@ (interactive) (call-interactively elfeed-show-refresh-function)) +(defcustom elfeed-show-unique-buffers nil + "When non-nil, every entry buffer gets a unique name. +This allows for displaying multiple show buffers at the same +time." + :group 'elfeed + :type 'boolean) + +(defun elfeed-show--buffer-name (entry) + "Return the appropriate buffer name for ENTRY. +The result depends on the value of `elfeed-show-unique-buffers'." + (if elfeed-show-unique-buffers + (format "*elfeed-entry-<%s %s>*" + (elfeed-entry-title entry) + (format-time-string "%F" (elfeed-entry-date entry))) + "*elfeed-entry*")) + (defun elfeed-show-entry (entry) "Display ENTRY in the current buffer." - (let ((buff (get-buffer-create "*elfeed-entry*"))) + (let ((buff (get-buffer-create (elfeed-show--buffer-name entry)))) (with-current-buffer buff (elfeed-show-mode) (setq elfeed-show-entry entry) @@ -377,45 +418,40 @@ (elfeed-show-save-enclosure-multi) (elfeed-show-save-enclosure-single))) -(defun elfeed-show-play-enclosure (&optional entry enclosure-index) - "Play enclosure number ENCLOSURE-INDEX from ENTRY using emms. -If ENTRY is nil use the elfeed-show-entry variable. -If ENCLOSURE-INDEX is nil ask for the enclosure number." - (interactive) - (require 'emms) ;; optional - (let* ((entry (or entry elfeed-show-entry)) - (enclosure-index (or enclosure-index - (elfeed--get-enclosure-num - "Enclosure to play" entry))) - (url-enclosure (car (elt (elfeed-entry-enclosures entry) - (- enclosure-index 1))))) - (with-no-warnings ;; due to lazy (require) - (with-current-emms-playlist - (let ((old-pos (point-max))) - (emms-add-url url-enclosure) - (goto-char old-pos) - ;; if we're sitting on a group name, move forward - (unless (emms-playlist-track-at (point)) - (emms-playlist-next)) - (emms-playlist-select (point))) - ;; FIXME: is there a better way of doing this? - (emms-stop) - (emms-start))))) +(defun elfeed--enclosure-maybe-prompt-index (entry) + "Prompt for an enclosure if there are multiple in ENTRY." + (if (= 1 (length (elfeed-entry-enclosures entry))) + 1 + (elfeed--get-enclosure-num "Enclosure to play" entry))) + +(defun elfeed-show-play-enclosure (enclosure-index) + "Play enclosure number ENCLOSURE-INDEX from current entry using EMMS. +Prompts for ENCLOSURE-INDEX when called interactively." + (interactive (list (elfeed--enclosure-maybe-prompt-index elfeed-show-entry))) + (elfeed-show-add-enclosure-to-playlist enclosure-index) + (with-no-warnings + (with-current-emms-playlist + (save-excursion + (emms-playlist-last) + (emms-playlist-mode-play-current-track))))) + +(defun elfeed-show-add-enclosure-to-playlist (enclosure-index) + "Add enclosure number ENCLOSURE-INDEX to current EMMS playlist. +Prompts for ENCLOSURE-INDEX when called interactively." -(defun elfeed-show-add-enclosure-to-playlist (&optional entry enclosure-index) - "Play enclosure number ENCLOSURE-INDEX from ENTRY using emms. -If ENTRY is nil use the elfeed-show-entry variable. -If ENCLOSURE-INDEX is nil ask for the enclosure number." - (interactive) + (interactive (list (elfeed--enclosure-maybe-prompt-index elfeed-show-entry))) (require 'emms) ;; optional - (let* ((entry (or entry elfeed-show-entry)) - (enclosure-index (or enclosure-index - (elfeed--get-enclosure-num - "Enclosure to add" entry))) - (url-enclosure (car (elt (elfeed-entry-enclosures entry) - (- enclosure-index 1))))) - (with-no-warnings ;; due to lazy (require ) - (emms-add-url url-enclosure)))) + (with-no-warnings ;; due to lazy (require ) + (emms-add-url (car (elt (elfeed-entry-enclosures elfeed-show-entry) + (- enclosure-index 1)))))) + +(defun elfeed-show-next-link () + "Skip to the next link, exclusive of the Link header." + (interactive) + (let ((properties (text-properties-at (line-beginning-position)))) + (when (memq 'message-header-name properties) + (forward-paragraph)) + (shr-next-link))) (provide 'elfeed-show) diff -Nru elfeed-3.1.0/Makefile elfeed-3.2.0/Makefile --- elfeed-3.1.0/Makefile 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/Makefile 2019-08-24 12:13:25.000000000 +0000 @@ -1,7 +1,7 @@ .POSIX: EMACS = emacs BATCH = $(EMACS) -batch -Q -L . -L tests -VERSION = 3.1.0 +VERSION = 3.2.0 EL = elfeed-csv.el elfeed-curl.el elfeed-db.el elfeed-lib.el \ elfeed-log.el elfeed-show.el elfeed.el xml-query.el \ @@ -23,6 +23,12 @@ clean: rm -f *.tar $(EL:.el=.elc) $(TEST:.el=.elc) +virtual: compile + (mkdir -p tmp-$$$$/.elfeed; \ + cp ~/.elfeed/index tmp-$$$$/.elfeed/ 2>/dev/null || true; \ + trap "rm -rf tmp-$$$$" INT EXIT; \ + HOME=$$PWD/tmp-$$$$ $(EMACS) -L . -l elfeed.elc $(ARGS)) + elfeed-$(VERSION).tar: $(EL) $(DOC) rm -rf elfeed-$(VERSION)/ mkdir elfeed-$(VERSION)/ diff -Nru elfeed-3.1.0/NEWS.md elfeed-3.2.0/NEWS.md --- elfeed-3.1.0/NEWS.md 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/NEWS.md 2019-08-24 12:13:25.000000000 +0000 @@ -1,5 +1,28 @@ # Changes +## 3.2.0 (2019-08-24) + +* Support for absolute date/time expressions in filters. See README.md + for documentation and examples. + +* curl's `--disable` is now default. To load your .curlrc file, use + `--config` explicitly in `elfeed-curl-extra-arguments`. + +* Re-enable curl's HTTP/2 support. + +* Function `elfeed-next-link` was renamed to `elfeed-show-next-link`. + +* New search buffer bindings: <, >, h, c + +* Multiple authors are now parsed from entries. Reflecting this, the + meta key for authors is now `:authors` instead of `:author`. The + value is always a list of zero or more authors. + +* New variable: `elfeed-show-unique-buffers`. Allows for displaying + multiple show buffers at the same time. + +* Various minor fixes and improvements. + ## 3.1.0 (2018-08-29) * Add `elfeed-show-enclosure-filename-function` for controlling diff -Nru elfeed-3.1.0/README.md elfeed-3.2.0/README.md --- elfeed-3.1.0/README.md 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/README.md 2019-08-24 12:13:25.000000000 +0000 @@ -64,6 +64,7 @@ * g: refresh view of the feed listing * G: fetch feed updates from the servers * s: update the search filter (see tags) + * c: clear the search filter This buffer will be empty until you add your feeds to the `elfeed-feeds` list and initiate an update with `M-x elfeed-update` @@ -133,17 +134,22 @@ To make tags useful, the Elfeed entry listing buffer can be filtered by tags. Use `elfeed-search-set-filter` (or s) to update -the filter. +the filter. Use `elfeed-search-clear-filter` to restore the default. Any component of the search string beginning with a `+` or a `-` is treated like a tag. `+` means the tag is required, `-` means the tag must not be present. -A component beginning with a `@` indicates an age. Entries older than -this age are filtered out. The age description accepts plain English, -but cannot have spaces, so use dashes. For example, `"@2-years-old"` -or `"@3-days-ago"`. The database is date-oriented, so **filters that -include an age restriction are significantly more efficient.** +A component beginning with a `@` indicates an age or a date range. An +age is a relative time expression or an absolute date expression. +Entries older than this age are filtered out. The age description +accepts plain English, but cannot have spaces, so use dashes. For +example, `"@2-years-old"`, `"@3-days-ago"` or `"@2019-06-24"`. A date +range are two ages seperated by a `--`, e.g. +`"@2019-06-20--2019-06-24"` or `"@5-days-ago--1-day-ago"`. The entry +must be newer than the first expression but older than the second. The +database is date-oriented, so **filters that include an age +restriction are significantly more efficient.** A component beginning with a `!` is treated as an "inverse" regular expression. This means that any entry matching this regular expression @@ -279,7 +285,7 @@ Elfeed itself adds some entries to this plist, some for your use, some for its own use. Here are the properties that Elfeed uses: -* `:author` : The entry's author of this entry. +* `:authors` : A list of author plists (`:name`, `:uri`, `:email`). * `:canonical-url` : The final URL for the feed after all redirects. * `:categories` : The feed-supplied categories for this entry. * `:etag` : HTTP Etag header, for conditional GETs. diff -Nru elfeed-3.1.0/tests/elfeed-lib-tests.el elfeed-3.2.0/tests/elfeed-lib-tests.el --- elfeed-3.1.0/tests/elfeed-lib-tests.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/tests/elfeed-lib-tests.el 2019-08-24 12:13:25.000000000 +0000 @@ -24,6 +24,26 @@ (should (= (elfeed-time-duration "1-day") (* 1.0 24 60 60))) (should (= (elfeed-time-duration "1hour") (* 1.0 60 60)))) +(ert-deftest elfeed-time-duration-absolute () + ;; fixed time for testing: assume U.S. eastern + (let ((now (float-time (encode-time 0 20 13 24 6 2019 (* -1 4 60 60))))) + ;; "2019-06-24T13:20:00-04:00" is "2019-06-24T17:20:00Z" so 17h 20mins is + ;; the time difference: + (should (= (+ (* 17 60 60) (* 20 60)) + (elfeed-time-duration "2019-06-24" now))) + (should (= (* 10 60) + (elfeed-time-duration "2019-06-24T17:10" now))) + (should (= (* 10 60) + (elfeed-time-duration "2019-06-24T17:10:00" now))) + (should (= (+ (* 9 60) 30) + (elfeed-time-duration "2019-06-24T17:10:30" now))) + (should (= (+ (* 9 60) 30) + (elfeed-time-duration "2019-06-24T17:10:30Z" now))) + (should (= (+ (* 9 60) 30) + (elfeed-time-duration "2019-06-24T17:10:30+00:00" now))) + (should (= (+ (* 9 60) 30) + (elfeed-time-duration "20190624T17:10:30+00:00" now))))) + (ert-deftest elfeed-format-column () (should (string= (elfeed-format-column "foo" 10 :right) " foo")) (should (string= (elfeed-format-column "foo" 10 :left) "foo ")) @@ -53,12 +73,13 @@ (should-not (elfeed-looks-like-url-p nil))) (ert-deftest elfeed-cleanup () - (should (string= (elfeed-cleanup " foo bar\n") "foo bar")) + (should (string= (elfeed-cleanup " foo bar\n") "foo bar")) (should (string= (elfeed-cleanup "foo\nbar") "foo bar"))) (ert-deftest elfeed-float-time () (cl-macrolet ((test (time seconds) `(should (= (elfeed-float-time ,time) ,seconds)))) + (test "1985-03-24" 480470400.0) (test "1985-03-24T03:23:42Z" 480482622.0) (test "Mon, 5 May 1986 15:16:09 GMT" 515690169.0) (test "2015-02-20" 1424390400.0) diff -Nru elfeed-3.1.0/tests/elfeed-search-tests.el elfeed-3.2.0/tests/elfeed-search-tests.el --- elfeed-3.1.0/tests/elfeed-search-tests.el 1970-01-01 00:00:00.000000000 +0000 +++ elfeed-3.2.0/tests/elfeed-search-tests.el 2019-08-24 12:13:25.000000000 +0000 @@ -0,0 +1,57 @@ +;;; elfeed-search-tests.el --- search tests -*- lexical-binding: t; -*- + +(require 'ert) +(require 'elfeed-search) + +(defmacro test-search-parse-filter-duration (filter after-days &optional before-days) + (let ((day (* 24 60 60))) + `(should (equal ',(cl-concatenate 'list + (when before-days + (list :before (float (* day before-days)))) + (list :after (float (* day after-days)))) + (elfeed-search-parse-filter ,filter))))) + +(ert-deftest elfeed-parse-filter-time-durations () + (cl-letf (((symbol-function 'current-time) + (lambda () (encode-time 0 0 0 24 6 2019 t)))) + (test-search-parse-filter-duration "@5-days-ago--3-days-ago" 5 3) + (test-search-parse-filter-duration "@3-days-ago--5-days-ago" 5 3) + (test-search-parse-filter-duration "@2019-06-01" 23) + (test-search-parse-filter-duration "@2019-06-20--2019-06-01" 23 4) + (test-search-parse-filter-duration "@2019-06-01--2019-06-20" 23 4) + (test-search-parse-filter-duration "@2019-06-01--4-days-ago" 23 4) + (test-search-parse-filter-duration "@4-days-ago--2019-06-01" 23 4))) + +(defmacro run-date-filter (filter entry-time-string test-time-string) + "Creates an entry with ENTRY-TIME-STRING, sets the current time +to TEST-TIME-STRING and then tests the compiled filter function +by calling it with entry and FILTER. Returns t if the filter +matches, nil otherwise." + `(let* ((test-time (seconds-to-time (elfeed-parse-simple-iso-8601 ,test-time-string))) + (entry-time (seconds-to-time (elfeed-parse-simple-iso-8601 ,entry-time-string))) + (orig-float-time (symbol-function 'float-time)) + (entry (elfeed-entry--create + :title "test-entry" + :date (float-time entry-time)))) + (cl-letf (((symbol-function 'current-time) + (lambda () test-time)) + ((symbol-function 'float-time) + (lambda (&optional time) (funcall orig-float-time test-time)))) + (catch 'elfeed-db-done + (let ((filter-fn (elfeed-search-compile-filter (elfeed-search-parse-filter ,filter)))) + (funcall filter-fn entry nil 0)))))) + +(ert-deftest elfeed-search-compile-filter () + (should (null (run-date-filter "@1-days-ago" "2019-06-23" "2019-06-25"))) + (should (run-date-filter "@3-days-ago" "2019-06-23" "2019-06-25")) + (should (null (run-date-filter "@30-days-ago--10-days-ago" "2019-06-23" "2019-06-25"))) + (should (run-date-filter "@2019-06-01" "2019-06-23" "2019-06-25")) + (should (null (run-date-filter "@2019-06-01--2019-06-20" "2019-06-23" "2019-06-25")))) + +(ert-deftest elfeed-search-unparse-filter () + (should (string-equal "@5-minutes-ago" (elfeed-search-unparse-filter '(:after 300)))) + (should (string-equal "@5-minutes-ago--1-minute-ago" (elfeed-search-unparse-filter '(:after 300 :before 60))))) + +(provide 'elfeed-search-tests) + +;;; elfeed-search-tests.el ends here diff -Nru elfeed-3.1.0/tests/elfeed-tests.el elfeed-3.2.0/tests/elfeed-tests.el --- elfeed-3.1.0/tests/elfeed-tests.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/tests/elfeed-tests.el 2019-08-24 12:13:25.000000000 +0000 @@ -26,7 +26,7 @@ - John Doe <john.doe@example.com> + john.doe@example.com (John Doe) 84815091-a6a3-35d4-7f04-80a6610dc85c Mon, 06 Sep 2009 16:20:00 +0000 example-entry @@ -38,6 +38,7 @@ Interesting description 2. http://www.wikipedia.org/ Jane Doe <jane.doe@example.com> + Baby Doe <baby.doe@example.com> 5059196a-7f8e-3678-ecfe-dad84511d76f Mon, 2 Sep 2013 20:25:07 GMT example-entry @@ -57,9 +58,13 @@ urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6 2003-12-13T18:30:02Z - Test Feed Author + John Doe (feed) johndoe@example.com + + Jane Doe (feed) + janedoe@example.com + Atom-Powered Robots Run Amok @@ -93,6 +98,11 @@ John Doe johndoe@example.com + + Jane Doe + janedoe@example.com + + Foo Bar ") @@ -223,15 +233,19 @@ (should (equal (elfeed-entry-id a) (cons namespace "84815091-a6a3-35d4-7f04-80a6610dc85c"))) - (should (equal (elfeed-meta a :author) - "John Doe ")) + (should (string= (plist-get (nth 0 (elfeed-meta a :authors)) :name) + "John Doe")) + (should (string= (plist-get (nth 0 (elfeed-meta a :authors)) :email) + "john.doe@example.com")) (should (string= (elfeed-entry-title b) "Example entry 2")) (should (= (elfeed-entry-date b) 1378153507.0)) (should (equal (elfeed-entry-id b) (cons namespace "5059196a-7f8e-3678-ecfe-dad84511d76f"))) - (should (equal (elfeed-meta b :author) - "Jane Doe ")) + (should (string= (plist-get (nth 0 (elfeed-meta b :authors)) :name) + "Jane Doe ")) + (should (string= (plist-get (nth 1 (elfeed-meta b :authors)) :name) + "Baby Doe ")) (should (member "example-entry" (elfeed-meta b :categories))) (should (member "Example Two" (elfeed-meta b :categories)))))) (with-temp-buffer @@ -242,11 +256,21 @@ (xml (elfeed-xml-parse-region)) (feed (elfeed-db-get-feed url))) (cl-destructuring-bind (a b) (elfeed-entries-from-atom url xml) - (should (string= (elfeed-feed-author feed) "Test Feed Author")) + ;; Authors + (should (string= (plist-get (nth 0 (elfeed-feed-author feed)) :name) + "John Doe (feed)")) + (should (string= (plist-get (nth 0 (elfeed-feed-author feed)) :email) + "johndoe@example.com")) + (should (string= (plist-get (nth 1 (elfeed-feed-author feed)) :name) + "Jane Doe (feed)")) + (should (string= (plist-get (nth 1 (elfeed-feed-author feed)) :email) + "janedoe@example.com")) + ;; Titles (should (string= (elfeed-feed-title (elfeed-db-get-feed url)) "Example Feed")) (should (string= (elfeed-entry-title a) "Atom-Powered Robots Run Amok")) + ;; Entry A (should (string= (elfeed-entry-link a) "http://example.org/2003/atom03.html")) (should (= (elfeed-entry-date a) 1071340202.0)) @@ -256,13 +280,20 @@ "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a"))) (should (member "example" (elfeed-meta a :categories))) (should (member "cat-1" (elfeed-meta a :categories))) + ;; Entry B (should (string= (elfeed-entry-title b) "It's Raining Cats and Dogs")) (should (string= (elfeed-entry-link b) "http://example.org/2004/12/13/atom03.html")) (should (= (elfeed-entry-date b) 1102962602.0)) - (should (equal (elfeed-meta b :author) - "John Doe ")) + (should (string= (plist-get (nth 0 (elfeed-meta b :authors)) :name) + "John Doe")) + (should (string= (plist-get (nth 0 (elfeed-meta b :authors)) :email) + "johndoe@example.com")) + (should (string= (plist-get (nth 1 (elfeed-meta b :authors)) :name) + "Jane Doe")) + (should (string= (plist-get (nth 1 (elfeed-meta b :authors)) :email) + "janedoe@example.com")) (should (member "example" (elfeed-meta b :categories))) (should (member "cat-2" (elfeed-meta b :categories))) (should diff -Nru elfeed-3.1.0/web/elfeed-web-pkg.el elfeed-3.2.0/web/elfeed-web-pkg.el --- elfeed-3.1.0/web/elfeed-web-pkg.el 2018-08-29 17:16:58.000000000 +0000 +++ elfeed-3.2.0/web/elfeed-web-pkg.el 2019-08-24 12:13:25.000000000 +0000 @@ -1,3 +1,3 @@ -(define-package "elfeed-web" "3.1.0" +(define-package "elfeed-web" "3.2.0" "web interface to Elfeed" - '((simple-httpd "1.4.3") (elfeed "1.4.0") (emacs "24.1"))) + '((simple-httpd "1.5.1") (elfeed "3.2.0") (emacs "24.3")))