Nimでファイルの文字コードを(外部ライブラリを利用して)判定する

# 概要

Nimのadventカレンダーが空いていたので、穴埋め的な感じで。

Nimで[libguess](http://www.honeyplanet.jp/download.html#libguess)を利用して、ファイルのエンコーディング判定を行ってみます。

libguessはダウンロードするとsoを作成するMakefileが入っていますが、こちらは利用せずにNimからコンパイルする方法を取ります。

## ダウンロード後の解凍状態

```
.
├── Makefile
├── guess.c
├── guess.scm
├── guess_tab.c
├── libguess.h
└── test.c

```

## c2nimで、ヘッダからnimファイルを生成

```
$ c2nim libguess.h                                                                                                                                                                             Hint: operation successful (48 lines compiled; 0 sec total; 516KiB; ) [SuccessX]

$ cat libguess.nim

## 
##  This code is derivative of guess.c of Gauche-0.8.3.
##  The following is the original copyright notice.
## 

.. 省略 ..

##  prototypes

proc guess_jp*(buf: cstring; buflen: cint): cstring
proc guess_tw*(buf: cstring; buflen: cint): cstring
proc guess_cn*(buf: cstring; buflen: cint): cstring
proc guess_kr*(buf: cstring; buflen: cint): cstring                                                                                                                                                                                          

```

## compile および importc ディレクティブを追加する

今回は、Nimでｃソースをコンパイルするので、compileとimportcディレクティブを利用します。

```nim:libguess.nim
{.compile: "guess.c".}

proc guess_jp*(buf: cstring; buflen: cint): cstring {.importc.}
proc guess_tw*(buf: cstring; buflen: cint): cstring {.importc.}
proc guess_cn*(buf: cstring; buflen: cint): cstring {.importc.}
proc guess_kr*(buf: cstring; buflen: cint): cstring {.importc.}
```

## テストコードを作成

libguess.nimをインポートし、guess_jpを呼び出すコードを作成します。
guess_jpの第２引数cintに渡す値をcast関数を使って、変換しています。

```nim:test.nim
import libguess

if isMainModule:
  let text = "あいうえお"
  let textLen:cint = cast[cint](text.len)
  echo guess_jp(text,textLen)
```

実行結果は、こちら

```shell-session
Hint: used config file '~/nim/nim-0.15.2/config/nim.cfg' [Conf]
Hint: system [Processing]
Hint: test [Processing]
Hint: libguess [Processing]
CC: test
Hint:  [Link]
Hint: operation successful (11608 lines compiled; 0.320 sec total; 17.004MiB; Debug Build) [SuccessX]
Hint: ./debug/test.nim.exe [Exec]
UTF-8
```

## ファイルの文字コードを判定する

Nimでファイルを読み出した文字列(というかバイト列)を、guess_jp関数に渡して文字コードを判定します。
バイト配列のポインタを、FileStreamのreadに渡すやり方がわからず、いろいろとネットを駆けずり回ったのは良い思い出。

```nim:test.nim

import libguess
import streams
import parseopt2
import os

# バッファ(8K)を用意します
var buff : array[8192,byte]
# ポインタを取得
var buffPtr = addr buff

proc detect(fileName:string) : string =
  # ファイルをオープンします  
  var fs = newFileStream(fileName,fmRead)
  defer:
    # 終了時にクローズ
    fs.close

  # ファイルをバッファへ読み込む
  let readLength = fs.readData(buffPtr, buff.len)
  # 結果を格納
  result = $guess_jp(cast[cstring](buffPtr),cast[cint](readLength))

# 引数なしの場合のテストコード
proc test(text:string) : string =
  let textLen:cint = cast[cint](text.len)
  result = $guess_jp(text,textLen)

# 開始
if isMainModule:
  if os.paramCount() == 0 :
    echo test("あいうえお")
  else :
    for kind,key,val in getopt() : 
      case kind
      of cmdArgument:
        echo key," = ",detect(key)
      of cmdLongOption,cmdShortOPtion,cmdEnd:
        discard
```

## テストしてみる

iconvでtest.nimをShift_JISおよびEUC-JPに変換しておき、３つのファイルでテストしてみます。

```shell-session
# 変換
$ iconv -f UTF-8 -t SHIFT_JIS test.nim > test.nim.shift_jis   
$ iconv -f UTF-8 -t EUC-JP test.nim > test.nim.euc_jp   

# Let's 判定!!
$ test.nim.exe test.nim test.nim.shift_jis test.nim.euc_jp
test.nim = UTF-8
test.nim.shift_jis = SJIS
test.nim.euc_jp = EUC-JP
```

ちゃんと識別できているようです。

## バイナリファイルを渡してみる

んーと・・・まあいっか

```
$ test.nim.exe /bin/ls /bin/mkdir
/bin/ls = SJIS
/bin/mkdir = SJIS
```

## まとめ

* nimでCのソースをコンパイルし、定義されている関数を呼び出すことができました
* compileおよびimportcディレクティブを利用しました