シングルファイルのEmacs Lispをダウンロードして読み込む
Emacsを使っていると小さなEmacs Lispがどんどん増えていく。通常は ~/.emacs.d/init.el
に記述したり、 ~/.emacs.d
にディレクトリ(例: ~/.emacs.d/lisp
)を作り、そのパスをload-pathに追加する事で、利用できるようにする事が多い。
そこそこ育ったEmacs LispはMELPAに登録し、そこからインストールして使用するという方法もある。その方法は、多くの人が使うものであれば望ましいけれど、自分だけの要求を満すだけのLispであれば、自分自身で気軽に修正できる近い距離にあった方が、管理コストを押える事ができるし、その後の保守が行き届く。パッケージアーカイブに登録されたまま、放置され続けるEmacs Lispは沢山ある。そうなるぐらいなら、保守しやすい距離にLispがある方が良いはずだ。
これらの方法はルールがあるようにも思えるけれど、実は特にルールなんて存在しないんじゃないのだろうか。かつては共有されたEmacs Lispを、思い思いの方法で適当なディレクトリにダウンロードし、パスを通して使うような事をしていた筈だ。EmacsWikiに張り付けられたEmacs Lispのように。その取り決めのないカオスな状態から、時代が進むにつれ取り決めができ、手段が整備されてきた。それは良い事だと思うけれど、自分自身が管理する全てのEmacs Lispまで足並みを揃えないといけないなんてルールはない。ルールに従って手数が増えてしまうぐらいなら、ルールに従わず手数が少なくし、その変わりに保守できる方が良い。
そんな事を考えて、自分自身のEmacs Lispの保守の仕方を試行錯誤したりしていた。MELPAの仕組みを参考に自分自身のELPAリポジトリを作成したり、ElgetやQuelpaを使ったり、GitやGistにEmacs Lispをホスティングしたり、それこそ ~/.emacs.d/lisp
配下に雑にelファイルを配置したりした。それぞれの手段は当然良い面もあれば悪い面もある。そういう気付きを得られた事は良かった。
今回はその試行錯誤の一環として、自分用のEmacs Lispのダウンローダを実装した。要はQuelpaやElgetのようなものなのだが、それらより圧倒的に機能が少ない。なぜそんな100番煎じのような事をするのかというと、一重に他のツールは機能が多すぎるからだ。いろんな事が出来るという事は、その分ややこしいとも言える。理想な状態というのは、必要な機能が全てあり、それ以外の機能がない事だと思う。必要な機能というのは人によって異なるから、正解という状態はないのだろうけれど、自分が現在考える最も良い方法を試してみて、実際にどうかを確かめたくなった。
数年前からブログを書いていて、しばしばそこにEmacs Lisp自体も記述している。簡単で小さなコードだから、GitHubに専用のリポジトリを用意したりMELPAに登録したりといった仰々しい管理はしたくない。また記事として文章でコードの説明をしていたりするので、それらをそのまま使用できると良さそうだった。
そこで、次の処理を行う事にした。
- Emacs Lispファイルがあるかどうかを確認し、あればそのディレクトリをload-pathに追加する。
- Emacs Lispファイルが無ければ、インターネットからダウンロードし、ダウンロード先のディレクトリをload-pathに追加する。
- ただし、ダウンロードしたものが正しいものかどうかをチェックするために、事前にハッシュ値を記述しておき、一致したもののみ使える状態とし、一致しなければ破棄する。
コードを掲載しておく。
;;; getelisp --- Simple Emacs Lisp Library downloader. -*- lexical-binding: t -*-
;; Copyright (C) 2023 TakesxiSximada
;; Author: TakesxiSximada
;; Maintainer: TakesxiSximada
;; Version: 1.2
;; Package-Version: 20231009.0000
;; Package-Requires: ((emacs "29.1"))
;; Date: 2023-10-09
;; This file is part of getelisp
;;; License:
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; Affero General Public License for more details.
;; You should have received a copy of the GNU Affero General Public
;; License along with this program. If not, see
;; <https://www.gnu.org/licenses/>.
;;; Code:
(require 'plz)
(defvar getlisp-hash-solt "*GETLISP*"
nil)
;;;###autoload
(defun getelisp-show-checksum-current-buffer ()
(interactive)
(let ((hash-val (secure-hash
'md5 (concat getlisp-hash-solt (buffer-string)))))
(message "Checksum Value: %s" hash-val)
(kill-new hash-val)))
;;;###autoload
(defun getelisp-show-checksum (url)
(interactive "sURL: ")
(plz 'get url :as 'response
:then (lambda (resp)
(let ((hash-val (secure-hash 'md5 (concat getlisp-hash-solt
(plz-response-body resp)))))
(message "Checksum Value: %s" hash-val)
(kill-new hash-val)))))
;;;###autoload
(defun getelisp (file-path url hash-val)
(let* ((abspath (expand-file-name file-path))
(absdir (file-name-directory abspath)))
(if (file-exists-p abspath)
(add-to-list 'load-path absdir)
(plz 'get url :as 'response ;; ファイルが存在しなければダウンロードを試みる
:then (lambda (resp)
;; ハッシュが一致するか確認する。一致しない物は怪しいデータと考えて受け入れない。
(let* ((body (plz-response-body resp))
(hash-val-actual (secure-hash 'md5 (concat getlisp-hash-solt body))))
(if (string-equal hash-val hash-val-actual)
(progn
(with-temp-buffer ;; ファイルへ書き込む
(insert body)
(make-directory absdir t) ;; directoryを作成
(write-region (point-min) (point-max) abspath)) ;; ファイルへ書き込む
(message "append to load-path: %s" absdir)
(add-to-list 'load-path absdir)) ;; パスの追加
(error "bad checksum: expected=%s actual=%s" hash-value hash-val-actual))))))))
(provide 'getelisp)