岩本隆史の日記帳(アーカイブ)

はてなダイアリーのサービス終了をうけて移行したものです。更新はしません。

Amazon Product Advertising APIの容赦ない変更点

Amazon Product Advertising APIの最新バージョン以降では、カスタマーレビューの内容が取得できなくなるかもしれません。レビューを表示しているアプリの製作者は、ドキュメントをチェックしたほうがよいと思います。

変更点

Product Advertising APIの2010-09-01バージョンが、昨日付けでリリースされました。

リリースノートにはこうあります。

The reviews request group no longer returns customer review content. Instead, it returns an iframe URL that contains reviews which you can imbed on a web page.*1

私なりにまとめると:

  • Reviewsレスポンスグループを指定しても、返ってくるXMLにカスタマーレビューの内容が含まれなくなる
  • 代わりに、レビュー埋め込み用のiframe URLが含まれるようになる

ということです。

現時点の挙動

2010-09-01バージョンの挙動を確認したところ、まだカスタマーレビューの内容が含まれていました(CustomerReviews/Review)が、いつ消えるかは分かりません2010-11-10に消えるそうです(本記事下部の追記をご参照ください)。

埋め込み用URLは CustomerReviews/IFrameURL に含まれていました。下記は実際の取得例です。

http://www.amazon.jp/reviews/iframe?akid=066ZK100NJ177KN8CF82&asin=4774142042&exp=2010-09-09T11%3A23%3A53Z&linkCode=xm2&tag=chintara-22&v=2&sig=W46Rk6k3Pbkz5s3ISqyqUUpHPJcRCYCZgaZW1X5ZPv0%3D

よく見ると「exp」で1日後が指定されています。

埋め込みURLの有効期限に注意

PDF版のテクニカルドキュメントを調べたところ、下記の記載がありました。

Each iframe URL is valid for 24 hours. If the iframe URL expires, you will receive a 403 Forbidden error code.(p.132)

24時間以内に埋め込みURLを更新しないと、iframeの中身がおかしなことになるということです。容赦ない。

追記(2010-09-08)

本件に関し、「Product Advertising APIのReviewsレスポンスグループを最近ご利用された方」宛に、Amazonから案内メールが来ました。2010-11-09をもって、レビュー内容の返却を終了するとのことです。2010-11-10以降はiframe URLのみの提供となるわけですね。

*1:"The reviews request group" は "The reviews response group" の誤記でしょう

リリースチェッカーをさくらのVPSで試験運用中

リリースチェッカーをさくらのVPSで試験運用中です。もともとWebKeepersのVDSで運用していたのですが、このまま乗り換えることになりそうです。

最近、国内のVPSサービスが充実してきています(参考:VPS比較 SaaSes, さくら, ServersMan@VPS)。

拙作のリリースチェッカーは、WebKeepersのVDSで運用していたのですが、サーバが海外にあることや、月額15ドルと国内VPSより高いことから、国内VPSに乗り換えることにしました。

どのサービスに乗り換えるか迷いつつも、初期費用ありのSaaSesは却下、スワップ領域なしのServersMan@VPSも却下して、さくらのVPSを試用することにしました。

現在リリースチェッカーは、無料期間中のさくらのVPS上で動いています。今のところCPUもメモリも充分で、さくさくです。このまま問題が起きなければ、正式に乗り換えるつもりです。

RubyからHaskellを呼び出したくてHubrisをインストールした

Hubrisという、RubyからHaskellのコードを呼ぶためのブリッジがあります。HaskellパッケージRubyライブラリで構成されており、現在のバージョンは0.0.3です。

とある理由から、このHubrisで遊んでみようと思ったのですが、Haskellパッケージのインストールにかなり手こずったため、ポイントをまとめておきます。ちなみにRubyライブラリのほうは「gem install hubris」で簡単に入りました。

今回まとめる内容は、あくまで僕の環境(CentOS 5)でうまくいったもので、他の環境ではどうだか分かりません。誤った記述があるかもしれませんので、お気づきの際はぜひお知らせください。

Hubrisで遊びたくなった理由については、後日書くつもりです。

ざっくりした流れ

