Subject: Re: bug#44597: 26.3; bibtex should allow reverse sorting

From: Francesco Potortì <[email-masked]>
Date: Sun, 06 Dec 2020 17:16:42 +0100

>> Ideally, bibtex.el should provide a hook where one can install any
>> function for sorting.  The hook should get some arguments (the type of
>> entry, the name of entry, the date, a bibtex custom field and maybe
>> others).  That would make bibtex.el more generally flexible.
>
>Yes, I think introducing a variable to control the sorting would be a
>good idea.  It'd default to `bibtex-lessp' (or rather, something that
>does the same), but (as you say) have an input that makes implementing
>other sorting functions easier.
>
>Patches welcome.  :-)

This is a patch that improves things, in my opinion.  This is not the
ideal situation I mentioned above, but "il meglio è il nemico del bene"
(best is enemy of good).  So here you are, this one satisfies my needs
and is hoefully a step towards a more general solution.

diff -pub /home/pot/elisp/bibtex-trunk.el /home/pot/elisp/bibtex.el
--- /home/pot/elisp/bibtex-trunk.el	2020-12-03 18:52:11.000000000 +0100
+++ /home/pot/elisp/bibtex.el	2020-12-06 17:02:06.000000000 +0100
@@ -202,7 +202,10 @@ narrowed to just the entry."
 (defcustom bibtex-maintain-sorted-entries nil
   "If non-nil, BibTeX mode maintains all entries in sorted order.
 Allowed non-nil values are:
-plain or t   All entries are sorted alphabetically.
+plain or t   Entries are sorted alphabetically.
+reverse      Entries are sorted in reverse alphabetic order.
+date         Entries are sorted by date.
+reversedate  Entries are reverse sorted by date, starting with newest.
 crossref     All entries are sorted alphabetically unless an entry has a
              crossref field.  These crossrefed entries are placed in
              alphabetical order immediately preceding the main entry.
@@ -213,10 +216,13 @@ See also `bibtex-sort-ignore-string-entr
   :group 'bibtex
   :type '(choice (const nil)
                  (const plain)
+		 (const reverse)
+		 (const date)
+		 (const reversedate)
                  (const crossref)
                  (const entry-class)
                  (const t))
-  :safe (lambda (a) (memq a '(nil t plain crossref entry-class))))
+  :safe (lambda (a) (memq a '(nil t plain reverse date reversedate crossref entry-class))))
 
 (defcustom bibtex-sort-entry-class
   '(("String")
@@ -3993,23 +3999,25 @@ If mark is active count entries in regio
 
 (defun bibtex-entry-index ()
   "Return index of BibTeX entry head at or past position of point.
-The index is a list (KEY CROSSREF-KEY ENTRY-TYPE) that is used for sorting
-the entries of the BibTeX buffer.  CROSSREF-KEY is nil unless the value
-of `bibtex-maintain-sorted-entries' is `crossref'.  Move point to the end
-of the head of the entry found.  Return nil if no entry found."
+The index is a list (KEY CROSSREF-KEY ENTRY-TYPE DATE) that is
+used for sorting the entries of the BibTeX buffer.  CROSSREF-KEY
+is nil unless the value of `bibtex-maintain-sorted-entries' is
+`crossref'.  Move point to the end of the head of the entry
+found.  Return nil if no entry found."
   (let ((case-fold-search t))
     (if (re-search-forward bibtex-entry-maybe-empty-head nil t)
         (let ((key (bibtex-key-in-head))
               ;; all entry types should be downcase (for ease of comparison)
-              (entry-type (downcase (bibtex-type-in-head))))
+	      (crossref-key nil)
+              (entry-type (downcase (bibtex-type-in-head)))
+	      (date (bibtex-get-date)))
           ;; Don't search CROSSREF-KEY if we don't need it.
-          (if (eq bibtex-maintain-sorted-entries 'crossref)
+          (when (eq bibtex-maintain-sorted-entries 'crossref)
               (let ((bounds (bibtex-search-forward-field
                              "\\(OPT\\)?crossref" t)))
-                (list key
-                      (if bounds (bibtex-text-in-field-bounds bounds t))
-                      entry-type))
-            (list key nil entry-type))))))
+              (when bounds
+		(setq crossref-key (bibtex-text-in-field-bounds bounds t)))))
+          (list key crossref-key entry-type date)))))
 
 (defun bibtex-init-sort-entry-class-alist ()
   "Initialize `bibtex-sort-entry-class-alist' (buffer-local)."
@@ -4024,6 +4032,28 @@ of the head of the entry found.  Return
                      alist)))
            alist))))
 
+(defun bibtex-get-date (&optional latest)
+  "Return date of current entry as a string in yyyy-mm-dd format.
+If any part of date is missing, use the earliest possible missing
+value, unless LATEST is not nil, in which case use the latest possible
+missing value."
+  (let ((date (bibtex-autokey-get-field "date")))
+    (if (string> date "")
+	date
+      (let ((year (bibtex-autokey-get-field "year"))
+	    (month (bibtex-autokey-get-field
+		    "month" '(("^jan.*". "01") ("^feb.*". "02") ("^mar.*". "03")
+			      ("^apr.*". "04") ("^may.*". "05") ("^jun.*". "06")
+			      ("^jul.*". "07") ("^aug.*". "08") ("^sep.*". "09")
+			      ("^oct.*". "10") ("^nov.*". "11") ("^dec.*". "12"))))
+	    (day (bibtex-autokey-get-field "days" '(("-.*" . "")))))
+	(when (string= year "") (setq year (if latest "9999" "1900")))
+	(when (string= month "") (setq month (if latest "12" "01")))
+	(when (string= day "") (setq day (if latest "31" "01")))
+	(concat year "-" month "-" (if (= (string-width day) 1)
+				       (concat "0" day)
+				     (substring day 0 2)))))))
+
 (defun bibtex-lessp (index1 index2)
   "Predicate for sorting BibTeX entries with indices INDEX1 and INDEX2.
 Each index is a list (KEY CROSSREF-KEY ENTRY-TYPE).
@@ -4056,6 +4086,12 @@ If its value is nil use plain sorting."
            (or (< n1 n2)
                (and (= n1 n2)
                     (string-lessp (car index1) (car index2))))))
+	((eq bibtex-maintain-sorted-entries 'reversedate)
+	 (string-lessp (nth 3 index2) (nth 3 index1)))
+	((eq bibtex-maintain-sorted-entries 'date)
+	 (string-lessp (nth 3 index1) (nth 3 index2)))
+	((eq bibtex-maintain-sorted-entries 'reverse)
+	 (string-lessp (car index2) (car index1)))
         (t ; (eq bibtex-maintain-sorted-entries 'plain)
          (string-lessp (car index1) (car index2)))))