[Windows][Haskell][emacs]windows環境でのghc-mod設定方法

windows環境でのemacsに対してhaskell-modeとghc-modを適用する方法について。Windows環境だと意外とめんどくさかった。

説明中にある「XXXX」は任意のユーザ名に変換する必要あり。

前提条件

手順

1.「ghc-mod」のインストール

cabal install ghc-mod
cd "C:\Users\XXXX\AppData\Roaming\cabal\ghc-mod-1.11.0"
make

2.「config.ini」の設定
gnupackのPATHにWindowsのパスを通す。*1
これを行わないと、ghc-modの呼び出しが失敗する。

gnupackのディレクトリ直下にある「config.ini」を編集する。

         ↓「%PATH%;」を追加
PATH           = %PATH%;%EMACS_DIR%\bin;%INST_DIR%\app\vim;%INST_DIR%\app\script
PATH           = %PATH%;%INST_DIR%\app\mingw\tdm\bin;%_local_%\bin;%CYGWIN_DIR%\bin
PATH           = %PATH%;%windir%\system32;%windir%;%windir%\system32\Wbem

3.「haskell-mode」のインストール
https://github.com/haskell/haskell-mode からhaskell-modeをダウンロードし、「~/.emacs.d」以下に配置する。

4.「emacs.el」の設定
emacs.elに下記内容を追記する。

;; haskell-mode
(load "~/.emacs.d/haskell-mode/haskell-site-file")
(add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode)
(add-hook 'haskell-mode-hook 'turn-on-haskell-indent)
(add-hook 'haskell-mode-hook 'font-lock-mode)
(add-hook 'haskell-mode-hook 'turn-on-haskell-ghci)