$ wget http://hackage.haskell.org/packages/archive/hubris/0.0.3/hubris-0.0.3.tar.gz
$ tar xvzf hubris-0.0.3.tar.gz
$ cd hubris-0.0.3
$ runhaskell Setup.hs configure

(ここで dist/build/autogen/Includes.hs を編集)

$ cabal install --enable-shared \
>  --extra-include-dirs=/home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/i686-linux \
>  --extra-lib-dirs=/home/iwamot/.rvm/rubies/ruby-1.9.1-p378/lib \
>  --extra-lib-dirs=/home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib

「runhaskell Setup.hs configure」すると、ビルドに必要な dist/build/autogen/Includes.hs が生成されます。ただし、生成された内容のままでビルドすると、Hubrisが正常動作しないため、手を加える必要があります。

cabal installのオプションでは、Ruby 1.8のヘッダファイル格納先と、Ruby 1.8および1.9の共有ライブラリの格納先を渡しています。

dist/build/autogen/Includes.hs の編集

僕の環境*1では、生成された dist/build/autogen/Includes.hs の内容は下記の通りでした。

module Includes where
extraIncludeDirs=["/home/iwamot/.rvm/rubies/ruby-1.9.1-p378/lib/ruby/1.9.1/i686-linux"]

このままでもビルドはできるのですが、extraIncludeDirsで指定されたディレクトリにヘッダファイルが格納されていないため、Hubrisの実行時にエラーが発生してしまいます。

エラー内容を参考に、下記のように編集すると、うまく動くようになりました。

module Includes where
extraIncludeDirs=["/home/iwamot/.rvm/rubies/ruby-1.9.1-p378/include/ruby-1.9.1",
                  "/home/iwamot/.rvm/rubies/ruby-1.9.1-p378/include/ruby-1.9.1/i686-linux"]

cabal installのオプション

cabal install部分を再掲します。

$ cabal install --enable-shared \
>  --extra-include-dirs=/home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib/ruby/1.8/i686-linux \
>  --extra-lib-dirs=/home/iwamot/.rvm/rubies/ruby-1.9.1-p378/lib \
>  --extra-lib-dirs=/home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib

「--extra-include-dirs」は問題ないでしょう。Ruby 1.8のヘッダファイル格納先を渡します。

問題は「--extra-lib-dirs」で、上記のように指定すると、下記ライブラリファイルの参照をインストーラに指示することになります。

  • /home/iwamot/.rvm/rubies/ruby-1.9.1-p378/lib/libruby.so
  • /home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib/libruby1.8.so

注意すべきポイントは2点です。

  • Ruby 1.8および1.9を「--enable-shared」フラグ付きでインストールする必要がある
  • Ruby 1.8の共有ライブラリを「libruby1.8.so」にリネームする必要がある

まっさらなGHC環境がお勧め

HubrisのHaskellパッケージをインストールすると、下記の依存パッケージもインストールされます。

  • MonadCatchIO-mtl
  • ghc-mtl
  • ghc-paths
  • haskell-src
  • hint
  • mtl
  • utf8-string

いずれかが「--enable-shared」なしでインストールされている場合、HubrisのHaskellパッケージのインストールに失敗します。回避するには、事前に削除(cabal unregister)しておくか、「--enable-shared」付きで再インストール(cabal install --reinstall --enable-shared)しておかなければなりません。

僕は当初、Haskell Platformを使っていたのですが、依存関係の縛りで、削除も再インストールもできませんでした。そこでHaskell Platformをアンインストールし、GHC 6.12.3を入れ直し*2、インストール済みのパッケージを全削除しました。

Hubrisを実行する前に

インストールが終わればHubrisを実行できるのですが、事前に確認しておかなければならないことがあります。

共有ライブラリのロード

まず、Ruby 1.8の共有ライブラリをロードしておかなければなりません。

$ echo /home/iwamot/.rvm/rubies/ruby-1.8.7-p302/lib > /etc/ld.so.conf.d/hubris.conf
$ sudo /sbin/ldconfig

Hubris実行時に検索されるライブラリファイル名は「libruby.so.1.8」です。cabal install時の「libruby1.8.so」とは異なるため、注意が必要です。

