正規表現の最短マッチに関するVimとPerlの違い

以前にも近い話題でひとつ書いたのですが、

note103.hateblo.jp

それと重なりながらもちょっとズレるトピックでけっこうハマったので、おもに未来の自分用にまとめておきます。

例題

以下の文字列に対して、最初の apple だけマッチさせるパターンを考えてください。

appleorangeapple

という問題があったとき、「最初の apple だけ」ということは、いわゆる「最短マッチ(非欲張り型)」のパターンを考えることになりますから、Perl だとこんな感じで作れます。

Perl

my $foo = 'appleorangeapple';
say "最短マッチ: $1" if $foo =~ /^(.*?e)/;
say "最長マッチ: $1" if $foo =~ /^(.*e)/;

実行。

最短マッチ: apple
最長マッチ: appleorangeapple

パターンの部分だけを取り出すと、こんな感じになります。

/^.*?e/

Vim

次に、Vimで同じことをやりたい場合、通常のVim正規表現だと、丸括弧などのエスケープ処理がPerlのそれに比べてかなり面倒なため、ここではそうしたVim特有のクセをなるべく排除できるように \vパターンスイッチを使います。

\vパターンスイッチとは何か? ということについて、Vim教科書のマスターピースこと『実践Vim』から説明を抜き出すと、

\vパターンスイッチを使うと、すべての特殊記号に関する規則を正規化できる。(略)
\vパターンにより、Vim正規表現エンジンの振る舞いはPerlPythonRubyにより近いものに切り替わる。それでも違いはあるけれど。
(p244)

とのこと。

ぼくは少し前までは、「Vim正規表現にはいろいろクセがあるけれど、これを体得してこそ真のVimmerになれるはず……」と思って一つ一つ地道にエスケープしていましたが、最近になって「そろそろラクになってもいいのでは……」と思って\vを使うようになりました。

するとたしかに、大半のケースにおいてはそれでほぼ、Perlで使うのと同様の結果を期待できるのですが、今回とり上げる最短マッチはその例外、つまり上の説明で言うところの「それでも違いはあるけれど。」にあたるようで、Vimで\vパターンスイッチを付けながら同じことをやろうとすると、こんな感じになります。

/\v^.{-}e

先ほどのPerlの場合とパターンの中身だけ並べてみましょう。

^.*?e
^.{-}e

Perlでは「*?」となっていた部分が、Vimだと「{-}」になっています。

ちなみに、最長マッチのほうでは、PerlVimもこれで行けます。

^.*e

つまりここでの違いは、Vimにおいては「?」を最短マッチの道具として使えない、ということですね。

もう少し具体的に言うと、Vimでは単純に「?」の代わりに何かを使うとか、「?」をエスケープするとかではなく、「*?」を「{-}」で代替するという、なかなか自然には想像のつきづらい規則だったので、把握するまでにけっこうな時間を要しました。

ちなみに、Perlにおける「.+?」はVimだと「.{-1,}」になるようです。

このあたりの情報については、前回の記事でも謝辞とともに紹介しました以下のサイトのまとめが非常にわかりやすかったです。

実際の用途としては、ひとまずVimでは「.{-}」がPerlにおける「.*?」の代わりになる、とだけ覚えておけば大半の状況は乗り越えられそうな気がしますが、とはいえ1年で何回使うかわからないぐらいレアなパターンかもしれないので、忘れてしまってもすぐに思い出せるよう、ここにまとめておきました。

実践Vim 思考のスピードで編集しよう!

実践Vim 思考のスピードで編集しよう!

変数は「箱」か?(2)

以下の記事の続編です。
note103.hateblo.jp

目的を共有する

前回の記事はわれながら丁寧に書いたなと思っていて、その時点で書き残したことはほとんどなかったのですが、ただ全体が長かったせいか、ちょっと意図が伝わっていないかな、と思える反応をいくつか見かけたので、まずは以下のまとめ部分を中心に、そもそもの目的について簡単に補足してみたいと思います。

変数は「箱」か? という問いに対しては、そうとも言えるし、そうじゃないとも言えます。

しかしいずれにせよ、大切なことは、「それが入門者にとって効果的なのかどうか」を考え続けることだと思います。
「とりあえずこんな風に説明しておけばいい」という標準的な例を洗練させながら、最終的には、その瞬間に目の前にいる「教わる人」に最適な説明を考えていくべきでしょう。

この最後の1行では、二つのことを言っています。

少しアレンジしつつ2行に分けると、このようになります。

  1. 「とりあえずこんな風に説明しておけばいい」という標準的な例を洗練させていけるといいな。
  2. でもそれはそれとして、最終的には一人ひとりの「教わる人」に最適な説明を考えていくべきだよな。

ぼくはこの二つは対立するものではなく、矛盾なく両立させることができると思っています。

より具体的に言うと、「もっと良い喩え話はないかなあ?」という考えは前者(1)にあたります。

そして、「喩え自体はなんでもよくて、教わる人が理解しやすければいい」という考え方は後者(2)です。

前回のぼくの結論は、「結局は2が大事だけど、現実的には1が必要とされる状況も少なくないから、1の質も継続的に高めていけるといいよね」みたいなことです。

しかしながら、Twitterなどで記事への反応を見てみると、1の「もっと良い喩え」に関する考えに対して、「結局は喩えなんだからそこにこだわっても意味ないよ」と、2の論理で打ち消すような意見が少なくないように感じました。

これは話が通じていません。長文ゆえの斜め読みのせいか、ぼくの文章が悪かったか、あるいはその両方でしょう。

くり返しますが、1と2は矛盾なく両立するものであり、打ち消し合うものではないと思います。
2を大切にしながら、1を洗練させられたらいいなあ、というのがぼくの言いたかったことです。

「わかりやすい」が意味するもの

ところで、本件に関する意見の中には、「箱の例を使ったほうがわかりやすいのだから云々」というものがいくつかありました。
しかし、これには以下のような疑問を感じます。

第一に、そこで言う「箱の方がわかりやすい」の「わかりやすさ」とは何を指しているのか? という疑問。

ぼく自身も前回の記事で、「箱の方が直感的」と書きましたが、しかし「具体的な箱のイメージを思い浮かべやすいこと」と、「変数を理解する際の助けになりやすいこと」は必ずしも一致しないような気もします。

もしかすると、そこで言われる「わかりやすさ」とは、「箱のイメージしやすさ」のことであって、それがいつのまにか「変数の理解しやすさ」にすり替わってしまっているのではないか、と思いました。

第二に、「箱の方が変数を理解しやすい」と言える根拠はなんだろう? という疑問もあります。
そのような実証研究が行われているのか、そしてその研究成果はすでに広く知れ渡っているものなのか。

おそらくそういうことではないですね。
これももしかすると、素朴に「箱自体のイメージのしやすさ」のことを言っているだけかもしれないと思っています。

ヒューリスティック

この話について考える際、何度か頭をよぎったのは「ヒューリスティック」という概念でした。

ぼくはこれまで、それについてくり返し言及しているので、またかと思う人もいるかもしれませんが、以前に別のブログに載せた引用を再掲します。

複雑なことにも私たちがなぜ直感的に意見を言えるのか、私から明快な説明を提案しよう。難しい質問に対してすぐには満足な答が出せないとき、システム1はもとの質問に関連する簡単な質問を見つけて、それに答えるからである。このように代わりの質問に答える操作を「置き換え(substitution)」と呼ぶ。ここでは、もともと答えるべき質問を「ターゲット質問」、代わりに答える簡単な質問を「ヒューリスティック質問」と呼ぶことにする。
 
ヒューリスティックの専門的な定義は、「困難な質問に対して、適切ではあるが往々にして不完全な答を見つけるための単純な手続き」である。ヒューリスティックという言葉は、「見つけた!」を意味するギリシャ語のユーレカを語源に持つ。(略)
 
