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
)發送給接口了。
全文完。
【閱讀原文】