ディレクトリのアクセス権確認

また、Hubrisを実行すると、「/var/hubris/cache」「/var/hubris/source」ディレクトリが作成され、キャッシュファイルなどが配置されていきます。必要に応じて、ディレクトリへのアクセス権を設定しておかなければなりません。僕は下記のように「/var/hubris」を作りました。

$ sudo mkdir /var/hubris
$ sudo chown iwamot /var/hubris

サンプルの実行

インストールメモは以上で終わりですが、せっかくなのでサンプルを動かしてみましょう。

/home/iwamot/.rvm/gems/ruby-1.9.1-p378/gems/hubris-0.0.3/sample にサンプルスクリプトが配置されています。実は、このサンプルも一部修正が必要でした。

config.ru(一部修正)
use Rack::Reloader, 0
use Rack::ContentLength

require 'pp'
require 'hubris'
class Fibonacci
  hubris :source => 'Fibonacci.hs'
end

def arg_from env
# 岩本修正ここから
 #env['REQUEST_URI'] ? env['REQUEST_URI'].to_s.sub(/^\//, '').to_i : 0
  env['PATH_INFO'] ? env['PATH_INFO'].to_s.sub(/^\//, '').to_i : 0
# ここまで
end

app = proc do |env|
  value = Fibonacci.new.fibonacci( arg_from env )
  [ 200, {'Content-Type' => 'text/plain'}, "The fib number is #{value }" ]
end

run app
Fibonacci.hs(修正なし、コメント行は省略)
module Fibonacci where

import Foreign.C.Types
import Maybe

fibonacci :: Int -> Int
fibonacci n = fibs !! n
  where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
実行
$ cd /home/iwamot/.rvm/gems/ruby-1.9.1-p378/gems/hubris-0.0.3/sample
$ rackup config.ru

http://localhost:8765/10 にアクセスすると、無事「The fib number is 55」と表示されました。

今後の予定

冒頭で書いたとおり、Hubrisで遊びたくなった理由を次回以降の日記に書く予定です。

また、当然ながら、自作のHaskellコードをRubyから呼び出してみようと思っています。「http://www.engineyard.com/blog/2010/a-hint-of-hubris/」が参考になりそうです。

*1:RVMでRuby 1.9.1をインストールしています

*2:インストール手順は「Haskell初心者の僕が試したことをまとめてみた」に書いたとおりです

Haskell Platformを入れた

以前の日記(「Haskell初心者の僕が試したことをまとめてみた」)を書いたときには知らなかったのですが、Haskell Platformという開発プラットフォームがあるんですね。http://www.sampou.org/cgi-bin/w3ml.cgi/haskell-jp/msg/480で知りました。

今のところ僕が使いたいのはGHCとCabalぐらいなので、Haskell Platformに含まれるツール群を見るかぎり、Haskell Platformに移行する必要はなさそうです。

が、GHCのサイトに:

For most users, we recommend installing the Haskell Platform instead of GHC. The current Haskell Platform release includes a recent GHC release as well as some other tools (such as cabal), and a larger set of libraries that are known to work together. This standalone GHC 6.12.3 release is aimed primarily at package maintainers and early adopters.

GHC: Download version 6.12.3

とあったり、たまたま今日新しいバージョンがリリースされたりしたので、勉強がてら手元のCentOS 5に入れることにしました。

GHCのインストール

まず、GHC 6.12.3を入れる必要があります。僕はpacoでパッケージ管理しているので、先に「sudo paco -r ghc」でGHC 6.12.2をアンインストールしました。

その後、GHC 6.12.3をインストールしました。

$ wget http://darcs.haskell.org/download/dist/6.12.3/ghc-6.12.3-i386-unknown-linux-n.tar.bz2
$ tar xvf ghc-6.12.3-i386-unknown-linux-n.tar.bz2
$ cd ghc-6.12.3
$ ./configure
$ sudo paco -D make install

Haskell Platformのインストール

つづいて、Haskell Platform 2010.2.0.0をインストールしました。僕の環境では、先にmesa-libGLU-develとfreeglut-develのインストールが必要でした。

$ sudo yum install mesa-libGLU-devel
$ sudo yum install freeglut-devel
$ wget http://hackage.haskell.org/platform/2010.2.0.0/haskell-platform-2010.2.0.0.tar.gz
$ tar xvzf haskell-platform-2010.2.0.0.tar.gz
$ cd haskell-platform-2010.2.0.0
$ ./configure
$ make
$ sudo paco -D make install
$ cabal update

これだけです。ついでに、日本語出力対応版のHUnitを「cabal install」してみましたが、問題なく動きました。

雑感

Haskell PlatformにはHaddockだのプロファイラだのも含まれています。そのうち使うことになりそうなので、GHC+Cabalの環境には戻さず、このまま行こうと思います。インストールも簡単でしたし。

おまけ

pacoも2.0.9に上げました。手順を忘れそうなのでメモ。

$ sudo paco -r paco
$ wget http://downloads.sourceforge.net/project/paco/paco/2.0.9/paco-2.0.9.tar.bz2?use_mirror=jaist
$ tar xvf paco-2.0.9.tar.bz2
$ cd paco-2.0.9
$ ./configure --disable-gpaco
$ make
$ sudo make install
# make logme

HUnitで日本語を出力してみる→成功

こんなテストケース(test.hs)があるとします。

module Main where

import Test.HUnit

test1 = TestCase (assertEqual "アサーションのラベル" "期待値" "実際の値")
tests = TestList [TestLabel "テストのラベル" test1]

これを走らせると:

Prelude> :load test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> runTestTT tests
Loading package HUnit-1.2.2.1 ... linking ... done.
### Failure in: 0:"\12486\12473\12488\12398\12521\12505\12523"
アサーションのラベル
expected: "\26399\24453\20516"
 but got: "\23455\38555\12398\20516"
Cases: 1  Tried: 1  Errors: 0  Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}