;; ghc-mod
(add-to-list 'exec-path (concat (getenv "HOME") "c:/Users/XXXX/AppData/Roaming/cabal/bin/"))
(add-to-list 'load-path "c:/Users/XXXX/AppData/Roaming/cabal/ghc-mod-1.11.0")
(autoload 'ghc-init "ghc" nil t)
(add-hook 'haskell-mode-hook (lambda () (ghc-init) (flymake-mode)))

結果

こんな感じでちゃんと動く

*1:手抜きしないで、まじめにやる場合はHaskell Platformとかのパスを通す。

faradayライブラリのwarning

Rubyの1.9にてfaradayライブラリを使うとwarningが出て気持ち悪い件の対処方法について。

現象

使うたびにwarningが出て、大変なことになる。
実際に出てくるwarningメッセージ

/opt/lib/ruby/gems/1.9.1/gems/faraday-0.7.4/lib/faraday/utils.rb:128: warning: rto UTF-8 string

対処方法

「faraday/utils.rb」のとある1行を書き換える。

変更前

    def escape(s)
      s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
        '%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
      end
    end

対応後

    def escape(s)                    #↓ここを修正
      s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/u) do
        '%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
      end
    end

gsubの正規表現にて使用している「n」を「u」に変えるだけ。
これでwarningは出なくなる。
対応方法が正しいかはメンドイのでまったく考えていない・・・

レシートをスキャンして日付ごとに分類してみた

ドキュメントスキャナのDR-C125を買ったので、面白い使い道ないかな〜と考えた結果、レシートをスキャンして日付ごとに分類させて見ることにしました。
スキャンさせるだけなら、ものぐさで家計簿つけれない自分でも続くはず。。。
別に合計金額とかも出すわけじゃないので、家計簿用途にはまったく使えないけど、時々昔買ったあの製品いくらだっけ?と思い出せず悩むことがあるので、そういう時には有効かと

ステップ1:スキャン時の設定

適切な設定を探す作業は、分類のためのプログラム書いている時間の数倍かかった。。。
結論としては、

モード
アドバンストテキストエンハンスメントII
明るさ
かなり暗く(50くらい?)
重送検知
超音波検知のみ(長さ検知をしてしまうと、レシートは細長いので誤判定される)
読み取り面
片面(両面にすると、裏に店名のロゴとかがあるレシートの時問題あり)
カラードロップアウト
クレジットカードのレシートとかは紙が青だったり、赤だったりするので読み込む際に適宜設定

な感じ。

ステップ2:プログラム作成

正規表現でパターンマッチングするだけだと思いきや、スキャナの誤検知の尻拭いをしたり、レシートのフォーマットが微妙に違ったりでちょっぴり苦労。

ポイントは

  • 「0」を「o」、「1」を「l」と誤検知することが多い
  • 年は「2011」と「11」の2種類がある
  • 年が省略されて月日のみの場合もある
  • 年月日に使用されるセパレータは3パターン
    • 2011年8月7日
    • 2011/8/7
    • 2011-8-7
  • 月や日が一桁のときは前に0が入ったりは入っていなかったりする
  • ポイントの失効日や広告が日付として入っている場合がある

日付に「2011-8-7」のパターンを認めてしまうと誤検地が一気に上昇する、店の住所が「XX町11-5-6」とかだと完全に区別がつかない・・・
あと何気に「2011/8/7」のパターンもスラッシュが文の中に意外とあるので、変なところが引っかかってしまう。
という訳で信頼度は

  1. 2011年8月7日
  2. 2011/8/7
  3. 2011-8-7

の順となり、この順序で調べていくことになる。

ポイントの失効日や広告の日付は大抵未来の日付なので、プログラムで解析した日付よりも未来の日付を削ってやれば上手くいく。
複数の日付が取れた場合はその中の一番過去の日付を採用してやればいいという考え方もあるが、今回試した限りでは古いほうの日付は誤検知日付であったという例が結構あったので、その対応は行わず。

というわけでプログラム。
今回は、日付の抽出のみで実際にディレクトリごとに仕分けとかは行っていない。

# -*- coding: utf-8 -*-
require 'kconv'
require 'date'

NUM_REG = '[0-9ol]'

def to_num(str)
  str.to_s.tr("ol", "01").to_i
end

def split_date(txt, ym_sep, md_sep)
  ret = []

  txt.scan(/(?:((?:20)?#{NUM_REG}{2})[#{ym_sep}])?(#{NUM_REG}{1,2})[#{md_sep}](#{NUM_REG}{1,2})(?:)?[^\-]/u) do |y, m, d|
    y = 2011 if y==nil
    y = to_num(y)
    y += 2000 if y<100
    m = to_num(m)
    d = to_num(d)

    if Date::exist?(y, m, d) && Date::new(y, m, d) <= Date::today
        ret << "#{y}/#{m}/#{d}"
    end
  end

  ret
end

def read_date(filename)
  ret = []
  date = ""
  IO.popen("xdoc2txt -o=0 -o=1  #{filename}", 'r') do |io|
    data = io.read.toutf8.gsub(/\s/, "")
#    puts data
    ret += split_date(data, '', '')
    ret += split_date(data, '/ノ', '/ノ') if ret==[]
    ret += split_date(data, '\-', '\-')   if ret==[]
  end
  ret.uniq
end

def analyze_date()
  empty_day    = 0
  single_day   = 0
  multiple_day = 0
  Dir.glob('*.pdf').each do |file|
    date_list = read_date(file)

    case date_list.size
    when 0
      empty_day += 1
    when 1
      single_day += 1
    else
      multiple_day += 1
    end

    date_list = date_list.join(" ")
    puts "#{file}:#{date_list}"
  end
  puts "empty_day:#{empty_day} single_day:#{single_day} multiple_day:#{multiple_day}"
end

analyze_date()

ステップ3:解析精度計測

作ったプログラムがどの程度正しく動くか試してみた。
使用データはこの日のためにコツコツと貯めていた124枚のレシートたち。

実行結果
$ ruby pick_out_date.rb
レシート20110807132143.pdf:2011/7/8
レシート20110807132145.pdf:
レシート20110807132148.pdf:2011/7/21
レシート20110807132150.pdf:2011/7/18
レシート20110807132152.pdf:2011/7/18
レシート20110807132155.pdf:2011/7/22
レシート20110807132159.pdf:2011/7/23

(中略)

レシート20110807232317.pdf:2011/1/1 2011/7/31
レシート20110807232321.pdf:2011/1/1 2011/7/31
レシート20110807232322.pdf:2011/7/31
レシート20110807232324.pdf:2011/7/30
レシート20110807232328.pdf:2011/7/30 2011/8/8
レシート20110807232330.pdf:2011/8/7
レシート20110807232332.pdf:2011/7/30
レシート20110807232335.pdf:
レシート20110807233227.pdf:2011/7/31
レシート20110807233229.pdf:2011/7/31
レシート20110807233402.pdf:2011/7/31
レシート20110807233403.pdf:
レシート20110807233611.pdf:2011/7/31
empty_day:28 single_day:89 multiple_day:7
まとめ
日付抽出失敗 23%
日付抽出成功 72%
日付複数抽出 6%

割といい感じで抽出できている。
ただし、日付抽出に成功したもののうち何%が本当に正しい日付かどうかは確認していない。
本当に正しいかどうかを確認すると50%程度になってしまう気がしている。というのもデータを見渡してみると絶対違うだろと突っ込みたくなるのが少なからずあるため。。。

結論

日付による自動分類は出来なくはないが、若干微妙・・・
もう少し頑張れば行けるかも。

TrashMove v0.9.3

ごみ箱のバグをちょっとずつ修正中〜
以下修正点

  • プログラムの実行中にごみ箱の名前を変えるとだめな問題を修正 (あしぃさん不具合報告ありがとうございます。)
  • 起動時の初期化処理をちょいと修正

ダウンロードは配布ページから → http://d.hatena.ne.jp/seroron/20110519

Program_optionsをWindows環境で使用する方法

Boost::Program_optionsをWindows環境で使用する方法について。

Program_optionsをWindowsで使おうとすると2つ問題が起こる。

  1. WinMainにはargc、argvがない
  2. デフォルトでは「/h」とかのスラッシュから始まるオプションは受け付けてくれない。

argc,argvについては「__argc」、「__argv」(「__wargv」)を使うという手段がある。
または、「boost::program_options::split_winmain」というのが用意されているので、それを使用すればよい。

スラッシュについてはパース時のスタイルを変更してやればよい。
具体的にはboost::program_options::command_line_style::allow_slash_for_short」、
「 boost::program_options::command_line_style::allow_long_disguise」を使う。

まとめるとこんな感じ

using namespace boost::program_options;

variables_map vm;

int style = command_line_style::unix_style 
    | command_line_style::allow_long_disguise   // 「-num=10」のようなスタイルを許可
                                                //   (「--num=10」じゃなくてもOKにする)
    | command_line_style::allow_slash_for_short // 「/」によるオプション開始の許可
    ;

std::vector<std::wstring> args = split_winmain(cmdline);
store(wcommand_line_parser(args).style(style).options(cmdline_desc).run(), vm);


ここでは「unix_style」を使用しているが、これだと「-h」とかの引数も受け付けてしまうため、これを禁止したい場合は「unix_style」を使用せず、自分ですべてオプションを定義してやる必要がある。
unix_style」は

unix_style =  (allow_short | short_allow_adjacent | short_allow_next
             | allow_long | long_allow_adjacent | long_allow_next
             | allow_sticky | allow_guessing 
             | allow_dash_for_short)

なので、あとはよしなに・・・

ちなみに、Program_optionsについての記事が検索してもそんなに出てこないんだけど、余り皆使っていないのかな?
自分はこれ結構好きなんだけど。

TrashMove v0.9.2

バグを1件修正しました。これで、動かないといっていた人の何割かは解決するはず。
画面の色数が16bitでは動かないと聞いたとき、なんでやねんと最初は思ったが、よくよく考えてみると確かにそれでは動かないという・・・そんな原因思いつかないよ(ーー;)

報告をしていただいたtmcさん、本当にありがとうございます。

ダウンロードは配布ページから → http://d.hatena.ne.jp/seroron/20110519

時間がほしい

軽い気持ちでアップしたごみ箱が大変なことになっている。。。
なんかニコニコの総合ランキングでデイリー1位になってしまった模様。
目標再生数2000だったんだけどなぁ。
見てくれた皆様本当にありがとうございます。

そしてPC自体に全然触れる時間が取れていないため、色々な問題が起きているようですが対応できず、皆様に多大なご迷惑をおかけしているようです。本当に申し訳ないです。
動かないというコメントがくることは予想していたけど、まさかファイルのダウンロードにも問題が発生するとは・・・