[denote-citar intergration] 给citar.el添加根据title匹配library file 和notes的功能

RT,因为citar默认文件匹配规则要么是直接写在bib文件里面,要么文件名需要是bib文件里面对应entry 的citekey,而denote的命名规则里面文件名不含cite key,而一般包含title信息,所以有了这个hack。

利用denote创建新笔记的部分可以参考denote-ciar

(require 'denote)
(setq citar-templates
        '((main . "${author editor:30}    ${date year issued:4}    ${title:90}")
          (suffix . "    ${=key= id:15}    ${=type=:12}    ${volume keywords:*}")
          (preview . "${author editor} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.\n")
          (note . "Notes on ${author editor}, ${title}")))

  (add-to-list 'citar-file-sources '(:items citar-denote--get-library-files :hasitems citar-denote--has-library-files))

  (defun citar-denote--translate-citekey (citekey)
    (denote-sluggify
     (concat
      (citar-get-value "title" citekey)
      (when (member (citar-get-value "=type=" citekey) '("book" "mvbook"))
        (format " volume %s" (citar-get-value "volume" citekey))))))

  (defun citar-denote--has-library-files ()
    (lambda (citekey)
      (not (hash-table-empty-p (citar-denote--get-library-files (list citekey))))))

  (defun citar-denote--get-library-files (&optional keys)
    (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions)
    (citar-denote--directory-files
     citar-library-paths keys citar-library-file-extensions))

  (defun citar-denote--has-notes ()
    (lambda (citekey)
      (not (hash-table-empty-p (citar-denote--get-notes (list citekey))))))

  (defun citar-denote--get-notes (&optional keys)
    (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions)
    (citar-denote--directory-files
     citar-notes-paths keys citar-file-note-extensions))

  (defun citar-denote--directory-files (dirs &optional keys extensions)

    (let ((files (make-hash-table :test 'equal)))
      (prog1 files
        (dolist (dir dirs)
          (when (file-directory-p dir)
            (dolist (key keys)
              (let ((filenames (directory-files-recursively dir (citar-denote--translate-citekey key))))
                (dolist (filename filenames)
                  (let ((filename (expand-file-name filename dir)))
                    (when (and (file-exists-p filename) (member (file-name-extension filename) extensions))
                      (push filename (gethash key files)))))))))
        ;; Reverse file lists because push adds elements to the front
        (maphash (lambda (key filelist)
                   (puthash key (nreverse filelist) files))
                 files))))
2 个赞

新版本,稍微提升了一些性能,逻辑和citar内置函数更类似了,并加入了一个简单的从citar创建denote笔记的例子。

(with-eval-after-load 'citar
  (citar-embark-mode)
  (require 'denote)

  (setq citar-templates
        '((main . "${author editor:30}    ${date year issued:4}    ${title:90}")
          (suffix . "    ${=key= id:15}    ${=type=:12}    ${volume keywords:*}")
          (preview . "${author editor} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.\n")
          (note . "Notes on ${author editor}, ${title}")))

  (add-to-list 'citar-file-sources '(:items citar-denote--get-library-files :hasitems citar-denote--has-library-files))

  (defun citar-denote--translate-citekey (citekey)
    "Translate citekey into proper title."
    (concat
     (citar-get-value "title" citekey)
     (when (and (citar-get-value "volume" citekey) (member (citar-get-value "=type=" citekey) '("book" "mvbook")))
       (format " volume %s" (citar-get-value "volume" citekey)))))

  (defun citar-denote--translate-title (title &optional type volume)
    "Translate TITLE into denote style file name."
    (denote-sluggify
     (concat
      title
      (when (and volume (member type '("book" "mvbook")))
        (format " volume %s" volume)))))

  (defun citar-denote--has-library-files ()
    "Return predicate testing whether cite key has library files."
    (let ((files (citar-denote--get-library-files)))
      (unless (hash-table-empty-p files)
        (lambda (citekey)
          (and (gethash citekey files) t)))))

  (defun citar-denote--get-library-files (&optional keys)
    (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions)
    (citar-denote--directory-files
     citar-library-paths keys citar-library-file-extensions))

  (defun citar-denote--has-notes ()
    "Return predicate testing whether cite key has library files."
    (let ((files (citar-denote--get-notes)))
      (unless (hash-table-empty-p files)
        (lambda (citekey)
          (and (gethash citekey files) t)))))

  (defun citar-denote--get-notes (&optional keys)
    (citar--check-configuration 'citar-library-paths 'citar-library-file-extensions)
    (citar-denote--directory-files
     citar-notes-paths keys citar-file-note-extensions))

  (defun citar-denote--directory-files (dirs &optional keys extensions)
    "Check corresponding files in DIR by KEYS, filtered by EXTENSIONS."
    (let ((files (make-hash-table :test 'equal))
          (keys (if keys keys (hash-table-keys (citar-get-entries)))))
      (prog1 files
        (dolist (dir dirs)
          (when (file-directory-p dir)
            (dolist (key keys)
              (let* ((entry (gethash key (citar-get-entries)))
                     (type (cdr (assoc "=type=" entry)))
                     (title (cdr (assoc "title" entry)))
                     (volume (cdr (assoc "volume" entry)))
                     (filenames (directory-files-recursively
                                 dir
                                 (citar-denote--translate-title title type volume))))
                (dolist (filename filenames)
                  (when (and (file-exists-p filename) (member (file-name-extension filename) extensions))
                    (push filename (gethash key files))))))))
        ;; Reverse file lists because push adds elements to the front
        (maphash (lambda (key filelist)
                   (puthash key (nreverse filelist) files))
                 files))))

  (defun citar-denote--keywords-prompt ()
    "Prompt for one or more keywords and include `denote-citar-keyword'."
    (let ((choice (denote--keywords-crm (denote-keywords))))
      (if denote-sort-keywords
          (sort choice #'string-lessp)
        choice)))

  (defun citar-denote-file--create-note (key &optional entry)
    "Create a bibliography note through Citar."
    (let ((denote-file-type 'org)) ; make sure it is Org
      (denote
       (citar-denote--translate-citekey key)
       (citar-denote--keywords-prompt))
      ;; The `denote-last-buffer' is the one we just created with `denote'.
	  (goto-char (point-max))))

  (setq citar-notes-sources
        `((citar-file .
                      ,(list :name "Notes"
                             :category 'file
                             :items #'citar-denote--get-notes
                             :hasitems #'citar-denote--has-notes
                             :open #'find-file
                             :create #'citar-denote-file--create-note
                             :transform #'file-name-nondirectory)))))