ではここで、表1の左欄を見てほしい。ここに掲げられているのは難しい質問であり、どれ一つとっても、まともな答を出すためには他のもっと難しい問題に取り組まなければならない。
 

ターゲット質問 ヒューリスティック質問
絶滅危惧種を救うためにいくら寄付するか? 瀕死のイルカを見かけたらどんな気持ちになるか?
現在の生活はどのくらい幸福か? 今の自分は気分がいいか?
今から六ヶ月後の大統領の支持率はどの程度か? いま現在の大統領の人気はどの程度か?
高齢者を騙したフィナンシャル・アドバイザーにはどの程度の刑罰を与えるべきか? 金融詐欺に自分はどのくらい怒りを覚えるか?
次の予備選挙に立候補予定のこの女性は、政界でどこまで出世するか? この女性は誰か政界の大物と似ているか?

 
たとえば、絶滅危惧種以外に考慮すべき環境問題や社会問題は、他にどんなものがあるか。幸福とは何か。今後六ヶ月の間に政治はどのような展開になりそうか。他の金融犯罪に対する標準的な判決はどうなっているのか。候補者が直面する競争はどの程度厳しいのか、等々。
 
こうしたことを真剣に考え抜いてから答えるのは、どうみても現実的ではない。だがあなたは、完璧に論理的根拠のある答を出すには及ばない。ヒューリスティック質問に答えても、そこそこ筋は通る。このやり方はときにうまくいく――が、ときに重大なエラーにつながる。
 
右欄の質問は左欄の質問からたやすく思いつくし、答えるのも容易である。瀕死のイルカや金融詐欺に対する気持ち、自分の今の気分、候補者の政治的手腕の印象、大統領の今の人気などは、すぐに思い浮かぶことだろう。ヒューリスティック質問は、難しいターゲット質問に対しても、すぐさま答を出してくれる。

(ダニエル・カーネマン『ファスト&スロー(上)』第9章より)

喩え話について考えているとけっこう頭を使いますし、その喩えと現実を結びつけながら次々に現れる矛盾を解消していこうとすると、負担はさらに増していきます。

だからその途中で、「あ〜面倒くさい! そもそも喩え話なんて本質的な問題じゃないんだから(以下略)」というふうに考えてしまっても、それはそれで自然なことだと思います。

とはいえ、冒頭にも書いたとおり「より良い喩えを考えてみよう」ということと、「どうすれば変数の概念をわかりやすく説明できるか」ということは、繋がってはいますが別々の問題です。

そしてこの「関係はあるけど別の問題」を、あたかも同じ問題であるかのように結びつけて、より答えやすい方の質問に答えてしまうという状況が上の引用で挙げたヒューリスティックというものです。

本件に関する反応には、そういった傾向が少なからずあるように思えました。

参照渡しと値渡し

一方、そのようなズレが生じてしまう理由は、ヒューリスティックによるものだけでなく、単に元記事の説明不足による場合もあるだろうと思っています。

たとえば、前回の話を「参照渡し」(CのポインタやPerlのリファレンスのような)の概念にまで広げて適用させようとしている人が何人かいましたが、ぼく自身は「初めてプログラミングを学ぶ人に変数を教えるなら」という前提で考えていたので、ここで言う「変数」とはあくまで初歩的な「値渡し」をするものであり、さらに言えば、Perlにおける「スカラー変数を使った値渡し」を想定していました。

「参照渡し」については、「値渡し/参照渡し」というふうに別々の名前が付けられていることからもわかるように、「値渡し」とは別の現象ですから、「箱」であれ「名札」であれ、ひとつの物をその両方に適用するというのはちょっと無理があるように感じています。

またそれに限らず、元々ぼくの方で暗黙のうちに設定していた様々な条件から外れた上での意見も複数あったので、こうした「喩え」に関する話、とくに「箱」や「名札」や「変数」のような一般的な(定義の曖昧な)名詞を使った喩え話について、不特定多数で考える際には、その土台となる条件・要素を可能なかぎり細かくすり合わせておかないと、不毛な状況になってしまいそうだなと感じました。

名札の問題

ところで、というかそれとも繋がる話で、これは後から気づいたことですが、前回「名札」の例を使った書籍として紹介した『初めてのRuby』では、名札のイラストを用いながら、じつは「参照渡し」が解説されていました。

ぼくは上記のとおり、前回の記事を書いた段階では「値渡し」についてのみ考えていたので、これにはけっこう驚きましたが、たしかに喩えの使用条件を限定すれば、その説明も成り立つとは思います。

またもう一点、これも後から気づいたことですが、その『初めてのRuby』や、同じく名札の例として挙げた『たのしいRuby』では、「名札」を旅行カバンに付ける荷札のように、実体(値・オブジェクト)と直接結びつかせるイメージとして使っていたので、これだと結果的に、箱の例がやっていることとあまり変わらないかな? とも思いました。

いずれにせよ、このことからもわかるように、名札には名札の問題があって、それは「名札」という言葉からイメージされるものが曖昧で、受け手にとって解釈の余地がありすぎるということです。

ちなみに、ではぼく自身は前回、「名札」と言いながらどんなものを想定していたのかというと、それは付箋や名刺のようなもので、同じ言葉(変数名)が記載されたそれを複製していくようなイメージで考えていました。

しかし後述のように、こうした喩えに人物の実名を交える場合は、書き換え可能な「変数」よりも書き換え不能な「定数」の例に使うほうが適切であるように今は思っているので、名刺は定数の説明に限定して使うべきかもしれません。

また、付箋の例だとそこには通常、変数名だけが記載され、それが指し示す対象の情報が入らないため、これもちょっと惜しい気がします。

あえて言えば、メールのように「件名(変数名)」と「本文(値)」を書き込める付箋(またはメモ用紙)という喩えなら諸条件をクリアできそうですが、実際にそのような道具や商品はあまり見かけないので、その点がちょっと弱いかもしれません。

新たな喩えの案

さて、そのように、既存のアイデアにそれぞれの問題があるのだとすれば、また新たな喩えを考えてみたくなるのが人情です。

これはあくまで楽しみとしての思索であり、その案を誰かに押しつけたいわけではまったくありませんが、この機会に考えたものをつらつら書いてみたいと思います。

「物体」ではなく「情報」として扱う

まず原則的な話として、「箱」や「名札(または付箋)」にどのような問題があったのかというと、実際のプログラミングでは物体ではないデータ(数値や文字や式など)を扱っているにもかかわらず、それを物体に置き換えようとしていた点に各種の不整合の原因があったように思えます。

ですから、ここからの案ではなるべく物体に置き換えるのではなく、それ自体は実体を持たないデータ・情報の領域から使える概念を探し、それに置き換えてみたいと思います。

値渡し(スカラー変数)

手始めは、最も初歩的かつ重要な値渡しの例ですが、これには「あだ名(ニックネーム)」、あるいは「仮の名前」という案を挙げたいと思います。

これらに共通するのは、「本名Aのことを仮名Bと呼ぶ」という、本来の名前を別の名前に付け替えるという行為です。

子供がどんな犬を見ても「わんわん」と言うとき、「わんわん」は変数(仮の名前)であり、そこで指し示される値(実体)は犬です。

そしてこの際、子供はその犬がゴールデン・レトリバーなのか、シベリアン・ハスキーなのか、柴犬なのかといった犬種を知らずとも、「わんわん」と言うことで「その犬」を指し示すことができ、これは次々と変数の値を更新し、また活用している状況を彷彿させます。

あるいは、自社にいる能力の高い社員を外部の人に紹介する際、「うちのエースです」などと言うことがありますが(実際にそういう現場に何度か遭遇したことがありますが)、ここで言う「エース」も仮の名前に入るでしょう。

