emacsでjqをJSONファイルのformatterとして使う
emacsでjqをJSONファイルのformatterとして使う。方法は2つ。
- shell-command-on-regionを使う
- formatter専用の関数を作る
shell-command-on-regionを使う
M-|
にshell-command-on-region
という関数がbindされている。これに引数を与えてあげると、現在選択されているリージョンを上書きしてくれる。
shell-command-on-region
の定義は以下の様になっている(引数の部分のみ)。
(defun shell-command-on-region (start end command &optional output-buffer replace error-buffer display-error-buffer region-noncontiguous-p) (interactive (let (string) (unless (mark) (user-error "The mark is not set now, so there is no region")) ;; Do this before calling region-beginning ;; and region-end, in case subprocess output ;; relocates them while we are in the minibuffer. (setq string (read-shell-command "Shell command on region: ")) ;; call-interactively recognizes region-beginning and ;; region-end specially, leaving them in the history. (list (region-beginning) (region-end) string current-prefix-arg current-prefix-arg shell-command-default-error-buffer t (region-noncontiguous-p)))) ... )
ここでreplaceの引数にnon-nilな値を与えてあげれば良い。例えば以下の様な形で使う。
C-u - 1 M-| # minibufferで jq . -S
formatter専用の関数を作る
shell-command-on-region
を使うのも便利なんだけれど。呼び出しが頻繁になってきた場合に以下の様な点で困る。
- エラーのときにもエラーメッセージで置換されてしまう
- そもそもjqとコマンドを打つのがめんどくさい
例えば、javascript-modeを拡張したjson-modeを自分で作ってあげて、拡張子が.json
のファイルを開いた場合には、この自作のjson-modeで開くようにする。
そして、この自作したmodeのkey-mapにテキトウなformmatter専用の関数を割り当てる。
(require 'derived) (define-derived-mode my:json-mode javascript-mode "json mode") (defun my:json-mode-setup () ;; 個人的にはバッファの保存は自動で行っているので`C-x C-s`に割り当ててしまっている (define-key my:json-mode-map (kbd "C-x C-s") 'my:jsonfmt) ) (add-to-list 'auto-mode-alist '("\\.json$" . my:json-mode)) (add-hook 'my:json-mode-hook 'my:json-mode-setup)
ここで、my:jsonfmtが自作したformatter関数。定義は以下の様な感じ。
(defun my:jsonfmt (beg end) (interactive "r") (unless (region-active-p) (setq beg (point-min)) (setq end (point-max)) ) (my:execute-formatter-command "jq" "jq . -S -e" beg end)) (defun my:get-fresh-buffer-create (name) (let ((buf (get-buffer-create name))) (with-current-buffer buf (setf (buffer-string) "")) buf )) (defun my:execute-formatter-command (cmd-name cmd beg end) (cond ((executable-find cmd-name) (save-excursion (save-restriction (narrow-to-region beg end) (let ((buf (my:get-fresh-buffer-create (format "*%s*" cmd-name))) (err-buf (my:get-fresh-buffer-create (format "*%s error*" cmd-name)))) (let ((status ;; xxx (flet ((display-message-or-buffer (&rest args) nil)) (shell-command-on-region (point-min) (point-max) cmd buf nil err-buf) ))) (cond ((= 0 status) (let ((replaced (with-current-buffer buf (buffer-string)))) (cond ((string= replaced "") (message "succeeded with no output")) (t (delete-region (point-min) (point-max)) (insert replaced))))) (t (message (with-current-buffer err-buf (buffer-string)))))))))) (t (message (format "%s is not found" cmd-name)))))
(メッセージを抑制するためにfletを使っているのだけれど。お行儀が良くない)