【存档】Org所指属性


缓存依赖

考虑这样一种场景:

有属性 #'create-time, 然 #'create-time 返回的是 Org时间戳 字符串。如果我们想基于 #'create-time 做排序,那么我们必须解析其返回的 Org时间戳 字符串。当数据量特别大时,解析时间戳将占据可观的执行时间。为此,我们可能更倾向于定义一个已经转换好的时间戳属性,比如: #'create-time%float-time, 它先通过 #'create-time 获取 Org时间戳 字符串,然后将时间戳解析并转换为浮点类型。

这时,我们说 #'create-time%float-time 依赖 #'create-time. 我们希望当 #'create-time 改变时, #'create-time%float-time 也能同步改变,或将 #'create-time%float-time 的缓存置于 失效 状态,以便下次获取其属性值时能够先更新其缓存。

为此,我们于缓存项结构中加入 dependent域——一个从属属性集。当某属性的缓存更新时,其 dependent域 中的从属属性的缓存会被置为失效状态,以致获取这些从属属性的值时将触发缓存更新流程。

一个 PROPERTY 可通过如下方式设置其依赖项:

(org-referent-get 'set PROPERTY :depends '(P1 P2 ...))

依赖项 P1, P2, … 中任意属性的更新都将导致 PROPERTY 的缓存失效。

缓存依赖的实现细节:

给某个属性添加从属属性 (add-dependent):

