关于elisp:一箭双雕orgmode中的ID和CUSTOMID字段

在org-mode中,一个条目(entry)能够设置多个属性(Properties)。有的属性是org-mode内置的,有它们的非凡用处。有的属性是自定义的,用在一些插件或仅仅用于记录信息。CUSTOM_ID属于前者,而ID属性后者。 CUSTOM_IDCUSTOM_ID用于跳转。org-mode反对丰盛的内部链接格局,其中之一便是链接到指定.org文件的指定CUSTOM_ID的条目。 比方在一个.org文件中有file:/Users/liutos/Dropbox/gtd/roles/writer.org::#d1bdc978-a8ce-4266-9ffa-b6041f818431这么一段文本,那么当光标置于这个文本中时,按下快捷键C-c C-o,Emacs便会关上文件/Users/liutos/Dropbox/gtd/roles/writer.org,并将光标对应的条目上。 IDID用于分割两个条目。一个名叫org-edna的第三方插件可能实现两个条目间的依赖,其中一个因素便是条目标ID属性。 比方我有一个解说Ada语言的工作(以一个条目标模式存在),同时也有一个学习Ada语言的工作(另一个条目)。显然,必须先学习一番能力讲给别人听,所以第一个条目依赖于第二个条目,于是我先给学习Ada语言的条目设置一个ID属性,值为905fc2f4-4e28-4966-84fa-84c9e6bae96c,而后再为解说Ada语言的条目中设置一个BLOCKER属性,值为ids(905fc2f4-4e28-4966-84fa-84c9e6bae96c)。如此一来,当解说Ada语言的条目呈现在*Org Agenda*中时,Emacs会将其置灰显示,代表它处于阻塞的状态,必须先解决它的依赖才行。 主动填充CUSTOM_ID和ID建设依赖和跳转都是很罕用的性能,因而我会给每一个条目都设置CUSTOM_ID和ID属性。为了罢黜每次都手动设置的麻烦,我用org-mode的capture-template个性来实现主动填充。 capture-template是org-mode的又一项利器,用于生成条目间共性的内容,比方行首的星号、关键字,以及写入到哪一个文件的哪一个层级中。org-mode的官网便有一个例子 (setq org-capture-templates '(("t" "Todo" entry (file+headline "~/org/gtd.org" "Tasks") "* TODO %?\n %i\n %a") ("j" "Journal" entry (file+datetree "~/org/journal.org") "* %?\nEntered on %U\n %i\n %a")))在capture-template中除了能够用预置的占位符(比方上文的%U、%i,以及%a),还能够调用任意的Elisp函数——这正适宜填充ID和CUSTOM_ID这类不反复,并且有肯定的格局要求的属性。ID属性的值能够用来自于第三方插件uuidgen的uuidgen-4函数来生成 (setq org-capture-templates '(("t" "Todo" entry (file+headline "~/org/gtd.org" "Tasks") "* TODO %? :PROPERTIES:\n :CUSTOM_ID: %(uuidgen-4)\n :ID: %(uuidgen-4)\n :END:")))美中不足的是,CUSTOM_ID和ID的值是不同的,因为uuidgen-4每次都会返回不同的字符串。有没有什么方法可能让它们一样的呢?答案是必定的。 一式两份既然两次调用uuidgen-4的后果不同,那么就将第一次调用后的后果保存起来,而后重复使用即可。思路很简略,实现代码也很直白 (let (lt-org-capture--uuid) (defun lt-org-capture-uuidgen () "生成一个UUID并填充到词法作用域的变量中。" (setf lt-org-capture--uuid (uuidgen-4)) lt-org-capture--uuid) (defun lt-org-capture-uuidclr () "返回生成好的UUID并清空它。" lt-org-capture--uuid))capture-template也是瓜熟蒂落的 ...

November 19, 2020 · 1 min · jiezi

拿Emacs对接我的cuckoo

