Dictionaries on Terminal

#本記事での主要な報告
* Macの辞書の実体が見つからない場合がある
* 現状、Swift3.1で辞書の切り替えが不可能な可能性がある
* その場合、検索される辞書は、辞書.app、環境設定のリストで上位順に選ばれるよう


#本記事の欲求
##ターミナルで辞書引きたい
MacOSXは標準で辞書.appが入っています。でも、単語1個調べるためだけに、わざわざ辞書.app、開きたくないですよね？　
ま、確かに、Spotlight検索を使うって手もあります[^1]。でも、あれも面倒臭い。私の環境だとなんか、起動が遅いんですよ、特に起動後の初回。理由はよくわからないですけど。正直、結果が返ってくるまでに2冊紙の辞書を引けます。

で、あれば、既に開いてあるターミナルで辞書を引ければ楽だなぁって。
##Swiftを使いたい
Pythonを用いて、サブロウ丸, [【Mac】ターミナルで辞書を表示](http://inarizuuuushi.hatenablog.com/entry/2016/12/01/080354)のように`pip3 install pyobjc`とすれば、良いようですが、Pythonを使いたくない、というか、せっかくMacだし、手をつけてなかったSwiftをやってみるか、ということで、当記事ではSwiftを用います。
##その他
で、ターミナルから辞書を、と調べると、`open dict:/word`すればいいとかが出てきます[^2]。が、それをやると開きたくない辞書.appを開く羽目になるので、個人的には無しの方向で考えました。
あとは、外部の辞書を使わない、というのも。辞書の導入面倒だし。

#構成
##筆者の環境
* Swift 3.1
* MacBookPro mid2012
* macOS Sierra 10.12.6
* 辞書.app 2.2.1 (194)

##Code
doraTeX, 「[Swiftでシステム内蔵辞書を検索する方法](http://qiita.com/doraTeX/items/9b290f4e39f1e100558b "Swiftでシステム内蔵辞書を検索する方法") 」を参考にしました。
DictSearch.swiftの方に関して、主に参照しています。ただし、いくらか動かない箇所があったので、一部変更を加えました。
コードは2つのファイルに分けました。1つが辞書を検索するクラスを持つファイル。もう1つが、Terminalから値を受け取り、引き渡すファイルです。
分割の必要性には疑問を感じるところではありますが、将来的に機能を付け加える場合には、分かれていた方がいいと思っています。

```swift:DictSearch.swift
import Foundation
internal class DictSearch{
    func lookUp(word : CFString) -> String {
        let range = CFRangeMake(0, (word as String).utf16.count)
        let preResult = DCSCopyTextDefinition(nil, word, range)
        if let result = preResult{
            return result.takeRetainedValue() as String
        }else{
            return  "[NotFound]" as String
        }
    }
}
```

```swift:main.swift
import Foundation
let word=CommandLine.arguments[1]
let search: DictSearch = DictSearch()
print(search.lookUp(word: word as CFString))

```

あとは、これをリリースビルドして、パス通ってるところに投げ、起動すればいいわけです。

##解説
DCSCopyTextDefinitionがコアです。ここで辞書を引いています。
Apple, [DCSCopyTextDefinition(\_:\_:\_:)](https://developer.apple.com/documentation/coreservices/1446842-dcscopytextdefinition)に資料があります。
Declarationをそのまま以下に引用します。

```swift:Swift
func DCSCopyTextDefinition(_ dictionary: DCSDictionary?, 
                         _ textString: CFString, 
                         _ range: CFRange) -> Unmanaged<CFString>?
```

この関数は一見引数を3つ取っていますが、実質は2つ。1番はじめにDCSDictionary型の引数を取っていますが、これが使われていません（for future useだそうです。よくわかりませんが、バグの種って翻訳であっているでしょうか）。それゆえに、NULL投げといて♡って書いてありました。ので、nilが入っています。2番目のCFString型の引数が検索する単語で、3番目が2番目の対象となる範囲（だと思います）です。ここに、値を与えなくてはならないので、let rangeの行があります。

そして、それによって出力された定数こそが、今回、表示させたい辞書による定義です。
定義が辞書中に存在しなかった場合には、ここにnilが飛んでくる形になるので、その判定をし、nilでないときだけtakeRetainedValue()してUnmanagedに対する対処を施し、Stringにキャストして、返します。

main.swiftの方は、それを呼んでるだけですが、`CommandLine.arguments`ってのを使っています。ターミナル上で入力された引数が多分配列的なノリで格納されているほか、[0]のところには自分が存在する場所への絶対パスが入ってるみたいです。調べる気は無いので放置。
そのため、初めに入力された引数にアクセスするためには[1]です。

#現状の問題点
* 辞書の指定ができない（できるけどできない）
* 横断検索はできるが、その結果を選択できない（上とほぼ同じ）
* 遅い（Spotlightよりはマシだが、十分ではない）
* 出力のされ方が汚い

##辞書の指定と横断検索
###辞書の指定がいかにしてできなかったか
現状、辞書の指定はできません。
ただ、これに関しては正直、できるといいかな、程度の問題で、真面目に調べたいなら、机の上にある紙の辞書を引きますし、大きな問題ではないので、気にしてはいません。
doraTeX氏が示された方法は、これの解消を狙ったものだと思われますが、私の環境では2つの理由からできませんでした。
1. 辞書に関する設定情報がない
2. 辞書本体へのパスが見当たらない
まず、1についてですが、

```swift:疑似コード
let userDef=UserDefaults.standard
let globalDomain=userDef.persistentDomainForName("Apple Global Domain")
globalDomain["com.apple.DictionaryServices"]
```

的な感じでやると、辞書に関する設定が見つかるはずなのですが、これがnil。設定を読めないので元の辞書に関する設定の様子はわかりません。

2については、私の環境では、なぜか/Library/Dictionaries/が存在しないのです。辞書自体は引けるのに実体がないです。よくわかりませんが。なにぶん古いPCなので、OSを乗り換えるうちにどこかに取り残されていったのかもしれません。辞書.app内部にも発見できず、Michel Clasquin-Johnson, clasqm's virtual home, [Mac OS X Dictionaries](http://clasquin-johnson.co.za/michel/mac-os-x-dictionaries/index.html)に１つのユーザにのみ辞書を導入する場合に放り込む先として指示されている、~/Library/Dictionariesにもなく[^3]。ただ、後者は主に日本語変換とかの変換辞書を主に入れる先なんでは？　という気もします。ともかく、発見できませんでした。
そのため、辞書名を直接指定して、辞書を切り替えることもできませんでした。

まあ、正直なところ、目的はあくまで、ターミナルで辞書定義を吐かせることなんで、大きな問題ではないです。
###横断検索の様子
検索は、強制的に横断検索になっているようです。そして、辞書.app、環境設定のリストで上位順に検索が進んでいくようです。1番目にウィズダム英和辞典 / ウィズダム和英辞典、2番目にスーパー大辞林、3番目にApple用語辞典を配置すると、
"give"では1つ目
"あげる"では1つ目
"ぬばたま"では2つ目
"Xcode"では3つ目
で引っかかります。
まあ、辞書.appを普段使わないなら、順番を入れ替えても別段問題ないかと思います。

##動作の遅さ
遅いです。具体的には、not foundの場合、カーソルが3回点滅できるぐらいの速度です。検索本体が遅い可能性があるので、どうしようもないかもしれません。できることならカーソル点滅1回以下を目指して要改良です。

##出力が汚い
現状、長い長いStringをそのままprintしてるので、それはそう。要改良。

#まとめ
* ターミナルから辞書を引き、ターミナルに表示させた
* 辞書は指定できない
* 個人的にはまだ遅く感じる

#Appendix
引用は本来URLを付記すべきと思いますが、煩雑なので、リンクで代えさせていただいております。閲覧日は全て投稿日とします。

[^1]: どうでもいいですが、個人的にはCTRL+qがCTRL使うならおすすめです。本当にどうでもいいですけど。記事と関係ないですし。ちなみに、欠点はCTRL+sでとめた画面出力を再開するコマンドと被っている点です。
[^2]: ここのバックスラッシュの数、どうも諸説ありますが、何が違うのかよくわかりません。2つパターンと3つパターンを見かけましたが、私の環境では1つでもうまくいくようでした。
[^3]: ちなみに辞書.appからここへの参照があります。ファイル, "辞書フォルダ"を開くです。