Org节点自动化

39 阅读16分钟

背景

在笔记系统的自动化方面常常有这样的需求:如果某些节点不存在,创建这些节点,如存在,视情况更新它们的内容。

Org mode 提供的 org-capture 特性一定程度上处理了节点创建相关的方面,但我们还需要某种检测及更新机制:检测目标节点在笔记系统中是否存在,并提供一个更新已存在节点的机制。

概述

org-autogen-defentry, 一个Org节点自动化工具。

注:本文中的 Org节点 特指 Org entry.

org-autogen-defentry 的核心逻辑很简单:目标节点存在则视情况更新,不存在则创建(并写入内容)。

为实现上述逻辑, org-autogen-defentry 定义了一些接口,比如判断当前位置是否是目标节点类型的 here?; 又比如提供节点信息的 info 以及根据节点信息寻找目标节点位置的 find; 还有用于判断目标节点是否需要写入内容的 dirty?; 用于更新目标节点内容的 update; 用于生成目标节点元数据和内容的 meta-data 及 content; 用于插入目标节点内容的 insert 等。(一个用例:仅往节点中插入内容)

这些接口的实现都需要由调用方提供给 org-autogen-defentry. org-autogen-defentry 通过这些接口将 节点的具体定义细节 与 节点的自动创建及更新逻辑 隔离开。

注:注意这两个概念:“节点定义细节”;“节点更新及创建逻辑”. org-autogen-defentry 只负责后者。

用例

以下的用例展示了通过 org-autogen-defentry 定义一个用于将日总结自动化的节点类型。

(org-autogen-defentry
  ;; 定义名为 day-log 的节点类型。
  ;; 可通过 M-x org-entry:day-log 创建或
  ;; 更新现存节点。
  day-log

  ;; 通过“意识流”标签及是否具备 CUSTOM_ID
  ;; 判断当前位置是否目标节点。
  "+意识流+CUSTOM_ID={.}"

  ;; 寻找、创建目标节点所需的信息。
  :info (org-read-date nil t)

  ;; 通过 match 寻找目标节点。
  :find-match
  (format
   "+ITEM={%s}+意识流+CUSTOM_ID={.}"
   (format-time-string "%y.%-m.%-d" info))

  ;; match 寻找的范围。
  :find-match-scope '("~/org/stream.org")

  ;; 创建目标节点时,节点的元数据。
  :meta-data
  (list
   :ITEM (format-time-string
          "%y/%-m/%-d" info)
   :TAGS "意识流"
   :CUSTOM_ID
   (format-time-string "%Y-%m-%d" info)
   :CREATE_TIME
   (format-time-string
    (org-time-stamp-format t t) info))

  ;; 目标节点的内容
  :content
  (concat "#+begin: ts-text :scope all\n"
          "#+end:")

  ;; 目标节点将存于何处?
  :target
  '(id "a867203a-ab58-40b9-becb-1443f700a391")

  ;; 目标节点将如何更新?
  :update (org-map-dblocks))

执行上述代码片段后, org-autogen-defentry 将定义一个名为 org-entry:day-log 的 Emacs 命令,节点的更新或创建可通过 M-x org-entry:day-log 完成。

本文后续将逐步深入 org-autogen-defentry 的实现,介绍其更新或创建节点的逻辑,以及其配置参数的具体用法。

接口说明

(org-autogen-defentry NAME HERE? &rest CONF)

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‍​‍‌​​‌​​‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍定义一个名为 NAME 的自动化节点类型,其节点可通过

M-x org-entry:NAME

更新或创建。

HERE?                 判断此处的节点是否为目标类型。

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::here?]])>>

CONF

:info                 定位、创建节点时所需的节点信息。
:meta-data            诸如标题、标签、属性等节点的元数据。
:content              节点的内容。
:find                 定位节点所在位置。
:find-match           通过 Org Match String 定位节点。
:find-match-scope     :find-match 的寻找范围。
:dirty?               判断是否需要插入节点内容。
:insert               节点内容的插入函数。
:update               用于更新节点的函数。
:update-just-created  是否更新刚刚创建的节点。
:update-here          是否更新此处的节点。
:update-elsewhere     是否更新别处的节点。
:pre                  用于扩展的前处理函数。
:post                 用于扩展的后处理函数。
:target               ‘org-capture-templates’ target.

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::conf]])>>
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

节点命令

