blog
DONE License
License
- All articles (sentence) are distributed under CC BY.
Creative Commons Attribution 4.0 International Public License (CC BY) Copyright (c) Hiroshima.lisp - https://lisp.netlify.app https://creativecommons.org/licenses/by/4.0/legalcode - All source code are distributed under CC0.
- All cited source code respects the license of the cited source.
- GPLv3 - Emacs related source code.
- MIT
Dependent materials/sources license
- Hiroshima.lisp icon made by freepik from www.flaticon.com (Term of use).
- Font Awesome - SIL OFL 1.1
- Feather icons - MIT
- jQuery - MIT
- Bootstrap - MIT
- Hugo - Apache 2.0
- Go - BSD
Using Hugo blog theme
Respected Hugo blog theme
DONE Netlify + Hugo + ox-hugoでブログを初める第一歩
Intro
HTMLジェネレータのseml-mode.elとOrg->HTMLジェネレータ拡張のorglyth.elを作っていて、自分のブログくらいはそれらを使って構築しようと思っていました。
しかし結局落ち着かないまま1年が経過し、leaf.elのドキュメントや、専門分野のまとめなどネットで公開したいものだけ溜まっていました。
conao3.comのドメインも2017年から持っていますが、3年もの間まともなコンテンツは提供されませんでした。
ここらへんで諦めてさっさとHugoとNetlifyで公開してしまえということで、きちんと整備されたツールを使わせてもらい、この度ようやくconao3.comでまともなコンテンツを提供できるようになりました。
Netlify
よい情報で溢れているので省略します。
NetlifyはDNSも提供しているようなので、「さくらのドメイン」で取得したconao3.comをNetlify DNSで解決させるようにしました。
また、デフォルトでは古いHugoが動いているようなので、「Build & Deploy」->「Environment」->「Environment variables」で「HUGO_VERSION」をローカルのバージョンで設定します。 私の場合「0.69.2」でした。
Hugo
Hugoのインストール
Install Hugoを参考にHugoをインストールします。
記載がありませんが、Archの場合はpacmanで簡単に入ります。(Archlinux - Package)
# pacman -S hugo $ hugo version Hugo Static Site Generator v0.69.2/extended linux/amd64 BuildDate: unknown
ブログテンプレートの展開
Quick Startを参考にテンプレートを展開します。
私はローカルのレポジトリ置き場は
~/dev/reposにしています。$ cd ~/dev/repos $ hugo new site netlify-src以下、
netlify-srcはhugoサイトのルートディレクトリということにします。
ブログテーマの適用
Hugo themesからいい感じのテーマを探し、submoduleでレポジトリに持ちます。
テーマはローカルで簡単にカスタマイズできる仕組みがあるので、多少気に入らないところがあっても大丈夫です。
$ cd quickstart $ git init $ git submodule add https://github.com/lxndrblz/anatole.git themes/anatoleテーマごとにカスタマイズする方法が違うので、テーマのREADMEを読んで設定をして下さい。
なお、私は当時ローカルで簡単にカスタマイズできる機構を知らず、forkでカスタマイズするしかないと思っていたので、「Make a Hugo blog from scratch」を参考に0からテーマを作りました。
私が作ったテーマはconao3/anatole-extで公開しています。
サンプル記事の追加
サンプルの記事を追加します。
テーマが
exampleSiteというフォルダを持っている場合は、それらのファイルをnetlify-src/にコピーするだけでいいです。コマンドでテンプレートを作成するには次のコマンドを実行します。 実行後、
content/blog/my-first-post.mdが作成されているので、よしなに編集します。$ hugo new blog/my-first-post.md
ローカルサーバーの起動
Hugoは簡単にローカルサーバーを起動でき、さらに依存ファイルが変更されたときに自動でブラウザにリロードさせることができます。
$ hugo server -Dサーバーを起動すると
localhost:1333で配信されます。
静的サイトのビルド
サイト全体のビルドは下記で行います。
Netlifyで公開するならローカルでビルドする必要はないのですが、最終的にどんなツリーになっているかを確認することができます。
$ hugo
ox-hugo
ox-hugoのインストールと設定
ox-hugoをインストールし、適宜設定を行ないます。
「hugo | IMADENALE」を参考に無難な構成を考えましたが、もっといい方法がある気がします。 とりあえず私は下記の設定と方針でしばらくやってみます。
(leaf ox-hugo :doc "Hugo Markdown Back-End for Org Export Engine" :req "emacs-24.4" "org-9.0" :tag "docs" "markdown" "org" "emacs>=24.4" :added "2020-05-05" :url "https://ox-hugo.scripter.co" :emacs>= 24.4 :ensure t :after ox :custom ((org-hugo-front-matter-format . "yaml")))
orgファイル管理の方針
ox-hugo用のorgファイルは
netlify-src/org/{{user}}.orgに持つことにしました。#+title: Hugo blog source #+author: conao3 #+date: <2020-05-05 Tue> #+options: ^:{} #+hugo_base_dir: ../ #+hugo_section: blog #+link: files file+sys:../static/files/ * blog :PROPERTIES: :EXPORT_HUGO_SECTION: blog :END: ** DONE test :meta:hugo: CLOSED: [2020-05-05 Tue 19:21] :PROPERTIES: :EXPORT_FILE_NAME: test :EXPORT_DATE: 2020-05-05T00:00:00+09:00 :EXPORT_HUGO_LASTMOD: [2020-05-05 Tue 16:20] :END: testestest.もし将来的にこのorgファイルが超巨大なファイル(1万行~)になれば、適宜
netlify-src/org/archive-{{user}}-{{num}}.orgに移すことにします。レベル1はセクションの分類に使い、レベル2のheadingから記事のツリーと解釈されます。
静的ファイル管理の方針
filesのlinkは「画像の埋め込みテスト | imadenale」を参考にしました。
スクリーンショットやPDFは
netlify-src/static/files以下に持つことにします。netlify-src/staticはレポジトリ肥大化を避けてconao3/netlify-srcからconao3/netlify-src-blobに切り出し、submoduleで持つことにします。参考記事ではfilesのリンクをURLで設定していましたが、「Org Modeのリンク機能で情報集約 | Qiita」を参考に
fils+sys:指定を使うとorgの画像インライン表示もできますし、きちんとox-hugoによってリンクが修正され、正しいマークダウンが出力されました。Input
#+link: files file+sys:../static/files/ #+attr_html: :width 128px [[files:logo.jpg]]Output
{{< figure src="/files/logo.jpg" width="128px" >}}Rendering

Deploy
NetlifyのデプロイはGitHubにpushするだけです!
Hugoのビルドはとても早く、pushしてNetlifyのログを見に行くともう終わっています。
ビルドできたら個人Slackに通知飛したりするのをまた今度やりたいと思っています。
Conclusion
Netlify + Hugo + ox-hugoで快適なブログ執筆環境を整えることができました!
ようやく情報発信する環境が整ったので、どんどん情報を発信していきたいと思います。
まずは見やすいleaf.elのドキュメントを書きます!
また、執筆現在はconao3/netlify-srcとconao3/netlify-src-blobをパブリックレポジトリとしていますが、後でプライベートレポジトリに変更し、Patreonの特典にします。
本来公開するものを非公開にすることでしか価値を付けられないことをお許しください。
ぜひPatreonで私の活動のサポートをして頂けるとうれしいです!
DONE 個人ブログのパーマリンクに関する最適戦略について
Intro
Webで情報を公開するにあたって、パーマリンクの設計は将来に渡って重要なものです。
このブログは人生において3つめのブログであり、これまでの2つのブログは勝手にアーカイブしてネットから消してしまいました。
しかしこのブログは今のところ死ぬまで付き合うつもりであり、それが出来るようにコンテンツの少ない今、設計する必要があります。
ブログ管理の裏側については「Netlify + Hugo + ox-hugoでブログを初める第一歩」に書きましたが、パーマリンクの設計は表側の設計であり、ブログの人生の中で途中で変えることは許されていません。
もし無理矢理変えれば、これまでシェアしてもらったURLの意味は失くなり、404ページに多くの読者を案内してしまうことになります。
さて、このブログではどのようにパーマリンクを設計すると良いのでしょうか。
調査
パーマリンクの設計といっても選択肢は多くありません。
そもそも使える情報が タイトル と 公開日時 と 連番 くらいしかないので、代表的なものは以下のような形式か、その組み合わせです。
{{base-url}}/{{title}}/{{base-url}}/{{short-title}}/{{base-url}}/{{category}}/{{short-title}}/{{base-url}}/{{num}}/{{base-url}}/{{year}}/{{month}}/{{day}}/
{{title}} はそのまま記事のタイトルを使う方法です。
日本語のタイトルはそのままURLエンコードされ、URLになるパターンです。
{{short-title}} はSEOに強い、記事ごとにつけられた短いタイトルを使う方法です。
あえて日本語を使う理由もなく、普通は英数字のみで長くても30文字くらいでまとめます。
{{category}} は記事ごとに設定されたカテゴリーを使う方法です。
{{num}} はサイトごとにカウンタを持っておいて、その連番をURLに使用する方法です。
{{year}}, {{month}}, {{day}} は記事の公開日です。
評価
さて、これらの方法のなかで、どれがよいのでしょうか。
大きく分けると2つの分類ができ、記事ごとに新たに考える方法と自動で生成する方法です。
記事ごとに新たに考える方法
{{short-title}}- メリット
- SEOやURLシェアの観点から最高の選択肢
- デメリット
- 記事ごとに短いタイトルを英語でつけなおさなければならない
- 記事を少ない英単語で表現するのは一般的に難しく、長くなってしまう場合もある
- 少ない英単語で命名するため、URL衝突を考慮する必要がある
- もし、Emacsと命名した記事より、もっと適切な記事を書いた場合にその名前はもう使えない
- メリット
{{category}}- メリット
- URLから得られる情報がある
- カテゴリー名は記事名に比べて、ずっと少ないので、命名はあまり苦でない
- デメリット
- こうもり問題の発生 (カテゴリ管理の問題点)
- メリット
自動で生成する方法
{{title}}- メリット
- 記事ごとに考えなくてよい
- デメリット
- 日本語のタイトルをつけるとURLが長大になる
- メリット
{{num}}- メリット
- 記事ごとに考えなくてよい
- キリ番記事が作りやすい (100記事達成記事など)
- デメリット
- 記事を後から非公開にすると、欠番が生じる
- URLから得られる情報が少ない
- メリット
{{year}},{{month}},{{day}}- メリット
- 記事ごとに考えなくてよい
- URLを見れば公開日が分かる (技術系記事において新しい記事は重要)
- デメリット
- URLから得られる情報が少ない
- メリット
というところでしょうか。 それぞれにメリットとデメリットがあるので、それを評価した後は好みの問題となります。
決定
前段の評価に基づいて、私はこのブログのパーマリンクを以下のように設計しました。
{{base-url}}/ blog /{{year}}/{{random}}
まず、私はとてもものぐさなので、記事ごとにショートタイトルを与える方法は難しいです。 いくらSEOやURLシェアの観点から最良の選択だと言っても、記事を書くモチベーションを失っては元も子もありません。
タイトルをそのまま付ける方法は、記事ごとに自動で決定できるので魅力的ですがきびしいです。各見出しの id は日本語をURLエンコードしたものをしかたなく使うとして、2つの要素をURLエンコードすると簡単に256文字を超えます。もちろん現代ではURLの流さに制限はないですが、限度はあると思います。
カテゴリー管理も同じような階層が現われ、破綻することが目に見えています。
実は前回のブログはそれで破綻したのです。 /emacs/ と /blog/emacs/, blog/advent/emacs/ などが乱立し、新たな記事をどの階層に作ればよいのか混乱する要因になりました。
連番を付ける方法も魅力的ですが、欠番が発生することに耐えられません。これは気分の問題です。
日付を使用する方法はとても良いと思います。 URLから得られる情報もありますし、後述するランダム値の衝突について「永遠」を考えなくてすみます。
ここまで、日付を使うことが決まり、タイトルもだめ、ショートタイトルもだめ、連番もだめといろいろな物を拒否した結果、一意性を確保するものとして使えるものはランダム値しかありません。
まとめ
使用するパーマリンクを設計することができました。
しかし、 {{random}} はどれだけのビット数を使い、どのようなフォーマットを使うべきでしょうか。
その考察は次の記事「誕生日のパラドックスから考察する、個人ブログURLに必要なランダムビット数」に譲るとして、この記事ではパーマリンク設計について大きな決定ができたので、とても満足しています。
なお、例外的に {{base-url}} / emacs / {{package-name}} というURLを使おうと思っています。
これはるびきちさんのパーマリンク設計を踏襲しています。
るびきちさんと同じようにEmacsのパッケージ紹介記事ではこのURLを使用しようと思います。
DONE 誕生日のパラドックスから考察する、個人ブログURLに必要なランダムビット数
tl;dr
- ブログのURLにランダム値を使うとして、必要なビット数は何桁だろうか
- ハッシュ衝突の問題は「誕生日のパラドックス」と同じ状況設定
- 数学的な裏付けの元で、ランダムビットを28桁確保すれば十分
Intro
「個人ブログのパーマリンクに関する最適戦略について」において、
{{base-url}}/ blog /{{year}}/{{random}}
というURLをこのブログに使うことに決めました。しかし、 {{random}} はどれだけのビット数を使い、どのようなフォーマットを使うべきでしょうか。
幸い、 {{year}} を入れているので、「永遠」を考える必要はありません。
せいぜい「1年」でどれだけの記事を書くのかを見積れば良いことになります。
毎日、1記事を書いたとして \(365\) 記事、多めに見積もって \(400\) 記事とします。 さらに毎日、 \(5\) 記事書くことを上限と仮定すると \(5 \times 400 = 2000\) 記事となり、せいぜい \(2000\) 記事がある場合に必要なランダムビット数を求めたいと思います。
誕生日のパラドックス
誕生日のパラドックス(たんじょうびのパラドックス、英: birthday paradox)とは「何人集まれば、その中に誕生日が同一の2人(以上)がいる確率が、50%を超えるか?」という問題から生じるパラドックスである。鳩の巣原理より、366人(閏日も考えるなら367人)集まれば確率は100%となるが、しかしその5分の1に満たない70人しか集まらなくても確率は99.9%を超え、50%を超えるのに必要なのはわずか23人である。
誕生日のパラドックスは論理的な矛盾に基づいているという意味でのパラドックスではなく、結果が一般的な直感と反しているという意味でのパラドックスである。
Wikipediaでも計算してありますが、ここでも計算しておきます。
きちんと問題として整理すると以下のようになります。
Aさんを含め、n人がいる。一年を365日とし、誕生日は全ての日で等確率とする。
- Aさんと同じ誕生日の人が存在する確率 \(P_1\)
- 同じ誕生日の人が存在する確率 \(P_2\)
- \(P_1\) と \(P_2\) が \(50\) %を超えるのにそれぞれ必要な人数 \(n_\text{min1}\), \(n_\text{min2}\)
Aさんと同じ誕生日の人が存在する確率 \(P_1\)
余事象で求める。
Aさん以外の人は \(\frac{364}{365}\) の確率でAさんの誕生日と衝突しない。 Aさん以外の人は \(n-1\) 人存在するので、
\[ P_1 = 1 - \qty(\frac{364}{365})^{n-1} \]
同じ誕生日の人が存在する確率 \(P_2\)
余事象で求める。
n人の誕生日が異なる確率は \[ \frac{364}{365}\cdot\frac{363}{365}\cdot\frac{362}{365}\cdots\frac{365-(n-1)}{365} = \frac{{}_{364}\mathrm{P}_{n-1}}{365^{n-1}} = \frac{{}_{365}\mathrm{P}_n}{365^n}\]
よって \[ P_2 = 1 - \frac{{}_{365}\mathrm{P}_n}{365^n} \]
\(P_1\) と \(P_2\) が \(50\) %を超えるのにそれぞれ必要な人数 \(n_\text{min1}\), \(n_\text{min2}\)
\(n_\text{min1}\) については計算できる。
\begin{aligned} P_1 = 1 - \qty(\frac{364}{365})^{n-1} &> 0.5 \\
0.5 &> \qty(\frac{364}{365})^{n-1} \\
\log_2(0.5) &> (n-1)\log_2\qty(\frac{364}{365}) \\
-1 &> (n-1)(-0.003958) \\
253.65 &< n \end{aligned}よって \(n_\text{min1}=254\) となる。
\(n_\text{min2}\) については面倒なので、Pythonを用いて図示する。
import matplotlib.pyplot as plt import numpy as np memo = np.full(400, -1.0) def NPn (n): """ 余事象の確率 n人の誕生日が異なる確率 """ if n == 0 or n == 1: memo[0] = 1 return memo[0] elif n >= 366: return 1; elif memo[n] != -1: return memo[n] else: p = NPn(n-1) * ((365-(n-1))/365.0) memo[n] = p return memo[n] def Pn (n): """ n人のうち、2人以上誕生日が同じ確率 """ return 1 - NPn(n) n = 70 with plt.style.context(('science-transparent')): y = [Pn(i) for i in range(n)] plt.plot(y) plt.xlabel('n') plt.ylabel('P_n') plt.axhline(0.5, color='C1') n_min2 = np.searchsorted(y, 0.5) plt.annotate('({}, {:.2f})'.format(n_min2, y[n_min2]), (n_min2, y[n_min2]), (n_min2-15, 0.8), arrowprops = dict(arrowstyle='-|>', mutation_scale=20)) plt.show()
グラフから、 \(n_\text{min2} = 23\) 。
上記のとおり \(n_\text{min1} = 254\), \(n_\text{min2} = 23\) となり \(P_\text{min2}\) は直感よりも少ないように思います。
直感との乖離の理由は「自分と同じ誕生日の人が存在する確率」と「同じ誕生日の組が存在する確率」を混同してしまうからです。
ランダム値の衝突
今回問題にしているようなランダム値の衝突については、 「あるひとつのランダム値が衝突する事象」ではなく、「どれでもいいのでランダム値が衝突する事象」を考える必要があるので、 まさに誕生日のパラドックスと同じ状況設定となります。
誕生日のパラドックスでは365個の集合でしたが、今回は変数 \(N\) とします。
前段の \(P_2\) を参考に、 \(N\) 個の集合から \(n\) 個ランダム値を取ってきたときに、同じ値が存在する確率 \(p\) は次の形となります。
\begin{aligned}
p &= 1 - \frac{N-1}{N}\cdot\frac{N-2}{N}\cdot\frac{N-3}{N}\cdots\frac{N-(n-1)}{N} \\
&= 1 - \qty(\qty(1-\frac{1}{N})\qty(1-\frac{2}{N})\qty(1-\frac{3}{N})\qty(1-\frac{n-1}{N})) \\
&= 1 - \prod_{m=1}^{n-1}\qty(1-\frac{m}{N})
\end{aligned}
ここで、ネイピア数のテイラー展開は次の形です。 \(x\) が十分小さいときは1次までの近似が使えます。
\begin{aligned}
e^{-ax} &= 1 - ax + a^2\frac{x^2}{2!} - a^3\frac{x^3}{3!} + a^4\frac{x^4}{4!} + \cdots \\
&\approx 1 - ax
\end{aligned}
\(N\) はランダム値の全体総数なので、 \(\frac{i}{N}\) は十分小さく、ネイピア数のテイラー展開から近似を使えます。
\begin{aligned}
p = 1 - \prod_{m=1}^{n-1}\qty(1-\frac{1}{N}m) &\approx 1 - \prod_{m=1}^{n-1}\exp\qty(-\frac{1}{N}m) \\
&= 1 - \exp\qty(\sum_{m=1}^{n-1}\qty(-\frac{m}{N})) \\
&= 1 - \exp\qty(-\frac{1}{N}\sum_{m=1}^{n-1}m) \\
&= 1 - \exp\qty(-\frac{n(n-1)}{2N})
\end{aligned}
さらに \(n\) について変形します。途中、 \(n\) が十分大きいことから、定数の減算を無視しました。
\begin{aligned}
\exp\qty(-\frac{n(n-1)}{2N}) &= 1-p \\
-\frac{n(n-1)}{2N} &= \ln(1-p) \\
n(n-1) &= -2N\ln(1-p) \\
n^2 &\approx 2N\ln\qty(\frac{1}{1-p}) \\
n &= \sqrt{2N\ln\qty(\frac{1}{1-p})}
\end{aligned}
数値計算
Nが与えられている場合
ランダム値の大きさ(\(N\))が与えられている場合、何個ランダム値を取り出したら(\(n\))衝突するでしょうか。 なお \(p=0.5\) とします。
\begin{aligned} n = \sqrt{2N\ln\qty(\frac{1}{1-0.5})} &= \sqrt{2N\ln2} \\
&\approx 1.1774\sqrt{N} \\
&\approx \sqrt{N} \end{aligned}最後の近似はオーダーを見積もるためにとても雑な近似を行いました。
もしランダムビットが 64bit の場合、ランダム値の集合は \(2^{64}\) となり、 \(2^{32} = (42.9\text{億})\) 個(WolframAlpha)のランダム値を生成したら、確率 \(0.5\) で衝突することになります。
Nを求めたい場合
逆に今回私は試行回数、1年 \(2000\) 記事(\(n=2000\))からランダム値の大きさ(\(N\))を求めたいと思っています。 なお、ランダム値の大きさ \(N\) はビット数 \(b\) を用いて \(N=2^b\) と書けます。
衝突する確率 \(p\) は \(1\) %と仮定します。
\begin{aligned} n &= \sqrt{2N\ln\left(\frac{1}{1-0.01}\right)} \\
2000 &= \sqrt{2N\ln\left(\frac{1}{0.99}\right)} \\
2000^2 &= 2N \cdot 0.0100 \\
N = 2^b &= 1000 \cdot 2000 \cdot 100 \\
b &= 27.5 \end{aligned}以上、ランダムビット数は28桁あれば十分ということになります。
16進数の表記を使えば1文字で4ビットの自由度があるので、結局、8文字あれば十分ということが分かりました。
まとめ
誕生日のパラドックスを導入としてランダム値の衝突問題(ハッシュ衝突問題)について考えました。
長々書いてきましたが、数学的な裏付けのもとで必要なランダムビット数が分かり、パーマリンクのフォーマットを決めることができました。
次の記事「ox-hugo用のorg-captureテンプレートについて」ではこのフォーマットを実現するためのox-hugoの運用について書きたいと思います。
参考
- 誕生日のパラドックス - Wikipedia
- 誕生日攻撃 - Wikipedia
- 同じ誕生日の二人組がいる確率について - 高校数学の美しい物語
- UUID(v4) がぶつかる可能性を考えなくていい理由 - Qiita
- 誕生日攻撃 (Birthday Attack) - 晴耕雨読
DONE ox-hugo用のorg-captureテンプレートについて
Intro
下記のブログの記事で考えて、ようやく記事を新規作成するために必要な情報が整いました。
- 「Netlify + Hugo + ox-hugoでブログを初める第一歩」
- 「個人ブログのパーマリンクに関する最適戦略について」
- 「誕生日のパラドックスから考察する、個人ブログURLに必要なランダムビット数」
org-captureはいつも設定しておきながら、設定したことを忘れているのですが、ox-hugoを使うにあたってきちんと入門したいと思います。
org-capture
org-captureはEmacsのどこにいてもorgでメモを取るためのフロントエンドです。
org-captureのユーザーはたくさんおり、設定例も溢れていますが、 「自分でグローバルバインドを設定しなければならない」という点が性に合わず、結局使わずじまいでした。
少し本題からずれますが、そもそも C-c につづくアルファベット、修飾キー付きのアルファベット、プレフィックスを自由に設定できるとして、覚えられる気がしません。
私のglobal-mapはtransient-dwimだけを設定しており、他のバインドはマイナーモード、メジャーモードのキーマップのみです。 transient-dwimはtransientを利用したキーバインド管理パッケージです。
transient-dwimを M-= に設定している場合、org-captureは M-= M-o M-o で起動できます。

