【存档】Org ID Remap


更新

表项覆盖;构建时间嵌入(附带更新)。

1 表项覆盖

在使用的过程中,常常会遇到这种场景[S]:个别文本块需要更新,进而联动到复制一份映射表,然后更新映射表中的 “个别” 表项。同样地,为了减少更新量,我们期望能够复用先前的映射表,或者说,在先前映射表的基础上,覆盖掉个别需要更新的表项。

原先 org-id-remap 的调用设计是为了实现一对多映射,即某个 id 存在多条映射链接(出于数据冗余考虑),具体来说:

映射表:

(org-id-remap
 "id1" "link1"
 "id1" "link2"
 "id1" "link3")

可以让我们具有多份 id1 的映射,以便某个 link 损毁的情况下能使用其他 links.

现在,场景[S]对映射表提出了另一种需求:同一 id 不同版本。

为此,最简单且直观的实现方式为:新增一个特性,使得 “每次” 调用 org-id-remap 建立表项时,覆盖已存在的表项。具体来说:

某次调用:

(org-id-remap
 "id1" "link1"
 "id1" "link2"
 "id2" "link3"
 "id3" "link4")

后跟有调用:

(org-id-remap
 "id2" "link6"
 "id2" "link5")

此时,旧的 id2 将被覆盖,即当前映射表为:

id1->{link1,link2}, id2->{link5,link6}, id3->{link4}.

实现

分裂 cmd-impl, 剥离 set-mapping.

#+name: 2025-07-25-21-30
#+begin_src emacs-lisp :eval no
(!def enable?
 (lambda ()
   (get remap 'org-link-abbrev-safe)))
(!def enable
 (lambda ()
   (put remap 'org-link-abbrev-safe t)
   (setf (alist-get
          "id" org-link-abbrev-alist
          nil nil #'equal)
         remap)
   (push `(t . ,open-file-link)
         org-file-apps)
   (add-hook
    'org-execute-file-search-functions
    search)
   (message "Org ID remap enable.")))
(!def disable
 (lambda ()
   (put remap 'org-link-abbrev-safe nil)
   (setq org-link-abbrev-alist
         (assoc-delete-all
          "id" org-link-abbrev-alist
          #'equal))
   (setq org-file-apps
         (seq-filter
          (lambda (it)
            (not
             (equal
              it `(t . ,open-file-link))))
          org-file-apps))
   (remove-hook
    'org-execute-file-search-functions
    search)
   (message "Org ID remap disable.")))
(!def reset
 (lambda ()
   (clrhash mapping)
   (message "Org ID mapping reset.")))
<<@([[id:org-id-remap::set-mapping]])>>
(!def get-mapping
 (lambda ()
   (!let (r)
    (maphash
     (lambda (k vs)
       (mapc
        (lambda (v) (push (list k v) r)) vs))
     mapping)
    (flatten-list r))))
#+end_src

更新 set-mapping. 实现旧表项覆盖。

#+name: 2025-07-25-21-31
#+begin_src emacs-lisp :eval no
(!def set-mapping
 (lambda (mappings)
   (let ((mappings
          (seq-partition mappings 2)))
     ;; 覆盖已有的映射表项,以用新表项变更文本版本。
     (mapcar
      (lambda (kv)
        (setf (gethash (car kv) mapping) nil))
      mappings)
     ;; 建立映射。
     (mapcar
      (lambda (kv)
        (let ((k (car kv)) (v (cadr kv)))
          (unless
              (member v (gethash k mapping))
            (push v (gethash k mapping)))))
      mappings))))
#+end_src

2 构建时间嵌入(附带更新)

新增 build-time 命令,以识别不同的 build.

#+name: 2025-07-25-21-32
#+begin_src emacs-lisp :results silent
(format-time-string "%FT%T%z")
#+end_src
:2025-07-25-21-33:
将 Org ID 链接重映射为其他链接。
重映射仅在“打开链接”的上下文中有效。

零参时, toggle org-id-remap.

单参时:
  'reset 重置重映射;
  'enable? 查询使能情况;
  nil 禁用重映射;其他使能。

其他情况时,建立 id 映射,调用形式如下:
  (org-id-remap
   \"id1\" \"id:real-target1\"
   \"id2\" \"id:real-target2\"
   ...)
:end:
#+name: 2025-07-25-21-34
#+begin_src emacs-lisp :eval no
(lambda (&rest args)
  "Org ID 重映射。

<<@([[id:org-id-remap::cmd-doc]],rm-ws-p=1)>>"
  (interactive)
  (declare (indent 0))
  (cond
   ((length= args 0)
    (if (enable?) (disable) (enable)))
   ((length= args 1)
    (setq args (car args))
    (cond
     ((eq 'enable? args) (enable?))
     ((eq 'reset args) (reset))
     ((eq 'get-mapping args) (get-mapping))
     ((eq 'build-time args)
      "<<2025-07-25-21-32()>>")
     ((null args) (disable))
     (t (enable))))
   (t (set-mapping args))))
#+end_src

构建配置

映射表:

#+name: 2025-07-25-21-38
#+begin_src emacs-lisp :eval no
"org-id-remap::cmd-impl"
"emacs-china/org-id-remap:29814::2025-07-25-21-30"
"org-id-remap::set-mapping"
"emacs-china/org-id-remap:29814::2025-07-25-21-31"

"org-id-remap::cmd-entry"
"emacs-china/org-id-remap:29814::2025-07-25-21-34"
"org-id-remap::cmd-doc"
"emacs-china/org-id-remap:29814::2025-07-25-21-33"

;; 未变更部分

"org-id-remap::org-id-remap"
"emacs-china/org-id-remap:29814::2025-07-25-20-48"
"org-id-remap::link-verify"
"emacs-china/org-id-remap:29814::2025-07-25-20-49"
"org-id-remap::drawer-search"
"emacs-china/org-id-remap:29814::2025-07-25-20-50"
"org-id-remap::open-file-link"
"emacs-china/org-id-remap:29814::2025-07-25-20-51"
"org-id-remap::remap"
"emacs-china/org-id-remap:29814::2025-07-25-20-52"
"org-id-remap::remap-sort-links"
"emacs-china/org-id-remap:29814::2025-07-25-20-53"
"org-id-remap::cache-http-link"
"emacs-china/org-id-remap:29814::cache-http-link"
"org-id-remap::cmd-set"
"emacs-china/org-id-remap:29814::cmd-set"
"org-id-remap::log"
"emacs-china/org-id-remap:29814::log"
"org-id-remap::private"
"emacs-china/org-id-remap:29814::private"
#+end_src

Eval 及 Tangle入口

#+name: 2025-07-25-21-36
#+begin_src emacs-lisp :eval no
(org-id-remap
 "org-id-remap::2025-07-25-21-38"
 "https://emacs-china.org/t/org-id-remap/29814::2025-07-25-21-38"
 )
#+end_src
#+name: 2025-07-25-21-37
#+header: :depend (org-sbe "2025-07-25-21-36" ":eval yes")
#+header: :depend (org-id-remap t)
#+begin_src emacs-lisp :results silent :noweb yes
(org-id-remap 'reset)
(org-id-remap
 <<@([[id:org-id-remap::2025-07-25-21-38]])>>
 )
(org-with-wide-buffer (org-sbe "2025-07-25-20-39" ":eval yes"))
#+end_src