From 38099f3dd3b623b0ab44c050905a407eea8510ae Mon Sep 17 00:00:00 2001 From: SqrtMinusOne Date: Sat, 10 Dec 2022 13:41:26 +0300 Subject: [PATCH] feat: add tag-groups --- elfeed-summary.el | 123 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 14 deletions(-) diff --git a/elfeed-summary.el b/elfeed-summary.el index 3472e3a..42d6401 100644 --- a/elfeed-summary.el +++ b/elfeed-summary.el @@ -127,7 +127,7 @@ (boolean :tag "Add the default filter string"))))) (cons :tag "Generate a tree from tags" (const auto-tags) - (repeat :tag "Generate a tree from tags" + (repeat :tag "Tree generation parameters" (choice (cons :tag "Source" (const :tag "Source" :source) @@ -141,11 +141,28 @@ (number :tag "Value")) (cons :tag "Original tag order" (const :tag "Original tag order" :original-order) - (boolean :tag "Reorder tags")) + (boolean :tag "Original tag order")) (cons :tag "Faces" (const :tag "Faces" :faces) (repeat (face :tag "Face")))))) + (cons :tag "Insert each tag as group" + (const tag-groups) + (repeat :tag "Tag group parameters" + (choice + (cons :tag "Source" + (const :tag "Source" :source) + (choice + (cons :tag "Query" + (const query) + elfeed-summary-query) + (const :tag "Misc feeds" :misc))) + (cons :tag "Allow feeds to repeat" + (const :tag "Allow feeds to repeat" :repeat-feeds) + (boolean :tag "Allow feeds to repeat")) + (cons :tag "Face" + (const :tag "Face" :faces) + (face :tag "Face"))))) (const :tag "Misc feeds" :misc)))) (defgroup elfeed-summary () @@ -174,6 +191,8 @@ This is a list of these possible items: Elfeed search, as defined by `elfeed-search-set-filter'. - Tags tree `(auto-tags . )' A tree generated automatically from the available tags. +- Tag groups `(tag-groups . )' + Insert one tag as one group. - a few special forms `' is an alist with the following keys: @@ -224,6 +243,13 @@ Query examples: putting the most frequent tags closer to the root of the tree. - `:faces' - list of faces for groups. +`' is an alist with the following keys: +- `:source' - which feeds to use to build the tree. + Can be `:misc' (default) or `(query . )'. +- `:repeat-feeds' - allow feeds to repeat. Otherwise, each feed is + assigned to group with the least amount of members. +- `:face' - face for groups. + Available special forms: - `:misc' - print out feeds, not found by any query above. @@ -722,6 +748,27 @@ LEVEL is the current level of recursion, which is 0 by default." param (cdr datum) feeds-by-tag-sequence unread-count total-count (1+ level))))))) +(defun elfeed-summary--build-tree-get-feeds (param misc-feeds) + "Get feeds for PARAM. + +PARAM is an alist with the optional `:source' key. The value can be +either `(query . )' or `:misc' (default). + +MISC-FEEDS is the list of feeds used for `:misc'. + +The result is a list of items like (`' tag1 tag2 ...), where +`' is an instance of `elfeed-feed'." + (let* ((source (alist-get :source (cdr param)))) + (mapcar + (lambda (feed) + (cons feed (alist-get (elfeed-feed-id feed) elfeed-feeds + nil nil #'equal))) + (cond ((or (eq source :misc) (null source)) + misc-feeds) + ((and (listp source) (eq (car source) 'query)) + (elfeed-summary--get-feeds (cdr source))) + (t (error "Invalid source: %s" source)))))) + (defun elfeed-summary--build-tree-auto-tags (param unread-count total-count misc-feeds) "Create the auto-tags tree. @@ -732,17 +779,8 @@ UNREAD-COUNT and TOTAL-COUNT are hashmaps with feed ids as keys and corresponding numbers of entries as values. MISC-FEEDS is a list of feeds that were not used in PARAMS." - (let* ((source (alist-get :source (cdr param))) - (feeds (mapcar - (lambda (feed) - (cons feed (alist-get (elfeed-feed-id feed) elfeed-feeds - nil nil #'equal))) - (cond ((or (eq source :misc) (null source)) - misc-feeds) - ((and (listp source) (eq (car source) 'query)) - (elfeed-summary--get-feeds (cdr source))) - (t (error "Invalid auto-tags source: %s" source))))) - (reorder-tags (not (alist-get :original-order (cdr param))))) + (let ((feeds (elfeed-summary--build-tree-get-feeds param misc-feeds)) + (reorder-tags (not (alist-get :original-order (cdr param))))) (when reorder-tags (setq feeds (elfeed-summary--build-tree-auto-tags-reorder-tags feeds))) (let ((tree (elfeed-summary--arrange-sequences-in-tree @@ -755,6 +793,59 @@ MISC-FEEDS is a list of feeds that were not used in PARAMS." (elfeed-summary--build-tree-auto-tags-recursive param tree feeds-by-tag-sequence unread-count total-count)))) +(defun elfeed-summary--build-tree-tag-groups (param unread-count total-count misc-feeds) + "Create the tag-groups tree. + +PARAM is a cell of `(tag-groups . )', with the +`' form as defined in `elfeed-summary-settings'. + +UNREAD-COUNT and TOTAL-COUNT are hashmaps with feed ids as keys and +corresponding numbers of entries as values. + +MISC-FEEDS is a list of feeds that were not used in PARAMS." + (let ((feeds (elfeed-summary--build-tree-get-feeds param misc-feeds)) + (repeat-feeds (alist-get :repeat-feeds (cdr param))) + (face (alist-get :face (cdr param))) + (groups (make-hash-table))) + (if (not repeat-feeds) + (let ((tag-freqs (make-hash-table))) + (cl-loop for feed in feeds do + (cl-loop for tag in (cdr feed) do + (puthash + tag (1+ (gethash tag tag-freqs 0)) + tag-freqs))) + (cl-loop for feed in feeds + for min-freq-tag = (cl-reduce + (lambda (acc tag) + (let ((freq (gethash tag tag-freqs))) + (if (or (null (cdr acc)) (< freq (cdr acc))) + (cons tag freq) + acc))) + (cdr feed) + :initial-value '(nil . nil)) + when min-freq-tag do + (puthash (car min-freq-tag) + (cons (car feed) (gethash min-freq-tag groups)) + groups))) + (cl-loop for feed in feeds do + (cl-loop for tag in (cdr feed) do + (puthash tag (cons (car feed) (gethash tag groups)) groups)))) + (let ((groups-list (seq-sort-by + (lambda (f) (symbol-name (car f))) + #'string-lessp + (cl-loop for tag being the hash-keys of groups + using (hash-values feeds) + collect (cons tag feeds))))) + (cl-loop for (tag . feeds) in groups-list + collect `(group + . ((params . ((:title . ,(symbol-name tag)))) + (face . ,face) + (children . ,(mapcar + (lambda (feed) (elfeed-summary--build-tree-feed + feed unread-count total-count)) + (seq-sort + #'elfeed-summary--feed-sort-fn feeds))))))))) + (defun elfeed-summary--build-tree (params unread-count total-count misc-feeds) "Recursively create the summary details tree. @@ -782,6 +873,9 @@ The resulting form is described in `elfeed-summary--get-data'." else if (and (listp param) (eq (car param) 'auto-tags)) append (elfeed-summary--build-tree-auto-tags param unread-count total-count misc-feeds) + else if (and (listp param) (eq (car param) 'tag-groups)) + append (elfeed-summary--build-tree-tag-groups + param unread-count total-count misc-feeds) else if (eq param :misc) append (cl-loop for feed in (seq-sort #'elfeed-summary--feed-sort-fn misc-feeds) @@ -799,7 +893,8 @@ PARAMS is a form as described in `elfeed-summary-settings'." (cdr (assoc :elements (cdr param)))) else if (and (listp param) (eq (car param) 'query)) append (elfeed-summary--get-feeds (cdr param)) - else if (and (listp param) (eq (car param) 'auto-tags) + else if (and (listp param) + (or (eq (car param) 'auto-tags) (eq (car param) 'tag-groups)) (eq (car-safe (alist-get :source (cdr param))) 'query)) append (elfeed-summary--get-feeds