この「仮の名前」があることによって、以前にそれを聞いた取引先の誰かが「ほら、なんという名前だっけ、御社のエースの……」と言えば、対象となる人物の実名を思い出せなくても、その人についての話を続けることができます。
これもまた、「一度宣言(+代入)すれば、その後は元の値をくり返し記述しなくても複数箇所で処理を進められる」という、変数を利用した状況に似ていると思います。

ちなみに、ここで大事なことは、その変数に格納される対象は交換可能な何かであって、交換不能ではない、ということです。

「わんわん」や「エース」は状況や時期によって交換可能ですが、もしこれが「イチロー」だった場合、それは厳密には本名ではない「仮の名前」ですが、とはいえ現実世界ではそこに格納される人物は一人だけですから、こうした名前は交換不能な「定数」の例として利用する方が適切だと考えられます。

値渡し(配列)

次に、配列の変数の例としては、プロ野球の「球団」と「打順」と「選手」の関係を使えるのではないかと考えています。

たとえば、「巨人の4番」といえば王貞治、原、落合、松井、清原、高橋……などが浮かびますが(世代によるでしょうが)、ここでは選手名が「値」、4番という打順が「添字」にあたります。Perlっぽく書くと、

print $巨人[4番];

という感じでしょうか。

実行すると、

松井秀喜

みたいな。
(添字の4って実際は5番目だろ、というツッコミはご容赦ください)

あるいは年度も指定して、「配列の配列」とすればより具体的になりそうです。

print $巨人[2000年][4番];

実行。

松井秀喜

値渡し(ハッシュ)

また、ハッシュ(連想配列・辞書型)の例としては、会社などの「肩書」や「役職」という案があります。

A商事の社長、B株式会社の営業部長、C銀行の広報担当というふうに、所属先の名前は基本的に一定ですが、それらの社名や肩書を通して呼び出される個々人は、人事異動のたびに入れ替わります。

たとえばマイクロソフトのCEOは、現在ナデラさんですから、以下のようになるでしょう。

print $マイクロソフト{CEO}; #=> サティア・ナデラ

あるいはこれも、年数と組み合わせれば「配列のハッシュ」の例に使えるでしょうか。

print $マイクロソフト[2008]{CEO}; #=> スティーブ・バルマー

参照渡し

さて、懸案の「参照渡し」ですが、これについては先に喩えを成立させるための条件を洗い出してみたいと思います。

第一に、これは変数であり、複数の場所に置いていろいろな人が同時に触れられることが前提ですから、変数自体が複製可能でなければいけません。

と同時に、この変数の中身を誰かがどこかで変更すると、他のすべての人の手元にあるそれの中身も変わってしまうものでなければいけません。

それで最初に思いついたのは、Wikipediaです。
Wikipediaは、基本的に誰もが自分の手元で更新でき、更新された情報はすべての人の目の前のスクリーンに反映されます。

「それを言ったらべつにWikipediaじゃなくても、掲示板やTwitterなどのWebサービスの大半がそれに該当するじゃないか」という気もしますが、まあ、イメージしやすいので……ということで。

とはいえ、コンピュータ・プログラミングの学習に関する喩え話で、コンピュータに関わる現象を例に使ってしまうのもちょっと筋が悪い気がするので、もう少し物質感のある、ソフトウェアから離れた例はないかと考えましたが、少し近いかなと思ったのは、新聞や雑誌、あるいはテレビやラジオにおける人生相談のような、ユーザー参加型のメディアです。

そういったメディアは不特定の人が購読・受信していて、たとえばラジオの人生相談のコーナーでは、リスナーの電話の声がそのままラジオ番組の内容を更新し、即座に他のリスナーの耳にも届きます。

ただ、これの問題は、あまり日常的には触れない事象だということですね。

もう一つ、いくつかの条件は無視することになりますが、感覚的に「これって参照渡しっぽいよなあ」と思う事例があって、それは「酔っ払ってTwitterに投稿すること」です。

覚えのある人ならわかると思いますが、当人はあくまで自分の半径数メートルぐらいの、ローカルな世界に対して何かを発信しているようなリラックスした(あるいは向こう見ずな)気分ですが、実際には仕事先や家族なども含む全世界のネットユーザーにその意見を開陳しているわけで、翌朝つらい気分になることも少なくありません。

これは値渡し(コピー)で得た手元の値に手を加えたつもりが、呼び出し元の実在に影響を与えていた、というときの冷たい驚きに通ずるものがあると思います。

寿限無

いろいろと考えてきましたが、最後に、本件に関する自分の原点のような例を挙げて終わりにしたいと思います。

じつはぼくが変数について考えるとき、いつも頭に思い浮かべる話が一つあって、それは落語の「寿限無」です。

寿限無 - Wikipedia

もしもここに出てくる子供の名前を、実際の長々とした名前ではなく、「$寿限無」という数文字の変数に格納できていたら、話(噺)はずっとラクに進められるだろうに、と思います。
(もちろん落語の意味は変わってしまいますが)

これは前記した「仮の名前」の例でありながら、同時に落語の中のその子供、という特定の人物を収める変数なので、実際にはやはり定数の例として使ったほうが適切だと思いますが、とはいえ、ぼくが変数に対して「便利だなあ」と感じる以下の要素を満たしているという点で、それなりに有効と思われる題材です。

本来長い名前が入るはずの部分に変数「$寿限無」を使えば……

  • 同じことを何度も言わずに済む。
  • 修正をひとつの場所で行える。
    • 1回修正すれば複数の箇所がすべて更新される。
    • 複数の箇所を直す中で新たなミスが生まれるという事態を回避できる。

https://www.youtube.com/watch?v=JfDF7J06AeYwww.youtube.com

関連記事

note103.hateblo.jp

choっと作った便利なツール

以前の記事で、dirmove というディレクトリ間をさくさく移動するための自作コマンドラインツールを紹介しましたが、

note103.hateblo.jp

これを @mattn さんによる cho というコマンドラインツールと組み合わせたらさらに便利になってしまい、以前の時点でも自作ツール史上トップ3ぐらいに使っていましたが、今ではおそらく最も利用しているであろうツールになりました。

DEMO

cho と組み合せた様子はこんな感じです。