如前文所述,对于名为 NAME 的节点类型, org-autogen-defentry 会生成一个名为 org-entry:NAME 的 Emacs 命令。节点则通过 M-x org-entry:NAME 更新或创建。

org-entry:NAME 的结构如下所示:

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​​‌‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​‍​​​‍‍‌‍‍‌‍‌‌‌‍‌‌‌‍​‍​‍​​‌‍‍‌‍​‍‌‌‍‌‌‌‍​‌​​‌‍‌​‍‌‌​​​‍‍`(defun ,(intern (concat ns ":" n))
     (&optional info &rest args)
   (interactive)
   (!let ((cmd this-command) ret
          (info info) (find ,find)
          (here? ,here?) (dirty? ,dirty?)
          (insert ,insert) (update ,update)
          (meta-data ,meta-data) (content ,content)
          (target ,target) (pre ,pre) (post ,post))
    (catch ',return-sym
      ;; PRE
      (apply pre info args)

      ;; 节点更新及创建逻辑
      (!def ret
       (cond
        ;; 更新此处节点
        <<@([[id:org-autogen-defentry::org-entry:x--update-here]])>>
        ;; 获取节点信息
        <<@([[id:org-autogen-defentry::org-entry:x--read-info]])>>
        ;; 更新别处节点
        <<@([[id:org-autogen-defentry::org-entry:x--update-elsewhere]])>>
        ;; 创建新节点
        <<@([[id:org-autogen-defentry::org-entry:x--create]])>>))

      ;; POST
      (when (and post (markerp ret))
        (let ((this-command cmd)) (post ret)))

      ret)))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

对于 org-autogen-defentry 的“节点更新及创建逻辑”而言,输入只需节点信息 (info), 输出只有节点位置 (marker). 不过,对于 org-entry:NAME 而言,它本身除了作为 Emacs 命令用以更新或创建节点之外,它同时还可以作为一个 elisp 接口。作为 elisp 接口时,它可以有除 info 之外的其他输入,输出还可能是其他数据结构,其具体定义属于“节点定义细节”,不属于 org-autogen-defentry 所负责的“节点更新及创建逻辑”。

如其结构所示,通过 pre 参数和 catch 语句, org-entry:NAME 可以绕开“节点更新及创建逻辑”,以作为一个纯粹的 elisp 函数使用。

创建节点

org-autogen-defentry 将一个节点划分为两个部分:包含标题、标签、属性等信息的节点元数据 (meta-data), 除了元数据之外的节点内容 (content), 如下所示:

* ITEM                    TAGS
:PROPERTIES:
:P1: V1:
:P2: V2:
:END:

--此上为节点元数据,此下为节点内容--

通常,有一些特别的 block, 如
dynamic block, 会作为“更新节点内容”
的目标,被视为自动内容。

此外,节点中也许还有手动部分,这部分
不在节点更新范围中。

为了实现节点自动化, org-autogen-defentry 将节点生成的过程分为“创建节点模板”及“更新节点内容”两个步骤,并且将“节点内容”进一步区分为手动部分和自动部分。其中,节点内容的自动部分是 org-autogen-defentry “更新节点内容”的更新目标。

org-capture 为创建节点提供了丰富的特性,比如指定节点的存储位置,比如替换 org-capture template 中的模板参数等. 故在创建新节点时, org-autogen-defentry 将直接使用 org-capture, 并裁剪 org-capture 的配置参数, 比如 org-autogen-defentry 的 :target 参数 等价于 org-capture 的 target 参数;而 org-capture 的 template 将由 org-autogen-defentry 的 :info, :meta-data, :content, :update 配合参数生成。

注:本文中的“节点模板”和 org-capture template 非同一概念。“创建节点模板”并“更新节点内容”所得的最终结果才是 org-capture template.

org-autogen-defentry 创建节点的逻辑如下:

org-autogen-defentry::org-autogen-defentry–create[2025-09-13 Sat 10:35]

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍((setq update ,update-just-created)
 (require 'org-capture)
 (let* ((org-capture-mode-hook nil)
        (org-capture-templates
         `(("t" ""
            entry ,target
            ,(with-temp-buffer
               (setq insert nil)
               (org-mode)
               ;; 创建节点模板
               (save-excursion
                 (insert (meta-data info))
                 (insert (content)))
               ;; 更新节点内容
               (update)
               (buffer-string))
            :immediate-finish t))))
   (org-capture nil "t")
   org-capture-last-stored-marker))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

