cuckoo是一个我本身开发的相似待办事项的工具,运行在我本地的电脑上。它有以下两个接口:html
这样一来,便能在设定的时刻调用alerter
在屏幕右上角弹出提醒。git
我喜欢用Emacs的org-mode来安排任务,但惋惜的是,org-mode没有定点提醒的功能(若是有的话但愿来我的打个人脸XD)。开发了cuckoo后,突然灵机一动——何不给Emacs添砖加瓦,让它能够把org-mode中的条目内容(所谓的heading)当作任务丢给cuckoo,以此来实现定点提醒呢。感受是个好主意,立刻着手写这么些Elisp函数。github
PS:读者朋友们就不用执着于个人cuckoo到底是怎样的接口定义了。json
为了实现所须要的功能,让我从结果反过来推导一番。首先,须要提炼一个TODO条目的标题和时间戳(用来建立提醒获取ID),才能调用cuckoo的接口。标题就是org-mode中一个TODO条目的heading text,在Emacs中用下面的代码获取app
(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库,示例代码以下this
(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
)发送给接口了。
全文完。
【阅读原文】