#+name: 2025-08-23-16-51
#+begin_src emacs-lisp :eval no
;; depends: get-cache, log.
(lambda (link base prop)
  (and-let* ((C (get-cache link base t)))
   (unless (memq prop (nth 1 C))
     (setf (nth 1 C) `(,@(nth 1 C) ,prop))
     (log "  %S dependent %S" base (nth 1 C)))))
#+end_src

将某个属性与其依赖项关联 (setup-dependencies):

#+name: 2025-08-23-16-52
#+begin_src emacs-lisp :eval no
;; depends: add-dependent, log.
(lambda (link prop)
  (dolist (dep (get prop 'org-referent-get-depends))
    (log "  depend on: %s" dep)
    (add-dependent link dep prop)))
#+end_src

将某个属性所有的从属属性的缓存置为失效状态 (invalidate-dependent):

#+name: 2025-08-23-16-53
#+begin_src emacs-lisp :eval no
;; depends: cache, log.
(lambda (link property)
  (and-let* ((P (gethash link cache))
             (C (gethash property P))
             (depends 'org-referent-get-depends))
    ;; 移除可能无效的 PROPERTIES.
    (setf (nth 1 C)
          (seq-remove
           (lambda (p)
             (or (not (symbolp p))
                 (not (functionp p))
                 (not (member property
                              (get p depends)))))
           (nth 1 C)))
    (log "  %S dependent: %S" property (nth 1 C))
    ;; 使 PROPERTIES 缓存失效.
    (dolist (p (nth 1 C))
      (when (gethash p P)
        (setf (gethash p P) nil)))))
#+end_src

间接属性值

缓存持久化中所述,某些属性值可能无法被持久化,比如 marker. marker 本身无法持久化,但它可以通过 file 加 point 的方式间接持久化,然后通过 无参lambda调用 将 (file point) 转化为实际的 marker.

以代码说明,下面的 PROPERTY 取值函数在调用时并未直接返回 marker, 而是返回一个捕获了 file 和 point 的 无参lambda:

#+name: 2025-08-23-16-57
#+begin_src emacs-lisp :eval no
(org-N-defprop location
  ;; 因为 marker 无法被持久化,这里我们做些
  ;; hacking. 当此 lambda 被调用时,我们
  ;; 用 point 和 file 捕获当前位置。
  (lambda nil
    (let ((point (point))
          (file (buffer-file-name)))
      (lambda nil
        (set-marker
         (make-marker) point
         (find-file-noselect file)))))
  :apply t)
#+end_src

为处理这类无法持久化的属性, org-referent-get 有一个 间接属性值 机制,其原理如所上述:将属性值转化为可持久化的 无参lambda + 执行环境。当获取这类间接属性值时, org-referent-get 会对其 求值,并返回其返回值作为实际的属性值。

一个 PROPERTY 可通过如下方式启用间接属性值:

(org-referent-get 'set PROPERTY :apply t)

间接属性值的实现细节 (compute-value):

#+name: 2025-08-23-16-58
#+begin_src emacs-lisp :eval no
(lambda (prop val)
  (!let ((apply 'org-referent-get-apply)
         (val val))
   (cond
    ((or (not (symbolp prop))
         (null (setq apply (get prop apply))))
     val)
    ((functionp apply) (apply val))
    (t (val)))))
#+end_src

属性配置接口

PROPERTY 的配置(如缓存依赖、间接属性值等)接口汇总:

(org-referent-get 'set PROPERTY :base P)
(org-referent-get 'set PROPERTY :depends '(P1 P2 ...))
(org-referent-get 'set PROPERTY :apply t)

其实现为 (do-conf):

#+name: 2025-08-23-17-03
#+begin_src emacs-lisp :eval no
;; depends: log.
(lambda (op property &rest kargs)
  (pcase op
    ('set
     (when (symbolp property)
       (when (plist-get kargs :base)
         (put property 'org-referent-get-base
              (plist-get kargs :base))
         (log "%s base %s" property
              (plist-get kargs :base)))
       (when (plist-get kargs :depends)
         (put property 'org-referent-get-depends
              (plist-get kargs :depends))
         (log "%s depends %s" property
              (plist-get kargs :depends)))
       (when (plist-get kargs :apply)
         (put property 'org-referent-get-apply
              (plist-get kargs :apply))
         (log "%s apply %s" property
              (plist-get kargs :apply)))))))
#+end_src

取值逻辑

org-referent-get 涉及链接定位、属性取值、属性值缓存,其中,链接定位与属性取值相关的逻辑由取值逻辑 (get-prop) 处理。当 org-referent-get 找不到属性的缓存值 或 被配置为需要强制更新缓存 或 被配置为不使用缓存时,执行流将进入此分支。

PROPERTY 有三种取值策略: string 类 PROPERTY 通过 org-entry-get 取值; symbol 类 PROPERTY 依据其 org-referent-get-base 属性的有无分为两类:无则直接调用 PROPERTY 取值;有则基于 org-referent-get-base 中的 PROPERTY 取值。

取值逻辑 (get-prop):

#+name: 2025-08-23-17-04
#+begin_src emacs-lisp :eval no
;; self:    get-prop
;; depends: put-cache, locate, bind-value,
;;          setup-dependencies, log.
(lambda (link prop &rest kargs)
  (!let ((no-cache (or (plist-get kargs :no-cache)
                       (null link)))
         (P prop) (L link)
         (B 'org-referent-get-base)
         (error (lambda nil
                  (error "Invalid property: %S"
                         prop))))
   (when no-cache (log "  %S no cache" P))
   (org-with-point-at
       (or (plist-get kargs :epom)
           (and L (always (log "  %S locate" P))
                (locate L t)))
     ;; 置 :epom 以便后续递归调用时无需再定位。
     (plist-put kargs :epom (point-marker))
     (cond
      ;; prop 是 string, 代表 Org属性 的字符串。
      ((stringp P)
       (!let ((v (org-entry-get nil P)))
        (if no-cache v (put-cache L P v))))
      ;; prop 是 lambda.
      ((not (symbolp P))
       (if (functionp P) (P) (error)))
      ;; prop 是 symbol.
      ((ignore (setup-dependencies L P)))
      ;; prop 无 base.
      ((null (setq B (get P B)))
       (unless (functionp P) (error))
       (if no-cache (P) (put-cache L P (P))))
      ;; prop 有 base.
      ((!let ((v (apply get-prop L B kargs)))
        (cond
         (no-cache v)
         ((or (stringp B) (symbolp B))
          (bind-value L P B))
         ((put-cache L P v)))))))))
#+end_src

文档

(org-referent-get &optinal LINK PROPERTY &rest KARGS)

#+name: 2025-08-23-17-08
#+begin: elisp-docstring

定位并跳转到链接 LINK 所指位置,于该处获取属性 PROPERTY
的属性值并缓存备用。

LINK 通常为 string. PROPERTY 可以是 string, lambda,
symbol, nil.

LINK 为 string 时表示一条 Org链接,如 “id:x”,
“[[id:x]]”; 其他情况见后续。

PROPERTY 为 string 时, ‘org-referent-get’ 通过
(org-entry-get nil PROPERTY) 获取属性值,支持缓存;

PROPERTY 为 lambda 时, ‘org-referent-get’ 通过
(funcall PROPERTY) 获取属性值,不支持缓存;

PROPERTY 为 symbol 时, ‘org-referent-get’ 通过
(funcall PROPERTY) 获取属性值,支持缓存。

若 PROPERTY symbol 有 'org-referent-get-base 属性
(后简称 base), base 可以是另一个 property, 或一个
无参lambda, PROPERTY 将通过该 base 获取其属性值。

KARGS

:epom 所指位置,默认 nil.
:force 强制更新缓存标志,默认 nil.
:no-cache 禁用缓存标志,默认 nil.
:base 将属性值绑定至 BASE 属性值,默认 nil.
:depends 设置属性依赖项,默认 nil.
:apply 启用间接属性值,默认 nil.

‘org-referent-get’涉及链接定位、属性取值、属性值缓存。
通过 LINK 及 某些 KARGS 可控制其执行行为,具体如下表
所示 (配置的优先及由高到低排列,PROPERTY 非 nil):

LINK    :epom    :force :no-cache
non-nil  non-nil  _      _        取值/缓存
nil      non-nil  _      _        取值
non-nil  nil      nil    nil      取缓存值
non-nil  nil      t      nil      定位/取值/缓存
non-nil  nil      _      t        定位/取值
nil      nil      _      _        取值

其中,‘_’表示忽略该标志。
取值:于当前位置,通过 PROPERTY, 取属性值。
定位:定位并跳转至 LINK 所指位置(费时操作)。
缓存:缓存所取属性值。

返回值及其他用法

正常情况下,返回 LINK 合 PROPERTY 所指的属性值;但根据
输入参数的状态,还可细分:属性配置;缓存更新与重置;命令模
式等用法,有区别于返回属性值。

属性配置

(org-referent-get 'set P0 :base P1)
=> 使 P0 基于 P1, P0 属性缓存值的将与 P1 保持同步。

(org-referent-get 'set P0 :depends '(P1 ...))
=> 使 P0 依赖 P1 等,任一属性的变化将导致 P0 缓存失效。

(org-referent-get 'set P0 :apply t)
=> 使 P0 为间接属性值,其属性值的调用返回值将作为属性值。

缓存更新与重置

(org-referent-get LINK)
=> 返回 LINK 所有属性 PROPERTY.

(org-referent-get LINK nil :force t)
=> 强制更新 LINK 所有的属性缓存值。

(org-referent-get LINK nil :no-cache t)
=> 删除 LINK 所有的属性缓存值。

(org-referent-get nil nil :force t)
=> 强制更新所有的属性缓存值。

(org-referent-get nil nil :no-cache t)
=> 重置所有的属性缓存值。

其他

M-x org-referent-get => 命令模式

(org-referent-get) => 返回内部缓存用的哈希表。

#+end:


入口

org-referent-get:

#+name: 2025-08-23-17-13
#+begin_src emacs-lisp :eval no :noweb tangle
(lambda (&optional link property &rest kargs)
  "Org (Link) Referent Property.

<<@([[id:org-referent-get::doc:org-referent-get]])>>"
  (interactive)
  ;; 保证 kargs 非 nil, 以免后续 plist-put 无效。
  (unless kargs
    (setq kargs (plist-put kargs :_ nil)))
  (when (or (markerp link) (integerp link))
    (plist-put kargs :epom link)
    (setq link nil))
  (cond
   ;; 缓存控制及命令模式
   ((and (null link) (null property))
    (cond
     ((plist-get kargs :force) (update-cache))
     ((plist-get kargs :no-cache) (reset-cache))
     ((called-interactively-p 'interactively)
      (do-cmd))
     (t cache)))
   ;; 单条 LINK 的缓存控制
   ((null property)
    (cond
     ((plist-get kargs :force)
      (update-cache `(,link)))
     ((plist-get kargs :no-cache)
      (reset-cache `(,link)))
     ((and-let* ((hash (gethash link cache)))
        (hash-table-keys hash)))))
   ;; 控制命令
   ((and link (symbolp link))
    (apply do-conf link property kargs))
   ((ignore
     (when (or (not (or (symbolp property)
                        (stringp property)))
               (null link))
       (plist-put kargs :no-cache t))))
   ;; 获取属性值
   ((or (plist-get kargs :epom)
        (plist-get kargs :no-cache))
    (log "link: %s\n  kargs: %S" link kargs)
    (!let ((v (apply get-prop link property kargs)))
     (setq v (compute-value property v))
     (log "  %.40S value: %S" property v)
     v))
   ;; 获取属性缓存值
   ((!let* ((P (or (gethash link cache)
                   (make-hash-table :test #'equal)))
            (C (get-cache link property t))
            (v (car (last C))))
     (log "link: %s\n  kargs: %S" link kargs)
     (when (or (null C) (plist-get kargs :force))
       (setq v (apply get-prop link property kargs)))
     (setq v (compute-value property v))
     (log "  %.40S value: %S" property v)
     v))))
#+end_src

内部符号

org-referent-get 内部符号,其中, cache 为缓存用的哈希表。

#+name: 2025-08-23-17-15
#+begin_src emacs-lisp :eval no
do-cmd do-conf
cache update-cache reset-cache
get-cache put-cache get-prop
bind-value compute-value
add-dependent setup-dependencies
invalidate-dependent
(locate (make-symbol "locate"))
(log (lambda (&rest _)))
#+end_src

整体结构

整体结构:

#+name: 2025-08-23-17-16
#+begin_src emacs-lisp :eval no :noweb tangle
;;; org-referent-get  -*- lexical-binding: t; -*-
(!def 'org-referent-get
;;;; private
 (!let (org-referent-get
        <<@([[id:org-referent-get::private]])>>)

;;;; org-referent-get
  (put 'org-referent-get 'lisp-indent-function 'defun)
  (!def org-referent-get
   <<@([[id:org-referent-get::org-referent-get]])>>)

<<@([[id:org-referent-get::api-related]])>>

;;;; get-cache
  (!def get-cache
   <<@([[id:org-referent-get::get-cache]])>>)

;;;; put-cache
  (!def put-cache
   <<@([[id:org-referent-get::put-cache]])>>)

;;;; get-prop
  (!def get-prop
   <<@([[id:org-referent-get::get-prop]])>>)

;;;; locate
  (!def locate
   <<@([[id:org-referent-get::locate]])>>)

;;;; log
  (!def log
   <<@([[id:org-referent-get::log]])>>)
  (!def log (log " log:org-referent-get"))

<<@([[id:org-referent-get::internal]])>>

;;;; end
  ;; 延迟加载
  <<@([[id:org-referent-get::delay-load]])>>))
#+end_src

API相关:

#+name: 2025-08-23-17-17
#+begin_src emacs-lisp :eval no :noweb tangle
;;;; do-cmd
  (!def do-cmd
   <<@([[id:org-referent-get::do-cmd]])>>)

;;;; do-conf
  (!def do-conf
   <<@([[id:org-referent-get::do-conf]])>>)

;;;; update-cache
  (!def update-cache
   <<@([[id:org-referent-get::update-cache]])>>)

;;;; reset-cache
  (!def reset-cache
   <<@([[id:org-referent-get::reset-cache]])>>)
#+end_src

特性相关:

#+name: 2025-08-23-17-18
#+begin_src emacs-lisp :eval no :noweb tangle
;;;; bind-value
  (!def bind-value
   <<@([[id:org-referent-get::bind-value]])>>)

;;;; compute-value
  (!def compute-value
   <<@([[id:org-referent-get::compute-value]])>>)

;;;; add-dependent
  (!def add-dependent
   <<@([[id:org-referent-get::add-dependent]])>>)

;;;; setup-dependencies
  (!def setup-dependencies
   <<@([[id:org-referent-get::setup-dependencies]])>>)

;;;; invalidate-dependent
  (!def invalidate-dependent
   <<@([[id:org-referent-get::invalidate-dependent]])>>)
#+end_src

附:新版 locate 与 log

locate, 新增 signal-errors:

#+name: 2025-08-23-17-20
#+begin_src emacs-lisp :eval no
(lambda (link &optional signal-errors)
  (let ((inhibit-message t)
        (message-log-max nil)
        (org-link-search-must-match-exact-headline t)
        (org-link-frame-setup
         `((file . find-file-other-window)
    	   ,@org-link-frame-setup))
        (marker (make-marker)))
    (condition-case msg
        (save-window-excursion
          ;; 这个 guard 实际只对当前 buffer 有效,
          ;; 无法处理 open-link 跑到别的 buffer 的
          ;; 情况。但考虑到有时我们会 open 当前
          ;; buffer 中的 link, 为了防止因为可见性引
          ;; 发的链接查找失败,我们还是在这里加上这
          ;; 个 gaurd, 当然,也许还有更好的实现方法,
          ;; 但到时再说。
          (org-with-wide-buffer
           (if (not (stringp link))
               (org-link-open link t)
             (org-link-open-from-string link t))
           (set-marker marker (point))))
      (error
       (when signal-errors
         (signal (car msg) (cdr msg)))))))