f:id:note103:20160626013034g:plain
(初めて打鍵の様子が出るようにしてみた…… powerd by KeyCast

peco との連携はこのように。

f:id:note103:20160626115735g:plain

Cool!

概要

ここでやっていることは大きく二つあって、ひとつはツール名のとおりディレクトリ移動。
もう一つは、移動した先のディレクトリでテキストファイルなどを選択すると、それをオープンする、というもの。

元々は前者のみの機能として作っていたので、素朴に「dirmove」などという名前を付けていましたが、今となってはファイルオープンもけっこう大きな要素なので、変名するか若干検討中です……。

選択

冒頭で挙げたブログ記事でも触れていますが、以前は pecosentaku という2種類のコマンドラインを同様のかたちで組み合わせていました。

しかしその後、 @mattn さんが cho という汎用性の高い小さなツールを公開されて、

mattn.kaoriya.net

これがその sentaku と非常にかぶるというか、ぼくが sentaku でやりたかったことをほぼそのままカバーしていたことから、とりあえず使ってみたいと思って peco, sentaku に続く3つめの連携ツールとして取り入れてみました。

しかしそのうち、大半の操作で cho 連携の方しか使っていない状況になってしまい、後述のように使い分けが可能な peco はともかく、ほぼ同等の役割を果たせる sentaku まわりのコードまで保守(というか)をするのもちょっと負担だなと思うようになり、現状の peco, cho との連携方式に収まりました。

peco との違い・使い分け

上の @mattn さんのブログ記事にもありますが、基本的に cho の機能は j/k キーで上下に1行ずつ動くことだけなので、対象となるファイル名などがある程度明確にわかっている場合は、文字入力しながらインクリメンタルに絞り込んでいける peco のほうが便利です。

また、peco は最上行からさらに上へ進むと最下行へジャンプする、というトグルの動作をするので、大量の選択肢の中から一番下の方を見たい、というときも peco 連携のほうが良いでしょう。

一方、自分の普段の作業を振り返ると、ディレクトリやファイルを選ぶときというのは、あまり明確に対象を意識できていない場合も多く、頭の中の「あんな感じのあれがほしい……」という言語化される前のぼんやりした意識が率先してどんどん先へ進んでいくようなところがあるので、そういう状態でも「とりあえずディレクトリを開いて内部を探していける」、そして「j/k キーを1個ずつ押していくだけで移動できる」という cho との連携は、多くの場面で感覚的なサポートを果たしてくれて便利です。

用途・有効性

このツールがとくに力を発揮するのは、たとえば以下のようなファイルの中から、ひとつだけ対象を選びたいときです。

2008-04-22-67d8eab_project1.txt
2013-05-24-7968fe7_project2.txt
2016-05-25-2f086cd_project3.md
2016-05-26-2ed78da_project4.txt
2016-06-26-5ce1266_project5.md
(以下延々と続く)

このうち、もし3番目のproject3を選びたい場合、コマンドライン上から指定しようとすると、一意を示す要素に辿りつくまでに

# 2を打ってタブ
20 # 1を打ってタブ
201 # 6を打ってタブ
2016-0 # 5を打ってタブ
2016-05-2 # 5を打ってタブ
2016-05-25-2f086cd_project3.md # ようやくすべて出てくる

……と、合計5*2回(数字キー+タブキー)の打鍵が必要になり、この間のストレスはなかなかのものです。

しかし、ここで今回のツールを使えば、目当てのものに辿りつくまで j/k キーを押していくだけで、半ば無意識のうちにファイルを指し示すことができます。

f:id:note103:20160626013550g:plain

オプション

その他の機能として、引数で対象ファイル/ディレクトリの絞込みをできるようにしています。

上の設定で言うと、デフォルトのコマンドは「dfm」のひとつだけで、引数のバリエーションは以下のようになります。

$ dfm   # cho方式で選択, ファイル&ディレクトリともに選択対象, 不可視ファイルを除く
$ dfm p # peco方式で選択
$ dfm . # 不可視ファイルも選択肢に含める
$ dfm f # ファイルのみ選択対象とする
$ dfm d # ディレクトリのみ選択対象とする

それぞれの引数は、複数利用可、順番も自由です。
f と d は片方を選択するものですが、もし両方入れてしまったら後から入れたほうが使われます。

デフォルトを peco ではなく cho にしているのは、それで充分というケースのほうが多いと感じているためですが、前述のように peco のほうが便利な場面も少なくないため、実際にはエイリアスを使って、 peco も簡単に起動できるようにしてあります。
(READMEの設定例をご参照)

まとめ

今回は @mattn さん製の cho を使った自作コマンドラインツール/環境を紹介してみました。

実際には、この他にも同様の仕組みで作成&使用している便利ツールがいくつかあって、それらも合わせて紹介するつもりでしたが、ちょっと長くなりそうだったのでまた別の機会に紹介できればと思います。

cho も peco も機能はシンプルながら大変有用なツールで、日々のちょっとした面倒な作業がチリも積もれば的に大きく改善されていると感じます。

作者のお二方、そしてコントリビューターやコミュニティの皆さんに心から感謝します。

textlintをVimから使う

textlintVimの定番シンタックスチェック用プラグインである scrooloose/syntastic から使える、ということは @azu さんのブログ記事や、同じく @azu さんによる最初のぼくのtextlint記事に対して頂いたブックマーク・コメントなどを通して、情報としては把握していたのですが、どうもそのリンク先に飛んでみても具体的な設定方法などが示されておらず、

ハードル高……という感じで手を打てずにいました。

しかしふと一念発起的に、「とりあえずなんかやってみるか」と、カンであれこれやってみていたら、「あ……動いた」みたいな感じになったので、その知見を共有します。

とりあえず動くまで

まず結論的に、VimMarkdownやテキストファイル(.txt)を開いているときにtextlintを走らせるための設定を示すと以下です。

.vimrc

Plug 'scrooloose/syntastic'

set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*

let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0

let g:syntastic_markdown_checkers = ['textlint']
let g:syntastic_text_checkers = ['textlint']

簡単に解説すると、1行目はVimプラグインのパッケージ管理ツール vim-plug を使って syntastic を読み込んでいるところ。

一旦途中を飛ばして、最後の2行がtextlint に関わる設定。
そこでMarkdownとテキストファイルをtextlintでチェックするように、と書いています。

……と、あたかも当然のことのように書きましたが、この2行分の内容がどこを探しても例示されていなくて*1、しかしぼくはPerlに関する同プラグインの記述を以下のようにしているので、

let g:syntastic_enable_perl_checker = 1
let g:syntastic_perl_checkers = ['perl', 'podchecker']

この一部をそれっぽく書き換えてみたら動いた、という感じでした。

話を戻して、その中間部分の数行は syntastic の基本的なレコメンド・セッティングです。

ここまで設定すると、こんな感じでエラーが画面下部に出てくるようになります。

f:id:note103:20160623041800p:plain

Great!!

passive モードを設定する

ただ、このままだとファイルを保存するたびにチェックが走ってしまい、その間はVimが止まって文章の編集をできなくなってしまうので(しかもけっこう待つ……)チェックしたいときだけ走らせられるように、以下の記述も追加します。

let g:syntastic_mode_map = { 'mode': 'active',
                           \ 'passive_filetypes': ['markdown', 'text'] }

これによって、通常は保存するたびにsyntastic が動き、Markdown とテキストファイルのときだけは明示的に命令した時点でチェックが走るようになります。

では、その明示的に命令するのはどうやるのかというと、syntastic の実行コマンドというのが以下なので、

:SyntasticCheck

こんな感じでマッピングを設定しておきます。

" Syntastic Run:
nnoremap <Leader>sc :SyntasticCheck<CR>

これによって、Leaderキー(通常はバックスラッシュでしょうか。ぼくはカンマにしていますが)とscを打鍵したときだけチェックが走るようになります。

また、チェック機能自体を有効・無効化するトグルコマンドが以下なので、

:SyntasticToggleMode

以下のように設定しておくと、エラー画面を閉じたりするのもラクで便利です。

" Syntastic Toggle:
nnoremap <Leader>st :SyntasticToggleMode<CR>

なお、これらの syntastic の設定方法については、以下が大変参考になりました。

留意点

ところで、これにまつわるちょっとしたハマりどころというか、ありがちなミスとして、前回の記事に書いたこととも繋がりますが、

Vimから使う場合でも、.textlintrc がホームディレクトリまたはカレントディレクトリ以上の階層に存在しないとtextlintは動きません。

そりゃそうでしょ、という感じでもありますが、何度か「あれ……動かないな」と思ってみたら単にそれだけのことだった、ということがあったので。

ようはVim経由だからといって、Vimプラグインのインストールや設定だけで完結するわけではなく、通常の設定は別途必要だということですね。

まとめ

以上、textlint をVimから利用する方法についてまとめてみました。

最初に以下の記事を書いたときは、この方法を知らなかったので、Vim からエラー(指摘)を見るためにシェルスクリプトPerlを組み合わせながらけっこう泥臭いことをやっていましたが、

note103.hateblo.jp

おかげさまでその大半が不要になりました(笑)。

何しろ、Vim で文章を書いたり編集したりしているときはこの程度のエラー表示で充分という気もしますし、またこのtextlint とsyntasticの組み合わせは、シンプルであるがゆえになかなか強力という印象もあるので(くり返し使いやすい、というか)。

一方、textlint には pretty-error フォーマットや、--fix オプションなど多彩な機能がありますから、これまでの知見を生かしながら、さらに自分に適した方法を探していきたいと思っています。

*1:一応こういう検索結果はできるかぎり見回ったのだけど、参考になるようなならないような……という感じだった。 Search Results · GitHub

textlintの設定をアップデートした

前回の記事には、珍しくたくさんのブックマークが付いていました。

note103.hateblo.jp

あんなに長文だったのに……。励みになりました。ご紹介くださった方々、ありがとうございます。

さて、今回はそれ以降の知見として、いくつかtextlint への理解が深まり、それにともない設定の構成をあらためた部分がいくつかあったので、その情報を共有しておきたいと思います。

.textlintrc の設置場所を変更

今回自分にとって一番大きな変更はこれで、前回の段階ではマシン内のどこからでも(どのファイルに対しても)利用できるよう、各種のルールを事前に設定・記述しておく「.textlintrc」をホームディレクトリに置いていましたが、これをプロジェクトごと、リポジトリごと、といったかたちで個別の置き場にそのつど作る方式に変更しました。

具体的には、以前から .textlintrc の元になるマスターファイルは任意のドットファイル置き場に置いてあったので、

# example
$ /path/to/dotfiles/textlintrc

bashrcに以下のようにエイリアスを入れておいて、手軽にカレントディレクトリへコピーできるようにしています。

# .bashrc
alias tt="cp /path/to/dotfiles/textlintrc ./.textlintrc"

またこの際、「あれ、このプロジェクトに .textlintrc って入れてたっけ・・」などとわざわざ確認しなくてもすぐに状況を把握できるよう、以下のような関数も設定しておいて……

# .bashrc
function tlo {
    if [ -e ".textlintrc" ] ; then
        vi -g .textlintrc
    else
        echo "No files. If you need it, type 'tt'."
    fi
}

とりあえずターミナルで「tlo」と叩いて、もしファイルがあればオープンし、なければ「今そのファイルはないので、ほしければ生成用のコマンド(tt)を叩きましょう」と教えてくれるようにしました。

変更した理由

以前までは、.textlintrc への記述を通して textlint-rule-prh のための辞書.yml の場所を指定できる印象が強かったので、「プロジェクトごとに配置するならその辞書.ymlの方だろう」と考えていましたが、しばらく使ってみると、textlint-rule-prh は汎用的なツールというより、特定の用語を対象とした特化的なツールという側面のほうが大きく感じられたので、より柔軟に幅広く校正ルールを設定するには .textlintrc で様々なルールを組み合わせたほうが手っ取り早いと思うようになりました。

となれば、これはたったひとつのそれを使い回すのではなく、プロジェクトごとに配置して、それぞれのためにカスタマイズするほうが合理的だと思われ、上記のような設定に至りました。

textlint-rule-prh の辞書.ymlの優先度

逆にというか、textlint-rule-prh の辞書.ymlのほうはこれまでどおり、基本的には一つの場所にマスターファイルを置いておいて、各textlintrcからは絶対パスでそれを直接指定し、どこからでも同じ辞書.ymlを使えるようにしています。

数日これで運用した感想としては、今のところはこれで不便はないというか、この辞書.ymlをプロジェクトごとに変える必要はあまりないかなと感じています。

また、.textlintrc は用途に応じてルールを組み合わせたい場合が少なくないため、プロジェクトごとに配置することは妥当だと思えますが、辞書.yml の方は「育てていく」感じというか、けっこう記述量が多くなりがちなだけに、秘伝のタレ的にそれをメンテナンスしていく感じのほうが使いやすいかな、という印象もあります。

とはいえ、せっかくこの辞書.yml もプロジェクトごとに利用しやすくなっているので、それを生かせるよう、上記の .textlintrc と同様のコマンドを設定して、いつでもマスターファイルからカレントディレクトリへコピーできるようにしてあります。

# .bashrc
# 'dio'でカレントディレクトリに辞書があるかをチェックし、あればファイル・オープン、なければ生成用コマンドを知らせる

function dio {
    if [ -e "dict.yml" ] ; then
        vi -g dict.yml
    else
        echo "No files. If you need it, type 'dd'."
    fi
}
alias dd="cp /path/to/dict/dict.yml ./dict.yml"

変更に際しての留意点

上記2点の変更は結果的にうまくいって、現在は大変快適に利用できていますが、最初にちょっとハマった点として、.textlintrc をホームディレクトリに置いたままだと、それ以下の階層に置かれた .textlintrc は機能しません。

よって、.textlintrc をプロジェクトごとに設置する場合は、それをプロジェクトのルート(あるいは対象のソースコードがある)ディレクトリに置きつつ、ホームディレクトリからは外しておく必要があります。

今から思えば当然というか、単純なことですが、身にしみて理解するまではちょっと時間がかかりました。

今使っている .textlintrc の公開・共有

最後に、現在使っている .textlintrc(マスターファイル)の設定を紹介しておきます。

textlintrc_2016-06-18-18-43-35.txt · GitHub

終盤の // でコメントアウトしている部分は、英文の校正用ルールです。
日本語の文章を編集しているときにこれを生かしておくと、意図しないエラーを拾うことが多いようだったので、日本語を扱うときは外しています。

まとめ・予告

今回は textlint の利用に際して必要な設定ファイル・辞書などの構成に関する現状をまとめてみました。

じつは同じ記事内に、Vim から textlint を動かした話も続ける予定だったのですが、1本にまとまっているとちょっと読みづらそうだったので、それは別の記事に分岐します。

※分岐しました。

textlintで日本語テキストの文字校正を試してみた

はじめに

textlintについては少し前から時々名前を聞くなと思っていましたが、自分に関わりがありそうなものとして意識したきっかけは、@t_wada さんによる以下のツイートだったと思います。

これを見た時点では、まあそういうこともあるだろうな、という程度の軽い感想でしたが、その後じわじわと「気になる」感じが増してきて、ようやく週末に少し時間を取れそうだったので、とりあえず1〜2日トライしてみたのが以下の話です。

先にぼくの背景について簡単に記しておくと、ぼくは3年前のちょうど今頃からプログラミングに入門し、最初はRubyJavaScriptを触っていたのだけど、同年8月頃にたまたまPerlを教えてくれる人たちに出会って、以後このブログに書いてきたようなことをしている非エンジニアの編集者です。

※その他の履歴についてはこちらをどうぞ。 → Profile

プログラミング歴3年とは言っても、普段の仕事ではとくにプログラミングを活用したり学習したりする機会もなく、ようやくチョット使えるようになってきたのもPerlVimぐらいで、今回のテーマであるtextlintまわりで使われているJavaScriptなどの知識はほとんど無いので、いろいろ勘違いしているところもあるかと思いますが、その際にはコメント欄やTwitterにて適宜ご指摘ください。

導入

さて、上記のような興味とともに最初にチェックしたのは、textlint作者である @azu さんの以下のブログ記事でした。
efcl.info

レポジトリにも導入のチュートリアルがあるので、どちらを見ながらスタートするか少し迷いましたが、
github.com

日本語で書かれている前者の記事をぼんやり目で追いながら、順にコマンドを打ち込んでいったらいつの間にか最初の環境づくりはほぼ完了していました。

※具体的には、textlint本体とmax-ten, spellcheck-tech-word, no-mix-dearu-desumasuというルール群をインストールして、試しに動かしてみるまで。

ちなみに、とりあえずtextlintを使ってどのような校正が成されるのか体験してみたい、という目的であれば、その記事でも紹介されているように Chrome拡張を使うという選択肢もあります。

ぼくの場合は、以前にVimJSONシンタックスチェックを設定した際、少しだけnpmを触ったことがあって、

note103.hateblo.jp

かつ、このときにはけっこうハマったので、その復習もかねてコマンドラインからのインストール&実行を試すことにしました。

一瞬話がそれますが、僕がこういうことをやる一番の目的は、「文字校正さえできればいい」といった最終形を求めてのことではなく、それができるまでの過程を体験しながら、それが置かれているシステムの裏側(仕組み)を知りたいということ、またそれを身につけて自分一人でも手元でイチから再現できるようにしたい、といった点にあるようで、それがこういうトライの原動力になっているようにも思えます。

textlintrcを設置

本題に戻ると、上記の導入記事で紹介されている最初のルール群を入れた後は、そのまま案内に沿って textlintrc というvimrc的なものを作りました。

書式はJSONYAML、JSモジュール(って何?)のいずれでも良いようですが、ここは素直に @azu さんが例示していた以下の形で。

{
  "rules": {
    "max-ten": {
      "max": 3
    },
    "spellcheck-tech-word": true,
    "no-mix-dearu-desumasu": true
  }
}

これはJSON形式だと思いますが(いちいち自信がない)、JavaScriptで使うようなコメントアウトが使用可能なので後々大変助かりました。コメントアウト便利。

で、これを書いたら「.textlintrc」としてホームディレクトリに設置します。
といっても、実際にはvimrcやbashrcと同様、ファイル本体はドットファイルを集めるディレクトリに入れて、そこからシンボリックリンクでホームディレクトリへリンクしています。

$ ln -s /path/to/dotfiles/textlintrc ~/.textlintrc

このtextlintrcを設定して何が嬉しいのかというと、たとえばこれを設定せずに上記のルールを使おうとしたら、

$ textlint --rule no-mix-dearu-desumasu --rule max-ten --rule spellcheck-tech-word README.md

というふうに、けっこう壮大な引数をタイプする必要がありますが、textlintrcを設定しておけば、

$ textlint README.md

だけで済みます。(上記二つのサンプルコードは @azu さんのブログより)

これは使わない手はないですね。

感覚的には、vimrcに各種Vimプラグインやそれぞれの設定を記述していくことに近いと感じました。

最初のつまずき

環境設定はそんなところで、ここからは実際のテキストを使ってツールを動かしていきます。

サンプルテキストには、ぼくらの夏目漱石&ぼくらの青空文庫から「草枕」をお借りしました。

山路やまみちを登りながら、こう考えた。
 智ちに働けば角かどが立つ。情じょうに棹さおさせば流される。意地を通とおせば窮屈きゅうくつだ。とかくに人の世は住みにくい。
 住みにくさが高こうじると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟さとった時、詩が生れて、画えが出来る。
(略)

時間の都合上、ブラウザでそのままコピペしただけなのでルビもそのまま残っていますが、サンプルなのでご容赦ください。

全文だとさすがに長すぎるので、冒頭の一部を「kusamakura.txt」というファイルに保存して、さっそく実行。

f:id:note103:20160612202105g:plain

ん……? なんとなく良いようにも見えるけど、最後のほうがなんか変?

これってそもそもこういう表示をするツールだということなのか、それともまだ鋭意開発中のものだから環境によってはちゃんと動かない、ということなのか……? といろいろ考えてしまいました。
これが今回最初のつまずき。

これに対しては、その後「とりあえずlessで見てみるか……」と思いついたのが幸いして。

f:id:note103:20160612202146g:plain

今度は最後まで表示されました。さっきのはやはり途中で切れていたようです。

見たところ、漱石先生は一文あたりの読点がけっこう多いようですね……。

prh を使ってみる | 2度目のつまずき

基本的な動き方は確認できました。ここからは、これをどう応用できるのか、どんなバリエーションを付けていけるのか、といったことを考えていくわけですが、このツールではあまりにもいろんなことが出来そうで、また実際「こんなこともできるよ!」という説明も膨大にあるようで、もはやワクワクするのを通り越してウツになりそう、できれば最小限のことだけをとりあえずできるようになりたい……などと思いつつ、それでもとりあえずこれだけはやってみたい! と思ったのがこちら。

efcl.info

ここで紹介されている @vvakameさんは技術(書)界隈では有名な方で、ぼくも以前にRe:VIEWの開発者カンファレンスに参加した際に発表を拝見しましたが、

※参考: 書籍執筆支援システム「ReVIEW」に触ってみた話(&リンク集) - the code to rock

このtextlint-rule-prhというのは、その@vvakameさんによるproofread-helper(以下「prh」)というツールをtextlintから使うというもの。
github.com

ということで、さっそく案内にあるとおりにあれこれ設定して実行してみます。

f:id:note103:20160612035210g:plain

おぉっと、盛大なエラー……。
メッセージをコピペすると、こんな感じ。

Unhandled rejection Error: Error while loading rule 'prh': ENOENT: no such file or directory, open '/Users/kadomatsuhiroaki/sample/textlint/~/prh.yml'
    at Error (native)
    at Object.fs.openSync (fs.js:634:18)
    at Object.fs.readFileSync (fs.js:502:33)
    at Object.fromYAMLFilePath (/usr/local/lib/node_modules/textlint-rule-prh/node_modules/prh/lib/index.js:9:22)
    at createPrhEngine (/usr/local/lib/node_modules/textlint-rule-prh/lib/prh-rule.js:30:35)
    at reporter (/usr/local/lib/node_modules/textlint-rule-prh/lib/prh-rule.js:79:26)
    (略)

本当はもう少し長いエラーですが、注目すべきはたぶん1行目のこれで。

no such file or directory, open '/Users/kadomatsuhiroaki/sample/textlint/~/prh.yml'

「/Users/kadomatsuhiroaki」というのはぼくのMacのホームディレクトリ。そして「/sample/textlint」というのは今作業をしているディレクトリです。

そのつなぎ目がなんか変ですね。

/textlint/~/prh.yml

これは何かというと、おそらくtextlint-rule-prhの説明を見ながら書いた、textlintrc内の以下の記述が影響しているようです。

    "prh": {
      "rulePaths": [
        "~/prh.yml"
      ]
    },

ぼくとしては、「~/prh.yml」と書くことによって、「ホームディレクトリ直下にprh.ymlというファイルを置いたよ」とコンピューターに指示したつもりでしたが、上のエラーから察するに、そういう意味では受け取られておらず、コードを実行しているカレントディレクトリからの相対パスとして「~/prh.yml」が受け取られているようです。

しかしこの仕様だと、ファイルを実行するディレクトリごとに異なるprh.ymlを設置することになるので、本当にそうなのかなあ? としばらくハマりました。(使い方や理解の仕方が間違っているのかなと)

僕の感覚では、こういう多種多様なファイルを処理するツールの場合、作業場所もコロコロ変わると思うので、そのつどこういう必須データを生成するという発想はなかったのですが、ただあらためて考えてみると、校正ルールというのは文書の内容や方針によって変わるほうが自然とも言えて、毎回まったく同じというのも確かに不便なのかもしれません。

ということで、これについてはtextlintrcを以下のように直し、

    "prh": {
      "rulePaths": [
        "./prh.yml"  //<= 「~」を「.」に
      ]
    },

基本的にはつねに作業するディレクトリにprh.ymlを生成・設置することにしました。(生成方法については後出のtx.shをご参照)

追記: 絶対パスにも対応して頂きました

上記の「~/prh.yml と記述してもホームディレクトリだと認識してもらえない」という件に関して、作者の @azu さんが早々に対応してくださいました。

textlint-rule-prhの v3.1.0以降とのこと。

これにより、自分は基本的にprhの辞書はいつも同じでよい、という場合は絶対パスで以下のようにしておいて、

    "prh": {
      "rulePaths": [
        "~/path/to/prh.yml"
      ]
    },

プロジェクトごとに辞書を管理したい場合はこれまで通り、たとえば以下のようにしておけば大丈夫です。

    "prh": {
      "rulePaths": [
        "./prh.yml"
      ]
    },

なお、後出のコード「tx.sh」では、後者の設定を前提としていますのでご留意ください。

@azu さん、ありがとうございました!!

(追記ここまで)

prh を使ってみる(2) | 辞書を選ぶ

さて、そのprhですが、初期状態では以下のような辞書が用意されています。

prh.yml
review-ignore.yml
target.yml
techbooster.yml
WEB+DB_PRESS.yml

といっても、このすべてが同等に作られているということではなくて、とくに実用的なのは以下の二つのようです。

techbooster.yml
WEB+DB_PRESS.yml

前者の「techbooster」というのは、Androidを初めとする技術系のサークル「TechBooster(テックブースター)」の方針に沿って作られた辞書とのこと。

同サークルの名前は電子書籍の分野でもよく聞きますが、@vvakameさんとも関わりが深いようで、prh的にもこの辞書が一押しだそうです。

WEB+DB PRESS」は言わずと知れた技術誌の雄ですが、同誌編集者の稲尾さんが公開された用語統一ルールなどをもとに作られているようです。

複数の辞書をマージして使うこともできるようですが、素直なぼくとしては(そしてYAML力のないぼくとしては)、ひとまず一押しの「techbooster.yml」を使ってみます。

先の作業ディレクトリに prh.yml という名前でコピーして*1、実行。

$ textlint kusamakura.txt | less

結果。

/Users/kadomatsuhiroaki/sample/textlint/kusamakura.txt
3:57 ✓ error 出来る => できる prh
5:4 ✓ error 事 => こと prh
5:77 ✓ error 出来て => できて prh
6:293 ✓ error 故に => ゆえに prh
6:498 ✓ error ―― => ── spellcheck-tech-word
7:89 ✓ error ―― => ── spellcheck-tech-word
9:141 ✓ error 一つ => ひとつ prh
(略)

ふむ。いい感じにprh名義のエラー(というか指摘)が出てきましたね。だんだん使い方がわかってきた気がします。

結果をテキストファイルに書き出す

ここまでの作業を一旦ふり返ると、まずチェックしたいファイルと、準拠したいルールを用意して、そのチェックを通した「結果」を受け取る、ということまで出来ました。

しかしながら、この段階ではまだ毎回ターミナル上のlessを目視しては「ふむふむ」と確認しているだけなので、あまり実用的ではないですね。

ということで、次はこの出力されたエラーをテキストファイルに書き出してみようと考えました。

ただ、ちょっと冷静に考えてみると、上のような出力結果を受け取ったところで、結局それをまた元の文書とひとつひとつ突き合わせて、どこがどう間違っているのか確認していくという修行のような作業が待っているわけで、それはそれで大変そうです。

一方、textlintのオプション機能として、出力フォーマットを指定することができて、たとえば以下のようにすると……

$ textlint -f pretty-error kusamakura.txt | less

こんな感じに出てきます。

f:id:note103:20160612025026g:plain

ええと、例が悪くて何がいいんだかわかりづらいですが、行の中のどの辺がエラーなのか、というのが(本当は)見やすくなっています。

じゃあ、このフォーマットでテキストファイルに書き出してみようと、素朴なぼくとしては以下のコマンドを叩くわけですが、

$ textlint -f pretty-error kusamakura.txt > pretty-error.txt

こんな感じで出てきます。

[4m/Users/kadomatsuhiroaki/sample/textlint/kusamakura.txt[24m
[90m...[39m
[31m-  住みにくさが高こうじると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟さとった時、詩が生れて、画えが出来る。
[39m[32m+  住みにくさが高こうじると、安い所へ引き越したくなる。どこへ越しても住みにくいと悟さとった時、詩が生れて、画えができる。
[39m[90m 人の世を作ったものは神でもなければ鬼でもない。やはり向う三軒両隣りょうどなりにちらちらするただの人である。ただの人が作った人の世が住みにくいからとて、越す国はあるまい。(略)

おぉぅ……。「[39m[32m」とか「[39m[31m」とかいう文字化け的な何かが出てきました。

ということで、これについてはPerl正規表現を使って整形していくことにしましょう。

追記: [39m[32m などを消す方法

こちらについても、その後 @azu さんから対応方法を教えて頂きました。

なるほど……じつは本記事には書かなかったのですが、この表示についてはけっこう試行錯誤しまして。一生懸命 less の設定をいじったりしていたのですが、まったく直らず諦めたのでした。
textlint コマンドのほうにこういったオプションを付ければ良かったのですね……。

ANSI escape code というものもこの機会に知ることができて大変勉強になりました。
これまたいろいろ検索したのですが、まったく引っかからなかったので……。

上記の方法を併用すれば、以下に出てくるコードもよりシンプルに書けそうですが、記事としてはこのオプションを知らない前提で話を進めていきますので、その旨ご留意ください。

あらためまして @azu さん、ありがとうございました!!

(追記ここまで)

コードを書く

あらためて、この段階で実現したいことを整理すると、

  1. ターミナルでコマンド+ファイル名を打ち込むと、
  2. 通常の結果と、pretty-errorフォーマットで出力された結果がそれぞれテキストファイルに書き出される

という状態を求めています。

そして、それを実現するために書いたシェルスクリプトPerlスクリプトが以下です。

tx.sh
#!/bin/sh

if [ ! -f "$1" ] ; then  # 引数で指定したファイルがカレントディレクトリになければ実行しない
    echo "No $1."
else
    if [ ! -f "prh.yml" ] ; then  # カレントディレクトリにprh.ymlがなければ元の置場からコピーしてくる
        cp /path/to/techbooster.yml ./prh.yml;
    fi

    textlint "$1" > textlint_error.txt  # 通常のtextlintをして結果をtextlint_error.txtに書き出し
    cat textlint_error.txt  # 結果をターミナルにも出力

    textlint -f pretty-error "$1" > textlint_pretty_error.txt  # pretty-errorフォーマットで出力し、一時ファイルに書き出し
    perl /path/to/textlint_pretty_error_tidy.pl textlint_pretty_error.txt > textlint_pretty_error_tidied.txt  # 後出のPerlスクリプトで整形し別ファイルへ書き出し
    rm -f textlint_pretty_error.txt  # 一時ファイルを削除
fi
textlint_pretty_error_tidy.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use File::Slurp;

my $data = read_file($ARGV[0]);  # 一時ファイルを読み込み
my @data = split/\n/, $data;

# 不要な文字列をカット
for (@data) {
    chomp $_;
    $_ =~ s/\\[90m//g;
    $_ =~ s/\\[31m//g;
    $_ =~ s/\\[0m//g;
    $_ =~ s/.*?([^\/]+\.[^\/]+)$/$1/g;  # ファイル名が絶対パスで出てくるのでbasenameのみにする
}

say for @data;

そしてすかさず、上記のシェルスクリプトを「tx」というコマンドで、マシンのどこからでも使えるようにしてみます。

$ chmod a+x tx.sh
$ ln -s /path/to/tx.sh /usr/local/bin/tx

シェルスクリプトでは引数から対象ファイルを受け取るようにしているので、以下のように入力すれば、

$ tx kusamakura.txt

あとはシェルスクリプトPerlと結託してあれこれやった挙句、作業ディレクトリ内に以下のファイルを生成してくれます。

textlint_error.txt
textlint_pretty_error_tidied.txt

Vimから使えるようにする

長くなりましたが、この勢いでもうひと山のぼります。

目当てのファイルを生成するところまで来ましたが、これでも今ひとつ不便です。
というのも、この方法だとtextlintを実行するたびにターミナルに行く必要がありますし、実行後には書き出したファイルをわざわざ開く手間が生じるからです。

では逆に考えろ、ということで、どうなったら満足なのか想像力を働かせてみると、

  1. Vimで文書を編集しているときにコマンド(マッピング)を叩くと、
  2. 上記の2ファイル(エラー結果を書き出したもの)が生成されて、
  3. 元バッファに加えて計3つのバッファが分割画面で表示される。

みたいな状況が良さそうなので、上のシェルスクリプトを活用して以下の関数をvimrcに書いてみました。

function! s:TextLintErrors()
  execute ":! tx %"
  execute ":sp textlint_error.txt"
  execute ":sp textlint_pretty_error_tidied.txt"
endfunction
nnoremap <silent> <Leader>tl :<C-u>call <SID>TextLintErrors()<CR><CR>

実行。

f:id:note103:20160612171920g:plain

できました。

動画だけだとわかりづらいので簡単に説明しますと、

  1. 最初に1画面まるまる「草枕」の状態
  2. 次に実行中の状況が流れていく様子(カクカク動きながら例の文字化けテキストも出てくる)
  3. 最後に画面が3分割された状態(下2つの出力結果をざっとスクロールしながら覗いていく)

という順に進んでいます。

展望とまとめ

ということで、かなり駆け足&欲張り&行き当たりばったりでしたが、自分なりにtextlintに触れた経過を書いてみました。

実質ほぼ1日半ぐらいの間に試したことなので、見落としや根本的な勘違いなどもあるかもしれませんし、実用性の面では、初めのほうで触れたブラウザ拡張などを用いたほうがずっと便利かもしれません。
io-monad.hatenablog.com

それでも、この機会にいろいろと学べたことは多く(とくにほとんど忘れていたシェルスクリプトの書き方とか)、個人的には良い勉強になりました。

今後の目標としては、上で紹介した設定やルールの使い方ではまだ不足が多いので、textlintには他にどんな機能があるのか、そしてそれをどう利用していけるのか、そのチューニングの方法なども含めてもう少し研究してみたいと思っています。

ちなみに、以下を見るとすでに少なからぬルールが揃いつつあるようなので、

この辺の状況も、自分なりに整理・把握していきたいところです。

ざっと触ってみた感触として、textlintは非常にプラガブルな思想にもとづくツールで、使い手が自由にカスタマイズしながら使えることが大きな魅力であると感じました。

こういうツールに対しては、「自然言語ってのはコードの正誤ほど簡単に白黒つけられないんだよな〜」といった否定的な見解が避けがたく出てくるものだと思いますが、そうであるからこそ、「唯一の正解」を設けない、誰もがそれぞれの用途に応じて一から設定していけるこのあり方には共感します。

職業としてこういう道具を必要とする人々(執筆家・編集者・校正者など)に届くまではもうしばらく時間が必要かもしれませんが、様々な専門領域の文書作成者たちが、それぞれのルールを一定の方式に沿って共有空間へ提供し、誰もがそれを使い、また自分なりにカスタマイズし……などという未来がくれば、それは大変喜ばしいことだと思います。

ぼくがその中でどのような役割を果たせるかはわかりませんが、そのような希望を抱いた数日でした。

*1:textlintrcのほうのファイル(辞書)名を書き換えてもいいと思います。

Webページ・ソーシャル文字校正サービスの案

前提

充実したブログ記事を読んでいるときなどに、ちょっとした誤字や「ここ入れ替えたほうが読みやすいんだけどな〜」と思える記述にぶつかることがあります。

いわゆる職業病ということかもしれませんが(編集の仕事をしているので)、これはたんに読んでいる僕がストレスを抱えて終わり(気にしなければOK)という問題というよりは、そこをちょっと直すだけでその記事の寿命も延びるというか、よりいろんな人に伝わりやすくなるだろうという意味で、なんとかスマートな形で教えてあげたいなあ、とそのたび思ったりします。

もちろん、その指摘が執筆者に受け入れられなければそれはそれで良くて、しかしもしそのミスが執筆者の意図しないもので、「直せたら直したい」ものなら、そこのマッチング(指摘したい人とされたい人の)には価値があるだろうと思います。

さて、とはいえそんな些細なミスを、コメント欄やTwitterなどで直接伝えるのもどうなのか、という問題があります。
この場合の一番大きなネックは、いかにおせっかいな人であっても、その目的に比して指摘するまでのコスト(かかる時間・精神的負担など)が高すぎる、ということです。

それなら気軽にはてブで指摘したら? という気もしますが、それはそれでちょっと感じ悪そうというか、そもそもはてブというのは当のWebページ(やブログ)の執筆者とコミュニケーションをとる目的では作られていないので*1、このようなややセンシティブな(人によっては「否定された!」とネガティブに受け取りかねない)やり取りには向いていないとも思えます。

よって、ひとことで言うと、このような欲求に対する解は今のところないですね。

と同時に、それならどんな感じならイイの? ということを書いてみたいと思います。

1. 対象を記録する

Webページの誤植等に気づくわけですから、舞台はブラウザです。
よって、すぐに思い浮かぶ記録方法としてはブックマークレットか、各種ブラウザの拡張機能を使うことが考えられます。

ここでぼくがイメージするのは、たとえばはてなブックマークのそれです。

対象の文字列、あるいは文章を選択し、その状態でボタン(ブックマークレット)をクリックします。あるいは、選択した状態で右クリックをして、その選択肢を選ぶのでもいいと思います。

2. 修正案を書き込む

はてなブックマークGoogle Chrome 拡張だと、ボタンをクリックしたときにコメントを記入する欄が出てきます。
ここでもそのような欄を出して、修正案を書き込めるようにしましょう。

ただし、この段階では「修正案」以外の記述は行いません。
たとえば、本来「リンゴ」と書きたかったのであろう場所が「リゴン」となっていたなら、まず「リゴン」を選択しておいて、修正案の欄には「リンゴ」とだけ書き込みます。

3. 備考を加える

それだけで問題が伝わりそうなら「2」で終わりでも良いですが、ひと言コメントを付け加えたくなることもあるでしょう。
たとえば、「その表現も間違いではないけど、今の業界の標準はこちらだよ」とか。

つまり、修正案を見るだけでは「なぜ」「どこが間違っているのか」が伝わらない可能性もあるので、そういう場合には備考欄で補足します。

よって、「1」で出てくる記入欄には、修正案用と、備考用の2つが必要になります。

4. 閲覧&コミュニケーション場所へ

指摘する側のアクションは「1」〜「3」で完了です。
この後はその内容を使うフェーズに入ります。

これは、はてなブックマークのトップページのようなバリエーションに富んだ感じよりは、Hacker Newsのような超シンプルな構成のほうが良いように感じます。
純粋に直近の投稿から順に、過去へ向かって1カラムで下へ下へ伸びていく感じ。

しかしそれだと、どこに目当てのWebページに関する情報があるかわかりづらいですから、基本的には検索欄を使うようにします。

検索欄に対象となるWebサイトのトップページを打ち込むと、そのサイト内のWebページに関する情報がダララっと出てくるようにします。
で、それをRSSフィードで購読できるようにしましょう。

そうすれば、フィードをフィードリーダーで読んだり、あるいは各種連携サービスを通してSlackやメールなどにプッシュ通知することが可能になります。

5. 問題解消へ

自分のサイト/Webページに関する指摘を見つけたら、それに関する個別ページで指摘内容を吟味したり、コメント欄で掲示板的に意見を交換をできるようにしましょう。

この個別ページは、Stack Overflow みたいな感じがカッコイイかもしれません。

まあ、Vote 機能は不要かもしれませんが、お題となる「現状・修正案・備考」の情報が1セットでページ上部にあり、そこからぶら下がるようにコメントが続いていく、というイメージです。

結論が出たら、当該サイトの管理者はその個別ページをアーカイブに入れることができます。
あ、いやその認証には手間がかかるか……まあ、単に放置しておくだけでもいいかもしれませんが。

まとめ

案の終盤はけっこう雑になりましたが、要点としては「ブックマークレット的なもので最小限の修正要素をクリップし、1つの場所に溜めていける&誰もがそこにアクセスできる」という状況があればいいのになあ、ということでした。

日本語表現のありがちな間違いや、英語の添削など、当事者以外にもそれなりに役立つ場所になるのでは、と夢想しています。

*1:コメントのやり取りができない、文字数が100文字まで、などの制約により。