のように表示されてしまいます。「テストのラベル」「期待値」「実際の値」がエスケープされていて、意図通りなのは「アサーションのラベル」だけです。

意図通り表示したいのですが、どうしたものでしょうか。

エスケープの原因

まず、エスケープの原因を探る必要があります。

調べると、下記のリソースが見つかりました。

putStrLnやhPutStrLnではなくprintを日本語文字列の出力に使った場合,文字列が数値を使った表現にエスケープされてしまう点に注意してください。(中略)

エスケープの原因は,printの内部で使われているshow関数にあります。Char型のShowクラスに対するインスタンスでは,文字列をどんな環境でも表示可能にするため,UnicodeでDELよりも後にくる文字をエスケープするよう定義されています(中略)

エスケープされた文字列は,Readクラスのreadメソッドを使うことで元に戻せます。

第39回 一般向けの「Haskell Platform」とインストール・ツールの「cabalコマンド」(5ページ目) | 日経 xTECH(クロステック)

なので、テスト結果出力部分でprint関数なりshow関数なりが呼ばれている可能性が高そうです。

show関数が使われていた

実際にHUnitのコードを追ってみると、予想通りでした。

Test/Text.hs
showPath nodes = foldl1 f (map showNode nodes)
 where f b a = a ++ ":" ++ b
       showNode (ListItem n) = show n
       showNode (Label label) = safe label (show label)
       safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s
Test/Base.hs
assertEqual preface expected actual =
  unless (actual == expected) (assertFailure msg)
 where msg = (if null preface then "" else preface ++ "\n") ++
             "expected: " ++ show expected ++ "\n but got: " ++ show actual

各所でshow関数が使われています。これがエスケープの原因です。

read関数をかます

当該部分にread関数をかまし、ビルド〜インストールし直せば、意図通りに出力できそうです。

実際にやってみます。まず、HUnitのtarボールを展開します。

$ cd ~/.cabal/packages/hackage.haskell.org/HUnit/1.2.2.1
$ tar xzf HUnit-1.2.2.1.tar.gz
$ cd HUnit-1.2.2.1

そして、Test/Text.hsとTest/Base.hsを編集します。diffは下記の通りで、read関数をかましているだけです。