更新节点

更新节点涉及“寻找目标节点”以及“更新节点内容”,而寻找目标节点又可细分为:判断当前位置是否是目标节点 或 寻找位于其他位置的目标节点,故更新节点细分为“更新此处节点”和“更新别处节点”。

进行此区分的原因在于:只有需要定位位于别处的节点时才需要输入节点信息 (info), 该信息通常由用户以交互的方式提供。

注:想象一下,你当前已经打开了某个类型为 NAME 的节点, 此时,若你在该节点上 M-x org-entry:NAME, org-entry:NAME 将断定你的意图为更新当前这个打开的节点,而不是向你询问节点信息。

更新此处节点:
org-autogen-defentry::org-autogen-defentry–update-here

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‌​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍;; info 非空时直接进入查找节点的分支。
((and here? (null info) (here?))
 (setq update ,update-here)
 (save-excursion
   (org-back-to-heading)
   (setq ret (point-marker))
   (unless (dirty?) (insert (content)))
   (goto-char ret)
   (update)
   ret))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

当此处非目标节点时, org-entry:NAME 将试图通过输入的节点信息 info 寻找目标节点。

获取节点信息:
org-autogen-defentry::org-autogen-defentry–read-info

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‍‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍;; 通常由用户交互式输入,也可由程序输入。
((ignore
  (setq info (if info info ,info))
  (when (functionp info)
    (let ((this-command cmd))
      (setq info (info))))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

更新别处节点:
org-autogen-defentry::org-autogen-defentry–update-elsewhere

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‌‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍((and find (setq ret (find info)))
 (setq update ,update-elsewhere)
 (org-with-point-at ret
   (unless (dirty?) (insert (content)))
   (goto-char ret)
   (update)
   ret))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

作为elisp接口

在详细介绍 org-autogen-defentry 的各个配置参数之前,我们先介绍一个关于 pre 参数的特殊用例。

如前文所述,通过 pre 参数和 catch 语句, org-entry:NAME 可以绕开“节点更新及创建逻辑”,作为一个纯粹的 elisp 函数使用.

:pre 的定义如下:

org-autogen-defentry::pre

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​​​‍‌​‍‌​‌‌​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def pre
 (pcase pre
   ;; lambda?
   (`(lambda . ,_)
    `(!let ((here? ,here?)
            (return ,return))
      ,pre))
   ;; let 裹 lambda?
   (`(,(and (or 'let* 'let
                '!let '!let*))
      ,bindings .
      ,(and body
            (guard
             (functionp
              (car (last body))))))
    (eval
     `(!let ((here? ,here?)
             (return ,return))
       ,pre)
     t))
   ;; sexp
   (_ `(!let ((here? ,here?)
              (return ,return))
        (lambda (info &rest args)
          ,pre)))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

考虑这样一个节点类型:

(org-autogen-defentry X nil
  :pre
  (let ((hooks nil))
    (lambda (info &rest args)
      (cond
       ((eq info 'add-hook)
        (push (car args) hooks)
        (return hooks))
       ((eq info 'hook)
        (return hooks)))))
  ;; ...
  :content
  (mapconcat
   #'funcall (org-entry:X 'hook) "\n")
  ;; ...
  )

在上述样例中, :pre 被配置为一个“被 let 裹住的 lambda”, 该 lambda 中使用了一个由 org-autogen-defentry 预先绑定函数 return. 借助 return,

(org-entry:X 'hook) 将返回上述片段中被 let-绑定 的变量 hooks.

如此, org-autogen-defentry 为用户提供一种“避免往全局环境中引入 org-entry:X–hook”的手段,同时依旧极大程度上保持 org-entry:X 符号的可定义性。

除 return 外, org-autogen-defentry 还绑定了一个函数 here?, 以便有用户欲借 (org-entry:X 'here?) 检测当前位置是否为目标节点,比如,配合 org-ctrl-c-ctrl-c-hook 检测并更新节点。

整体结构

org-autogen-defentry 实现为宏,其结构如下:

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​‌​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​‍​​​‍‍‌‍‍‌‍‌‌‌‍‌‌‌‍​‍​‍​​‌‍‍‌‍​‍‌‌‍‌‌‌‍​‌​​‌‍‌​‍‌‌​​​‍‍;;; org-autogen-defentry  -*- lexical-binding: t; -*-
(defmacro org-autogen-defentry (name here? &rest conf)
  "Define Org autogen entry.

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry]])>>"
  (declare (indent defun))
  (!let ((ns "org-entry") (n (format "%s" name))
         (prompt (format "Update %s?" name))
         <<@([[id:org-autogen-defentry::conf]])>>
         query-update gen-update gen-meta-data
         (return-sym (gensym "return")) return)

   (!def return
    (lambda (v) (throw return-sym v)))

   <<@([[id:org-autogen-defentry::info]])>>

   <<@([[id:org-autogen-defentry::gen-meta-data]])>>
   <<@([[id:org-autogen-defentry::meta-data]])>>

   <<@([[id:org-autogen-defentry::content]])>>

   <<@([[id:org-autogen-defentry::here?]])>>

   <<@([[id:org-autogen-defentry::find]])>>

   <<@([[id:org-autogen-defentry::dirty?]])>>

   <<@([[id:org-autogen-defentry::insert]])>>

   <<@([[id:org-autogen-defentry::update]])>>

   <<@([[id:org-autogen-defentry::pre]])>>

   <<@([[id:org-autogen-defentry::post]])>>

   <<@([[id:org-autogen-defentry::org-entry:x]])>>))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

org-autogen-defentry::conf

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌‌​​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(dirty? (plist-get conf :dirty?))
(content (plist-get conf :content))
(meta-data (plist-get conf :meta-data))
(info (plist-get conf :info))
(find (plist-get conf :find))
(target (plist-get conf :target))
(pre (plist-get conf :pre))
(post (plist-get conf :post))
(insert (plist-get conf :insert))
(update (plist-get conf :update))
(update-here
 (plist-get conf :update-here))
(update-elsewhere
 (plist-get conf :update-elsewhere))
(update-just-created
 (plist-get conf :update-just-created))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

配置参数

至此,我们基本介绍完了 org-autogen-defentry 为实现节点自动化而设计的“节点更新及创建逻辑”以及一些特殊用法,其中涉及了许多配置参数,现依次说明。

info

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​‍​‍‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:info                 定位、创建节点时所需的节点信息。
类型: sexp, (lambda nil _).
‘org-autogen-defentry’ 的节点更新及创建逻辑并不直接使用该
参数,而是将其传递给 :meta-data, :find, :find-match 等。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::info

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​‍​‌‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def info
 (cond ((functionp info) info)
       (`(lambda nil ,info))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

meta-data

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:meta-data            诸如标题、标签、属性等节点的元数据。
类型: sexp, str, (lambda (info) (or str plist)).
为 sexp 时, info 绑定为 :info 返回值。
当输出 plist 时, plist 中的 键值对 将生成节点的属性及属性
值。其中, :ITEM 和 :TAGS 将被特殊处理. :ITEM 将生成节点
标题, :TAGS 则生成节点标签。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::meta-data

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def meta-data
 (cond ((functionp meta-data) meta-data)
       ((stringp meta-data) meta-data)
       (`(lambda (info) (or ,meta-data "")))))
(!def meta-data
 (!let ((md meta-data))
  (lambda (info)
    (let ((md (md info)))
      (when (listp md)
        (setq md (gen-meta-data md)))
      md))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

因为 meta-data 支持输出 plist, org-autogen-defentry 中提供如下函数将 plist 类 meta-data 转化为 string 类 meta-data.

org-autogen-defentry::gen-meta-data

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def gen-meta-data
 (lambda (meta)
   (with-temp-buffer
     (org-mode)
     (let ((insert nil))
       (insert "* " (or (plist-get meta :ITEM) ""))
       (!def meta (org-plist-delete meta :ITEM))
       (goto-char (point-min))
       (org-set-tags (plist-get meta :TAGS))
       (!def meta (org-plist-delete meta :TAGS))
       (mapcar
        (lambda (pv)
          (org-set-property
           (string-trim
            (format "%s" (car pv)) ":")
           (format "%s" (cadr pv))))
        (seq-partition meta 2))
       (end-of-buffer)
       (insert "\n")
       (buffer-string)))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

content

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:content              节点的内容。
类型: sexp, (lambda nil str).
执行上下文为目标节点所在位置。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::content

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def content
 (cond ((functionp content) content)
       (`(lambda nil (or ,content "")))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

here?

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍类型: str, (lambda nil bool).
HERE? 通常为 str, 表示 Org Match String. 为 lambda 时
的执行上下文为当前位置。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::here?

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def here?
 (cond ((stringp here?)
        `(lambda nil
           (when (derived-mode-p 'org-mode)
             (ignore-errors
               (org-with-wide-buffer
                (org-back-to-heading)
                (narrow-to-region
                 (point)
                 (or (outline-next-heading)
                     (org-end-of-subtree)))
                (goto-char (point-min))
                (org-map-entries t ,here?))))))
       ((null here?) here?)
       ((functionp here?) here?)
       (`(lambda nil ,here?))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

find

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:find                 定位节点所在位置。
类型: sexp, (lambda (info) marker).
为 sexp 时, info 绑定为 :info 返回值。
当提供 :find-match 时, :find 参数将被忽略。

:find-match           通过 Org Match String 定位节点。
类型: str, sexp, (lambda (info) str).
为 sexp 时, info 绑定为 :info 返回值。
当提供 :find-match 时, :find 参数将被忽略。

:find-match-scope     :find-match 的寻找范围。
同 ‘org-map-entries’ SCOPE 参数。
需配合 :find-match 使用。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::find

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def find
 (cond ((plist-get conf :find-match)
        (let ((match
               (plist-get conf :find-match))
              (scope
               (plist-get
                conf :find-match-scope)))
          `(lambda (info)
             (car
              (org-map-entries
               #'point-marker
               ,(cond
                 ((stringp match) match)
                 ((functionp match)
                  `(funcall ,match info))
                 ((listp match) match)
                 ((error "Bad `match': %S"
                         match)))
               ,scope)))))
       ((null find) find)
       ((functionp find) find)
       (`(lambda (info) ,find))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

dirty

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:dirty?               判断是否需要插入节点内容。
类型: sexp, (lambda nil bool).
dirty? 的执行上下文为目标节点所在位置。
存在内置实现,内置实现通过检测节点内容长度判断是否需插入内容。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::dirty?

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def dirty?
 (cond ((null dirty?)
        (lambda nil
          (and-let* ((content
                      (save-mark-and-excursion
                        (org-mark-subtree)
                        (deactivate-mark)
                        (org-end-of-meta-data)
                        (buffer-substring
                         (point) (mark))))
                     (content
                      (replace-regexp-in-string
                       "[ \n\t]" "" content))
                     (_ (length> content 10))))))
       ((functionp dirty?) dirty?)
       (`(lambda nil ,dirty?))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