cuckoo是一个我自己开发的类似待办事项的工具,运行在我本地的电脑上。它有如下两个接口:传入一个UNIX Epoch时间戳创建提醒传入一个标题以及提醒的ID来创建任务这样一来,便能在设定的时刻调用alerter在屏幕右上角弹出提醒。我喜欢用Emacs的org-mode来安排任务,但可惜的是,org-mode没有定点提醒的功能(如果有的话希望来个人打我的脸XD)。开发了cuckoo后,忽然灵机一动——何不给Emacs添砖加瓦,让它可以把org-mode中的条目内容(所谓的heading)当做任务丢给cuckoo,以此来实现定点提醒呢。感觉是个好主意,马上着手写这么些Elisp函数。PS:读者朋友们就不用执着于我的cuckoo究竟是怎样的接口定义了。为了实现所需要的功能,让我从结果反过来推导一番。首先,需要提炼一个TODO条目的标题和时间戳(用来创建提醒获取ID),才能调用cuckoo的接口。标题就是org-mode中一个TODO条目的heading text,在Emacs中用下面的代码获取(nth 4 (org-heading-components))org-headline-components在光标位于TODO条目上的时候,会返回许多信息(参见下图)其中下标为4的component就是我所需要的内容。接着便是要获取一个提醒的ID。ID当然是从cuckoo的接口中返回的,这就需要能够解析JSON格式的文本。在Emacs中解析JSON序列化后的文本可以用json这个库,示例代码如下(let ((s “{"remind":{"create_at":"2019-01-11T14:53:59.000Z","duration":null,"id":41,"restricted_hours":null,"timestamp":1547216100,"update_at":"2019-01-11T14:53:59.000Z"}}”)) (cdr (assoc ‘id (cdr (car (json-read-from-string s))))))既然知道如何解析(同时还知道如何提取解析后的内容),那么接下来便是要能够获取上述示例代码中的s。s来自于HTTP响应的body,为了发出HTTP请求,可以用Emacs的request库,示例代码如下(let* ((this-request (request “http://localhost:7001/remind” :data “{"timestamp":1547216100}” :headers ‘((“Content-Type” . “application/json”)) :parser ‘buffer-string :type “POST” :success (cl-function (lambda (&key data &allow-other-keys) (message “data: %S” data))) :sync t)) (data (request-response-data this-request))) data)此处的:sync参数花了我好长的时间才捣鼓出来——看了一下request函数的docstring后才发现,原来需要传递:sync为t才可以让request函数阻塞地调用,否则一调用request就立马返回了nil。现在需要的就是构造:data的值了,其中的关键是生成秒级的UNIX Epoch时间戳,这个时间戳可以通过TODO条目的SCHEDULED属性转换而来。比如,一个条目的SCHEDULED属性的值可能是<2019-01-11 Fri 22:15>,将这个字符串传递给date-to-time函数可以解析成代表着秒数的几个数字(date-to-time “<2019-01-11 Fri 22:15>")时间戳字符串要怎么拿到?答案是使用org-mode的org-entry-get函数(org-entry-get nil “SCHEDULED”)PS:需要先将光标定位在一个TODO条目上。至此,所有的原件都准备齐全了,最终我的Elisp代码如下(defun scheduled-to-time (scheduled) “将TODO条目的SCHEDULED属性转换为UNIX时间戳” (let ((lst (date-to-time scheduled))) (+ (* (car lst) (expt 2 16)) (cadr lst))))(defun create-remind-in-cuckoo (timestamp) “往cuckoo中创建一个定时提醒并返回这个刚创建的提醒的ID” (let (remind-id) (request “http://localhost:7001/remind” :data (json-encode-alist (list (cons “timestamp” timestamp))) :headers ‘((“Content-Type” . “application/json”)) :parser ‘buffer-string :type “POST” :success (cl-function (lambda (&key data &allow-other-keys) (message “返回内容为:%S” data) (let ((remind (json-read-from-string data))) (setq remind-id (cdr (assoc ‘id (cdr (car remind)))))))) :sync t) remind-id))(defun create-task-in-cuckoo () (interactive) (let ((brief) (remind-id)) (setq brief (nth 4 (org-heading-components))) (let* ((scheduled (org-entry-get nil “SCHEDULED”)) (timestamp (scheduled-to-time scheduled))) (setq remind-id (create-remind-in-cuckoo timestamp))) (request “http://localhost:7001/task” :data (concat “brief=” (url-encode-url brief) “&detail=&remind_id=” (format “%S” remind-id)) :type “POST” :success (cl-function (lambda (&key data &allow-other-keys) (message “任务创建完毕”))))))在create-task-in-cuckoo中,之所以没有再传递application/json形式的数据给cuckoo,是因为不管我怎么测试,始终无法避免中文字符在传递到接口的时候变成了\u编码的形式,不得已而为之,只好把中文先做一遍url encoding,然后再通过表单的形式(form/x-www-urlencode)发送给接口了。全文完。 ...

February 5, 2019 · 1 min · jiezi