Test/Text.hs
$ diff -u Test/HUnit/Text.hs.org Test/HUnit/Text.hs
--- Test/HUnit/Text.hs.org      2010-07-22 14:09:01.000000000 +0000
+++ Test/HUnit/Text.hs  2010-07-22 14:10:08.000000000 +0000
@@ -113,7 +113,7 @@
 showPath nodes = foldl1 f (map showNode nodes)
  where f b a = a ++ ":" ++ b
        showNode (ListItem n) = show n
-       showNode (Label label) = safe label (show label)
+       showNode (Label label) = safe label (read (show label))
        safe s ss = if ':' `elem` s || "\"" ++ s ++ "\"" /= ss then ss else s

Test/Base.hs
$ diff -u Test/HUnit/Base.hs.org Test/HUnit/Base.hs
--- Test/HUnit/Base.hs.org      2010-07-22 14:01:40.000000000 +0000
+++ Test/HUnit/Base.hs  2010-07-22 14:00:36.000000000 +0000
@@ -73,7 +73,7 @@
 assertEqual preface expected actual =
   unless (actual == expected) (assertFailure msg)
  where msg = (if null preface then "" else preface ++ "\n") ++
-             "expected: " ++ show expected ++ "\n but got: " ++ show actual
+             "expected: " ++ read (show expected) ++ "\n but got: " ++ read (show actual)


 -- Overloaded `assert` Function

read版をインストール

ビルドの準備ができたので、「cabal install」します。

$ cabal install

問題なくインストールできました。

テスト実行

さて、テストを実行します。意図通り表示されるでしょうか。

Prelude> :load test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> runTestTT tests
Loading package HUnit-1.2.2.1 ... linking ... done.
### Failure in: 0:テストのラベル
アサーションのラベル
expected: 期待値
 but got: 実際の値
Cases: 1  Tried: 1  Errors: 0  Failures: 1
Counts {cases = 1, tried = 1, errors = 0, failures = 1}

されました!

雑感

僕の環境(CentOS 5+GHC 6.12.2)では問題なさそうなので、このまま使おうと思っています。

次期リリースのHUnitがどうなるのかとか、何らかの理由でエスケープありのままにしておくほうが無難なのかとか、よく分かっていません。GHC自体どんどん進化しているようなので、この日記の内容もすぐ古くなる可能性があります。

HUnitを使ってみた(ついでにHLintも)

HaskellユニットテストフレームワークであるHUnitを使ってみました。テスト対象のコードは、先日書いたsign関数です。

HUnitのインストール

なにはともあれ、まずはHUnitをインストールしました。Cabalのおかげで簡単に入れられました。

$ cabal install HUnit

テスト対象コードのモジュール名検討

つづいて、テスト対象コードのモジュール名を何にするか検討しました。このモジュールは「キープリスト」というWebアプリケーションで使う予定であり、AmazonAPIに関わるものなので、「KeepList.Amazon」に決めました。

ファイル構成の検討

つづいて、テスト対象コードとテストコードをどのように配置するか検討しました。モジュール名の名前空間の階層をそのまま生かし、テストコードを「tests」ディレクトリに入れることに決めました。

$ tree
.
|-- KeepList
|   `-- Amazon.hs
`-- tests
    `-- KeepList
        `-- Amazon.hs

テスト対象コードの記述

つづいて、テスト対象コード(KeepList/Amazon.hs)を記述しました。といっても、先日書いたコードのモジュール名を「KeepList.Amazon」に変え、main関数を削除し、sign関数の名前を「signature」に変えただけです。

module KeepList.Amazon (signature) where

import Data.ByteString.Lazy.Char8 (pack)
import Data.Digest.Pure.SHA (hmacSha256, bytestringDigest)
import Data.ByteString.Lazy (unpack)
import Codec.Binary.Base64 (encode)

signature :: String -> String -> String
signature secret_key message = encode $ unpack $ bytestringDigest $ hmacSha256 (pack secret_key) (pack message)

テストコードの記述

つづいて、テストコード(tests/KeepList/Amazon.hs)を記述しました。署名の例はAmazon Product Advertising APIのドキュメントから集めました。テストの書き方は「第16回 Haskellでのテストの自動化を考える(2ページ目) | 日経 xTECH(クロステック)」を参考にしました。