insert

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:insert               节点内容的插入函数。
类型: sexp, (lambda (content) _).
为 sexp 时, content 绑定为 :content 返回值。
insert 的执行上下文为目标节点所在位置。
存在内置实现,内置实现将节点内容插入于节点末尾。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::insert

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‍​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def insert
 (cond ((null insert)
        (lambda (content)
          ;; Insert to entry end
          (save-excursion
            (org-end-of-subtree nil t)
            (let ((insert nil))
              (insert content)
              (unless (eolp) (insert "\n"))))))
       ((functionp insert) insert)
       (`(lambda (content) ,insert))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

update

在更新节点内容方面,为对更新流程的控制提供最大的灵活性, org-autogen-defentry 将 更新时机 细分为三种情况:于此处更新;于别处更新;于创建时更新。这三种情况是否触发更新操作可分别由 :update-here, :update-elsewhere, update-just-created 控制。每个配置参数都有三种选择:静默更新、静默不更新、询问是否更新。

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​‍​​​‍​​​‍‍‌‍‌‌‌​‌‍​‌‍​‌‌​​‌‍‌‌​‍​​​‍​‍‌‍‍‌‍​‍​‍​​‍‍:update               用于更新节点的函数。
类型: sexp, (lambda nil _).
update 的执行上下为目标节点所在位置。

:update-here          nil, t, 'query. 默认 t.
:update-elsewhere     nil, t, 'query. 默认 nil.
:update-just-created  nil, t, 'query. 默认 nil.
更新节点内容在不同情况下的细分配置。nil 不更新; t 更新;
'query 询问更新。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::update

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌‌​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def update
 (cond ((functionp update) update)
       (`(lambda nil ,update))))
