hexo 與 Emacs 的整合

注意 Attention

中文

這篇文章已經被拋棄,因為現在我已經把所有 Hexo 相關的功能獨立出來,順便加強整個重寫成一個真正的 major-mode 並放在 kuanyui/hexo.el內了,有興趣、想使用的請自行去抓來看,以後如果有更新也會在那裡。
這篇文章不會再更新。

English

This article has been deprecated. Because now I have rewritten all following functions and do more & more jobs as a real major-mode in kuanyui/hexo.el. If you want to use Hexo in Emacs, please take a look of that. Any update in future will be pushed onto that repositor

This article will NOT be updated anymore.


由於我幾乎完全在 Emacs 下使用 hexo,所以要好好地利用 Emacs 的擴充性,弄了幾個讓 hexo 操作更方便的 function:

> 讓寫 Markdown 更方便,你也可以參考:
> - 使用 Flickr API 在 Markdown-mode 中插入圖片
> - 使用 url.el 快速插入 Markdown 連結

# 到處 hexo new
只要在 hexo 的子目錄中任何地方使用 M-x hexo-new 即可新增文章,不需要手動 cd 到 hexo 的根目錄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(defun hexo-new ()
"Call `hexo new` anywhere as long as in any child directory
under a Hexo repository.
That's to say, you can use this function to create new post, even though
under theme/default/layout/"
(interactive)
(let* (BUFFER-STRING
OUTPUT
(DEF-DIR (if (boundp 'DEF-DIR)
DEF-DIR
default-directory)))
(if (file-exists-p (format "%s%s" DEF-DIR "_config.yml"))
(progn
(with-temp-buffer
(insert-file-contents (format "%s%s" DEF-DIR "_config.yml"))
(setq BUFFER-STRING (buffer-string)))
(if (and (string-match "title: " BUFFER-STRING)
(string-match "url: " BUFFER-STRING)
(string-match "new_post_name: " BUFFER-STRING))
;; call `hexo new` command
(let ((default-directory DEF-DIR))
(setq OUTPUT (shell-command-to-string
(concat "hexo new '"
(read-from-minibuffer
"Title of the new article: ") "'")))
(string-match "/.*\\.md$" OUTPUT)
(find-file (match-string 0 OUTPUT)))
(progn (setq DEF-DIR (file-truename (concat DEF-DIR "../")))
(hexo-new))))
(progn
(if (not (equal DEF-DIR "/"))
(progn (setq DEF-DIR (file-truename (concat DEF-DIR "../")))
(hexo-new))
(progn
(message "Not in a hexo or its child directory.")))))))


# 讓文章依照 date: 排序
用 hexo 這類的 static page generator 會有個問題,就是當修改了舊文章,他的檔案修改時間會被更新,這樣的話從 Dired 檔案列表會很難看出哪篇文章其實才是最新的。

下面這個 function 會根據目前目錄中的所有 *.md 的檔案開頭中的 date:(沒有的話會爆掉) 來 touch -t 該檔案,如此一來就能在 Dired 中按照時間排序文章列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(defun hexo-touch-files-in-dir-by-time ()
"`touch' markdown article files according their \"date: \" to
make it easy to sort file according date in Dired.
Please run this under _posts/ or _draft/ within Dired buffer."
(interactive)
(if (and (eq major-mode 'dired-mode)
(or (equal (buffer-name) "_posts")
(equal (buffer-name) "_draft")))
(progn
(let (current-file-name file-list)
(setq file-list (directory-files (dired-current-directory)))
(progn
(mapcar
(lambda (current-file-name)
(if (and (not (string-match "#.+#$" current-file-name))
(not (string-match ".+~$" current-file-name))
(not (string-match "^\.\.?$" current-file-name))
(string-match ".+\.md$" current-file-name))
(let (touch-cmd head)
(setq head
(shell-command-to-string
(format "head -n 5 '%s'" current-file-name)))
(save-match-data
(string-match "^date: \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\)$" head)
(setq touch-cmd
(format "touch -t %s%s%s%s%s.%s %s"
(match-string 1 head)
(match-string 2 head)
(match-string 3 head)
(match-string 4 head)
(match-string 5 head)
(match-string 6 head)
current-file-name
)))
(shell-command touch-cmd))
))
file-list))) ;; 這個 file-list 為 lambda 的 arg
(revert-buffer)
(message "Done."))
(message "Please run this under _posts/ or _drafts/ within Dired buffer.")))


這個跑起來有個問題:超慢。原本我以為是檢查文章內是否包含 string 時使用了 (buffer-string) 來輸出整個 buffer 內容而拖慢速度(這也是令人納悶的一點,Emacs Lisp 沒辦法簡單地辦到「把檔案頭 n 行存成 string」這種事)。不過後來發現癥結似乎不在這,因為改成用 head -n 還是一樣慢 orz。

我不知該怎麼檢查到底是哪裡拖慢速度,regexp 那段我也不是很清楚怎樣做會比較有效率。嗯…不過反正可以用,而且也不是很常需要跑這個。

# Switch between _posts and _drafts
將當前檔案在 _post 與 _drafts 兩者之間切換。

如果當前檔案在 _drafts 內,則移動到 _posts 中。
反之亦然。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(defun hexo-move-article ()
"Move current file between _post and _draft;
You can run this function in dired or a hexo article."
(interactive)
(if (string-match "/\\(_posts/\\|_drafts/\\)$" default-directory)
(let* ((parent-dir (file-truename (concat default-directory "../")))
(dest-dir (if (string-match "_drafts/$" default-directory) "_posts/" "_drafts/")))
(cond ((eq major-mode 'markdown-mode)
(let* ((cur-file (buffer-file-name))
(new-file (concat parent-dir dest-dir (buffer-name))))
(save-buffer)
(kill-buffer)
(rename-file cur-file new-file)
(find-file new-file)
(message (format "Now in %s" dest-dir))))
((eq major-mode 'dired-mode)
(dired-rename-file (dired-get-filename nil)
(concat parent-dir dest-dir (dired-get-filename t))
nil)
(message (format "The article has been moved to %s" dest-dir)))))
(message "You have to run this in a hexo article buffer or dired")))


# Update article date stamp
有時候需要更新文章的時間(檔案開頭處的 date:),用這個可以幫你更新到目前時間。
慎用這個 function,因為當你的文章 deploy 方式是設定成按照時間分資料夾(如/2014/06/26/article.html),就會影響到你的文章連結。
1
2
3
4
5
6
7
8
9
10
11
12
13
(defun hexo-update-current-article-date ()
"Update article's date by current time.
Please run this function in the article."
(interactive)
(save-excursion
(goto-char (point-min))
(save-match-data
(if (re-search-forward "^date: [0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}" nil :no-error)
(let ((current-time (format-time-string "date: %Y-%m-%d %H:%M:%S")))
(replace-match current-time)
(save-buffer)
(message (concat "Date updated: " current-time)))
(message "Didn't find any time stamp in this article, abort.")))))



[2014-06-26 木 13:08] 更新 function,修正 bugs,移除 dependencies。
[2014-01-22 水 12:00] typo fix
- hexo-touch 2 小時 07 分
- hexo-new 1 小時 52 分
- hexo-move 與外加寫這篇文章 2 小時 20 分
[2014-01-22 水 11:44]寫完惹 QAQ
[2014-01-22 水 02:33] 我到底在幹麼勒。
[2016-02-29 月 20:01] 加上 hexo.el 免得有人再用這個頁面裡的東西。