module Main where

import Test.HUnit
import KeepList.Amazon

secret_key = "1234567890"

tests = test
  [ "TuM6E5L9u/uNqOX09ET03BXVmHLVFfJIna5cxXuHxiU="
    ~=? signature secret_key ("GET\necs.amazonaws.co.uk\n/onca/xml\nA" ++
                              "WSAccessKeyId=00000000000000000000&Act" ++
                              "or=Johnny%20Depp&AssociateTag=mytag-20" ++
                              "&Operation=ItemSearch&ResponseGroup=It" ++
                              "emAttributes%2COffers%2CImages%2CRevie" ++
                              "ws%2CVariations&SearchIndex=DVD&Servic" ++
                              "e=AWSECommerceService&Sort=salesrank&T" ++
                              "imestamp=2009-01-01T12%3A00%3A00Z&Vers" ++
                              "ion=2009-01-01"),

    "cF3UtjbJb1+xDh387C/EmS1BCtS/Z01taykBCGemvUU="
    ~=? signature secret_key ("GET\necs.amazonaws.com\n/onca/xml\nAWS" ++
                              "AccessKeyId=00000000000000000000&Assoc" ++
                              "iateTag=mytag-20&Item.1.OfferListingId" ++
                              "=j8ejq9wxDfSYWf2OCp6XQGDsVrWhl08GSQ9m5" ++
                              "j%2Be8MS449BN1XGUC3DfU5Zw4nt%2FFBt87cs" ++
                              "pLow1QXzfvZpvzg%3D%3D&Item.1.Quantity=" ++
                              "3&Operation=CartCreate&Service=AWSECom" ++
                              "merceService&Timestamp=2009-01-01T12%3" ++
                              "A00%3A00Z&Version=2009-01-01"),

    "aMFgBNKPrz9PRR9Ato7yanlaG/PkQsNxIWYbLD1V9Zc="
    ~=? signature secret_key ("GET\necs.amazonaws.jp\n/onca/xml\nAWSA" ++
                              "ccessKeyId=00000000000000000000&Associ" ++
                              "ateTag=mytag-20&ListType=WishList&Name" ++
                              "=wu&Operation=ListSearch&Service=AWSEC" ++
                              "ommerceService&Timestamp=2009-01-01T12" ++
                              "%3A00%3A00Z&Version=2009-01-01"),

    "H5u4W10g0vmyB1KA6hmkrea36AFvSryL9SQfPejvWNs="
    ~=? signature secret_key ("GET\necs.amazonaws.com\n/onca/xml\nAWS" ++
                              "AccessKeyId=00000000000000000000&Assoc" ++
                              "iateTag=mytag-20&ListId=34AN6HPUN5AMX&" ++
                              "ListType=WishList&Operation=ListLookup" ++
                              "&ResponseGroup=ListItems%2COffers%2CIm" ++
                              "ages&Service=AWSECommerceService&Times" ++
                              "tamp=2009-01-01T12%3A00%3A00Z&Version=" ++
                              "2009-01-01"),

    "kEXxAIqhh6eBhLhrVMz2gt3ocMaH/OBVPbjvc9TG8ao="
    ~=? signature secret_key ("GET\necs.amazonaws.com\n/onca/xml\nAWS" ++
                              "AccessKeyId=00000000000000000000&Assoc" ++
                              "iateTag=mytag-20&BrowseNodeId=465600&O" ++
                              "peration=BrowseNodeLookup&ResponseGrou" ++
                              "p=BrowseNodeInfo%2CTopSellers%2CNewRel" ++
                              "eases%2CMostWishedFor%2CMostGifted&Ser" ++
                              "vice=AWSECommerceService&Timestamp=200" ++
                              "9-01-01T12%3A00%3A00Z&Version=2009-01-" ++
                              "01"),

    "I2pbqxuS/mZK6Apwz0oLBxJn2wDL5n4kFQhgYWgLM7I="
    ~=? signature secret_key ("GET\necs.amazonaws.com\n/onca/xml\nAWS" ++
                              "AccessKeyId=00000000000000000000&Assoc" ++
                              "iateTag=mytag-20&Condition=New&ItemId=" ++
                              "B0011ZK6PC%2CB000NK8EWI&Merchant=Amazo" ++
                              "n&Operation=SimilarityLookup&ResponseG" ++
                              "roup=Offers%2CItemAttributes&Service=A" ++
                              "WSECommerceService&SimilarityType=Inte" ++
                              "rsection&Timestamp=2009-01-01T12%3A00%" ++
                              "3A00Z&Version=2009-01-01") ]