(!def query-update
 (cond
  ((equal update (lambda)) (lambda))
  ((lambda nil
     (save-window-excursion
       (save-restriction
         (org-narrow-to-subtree)
         (pop-to-buffer (current-buffer))
         (when (y-or-n-p prompt)
           (update))))))))
;; update-here 默认询问更新
(!def update-here
 (if (plist-member conf :update-here)
     update-here 'query))
(!def gen-update
 (lambda (type)
   (cond
    ((eq type t) update)
    ((eq type 'query) query-update)
    ((lambda)))))
(setq update-here (gen-update update-here))
;; update-elsewhere 默认静默不更新
(setq update-elsewhere
      (gen-update update-elsewhere))
;; update-just-created 默认静默不更新
(setq update-just-created
      (gen-update update-just-created))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

pre&post

为扩展 org-entry:X, org-autogen-defentry 提供了 :pre 及 :post 参数。通过 :pre 可以在控制流进入“节点更新及创建逻辑”之前执行额外的操作,甚至绕过整个节点创建及更新逻辑;通过 :post 可以在“节点更新及创建逻辑”之后执行某些额外的操作,比如跳转至节点所在位置。

pre:

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​​​‍‌​‍‌​‌‌​‌‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:pre                  用于扩展的前处理函数。
类型: sexp, (lambda (info &rest args) _).
为 sexp 时, info, args 绑定同 org-entry:NAME 入参。
为 lambda 且裹于 let 中, :pre 将立即被求值为闭包。
无论何种情况, pre 的执行上下文中均含两个绑定函数 here? 以及
return.
若 pre 中含 return 语句, org-entry:NAME 将绕过节点更新及
创建逻辑, 直接将 (return value) 的 value 作为返回值返回。
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

post:

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍​‍‍​‍​​‌‍‌‌‌‍​‌‍‍‌‌​‌​​​‍‌‌‍‌​‌‍‌‍​‌​‌‌​‌​‍‌‍‍‌‌‍‍‌‍‌​​‍‍:post                 用于扩展的后处理函数。
类型: sexp, (lambda (marker) _).
为 sexp 时, location 绑定为目标节点位置 (marker).
​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​​‍‍

org-autogen-defentry::post

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‍‌​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍(!def post
 (cond ((null post) post)
       ((functionp post) post)
       (`(lambda (location) ,post))))​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

conf

org-autogen-defentry::doc:org-autogen-defentry::conf

​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​‌​​​​‍‌​​​​​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌​‍‌‍‌​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::info]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::meta-data]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::content]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::find]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::dirty?]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::insert]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::update]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::pre]])>>

<<@([[id:org-autogen-defentry::doc:org-autogen-defentry::post]])>>​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​

​‍​​‍​​‍‌‍‍‌‍‍​‌​‍‌‌‍‍‍‍‍​​‍‍​​‍‍‍‌‍‍‌‍​‍‍​​‍‌‌‍​​‍​‌​‍‍​‍‍​‌‍‍‍​​‍​​​‍​​​​‍‍​​‍‍‌‍​​‌‍​​‌‍​​‌‌​‌‍​‌‌​​​‍‍​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​​‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​‌​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‍​‍‌​​‌​​‍​‍​‍​​‍‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​​‌‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍​​‍‌​‍‌‌‍​‌​‍‌‍‌‌‌‍​‌‌‌​‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍​​‍‌​‍‌‌‌‌‌​​‌‍‌​‌‍​‌‌‌​‌‍‌‌​‍‌‌‍‍​‌‍‌‌‌​‍‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‌​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍​​‍‌​‍‌‌​‍‌‍‌‌‌‍​‌‌‍‌​​‍‌‌‍‍‌‌‍‍‌‍‌‍‌‍​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‍‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍​​‍‌​‍‌‌‌‌‌​​‌‍‌​‌‍​‌‌‌​‌‍‌‌​‍‌‌‍‌‌‌‍​‌​‌‍‌‌‌‌‌‍‍​‌‍‌‌‌​‍‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​‌‌​‌‌​‍​‍​​‍‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍‌‌‍‍‌‍‌‍‌‍​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​‍​‌‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌‌‍‌‌‌‌​‌‍​‌​‍‌‌‍‌​‌‍​‌‌‌​‌‍​‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌‌‍‌‌‌‌​‌‍​‌​‍‌‌‍‌​‌‍​‌‌‌​‌‍​‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​​​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍​‌‍‌‍‍‌‌​‌‍‌‌‌‍‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​​​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍​‌‍‌‌‌​‍‌‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌‍‌‍‍‌‌‍‍‌‍‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‍‌‌​‍‌‌​‌‍‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍‌‌‍‍‌​‌‍‌‌‌​‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‍​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‌‌‌​​‌‍‌​‌‍​‌‌‌​‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌‌​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌​​‌​‍‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​​​‍‌​‍‌​‌‌​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌​​‌‍‌​‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌​​‍‌​‍​‍​​‍‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍​‌‍‌‍‍‌‍‌‍​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​‌​​​​‍‌​​​​​​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍‌‌‍‍‌‍‌‍‌‍​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​‍​‍‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌‌‍‌‌‌‌​‌‍​‌​‍‌‌‍‌​‌‍​‌‌‌​‌‍​‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍​‌‍‌‍‍‌‌​‌‍‌‌‌‍‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍​‌‍‌‌‌​‍‌‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌‍‌‍‍‌‌‍‍‌‍‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‍‌‌​‍‌‌​‌‍‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌​​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‍‌‌‍‍‌​‌‍‌‌‌​‍‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌‌​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‌‌‌​​‌‍‌​‌‍​‌‌‌​‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌​​‌​‍‌‍‌‌​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​‌‌​‍‌​​​​‍‌​‍‌​‌‌​‌‍​‍​‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍‌​‌‍‌‍​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌​​‌‍‌​‌‌​​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​‌​​‍‌​​​‌​‍​‍​​‍‍​​‍‍​‍​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍​‍‍‌‍​‌‍‌‍‍‌‍‌‍​‍​‍​​‍‍​‍​‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​‌​‍‌​‌‌​​​​‍​‍​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​​​‍‍‌‍​​‌‍​​‌‍​​​​‍‍​​‍‍‍‌‍‍‌‍‍​‌​‍‌‌‍‍‍‍‍‍‌‍‌‍‍‍‍‍‌‍‍‍​​‍​‌​‍​​​‍​​​​‍‍​​‍‍‌‍​​‌‍​​‌‍​​‌‌​‌‍​‌‌​​​‍‍​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​​​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​‌‍‍‌‍​‍​​​‍‍‌‍‍‌‍‌‌‌‍‌‌‌‍​‍​‍​​‌‍‍‌‍​‍‌‌‍‌‌‌‍​‌​​‌‍‌​‍‌‌​​​‍‍​​​​‌​​​​‍‍​‌‌‍‌‌‍‌‍‍‌‌‍‌​​‍‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌‌‌‌‌‌‌​‍‍‌​‍​‍​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​​​‍‍‌‍​​‌‍​​‌‍​​​​‍‍​​‍‍‍‌‍‍‌‍‍​‌​‍‌‌‍‍‍‍‍​‍​​​‍​​​​‍‍​​‍‍‌‍​​‌‍​​‌‍​​‌‌​‌‍​‌‌​​​‍‍​‍​​‍‍‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍​‍​​​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‍​​‌​‍‌​​‍​​​​‍‍​‍​​‍‍‌‍‍​‌‍‌‌‌‍​‌‌‍‌​‌‍‌‌‌​‍​‍‍​‍​​​‍‍‌‌‍‌‍​‌‌​‍​‍​​‌‌​‌‍​‌‌‍‍‌‍‌‌‍​‌‍‌‌​‌​‍‍​‌‍‍‌‌‍‌‌‍‍‌‍‌​‍‌‍‌‌​‍‍‌​‍​​‌‍​‌‍‌‍​‌‌‍‌​​‌​‍‍​‌‍‍‌‌‍‌‌‍‍‌‍‌​‍‌‍‌‌​‍‍‌​‍​​‌‍​‌‍‌‍‍‌‍‌‍​‍‌‌‍‌‍‍‌‍​‌‍‌​‌​‍​‍‌‍‍‌‍​‍​‍​​‍‍​‍​​‍‍‌‍​‍‌‍‌‌‌‍‌‌‍‍‌‌‍‍‌‌‌​‌​‍‌‍​​‍​​‌‍‌‌‌‍‌‌‍​‌‌‍​‌​​‍‌‌‍​‌‍‍‌‌​‌​​​‍​​​‍‍‌​‍‌‍‌‌‌​‌‌‌‌‍​‌‌​‌​​‍​​‌‍‍‌‍‌‍‍‌‍‌‌​​‍‍​‍‍​‌‍‌​‍‌‍‌​‍‌‌‍‌‌‌‍​‌‍‌‌‌‍​​‍​​​‍​‍‌‌‍‌‌‍‌‍‍‌‌‍‌​​‍‍‌‍​‌‍‌​‌‍​‌‍‌​‌‍​‌‍​‌​‌‌‍​‍​‍‌​‍​‌‍​‌‌‍‌​​‌​‍‌​‌​​‍​​​​​​​​‍‌​‍‌‌‍‌‍​​​​‍​​‍‌‌‍​​​‌‍​​​‍‌‍‌‌​‌‌‍‌‌​‌​​‌‌‌‍​‌‍‌‌‌‍‌‍​‍‍​‍‍​​‍​​​​​‍​‌‌​‍‌​​​​‌​‍‌​​‍​‍​​‍‌​​‌​​​​‍‌​‌​​‌‍‌‌‌‌‌‌​‍​‍​‍​​‌‍‍‌‍‍‌‌‍​​​‍‍​‍​​​‍​​​‍‍‌‍‌‌‌‌‍‌‍​‌‌‍​​‍​​​‍​‍‌‍‌‌‍‌‌‌​​‍​‍​​‍‍​‍​​​‍​​​‍‍‌​‍‌‍‌‌‌​‌‌‌‌‍​‌‌​‌​​‍​​​‍​‍‌‍‍‌‍‌‍‍‌‍‌‌​‍​‍​​‍‍​‍​​​‍​​​‍‌‌‍​‌‍‌‍‍‌‌​‌‍‌‌‌‍​‌‌​​‍​​‌‍​​‌‍​​​‍‍​​‍​​‍‍​‌‍​‍‌‌‌‌‍‌‍‌‍‌‍‌‍‌‌‌​‍​‍‌‌‍‍‌‍​‌‌‍‌‌‍‌‌​‍‍‌​‍​​​‍‍​‍​​​‍​​‍‍​‌​​‌‍‌‍‍‌‌‍‍‌‌​​‍‍‌​‍‍‌​​‍‍​‍​​​‍​​​‍‌‌‌​‌‍​‌‌​‍‌‍‌‌‍‌‌‌‌​​‍​​​‍​‍‌‌‍‌‌‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​​‌‌‌‌‌‌​‍​‍​​‍‍​‍​​​‍​​​‍‌‌‍‌‌‍​‌‌​​​‍‌‌‌​‌‍​‌‌‍​‍‌‍​‌‍‌‌​‍​​​‍​‍‌‌‍‌‌‍​​‍​​​​​‍​‌‌​‍‌​​​​‍‌​‍‌​​‌​​​‍‌​​‌​​​​‍‌​​‌​​‍‌‌‌‌‌‌​‍​‍​​‍‍​‍​​​‍​​​‍‌‌‌​‌‍​‌‌‍‍‌‍‌‌‍​‌‍‌‌​‍​​​‍‍​‌‍‌​‍​‍​​‌‌​‌‍​‌‌‍‍‌‍‌‌‍​‌‍‌‌​‍​​​‍​‍‌‍​‍‌‍‌​‍‌‍‌​‍‌‍‌​‍‌‍‌​‍‌‌‍​‌‌‌‌‌‌​‌‍‌‍‌‌‍‌‌‌‍‍​‍‌‌‍‌​‌‍‌‌‌‍‌‍‌‍‌‌‌‍‍‌‌​‌​‍‌‍‌​‍‍‌‍‌‌‌‍​​‍​‍​‍‍‌​​‍‍​‍​​​‍​​​‍‌‌‍​‌‍‌‍​‌‌‍‌​​‍​​​‍‍​‌‍‌​‍​‍​​‌‍​‌‍‌‍​‌‌‍‌​​‍​​​‍​‍‌‍‍‌‍​‍​‍​‍‍‌​​‍‍​‍​​​‍​​​‍‌‌‍​‌‍‌‍‍‌‍‌‍​‍‌‌‍‌‍‍‌‍​‌‍‌​‍​​‌‍​‌‍‌‍‍‌‍‌‍​‍‌‌‍‌‍‍‌‍​‌‍‌​‍‍‌​​‍‍​‍​​‍‍‌‍‌‌‌‍‍‌‍‌​‌‌‌​‌​‍‌‍​​​‍‍‌‍​​‌‍​​‌‍​​​​‍‍​​‍‍