Deploy org agenda files on save
Table of Contents
1. Issued
我過往同步 org agenda files 方法:
- Github repo 用 git push/pull 去同步我的 agenda files。但是在多台電腦切換就會變得麻煩,而且不夠靈活。
- 把 org 文件直接放在移動硬盤裡。最大問題是會把硬盤遺留在某個機子上。
最後我選擇了直接寫 emacs lisp 吧!(雖然不太會寫…)但經過 Google search 找到幾個 function 發現能做到想要的功能 - 在用 Emacs 保存指定的 org files 時進行 sync。這裡的 sync 是把自己的 agenda view 上傳到服務器上, 這樣當我隨時隨地用瀏覽器打 https://some-domain.com/agenda 就可以查看到我的 agenda 啦!
2. org-mode-hook & after-save-hook
我發現 Emacs 方便地方在於它的很多 packages 或者 mode (php-mode, web-mode, python-mode,etc…) 它都會帶一個 hook
function。 在 Emacs 啟動某個 mode 的時候就會觸法它的 hook
。 這個設計在很多系統, Web Framework 等,都有它的身影。 比如 Ruby on Rails:
在 User record 創建之前 call MaybeAddName
class User < ApplicationRecord before_create MaybeAddName end class MaybeAddName def self.before_create(record) if record.name.blank? record.name = record.login.capitalize end end end
有了這個 org-mode-hook
就可以把我們想運行的代碼用這個 hook 掛到 org mode上, 當 org mode 載入 emacs buffer 時就會運行:
(add-hook 'org-mode-hook 'my/upload-agenda-after-save-hook)
第一個條件達成,接下來就是當 org 文件用 Emacs 儲存的時候觸發上傳 agenda view 功能, 好在 Emacs 優秀的 api 設計,在 Emacs save 一個文件的時候就會觸發 after-save-hook
, 於是:
(defun my/upload-agenda-after-save-hook () (add-hook 'after-save-hook 'my/agenda-deploy))
這樣需要的鉤子就完成了,我們的 my/agenda-deploy
只會在 org mode 加上儲存 org 文件時才觸發。
3. s-prefix? & buffer-file-name
接下來問題是,我們不需要所有 org files 在儲存的時候都上傳,我們只需要指定特定幾個文件來做就可以了。
首先這是我目前的 org 目錄。
org ├── deploy.sh ├── gtd │ ├── agenda.org │ ├── done.org │ ├── inbox.org │ ├── project.org │ └── read.org
實際上我的 agenda files 都在 ~/org/gtd/
里面,所以最簡單就是直接檢查當前 Org files 的文件目錄是不是 under org/gtd 目錄裡面。
直接運行這個 command 就可以獲得當前正在編輯的文件名加上 absolute path。
(buffer-file-name)
有了這個 absolute path 我們就可以知道它是不是 under 在 org/gtd裡
(s-prefix? (expand-file-name "~/org/gtd/") (buffer-file-name (current-buffer)))
expand-file-name
是方便 function, 获得指定目录或者文件的 absolute path。
(expand-file-name "~/org/gtd/")
s-prefix?
它是 s-starts-with?
的 alias, 同一个function。 而 s-starts-with?
在 Doc 里面一句簡單易懂的解釋:
Does S start with PREFIX?
這個解釋直接就是我們想要的。
這是一個條件判斷,我們把 s-prefix?
放在 when
裡面:
(when (s-prefix? (expand-file-name "~/org/gtd/") (buffer-file-name (current-buffer))) ( ;; deploy action here.... ) )
4. Export Agenda view
在 deploy agenda view 之前,要把我的 agenda view export 出來。
(add-to-list 'org-agenda-custom-commands '("g" "Get Things Done (GTD)" ( (tags "inbox+TODO=\"TODO\"+PRIORITY=\"A\"|project+TODO=\"TODO\"+PRIORITY=\"A\"|read+TODO=\"TODO\"+PRIORITY=\"A\"" ( (org-agenda-prefix-format " %?-12t% s [%e] ") (org-agenda-overriding-header "\nGet thing Done\n"))) .... nil ("~/org/index.html")))
這個是我目前自己用的 custom agenda view,你會看到在最後的 ~~/org/index.html~,便是 agenda 最終export 的目的地。
運行這個 command, emacs 就可以根據後綴去 export agenda view, 我這裡要上傳到 Web Server 所以後綴是 html。
(org-store-agenda-views)
5. Deploy Agenda html
上傳某文件到服務器的命令就是
scp -r ./index.html youruser@yourserver:/var/www/html/agenda/
我把它寫在了 deploy.sh
裡面。
6. start-process
最後一步就是用 Emacs 去 call 這個 deploy.sh
在 Emacs 運行系統指令:
(shell-command "ls")
但直接 (shell-command './deploy')
會讓 Emacs 卡住, 因為 scp 是要連結伺服器,上傳文件,斷開連結這些動作,簡單來說我們讓它背後自己跑就可以了,不影響emacs 進程。
我們需要用到 start-process
- (start-process NAME BUFFER PROGRAM &rest PROGRAM-ARGS)
(let ((default-directory "~/org/")) (start-process "" nil "sh" "deploy.sh"))
default-directory
將 scope 裡面的目錄跳到指定目錄,這樣就省下打一長串的 path 了。
最後把所有東西加上
(defun my/agenda-deploy () (when (s-prefix? (expand-file-name "~/org/gtd/") (buffer-file-name (current-buffer))) (let ((default-directory "~/org/")) (shell-command "rm index.html") (org-store-agenda-views) (start-process "" nil "sh" "deploy.sh")) )) (defun my/upload-agenda-after-save-hook () (add-hook 'after-save-hook 'my/agenda-deploy)) (add-hook 'org-mode-hook 'my/upload-agenda-after-save-hook)