ox-hugoの設定
「ファイル名をUUIDで自動生成するテスト - IMADENELE」と「最終変更日の手動設定 - IMADENELE」を参考にテンプレートを調整しました。
(leaf ox-hugo
:doc "Hugo Markdown Back-End for Org Export Engine"
:req "emacs-24.4" "org-9.0"
:tag "docs" "markdown" "org" "emacs>=24.4"
:added "2020-05-05"
:url "https://ox-hugo.scripter.co"
:emacs>= 24.4
:ensure t
:after org
:require t
:defun (org-set-property)
:custom ((org-hugo-front-matter-format . "yaml"))
:config
(defun c/ox-hugo-add-lastmod nil
"Add `lastmod' property with the current time."
(interactive)
(org-set-property "EXPORT_HUGO_LASTMOD"
(format-time-string "[%Y-%m-%d %a %H:%M]")))
(leaf *ox-hugo--capture
:require org-capture
:defvar (org-capture-templates)
:config
(add-to-list 'org-capture-templates
'("b" "Create new blog post" entry
(file+headline "~/dev/repos/netlify-src/org/conao3.org" "blog")
"** TODO %?
:PROPERTIES:
:EXPORT_FILE_NAME: %(apply #'format \"%s-%s-%s\"
(format-time-string \"%Y\")
(let ((sha1 (sha1 (shell-command-to-string \"head -c 1k /dev/urandom\"))))
(cl-loop for (a b c d) on (cdr (split-string sha1 \"\")) by #'cddddr repeat 2 collect (concat a b c d))))
:EXPORT_HUGO_TAGS:
:EXPORT_HUGO_LASTMOD:
:END:
"))
(add-to-list 'org-capture-templates
'("p" "Create new package post" entry
(file+headline "~/dev/repos/netlify-src/org/conao3.org" "emacs")
"** TODO %?
:PROPERTIES:
:EXPORT_FILE_NAME:
:EXPORT_HUGO_TAGS: emacs
:EXPORT_HUGO_LASTMOD:
:END:
"))))
p はパッケージの紹介用で emacs ツリー以下に展開されます。
b は簡単なブログ用で :EXPORT_FILE_NAME は「誕生日のパラドックスから考察する、個人ブログURLに必要なランダムビット数」をもとに8桁の16進数を使って自動生成します。
8桁の16進数は /dev/urandom から1KB読んで、それのsha1の先頭8桁のみを取ることにしました。
この乱数は衝突するときわめて面倒なことになるので、できるだけ均質なランダム値を得る必要があります。
この仕組みを作った後は衝突しないことをもう祈るしかない。。(もし衝突すると単に新しい記事で古い記事が上書きされて、特に警告は出ない。。)
まとめ
org-captureの設定をすることによってブログ記事を書くハードルがとても下がりました。
これから情報発信に努めていきたいと思います!
TODO Arch Linuxでいい感じに揃ったスクリーンショットを撮り、管理する方法
Netlify + Hugo + ox-hugoでブログを初める第一歩
TODO Test
test test test
test test test
平方根の高さを揃える \(\sqrt{g}\) に \\(\sqrt{h}\) は \mathstrut と \smash コマンドを使って \(\ssqrt{g}\) と \(\ssqrt{h}\) のように表示できる。
test
\[a\]
\[\RR\]
\[\div a\]
\[\sin(\frac{a}{b})\]
\[a \between b\]
TODO VMにArch Linux入れて初期設定するまで
intro
DONE 令和時代のEmacsカラーテーマの作り方
Intro
昨日の夜あまりにも進捗が出ないので、寝てしまおうと思ったらこんな感じのツイートを不覚にも見てしまいました。
1000とかすごいですね! おめでとうございます!
— Nyoho (@NeXTSTEP2OSX) May 8, 2020
これMITライセンスということはIcebergなEmacsのカラーtheme作ってもいいわけですね〜! やったー
ちょっと興味を引かれたので、さっとEmacsポートを作る気になりました。 今のSolarizedなら簡単にできるはずです。
「今の」というのは、私が出したパッチ「[new feature] solarized.el as multi color scheme」が入ったSolarized。
いろいろ議論を大きくしてしまいましたが、結局私のコードは元気に動いており、私もいつも自作カラーパレットでSolarizedを使っています。
今回、iceberg-theme.elをどのように作ったのか、まとめておきます。
成果物はこちら。iceberg-theme.el

前提知識
Emacsにおけるface
faceとは下記のようにdeffaceで各パッケージで定義されており、ユーザーのディスプレイ環境に応じて様々な色を設定できるようになっています。下記の
faceの例において、featherの例ではbackgroundがdarkかlightかで色の分岐を行なっていますし、Emacs標準添付パッケージのisearchではbackground判定に加え、最大表示色数も考慮されています。(defface feather-dashboard-header '((((background dark)) :background "#223377" :foreground "white" :weight bold :height 1.3 :family "Sans Serif") (((background light)) :background "#abd7f0" :foreground "black" :weight bold :height 1.3 :family "Sans Serif")) "Face for feather-dashboard header." :group 'feather) (defface isearch-fail '((((class color) (min-colors 88) (background light)) (:background "RosyBrown1")) (((class color) (min-colors 88) (background dark)) (:background "red4")) (((class color) (min-colors 16)) (:background "red")) (((class color) (min-colors 8)) (:background "red")) (((class color grayscale)) :foreground "grey") (t (:inverse-video t))) "Face for highlighting failed part in Isearch echo-area message." :version "23.1")あとはこの
faceを言語をパース後、ある意味のところに貼り付けたり、オーバーレイである文字のところに適用したりして使います。
Emacsにおけるカラーテーマ
Emacsのカラーテーマはこれらパッケージが提供する
faceを一括で変更してしまうものです。具体例はemacs-jp/replace-colorthemesにたくさんあります。
このように
faceの名前と対応するスタイルを逐一変更するものがEmacsのカラーテーマです。例えばaalto-dark-themeの冒頭はこのようになっています。
(deftheme aalto-dark "aalto-dark theme") (custom-theme-set-faces 'aalto-dark '(default ((t (:background "DeepSkyBlue3" :foreground "white")))) '(mouse ((t (:foreground "black")))) '(cursor ((t (:background "yellow")))) '(border ((t (:foreground "black")))) '(bold ((t (:bold t :background "blue3" :foreground "white")))) '(bold-italic ((t (:italic t :bold t :foreground "blue3")))) '(calendar-today-face ((t (:underline t)))) '(diary-face ((t (:foreground "red")))) '(font-lock-builtin-face ((t (:foreground "LightSteelBlue")))) '(font-lock-comment-face ((t (:foreground "OrangeRed")))) ;; ... ) (provide-theme 'aalto-dark)カラーテーマパッケージがどのようなことをしているか掴めてきましたでしょうか。
カラーテーマの問題点
さて、Emacsの
faceは先程のようにパッケージごとに定義されています。 もし、パッケージが増えた場合、カラーテーマはどのように対応するべきでしょうか。簡単ですね。新しいパッケージが増えたら、その都度
faceの設定を書き足すのです。。例えばandreas-themeでは
gnusやparenというパッケージに対応していることが分かります。このようにカラーテーマごとに「対応しているパッケージ」が異なっており、メンテナンスされていないカラーテーマを使っていると、パッケージが決めたデフォルトの色がそのまま表示されることになります。
そのため「色が暗い」「色がビビットすぎる」「そもそも見えない」などの問題が発生します。
Solarized
Solarizedのface定義
Emacsコミュニティによって長年メンテナンスされているSolarizedはどうなっているでしょうか。
実際のファイルはsolarized-emacs/solarized-faces.elです。
faceの再定義は2000行に及び、もし新しいパッケージが生まれたとしても有志の手によって速やかに追加されています。これをSolarizedだけで使うのはもったいありません。
Solarizedも内部では基本となる10色を定義し、その色で
faceを再定義しています。では、基本となる10色を外部から指定できるようにしたらSolarizedの仕組みに乗った上で簡単にカラーテーマが作れるのではないか。
それを提案したのがzk_phiさんの「[Emacs] メタ・カラースキームとしての solarized」であり、私のパッチはそれを本家で実現したものです。
Solarizedから作るカラーテーマ
SolarizedのAPIで使いやすいのは
solarized-create-theme-file-with-paletteです。 この関数は最暗色、最明色に加え、アクセントカラー8色を指定することでSolarizedのface定義を使うカラーテーマファイルを生成し、.emacs/themes以下に保存します。早速icebergからアクセントカラーを貰ってカラーテーマを生成しましょう!
(solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark '("#161821" "#c6c8d1" "#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6"))上記で書いた色はVim Scriptのソースからではなく講演資料から色を貰いました。

なお、icebergではアクセントカラーが6色のようなので、Solarizedが求めているアクセントカラー8色に足りません。そのため2色をダブらせて生成しました。
関数を実行して
.emacs/themes以下に保存されたテーマファイルはload-themeでロードできます。
微調整の仕方
Emacsの本文部分は default という face が当てられているのですが、solarizedのデフォルトでは文字色が最明色になりません。。
まずそこからカスタマイズしていきます。
solarized-create-theme-file-with-palette の第3引数にS式のリストを渡すことでテーマのカスタマイズができます。
(solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark
'("#161821" "#c6c8d1"
"#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6")
'((custom-theme-set-faces
theme-name
`(default ((,class (:foreground ,base3 :background ,base03)))))))
カスタマイズはSolarizedの内部で色がどのように管理されているかを少し知っておく必要があります。
基本的に次のように色の名前が管理されています。
<brightest darkest>
base03 base02 base01 base00 base0 base1 base2 base3
<accent colors>
yellow orange red magenta violet blue cyan green
さらに色のバリエーションとして yellow-d や yellow-l という色も定義されており、第3引数で使用できます。
これらは solarized-create-color-palette という関数の中で自動生成されています。
完成形
本家のiceberg.vimのスクショと比較しながら、間違っている face を describe-face で確認して直す。。という作業を繰り返すと徐々に本家に近づいていきます。
最終的に次のような形になりました。
(defun iceberg-theme-create-theme-file ()
"Create iceberg-theme color theme using solarized API.
Solarized pallete.
brightest darkest
base03 base02 base01 base00 base0 base1 base2 base3
yellow orange red magenta violet blue cyan green"
(solarized-create-theme-file-with-palette 'dark 'solarized-iceberg-dark
'("#161821" "#c6c8d1"
"#e2a478" "#e27878" "#e27878" "#a093c7" "#b4be82" "#84a0c6" "#89b8c2" "#84a0c6")
'((custom-theme-set-faces
theme-name
`(default ((,class (:foreground ,base3 :background ,base03))))
`(vertical-border ((,class (:foreground ,base03))))
`(mode-line ((,class (:foreground ,base2 :background ,base02))))
`(mode-line-inactive ((,class (:foreground ,base0 :background ,base03))))
`(font-lock-comment-delimiter-face ((,class (:foreground "#6b7089"))))
`(font-lock-comment-face ((,class (:foreground "#6b7089"))))
`(font-lock-preprocessor-face ((,class (:foreground ,green)))) ; yellow
`(font-lock-type-face ((,class (:foreground ,cyan)))) ; yellow
`(font-lock-builtin-face ((,class (:foreground ,green)))) ; base0
`(diff-function ((,class (:foreground ,violet-1fg))))
`(diff-header ((,class (:foreground ,green))))
`(diff-hunk-header ((,class (:foreground ,green))))
`(diff-file-header ((,class (:background ,base03 :foreground ,green))))
`(diff-added ((,class (:background ,violet-1bg :foreground ,violet-1fg))))
`(diff-indicator-added ((t (:foreground ,violet))))
`(markdown-header-face ((,class (:foreground ,yellow))))
`(markdown-header-rule-face ((,class (:foreground ,green))))
`(markdown-markup-face ((,class (:inherit default))))
`(markdown-url-face ((,class (:foreground ,magenta))))
`(markdown-link-face ((,class (:foreground ,green :underline t))))
`(markdown-inline-code-face ((,class (:foreground ,cyan))))
`(markdown-pre-face ((,class (:foreground ,cyan))))
`(sh-quoted-exec ((,class (:foreground ,violet))))
`(haskell-type-face ((,class (:inherit default))))
`(haskell-constructor-face ((,class (:inherit default))))
`(haskell-operator-face ((,class (:foreground ,green))))
`(haskell-definition-face ((,class (:inherit default))))
`(web-mode-block-delimiter-face ((,class (:inherit default))))
`(web-mode-html-attr-value-face ((,class (:foreground ,cyan))))
`(web-mode-mode-type-face ((,class (:inherit default))))
`(web-mode-function-call-face ((,class (:inherit default))))
`(web-mode-keyword-face ((,class (:foreground ,green))))
`(web-mode-constant-face ((,class (:foreground ,cyan))))
`(web-mode-variable-name-face ((,class (:foreground ,cyan))))
`(web-mode-html-tag-bracket-face ((,class (:foreground ,green))))
`(org-verbatim ((,class (:foreground ,cyan))))
`(php-php-tag ((,class (:inherit default))))
`(php-constant ((,class (:inherit default))))
`(php-paamayim-nekudotayim ((,class (:foreground ,green))))
`(php-object-op ((,class (:foreground ,cyan))))
`(php-variable-name ((,class (:foreground ,cyan))))
`(php-variable-sigil ((,class (:foreground ,cyan))))))))
テーマを作る関数にしているのは「requireしただけでEmacsの動作を変えない」というパッケージ作成のルールに従うためです。
これでユーザーはiceberg-themeをインストール後、 iceberg-theme-create-theme-file を実行した後で M-x load-theme solarized-iceberg-dark と実行できるはずです。
まとめ
現在ではSolarizedの資産を生かし、簡単にカラーテーマを作成することができます。
ぜひ独創的かつ目にやさしいカラーテーマを作っていただき、日々のEmacs生活がよりよりものになれば良いなと思っています。
DONE Archのrsyncで転送できないときに確認すること
Intro
celpa.conao3.comは私が契約している100GBの「さくらのレンタルサーバー」を向いているのだけど、その公開ディレクトリにGitHub Actionsからrsyncでデプロイしたい。
もちろん、GitHubのワーカーに秘密鍵をアップロードすることになるので、その秘密鍵は上手い具合に制限を設ける必要があります。
ローカルで試していたとき上手くいかなかったので、そのメモ。
普通のrsync
rsyncは接続先のサーバーにrsyncを起動して、そのプロセスと通信します。
どんなコマンドが実行されているかは -vvv オプションを付けることで表示されます。
$ rsync -vvv -an README.md conao3@conao3.sakura.ne.jp:/home/conao3/
opening connection using: ssh -l conao3 conao3.sakura.ne.jp rsync --server -vvvnlogDtpre.iLsfxC . /home/conao3/ (9 args)
sending incremental file list
[sender] make_file(README.md,*,0)
...
sent 60 bytes received 368 bytes 285.33 bytes/sec
total size is 15,379 speedup is 35.93 (DRY RUN)
[sender] _exit_cleanup(code=0, file=main.c, line=1189): about to call exit(0) (DRY RUN)
opening connection using の行の rsync 以降が接続先で実行されているコマンドです。
そのため、そのコマンドだけ実行できる鍵を登録するには以下のように authorized_keys に登録します。 (-vvv はいらないので削る)
command="rsync --server -nlogDtpre.iLsfxC . /home/conao3/",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa AAAA...
rsyncdを使ったrsync
鍵の作成
rsync用の鍵を新しく作る。 GitHub Actionsのsshが古く、Openssh形式の秘密鍵が読めなかったのでPEM形式で出力するようにオプションを付けた。
鍵の名前は
sakura-rsync_rsaと入力した。cd .ssh ssh-keygen -t rsa -b 4096 -m PEM -C ""
鍵の登録
ファイルをやりとりしたいサーバーにログインし、
authorized_keysに公開鍵sakura-rsync_rsa.pubを登録する。command="rsync --server --daemon --config=/home/conao3/.rsyncd.conf .",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-rsa AAAA...
rsyncd.confの作成
私が使っているのは共有サーバーなので、当然、ユーザーホーム以下しか触れない。 そのため
rsyncd.confは~/.rsyncd.confに作成することにしました。use chroot = no read only = yes [www] read only = no write only = yes path = /home/conao3/www [cache] path = /home/conao3/www/files/cache-40bbae27
使ってみる
なぜかファイル転送されない。。。
$ rsync -vvv -e "ssh -i sakura-rsync_rsa" /home/conao/dev/template/elisp conao3@conao3.sakura.ne.jp::foo/ opening connection using: ssh -i sakura-rsync_rsa -l conao3 conao3.sakura.ne.jp rsync --server --daemon . (10 args) sending daemon args: --server -vvve.LsfxC . foo/ (4 args) skipping directory elisp send_file_list done send_files starting send_files phase=1 send_files phase=2 send files finished total: matches=0 hash_hits=0 false_alarms=0 data=0 sent 16 bytes received 11 bytes 18.00 bytes/sec total size is 0 speedup is 0.00 [sender] _exit_cleanup(code=0, file=main.c, line=1189): about to call exit(0)-aオプションを付けると転送された。$ rsync -a -vvv -e "ssh -i sakura-rsync_rsa" /home/conao/dev/template/elisp conao3@conao3.sakura.ne.jp::foo opening connection using: ssh -i sakura-rsync_rsa -l conao3 conao3.sakura.ne.jp rsync --server --daemon . (10 args) sending daemon args: --server -vvvlogDtpre.iLsfxC . foo/ (4 args) sending incremental file list [sender] make_file(elisp,*,0) send_file_list done [sender] pushing local filters for /home/conao/dev/template/elisp/ [sender] make_file(elisp/README.org,*,2) [sender] make_file(elisp/Makefile,*,2) [sender] make_file(elisp/elisp-template.el,*,2) [sender] make_file(elisp/.github,*,2) [sender] make_file(elisp/elisp-template-tests.el,*,2) [sender] make_file(elisp/git-hooks,*,2) [sender] make_file(elisp/.gitignore,*,2) [sender] make_file(elisp/LICENSE,*,2) [sender] make_file(elisp/Cask,*,2) send_files starting
GitHub Actionsから使う
こんなファイルを
.github/workflows/test.ymlに保存すればできる。「GitHub ActionsでSSHを使う - Qiita」で紹介されていた
shimataro/ssh-key-actionを使わせてもらいました。name: Main workflow on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-latest strategy: matrix: emacs_version: - '26.3' steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1.1.1 - uses: purcell/setup-emacs@master with: version: ${{ matrix.emacs_version }} - uses: conao3/setup-cask@master - name: Install SSH key uses: shimataro/ssh-key-action@v1 with: private-key: ${{ secrets.SSH_KEY }} public-key: ${{ secrets.SSH_KEY_PUBLIC }} known-hosts: ${{ secrets.KNOWN_HOSTS }} - name: Cache fetch run: | rsync -av --progress -e 'ssh' conao3@conao3.sakura.ne.jp::cache/celpa.tar.gz /tmp/ rm -rf working tar xf /tmp/celpa.tar.gz - name: Build run: | make - name: Deploy run: | rsync -av -e 'ssh' --delete --exclude=working ./ conao3@conao3.sakura.ne.jp::www/celpa/ - name: Cache push run: | rm /tmp/celpa.tar.gz tar czf /tmp/celpa.tar.gz working rsync -av --progress -e 'ssh' /tmp/celpa.tar.gz conao3@conao3.sakura.ne.jp::cache/
まとめ
rsyncを使ってサイトをGitHub Actionsからデプロイできるようになりました。
参考
- rsync+sshはdaemonモードを使うと更に安全になる - 純規の暇人趣味ブログ
- GitHub ActionsでSSHを使う - Qiita
- ssh-keygen で生成された OPENSSH フォーマットの秘密鍵を pem フォーマットへ変換する - Think Abstract
- SSHの秘密鍵について - 禿散らかしてました
DONE CELPA (Conao3’s Emacs Lisp Package Archive) をデプロイした話
Intro
MELPAのレビューが遅い。
MELPAのメンテナの方には感謝してしきれないが、実際のところ、レシピ追加PRを出して2週間から3週間音沙汰ないまま放置されたり、先方のレビューを反映して修正しても、そこから2週間、3週間待たされることがままあり、ストレスを抱えていました。
私はあまりinit.elを大きくしたくなく、ある程度の大きさになるとパッケージで切りだして (leaf hoge) で簡単にアクティベートしたいと考えているため、MELPAに入れてもらえないと el-get で取ってこないといけませんでした。
ここでさらに問題があり、 el-get は package との相性が悪く、微妙な評価タイミングの違いがとても重要になります。
init.elを自分で全てコントロールしていたとしても、おまじないの部分が増えるし、leaf-managerで自動管理している場合は原理的に不可能でした。
そのためMELPAっぽく、さらにレシピの追加を自分でコントロールできるアーカイブが必要となり、今回、celpa.conao3.comをデプロイしました。

MELPAの仕組み
MELPAは単に利用するもので、自分でMELPAを作りたい場合、どこから手をつけたら良いか分かりませんでした。
ただ、takaxpさんがwikiを翻訳して下さっており、この通り進めればなんとかなりました。
もちろんwiki(Custom Melpa Archive, Melpa and package build)も参照しました。
MELPAソースの修正
レシピの削除
MELPAのクローンを作ろうとしているわけではないので、MELPAに既に登録されているレシピは必要ありません。 全部削除してしまいます。
レシピの追加
欲しいパッケージのレシピをおもむろに追加します。もちろんMELPAの形式と一緒のものです。
サーバーの用意
いい感じのサーバーとドメインを用意し、rsyncでよしなに同期できるようにします。 その話については「Archのrsyncで転送できないときに確認すること」に書きました。
index.htmlの修正
いい感じにindex.htmlを修正します。 おそらくjsonが足らない(CELPAではダウンロード数の記録はしてない)らしく、MELPAに付属しているjavascriptが動きません。
適当にjsを書いたり、リンクを修正したりします。
GitHub Actionsの設定ファイル追加
GitHub Actionsで30分ごとにビルドが走るようにします。
name: Main workflow on: push: branches: - master pull_request: branches: - master schedule: - cron: '*/30 * * * *' jobs: build: runs-on: ubuntu-latest strategy: matrix: emacs_version: - '26.3' steps: - uses: actions/checkout@v1 - uses: actions/cache@v1 with: path: working key: ${{ runner.os }}-working - uses: actions/setup-python@v1.1.1 - uses: purcell/setup-emacs@master with: version: ${{ matrix.emacs_version }} - uses: conao3/setup-cask@master - name: Build run: | git status make add-package-build-remote make pull-package-build make - name: Install SSH key if: github.ref == 'refs/heads/master' uses: shimataro/ssh-key-action@v1 with: private-key: ${{ secrets.SSH_KEY }} public-key: ${{ secrets.SSH_KEY_PUBLIC }} known-hosts: ${{ secrets.KNOWN_HOSTS }} - name: Deploy if: github.ref == 'refs/heads/master' run: | rsync -av -e 'ssh' --delete --exclude=.git --exclude=working ./ conao3@conao3.sakura.ne.jp::www/celpa/
まとめ
振り返ってみるとMELPAをCELPAとして動かせるように修正するのは簡単でした。
やはりミスが許されず、時間がかかるのは秘密鍵/権限回りで、その部分で半分以上の時間を費しました。
レシピを直接pushできるというのはMELPAを使っていた頃には考えられない快適さです。
ぜひみなさんもオレオレMELPAをデプロイして、package.elだけでパッケージを管理できるようにしましょう!
DONE Emacsで競技プログラミング | ojのラッパーパッケージを書いた話
Intro
ちょっとEmacsで競技プログラミングをしてみようかと思いました。
どうやらoj、oj-templateという便利なツールがあるようなので、Emacsから簡単に実行できるようoj.elというパッケージを作成しました。
oj, oj-template
ojは多数のオンラインジャッジに対応しているCLIツールです。
オンラインジャッジによって実装されている機能に制限はありますが、サンプルケースのダウンロード、テスト、提出、ランダムインプットの生成などの機能があります。

さらにoj-templateは入出力サンプルからそれっぽいcppとpythonファイルを生成してくれます。
また、 oj-prepare はコンテストURLを指定すると、そのコンテストの全ての問題について oj donwload と oj-template をしてくれます。
コンテスト中は時間との戦いなので、これらのコマンドによる時短は大きなものになると思います。
oj.el
oj.elは先般の oj, oj-template をEmacsから実行できるようにしたものです。
(leaf oj
:ensure t
:custom ((oj-compiler-c . "clang")
(oj-compiler-python . "pypy")
(oj-default-online-judge . "atcoder")))

単に関数にバインドしただけでなく、多少の改善を入れています。
例えば oj はダウンロードするためにURL全体を指定する必要がありますが、 oj-default-online-judge で設定されているオンラインジャッジを規定のものと考え、入力を省略できるようにしています。
(CLIツールとしての方針と干渉するものではなく、エディタの拡張としての求められる機能として盛りこんだものです。)
例えば atcoder デフォルトに設定されている状況で、 M-x oj-prepare に abc167 を入力すると下記コマンドが実行されます。
oj-template https://atcoder.jp/contests/abc167
さらに、 abc167/a と入力すると下記コマンドが実行されます。
oj-template https://atcoder.jp/contests/abc167/tasks/abc167_a
このように推測できる部分については、より推測するように実装しています。
詳しい使い方についてはREADMEを参照して下さい。
基本的に oj-install-package, oj-prepare, oj-test, oj-submit の順にコマンドを実行すればいい感じに動くはずです。
まとめ
ojをEmacsから使いやすくするラッパーパッケージを書きました。
こういう方向のパッケージもお手軽で良いですね。
DONE Emacsで最終行にポイントを動かさないようにするアドバイス
Intro
Elispのadviceはとても便利。
でも気をつけないとバグの温床になりますっ!(笑
— Takaaki Ishikawa (@takaxp) May 22, 2020
末尾にポイントを移動しない
Emacs-jpのスレッドで次のような質問があった。
こんにちは! 2 行のファイルがあったとき、 3 行目にカーソル移動できないようにしたいです 何か方法は無いでしょうか
このような細かい挙動はアドバイスで簡単に実現できる。
(leaf simple
:preface
(defun c/advice-next-line (fn &rest args)
(let ((maxline (count-lines (point-min) (point-max))))
(if (< maxline (line-number-at-pos (line-beginning-position 2)))
(message "restricted! (next-line)")
(apply fn args))))
(defun c/advice-forward-char (fn &rest args)
(let ((maxline (count-lines (point-min) (point-max))))
(if (< maxline (line-number-at-pos (+ (point) 1)))
(message "restricted! (forward-char)")
(apply fn args))))
:advice ((:around next-line c/advice-next-line)
(:around forward-char c/advice-forward-char)))
(leaf simple
:advice-remove ((next-line c/advice-next-line)
(forward-char c/advice-forward-char)))

末尾にポイントを移動しようとしたら改行する
さらに、仕様を変えて、「最終行以上にポイントを動かそうとしたときに、自動で改行を入れる」ことも簡単に実現できる。
(leaf simple
:preface
(defun c/advice-next-line (fn &rest args)
(let ((maxline (count-lines (point-min) (point-max))))
(if (< maxline (line-number-at-pos (line-beginning-position 2)))
(progn (goto-char (line-beginning-position 2))
(save-excursion (insert "\n")))
(apply fn args))))
(defun c/advice-forward-char (fn &rest args)
(let ((maxline (count-lines (point-min) (point-max))))
(if (< maxline (line-number-at-pos (+ (point) 1)))
(insert "\n")
(apply fn args))))
:advice ((:around next-line c/advice-next-line)
(:around forward-char c/advice-forward-char)))
(leaf simple
:advice-remove ((next-line c/advice-next-line)
(forward-char c/advice-forward-char)))

これらのアドバイスで「ファイル末尾に必ず改行がある」ことを強制できる。
まとめ
ただ、今回の要求については「ファイル保存時に改行がない場合、末尾改行を付加する」というオプションがあり、私はこれを使っている。
(leaf files
:custom `((require-final-newline . t)))
とはいえ、このようなオプションがないときElispのアドバイスはとても便利。
細かい挙動がストレスになることもあるので、簡単にカスタマイズできるEmacsは良いエディタ。
DONE 制御記号付きのmake-processの出力をいい感じに処理する方法
Intro
Emacsからプロセスを実行できます。
実行する方法はいろいろありますが、最も低レベルな関数は make-process です。
最もユーザーに近い関数は shell-command で、 M-! に割り当てられています。
この関数はコマンド文字列を渡すだけで、お手軽に実行できます。
make-process
make-processは非同期プロセスを作成します。
必須のオプションは :name で、プロセスオブジェクトを返却します。
さらに :buffer, :command, :coding, :connection-type, :noquery, :stop, :filter, :sentinel, :stderr オプションが存在し、細かくプロセスの管理方法を制御できます。
基本的に :name, :buffer, :command を指定することになります。
(setq proc
(make-process
:name "sh"
:buffer (get-buffer-create "*proc*")
:command '("hub" "clone" "conao3/celpa")))
(progn
(delete-process proc)
(shell-command "rm -rf celpa"))
ただ、コマンドから送られてきた文字列を素朴に出力するため、gitのように CR を利用してリッチに出力するコマンドの出力は盛大に壊れます。
CR を解釈しながら賢く処理をすれば良いのですが、とても面倒です。
Emacs-jpで助けを求めると、eshellの関数を利用すれば良いのではという助言があり、調べてみると、きちんと出力できました。
eshellの仕組みに乗っかるので、 eshell-mode にしなければフィルター関数を使えないかと思いましたが、よく見ればいくつかのマーカーを設定するだけで使えました。
(with-current-buffer (get-buffer-create "*proc*")
(set (make-local-variable 'eshell-last-input-start) (point-marker))
(set (make-local-variable 'eshell-last-input-end) (point-marker))
(set (make-local-variable 'eshell-last-output-start) (point-marker))
(set (make-local-variable 'eshell-last-output-end) (point-marker))
(set (make-local-variable 'eshell-last-output-block-begin) (point)))
(setq proc
(make-process
:name "sh"
:buffer (get-buffer-create "*proc*")
:command '("hub" "clone" "conao3/celpa")
:filter (lambda (proc string)
(eshell-output-filter proc string))))
(progn
(delete-process proc)
(shell-command "rm -rf celpa"))

まとめ
Emacsは良いエディタで、Elispは強力ですが、GitをElispで実装するのは現実的ではありません。
このように外部コマンドとElispを有効に協調させながら作業しやすい環境を整えていきましょう。
DONE リストに関数を適用して最初にnon-nilを返した値を得る方法
このような関数をさくっと使いたい。 3つのポイントがあり、「リストに関数を適用して」「最初にnon-nilが返ってきた」「その値を得る」ということ。
2つめのポイントまでなら cl-find-if がそのまま使える。
cl-find-if は「リストに関数を適用して」「最初にnon-nilが返ってきた」「元の値を得る」ことができる。
(cl-find-if (lambda (elm) (when (<= 3 elm) (* elm elm))) '(1 2 3 4))
;;=> 3
私が欲しいのはこういう関数です。
(defun first-value (fn lst)
"Apply FN for LST and return first non-nil value."
(funcall fn (cl-find-if fn lst)))
;;=> first-value
(first-value (lambda (elm) (when (<= 3 elm) (* elm elm))) '(1 2 3 4))
;;=> 9
Emacs-jpで聞いたところ、koshさんに seq-some を教えてもらいました。
(seq-some (lambda (elm) (when (<= 3 elm) (* elm elm))) '(1 2 3 4))
;;=> 9
便利。
DONE Lispでスペシャルフォームのorやandをapplyする方法
or や and を apply したい。
つまり、いろんな操作によって得られたリストに対して or や and がとりたい。
なんでこんな面倒なことをしてるんだと思ったら、andがsubrだからapplyできないのか。
— Conao3 (@conao_3) March 28, 2020
この問題、CLerはどうやって解決するんだろう #lisp pic.twitter.com/cmwx3p8ABm
(every #'identity <list>) というのも。
— Shiro Kawai (@anohana) March 28, 2020
河合さんに every を教えてもらいました。
(mapcar #'featurep '(emacs leaf use-package-leaf))
;;=> (t t nil)
(cl-every #'identity (mapcar #'featurep '(emacs leaf use-package-leaf)))
;;=> nil
(cl-every #'identity (mapcar #'featurep '(emacs leaf)))
;;=> t
たしかに上手くいっている。 every は and 相当だったので、 some は or 相当となる。
(cl-some #'identity (mapcar #'featurep '(emacs leaf)))
;;=> t
(cl-some #'identity (mapcar #'featurep '(use-package-leaf use)))
;;=> nil
もちろん、今回の方法はスペシャルフォームではないので、評価してはいけない式が後続している場合、普通にエラーになる。
(or nil t (/ 1 0))
;;=> t
(cl-some #'identity (list nil t (/ 1 0)))
;;=> Debugger entered--Lisp error: (arith-error)
;; /(1 0)
;; (list nil t (/ 1 0))
;; (cl-some (function identity) (list nil t (/ 1 0)))
;; eval((cl-some (function identity) (list nil t (/ 1 0))) nil)
;; scratch-comment--elisp--eval-last-sexp(t)
;; scratch-comment--eval-last-sexp(t)
;; scratch-comment-eval-sexp()
;; funcall-interactively(scratch-comment-evalp-sexp)
;; call-interactively(scratch-comment-eval-sexp nil nil)
;; command-execute(scratch-comment-eval-sexp)
TODO base64の画像をEmacsで表示する方法
ネタ元はROCKTAKEYさんのEmacs-jpでの発言。 (create-image (base64-decode-string “base64エンコードの画像”) nil t :width nil)
DONE Emacsに存在するwithプレフィックスのマクロ一覧
with マクロは便利。
with-temp-buffer や with-current-buffer は超基本マクロだし、helmなども with-helm-buffer という同じ文脈のマクロを提供している。
ふと、 with プレフィックスのマクロがどれだけEmacsに用意されているのか気になったので調べた。
ppp, promise, asyncに依存して、次のコード片を実行すれば調べられる。
31シンボル見つかった。
emacs-version
;;=> "26.3"
(ppp-list
(promise-wait-value
(promise-wait 5
(promise:async-start
(lambda ()
(let (res)
(mapatoms
(lambda (elm)
(when (and (fboundp elm)
(string-prefix-p "with-" (symbol-name elm)))
(push elm res))))
(sort res (lambda (a b) (string< (symbol-name a) (symbol-name b))))))))))
;;=> (with-auto-compression-mode
;; with-case-table
;; with-category-table
;; with-coding-priority
;; with-connection-local-profiles
;; with-current-buffer
;; with-current-buffer-window
;; with-demoted-errors
;; with-displayed-buffer-window
;; with-electric-help
;; with-eval-after-load
;; with-file-modes
;; with-help-window
;; with-isearch-suspended
;; with-local-quit
;; with-mutex
;; with-no-warnings
;; with-output-to-string
;; with-output-to-temp-buffer
;; with-selected-frame
;; with-selected-window
;; with-silent-modifications
;; with-syntax-table
;; with-temp-buffer
;; with-temp-buffer-window
;; with-temp-file
;; with-temp-message
;; with-timeout
;; with-timeout-suspend
;; with-timeout-unsuspend
;; with-wrapper-hook)
先のコード片で面倒なことをやっているのは、いわゆる emacs -q で実行した結果を得たいため。
今起動しているEmacsでの結果が欲しい場合はpromiseを消せば良い。
82シンボル見つかった。
(ppp-list
(let (res)
(mapatoms
(lambda (elm)
(when (and (fboundp elm)
(string-prefix-p "with-" (symbol-name elm)))
(push elm res))))
(sort res (lambda (a b) (string< (symbol-name a) (symbol-name b))))))
;;=> (with-auto-compression-mode
;; with-buffer-modified-unmodified
;; with-case-table
;; with-category-table
;; with-coding-priority
;; with-connection-local-profiles
;; with-current-buffer
;; with-current-buffer-window
;; with-decoded-time-value
;; with-demoted-errors
;; with-displayed-buffer-window
;; with-editor
;; with-editor*
;; with-editor--setup
;; with-editor-async-shell-command
;; with-editor-cancel
;; with-editor-debug
;; with-editor-emacsclient-path
;; with-editor-emacsclient-version
;; with-editor-emulate-terminal
;; with-editor-export-editor
;; with-editor-export-git-editor
;; with-editor-export-hg-editor
;; with-editor-finish
;; with-editor-kill-buffer-noop
;; with-editor-locate-emacsclient
;; with-editor-locate-emacsclient-1
;; with-editor-mode
;; with-editor-output-filter
;; with-editor-process-filter
;; with-editor-read-envvar
;; with-editor-return
;; with-editor-server-window
;; with-editor-set-process-filter
;; with-editor-shell-command
;; with-editor-shell-command-read-args
;; with-editor-usage-message
;; with-electric-help
;; with-eval-after-load
;; with-feather-dashboard-buffer
;; with-file-modes
;; with-helm-after-update-hook
;; with-helm-alive-p
;; with-helm-buffer
;; with-helm-current-buffer
;; with-helm-default-directory
;; with-helm-display-marked-candidates
;; with-helm-in-frame
;; with-helm-quittable
;; with-helm-temp-hook
;; with-helm-time-after-update
;; with-helm-window
;; with-help-window
;; with-httpd-buffer
;; with-isearch-suspended
;; with-ivy-window
;; with-local-quit
;; with-lsp-workspace
;; with-lsp-workspaces
;; with-mutex
;; with-no-warnings
;; with-output-to-string
;; with-output-to-temp-buffer
;; with-parsed-tramp-file-name
;; with-ppp--working-buffer
;; with-selected-frame
;; with-selected-window
;; with-silent-modifications
;; with-slots
;; with-syntax-table
;; with-temp-buffer
;; with-temp-buffer-window
;; with-temp-file
;; with-temp-message
;; with-timeout
;; with-timeout-suspend
;; with-timeout-unsuspend
;; with-tramp-connection-property
;; with-tramp-file-property
;; with-tramp-progress-reporter
;; with-vc-properties
;; with-wrapper-hook)
DONE Emacsでプレフィックスキーにバインドする方法
ネタ元はEmacs-jp。
namnium 06:43 すみません、とても小さい質問なのですが、検索ワードが思いつかなかったので質問させてください。 yasnippetでキーバインド C-x i i をスニペット挿入に割り当てたところ、今まで使用していた insert-file C-x i を打てなくなってしまったのですが、このようにキーバインドを途中で打ち切りたくなった時はどうすればいいのでしょうか? とりあえず現在は M-x insert-file で諦めています…
conao3 08:18 インタラクティブなら、C-x i iにnilをバインドした後にC-x iを再バインドします。 インタラクティブでなくていいなら、init.elの該当行を削除して再起動するだけです。
namnium 09:48 両立って無理な感じですか?
conao3 09:50 うーん、、独自にキーを待つ関数をC-x iに登録したら実現できるかもしれませんが、、
ということでEmacsにできないことはないので、実装してみた。
(leaf async-await
:ensure t
:config
(leaf c/insert-file
:leaf-defer nil
:pre-setq ((c/insert-file-enable . t)
(c/insert-file-delay . 1.0))
:bind (("C-x i" . nil)
("C-x i i" . yas-insert-snippet))
:config
(funcall
(async-lambda ()
(while c/insert-file-enable
(await (promise:delay c/insert-file-delay))
(warn "%s" (prin1-to-string
(which-key--this-command-keys)))
(when (equal [24 105] (which-key--this-command-keys))
(warn "Detect: C-x i")
(await (promise:delay c/insert-file-delay))
(when (equal [24 105] (which-key--this-command-keys))
(warn "Redetect: C-x i")
(call-interactively #'insert-file))))))))
(setq c/insert-file-enable nil)
async-awaitとwhich-keyに依存しています。
ループを止める手段が用意されてないので、外から c/insert-file-enable を nil に設定することで止めます。
あと call-interactively を quit すると無限ループが止まってしまうんですが、なぜだろう。。
C-x i を打って、Redetectされる前に i を素早く入力すると、 yas-insert-snippet が実行されます。
Redetectされると insert-file が実行されます。
DONE Emacsのキーマップlookup順序について
元記事のアーカイブのアーカイブ。なにか問題があれば教えて下さい。以下、アーカイブ。
何となく忘れてしまうのでメモ。

Info の 22.8 Searching the Active Keymaps, 22.9 Controlling the Active Keymaps などから。
「belong」は誰がその変数を管理するかとか、変数の所属先という感じ。
基本
全バッファ共通は global-map 、メジャーモードは local-map を設定する。
マイナーモードはまとめて minor-mode-map-alist に入っていて、マイナーモードの変数が on か off で有効無効が決まる。
このリストの順番も重要。
テキストには文字単位で keymap と言う属性をつけて、キーバインドを設定できる。
あと、これらのどれにもヒットしなければ、 local-function-key-map でキーの変換が行われる。
例えば、環境によって C-i (タブ文字) と <tab> は区別されているが、実質は同じと見なした方が便利なのでここで変換される。
優先順位は見たまま。 text-property 'keymap > minor-mode-map-alist > local-map > global-map > local-function-key-map
override
この優先順位を少し覆すことが出来るしくみが overriding 関係。
CSS の important! のような感じ。
まず、メジャーモードよりマイナーモードの方が優先されてしまうので、これを覆す仕組みが minor-mode-overriding-map-alist 。
ただ、覆そうとしているマイナーモードの名前を知らないといけない。
次に、文字単位の keymap をメジャーモードやマイナーモードから覆す仕組みが overriding-local-map 。
最後に、端末入力の最上位で入力を乗っ取る仕組みが overriding-terminal-local-map 。
これは影響範囲がその端末だけ。
emulation-mode-map-alists は他のメジャーモードやマイナーモードが別のモードのキーマップを乗っ取ったり、 remap で挙動を変えたりする場合に使うもの。
検索してもあまり情報がない (cua, viper とかぐらい)。
DONE Emacs Lisp の文字列操作まとめ
元記事のアーカイブのアーカイブ。なにか問題があれば教えて下さい。以下、アーカイブ。
基本的には Emacs Lisp の Info を読むと何となくパーツはそろっていることは分かる。 ただ、パーツが特殊だったりしてよく使い方を忘れるのでよく見る情報をメモ。
基本的情報源
以下の情報はローカルにブックマークするなどして即引けるようにしておくと便利。
- Info 4 Strings and Characters
- 文字列と文字
- 文字列の操作、比較、変換など
- Info 32.2 Examining Buffer Contents
- 31.2 バッファの内容を調べる
- バッファの文字列取得
- Info 32.19 Text Properties
- 31.19 テキスト属性
- テキストプロパティ(テキスト属性)のいろいろ
- Info 34 Searching and Matching
- 探索と一致
- 探索、正規表現
- Info 38.10 Width
- 38.9 表示幅
- 非 ASCII 文字列を含む文字や文字列の幅(画面上の長さ)、端っこ切る
- 逆引き Emacs Lisp 文字列
- それなりに数が多い
- no title
- 実は必要なことはここに大体書いてあったりする
Ruby との文字列操作対応表
参照: 文字列操作の比較表: Ruby, Python, JavaScript, Perl, C++ - bkブログ 基本的に破壊的操作は無し。
| Ruby (String) | Emacs Lisp | 注釈 |
|---|---|---|
| s = “abc” | (setq s “abc”) | 変数にバインドするならlet使う方が一般的 |
| s = x + y | (concat x y) | |
| s == x | (equal s x) | string-equal が正式 |
| s % [x, y] | ||
| sprintf(s, x, y) | (format s x y) | |
| [x, y, z].join(s) | (mapconcat ‘identity (list x y z) s) | 単に結合するなら (apply ‘concat (list x y z)) もアリ |
| s.capitalize | (capitalize s) | |
| s.capitalize! | ||
| s.center(x) | (cfw:render-center x s) | calfw.el 参照 |
| s.chomp | (regexp-in-string “[\n\r]+$” "” s) | |
| s.chomp! | ||
| s.chop | (substring s 0 -1) | 改行文字の扱いが違う |
| s.chop! | ||
| s.clear | (clear-string s) | |
| s.concat(x) | ||
| s.count(x) | ループで回すか、一時バッファを使ってre-search-forward で回数を数えるとか | |
| s.crypt(x) | rot13-string とか、 EPA とか | |
| EzCrypt | ||
| s.delete!(x) | (regexp-in-string x "” s) | 破壊的には行えない |
| s.downcase | (downcase s) | |
| s.downcase! | ||
| s.each_byte { x … ) | (loop for x across s do … ) | 文字単位でループ、xは文字コード(数値)文字コードからstringにするには char-to-string |
| s.each_line { x … ) | (loop for x in (split-string s “[\n\r]") do … ) | |
| s.empty? | (= 0 (length s)) | nullチェックが必要 |
| s.end_with?(x) | (string-match (concat x “$”) s) | sに改行が入らないことが前提 |
| s.gsub(x, y) | (regexp-in-string x y s) | |
| s.gsub!(x, y) | ||
| s.hex | ||
| s.to_i(16) | (string-to-number s 16) | |
| s.include?(x) | (string-match x s) | もし見つかれば開始位置が返ってくる |
| s.index(x) | (string-match x s) | |
| s.insert(i, x) | 一時バッファでやるか、自分でちぎってつなげるか | |
| s.intern | ||
| s.to_sym | (intern s) | シンボルテーブルに登録したくなければ make-symbol |
| s.length | ||
| s.size | (length s) | 文字数が返る |
| s.ljust(x) | (cfw:render-left x s) | calfw.el 参照 |
| s.lstrip | (regexp-in-string “^[ \t]+” "” s) | sに改行が入らないことが前提 |
| s.lstrip! | ||
| s.match(x) | ||
| x.match(s) | (string-match x s) | マッチ結果はグローバルに保持している match-string, match-beginning, match-end 等参照 |
| s.next! | ||
| s.succ! | ||
| s.oct | ||
| s.to_i(8) | (string-to-number s 8) | |
| s.partition(x) | string-matchで自分でちぎる | |
| s.replace(x) | ||
| s.reverse | (apply ‘string (reverse (append s nil))) | |
| s.reverse! | ||
| s.rindex(x) | 一時バッファで search-backward 等を使う | |
| s.rjust(x) | (cfw:render-right x s) | calfw.el 参照 |
| s.rpartition(x) | string-matchで自分でちぎる | |
| s.rstrip | (regexp-in-string “[ \t]+$” "” s) | sに改行が入らないことが前提 |
| s.rstrip! | ||
| s.scan(x) { … } | 一時バッファで re-search-forward 等を使う | |
| s[i] | ||
| s.slice(i) | (aref s i) | 文字コード(数値)が返ってくる文字コードからstringにするには char-to-string |
| s[i..-1] | ||
| s.slice(i..-1) | (substring s i) | |
| s[i, l] | ||
| s.slice(i, l) | (substring s i (+ i l)) | |
| s[i..j] | ||
| s.slice(i..j) | (substring s i (1+ j)) | |
| s[i…j] | ||
| s.slice(i…j) | (substring s i j) | |
| s.split(x) | (split-string s x) | |
| s.start_with?(x)*4 | (string-match (concat “^” x) s) | sに改行が入らないことが前提 |
| s.strip | (regexp-in-string “\\(^[ \t\n\r]+\\ [ \t\n\r]+$\)” "” s) | sに改行が入らないことが前提 |
| s.strip! | ||
| s.sub(x, y) | string-match を使って自分で初回を取ってくる | |
| s.sub!(x, y) | ||
| s.swapcase | ||
| s.to_f | (string-to-number s) | 整数に見える場合は整数が返る |
| s.to_i | (string-to-number s) | |
| s.to_s | (substring s 0) | |
| s.tr!(x, y) | ||
| s.tr_s!(x, y) | ||
| s.unpack(x) | bindatを使う | |
| s.upcase | (upcase s) | |
| s.upcase! | ||
| Regexp.escape(s) | ||
| Regexp.quote(s) | (regexp-quote s) |
※もし間違いを見つけたら教えてください。
表示幅について
format の桁指定("%5s” など)で表示幅のパディングが出来るが、あふれた文字列は切り落とされずに全部表示されてしまう。
指定幅で切り取りたい場合は、自分であらかじめ文字列の長さを見て切り落と(truncate)しておく必要がある。
切り落としには substring とかではなくて、全角文字などについても適切に幅を見てくれる truncate-string-to-width を使うと良い感じ。
文字列内の複雑な検索、置換など
replace-regexp-in-string を使えば大抵のことが出来る。関数も取れるので Ruby の gsub 相当も出来る。
ただ、複雑な正規表現をこねくり回したい場合は以下のように一時バッファを使う。
;; input に文字列が入っている ;; output に変換後の文字列を入れたい (setq output (with-temp-buffer (insert input) (goto-char (point-min)) (while (re-search-forward REGEXP nil t) ;; ここで match-string などを使って何かする ) (buffer-string)))
テキストプロパティについて
HTML の DOM オブジェクトに独自属性を付加するように、自由に属性を入れることが出来る。 HTML とは違って1文字単位でプロパティが設定できるが、大抵はまとまった領域にプロパティを設定する。
テキスト属性を使えるようになると、かなり複雑なアプリケーションが作れるようになる(と思う)。
アプリケーションからの利用
テキストプロパティに必要な値をごっそり入れておき、キー操作でカーソル上の文字列からテキストプロパティを取ってきて必要なアクションを行うというような方法が一般的に行われている。大抵は、行全体や必要な領域全体にプロパティをつけておいて、キー操作で取得するときは行頭の1文字目のプロパティを見るということがよく行われている。
Dired や orgmode など、多くのプログラムがこのような設計になっている。しかしながら、場当たり的にテキストプロパティを使い始めると、テキストプロパティを舞台にしたスパゲティコードになるため、本格的に使う場合はあらかじめ構造化したデータを代表して入れておく等の工夫をした方がよいと考えている。
はまりポイントなど
属性は文字列の操作などでもそのままコピーされてしまうので、欲しくない場合は substring-no-properties などで消す。
属性の操作は破壊的なので、うっかり変えてはいけないところまで変えてしまうことがある。また、ソースコードリテラルの文字列オブジェクトには属性の操作ができない。なので、外から来た文字列に対して属性を操作する場合は、文字列をコピーしておくと安全。
font-lock が有効なバッファで face に何か入れても速攻でクリアされてしまう。そんなときは font-lock-face に入れる。
テキスト属性のループパターン バッファの中の特定のテキストプロパティについてループを書きたいときがある。そんなときは next-property-change または next-single-property-change などを使う。 Info の next-property-change にループのサンプルがある。
以下は face の付いている領域についてループするイディオム。
(defun iterate-text-prop (prop func) (loop with pos = (point-min) for next = (next-single-property-change pos prop) for text-val = (and next (get-text-property next prop)) while next do (when text-val (funcall func pos next text-val)) (setq pos next))) (iterate-text-prop 'face (lambda (begin end val) (and val (message ">> %S : %S" val (buffer-substring-no-properties begin end)))))N オーダーではあるけどもなかなか高速に動作するので、 O(N^2) のアルゴリズムでなければ問題なさそう。
DONE MELPAのパッケージページにHomepageリンクが追加
MELPAの公式がツイートしてた。
We changed the MELPA website's package pages so that "Homepage" links go to the URL named in the package's Homepage or URL header, instead of directly to the source code. Hope that helps you explore more conveniently!
— MELPA (@melpa_emacs) May 26, 2020
どうやらこのコミットらしい。このissueが引用されています。 さらにこのMLの投稿がリンクされている。投稿者はEmmsのメンテナであるYuniさん。
ポイントは以下。
What the MEPLA people are doing that I don't like:
* Never communicate with the developers of the Emms in any way.
* Omit many files that come with Emms.
* Associate Emms with several Emms extensions that live only on
MELPA and that we, the Emms developers, have never heard
about. This would give anyone accessing Emms via MELPA that those
extensions are somehow a part of Emms, when they are not.
Maybe those extensions are good, in which case I would love for
the developers to contact us, the Emms developers. But Maybe those
extensions are bad, don't work, are out of date, or connect with
non-free services.
* Not even linking to the Emms home page
(https://www.gnu.org/software/emms/).
Ideas for improvement:
* Encourage people to speak to the developers of a project before
packaging it.
* Find a way of packaging a project as-is. For instance, Emms could
be distributed as is, and the M/ELPA software could simply point
at where Emms keeps its .el files for Emacs to find. This is
instead of how I see ELPA working now, which is to force the
software through a kind of a sieve (I think ELPA calls it a
recipe) where only a select few files come out the other end.
Emms doesn't need a recipe; it already comes organized and
packaged for working with Emacs.
It makes me think of taking bread, crumbling it up, the mushing
those crumbs back together to re-form a new loaf of bread.
この投稿を受けて、MELPAにHomepageリンクの追加が提案されました。

このようにHomepageというボタンが追加されました。
ただ、、MLは個人的にあまり使いたくない。。
そもそも量が膨大すぎるし、この量をさばくUIもない。 本家の開発に参加したいのは山々なのですが、本質とは関係ないところで敷居を高くしている気がします。
TODO Elispにおけるquote(')とfunction(#')の使い分け
ネタ元はEmacs-jp。
functionを使うとバイトコンパイル時に関数が見つからない旨、警告を出してくれる。(kakiさんから)
chuntaroさん
24 で lexical binding を実装するまでは一緒だったけど、24 から function の方にクロージャを生成すると言う機能が追加されてて、
'(lambda () ...)
#'(lambda () ...)
とした時に違いがあります。
詳しくは一応↓このツイートに書いてあります。
https://twitter.com/shima_tetsuo/status/1056030917284622336
ちなみに、シンボルに対しては (quote symbol) (function symbol) は単にシンボルを返すだけだから、全く同じです。
defun した関数は (function hoge) の様にするのみなので、これは単にシンボル hoge が返るだけですね。 ちなみに、ソース(C言語)を見てみると
DEFUN ("function", Ffunction, Sfunction, 1, UNEVALLED, 0,
doc: /* Like `quote', but preferred for objects which are functions.
In byte compilation, `function' causes its argument to be handled by
the byte compiler. `quote' cannot do that.
usage: (function ARG) */)
(Lisp_Object args)
{
Lisp_Object quoted = XCAR (args);
if (!NILP (XCDR (args)))
xsignal2 (Qwrong_number_of_arguments, Qfunction, Flength (args));
if (!NILP (Vinternal_interpreter_environment)
&& CONSP (quoted)
&& EQ (XCAR (quoted), Qlambda))
{ /* This is a lambda expression within a lexical environment;
return an interpreted closure instead of a simple lambda. */
Lisp_Object cdr = XCDR (quoted);
Lisp_Object tmp = cdr;
if (CONSP (tmp)
&& (tmp = XCDR (tmp), CONSP (tmp))
&& (tmp = XCAR (tmp), CONSP (tmp))
&& (EQ (QCdocumentation, XCAR (tmp))))
{ /* Handle the special (:documentation <form>) to build the docstring
dynamically. */
Lisp_Object docstring = eval_sub (Fcar (XCDR (tmp)));
CHECK_STRING (docstring);
cdr = Fcons (XCAR (cdr), Fcons (docstring, XCDR (XCDR (cdr))));
}
return Fcons (Qclosure, Fcons (Vinternal_interpreter_environment,
cdr));
}
else
/* Simply quote the argument. */
return quoted;
}
ちょっと分かり難いけど… (lambda …) となっているコンスセルのみに反応するようになってるのが分かると思います。
TODO mewが遅い件について
nom 12:24 もうずっと Mew を使っていますが,IMAP の fetch は確かに遅い気がします. サーバが Gmail の場合,読むのは Web UI で,返事書くのを Mew で書いたりすることもあります. nom 12:40 最新100件だけ fetch するのであれば, i (inc) ではなく,s (scan) して,range に last:100 のようにする方法がありますね.
TODO serch-forwardについて
roswell 22:23 “point がある行で、引数で指定した文字列の先頭に pointを移動” というような処理が必要なので、以下のような関数で動作確認はできたのですが … point がある行を読み取るのは、 (thing-at-point ‘line) でもできますが、もっと適切な関数等ありますでしょうか? 文字列検索 search-forward , word-search-forward は、文字列の最後に point 移動するので、string-match を使いましたが、指定文字列の先頭に point 移動するような関数ありますでしょうか? 1.,2. 組み合わせれば、もっと簡潔な記述できるような気がしていますが、もっと簡潔な記述ありますでしょうか? (defun my-move-point-to-begining-of-string-in-line (string) (interactive “sSearch String: “) (let ((pos (string-match string (thing-at-point ‘line)))) ; point がある行の、引数で指定した文字列の先頭に pointを移動 (if pos (move-to-column pos))))
zk-phi 11:15
- について、 `search-forward` した後は `match-beginning` で開始位置がとれるので、そこに `goto-char` してしまう手はありそうです。すると 1. の「 point のある行を文字列として切り出す」必要がなくなるぶん、気持ち簡潔になりますかね…?
(and (search-forward string (point-at-eol) nil t) (goto-char (match-beginning 0)) でもこれくらいしか思いつかないです (編集済み)
TODO EmacsでHTMLをいい感じにバッファに表示する方法
shr-render-buffer/shr-render-region
TODO Emacs-26.3新機能 add-variable-watcherを使ってみる
chuntaro 16:58
26.1 から add-variable-watcher なんてのが追加されてたんだな。
(defvar my-var 1)
my-var
(add-variable-watcher 'my-var (lambda (symbol newval operation where)
(print (list symbol newval operation where))))
nil
(setq my-var 2)
(my-var 2 set nil) ; ← こんな事が分かる
2
(let ((my-var 3))
(print my-var))
(my-var 3 let nil) ; let でも反応する
3
(my-var 2 unlet nil) ; 元に戻す事は unlet って言うのか
3
https://www.gnu.org/software/emacs/manual/html_node/elisp/Watching-Variables.html
chuntaro 17:00 add-variable-watcher を使うと、setq だけで処理を進められる邪悪なコーディングが出来てしまうから、あくまでもデバッグ目的で使うのが良いのかもね。 ただ、ビューアー的なものを作る場合は特定の変数の変化に対応出来るから便利かもしれない。
TODO push, setcdrについて
roswell 14:36 list の指定した位置のセルとセルの間に、セルを追加する場合、 例えば、下記の (0 1 2 3) の 1 と 2 の間に、 ‘a を追加する場合、 (setq xlist ‘(0 1 2 3)) => (0 1 2 3) (setcdr (nthcdr 1 xlist) (cons ‘a (nthcdr 2 xlist))) =>(a 2 3) xlist =>(0 1 a 2 3) の様に記述しているのですが、 もっと簡単な記述とかあればご教示いただけますでしょうか? 指定した位置に追加できる様な、関数など有りますでしょうか? (編集済み)
smzht 16:34 他にも方法はあると思いますが、こんなのとか。 (setq xlist ‘(0 1 2 3))
(defmacro add-to-list-2 (list element) `(setf ,list (cons ,element ,list)))
(add-to-list-2 (nthcdr 2 xlist) ‘a)
smzht 16:52 これでできるようです。 (push ‘a (nthcdr 2 xlist)) (編集済み)
roswell 17:32 @smzht さんご教示ありがとうございます。 defmacro の例より push の方が簡単に記述できるので 気に入りました。 info だと (setq l ‘(a b)) ⇒ (a b) (push ‘c l) ⇒ (c a b) l ⇒ (c a b) リストの先頭に追加する例だったので、 途中に追加した際に、その前の部分と接続がされるのか 想像にも及びませんでした。 (編集済み)
kosh 18:55 http://www.ic.unicamp.br/~meidanis/courses/mc336/2006s2/funcional/L-99_Ninety-Nine_Lisp_Problems.html L-99 の21問目が参考になるかも(ちょっとしたリスト遊びや再帰の勉強にもなる lisp (insert-at ‘alfa ‘(a b c d) 2) ;=> (A ALFA B C D)
kaki 22:38 push is a Lisp macro in `subr.el’.
(push NEWELT PLACE)
Add NEWELT to the list stored in the generalized variable PLACE. This is morally equivalent to (setf PLACE (cons NEWELT PLACE)), except that PLACE is only evaluated once (after NEWELT). とのことなので、 setf が使える対象には push も使えるみたいですね。これでちゃんとリストが繋がるのは、あとはコンスセルの構造を考えれば分かります。
TODO Emacs –batchで標準エラー出力に出力する方法
https://stackoverflow.com/questions/22455366/how-can-i-write-to-standard-output-in-emacs
TODO 競技プログラミングのためのC++リファレンス
cppは得意ではありませんが、競技プログラミングにおいては最強です。 特にClangの最適化の恩恵を得られることは大きいです。 コンテスト中にあちこちを見て回るのは時間の無駄なので、過去問演習中に必要だった情報をまとめておきます。
TODO Arch printer
http://cocu.hatenablog.com/entry/2016/07/19/225308
TODO AtCoderのjsonAPIについて
https://atcoder.jp/contests/abc086/standings/json にアクセスするとAC状況が見れる。 情報源はこのブログ http://tatsumack.hatenablog.com/entry/2018/12/09/113908 。* man
TODO スクリーンショットコマンドscrot(1)のオプションと使い方
| Short | Long | Description |
|---|---|---|
TODO mustacheのwarningをなおす
TODO ox-hugoのwarningをなおす
DONE Elispファイルをタブ/スペース混在インデントからスペースインデントに修正する方法
インデントの話
そもそもEmacsはタブ/スペース混在インデントが初期設定です。
そのためEmacs本体のソースなどはすべて混在インデントでインデントされています。 しかし「空白8つよりタブ1つにしてファイル容量を削減しなくては」という時代は明らかに終わったので、スペースでインデントしましょう。して下さい(泣)。
Emacs lisp style guideにもスペースでインデントすることが太字で記載されていますし、 white-space-mode での見た目も良いです。
(ということでほとんどのユーザーはグローバルで indent-tabs-mode を nil に設定しています。
そのため本体のソースはタブ/スペース混在インデントの部分とスペースインデントの部分が混在しています。カオス。)
これからパッケージを作り始めるときはさておき、歴史のあるソースで修正するのは多少の痛みを伴います。 しかし、Blameで一回変なコミットを見てしまうことを甘んじて受け入れてでもスペースでインデントして欲しいのです。
そもそもgit logやgit blameには空白の変更を無視するオプション( -w )があるので履歴の面でもデメリットは無視できます。
Magitのblameでは -w はデフォルトで指定されていますし、GitHubのdiffでも無視することができます。
メリットとデメリットは以上です。
インデントの変更
「よし、スペースインデントにしてやるぜ。」とタブ/スペース混在インデントから考えを変えて下さった方。ありがとうございます。
しかし、すでに成長しきっているプロジェクトで数十のファイルがあるんだけど。。というときには次のスニペットを使って頂ければと思います。
- diredでElispだけをフィルタリング(dired-filter)して、
- 最初のファイルにポインタを合わせ、
- 次のスニペットを
M-:から実行するだけです。(while (dired-get-filename nil 'no-error) (let ((file (dired-get-filename)) (indent-tabs-mode nil)) (with-temp-file file (insert-file-contents file) (emacs-lisp-mode) (delete-trailing-whitespace) (untabify (point-min) (point-max)) (indent-region (point-min) (point-max))) (dired-next-line 1)))
多少時間がかかるかもしれませんが、diredでリストされているファイルの末尾まで処理を実行してくれます。
今後の悲劇を防止する
タブ/スペース混在インデントでソースが管理されていたということは、レポジトリオーナーやコントリビュータによって、また混在インデントによるコミットが行われる可能性が高いです。
あるディレクトリ以下で特定の設定を強制することができます。 .dir-locals.el です。
以下の .dir-locals.el をレポジトリルートに置いて今後の悲劇を防止できます。この方法はmagitのレポジトリでも採用されています。
((emacs-lisp-mode
(indent-tabs-mode . nil))
.dir-locals.el をレポジトリルートに置きたくないんだけど。という場合には以下を各Elispファイルの末尾付近に置いておけば大丈夫です。
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
なお、いろいろなプロジェクトを見て慣習を探ったのですが、 provide と ends here の間に置くことが多いようです。
(provide '{{pkg-name}})
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; {{pkg-name}}.el ends here
まとめ
タブ/スペース混在インデントは役目を終えました。
Emacsは後方互換性を重視しているためデフォルト値が変更される望みは薄いですが、コミュニティとしてはスペースインデントを使うということでなにとぞ。。
TODO andがapplyできない。。Lispでリストの全てが条件を満たすか調べる方法
cl-every
and はスペシャルフォームなので、 apply できません。
(apply #'and (mapcar #'evenp '(2 4 6 8 10)))
;;=> Debugger entered--Lisp error: (invalid-function #<subr and>)
;; and(t t t t t)
;; apply(and (t t t t t))
;; eval((apply (function and) (mapcar (function evenp) (quote (2 4 6 8 10)))) nil)
;; scratch-comment--elisp--eval-last-sexp(t)
;; scratch-comment--eval-last-sexp(t)
;; scratch-comment-eval-sexp()
;; funcall-interactively(scratch-comment-eval-sexp)
;; call-interactively(scratch-comment-eval-sexp nil nil)
;; command-execute(scratch-comment-eval-sexp)
cl-lib に cl-every という、リストの全てが non-nil であることを検査する述語があります。
(require 'cl-lib)
(cl-every #'evenp '(2 4 6 8 10))
;;=> t
(cl-every #'evenp '(2 4 6 8 10 11))
;;=> nil
cl-some
同様に or もスペシャルフォームなので、 apply できません。
DONE Elispでstring-trimの代替を用意する方法
背景
string-trim とは与えた文字列の先頭と末尾の空白文字っぽいものを削除してくれる subr-x に定義されている便利関数です。
しかし、 string-trim が subr-x に追加されたのはEmacs-24.4からなので、Emacs-24.4未満で動かしたいエクストリームパッケージはその恩恵を得ることができません。
簡単な関数なので何度か作ったのですが、いちいち自分のパッケージをgrepするのが面倒なので、ブログポストとして残しておきます。
実装
string-trim はサブルーチンとして string-left と string-right を組み合わせて実装されています。
(defsubst string-trim-left (string &optional regexp)
"Trim STRING of leading string matching REGEXP.
REGEXP defaults to \"[ \\t\\n\\r]+\"."
(if (string-match (concat "\\`\\(?:" (or regexp "[ \t\n\r]+") "\\)") string)
(substring string (match-end 0))
string))
(defsubst string-trim-right (string &optional regexp)
"Trim STRING of trailing string matching REGEXP.
REGEXP defaults to \"[ \\t\\n\\r]+\"."
(let ((i (string-match-p (concat "\\(?:" (or regexp "[ \t\n\r]+") "\\)\\'")
string)))
(if i (substring string 0 i) string)))
(defsubst string-trim (string &optional trim-left trim-right)
"Trim STRING of leading and trailing strings matching TRIM-LEFT and TRIM-RIGHT.
TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"."
(string-trim-left (string-trim-right string trim-right) trim-left))
つまり、これらを連結すればできます。
(defun my--string-trim (str &optional trim-left trim-right)
"Trim STR of leading and trailing strings matching TRIM-LEFT and TRIM-RIGHT.
TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\".
Original function is `string-trim'.
This function is polyfill for Emacs<24.4."
(let ((res str))
(setq res (replace-regexp-in-string (or trim-left "\\`[ \t\n\r]*") "" res))
(setq res (replace-regexp-in-string (or trim-right "[ \t\n\r]*\\'") "" res))
res))
また、この実装では replace-regexp-in-string を2回実行していますが、正規表現を工夫すれば1回で良いです。
(defun my--string-trim (str)
"Trim STR of leading and trailing space like strings.
TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\".
Original function is `string-trim'.
This function is polyfill for Emacs<24.4."
(replace-regexp-in-string "\\`[ \t\n\r]*\\|[ \t\n\r]*\\'" "" str))
まとめ
string-trim を実装しました。
もう一度書きますが、この関数は subr-x に定義されている関数なので、「Emacs-24.4未満で動かしたいんだけど」というエクストリームパッケージ用です。
正攻法としてはEmacsの要求バージョンを上げてしまうのが一番早いので、そこは留意して頂ければと思います。
DONE GitHub Actionsで全てのジョブの実行を強制する方法
背景
GitHub Actions便利ですね。 着々とMSの資金力に囲われている気がしますが、便利なものには乗っかってしまった方がいいのが事実。
困っていたのはmatrixで複数バージョンのテストを走らせたときに、ひとつ失敗するとまだ終わってないジョブが強制的にキャンセルされてしまうこと。 (実際のActions)

この例だとバージョンの違いでCIが失敗することが分かりますが、その境界が分かりません。これは困る。
ドキュメントを良く見ると、この動作を変える方法が載ってたので紹介する次第です。
方法
ドキュメントの該当箇所は 「Workflow syntax for GitHub Actions - jobs.<job_id>.strategy.fail-fast」です。
strategy.fail-fast- に false を指定するだけです。全体はこんな感じ。(実際のActions)
name: Main workflow
on:
push: {branches: [master]}
pull_request: {branches: [master]}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: purcell/setup-emacs@master
with:
version: '26.3'
- uses: conao3/setup-keg@master
- name: Run lint
run: 'make lint'
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
emacs_version:
- '24.1'
- '24.2'
- '24.3'
- '24.4'
- '24.5'
- '25.1'
- '25.2'
- '25.3'
- '26.1'
- '26.2'
- '26.3'
- 'snapshot'
include:
- emacs_version: 'snapshot'
allow_failure: true
steps:
- uses: actions/checkout@v1
- uses: purcell/setup-emacs@master
with:
version: ${{ matrix.emacs_version }}
- uses: conao3/setup-keg@master
- name: Run tests
if: matrix.allow_failure != true
run: 'make test'
- name: Run tests (allow failure)
if: matrix.allow_failure == true
run: 'make test || true'
この設定をすることで以下の結果を得ることができました。 境界はEmacs-24.3とEmacs-24.4の間でしたね。

まとめ
マシンを貸し出す側からしたらさっさとジョブを中止したいのだと思いますが、
ユーザーからすると strategy.fail-fast を false に設定するのは必須かもしれません。
パブリックレポジトリでのActionsは無料なのでどんどん使ってバグをmasterに入れないようにしましょう!
参考
DONE Elispでplist-getをsetfに対応させる方法
背景
setf という便利な関数があります。
これは汎変数に対する setq と説明できるのですが、この短かい紹介では表せないほどの汎用性と利便性を提供してくれます。
汎変数についてはkawabataさんの神記事(Qiita - Emacs Lispの汎変数(とその他))があるので、まずそちらを参照していただければと思います。
さて、問題は alist-get は汎変数として定義されているのに、なぜか plist-get の汎変数が定義されていないことです。
alist も plist もlispの基本構造で、本質的に同じなので alist だけ使えて plist だけ使えないというのは
無意味な非対称性を感じます。
今回は plist に対して setf を使えるようにすることを目的とします。
汎変数の定義
magit などの大きな(そして注意深く実装されている)パッケージを読んでいると、いきなり gv- から始まるS式を見ることがあります。
これが汎変数の定義であり、標準で提供されていない汎変数を独自に定義することができます。
汎変数の定義についてもkawabataさんの記事(Qiita - Emacs Lispの汎変数(とその他)#汎変数の定義方法)に記載があります。
ここでは gv.el の実装を参考に、汎変数を定義する方法を解説します。
gv-define-simple-setter
最も簡単なのは
gv-define-simple-setterを使用できる場合です。 セッターを組み立てる際、get<->set/putの対応が取れている関数が用意されている場合で、単に関数シンボルを替えるだけで良い場合に使用できます。(gv-define-simple-setter aref aset) (gv-define-simple-setter car setcar) (gv-define-simple-setter cdr setcdr) (gv-define-simple-setter get put) (gv-define-simple-setter default-value set-default) (gv-define-simple-setter process-get process-put)setfやrotatefはこのように展開され、意図通りにリストが変更されます。(ppp-macroexpand-all (let ((target '(a b c))) (setf (car target) 'modify) target)) ;;=> (let ((target '(a b c))) ;; (let* ((v target)) ;; (setcar v 'modify)) ;; target) (let ((target '(a b c))) (setf (car target) 'modify) target) ;;=> (modify b c) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (ppp-macroexpand-all (let ((target '(a b c))) (cl-rotatef (car target) (cadr target)) target)) ;;=> (let ((target '(a b c))) ;; (let* ((v target) ;; (--cl-rotatef-- (car v))) ;; (progn ;; (setcar v ;; (let* ((v target)) ;; (prog1 (car (cdr v)) ;; (setcar ;; (cdr v) ;; --cl-rotatef--)))) ;; nil)) ;; target) (let ((target '(a b c))) (cl-rotatef (car target) (cadr target)) target) ;;=> (b a c)
gv-define-setter
gv-define-setterはセッターを組み立てる際に、単に関数シンボル置き換えでは実現できない場合に使用します。基本的にはこのマクロを使うことになるかと思います。 なお、先程の
gv-define-simple-setterはgv-define-setterを生成するマクロとして定義されていました。(gv-define-setter caar (val x) `(setcar (car ,x) ,val)) (gv-define-setter cadr (val x) `(setcar (cdr ,x) ,val)) (gv-define-setter cdar (val x) `(setcdr (car ,x) ,val)) (gv-define-setter cddr (val x) `(setcdr (cdr ,x) ,val)) (gv-define-setter elt (store seq n) `(if (listp ,seq) (setcar (nthcdr ,n ,seq) ,store) (aset ,seq ,n ,store))) (gv-define-setter gethash (val k h &optional _d) `(puthash ,k ,val ,h))gv-define-setterの使用方法は眺めていると分かってきますが、 第1引数に汎変数を定義する関数シンボル、第2引数に関数の引数、第3引数にマクロ展開部を指定します。 第2引数は先頭に変更先の値を受け取って、その後に関数の引数を並べることになっているようです。setfやrotatefはこのように展開され、意図通りにリストが変更されます。(ppp-macroexpand-all (let ((target '(a b c))) (setf (cadr target) 'modify) target)) ;;=> (let ((target '(a b c))) ;; (let* ((v target)) ;; (setcar (cdr v) 'modify)) ;; target) (let ((target '(a b c))) (setf (cadr target) 'modify) target) ;;=> (a modify c) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (ppp-macroexpand-all (let ((target '(a b c))) (cl-rotatef (cadr target) (caddr target)) target)) ;;=> (let ((target '(a b c))) ;; (let* ((v target) ;; (--cl-rotatef-- (car (cdr v)))) ;; (progn ;; (setcar ;; (cdr v) ;; (let* ((v (cdr (cdr target)))) ;; (prog1 (car v) ;; (setcar v --cl-rotatef--)))) ;; nil)) ;; target) (let ((target '(a b c))) (cl-rotatef (cadr target) (caddr target)) target) ;;=> (a c b)
gv-define-expander
gv-define-expanderはセッターを生成する際にセッターを使いたい場合など、特別な処理を要する関数に使用します。alist-getの汎変数はこのマクロで定義されています。なお、先程の
gv-define-setterはgv-define-expanderを生成するマクロとして定義されていました。(gv-define-expander alist-get (lambda (do key alist &optional default remove testfn) (macroexp-let2 macroexp-copyable-p k key (gv-letplace (getter setter) alist (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq))) (assoc ,k ,getter ,testfn) (assq ,k ,getter)) (funcall do (if (null default) `(cdr ,p) `(if ,p (cdr ,p) ,default)) (lambda (v) (macroexp-let2 nil v v (let ((set-exp `(if ,p (setcdr ,p ,v) ,(funcall setter `(cons (setq ,p (cons ,k ,v)) ,getter))))) `(progn ,(cond ((null remove) set-exp) ((or (eql v default) (and (eq (car-safe v) 'quote) (eq (car-safe default) 'quote) (eql (cadr v) (cadr default)))) `(if ,p ,(funcall setter `(delq ,p ,getter)))) (t `(cond ((not (eql ,default ,v)) ,set-exp) (,p ,(funcall setter `(delq ,p ,getter)))))) ,v))))))))))alist-getの汎変数の定義がこのように複雑になっているのは、「alistに存在しないキーに対してsetfしたときにコンスセルごと追加する」という処理があるからです。(ppp-macroexpand-all (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target)) ;;=> (let ((target '((a . "a") (b . "b") (c . "c")))) ;; (let* ((p (if (and nil (not (eq nil 'eq))) ;; (assoc 'd target nil) ;; (assq 'd target)))) ;; (progn ;; (if p ;; (setcdr p "modify") ;; (setq target (cons (setq p (cons 'd "modify")) target))) ;; "modify")) ;; target) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'a target) "modify") target) ;;=> ((a . "modify") (b . "b") (c . "c")) (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;;=> ((d . "modify") (a . "a") (b . "b") (c . "c"))「alistに存在しないキーに対して
setfしたときにコンスセルごと追加する」という処理が必要ない場合、gv-define-setterを使って簡単に定義することができます。 (さらに簡単のため、alist-getのdefault、remove、testfn引数も無視します。)(gv-define-setter alist-get (val key alist &optional _default _remove _testfn) `(setcdr (assq ,key ,alist) ,val)) ;;=> ... (ppp-macroexpand-all (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'a target) "modify") target)) ;;=> (let ((target '((a . "a") (b . "b") (c . "c")))) ;; (let* ((v target)) ;; (setcdr (assq 'a v) "modify")) ;; target) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'a target) "modify") target) ;;=> ((a . "modify") (b . "b") (c . "c")) (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;;=> Debugger entered--Lisp error: (wrong-type-argument consp nil) ;; setcdr(nil "modify") ;; (let* ((v target)) (setcdr (assq 'd v) "modify")) ;; (setf (alist-get 'd target) "modify") ;; (let ((target '((a . "a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;; (progn (let ((target '((a . "a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target)) ;; eval((progn (let ((target '((a . "a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target)) t)このように簡単に定義できますが、alistに存在しないキーに対して作用させようとするとエラーになってしまいます。
alist-getの汎変数はこのような要件を満たすために複雑な定義になっています。なお、
gv-define-expanderはgv-define-setterから生成されるので、gv-define-setterがどのような式を生成するかを眺めることでも使用法を予想することができます。(ppp-macroexpand (gv-define-setter cddr (val x) `(setcdr (cdr ,x) ,val))) ;;=> (gv-define-expander cddr ;; (lambda (do &rest args) ;; (declare-function gv--defsetter "gv" ;; (name setter do args &optional vars)) ;; (gv--defsetter 'cddr ;; (lambda (val x) `(setcdr (cdr ,x) ,val)) ;; do args)))alist-getの汎変数余談
gv-define-setterは分かりやすいので、どうにかしてalist-getの汎変数をgv-define-setterで定義できないでしょうか。 まず思いつくのはこのような定義です。(
macroexp-let2は第1引数がnilのときに、第2引数の名前でmake-symbolしたものに第3引数の値をlet束縛するものです。これはマクロ展開のイディオムを簡略化するものであり、望まない変数捕捉を避けるものです。(マクロの変数捕捉についてはOnlisp(Onlisp - 変数捕捉)参照))
(gv-define-setter alist-get (val key alist &optional _default _remove _testfn) (macroexp-let2 nil p `(assq ,key ,alist) `(if ,p (setcdr ,p ,val) (setq ,alist (cons (cons ,key ,val) ,alist))))) ;;=> ... (ppp-macroexpand-all (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'a target) "modify") target)) ;;=> (let ((target '((a . "a") (b . "b") (c . "c")))) ;; (let* ((v target) ;; (p (assq 'a v))) ;; (if p ;; (setcdr p "modify") ;; (setq v (cons (cons 'a "modify") v)))) ;; target) ;; nil ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'a target) "modify") target) ;;=> ((a . "modify") (b . "b") (c . "c")) (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;;=> ((a . "a") (b . "b") (c . "c"))しかし上手く動いてないようです。マクロ展開結果を注意深く見れば分かるのですが、letで一時的に束縛された変数にsetqしてしまっています。 これではグローバルの値を変更できていません。
先程確認したように
gv-define-setterはgv--defsetterを展開し、これに含まれるmacroexp-let2によりalistをlet束縛してしまいます。gv-define-expanderを直接使うことでmacroexp-let2を展開せず、gv-letplaceでグローバル値のセッターを得ることができます。(defun gv--defsetter (name setter do args &optional vars) "Helper function used by code generated by `gv-define-setter'. NAME is the name of the getter function. SETTER is a function that generates the code for the setter. NAME accept ARGS as arguments and SETTER accepts (NEWVAL . ARGS). VARS is used internally for recursive calls." (if (null args) (let ((vars (nreverse vars))) (funcall do `(,name ,@vars) (lambda (v) (apply setter v vars)))) ;; FIXME: Often it would be OK to skip this `let', but in general, ;; `do' may have all kinds of side-effects. (macroexp-let2 nil v (car args) (gv--defsetter name setter do (cdr args) (cons v vars))))) (ppp-macroexpand (gv-define-setter alist-get (val key alist &optional _default _remove _testfn) (gv-letplace (getter setter) alist (macroexp-let2 nil p `(assq ,key ,alist) `(if ,p (setcdr ,p ,val) ,(funcall setter `(cons (cons ,key ,val) ,getter))))))) ;;=> (gv-define-expander alist-get ;; (lambda (do &rest args) ;; (declare-function gv--defsetter "gv" ;; (name setter do args &optional vars)) ;; (gv--defsetter 'alist-get ;; (lambda (val key alist &optional _default _remove _testfn) ;; (gv-letplace (getter setter) alist ;; (macroexp-let2 nil p `(assq ,key ,alist) ;; `(if ,p ;; (setcdr ,p ,val) ;; ,(funcall setter `(cons (cons ,key ,val) ;; ,getter)))))) ;; do args))) ;; nil (ppp-macroexpand (gv-define-expander alist-get (lambda (do key alist &optional _default _remove _testfn) (gv-letplace (getter setter) alist (macroexp-let2 nil p `(assq ,key ,alist) (funcall do '_getter (lambda (val) `(if ,p (setcdr ,p ,val) ,(funcall setter `(cons (cons ,key ,val) ,getter)))))))))) ;;=> (function-put 'alist-get 'gv-expander ;; (lambda (do key alist &optional _default _remove _testfn) ;; (gv-letplace (getter setter) alist ;; (macroexp-let2 nil p `(assq ,key ,alist) ;; (funcall do '_getter ;; (lambda (val) ;; `(if ,p ;; (setcdr ,p ,val) ;; ,(funcall setter `(cons (cons ,key ,val) ;; ,getter))))))))) ;; nil次に示すように、
gv-define-setterでは上手くいかず、gv-define-expanderを使うと上手くいきます。(gv-define-setter alist-get (val key alist &optional _default _remove _testfn) (gv-letplace (getter setter) alist (macroexp-let2 nil p `(assq ,key ,alist) `(if ,p (setcdr ,p ,val) ,(funcall setter `(cons (cons ,key ,val) ,getter)))))) ;;=> ... (ppp-macroexpand-all (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target)) ;;=> (let ((target '((a . "a") ;; (b . "b") ;; (c . "c")))) ;; (let* ((v target) ;; (p (assq 'd v))) ;; (if p ;; (setcdr p "modify") ;; (setq v (cons (cons 'd "modify") v)))) ;; target) ;; nil (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;;=> ((a . "a") (b . "b") (c . "c")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (gv-define-expander alist-get (lambda (do key alist &optional _default _remove _testfn) (gv-letplace (getter setter) alist (macroexp-let2 nil p `(assq ,key ,alist) (funcall do '_getter (lambda (val) `(if ,p (setcdr ,p ,val) ,(funcall setter `(cons (cons ,key ,val) ,getter))))))))) ;;=> ... (ppp-macroexpand-all (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target)) ;;=> (let ((target '((a . "a") ;; (b . "b") ;; (c . "c")))) ;; (let* ((p (assq 'd target))) ;; (if p ;; (setcdr p "modify") ;; (setq target (cons (cons 'd "modify") target)))) ;; target) ;; nil (let ((target '((a ."a") (b . "b") (c . "c")))) (setf (alist-get 'd target) "modify") target) ;;=> ((d . "modify") (a . "a") (b . "b") (c . "c"))
まとめ
setf はとても便利なので、ぜひ使いこなして頂ければと思います。
今回、汎変数を定義する方法についてまとめたので、自作パッケージでも汎変数を定義しておいてあげるとユーザーが便利に使えるパッケージになると思います。
なお、今回の目的である plist-get の汎変数については gv-define-setter を使って、次のように定義しました。
後でemacs-develにも投稿しておきたいと思います。
本家に取り入れられればこのパッチなしで plit-get に対する setf を使用できるようになります。
(gv-define-expander plist-get
(lambda (do plist prop)
(macroexp-let2 macroexp-copyable-p key prop
(gv-letplace (getter setter) plist
(macroexp-let2 nil p `(plist-member ,getter ,key)
(funcall do
`(cadr ,p)
(lambda (val)
`(if (plist-member ,plist ,key) (setcar (cdr (plist-member ,plist ,key)) ,val)
,(funcall setter `(cons ,key (cons ,val ,getter)))))))))))
;;=> ...
(ppp-macroexpand-all
(let ((target '(:a "a" :b "b" :c "c")))
(setf (plist-get target :a) "modify")
target))
;;=> (let ((target '(:a "a" :b "b" :c "c")))
;; (let* ((p (plist-member target :a)))
;; (if (plist-member target :a)
;; (setcar
;; (cdr (plist-member target :a))
;; "modify")
;; (setq target (cons :a (cons "modify" target)))))
;; target)
;; nil
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(let ((target '(:a "a" :b "b" :c "c")))
(setf (plist-get target :a) "modify")
target)
;;=> (:a "modify" :b "b" :c "c")
(let ((target '(:a "a" :b "b" :c "c")))
(cl-rotatef (plist-get target :a) (plist-get target :c))
target)
;;=> (:a "c" :b "b" :c "a")
(let ((target '(:a "a" :b "b" :c "c")))
(setf (plist-get target :d) "modify")
target)
;;=> (:d "modify" :a "a" :b "b" :c "c")
参考
TODO Emacsにおける.dir-locals.elについて
背景
個々のファイルを汚さないためにも .dir-locals.el を活用していきたいものです。
しかし、歴史的な経緯により、 .el でサフィックスされているのに文法はElispではなく .dir-locals.el 独自のものとなっています。
それが .dir-locals.el をよく分からないものにしている一因んだと思われます。
この記事は様々な .dir-locals.el の具体例を示し、 .dir-locals.el の効果的な利用方法を見つけて頂く一助となればと思うものです。
.dir-locals.elの仕組み
.dir-locals.el はEmacs本体の lisp/files.el で処理されています。
なんと .dir-locals.el のファイル名は変数で管理されており、変更することができます(おすすめできませんが)。(GitHub - emacs-mirror/emacs - lisp/files.el)
(defconst dir-locals-file ".dir-locals.el"
"File that contains directory-local variables.
It has to be constant to enforce uniform values across different
environments and users.
A second dir-locals file can be used by a user to specify their
personal dir-local variables even if the current directory
already has a `dir-locals-file' that is shared with other
users (such as in a git repository). The name of this second
file is derived by appending \"-2\" to the base name of
`dir-locals-file'. With the default value of `dir-locals-file',
a \".dir-locals-2.el\" file in the same directory will override
the \".dir-locals.el\".
See Info node `(elisp)Directory Local Variables' for details.")
変数のドキュメントに記載があるように .dir-locals.el と .dir-locals-2.el が同時にディレクトリに存在する場合、後者が優先されるようですね。(知らなかった)
使用法としては、これも変数のドキュメントに記載のとおり、gitで .dir-locals.el が共有されていて、気に入らない値が設定されているとき .dir-locals.el を置くことで前者を無視することができるということです。
そういうユースケースなので、 .dir-locals-2.el はglobalの.gitignoreでignoreしておくといいのかもしれません。
dir-locals-file を使っている dir-locals--all-files に適当にデバッガを仕込んでバックトレースを取ると次の情報が得られました。
dir-locals--all-files("~/dev/dir-locals-test/")
locate-dominating-file("/home/conao/dev/dir-locals-test/" dir-locals--all-files)
dir-locals-find-file("/home/conao/dev/dir-locals-test/.dir-locals.el")
hack-dir-local-variables()
hack-local-variables(no-mode)
run-mode-hooks(emacs-lisp-mode-hook)
emacs-lisp-mode()
set-auto-mode-0(emacs-lisp-mode nil)
set-auto-mode()
normal-mode(t)
after-find-file(t t)
find-file-noselect-1(#<buffer .dir-locals.el> "~/dev/dir-locals-test/.dir-locals.el" nil nil "~/dev/dir-locals-test/.dir-locals.el" nil)
find-file-noselect("/home/conao/dev/dir-locals-test/.dir-locals.el" nil nil nil)
find-file("/home/conao/dev/dir-locals-test/.dir-locals.el")
つまり、 find-file でファイルを開いたり、 revert-buffer で開き直せば normal-mode が呼ばれるので、 .dir-locals.el の設定内容を反映できるようですね。
revert-buffer で反映できるのは .dir-locals.el のデバッグをするときには便利なので覚えておいて頂ければと思います。
TODO Lispとは何なのか。LISt Processerの力
背景
この記事はMomiji-LT#1の発表の草稿です。 Lispを触ったことのない人に「Lispとは何か」を紹介し、魅力を伝えようとするものです。
まとめ
参考
TODO Lispとは何なのか
はじめに
自己紹介
- 山下 直哉 (@conao3)
- 学業
- 広島大学先進理工系科学研究科
パターン認識研究室 (栗田・宮尾研究室)- 機械学習を用いた単眼動画像の深度予測
- 広島大学先進理工系科学研究科
- 趣味
- Emacsの布教活動 (Emacs-jpメンバー)
- Emacsのパッケージ開発 (leaf.el, seml-mode.el,,,)
- GitHubの草を生やす (OSS活動)
- Link: GitHub, Emacs Wiki, Twitter, conao3.com
GitHubの草

GitHub解析結果 (forkwell)

MELPA (Emacsパッケージ)

今回の目的
- LISP(概念)とは何か
- なぜLispが「強い」のか
- どこでLispの「強さ」が生きるのか
- なぜEmacsを選ぶのか
LISP(概念)とは何か
Lispの歴史と影響の広がり1

John McCarthy (1927 - 2011)2

- 「人工知能; Artificial Intelligence」の生みの親
- John McCarthy, Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I, 1960
- LISP - LISt Interpreter
- 言語のインタプリタ(eval)
を記述できる(最小の)言語 - 万能チューリングマシンの言い換え
- 特に「純」LISPと呼ぶ
- 5つの関数と4つのスペシャルフォーム
- 関数 (atom, eq, car, cons)
- スペシャルフォーム
(if(cond), quote, lambda, define)
- 純LISPの関数
関数 説明 記号的説明 atom 値がatomなら T を返す atom [(A B)] → nil
atom [nil] → Teq 同一なら T を返す eq [A ; A] → T car pairの左値をとり出す car [(A . B)] → A cdr pairの右値をとり出す cdr [(A . B)] → B cons pairを作る cons [A;B] → (A . B)
- 純LISPのスペシャルフォーム
名前 説明 例 if 条件分岐 (if (< a b) 'yes 'no)quote そのまま返す (quote yes),'yeslambda 無名関数を定義する (lambda (e) (+ 2 e))define 無名関数に名前を与える (define '2+ (lambda (e) (+ 2 e)))
Lisp族
- 純LISPはとても小さいのですぐに実装できる
- LISPの仲間が大量発生
- Scheme (Guile, Gauche, Racket,,,)
- EmacsのLisp: Emacs-lisp
- ANSIのLisp: CommonLisp
- JVMのLisp: Clojure
- PythonのLisp: Hy
- …
- Lispの入門としてEmacs-lisp!
- 処理系はEmacsをインストールするだけ
- カスタマイズはElispなので、
いつのまにか覚えている - 以後、例示のコードはElisp
なぜLispが「強い」のか
同図像性が「強い」
- データとプログラムが同じ形式
- プログラムがデータを作り、データがプログラムを作る
'(+ 1 2 3 4 5 (+ 1 2 3 4 5)) ;; => (+ 1 2 3 4 5 (+ 1 2 3 4 5)) `(+ 1 2 3 4 5 ,(+ 1 2 3 4 5)) ;; => (+ 1 2 3 4 5 15) (eval `(+ 1 2 3 4 5 ,(+ 1 2 3 4 5))) ;; => 30 `(+ (+ ,@(number-sequence 1 5)) (+ ,@(number-sequence 11 15)) (+ ,@(number-sequence 21 25))) ;; => (+ (+ 1 2 3 4 5) (+ 11 12 13 14 15) (+ 21 22 23 24 25)) (eval `(+ ,@(mapcar (lambda (elm) `(+ ,@(number-sequence (car elm) (cdr elm)))) '((1 . 5) (11 . 15) (21 . 25))))) ;; => 195
マクロが「強い」 1/2
- 構文木に対するマクロ
- C言語のマクロは「プログラム文字列」のマクロ
- 不整合エラーが発生しやすく、大域で扱い辛い
- マクロの場合、引数の評価前に変換、評価される
(defalias '2+ (lambda (arg) (+ 2 arg))) ;;=> 2+ (2+ 5) ;;=> 7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 関数の定義には defalias/lambda を使う ;; これはイディオムなので、何回も書くのは面倒。。 ;; defalias/lambda を展開するマクロを書いてしまえ (defmacro defun (name arglist &rest body) `(defalias ',name (lambda ,arglist ,@body))) ;;=> defun- 構文木に対するマクロ
マクロが「強い」 2/2
;; macro-expandで展開形を得ることができる (ppp-macroexpand (defun 2+ (arg) (+ 2 arg))) ;;=> (defalias '2+ ;; (lambda (arg) (+ 2 arg))) (defun 2+ (arg) (+ 2 arg)) ;;=> 2+ ;; defunマクロで定義した 2+ もちゃんと動く (2+ 5) ;;=> 7
カッコが「強い」 1/2
- Lispはカッコで構文木を表現する
- 機械フレンドリーである
- よって、エディタの支援が得られやすい
- インデントを間違えることがない
(C++などの言語では闇の力によって実現されている。。)
# with torch.no_grad(): を入れたいなぁ。。 total, correct, loss = 0, 0, 0.0 for testdata in testloader: images, labels = testdata outputs = net(images) ... loss += criterion.item() loss /= total # この条件分岐いらないな。。(then節だけでいいな。。) if os.path.exists("./cifer_net.pth"): torch.save(net.state_dict(), "./cifer_net.pth") else: print("file already exist. abort!")
カッコが「強い」 2/2
- paredit や smartparens による支援
;; | はポインタを示す ;; ワンキーでS式を囲む |(dolist (testdata testloader) ...) (|(dolist (testdata testloader) ...)) ;; ワンキーでS式を取り出す (if (file-exists-p "./cifer_net.pth") |(save (state_dict net) "./cifer_net.pth") (print "file already exist. abort!")) |(save (state_dict net) "./cifer_net.pth") ;; 「末尾まで削除」してもカッコの対応が崩れない (if (file-exists-p "./cifer_net.pth") (save (state_dict net) "./cifer_net.pth") |(print "file already exist. abort!")) (if (file-exists-p "./cifer_net.pth") (save (state_dict net) "./cifer_net.pth") |)
どこでLispの「強さ」が生きるのか
どこでLispの「強さ」が生きるのか
- どこでも。
- データを自由な形式で記述したい
- プログラムを自由な形式で記述したい
- イディオムを簡単に隠蔽したい
- プログラムを動的に生成したい
- つまり、どこでも。
- ただ、まともなプログラムを作ろうとすると依存管理、
ライブラリ検索などで闇を見る可能性。 - Emacsなら、閉じているのでElispを使い放題
- CommonLispでMLできるらしい。。
- HyならPythonになるのでPyTorchの全機能が使えるが、
補完、シンボルジャンプなどがないエクストリーム環境 - CommonLispからTorchのC++ APIが見えるらしい。。
まとめ
まとめ
- Lispの歴史と多言語への影響
- 純LISPの構成要素と内容
- Lispの特徴である、
- 「同図像性」・「マクロ」・「カッコ」
- Lispの活用場所
今回、時間の関係で挙げられなかったLispとその周辺、
インタプリタやコンパイラ、コンパイラ最適化、
継続(call/cc)、などなどLispからたくさん学べます。Lispは楽しい! みんなも触ってみよう!
DONE ピクシブ夏インターンシップ「PIXIV SUMMER BOOT CAMP 2020」体験記

はじめに
ピクシブさんのインターンシップ「PIXIV SUMMER BOOT CAMP 2020」へ参加してきました!
期間は8日間という短い期間でしたが、短い期間ながら、とても濃い経験が出来たと思います。
インターン最終日に発表した資料はこちら。
感想
アウトプットはピクシブ百科事典のオレオレフレームワークからPSR-7/15/17ベースのPHPへ(一部のクラスだが)移行したこと。 触らせて頂いたgitレポジトリのヒストリーだと、cvsからgitへ移行した最初のコミットが9年前、dispatcherやrouter、index.phpには多少手が入ることはあっても抜本的に手が入るのはこれが初めてでした。
最終発表ではこの「9年前」ということに対して、「いやもっと前。。。」というコメントもあったので、長らく「いつか改善したいなぁ」とピクシブのエンジニアさんが思っていたところを作業でき、そのコードに貢献できたのはとても嬉しいことでした。 技術的なことと、その変更をしているときに詰まったところなどは発表資料を参照して頂ければと。
メンターをして頂いたtadsanさんからのコメントです。 インターンで取り組んだ課題の背景などもスレッドで投稿されています。
ピクシブ百科事典のPSR-7/15/17化が圧倒的進捗しました💪 https://t.co/bhrbCcIgLe
— ぞぬ頃し半分子 (@tadsan) September 18, 2020

ノベルティもたくさん頂きました。 現地で参加できれば、このパーカーを着て作業していたらしいですが。。とても残念!
世界的な疫病の拡大により、インターンに係る全ての作業はリモートで行なわれ、始めてのインターンではあるものの不思議な感覚でした。。
そもそも広島からのリモートでのインターン参加を認めて下さったこと、さらにはインターネットの不調により急遽モバイルルータを手配して頂くなど、手厚いサポートを頂きました。
このピクシブのインターンで「圧倒的猛者」になれました!! 本当にありがとうございました!
TODO org-modeドキュメントからZenn Flavored Markdownを生成するox-zennの使い方
tl;dr
- ox-zennというorgからZenn flavored markdownを生成するパッケージを書きました。
- Zennマークダウンは独自記法があるので、orgファイルとの対応について少し注意することがある。
org-modeとは
概要
「org-modeとはEmacsのメジャーモードのひとつです」という説明は正しいのですが、org-modeの力を説明するには全く言葉が足りません。
Org-mode はノートの保存、TODOリストの管理、プロジェクト計画、文書編集のためのモードです。高速で効率的なプレーンテキストのシステムを使ってファイルを編集します。
と説明されています。
この説明でとても重要なのはorgドキュメントが「プレーンテキスト」であるという点です。
「プレーンテキスト」はロックインを避けるための究極の選択肢です。
「org-mode」にロックインしてるじゃないか。という手痛い指摘が飛んできそうですが、それには当たりません。なぜならorg-modeが編集の対象にしているのは「プレーンテキスト」であり、もしorg-modeが明日なくなったとしても普通のエディタで編集や読み書きができ、様々なUnixコマンドによる処理が容易できるからです。
万能のドキュメントソース
org-modeはとても大きく、私も全容を把握できていません。 しかしつまみ食いをするだけでもとても大きなメリットがある機能があります。 それがorgのエクスポート機能とコード実行機能1です。

このようにorgドキュメントを中心として、HTMLやLaTeX、Wordドキュメント生成や、実行可能なソースコードを生成する。ドキュメントの中で任意の言語のコード実行を伴なうこともできます。
LSP(Language Server Protocol Support)にも似た思想を感じます。 LSPも各言語に対するプログラミング支援環境を各エディタにそれぞれ実装するのは無駄が多いからという理由で現代では主流といっても良い、勢いとシェアがあります。
私がorgドキュメントとorg-modeを使う理由も同じです。 もうHTMLを、LaTeXを、Wordを、PowerPointをそれぞれのファイルフォーマットで編集する時代は終わっているのです。 私が編集するドキュメント形式はorgドキュメントだけであり、orgドキュメントに情報を集約することで情報の再利用性と利便性が向上するのです。
ox-zenn
orgはorgドキュメントをパースし、それぞれの「意味」でどのような「出力」にするのか制御することでエクスポート先のファイルを生成します。
つまり我々がorgドキュメントから新しいフォーマットで出力したい場合はorgが解釈した「意味」からどのような出力にするのかというエクスポートバックエンドのみを作成すれば良いことになります。 それがこちら「ox-zenn」です。
ox-zennの作り方については別の記事にするとして、この記事では使い方を説明します。
org-modeとox-zennのインストール
leafを使ってインストールします。 また、orgはEmacsに標準添付されているのですが、upstreamがとても活発に改善されているので、この手順に従い、最新版を入れることをおすすめします。
現状、ox-zennがまだcelpaにしか入っていないので、celpaのURLを
package-archivesに追加する必要があることに注意する必要があります。(eval-and-compile (customize-set-variable 'package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/") ("celpa" . "https://celpa.conao3.com/packages/") ("org" . "https://orgmode.org/elpa/"))) (package-initialize) (unless (package-installed-p 'leaf) (package-refresh-contents) (package-install 'leaf)) (leaf leaf-keywords :ensure t :config (leaf-keywords-init))) (leaf org :ensure org-plus-contrib :custom ((org-startup-indented . t) (org-return-follows-link . t) (org-src-window-setup . 'other-window) (org-highlight-latex-and-related . '(latex script entities)))) (leaf ox-zenn :ensure t :after org :require t)
基本のorg記法
orgのプロジェクトページにorg-syntaxのページがあるので参照して頂ければと。
ox-zenn拡張記法
Zenn公式によるMarkdownの解説記事があります。 Zennは基本的に今時のマークダウン、つまりGFM(GitHub Flavored Markdown)を受け取るように見えます。 さらにZenn独自の記法として独自拡張のブロック要素とコンテンツ埋め込み用のショートコードをサポートしています。
残念ながらorgには対応している記法が存在しないのでox-zennが特別に解釈する文法を追加しました。 もちろん、他のバックエンドは解釈しないのでドキュメントの可搬性が落ちることに留意する必要があります。
frontmatter
マークダウンの上にYAMLのfrontmatterが書けるのは、どのマークダウンが始めたのか分かりませんが、Zennのマークダウンもfrontmatterでタイトルなどのメタ要素を指定することができます。
Zennはfrontmatterで次の要素を指定することができます。
titletopicsemojitypepublished
orgドキュメントでは次のように指定すると、
#+title: ZennのMarkdown記法 #+gfm_tags: markdown zenn #+gfm_custom_front_matter: :emoji 👩💻 :type tech #+gfm_custom_front_matter: :published true次のようにマークダウンに出力されます。
--- author: Naoya Yamashita title: "ZennのMarkdown記法" last_modified: 2020-09-24 emoji: 👩💻 type: tech topics: [markdown, zenn] published: true ---gfm_tagsは空白区切りです。gfm_custom_front_matterはplist風に空白区切りで指定します。 それぞれ、一行が長くなる場合、複数行に分けて指定しても正しく解釈されます。authorとlast_modifiedは自動で追加されるので、それらはoptionsで無効化できます。#+title: ZennのMarkdown記法 #+options: broken-links:mark toc:nil author:nil last-modified:nil #+gfm_tags: markdown zenn #+gfm_custom_front_matter: :emoji 👩💻 :type tech #+gfm_custom_front_matter: :published trueZennではサイドバーにToCが表示されているので、
toc:nilを指定しておくのも便利です。:topicsの指定において、空白が含まれる値を設定する都合上、若干トリッキーになっているので注意する必要があります。
ブロック要素
ブロック要素はブロッククオートにhtml属性を付けることで対応しました。 このように定義することで他のバックエンドで出力した際にも多少は意味を保てると思います。
message : ブロッククオートに
#+attr_html:でx-typeにmessageを指定します。org #+attr_html: :x-type message #+begin_quote メッセージをここに #+end_quoteこれは次のようにマークダウンに変換され、markdown :::message メッセージをここに :::次のように表示されます。<div x-type="message"> > メッセージをここに </div>alert : ブロッククオートに
#+attr_html:でx-typeにalertを指定します。org #+attr_html: :x-type alert #+begin_quote メッセージをここに #+end_quoteこれは次のようにマークダウンに変換され、markdown :::alert メッセージをここに :::次のように表示されます。<div x-type="alert"> > メッセージをここに </div>details : ブロッククオートに
#+attr_html:でx-typeにdetailsを指定します。summaryを指定したい場合はx-summaryにサマリを指定します。org #+attr_html: :x-type details :x-summary タイトル #+begin_quote 表示したい内容 #+end_quoteこれは次のようにマークダウンに変換され、markdown :::details タイトル 表示したい内容 :::次のように表示されます。<div x-type="details" x-summary="タイトル"> > 表示したい内容 </div> なお、 `x-summary` 要素を省略した場合は `details` をサマリに指定したと解釈されます。
埋め込みショートコード
ショートコードは悩んだ結果、それぞれのサービス名をschemeとして指定するリンクを特別に解釈することにしました。
つまりorgで次のように記述した場合、
[[tweet://https://twitter.com/conao_3/status/1308747297790898176]] [[youtube://jNa3axo40qM]] [[codepen://https://codepen.io/alphardex/pen/poyOMgr]] [[slideshare://866aBtwVYswXvu]] [[speakerdeck://3491c9ab20ef40938119aecadb06f1c6]] [[jsfiddle://https://jsfiddle.net/chrisvfritz/50wL7mdz]]次のようにマークダウンで出力され、
@[tweet](https://twitter.com/conao_3/status/1308747297790898176) @[youtube](jNa3axo40qM) @[codepen](https://codepen.io/alphardex/pen/poyOMgr) @[slideshare](866aBtwVYswXvu) @[speakerdeck](3491c9ab20ef40938119aecadb06f1c6) @[jsfiddle](https://jsfiddle.net/chrisvfritz/50wL7mdz)次のように表示されます。
[BROKEN LINK: tweet://https://twitter.com/conao_3/status/1308747297790898176]
[BROKEN LINK: youtube://jNa3axo40qM]
[BROKEN LINK: codepen://https://codepen.io/alphardex/pen/poyOMgr]
[BROKEN LINK: slideshare://866aBtwVYswXvu]
[BROKEN LINK: speakerdeck://3491c9ab20ef40938119aecadb06f1c6]
[BROKEN LINK: jsfiddle://https://jsfiddle.net/chrisvfritz/50wL7mdz]
なお、この拡張は他のバックエンドで動きません。 またデフォルトの状態ではこのリンクは未定義要素へのリンクとして解釈されるため、他のドキュメントへのエクスポートが途中で止まってしまいます。。
これは未定義要素へのリンクを無視するように指定することで回避できます。 orgドキュメントの上の方に
#+options: broken-links:markと記述します。
画像の拡張記法
orgにおいて、画像を表示するにはdescriptionなしのリンクで表現します。
さらにZennは独自の記法により画像の横幅を指定できるようになっています。 この機能については
#+attr_htmlでalt属性とwidth属性を指定すると適切に出力するようにしました。つまりこのorgフォーマットは
[[https://raw.githubusercontent.com/conao3/files/master/blob/headers/png/ox-zenn.el.png]] #+attr_html: :alt image [[https://raw.githubusercontent.com/conao3/files/master/blob/headers/png/ox-zenn.el.png]] #+attr_html: :alt image :width 250px [[https://raw.githubusercontent.com/conao3/files/master/blob/headers/png/ox-zenn.el.png]]このように変換され、
  このように表示されます。



注意点は画像っぽいリンク、正確に言えば
org-html-inline-image-rulesにマッチするリンクでないとインライン表示されず、単なるリンクになってしまいます。org-html-inline-image-rules ;;=> (("file" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") ;; ("attachment" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") ;; ("http" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'") ;; ("https" . "\\.\\(jpeg\\|jpg\\|png\\|gif\\|svg\\)\\'"))
org-publishサポート
この章はオプショナルです。
ox-zennはorg-publishをサポートしているので、若干の設定が必要になりますが、こちらの方が便利だという向きもあるかもしれません。
(leaf ox-zenn :ensure t :after org :require t ox-publish :defun zenn/f-parent org-publish :defvar org-publish-project-alist :preface (defvar zenn/org-dir "~/dev/repos/zenn-src/org") (defun zenn/org-publish (arg) "Publish zenn blog files." (interactive "P") (let ((force (or (equal '(4) arg) (equal '(64) arg))) (async (or (equal '(16) arg) (equal '(64) arg)))) (org-publish "zenn" arg force async))) :config (setf (alist-get "zenn" org-publish-project-alist nil nil #'string=) (list :base-directory (expand-file-name "" zenn/org-dir) :base-extension "org" :publishing-directory (expand-file-name "../" zenn/org-dir) :recursive t :publishing-function 'org-zenn-publish-to-markdown)))こうすれば次のようなディレクトリ構成にした上で、orgディレクトリ以下のorgドキュメントを一括でarticlesディレクトリとbooksディレクトリにマークダウンに変換できます。(1記事/1orgドキュメント)
~/dev/repos/zenn-src/ ├── articles/ │ ├── (ox-zenn-usage.md) │ ├── (ox-publish-usage.md) │ ├── (ox-odt-usage.md) │ └── (ox-html-usage.md) ├── books/ │ └── ox-zenn-book/ │ ├── (1.md) │ ├── (2.md) │ ├── (3.md) │ ├── (config.yml) │ └── (cover.png) ├── org/ │ ├── articles/ │ │ ├── ox-zenn-usage.org │ │ ├── ox-publish-usage.org │ │ ├── ox-odt-usage.org │ │ └── ox-html-usage.org │ └── articles │ └── ox-zenn-book/ │ ├── 1.org │ ├── 2.org │ ├── 3.org │ ├── config.yml │ └── cover.png └── README.org()で囲われたファイルが生成対象のファイル群です。 orgディレクトリでのディレクトリ構造のままファイルがpublishされていることが分かります。
subtreeスタイル
この章もオプショナルです。
私はこの方法でZennコンテンツを管理することにしました。
ディレクトリ構造はこのようになります。
~/dev/repos/zenn-src/ ├── articles/ │ ├── (ox-zenn-usage.md) │ ├── (ox-publish-usage.md) │ ├── (ox-odt-usage.md) │ └── (ox-html-usage.md) ├── books/ │ └── ox-zenn-book/ │ ├── (1.md) │ ├── (2.md) │ ├── (3.md) │ ├── config.yml │ └── cover.png ├── main.org └── README.org()で囲われたファイルが生成対象のファイル群です。
main.orgに全てのコンテンツを集中させる方法(多記事/1orgドキュメント)です。 ox-hugoのような積極的なサポートではないですが、org標準の機能を使ってもこのように記事を管理できます。
main.orgは次のような形です。
#+title: Zenn blog source #+author: conao3 #+date: <2020-09-24 Thu> #+options: ^:{} toc:nil * articles ** org-modeドキュメントからZenn Flavored Markdownを生成するox-zennの使い方 :PROPERTIES: :EXPORT_FILE_NAME: articles/ox-zenn-usage :EXPORT_GFM_TAGS: markdown zenn :EXPORT_GFM_CUSTOM_FRONT_MATTER: :emoji 👩💻 :type tech :EXPORT_GFM_CUSTOM_FRONT_MATTER+: :published true :END: *** org-modeとは **** 概要 「[[https://orgmode.org/][org-mode]]とはEmacsのメジャーモードのひとつです」という説明は正しいのですが、org-modeの力を説明するには全く言葉が足りません。 ...単一orgドキュメントで管理するようになるので、各記事は第2レベルをトップレベルとするようになり、記事ごとのオプションはプロパティドロワで設定するようになります。
プロパティには
EXPORT_が前置されることに注意してください。エクスポートは記事タイトル(つまり第2レベルの見出し)にポイントを乗せた上で、
C-c C-e C-s z zという呪文()を入力すれば出力できます。
まとめ
ox-zennを使うことでorgドキュメントからZenn拡張の含まれたマークダウンを出力できるようになりました。 ぜひみなさんもこのバックエンドを使ってorgからZennの記事を書いてもらえればと思います。
気に入ってもらえたら、ぜひzennかpatreonでサポートを頂ければと思います!
TODO org-modeから始めるEmacs入門
はじめに
自己紹介
- 山下 直哉 (@conao3)
- 学業
- 広島大学先進理工系科学研究科
パターン認識研究室 (栗田・宮尾研究室)- 機械学習を用いた単眼動画像の深度予測
- 広島大学先進理工系科学研究科
- 趣味
- Emacsの布教活動 (Emacs-jpメンバー)
- Emacsのパッケージ開発 (leaf.el, seml-mode.el,,,)
- GitHubの草を生やす (OSS活動)
- Link: GitHub, Emacs Wiki, Twitter, conao3.com
界隈の最近
2020年代のEmacs入門3

Emacsに一生入門できねえ20204

風になりたい奴だけがEmacsを使えばいい 20205

最近のEmacsJP
- 事実として、他コミュニティのような流量はない
- vim-jp, Ruby-jp, Rust-jp,
- プログラミング言語処理系が好きな人の集まり,,,
- しかし、数多くの心強いつよつよハッカー達が集まっている
- ぜひ気軽に質問をしてください!
- 「Emacs-jp」で検索!
- Windows周辺の情報のまとめ、現在進行中!
- 事実として、他コミュニティのような流量はない
それはそれとして
Zenn
- 概要
- Qiita的な技術情報ブログサイト
- Note的な投げ銭システム
- とても簡単な「本」の出版 (公開)
- ローカルプレビューのためのcliの提供
- GitHub連携
- システム
- 個人開発 (CatNoseさん)
- Zenn needs help6
- Next.js, Rails, GCP, Vercel
- 個人開発 (CatNoseさん)
- 概要
org-mode
org-mode
- markdown的なstructuredプレーンテキスト
- Emacsでプレーンテキストを扱うならこれ
- org-tangle, org-export, org-publish, org-agenda, org-clock, org-capture, org-table 等々の便利機能
- Emacs界のキラーパッケージ
- (しかしでかすぎて全容を把握できない)
- org-export周辺だけでも、とても便利!
- 卒論をorgで書いた
- org -> LaTeX -> pdf
- ブログ(conao3.com)もorg (ox-hugo)
[markdown] / org-mode
- 思想は同じ
- structuredかつ簡単なスタイルを付けたプレーンテキスト
- markdownの進化
- 本来のmarkdownの仕様は小さく、(需要に対して)不十分だった
- 言語名付きのソースブロックはGFM拡張
- 最初の実装はperlスクリプト
- 「俺のmarkdown」(方言)の大量発生
- 基本的にCommonMarkに収束
- 空行やインデントの扱いなど、多くの実装依存な部分
- 本来のmarkdownの仕様は小さく、(需要に対して)不十分だった
- 思想は同じ
markdown / [org-mode]
- org-modeの進化
- Elispでの実装がずっとメンテナンスされている
- 方言はない
- Emacsで使うことを前提に、ノートテイキング、タスク管理などの機能
- 文芸的プログラミングのさきがけ
- jupyter notebookで有名に
- 任意のフォーマットへの変換機能!
- text, md, rst, LaTeX, beamer, html, odt(word)
- このスライドもorg形式 -> reveal.js向けのhtml
- Elispでの実装がずっとメンテナンスされている
- org-modeの進化
org文書のイメージ7

文芸的プログラミング

- Donald Ervin Knuth (1938-) 提唱
- (狭義)ドキュメントとコードを同じファイルに書く
- 狭義では単に埋め込みドキュメントも該当してしまう
- コード断片の評価及び返り値参照
- コード断片のマクロ展開
- 記述順に依存しない
orgの文芸的プログラミング
org-tangle: org文書からプログラムを出力

ox-zenn
- orgからzenn形式のmarkdownへ変換
- 今日! MELPAにマージ (23パッケージ目)
- ox-zennの開発期間は2日 (開発1日、修正1日)
- なぜそんなに早く開発できたのか
- 中間表現はすでに提供されている
- 多くの関連パッケージの存在
- 使い方はzennで公開8
- 作り方も公開予定
ox-zenn

まとめ
まとめ
- org-modeの紹介
- org-tangle, org-export, org-publish
- zennの紹介とox-zennを作った話
今回、時間の (そして私の知識の) 関係で、
orgの他の大部分の機能 (特にタスク管理)
については紹介できませんでした。しかし、orgのドキュメント変換機能は単体でもとても便利です。
私はorg-modeを使いたくてEmacsに入門しました。
ぜひ皆さんもorg-modeを使ってみて、そのパワフルさを感じてください!Emacsに入門する際にはEmacsJPで支えますので、
ぜひSlackに入ってみてください!
https://emacs-jp.slack.com- org-modeの紹介
脚注
- 2020年代のEmacs入門 - https://emacs-jp.github.io/tips/emacs-in-2020.html
- Emacsに一生入門できねえ2020 - https://anond.hatelabo.jp/20200921040234
- 風になりたい奴だけがEmacsを使えばいい 2020 - https://blog.tomoya.dev/posts/only-those-who-want-to-be-the-wind-should-use-emacs-2020/
- Zenn needs help - https://catnose99.com/zenn-needs-help/
- Eric Schulte, Dan Davison, Thomas Dye, et.al., “A Multi-Language Computing Environment for Literate Programming and Reproducible Research” https://www.jstatsoft.org/article/view/v046i03
- ox-zennの使い方 - https://zenn.dev/conao3/articles/ox-zenn-usage
DONE dioxus-tui入門
はじめに
この記事はRustアドベントカレンダー16日目の記事です。 昨日はyukinaritさんの「PGOでRustで書かれた広告サーバを早くする」でした。
RustでTUIアプリケーションを作りたいなーっと思っていろいろ探していると dioxus-tui というライブラリを見つけたので、使ってみた記事を書きます。
dioxusとは
TUIアプリケーション専用ではなく、仮想DOMを使ってWebフロントエンド、デスクトップアプリ、モバイルアプリを書き出すクレート。 日本語記事ではここらへんを参照
この記事ではdioxusを使ってTUIアプリを作ってみます。
Getting Started
適当なRustプロジェクトを作って遊んでみます。
mkdir rust-dioxus-tui-sample
cd rust-dioxus-tui-sample
cargo init --name dioxus-tui-sample
cargoで取ってきます。
cargo add dioxus dioxus-tui
src/main.rs を以下で置き換えます。
use dioxus::prelude::*;
fn main() {
dioxus_tui::launch(app);
}
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
}
})
}
cargoで起動します。
cargo run
起動できたら成功です! 🎉

rsx!マクロ
rsx
JSXのようなインターフェースを提供するマクロ。次のようなサンプルが動きます。
use dioxus::prelude::*; fn main() { dioxus_tui::launch(app); } fn app(cx: Scope) -> Element { let val = 42; let name = if val == 42 { "Jack" } else { "Bob" }; cx.render(rsx! ( div { "hello world" "hello {val}" "hello {name}" } )) }なおJSXのように
{}の中に任意の式を書くことはできないようです。一旦rsxマクロの外で計算しておくのがおすすめとのこと。
rsx - bool.then()
一方でこういうのはできるようです。
let show_title = true; rsx!( div { // Renders nothing by returning None when show_title is false show_title.then(|| rsx!{ "This is the title" }) } )
rsx - Option.map()
Optionに対するmap風のAPIも使えます。let user_name = Some("bob"); rsx!( div { // Renders nothing if user_name is None user_name.map(|name| rsx!("Hello {name}")) } )
rsx - iter
動的に
rsx!を作ることもできます。let names = ["jim", "bob", "jane", "doe", "a"]; cx.render(rsx! ( ul { names.iter().map(|name| rsx!{ li { "{name}" } }) } ))が、tuiのレンダラは上手く表示できないようです。(改行せずに上書き表示されてる?)

rsx - Components
Componentの実装
#![allow(non_snake_case)] use dioxus::prelude::*; #[derive(Props)] struct TitleCardProps<'a> { title: &'a str, } fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element { cx.render(rsx!{ h1 { "{cx.props.title}" } }) } fn main() { dioxus_tui::launch(app); } fn app(cx: Scope) -> Element { cx.render(rsx! ( TitleCard { title: "hello world" } )) }
hooks
React風のhookが利用できます。
useState
use_stateはReactと全く同じです。let count = use_state(&cx, || 0); cx.render(rsx! ( div { "count: {count}" } ))本来ならボタンを追加して値の更新をするのですが。。dioxus-tuiにはまだボタンが実装されていなく確かめられませんでした。
useFuture
dioxusの拡張。async関数を取って、非同期で実行してくれます。
use dioxus::prelude::*; fn main() { dioxus_tui::launch(app); } fn app(cx: Scope) -> Element { let count = use_state(&cx, || 0); use_future(&cx, (), move |_| { let count = count.to_owned(); let update = cx.schedule_update(); async move { loop { count.with_mut(|f| *f += 1); tokio::time::sleep(std::time::Duration::from_millis(1000)).await; update(); } } }); cx.render(rsx! { div { width: "100%", div { width: "50%", height: "5px", background_color: "blue", justify_content: "center", align_items: "center", "Hello {count}!" } div { width: "50%", height: "10px", background_color: "red", justify_content: "center", align_items: "center", "Hello {count}!" } } }) }
良く見るとこのサンプル、背景赤の場所に表示されるべき文字が左に出てますね。。
Events
on* を使ってイベントを処理できる。。らしい。が、dioxus-tui(0.2.2)ではpanicしてしまった。
let log_event = move |event: Event| {
events.write().push(event);
};
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "80%",
height: "50%",
border_width: "1px",
justify_content: "center",
align_items: "center",
background_color: "hsl(248, 53%, 58%)",
onmousemove: move |event| log_event(Event::MouseMove(event.data)),
onclick: move |event| log_event(Event::MouseClick(event.data)),
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.data)),
onmousedown: move |event| log_event(Event::MouseDown(event.data)),
onmouseup: move |event| log_event(Event::MouseUp(event.data)),
onwheel: move |event| log_event(Event::Wheel(event.data)),
onkeydown: move |event| log_event(Event::KeyDown(event.data)),
onkeyup: move |event| log_event(Event::KeyUp(event.data)),
onkeypress: move |event| log_event(Event::KeyPress(event.data)),
onfocusin: move |event| log_event(Event::FocusIn(event.data)),
onfocusout: move |event| log_event(Event::FocusOut(event.data)),
"Hover, click, type or scroll to see the info down below"
},
div {
width: "80%",
height: "50%",
flex_direction: "column",
events_rendered,
},
},
})

https://github.com/DioxusLabs/dioxus/blob/fc2aaa7df5/packages/tui/examples/tui_all_events.rs レポジトリに置いてあるHEADのサンプルも同じくpanicしてしまうのでどこかのコミットで壊れてそのままになってる。。かなにか動かし方を間違えてるようです。 動かせた人は教えてくれると嬉しいです。
まとめ
dioxusという意欲的なプロジェクトを見つけたのでちょっと動かしてみました。 TUIのレンダラはまだまだ発展途上のようですが、React文化をTUIに持ってくるのは普通に便利だと思うので期待してます。 (ユーザーとしては素直にnodeのinkを使った方がいいかもしれない。ただ俺はRustでTUIを作りたいんだ。。!)
doc
TODO leaf.el - Flexible, declarative and modern init.el package configuration
leaf
TODO oj.el - Competitive programming tools client for AtCoder, Codeforces
Description

Competitive programming tools(oj, oj-template) client for AtCoder, Codeforces, and more!
Now support below online judges and feature.
(defvar oj-online-judges
'((aoj . ((name . "Aizu Online Judge") (url . "https://onlinejudge.u-aizu.ac.jp/")))
(aoj2 . ((name . "Aizu Online Judge (Beta)") (url . "https://onlinejudge.u-aizu.ac.jp/courses/")))
(anrchy-golf . ((name . "Anarchy Golf") (url . "http://golf.shinh.org/p.rb?")))
(atcoder . ((name . "AtCoder") (url . "https://atcoder.jp/contests/")))
(codeforces . ((name . "Codeforces") (url . "https://codeforces.com/contests/")))
(cs-academy . ((name . "CS Academy") (url . "https://csacademy.com/contests/")))
(facebook . ((name . "Facebook Hacker Cup") (url . "https://www.facebook.com/hackercup/")))
(hackerrank . ((name . "HackerRank") (url . "https://www.hackerrank.com/challenges/")))
(hackerrank-contest . ((name . "HackerRank Contest") (url . "https://www.hackerrank.com/contests/")))
(kattis . ((name . "Kattis") (url . "https://open.kattis.com/problems/")))
(poj . ((name . "PKU JudgeOnline") (url . "http://poj.org/problem?id=")))
(topcoder . ((name . "Topcoder") (url . "https://www.topcoder.com/challenges/")))
(toph . ((name . "Toph (Problem Archive)") (url . "https://toph.co/p/")))
(codechef . ((name . "CodeChef") (url . "https://www.codechef.com/problems/")))
(soj . ((name . "Sphere online judge") (url . "https://www.spoj.com/problems/")))
(yukicoder . ((name . "yukicoder") (url . "https://yukicoder.me/problems/no/")))
(yukicoder-contest . ((name . "yukicoder Contest") (url . "https://yukicoder.me/contests/")))
(library-checker . ((name . "Library Checker") (url . "https://judge.yosupo.jp/problem/"))))
"Supported online judges.
- Download sample cases
(aoj-arena aoj anrchy-golf atcoder codeforces
cs-academy facebook hackerrank kattis poj toph
codechef soj yukicoder library-checker)
- Download system cases
(aoj yukicoder)
- Submit solution source code
(atcoder codeforces topcoder hackerrank toph)
NOTE: online-judge symbol MUST NOT include slash (\"/\").")
Install
(leaf oj
:ensure t
:custom ((oj-default-online-judge . 'codeforces)))
Usage
oj-install-package
This package requires oj, oj-template, selenium.
You can install these tools via
M-x oj-install-packages.(defun oj-install-packages () "Install `oj', `oj-template', `selenium' pip package via `pip3'." (interactive) (unless (yes-or-no-p "Install `oj', `oj-template', `selenium' via pip3?") (error "Abort install")) (dolist (elm '(("oj" . "online-judge-tools") ("oj-template" . "online-judge-template-generator") ("selenium" . "selenium"))) (unless (executable-find (car elm)) (unless (executable-find "python3") (error "Missing `python3'. Please ensure Emacs's PATH and the installing")) (unless (executable-find "pip3") (error "Missing `pip3'. Please ensure Emacs's PATH and the installing")) (oj--exec-script (format "pip3 install %s" (cdr elm))))))But if you want to install via this funciton, you can install them by your command-line shell.
pip3 install online-judge-tools pip3 install online-judge-template-generator pip3 install seleniumAnd then, make sure that the Emacs’s
PATHis set up properly.M-! which ojreturns some path toojexecutable, your Emacs can use it. If you get no output, please see Q&A or consider to use exec-path-from-shell.
oj-prepare
M-x oj-prepareis first step to use this package.This command accept below format.
- Problem URL: https://codeforces.com/contest/1349/problem/A
- Contest URL: https://codeforces.com/contest/1349/
- Problem shortcode (for
oj-defualt-online-judge): 1349/A - Contest shortcode (for
oj-default-online-judge): 1349
Edit your code
Template files are generated in
oj-home-dir, open file and edit it.$ tree codeforces/ codeforces/ └── 1349 ├── A │ ├── a.out │ ├── geckodriver.log │ ├── generate.py │ ├── main.cpp │ ├── main.py │ └── test │ ├── sample-1.in │ ├── sample-1.out │ ├── sample-2.in │ ├── sample-2.out │ ├── sample-3.in │ └── sample-3.out ... └── F2 ├── generate.py ├── main.cpp ├── main.py └── test ├── sample-1.in ├── sample-1.out ├── sample-2.in ├── sample-2.out ├── sample-3.in └── sample-3.out $ cat codeforces/1349/A/main.cpp #include <bits/stdc++.h> using namespace std; #define ll long long #define ld long double #define v vector #define rep(i, n) for (int i = 0; i < (int)(n); ++i) #define rep3(i, m, n) for (int i = (m); i < (int)(n); ++i) #define rrep(i, n) for (int i = (int)(n)-1; i >= 0; --i) #define rrep3(i, m, n) for (int i = (int)(n)-1; i >= (m); --i) #define all(x) x.begin(), x.end() #define rall(x) x.end(x), x.begin() #define endl '\n' ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ll solve(int ebd, const vector<ll> & zdf) { // TODO: edit here } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int ebd; cin >> ebd; vector<ll> zdf(ebd); rep (i, ebd) { cin >> zdf[i]; } auto ans = solve(ebd, zdf); cout << ans << endl; return 0; }
oj-test
M-x oj-testdocompileandtestyour code.If your code pass testcases, get below output in
*oj*buffer.[*] 3 cases found [!] GNU time is not available: time [*] sample-1 [x] time: 0.001666 sec [+] AC [*] sample-2 [x] time: 0.002213 sec [+] AC [*] sample-3 [x] time: 0.001923 sec [+] AC [x] slowest: 0.002213 sec (for sample-2) [+] test success: 3 casesCompiler command is automatically detected using the
quickrunpackage. You may usequickrun-add-commandto add or override commands.
oj-submit
M-x oj-submitsubmit your code to online judge. (The first time, you needM-x oj-loginper online judges.)
Customize
Variables
oj-shell-program : A path to shell executable used
*oj*buffer. (defaultshell-file-name(bashor some customized shell))oj-home-dir : A path for generate files. (default
"~/.emacs.d/oj/")oj-default-online-judge : A online-judge used for guessing. (default
'codeforces)oj-compiler-c : Compiler name to submit for C/C++. (default
gcc)If you want to also use Clang local, please add your init.el below. ```emacs-lisp (quickrun-set-default "c" "c/clang") (quickrun-set-default "c++" "c++/clang++") ```oj-compiler-python : Compiler name to submit for Python. (default
cpython)oj-login-args : Args for
oj login. (defaultnil) ```text usage: oj login [-h] [-u USERNAME] [-p PASSWORD] [–check] [–use-browser {always,auto,never}] urlpositional arguments: url optional arguments: -h, --help show this help message and exit -u USERNAME, --username USERNAME -p PASSWORD, --password PASSWORD --check check whether you are logged in or not --use-browser {always,auto,never} specify whether it uses a GUI web browser to login or not (default: auto) ```oj-prepare-args : Args for
oj-prepare. (defaultnil) ```text Args for `oj-prepare’.usage: oj-prepare [-h] [-v] [-c COOKIE] [--config-file CONFIG_FILE] url positional arguments: url optional arguments: -h, --help show this help message and exit -v, --verbose -c COOKIE, --cookie COOKIE --config-file CONFIG_FILE default: ~/.config/online-judge-tools/prepare.config.toml ```oj-test-args : Args for
oj test. (defaultnil) ```text Args foroj-test'. Note that the runtime command (-c’) is detected automatically.usage: oj test [-h] [-c COMMAND] [-f FORMAT] [-d DIRECTORY] [-m {simple,side-by-side}] [-S] [--no-rstrip] [--rstrip] [-s] [-e ERROR] [-t TLE] [--mle MLE] [-i] [-j N] [--print-memory] [--gnu-time GNU_TIME] [--no-ignore-backup] [--ignore-backup] [--json] [--judge-command JUDGE] [test [test ...]] positional arguments: test paths of test cases. (if empty: globbed from --format) optional arguments: -h, --help show this help message and exit -c COMMAND, --command COMMAND your solution to be tested. (default: "./a.out") -f FORMAT, --format FORMAT a format string to recognize the relationship of test cases. (default: "%s.%e") -d DIRECTORY, --directory DIRECTORY a directory name for test cases (default: test/) -m {simple,side-by-side}, --display-mode {simple,side-by-side} mode to display an output with the correct answer (default: simple) -S, --side-by-side display an output and the correct answer with side byside mode (equivalent to --display-mode side-by-side) --no-rstrip --rstrip rstrip output before compare (default) -s, --silent don't report output and correct answer even if not AC (for --mode all) -e ERROR, --error ERROR check as floating point number: correct if its absolute or relative error doesn't exceed it -t TLE, --tle TLE set the time limit (in second) (default: inf) --mle MLE set the memory limit (in megabyte) (default: inf) -i, --print-input print input cases if not AC -j N, --jobs N specifies the number of jobs to run simultaneously (default: no parallelization) --print-memory print the amount of memory which your program used, even if it is small enough --gnu-time GNU_TIME used to measure memory consumption (default: "time") --no-ignore-backup --ignore-backup ignore backup files and hidden files (i.e. files like "*~", "\#*\#" and ".*") (default) --json --judge-command JUDGE specify judge command instead of default diff judge. See https://online-judge-tools.readthedocs.io/en/ master/introduction.en.html #test-for-special-forms-of-problem for details format string for --format: %s name %e extension: "in" or "out" (both %s and %e are required.) tips: You can do similar things with shell: e.g. `for f in test/*.in ; do echo $f ; diff <(./a.out < $f) ${f/.in/.out} ; done` ```oj-submit-args : Args for
oj submit. (default'("-y")) ```text Args for `oj-submit’.usage: oj submit [-h] [-l LANGUAGE] [--no-guess] [-g] [--no-guess-latest] [--guess-cxx-latest] [--guess-cxx-compiler {gcc,clang,all}] [--guess-python-version {2,3,auto,all}] [--guess-python-interpreter {cpython,pypy,all}] [--format-dos2unix] [--format-rstrip] [-G] [--no-open] [--open] [-w SECOND] [-y] [url] file positional arguments: url the URL of the problem to submit. if not given, guessed from history of download command. file optional arguments: -h, --help show this help message and exit -l LANGUAGE, --language LANGUAGE narrow down language choices if ambiguous --no-guess -g, --guess guess the language for your file (default) --no-guess-latest --guess-cxx-latest use the lasest version for C++ (default) --guess-cxx-compiler {gcc,clang,all} use the specified C++ compiler if both of GCC and Clang are available (default: gcc) --guess-python-version {2,3,auto,all} default: auto --guess-python-interpreter {cpython,pypy,all} use the specified Python interpreter if both of CPython and PyPy are available (default: cpython) --format-dos2unix replace CRLF with LF for given file --format-rstrip remove trailing newlines from given file -G, --golf now equivalent to --format-dos2unix --format-rstrip --no-open --open open the result page after submission (default) -w SECOND, --wait SECOND sleep before submitting -y, --yes don't confirm ```
Template file
In oj, you can use template file for auto generate source code.
If you want use your customize template, you save like below file as
~/.config/online-judge-tools/template/template-ext.cpp.<%! import onlinejudge_template.generator.cplusplus as cplusplus import onlinejudge_template.generator.about as about %>\ <% data['config']['rep_macro'] = 'rep' data['config']['using_namespace_std'] = True data['config']['long_long_int'] = 'll' %>\ #include <iostream> #include <string> #include <vector> #include <algorithm> #include <utility> #include <tuple> #include <cstdint> #include <cstdio> #include <map> #include <queue> #include <set> #include <stack> #include <deque> #include <unordered_map> #include <unordered_set> #include <bitset> #include <cctype> using namespace std; #define ll long long #define ld long double #define v vector #define rep(i, n) for (int i = 0; i < (int)(n); ++i) #define rep3(i, m, n) for (int i = (m); i < (int)(n); ++i) #define rrep(i, n) for (int i = (int)(n)-1; i >= 0; --i) #define rrep3(i, m, n) for (int i = (int)(n)-1; i >= (m); --i) #define all(x) x.begin(), x.end() #define rall(x) x.end(x), x.begin() #define endl '\n' ${cplusplus.declare_constants(data)} ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ${cplusplus.return_type(data)} solve(${cplusplus.formal_arguments(data)}) { // TODO: edit here } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); ${cplusplus.read_input(data)} auto ${cplusplus.return_value(data)} = solve(${cplusplus.actual_arguments(data)}); ${cplusplus.write_output(data)} return 0; }And save below config toml as
~/.config/online-judge-tools/prepare.config.toml.[templates] "main.py" = "main.py" "main.cpp" = "template-ext.cpp" "generate.py" = "generate.py"
TODO oj.el - ja - Competitive programming tools client for AtCoder, Codeforces
Intro
競技プログラミングコミュニティが年々大きくなっています。 日本ではAOJやAtCoder、世界ではTopCoderやCodeforcesなどのオンラインジャッジが人気です。
オンラインジャッジはコンパイル、実行を行い、あらかじめ用意されているテストケースをパスするかどうか確認してくれます。
プログラミング初心者のプログラミング導入にも最適ですし、プログラマの力試し/筋力向上としても、とても良い教材です。
ただ、サンプルケースをダウンロードしたり、解答をいい感じに管理したりと定型作業も多いです。
ojとoj-templateというツールで、その定型作業を自動化できます。
そのojをEmacsと連携させた oj というパッケージを紹介します。
なお、このojは私が作ったパッケージなので、技術的な話は「」に書いてあります。
Install

(leaf oj
:doc "Competitive programming tools client for AtCoder, Codeforces"
:req "emacs-26.1" "quickrun-2.2"
:tag "convenience" "emacs>=26.1"
:url "https://github.com/conao3/oj.el"
:emacs>= 26.1
:ensure t
:custom ((oj-compiler-c . "clang")
(oj-compiler-python . "cpython")
(oj-default-online-judge . 'atcoder)))
Usage
oj-install-package
このパッケージはoj, oj-template, seleniumに依存しています。
これらのソフトは
M-x oj-install-packagesでインストールできます。(defun oj-install-packages () "Install `oj', `oj-template', `selenium' pip package via `pip3'." (interactive) (unless (yes-or-no-p "Install `oj', `oj-template', `selenium' via pip3?") (error "Abort install")) (dolist (elm '(("oj" . "online-judge-tools") ("oj-template" . "online-judge-template-generator") ("selenium" . "selenium"))) (unless (executable-find (car elm)) (unless (executable-find "python3") (error "Missing `python3'. Please ensure Emacs's PATH and the installing")) (unless (executable-find "pip3") (error "Missing `pip3'. Please ensure Emacs's PATH and the installing")) (oj--exec-script (format "pip3 install %s" (cdr elm))))))もしこの関数によってインストールしたくない場合は、ターミナルで次のコマンドでインストールします。
pip3 install online-judge-tools pip3 install online-judge-template-generator pip3 install seleniumさらにEmacsの
PATHが適切に設定されていることを確認してください。M-! which ojがojへのパスを返せば、Emacsからojを利用できます。 出力がない場合は、Q&Aを参照するか、exec-path-from-shellを導入することを検討して下さい。
oj-prepare
M-x oj-prepareはこのパッケージを使うためのファーストステップです。このコマンドは次のようなフォーマットを受理します。
- Problem URL: https://codeforces.com/contest/1349/problem/A
- Contest URL: https://codeforces.com/contest/1349/
- Problem shortcode (for
oj-defualt-online-judge): 1349/A - Contest shortcode (for
oj-default-online-judge): 1349
Edit your code
oj-home-dirにファイルが生成されるので、ファイルを開いて編集して下さい。$ tree codeforces/ codeforces/ └── 1349 ├── A │ ├── a.out │ ├── geckodriver.log │ ├── generate.py │ ├── main.cpp │ ├── main.py │ └── test │ ├── sample-1.in │ ├── sample-1.out │ ├── sample-2.in │ ├── sample-2.out │ ├── sample-3.in │ └── sample-3.out ... └── F2 ├── generate.py ├── main.cpp ├── main.py └── test ├── sample-1.in ├── sample-1.out ├── sample-2.in ├── sample-2.out ├── sample-3.in └── sample-3.out $ cat codeforces/1349/A/main.cpp #include <bits/stdc++.h> using namespace std; #define ll long long #define ld long double #define v vector #define rep(i, n) for (int i = 0; i < (int)(n); ++i) #define rep3(i, m, n) for (int i = (m); i < (int)(n); ++i) #define rrep(i, n) for (int i = (int)(n)-1; i >= 0; --i) #define rrep3(i, m, n) for (int i = (int)(n)-1; i >= (m); --i) #define all(x) x.begin(), x.end() #define rall(x) x.end(x), x.begin() #define endl '\n' ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ll solve(int ebd, const vector<ll> & zdf) { // TODO: edit here } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int ebd; cin >> ebd; vector<ll> zdf(ebd); rep (i, ebd) { cin >> zdf[i]; } auto ans = solve(ebd, zdf); cout << ans << endl; return 0; }
oj-test
M-x oj-testはcompileとtestを行います。もしテストケースをパスしたら、
*oj*バッファに次のように出力されます。[*] 3 cases found [!] GNU time is not available: time [*] sample-1 [x] time: 0.001666 sec [+] AC [*] sample-2 [x] time: 0.002213 sec [+] AC [*] sample-3 [x] time: 0.001923 sec [+] AC [x] slowest: 0.002213 sec (for sample-2) [+] test success: 3 casesコンパイルコマンドは自動的に
quickrunパッケージの定義から推測されます。quickrun-add-commandを使用することで、追加や上書きができます。
oj-submit
オンラインジャッジに提出するには
M-x oj-submitを実行します。 (初回にはオンラインジャッジごとにM-x oj-loginが必要です)
Customize
Variables
oj-shell-program :
*oj*バッファで使われるシェル (defaultshell-file-name(bashor some customized shell))oj-home-dir : ファイルが生成されるパス (default
"~/.emacs.d/oj/")oj-default-online-judge : 規定のオンラインジャッジ (default
'codeforces)oj-compiler-c : C/C++の提出で使われるコンパイラ (default
gcc)Clangをローカルで使いたい場合は次のスニペットをinit.elに追加します。 ```emacs-lisp (quickrun-set-default "c" "c/clang") (quickrun-set-default "c++" "c++/clang++") ```oj-compiler-python : Pythonの提出で使われるコンパイラ (default
cpython)oj-login-args :
oj loginのオプション引数 (defaultnil) ```text usage: oj login [-h] [-u USERNAME] [-p PASSWORD] [–check] [–use-browser {always,auto,never}] urlpositional arguments: url optional arguments: -h, --help show this help message and exit -u USERNAME, --username USERNAME -p PASSWORD, --password PASSWORD --check check whether you are logged in or not --use-browser {always,auto,never} specify whether it uses a GUI web browser to login or not (default: auto) ```oj-prepare-args :
oj-prepareのオプション引数 (defaultnil) ```text Args for `oj-prepare’.usage: oj-prepare [-h] [-v] [-c COOKIE] [--config-file CONFIG_FILE] url positional arguments: url optional arguments: -h, --help show this help message and exit -v, --verbose -c COOKIE, --cookie COOKIE --config-file CONFIG_FILE default: ~/.config/online-judge-tools/prepare.config.toml ```oj-test-args :
oj testのオプション引数 (defaultnil) ```text Args foroj-test'. Note that the runtime command (-c’) is detected automatically.usage: oj test [-h] [-c COMMAND] [-f FORMAT] [-d DIRECTORY] [-m {simple,side-by-side}] [-S] [--no-rstrip] [--rstrip] [-s] [-e ERROR] [-t TLE] [--mle MLE] [-i] [-j N] [--print-memory] [--gnu-time GNU_TIME] [--no-ignore-backup] [--ignore-backup] [--json] [--judge-command JUDGE] [test [test ...]] positional arguments: test paths of test cases. (if empty: globbed from --format) optional arguments: -h, --help show this help message and exit -c COMMAND, --command COMMAND your solution to be tested. (default: "./a.out") -f FORMAT, --format FORMAT a format string to recognize the relationship of test cases. (default: "%s.%e") -d DIRECTORY, --directory DIRECTORY a directory name for test cases (default: test/) -m {simple,side-by-side}, --display-mode {simple,side-by-side} mode to display an output with the correct answer (default: simple) -S, --side-by-side display an output and the correct answer with side byside mode (equivalent to --display-mode side-by-side) --no-rstrip --rstrip rstrip output before compare (default) -s, --silent don't report output and correct answer even if not AC (for --mode all) -e ERROR, --error ERROR check as floating point number: correct if its absolute or relative error doesn't exceed it -t TLE, --tle TLE set the time limit (in second) (default: inf) --mle MLE set the memory limit (in megabyte) (default: inf) -i, --print-input print input cases if not AC -j N, --jobs N specifies the number of jobs to run simultaneously (default: no parallelization) --print-memory print the amount of memory which your program used, even if it is small enough --gnu-time GNU_TIME used to measure memory consumption (default: "time") --no-ignore-backup --ignore-backup ignore backup files and hidden files (i.e. files like "*~", "\#*\#" and ".*") (default) --json --judge-command JUDGE specify judge command instead of default diff judge. See https://online-judge-tools.readthedocs.io/en/ master/introduction.en.html #test-for-special-forms-of-problem for details format string for --format: %s name %e extension: "in" or "out" (both %s and %e are required.) tips: You can do similar things with shell: e.g. `for f in test/*.in ; do echo $f ; diff <(./a.out < $f) ${f/.in/.out} ; done` ```oj-submit-args :
oj submitのオプション引数 (default'("-y")) ```text Args for `oj-submit’.usage: oj submit [-h] [-l LANGUAGE] [--no-guess] [-g] [--no-guess-latest] [--guess-cxx-latest] [--guess-cxx-compiler {gcc,clang,all}] [--guess-python-version {2,3,auto,all}] [--guess-python-interpreter {cpython,pypy,all}] [--format-dos2unix] [--format-rstrip] [-G] [--no-open] [--open] [-w SECOND] [-y] [url] file positional arguments: url the URL of the problem to submit. if not given, guessed from history of download command. file optional arguments: -h, --help show this help message and exit -l LANGUAGE, --language LANGUAGE narrow down language choices if ambiguous --no-guess -g, --guess guess the language for your file (default) --no-guess-latest --guess-cxx-latest use the lasest version for C++ (default) --guess-cxx-compiler {gcc,clang,all} use the specified C++ compiler if both of GCC and Clang are available (default: gcc) --guess-python-version {2,3,auto,all} default: auto --guess-python-interpreter {cpython,pypy,all} use the specified Python interpreter if both of CPython and PyPy are available (default: cpython) --format-dos2unix replace CRLF with LF for given file --format-rstrip remove trailing newlines from given file -G, --golf now equivalent to --format-dos2unix --format-rstrip --no-open --open open the result page after submission (default) -w SECOND, --wait SECOND sleep before submitting -y, --yes don't confirm ```
Template file
ojにおいて、自動生成されるソースコードにはテンプレートファイルを使うことができます。
テンプレートをカスタマイズするには、下記の様なファイルを
~/.config/online-judge-tools/template/template-ext.cppとして保存します。<%! import onlinejudge_template.generator.cplusplus as cplusplus import onlinejudge_template.generator.about as about %>\ <% data['config']['rep_macro'] = 'rep' data['config']['using_namespace_std'] = True data['config']['long_long_int'] = 'll' %>\ #include <iostream> #include <string> #include <vector> #include <algorithm> #include <utility> #include <tuple> #include <cstdint> #include <cstdio> #include <map> #include <queue> #include <set> #include <stack> #include <deque> #include <unordered_map> #include <unordered_set> #include <bitset> #include <cctype> using namespace std; #define ll long long #define ld long double #define v vector #define rep(i, n) for (int i = 0; i < (int)(n); ++i) #define rep3(i, m, n) for (int i = (m); i < (int)(n); ++i) #define rrep(i, n) for (int i = (int)(n)-1; i >= 0; --i) #define rrep3(i, m, n) for (int i = (int)(n)-1; i >= (m); --i) #define all(x) x.begin(), x.end() #define rall(x) x.end(x), x.begin() #define endl '\n' ${cplusplus.declare_constants(data)} ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } ${cplusplus.return_type(data)} solve(${cplusplus.formal_arguments(data)}) { // TODO: edit here } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); ${cplusplus.read_input(data)} auto ${cplusplus.return_value(data)} = solve(${cplusplus.actual_arguments(data)}); ${cplusplus.write_output(data)} return 0; }さらに下記の様なtomlを
~/.config/online-judge-tools/prepare.config.tomlとして保存します。[templates] "main.py" = "main.py" "main.cpp" = "template-ext.cpp" "generate.py" = "generate.py"
emacs
2020年代のEmacs入門 - https://emacs-jp.github.io/tips/emacs-in-2020.html ↩︎
Emacsに一生入門できねえ2020 - https://anond.hatelabo.jp/20200921040234 ↩︎
風になりたい奴だけがEmacsを使えばいい 2020 - https://blog.tomoya.dev/posts/only-those-who-want-to-be-the-wind-should-use-emacs-2020/ ↩︎
Zenn needs help - https://catnose99.com/zenn-needs-help/ ↩︎
Eric Schulte, Dan Davison, Thomas Dye, et.al., “A Multi-Language Computing Environment for Literate Programming and Reproducible Research” https://www.jstatsoft.org/article/view/v046i03 ↩︎
ox-zennの使い方 - https://zenn.dev/conao3/articles/ox-zenn-usage ↩︎