#+end_src

log, 多行日志时间戳拼接:

#+name: 2025-08-23-17-21
#+begin_src emacs-lisp :eval no
(lambda (log-target)
  (lambda (fmt &rest args)
    (when debug-on-error
      (let* ((ts (format-time-string
                  "[%Y-%m-%d %H:%M:%S.%3N]"))
             (buf (get-buffer-create
                   log-target)))
        (with-current-buffer buf
          (goto-char (point-max)))
        (princ
         (concat
          (mapconcat
           (lambda (line) (concat ts line))
           (string-split
            (apply #'format fmt args) "\n")
           "\n")
          "\n")
         buf)))))
#+end_src

构建:Org所指属性

映射表

#+name: 2025-08-23-21-14
#+begin_src emacs-lisp :eval no
"org-referent-get"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-16"
"org-referent-get::private"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-15"

"org-referent-get::doc:org-referent-get"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-08"
"org-referent-get::org-referent-get"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-13"

"org-referent-get::api-related"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-17"
"org-referent-get::do-cmd"
"https://emacs-china.org/t/org/29965::2025-08-23-16-41"
"org-referent-get::do-conf"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-03"
"org-referent-get::update-cache"
"https://emacs-china.org/t/org/29965::2025-08-23-16-43"
"org-referent-get::reset-cache"
"https://emacs-china.org/t/org/29965::2025-08-23-16-44"

"org-referent-get::get-cache"
"https://emacs-china.org/t/org/29965::2025-08-23-16-48"
"org-referent-get::put-cache"
"https://emacs-china.org/t/org/29965::2025-08-23-16-49"
"org-referent-get::get-prop"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-04"

"org-referent-get::locate"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-20"
"org-referent-get::log"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-21"
"org-referent-get::delay-load"
"https://emacs-china.org/t/org/29965::2025-08-23-16-38"

"org-referent-get::internal"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-17-18"
"org-referent-get::bind-value"
"https://emacs-china.org/t/org/29965::2025-08-23-16-46"
"org-referent-get::compute-value"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-16-58"
"org-referent-get::add-dependent"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-16-51"
"org-referent-get::setup-dependencies"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-16-52"
"org-referent-get::invalidate-dependent"
"https://emacs-china.org/t/org/29965?page=2::2025-08-23-16-53"
#+end_src

构建目标

#+name: 2025-08-23-21-15
#+begin_src emacs-lisp :eval no :noweb tangle
<<@([[id:org-referent-get]])>>
#+end_src

构建入口

#+name: 2025-08-15-15-42
#+header: :var tangle=(ignore) load=(ignore) conf-only="no"
#+begin_src emacs-lisp :results silent
(org-id-remap 'reset)
(org-id-remap t)
(org-exec
  "[[https://emacs-china.org/t/org-id-remap/29814::2025-08-03-11-27]]" nil
  :eval "yes"
  'target "[[https://emacs-china.org/t/org/29965?page=2::2025-08-23-21-15]]"
  'map-table ''("[[https://emacs-china.org/t/org/29965?page=2::2025-08-23-21-14]]")
  'tangle (or tangle "~/org/org-referent-get.el")
  'load (or load "yes")
  'conf-only conf-only)
#+end_src

构建: All in one版本

映射表

#+name: 2025-08-23-11-29
#+begin_src emacs-lisp :eval no
;; org-id-select
<<@([[https://emacs-china.org/t/org/29965::2025-08-23-10-36]])>>
;; org-N
<<@([[https://emacs-china.org/t/org/29965::2025-08-23-11-19]])>>
;; org-referent-get
<<@([[https://emacs-china.org/t/org/29965/30::2025-08-23-21-14]])>>
#+end_src

构建目标

#+name: 2025-08-23-11-30
#+begin_src emacs-lisp :eval no :noweb yes
;;; -*- lexical-binding: t; -*-

;;; Code:

;; dependencies
(require 'seq)
(require 'cl-lib)
(require 'cl-macs)
(require 'org)
(require 'org-clock)

<<@([[https://emacs-china.org/t/topic/29891::2025-08-02-11-33]])>>

<<@([[https://emacs-china.org/t/topic/29891::2025-08-02-11-32]])>>

<<@([[id:org-referent-get]])>>

<<@([[https://emacs-china.org/t/org/29965::2025-08-23-11-20]])>>

<<@([[https://emacs-china.org/t/org/29965::2025-08-23-11-03]])>>

<<@([[id:org-id-select]])>>

;;; ends here
#+end_src

构建入口

#+name: 2025-08-23-11-31
#+header: :var tangle=(ignore) load=(ignore) conf-only="no"
#+begin_src emacs-lisp :results silent :noweb yes
(org-id-remap 'reset)
(org-id-remap t)
(org-exec
  "[[https://emacs-china.org/t/org-id-remap/29814::2025-08-03-11-27]]" nil
  :eval "yes"
  'target "[[https://emacs-china.org/t/org/29965?page=2::2025-08-23-11-30]]"
  'map-table ''("[[https://emacs-china.org/t/org/29965?page=2::2025-08-23-11-29]]")
  'tangle (or tangle "~/org/2025-08-23-11-30.el")
  'load (or load "no")
  'conf-only conf-only)
#+end_src

Release 2025-08-23-21-55, 非最新版本。

By:

(org-exec "[[https://emacs-china.org/t/org/29965/31::2025-08-23-11-31]]")
;;; -*- lexical-binding: t; -*-

;;; Code:

;; dependencies
(require 'seq)
(require 'cl-lib)
(require 'cl-macs)
(require 'org)
(require 'org-clock)

(defmacro !let (bindings &rest body)
  (declare
   (indent
    (lambda (p s)
      (save-excursion
        (goto-char (car (last (nth 9 s))))
        (1+ (current-column))))))
  (cond
   ((null bindings) `(progn ,@body))
   (t
    (let (vars vals)
      (mapc
       (lambda (binding)
         (push (or (car-safe binding) binding) vars)
         (push (car (cdr-safe binding)) vals))
       bindings)
      (setq vars (nreverse vars))
      (setq vals (nreverse vals))
      `(funcall
        (lambda (,@vars)
          (cl-macrolet
              ,(mapcar
                (lambda (s)
                  `(,s (&rest args)
                       `(funcall
                         ;;,',s
                         (or (and (functionp ,',s) ,',s)
                             (function ,',s))
                         ,@args)))
                vars)
            ,@body))
        ,@vals)))))

(defmacro !let* (bindings &rest body)
  (declare
   (indent
    (lambda (p s)
      (save-excursion
        (goto-char (car (last (nth 9 s))))
        (1+ (current-column))))))
  (if (null bindings) `(progn ,@body)
    (setq bindings (reverse bindings))
    (while bindings
      (setq body (list `(!let (,(pop bindings))
                         ,@body))))
    (car body)))

(defmacro !def (sym val)
  (declare
   (indent
    (lambda (p s)
      (save-excursion
        (goto-char (car (last (nth 9 s))))
        (1+ (current-column))))))
  `(!let ((val ,val))
    (if (ignore-errors
          (and ,sym (symbolp ,sym) (functionp val)))
        (defalias ,sym val)
      (setq ,sym val))))

;;; org-referent-get  -*- lexical-binding: t; -*-
(!def 'org-referent-get
;;;; private
 (!let (org-referent-get
        do-cmd do-conf
        cache update-cache reset-cache
        get-cache put-cache get-prop
        bind-value compute-value
        add-dependent setup-dependencies
        invalidate-dependent
        (locate (make-symbol "locate"))
        (log (lambda (&rest _))))

;;;; org-referent-get
  (put 'org-referent-get 'lisp-indent-function 'defun)
  (!def org-referent-get
   (lambda (&optional link property &rest kargs)
     "Org (Link) Referent Property.
   
定位并跳转到链接 LINK 所指位置,于该处获取属性 PROPERTY
的属性值并缓存备用。

LINK 通常为 string. PROPERTY 可以是 string, lambda,
symbol, nil.

LINK 为 string 时表示一条 Org链接,如 “id:x”,
“[[id:x]]”; 其他情况见后续。

PROPERTY 为 string 时, ‘org-referent-get’ 通过
(org-entry-get nil PROPERTY) 获取属性值,支持缓存;

PROPERTY 为 lambda 时, ‘org-referent-get’ 通过
(funcall PROPERTY) 获取属性值,不支持缓存;

PROPERTY 为 symbol 时, ‘org-referent-get’ 通过
(funcall PROPERTY) 获取属性值,支持缓存。

若 PROPERTY symbol 有 \\='org-referent-get-base 属性
(后简称 base), base 可以是另一个 property, 或一个
无参lambda, PROPERTY 将通过该 base 获取其属性值。

KARGS

:epom 所指位置,默认 nil.
:force 强制更新缓存标志,默认 nil.
:no-cache 禁用缓存标志,默认 nil.
:base 将属性值绑定至 BASE 属性值,默认 nil.
:depends 设置属性依赖项,默认 nil.
:apply 启用间接属性值,默认 nil.

‘org-referent-get’涉及链接定位、属性取值、属性值缓存。
通过 LINK 及 某些 KARGS 可控制其执行行为,具体如下表
所示 (配置的优先及由高到低排列,PROPERTY 非 nil):

LINK    :epom    :force :no-cache
non-nil  non-nil  _      _        取值/缓存
nil      non-nil  _      _        取值
non-nil  nil      nil    nil      取缓存值
non-nil  nil      t      nil      定位/取值/缓存
non-nil  nil      _      t        定位/取值
nil      nil      _      _        取值

其中,‘_’表示忽略该标志。
取值:于当前位置,通过 PROPERTY, 取属性值。
定位:定位并跳转至 LINK 所指位置(费时操作)。
缓存:缓存所取属性值。

返回值及其他用法

正常情况下,返回 LINK 合 PROPERTY 所指的属性值;但根据
输入参数的状态,还可细分:属性配置;缓存更新与重置;命令模
式等用法,有区别于返回属性值。

属性配置

(org-referent-get \\='set P0 :base P1)
=> 使 P0 基于 P1, P0 属性缓存值的将与 P1 保持同步。

(org-referent-get \\='set P0 :depends \\='(P1 ...))
=> 使 P0 依赖 P1 等,任一属性的变化将导致 P0 缓存失效。

(org-referent-get \\='set P0 :apply t)
=> 使 P0 为间接属性值,其属性值的调用返回值将作为属性值。

缓存更新与重置

(org-referent-get LINK)
=> 返回 LINK 所有属性 PROPERTY.

(org-referent-get LINK nil :force t)
=> 强制更新 LINK 所有的属性缓存值。

(org-referent-get LINK nil :no-cache t)
=> 删除 LINK 所有的属性缓存值。

(org-referent-get nil nil :force t)
=> 强制更新所有的属性缓存值。

(org-referent-get nil nil :no-cache t)
=> 重置所有的属性缓存值。

其他

M-x org-referent-get => 命令模式

(org-referent-get) => 返回内部缓存用的哈希表。"
     (interactive)
     (cond
      ;; 缓存控制及命令模式
      ((and (null link) (null property))
       (cond
        ((plist-get kargs :force) (update-cache))
        ((plist-get kargs :no-cache) (reset-cache))
        ((called-interactively-p 'interactively)
         (do-cmd))
        (t cache)))
      ;; 单条 LINK 的缓存控制
      ((null property)
       (cond
        ((plist-get kargs :force)
         (update-cache `(,link)))
        ((plist-get kargs :no-cache)
         (reset-cache `(,link)))
        ((and-let* ((hash (gethash link cache)))
           (hash-table-keys hash)))))
      ;; 控制命令
      ((and link (symbolp link))
       (apply do-conf link property kargs))
      ((ignore
        (when (or (not (or (symbolp property)
                           (stringp property)))
                  (null link))
          (plist-put kargs :no-cache t))))
      ;; 获取属性值
      ((or (plist-get kargs :epom)
           (plist-get kargs :no-cache))
       (log "link: %s\n  kargs: %S" link kargs)
       (!let ((v (apply get-prop link property kargs)))
        (setq v (compute-value property v))
        (log "  %S value: %S" property v)
        v))
      ;; 获取属性缓存值
      ((!let* ((P (or (gethash link cache)
                      (make-hash-table :test #'equal)))
               (C (get-cache link property t))
               (v (car (last C))))
        (log "link: %s\n  kargs: %S" link kargs)
        (when (or (null C) (plist-get kargs :force))
          (setq v (apply get-prop link property kargs)))
        (setq v (compute-value property v))
        (log "  %S value: %S" property v)
        v)))))

;;;; do-cmd
  (!def do-cmd
   ;; depends: reset-cache, update-cache.
   (lambda ()
     (let ((choices
            '((?r "reset" "reset all cache")
              (?d "delete" "delete a cache entry")
              (?u "update" "update all cache")))
           link)
       (pcase (car (read-multiple-choice "?" choices))
         (?r (reset-cache))
         (?u (update-cache))
         (?d (setq link (read-string "Link: "))
             (when (length> link 0)
               (reset-cache `(,link))))))))

;;;; do-conf
  (!def do-conf
   ;; depends: log.
   (lambda (op property &rest kargs)
     (pcase op
       ('set
        (when (symbolp property)
          (when (plist-get kargs :base)
            (put property 'org-referent-get-base
                 (plist-get kargs :base))
            (log "%s base %s" property
                 (plist-get kargs :base)))
          (when (plist-get kargs :depends)
            (put property 'org-referent-get-depends
                 (plist-get kargs :depends))
            (log "%s depends %s" property
                 (plist-get kargs :depends)))
          (when (plist-get kargs :apply)
            (put property 'org-referent-get-apply
                 (plist-get kargs :apply))
            (log "%s apply %s" property
                 (plist-get kargs :apply))))))))

;;;; update-cache
  (!def update-cache
   ;; depends: cache, get-prop.
   (lambda (&optional links)
     (!let* ((L links)
             (L (or L (hash-table-keys cache)))
             (N (length L)) (Np 0) (i 0)
             (T #'float-time) (Ts (T)))
      (dolist (l L)
        (message
         "Updating cache (%d/%d): %s, %ds"
         (setf i (1+ i)) N l (- (T) Ts))
        (and-let* ((P (gethash l cache))
                   (P (hash-table-keys P)))
          (dolist (p P)
            (setf Np (1+ Np))
            (get-prop l p :force t))))
      (message
       (concat
        "%d links %d properties cache updated, "
        "time cost: %ds.")
       N Np (- (T) Ts)))))

;;;; reset-cache
  (!def reset-cache
   ;; depends: cache.
   (lambda (&optional links)
     (cond
      (links
       (dolist (l links)
         (remhash l cache)
         (message "link: %s cache deleted." l)))
      (t (clrhash cache)
         (message "All cache reset.")))))

;;;; get-cache
  (!def get-cache
   ;; depends: cache
   (lambda (link property &optional all)
     ;; 缓存项 C 的结构: (flag dependent value)
     ;; flag: t/nil, 表示是否有初始化。
     ;; dependent: symbol 类 property 集。
     ;; value: 属性缓存值,可用 (last cache) 共享。
     (and-let* ((P (gethash link cache))
                (C (gethash property P))
                (v (if all C (car (last C))))))))

;;;; put-cache
  (!def put-cache
   ;; depends: cache, invalidate-dependent, log.
   (lambda (link property value)
     (when (and (or (stringp property)
                    (symbolp property))
                link)
       (log "  %S update" property)
       (!let* ((P (or (gethash link cache)
                      (puthash link (make-hash-table
                                     :test #'equal)
                               cache)))
               (C (or (gethash property P)
                      `(nil nil ,value))))
        (cond
         ;; 引用保持。
         ((car C)
          (unless (equal (car (last C)) value)
            (setf (car (last C)) value)
            (invalidate-dependent link property)))
         ;; 缓存初始化。
         ((setf (car C) t)
          (puthash property C P)))))
     value))

;;;; get-prop
  (!def get-prop
   ;; self:    get-prop
   ;; depends: put-cache, locate, bind-value,
   ;;          setup-dependencies, log.
   (lambda (link prop &rest kargs)
     (!let ((no-cache (or (plist-get kargs :no-cache)
                          (null link)))
            (P prop) (L link)
            (B 'org-referent-get-base)
            (error (lambda nil
                     (error "Invalid property: %S"
                            prop))))
      (when no-cache (log "  %S no cache" P))
      (org-with-point-at
          (or (plist-get kargs :epom)
              (and L (always (log "  %S locate" P))
                   (locate L t)))
        ;; 置 :epom 以便后续递归调用时无需再定位。
        (plist-put kargs :epom (point-marker))
        (cond
         ;; prop 是 string, 代表 Org属性 的字符串。
         ((stringp P)
          (!let ((v (org-entry-get nil P)))
           (if no-cache v (put-cache L P v))))
         ;; prop 是 lambda.
         ((not (symbolp P))
          (if (functionp P) (P) (error)))
         ;; prop 是 symbol.
         ((ignore (setup-dependencies L P)))
         ;; prop 无 base.
         ((null (setq B (get P B)))
          (unless (functionp P) (error))
          (if no-cache (P) (put-cache L P (P))))
         ;; prop 有 base.
         ((!let ((v (apply get-prop L B kargs)))
           (cond
            (no-cache v)
            ((or (stringp B) (symbolp B))
             (bind-value L P B))
            ((put-cache L P v))))))))))

;;;; locate
  (!def locate
   (lambda (link &optional signal-errors)
     (let ((inhibit-message t)
           (message-log-max nil)
           (org-link-search-must-match-exact-headline t)
           (org-link-frame-setup
            `((file . find-file-other-window)
       	   ,@org-link-frame-setup))
           (marker (make-marker)))
       (condition-case msg
           (save-window-excursion
             ;; 这个 guard 实际只对当前 buffer 有效,
             ;; 无法处理 open-link 跑到别的 buffer 的
             ;; 情况。但考虑到有时我们会 open 当前
             ;; buffer 中的 link, 为了防止因为可见性引
             ;; 发的链接查找失败,我们还是在这里加上这
             ;; 个 gaurd, 当然,也许还有更好的实现方法,
             ;; 但到时再说。
             (org-with-wide-buffer
              (if (not (stringp link))
                  (org-link-open link t)
                (org-link-open-from-string link t))
              (set-marker marker (point))))
         (error
          (when signal-errors
            (signal (car msg) (cdr msg))))))))

;;;; log
  (!def log
   (lambda (log-target)
     (lambda (fmt &rest args)
       (when debug-on-error
         (let* ((ts (format-time-string
                     "[%Y-%m-%d %H:%M:%S.%3N]"))
                (buf (get-buffer-create
                      log-target)))
           (with-current-buffer buf
             (goto-char (point-max)))
           (princ
            (concat
             (mapconcat
              (lambda (line) (concat ts line))
              (string-split
               (apply #'format fmt args) "\n")
              "\n")
             "\n")
            buf))))))
  (!def log (log " log:org-referent-get"))

;;;; bind-value
  (!def bind-value
   ;; depends: cache.
   (lambda (link prop base)
     ;; org-persist 会破坏此处的引用式值共
     ;; 享,在其 index.eld 中,引用消失,仅
     ;; 剩值。只有属性被强制更新时才会重新建
     ;; 立共享引用。
     (and-let* ((P (gethash link cache))
                (V (last (gethash base P)))
                (C (cons t (cons nil V)))
                (_ (puthash prop C P)))
       ;; 返回属性缓存值
       (car V))))

;;;; compute-value
  (!def compute-value
   (lambda (prop val)
     (!let ((apply 'org-referent-get-apply)
            (val val))
      (cond
       ((or (not (symbolp prop))
            (null (setq apply (get prop apply))))
        val)
       ((functionp apply) (apply val))
       (t (val))))))

;;;; add-dependent
  (!def add-dependent
   ;; depends: get-cache, log.
   (lambda (link base prop)
     (and-let* ((C (get-cache link base t)))
      (unless (memq prop (nth 1 C))
        (setf (nth 1 C) `(,@(nth 1 C) ,prop))
        (log "  %S dependent %S" base (nth 1 C))))))

;;;; setup-dependencies
  (!def setup-dependencies
   ;; depends: add-dependent, log.
   (lambda (link prop)
     (dolist (dep (get prop 'org-referent-get-depends))
       (log "  depend on: %s" dep)
       (add-dependent link dep prop))))

;;;; invalidate-dependent
  (!def invalidate-dependent
   ;; depends: cache, log.
   (lambda (link property)
     (and-let* ((P (gethash link cache))
                (C (gethash property P))
                (depends 'org-referent-get-depends))
       ;; 移除可能无效的 PROPERTIES.
       (setf (nth 1 C)
             (seq-remove
              (lambda (p)
                (or (not (symbolp p))
                    (not (functionp p))
                    (not (member property
                                 (get p depends)))))
              (nth 1 C)))
       (log "  %S dependent: %S" property (nth 1 C))
       ;; 使 PROPERTIES 缓存失效.
       (dolist (p (nth 1 C))
         (when (gethash p P)
           (setf (gethash p P) nil))))))

;;;; end
  ;; 延迟加载
  ;; depends: org-referent-get, cache.
  (lambda (&rest a)
    "No load. load by
  
  M-x org-referent-get or M-: (org-referent-get)."
    (interactive)
    (!def 'org-referent-get org-referent-get)
    (require 'org-persist)
    (!def cache
     (or (cadr
          (org-persist-read
           "org-referent--cache"
           nil nil nil :read-related t))
         (make-hash-table :test #'equal)))
    (org-persist-register
     `("org-referent--cache" (elisp-data ,cache))
     nil :write-immediately t :expiry 'never)
    (if (called-interactively-p 'interactive)
        (call-interactively #'org-referent-get)
      (apply org-referent-get a)))))

;;; org-N  -*- lexical-binding: t; -*-
;; (require 'org)
;; (require 'org-element)
(defun org-N (property &optional link &rest kargs)
  "除 :type 外,所有参数均与 ‘org-referent-get’ 一致。"
  (declare (indent 2))
  (let ((at-point? (null link))
        (type (or (plist-get kargs :type) "N")))
    (setq kargs (org-plist-delete kargs :type))
    ;; 试寻 PROPERTY 完整定义。
    ;; (org-N 'P) -> (org-N 'org-TYPE.P)
    (and-let*
        ((_ (and type (symbolp property)))
         (fn (format "org-%s.%s" type property))
         (fn (intern-soft fn))
         (_ (fboundp fn)))
      (setq property fn))
    ;; 如 LINK nil, 于当前位置生成 LINK.
    (when at-point?
      (setq link (org-store-link nil nil))
      (unless link
        (user-error
         "Not link refer to position %S %S"
         (current-buffer) (point)))
      ;; (message "org-N at point: %s" link)
      ;; 截掉 LINK 的中括号。
      (with-temp-buffer
        (save-excursion (insert link))
        (setq link (org-element-property
                    :raw-link
                    (org-element-link-parser)))))
    (apply #'org-referent-get link property kargs)))

(defmacro org-N-defprop (property base &rest kargs)
  "定义 Org节点 属性。

此宏会定义一个名为 ‘org-TYPE.PROPERTY’ 的 Emacs 命令。
‘org-TYPE.PROPERTY’ 以 BASE 为基础,获取并返回属性值。
作为命令调用时,更新属性缓存,并拷贝属性值至 kill-ring.

PROPERTY 为 unquoted symbol. BASE 可以是 string,
quoted symbol, 无参 lambda, 用于从节点中提取属性。详见
‘org-referent-get’.

KARGS:

:type, 指定 TYPE, 可选,默认 “N”.

(org-N-defprop id \"ID\") => org-N.id
(org-N-defprop id \"ID\" :type \\='id) => org-id.id

:get, ‘org-TYPE.PROPERTY’的实现,可选。

get 为一个可零参调用的 lambda, 调用后返回 PROPERTY
的属性值。其执行环境中存在一个绑定变量 fn, 其值为
‘org-TYPE.PROPERTY’. 该 lambda可借
(eq this-command fn) 判断是否为交互式调用。
‘org-N-defprop’ 对 get 有一个要求:当交互式调用时,
强制更新属性缓存。用例:

(org-N-defprop ...
  :get
  (lambda (&optional any variables)
    (if (eq this-command fn)
        (get-value-update)
      (get-value))))

:apply, :depends 同 ‘org-referent-get’."
  (declare (indent defun))
  (let* ((type (or (plist-get kargs :type) "N"))
         ;; fn 名称
         (fn (format "org-%s.%s" type property))
         (fn (intern fn))
         ;; fn 默认实现
         (fn-impl
          `(lambda (&optional link use-cache)
             "LINK: Org 链接。
USE-CACHE: nil 禁缓存, t 用缓存, \\='update 更新缓存。"
             ;; variables from env: fn, apply.
             (let ((use-cache
                    (if (eq this-command fn)
                        'update use-cache))
                   kargs)
               (pcase use-cache
                 (`nil (setq kargs `(:no-cache t)))
                 (`update (setq kargs `(:force t))))
               (apply #'org-N fn link kargs))))
         (fn-impl (or (plist-get kargs :get) fn-impl))
         (fn-arglist (help-function-arglist fn-impl))
         (fn-args (seq-difference
                   fn-arglist '(&optional &rest)))
         (fn-doc (or (documentation fn-impl) "")))

    ;; 根据 BASE 及 fn-impl 生成 docstring.
    (if (or (stringp base)
            (and (cadr base) (symbolp (cadr base))))
        ;; string 或 quoted symbol.
        (setq fn-doc (format
                      "返回基于 %S 的节点属性。

交互式调用时,强制更新属性缓存,并拷贝属性值至 kill-ring.

%s"
                      base fn-doc))
      ;; lambda
      (when (functionp base)
        (setq fn-doc
              (format "%s\n\n%s"
                      (or (documentation base) "")
                      fn-doc))))
    (setq fn-doc (string-trim fn-doc))

    ;; 定义属性函数
    `(!let* ((fn ',fn) (fn-impl ,fn-impl))
      (org-referent-get 'set fn :base ,base)
      (org-referent-get 'set fn
        :apply ,(plist-get kargs :apply))
      (org-referent-get 'set fn
        :depends ,(plist-get kargs :depends))
      (!def fn
       (lambda ,fn-arglist
         ,fn-doc
         (interactive)
         (let* ((v (fn-impl ,@fn-args)))
           (when (eq this-command fn)
             (kill-new (format "%s" v))
             (message
              "Node's %s copied, value: %.50s."
              ',property v))
           v))))))

(org-N-defprop location
  ;; 因为 marker 无法被持久化,这里我们做些
  ;; hacking. 当此 lambda 被调用时,我们
  ;; 用 point 和 file 捕获当前位置。
  (lambda nil
    (let ((point (point))
          (file (buffer-file-name)))
      (lambda nil
        (set-marker
         (make-marker) point
         (find-file-noselect file)))))
  :apply t)
(org-N-defprop element-type
  (lambda nil
    (org-element-type
     (org-element-at-point-no-context))))

;;; org-id-property  -*- lexical-binding: t; -*-
(defun org-id-property (property &optional id force)
  "Org ID Property.

PROPERTY: 同 ‘org-referent-get’.
ID: Org ID.
FORCE: t 强制更新缓存。

返回: 属性值。"
  (declare (indent 2))
  (let ((at-point? (null id)) epom)
    (when at-point?
      (setq id (org-entry-get nil "ID" t))
      (unless id
        (user-error
         "No ID refer to position %S %S"
         (current-buffer) (point)))
      ;; (message "org-id at point: %s" id)
      ;; 因为上边获取 ID 时使用了 inherit 的方式
      ;; 所以需要定位所指位置。 在此代码路径下,
      ;; ‘org-referent-get’具有LINK 及 :epom
      ;; 信息,无需再定位。
      (setq epom (org-find-entry-with-id id)))
    (org-N property (format "id:%s" id)
      :type 'id :force force :epom epom)))

(defmacro org-id-defprop (name base &rest kargs)
  (declare (indent defun))
  `(org-N-defprop ,name ,base
     :type id
     :get
     (lambda (&optional id force)
       "ID: Org ID.
FORCE: t 强制更新缓存。"
       (org-id-property ',name id
         (or force (eq this-command fn))))
     ,@kargs))

(org-id-defprop id "ID") ; => #'org-id.id
(org-id-defprop title "ITEM")
(org-id-defprop todo "TODO")
(org-id-defprop closed "CLOSED")
(org-id-defprop timestamp-ia "TIMESTAMP_IA")
(org-id-defprop tags #'org-get-tags)
(org-id-defprop level #'org-current-level)
(org-id-defprop links
  (lambda nil
    "返回当前节点中所有 ID 链接。"
    (org-with-wide-buffer
     (org-narrow-to-subtree)
     (org-element-map
         (org-element-parse-buffer nil t t)
         '(link)
       (lambda (link)
         (let ((type (org-element-property
                      :type link)))
           (when (string= type "id")
             (intern
              (org-element-property
               :path link)))))))))
(org-id-defprop backlinks
  (lambda nil
    "返回当前节点所有的 ID 后链。"
    (when-let*
        ;; 同 org-id-property 保持一致,使用 inherited.
        ((id (org-entry-get nil "ID" t))
         (_
          (always
           (unless (hash-table-p org-id-locations)
             (org-id-update-id-locations))))
         (scope
          (when (hash-table-p org-id-locations)
            (seq-uniq
             (hash-table-values
              org-id-locations))))
         (bufs
          (mapcar
           (lambda (file)
             (or (find-buffer-visiting file)
                 (find-file-noselect file)))
           scope))
         (backlinks
          (mapcan
           (lambda (buf)
             (let ((re (concat
                        "\\["
                        "\\[id:" id "\\]"
                        "\\(\\[.*?\\]\\)?"
                        "\\]"))
                   (m (make-marker))
                   P)
               (org-with-point-at (set-marker m 1 buf)
                 (save-match-data
                   (while (re-search-forward re nil t)
                     (and-let*
                         ((p (org-entry-get
                              nil "ID" t))
                          (_ (not (string= p id)))
                          (p (intern p))
                          (_ (not (member p P))))
                       (push p P)))))
               P))
           bufs))
         (backlinks
          (seq-remove #'null backlinks)))
      backlinks)))

;;; org-id-select  -*- lexical-binding: t; -*-
;; (require 'seq)
;; (require 'org)
;; (require 'org-id)
;; (require 'org-clock)
(!def 'org-id-select
 (!let (org-id-select
        make-match-string select-by-match)

;;;; org-id-select
  (!def org-id-select
   (lambda (&rest kargs)
     "Org ID select.
   
查询匹配中的 Org ID, 返回 ID 集合。

关键字参数:

候选 ID 集

:id-set  候选 ID 集,可选。非 nil 时 ‘org-id-select’
         将从该集合中查询; nil 时从 ‘org-id-locations’
         获取。

用例:

  (org-id-select
    :id-set \\='(a b c)
    :match \"+TODO=\\\"DONE\\\"\")

  (org-id-select
    :id-set \\='(\"a\" \"b\" \"c\")
    :match \"+TODO=\\\"DONE\\\"\")

Match 字符串

:match  Org Match 字符串,详情见 Info node
        ‘(org)Matching tags and properties’.

用例:

  (org-id-select
    :match \"+CREATE_TIME>\\\"[2025-08-10]\\\"\")

时间范围查询

:tprop   Org 属性,值为 Org 时间戳;
:block   见 Info node ‘(org)The clock table’;
:tstart  见 Info node ‘(org)The clock table’;
:tend    见 Info node ‘(org)The clock table’.

用例:

  (org-id-select
    :tprop \"CREATE_TIME\" :block \\='2025-08)

排序

:sort-by  id 到 key 的映射;
:<        key 比较函数。

用例:

  (org-id-select
   :match \"+ITEM={Task.*}\"
   :tprop \"CREATE_TIME\" :block \\='today
   :sort-by #\\='org-id->time :< #\\='time-less-p)"
     (let* ((match (make-match-string kargs))
            (id-set
             (or (plist-get kargs :id-set)
                 (unless (hash-table-p
                          org-id-locations)
                   (org-id-update-id-locations)
                   nil)
                 (when (hash-table-p
                        org-id-locations)
                   (hash-table-keys
                    org-id-locations))))
            (ids (select-by-match id-set match))
            (sort-by (plist-get kargs :sort-by))
            (< (or (plist-get kargs :<) #'<))
            (ids (cond
                  (sort-by
                   (seq-sort-by sort-by < ids))
                  ((plist-get kargs :<)
                   (seq-sort < ids))
                  (t ids))))
       ids)))

;;;; make-match-string
  (!def make-match-string
   (lambda (kargs)
     (let* ((match (plist-get kargs :match))
            (block (plist-get kargs :block))
            (tstart (plist-get kargs :tstart))
            (tend (plist-get kargs :tend))
            (tprop0 (plist-get kargs :tprop))
            (tprop1 (or (plist-get kargs :tprop1)
                        tprop0))
            (range
             (when block
               (org-clock-special-range
                block nil t)))
            (tstart (or tstart (car range)))
            (tend (or tend (cadr range)))
            (match
             (if (null tprop0) match
               (concat
                match
                "+" tprop0 ">=\"" tstart "\""
                "+" tprop1 "<\"" tend "\""))))
       match)))

;;;; select-by-match
  (!def select-by-match
   (lambda (id-set mstr)
     ;; org 会使用 match 字符串构造一个 lambda (具体见
     ;; `org-make-tags-matcher'), 我们直接重构该 lambda,
     ;; 使其以 id 为入参,并用 `org-N' 获取属性。
     (!let* ((fn
              (cl-letf
                  ;; 防止 org-make-tags-matcher 编译
                  ;; lambda.
                  (((symbol-function #'byte-compile)
                    #'identity))
                (seq-remove
                 (lambda (s)
                   (eq (car-safe s) 'ignore))
                 (cdr (org-make-tags-matcher mstr)))))
             (body (flatten-list (car (last fn))))
             ;; 于此 hack 掉原本的 `org-entry-get'.
             (bindings
              `((link (format "id:%s" id))
                (org-entry-get
                 (lambda (_ P &rest _)
                   (org-N P link)))
                ,(when (memq 'todo body)
                   '(todo
                     (org-N "TODO" link)))
                ,(when (memq 'tags-list body)
                   '(tags-list
                     (string-split
                      (or (org-N "TAGS" link)
                          "")
                      ":" t)))
                ,(when (memq 'level body)
                   '(level
                     (org-N "LEVEL" link)))))
             (bindings (seq-remove #'null bindings)))
      ;; 改 lambda 参数列表及变量绑定。
      (setf (nth 1 fn) '(id))
      (setf (car (last fn))
            `(!let* (,@bindings) ,(car (last fn))))
      (seq-filter (byte-compile fn) id-set))))

;;;; end
  org-id-select))

;;; ends here