テストの実行

以上で、テストが実行できるようになりました。実行すると、下記の通り、6ケースとも成功しました。

$ ghci
GHCi, version 6.12.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.
Prelude> :load tests/KeepList/Amazon.hs
[1 of 2] Compiling KeepList.Amazon  ( KeepList/Amazon.hs, interpreted )
[2 of 2] Compiling Main             ( tests/KeepList/Amazon.hs, interpreted )
Ok, modules loaded: Main, KeepList.Amazon.
*Main> runTestTT tests
Loading package HUnit-1.2.2.1 ... linking ... done.
Loading package array-0.3.0.0 ... linking ... done.
Loading package syb-0.1.0.2 ... linking ... done.
Loading package base-3.0.3.2 ... linking ... done.
Loading package bytestring-0.9.1.6 ... linking ... done.
Loading package containers-0.3.0.0 ... linking ... done.
Loading package binary-0.5.0.2 ... linking ... done.
Loading package SHA-1.4.1.1 ... linking ... done.
Loading package dataenc-0.13.0.2 ... linking ... done.
Cases: 6  Tried: 6  Errors: 0  Failures: 0
Counts {cases = 6, tried = 6, errors = 0, failures = 0}
*Main> :q
Leaving GHCi.

HLintによるコードチェック

テストは通ったのですが、いまひとつコードの書き方に自信がないので、「How to write a Haskell program - HaskellWiki」で知ったHLintでコードをチェックすることにしました。

HLintのインストールは下記の手順でできました。

$ cabal install happy
$ cabal install haskell-src-exts
$ cabal install hlint

コードをチェックした結果が下記のものです。

$ hlint KeepList/Amazon.hs
No suggestions
$ hlint tests/KeepList/Amazon.hs
tests/KeepList/Amazon.hs:6:1: Warning: Use camelCase
Found:
  secret_key = ...
Why not:
  secretKey = ...

1 suggestion

テスト対象コードは問題なしでしたが、テストコードには一つ問題が見つかりました。『変数名の「secret_key」だけどさ、キャメルケースの「secretKey」にしたら?』ということですね。

コードは割愛しますが、テストコードを修正し、無事「No suggestions」となりました。じつにありがたいツールです。

今後の展望

今回はテスト対象コードが一つだけだったので、HUnitの「runTestTT」でテストを走らせましたが、数が増えてきたら、この方法では手間がかかって仕方ありません。

第16回 Haskellでのテストの自動化を考える(4ページ目) | 日経 xTECH(クロステック)」で紹介されているような、Cabalによる複数テスト実行が必要になると思っています。

削除できなくなったTwitterの検索メモが削除できるグリモン書いた

書きました。

リアルタイム検索結果が0件になると検索メモが削除(解除)できなくなるんですが、なんでこんな仕様なんでしょうね。

ともあれ、不必要な検索メモが削除できて、僕はすっきりしました。

追記(2010-07-20)

バージョンを1.1.0に上げました。リアルタイム検索結果が0件の場合でも、検索メモが保存できるようになっています。このほうがテストしやすいので。

保存できるし


削除もできる


追記その2(2010-07-20)

何度もすみません。バージョンを1.1.1に上げました。@include に「https://twitter.com/*」を追加しています。

追記その3(2010-07-22)

バージョンを1.2.0に上げました。検索メモを10件以上保存できるようになっています。せっかくなので、スクリプトの名前を「Twitter - Destroy any saved searches」から「Twitter - Better saved searches」に変えました。

ついでに、説明を英語で書いてみました。日本人以外にも役立ちそうだと思ったからです。英語にはまったく自信がないので、間違いがあればご指摘いただけると助かります。