2010-10-02

Cocoa Emacs で ispell が動かない

Cocoa Emacs (23.2 を自前でビルドしたもの) でスペルチェッカ (ispell) を使おうとすると、「ispell がない」と文句を言われる。ググって、これについての情報を見つけた。

この記事によると、Carbon Emacs (22.*) には ispell の代替となる GNU Aspell (のビルトイン版) がふくまれていたが、23.* にはない、と。つまり、23.* では Aspell を別途インストールする必要がある。

↑で対処法として挙げられているのは以下の 3 つ。

  1. Carbon Emacs からコピーしてくる。
  2. MacPorts で Aspell をインストールする。
  3. cocoAspell を利用する。

3. の cocoAspell は Aspell に Mac OS X 向けの拡張を加えたバイナリパッケージらしい。↑の記事の著者もこれを使っているとのこと。

どの方法を採用するか? 1. はいかにもアドホックでやりたくない(白状すると、調べる前はこれで済ませようか、と考えていた)。2. と 3. は好みの問題だな。MacPorts (とソースからビルド & インストール)が好きなら 2. で、「MacPorts って何?」っていうなら 3. で。お手軽なのは 3. だろう。

わたしは MacPorts 好きなので、2. を採用した。

MacPorts で Aspell をインストール

インストールするのは Aspell 本体 (aspell) と英語用の辞書 (aspell-dict-en) の 2 つ。以下が実行結果だ。

[imac] mnbi% sudo port install aspell
--->  Computing dependencies for aspell
--->  Dependencies to be installed: texinfo lzmautils
[...snip...]
--->  Fetching aspell
--->  Attempting to fetch aspell-0.60.6.tar.gz \
      from ftp://ftp.jp.FreeBSD.org/pub/FreeBSD/ports/distfiles/
--->  Verifying checksum(s) for aspell
--->  Extracting aspell
--->  Applying patches to aspell
--->  Configuring aspell
--->  Building aspell
--->  Staging aspell into destroot
--->  Installing aspell @0.60.6_3+nonls
--->  Activating aspell @0.60.6_3+nonls

You must install (at least) one of the language dictionaries after 
installing this port in order for it to work.

--->  Cleaning aspell
[imac] mnbi% hash -r
[imac] mnbi% which aspell
/opt/local/bin/aspell
[imac] mnbi% sudo port install aspell-dict-en
--->  Computing dependencies for aspell-dict-en
--->  Fetching aspell-dict-en
--->  Attempting to fetch aspell6-en-6.0-0.tar.bz2 \
      from ftp://ftp.dti.ad.jp/pub/GNU/aspell/dict/en
--->  Verifying checksum(s) for aspell-dict-en
--->  Extracting aspell-dict-en
--->  Configuring aspell-dict-en
--->  Building aspell-dict-en
--->  Staging aspell-dict-en into destroot
--->  Installing aspell-dict-en @6.0_0
--->  Activating aspell-dict-en @6.0_0
--->  Cleaning aspell-dict-en

Cocoa Emacs で Aspell を使う

上述の記事にあるように、ispell-program-name/opt/local/bin/aspell をセットすれば良い。Emacs から M-x customize-option を実行しても良いし、初期化ファイル(~/.emacs.d/init.el) に以下を追加しても良い。

(Emacs 初期化ファイルより)
 1: (setq ispell-program-name "/opt/local/bin/aspell")

関連リンク

関連記事

twitter より (2010-10-01)

  • 13:38  サービスが立ち上がっても、twitter のクライアントがそれを使ってくれなければ意味ないよな。→ http://japan.cnet.com/news/service/story/0,3800104747,20420807,00.htm
  • 13:44  URL短縮サービスのデメリットについてまとめた情報ソースってないかな。あと標準化の動きってあるのかな?
  • 13:46  頭をしぼってドメイン名やアプリ名、サービス名を考える身にとってはションボリな仕組みだろうけど、便利なことは確かだからな。> URL短縮サービス
  • 18:45  iTunes が 10 になってから曲名やアーティスト名から iTune Store に飛べなくなって不便だと思っていたけど、「Ping」ボタンを「▼」で開くとメニューが出てくるのね。わかりにくよ > アップル
  • 18:46  というか、ボタンを押すとメニューが開くのか。「Ping」。
Powered by twtr2src.

2010-10-01

Google App Engine SDK を動かす Python のバージョン

Python には 2010-10-01 時点で、バージョンの違いによる 2 つの系統が存在する。3 系と 2 系だ。GAE の本番環境では 2 系の 2.5 が使われている。開発環境と本番環境はできる限り同じ環境であることが望ましい。Mac で GAE SDK を使ってウェブアプリを開発するときにも、2.5 を使う方が良い。

実は数日前まで、GAE SDK (Mac 版の実体は「GoogleAppEngineLauncher.app」)には、Python の実行環境も本番と同一バージョンがふくまれていると思っていた。だから、Mac にインストールされているものが何であれ関係ないのだ、と。ところが、GAE Launcher の「Preferences」には Python のパスを指定するフィールドがある。これはつまり、SDK には Python の実行環境はふくまれておらず、Mac にインストールされているものを使う、という意味ではないだろうか?

で、確かめてみることにした。

Snow Leopard で利用可能な Python のバージョン

まず、Mac OS X Snow Leopard で利用可能な Python のバージョンについて整理しておこう。MacPorts を使えば最新版の 3 系もインストールできるが、ここでは標準でインストールされている Python だけを対象とする。確認は Snow Leopard のバージョン 10.6.4 で行った。ただし、そこに Xcode を中心とした開発ツール(実際には iOS SDK)一式がインストールされた状態のものだ。開発ツールが Python を上書きするかどうかはわからない。手元には素のままの Snow Leopard がないため確認できない。

Snow Leopard には Python の実行環境が 2 種類インストールされている。それも、2 系のものが 2 つだ。それぞれのバージョンを確認してみる。

/usr/bin/python

パス名を見ればわかるように、これが Snow Leopard 標準の Python だ。「標準」というのは、MacPorts 等を使っていない状態で、シェルのプロンプトから python として実行したときに起動されるもの、という意味。

[imac] mnbi% which python
/usr/bin/python
[imac] mnbi% python --version
Python 2.6.1
[imac] mnbi% file /usr/bin/python
/usr/bin/python: Mach-O universal binary with 3 architectures
/usr/bin/python (for architecture x86_64): Mach-O 64-bit executable x86_64
/usr/bin/python (for architecture i386): Mach-O executable i386
/usr/bin/python (for architecture ppc7400): Mach-O executable ppc
[imac] mnbi% python
Python 2.6.1 (r261:67515, Feb 11 2010, 00:51:29) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version
'2.6.1 (r261:67515, Feb 11 2010, 00:51:29) \n[GCC 4.2.1 (Apple Inc. build 5646)]'
>>> ^D

バージョンは 2.6.1 で、64 ビット版のバイナリもあることがわかる。PowerPC 用のバイナリがあるのが不思議だ。インタプリタを起動して、sys.version を表示させると、より詳細なバージョン情報を知ることができる。

/usr/bin/python2.5

パス名が変えられていることから、こちらは互換性のために残されているのだろうと推測できる。

[imac] mnbi% which python2.5
/usr/bin/python2.5
[imac] mnbi% python2.5 --version
Python 2.5.4
[imac] mnbi% file /usr/bin/python2.5
/usr/bin/python2.5: Mach-O universal binary with 2 architectures
/usr/bin/python2.5 (for architecture i386): Mach-O executable i386
/usr/bin/python2.5 (for architecture ppc7400): Mach-O executable ppc
[imac] mnbi% python2.5
Python 2.5.4 (r254:67916, Feb 11 2010, 00:50:55) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version
'2.5.4 (r254:67916, Feb 11 2010, 00:50:55) \n[GCC 4.2.1 (Apple Inc. build 5646)]'
>>> ^D

バージョンは 2.5.4 で、64 ビット版のバイナリはない。

GAE SDK を動かす Python はどれ?

GAE SDK (GoogleAppeEngineLauncher.app) をパッケージとして開いて、内容を確認してみると、やはり Python 本体は見当たらない。実際に、SDK 上でコードを動かして確認してみよう。GAE Launcher から「File」>「New Application... 」で新規アプリを作り、main.pyMainHandler を以下のように書き換える。"Hello, world!" の代わりに sys.version を表示させるわけだ。

import sys

class MainHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write(sys.version)

デフォルトの状態で実行させると、Safari に以下の文字列が表示される。

2.6.1 (r261:67515, Feb 11 2010, 00:51:29) [GCC 4.2.1 (Apple Inc. build 5646)]

↑で 標準の Python インタプリタに表示させたものと同一だ。

GAE Launcher の「Preferences」で Python 2.5 を指定してみる。

Google App Engine Launcher の Preferences

設定した後、バージョン表示用のアプリを立ち上げ直し、Safari でリロードすると以下の文字列が表示される。

2.5.4 (r254:67916, Feb 11 2010, 00:50:55) [GCC 4.2.1 (Apple Inc. build 5646)]

結論

  • GAE SDK (GoogleAppEngineLauncher.app) には Python の実行環境はふくまれていない。
  • このため Mac OS X にインストールされた Python 実行環境で SDK は動いている。
  • GAE の本番環境と合わせたいなら Launcher の「Preferences」で Python へのパスを指定する。

標準の Snow Leopard なら /usr/bin/python が使われるはず。MacPorts 等で他のバージョンの Python をインストールしてあり、かつ ~/.MacOSX/environment.plist でログイン環境(の PATH) をいじってあれば、他のバージョンが使われることもある。

2.5 と 2.6 (あるは 2 系最新の 2.7) にどんな差異があるのかは詳しく調べていないのでよくわからない。だから、2.6 を使ったとして GAE ウェブアプリの開発上で不都合があるかどうかもわからない。けれど、GAE の本番環境が 2.5 だと言うのだから、開発環境も 2.5 にしておく方が無難だ。そのためには、Launcher の「Preferences」で /usr/bin/python2.5 を指定する、と。

ブログの記事一覧を表示する」アプリでは、(実行環境のバージョンを気にしていなかったので) Snow Leopard 標準の(つまり、2.6)で動作確認していた。それでも何の不都合もなかったけどね。

関連リンク

関連記事

2010-09-30

既存のプロジェクトを github に公開する

ローカルで(つまり、手元の Mac や PC で)すでに git 管理しているディレクトリを github に公開する方法をメモしておく。単純な手順だし、ヘルプにも詳しく書かれているからそれを見れば良いだけなんだが、いずれ自分視点での記録も重宝するはず。

以前に書いた記事の続きになる。よって、ここでは Github へのユーザ登録はもちろん、SSH 公開鍵の登録も完了しているものとする。

手順

公開までの手順を段階に分けると以下のようになる。

  1. github に新プロジェクトを登録する。
  2. ローカルのリポジトリで、Github のプロジェクトリポジトリをリモートリポジトリとして追加する。
  3. ローカルからリモート(github)に既存のコミットを push する。
1. プロジェクトの登録

github にログインし Dashboard を開くと、右のサイドバーに「New Repository」というボタンが見つかる。これをクリックすると登録の開始となる。

登録画面で指定する情報は 3 つ。「Project Name」、「Description」そして「Homepage URL」だ。大事なのは「Project Name」。ここに入力したものがそのままリポジトリ名(の一部)になる。ここで日本語を指定するとどうなるかはわからない。また、プロジェクト名が早い者勝ちなのかどうかは不明(たぶんそうだろう)。

「Description」と「Homepage URL」は登録後にプロジェクトの個別ページから変更できる。登録には空でも良いようだ(少なくとも Homepage URL を空にしても問題なかった)。

2. リモートリポジトリの追加

手元の git リポジトリに対して、コミットをやったり(push)取ったり(pull)するためのリモートリポジトリを追加する。

git のヘルプ(Creating a repo) にあるように以下のコマンドを実行すれば良い。実際には、foo を github のユーザ名、bar を手順 1. で作ったプロジェクト名で、それぞれ置き換える。

git remote add origin git@github.com:foo/bar.git

このコマンドの意味は、手元の git リポジトリに対して、origin という名前で git@github.com... のリポジトリを参照する、と宣言するものだ。つまり、リポジトリに(短い)別名を付けているのだ(↓参照)。

(「入門git」p.105 〜 106 より)
自分のデフォルトのリモートリポジトリには origin という名前が付いている。これは、何であれクローンしてきたリポジトリのフルネームに対する別名だ。

[...snip...]

自分のリポジトリに origin がなければ、git remote add で、origin を追加することができる。ローカルリポジトリを git init で開始した後、それをリモートリポジトリに送る必要があるときには、この方法が便利だ。

3. 既存のコミットを push

push コマンドは、その名の示す通り、手元のリポジトリに対するコミットをリモートリポジトリに送るものだ。git push A Bで、B のリポジトリに対してなされたコミットを A のリポジトリに送る、という意味になる。引数の順序が少し混乱を招くが(「push A to B」だと思うと逆の意味なる)、これは引数を省略できるという仕様によるものだろう。

(「入門git」p.103 より)
Git はこのコマンドがパラメータなしで呼び出されると、いくつか推測をする。まず、プッシュ先は origin リポジトリだろうと推測する。次に、リポジトリ上にある現在のブランチを、リモートリポジトリ上の対応するブランチ(もしあれば)に送信するのだろうと推測する。

push で指定するのは、まず「どこに送るのか」という情報であり、次に「何を送るのか」を指定する、と覚えれば良い。少し考えれば当たり前のことだとわかる。なぜなら「何を送るのか」は大抵の場合わかりきったことだからだ。だから「どこに送るのか」を指定するのだ。で、リモートリポジトリが 1 つしかなければ、それすらも明白なので省略可能なのだ。

実行結果

以下に、手順 2. 〜 3. を実際に実行している様子を示す。ここでは bloggerglass という GAE 上のアプリのソース一式を登録している。

[imac] mnbi% git remote add origin git@github.com:mnbi/bloggerglass.git
[imac] mnbi% git config --list
[...snip...]
remote.origin.url=git@github.com:mnbi/bloggerglass.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
[imac] mnbi% git push origin master
Identity added: /Users/mnbi/.ssh/github_id_rsa (/Users/mnbi/.ssh/github_id_rsa)
Counting objects: 42, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (42/42), 8.72 KiB, done.
Total 42 (delta 8), reused 0 (delta 0)
To git@github.com:mnbi/bloggerglass.git
 * [new branch]      master -> master

日々のコミット作業

リモートリポジトリを作ったからといって、ローカルで行う日常の作業はほとんど変化しない。唯一違うのはローカルリポジトリにコミットした後、リモートリポジトリへ push することが増えたこと。

先に例として挙げた bloggerglass では、今のところリモートリポジトリは 1 つで、ブランチも作っていないから、引数なしで push コマンドを実行するだけで良い。

以下に、push コマンドの実行例を示す。

[imac] mnbi% git push
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 425 bytes, done.
Total 5 (delta 4), reused 0 (delta 0)
To git@github.com:mnbi/bloggerglass.git
   6057da1..7f9add6  master -> master

bloggerglassでは、すでに typo の修正やデバッグ用のコードの除去などで(...orz)、数回コミットを push している。↑の実行例もその 1 つ。

参考文献

入門git
Travis Swicegood
オーム社 ( 2009-08-12 )
ISBN: 9784274067679
おすすめ度:アマゾンおすすめ度

関連リンク

関連記事

twitter より (2010-09-29)

  • 16:24  なんか両隣りで合わせて3台もMacを使ってる。MacBook 1台にMacBook Pro 2台。アップルストア以外で一度にこんなにMacを見るのは初めてかも。
Powered by twtr2src.

2010-09-29

Python で書いたフィルタを GAE に載せる #3

まず最初に、今回の記事で書こうと思っていることを並べてみる。

  1. ウェブアプリのテンプレートについて考えたこと
  2. 全体をフィルタ(っぽい構造)にこだわらずに書き直したこと
  3. Blogger Glass を github にプロジェクトとして登録したこと

そんなわけで、今回が「Python で書いたフィルタを GAE に載せる」シリーズの完結編。といっても、できあがったモノはようやくプロトタイプのレベルなんだけどね。

ウェブアプリのテンプレート

前回、GAE のテンプレートの仕組み (Django) では複雑なことはできないと書いた。後になって改めて考えてみたところ、これは見当違いな思い込みだと気付いた。テンプレート(というか HTML)も「何か」を記述するための言語であることは間違いないし、見方によってはプログラミング言語だと言えなくもない。しかし、その記述の仕方は手続きを積み重ねて行く Ruby や Python とは別物になる。

Python でプログラミングするなら「あれして、それして、その後こうする」と考えるし、そのようにコードに書く。言わば動的(ダイナミック)な思考方式だ。思考のかけらが時系列に並ぶようなイメージ。一方、HTML でページを作るときは「あれは○○、それは△△、そしてこれは□□」というように考えて、要素を配置する。こちらは静的(スタティック)な思考と言える。言葉を変えれば、HTML でページを作るときに「h1 の内容を、40 pt で出力してから、その後 h2 の内容を 24pt で右揃えで出力する。それが完了したら h3 の内容を 20 pt で左揃えで出力する」などとは考えない、ということだ。

テンプレートは、(汎用プログラミング言語で)プログラムを書くように作るものではない。テンプレートにループや条件分岐があるのは、そこに配置するオブジェクトを出力するための補助として、だ。

また、前回の「テンプレートとコードが密に絡んでしまっている」という評価は、リクエストハンドラからテンプレートを呼び出すと考えてしまったことでできた悪いデザイン(設計)に対するものだ。これは、本来、リクエストハンドラが「表示用データをくるんだオブジェクト」をテンプレートにわたす、と考えるべきだった。これなら、リクエストハンドラとテンプレートは(データオブジェクトを間に挟んで)分離していると言える。

言い換えると、リクエストを処理して結果のデータを用意するコードと(バックエンド層)、ユーザの目に見えるテンプレートから生成される HTML (プレゼンテーション層)を直接つなげるから(あるいは、つながっていると考えるから)ダメ(悪いデザイン)になる。ここは、両層は独立したものだと考えて、間をつなぐ「何か」をデザイン(設計)すべきなのだ。一般的には、この「何か」をプロトコルと呼ぶ。

多層構造にして、層間のプロトコルを定義する、と書くと大袈裟だけど、要はコード(バックエンド層)を考えるときにはテンプレートの構造ではなくて、処理結果の情報が何でそれをどうまとめるかを考える。次にテンプレート(プレゼンテーション層)を考えるときには、バックエンドで何が起こっているかは気にせず、下層から上がってくるデータ(をくるんだオブジェクト)を表示することに集中する。

こう考えてくると、フィルタ構造の中で HTML のリストを生成していたのはウェブアプリには適していないとわかる。フィードのタイトルとリンクをどう表示するかは、バックエンド層で決めることではないからだ。それはテンプレート(プレゼンテーション層)で扱う問題だ。順序つきリストにするかもしれないし、順序なしのリストにするかもしれない。あるいは、テーブルで表現するかもしれないし、div で囲むだけかもしれない。バックエンドはただ表示すべきデータを集めて上層に送るだけで良い。

フィルタ構造が足枷になっている

プログラムを、とくに入力を処理して出力に変えるようなモノを、いくつかの単純なフィルタの組み合わせとして実現することは、コーディング/テスト/デバッグのいずれの作業も簡単にしてくれる。しかし、ここで肝心なことはフィルタ(つまり標準入出力で互いにつながるというアーキテクチャ)そのものではなく、複雑な処理を独立した小さな処理に分割できること、そして分割した処理ごとに動作確認ができることにある。

ウェブアプリでは、(バックエンドの)処理を分割して、個別に動作確認するような手法は取りにくい、というか取れない。というのも出力をそのまま入力につなげることができないからだ。途中結果をテキストとして出力してブラウザの画面で確認することはできても、それを次の処理の入力にはできない。

つまり、フィルタとして実現したプログラムをウェブアプリに変える際に、元の構造にこだわることは足枷になる。すっぱりとフィルタのことは忘れてしまう方が良い。構造は忘れて、処理の中身だけを移すようにすれば良い。例の「ブログの記事一覧を作るプログラム」なら、フィードの取得や、そこから正規表現で記事のタイトルと URL を抽出する部分はそのまま使える。

今回の書き直しでは、Blogger のフィード自体をデータオブジェクトとして捉え直した。AtomFeed というクラスがそれだ。このクラスのインスタンスに対して、get メソッドをページ番号を引数として呼び出せば、Blogger からフィードを取得し、それを Atom としてパースし、内部リストに Entry クラスのオブジェクトとして蓄える。Entry クラスはフィードにふくまれる entry 要素(のデータ)を保持するためのもので、今のプロトタイプ版ではタイトルと URL だけを収めている。そして、この Entry クラスのオブジェクトこそ、バックエンド層(リクエストハンドラ)とプレゼンテーション層(テンプレート)をつなぐプロトコルになっている。つまり、リクエストハンドラは AtomFeed をインスタンス化し、リクエストされたページを指定して get を呼び出せば、後は Entry オブジェクトのリスト(AtomFeed オブジェクトの entries プロパティになっている)をテンプレートにわたせば良いことになる。(→ bloggerglass/src/feed/core.py)

テンプレートの方では、リクエストハンドラから渡されてきた Entry オブジェクトのリストから単純なループを使ってオブジェクトを取り出し、さらにその title プロパティと url プロパティの値から、順序つきリストを生成している。(→ bloggerglass/src/page.html)

ついに公開……そして今後の展望

GAE への配備はまだだが、先に github にプロジェクトとして登録した(関連リンク参照)。これが、初めての github プロジェクトなので、公開にあたっては多少の右往左往があったりもしたが、それは別途書く予定だ。

最初にも書いたが、今の段階ではまだアプリのプロトタイプにすぎない。「アプリのかけら」が少し成長したと言った程度だ。元々の目的である「Blogger で作ったブログを Blogger とは独立した表示システムで見る」を実現するには、まだ作業が必要だ。

今後は、まず iPhone/iPad での表示に最適化する。これは主に HTML (テンプレート)と CSS をいじる作業になる。その後は、個別の記事を Blogger Glass 内で表示する機能を追加する。記事のデータそのものはフィードとして取得しているのだから、後はそれを流し込むテンプレートを用意すれば良い。ただ、記事内に張ったブログ内の他記事へのリンクをどうするかが課題だ。これは Blogger Glass 内の記事ページへのリンクに付け替える等の処理が必要になる。ここまでできたら、Blogger Glass としては ver. 1.0.0 としても良いかと考えている。

関連リンク

関連記事

2010-09-28

Python で書いたフィルタを GAE に載せる #2

前回で、おおよそのデザイン(意匠と設計)ができた。今回は、コードを書き、GAE の SDK 上で動かしてみることが目標だ。

アプリ設定

初期状態と違うのは、スタイルシート用のディレクトリを静的コンテンツとして追加している点だ。

(app.yaml より)
 1: application: bloggerglass
 2: version: 1
 3: runtime: python
 4: api_version: 1
 5: 
 6: handlers:
 7: - url: /stylesheets
 8:   static_dir: stylesheets
 9: 
10: - url: .*
11:   script: main.py

リクエストハンドラ

PageFeedExtractTitles それに MakeList が前回までのフィルタを書き直したクラスだ。そのフィルタを適用している部分が 5 〜 8 行目になる。ついでに、全記事数もフィルタの 1 つ(フィードを取得するフィルタ)から取り出している。

(main.py より)
 1: class MainHandler(webapp.RequestHandler):
 2:     def retrieve(self, page):
 3:         pf = PageFeed(page)
 4:         filters = [pf, ExtractTitles(), MakeList()]
 5:         pipe = []
 6:         for f in filters:
 7:             f.input(pipe)
 8:             pipe = f.output()
 9:         self.total_posts = pf.total_posts()
10:         return pipe
11: 

以下が、リクエストハンドラの本体だ。フィードの取得から HTML のリストとしての整形までは上記の retrieve で完了する。ここでは HTML ページを作るための、その他の情報をまとめて、テンプレートにわたしている。

(main.py より)
12:     def get(self):
13:         stylesheet = "/stylesheets/default.css"
14:         total_pages = 10
15:         request_page = int(self.request.get("page", default_value="0"))
16: 
17:         lines = self.retrieve(request_page)
18:         total_pages = self.total_posts // PAGESIZE + 1
19:         pages = []
20:         for p in range(total_pages):
21:             page = Page()
22:             page.number = p
23:             if p == request_page:
24:                 page.current = True
25:             pages.append(page)
26: 
27:         template_values = {
28:             'lang': "ja",
29:             'title': "Blogger Glass [LOG+REPO]",
30:             'stylesheet': stylesheet,
31:             'total_pages': total_pages,
32:             'current_page': request_page,
33:             'lines': lines,
34:             'pages': pages,
35:             'home_url': "http://bloggerglass.appspot.com/",
36:             }
37: 
38:         path = os.path.join(os.path.dirname(__file__), 'page.html')
39:         self.response.out.write(template.render(path, template_values))

テンプレート

リクエストハンドラが使用するテンプレートを以下に示す。

特徴は、(a) 記事一覧を作るループ(17 〜 19 行目)、(b) ページ切り替えのためのリンクを作るループ(26 〜 32 行目) の 2 箇所だ。後者では、ループの中で条件判断を行い生成する内容を切り替えている。

(page.html)
 1: <!DOCTYPE HTML>
 2: <html lang='{{ lang }}'>
 3: <head>
 4: <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
 5: <title>{{ title }}</title>
 6: <link type='text/css' rel='stylesheet' href="{{ stylesheet }}" />
 7: </head>
 8: <body>
 9: <header>
10: <h1>{{ title }}</h1>
11: <h2>Page #{{ current_page }}</h2>
12: <p>in {{ total_pages }} pages</p>
13: </header>
14: <section>
15: 
16: <div id="content">
17: {% for line in lines %}
18:   {{ line }}
19: {% endfor %}
20: </div>
21: </section>
22: 
23: <nav>
24: <div class="pager">
25:   <span>Gr</span>
26:   {% for page in pages %}
27:     {% if page.current %}
28:       <span class="current-page">a</span>
29:     {% else %}
30:       <span class="other-page"><a href="/?page={{ page.number }}">a</a></span>
31:     {% endif %}
32:   {% endfor %}
33:   <span>ss</span>
34: </div>
35: </nav>
36: 
37: <footer>
38: <div><a href="{{ home_url }}">Blogger Grass</a></div>
39: </footer>
40: </body>
41: </html>

GAE にふくまれているテンプレートシステムは Django と呼ばれるものだ。一見、{% ... %} の中には Python のコードがそのまま書けるように思えるのだが、実際には Python 風の独自の文法のようだ。ループや条件判断も書けるが、かなり制限が厳しい。その分、テンプレートを使うコードの方で工夫が必要になる。

上述のリクエストハンドラの 20 〜 25 行目は、テンプレートの 26 〜 32 行目の記述のための「工夫」だ。Django のテンプレートでは、条件判断は変数の値による真偽判定しかできない。変数の値を数値や文字列と比較するというようなことができないのだ。ここでは、現在のページと他のページで生成する内容を変えるため、ページ番号と現在のページか否かのフラグをオブジェクトにくるんで、ページ数分用意してリストに詰めるということをやっている。

Django のテンプレートは複雑な記述には向かない。今回のページ切り替え部分の記述程度でも、テンプレートとそれを使うコードが密に絡んでしまっている。これは将来の変更(デバッグやら拡張やら)の足枷になるにちがいない。

プロトタイプは動くことが大事

フィルタをもとに書き直した部分もふくめて、とりあえず動くものはできた。ただ、フィルタからの書き直し方や、ハンドラ内での情報の保持の仕組みが、どうにも気に入らない。また、Python のこと、GAE のこと、知らないことが多くて、手探り状態での作業になった。動かすので精一杯になっていた。もう少しプラットフォームに慣れないと、見通しの良い設計はできないな。

とはいえ、プロトタイプは動くことが肝心。作る過程、動かそうとする過程(デバッグとも言う)、そして動かすことで課題を見つけることがプロトタイプの役割りだ。

次回は、全体を見直しつつ、リファクタリングしてみよう。

関連リンク

関連記事

twitter より (2010-09-27)

Powered by twtr2src.

2010-09-27

Python で書いたフィルタを GAE に載せる #1

Blogger で作ったブログの記事一覧を作成するプログラムを、まず Ruby で書き、次にそれを単純なフィルタをつなげたフィルタとして書き直し、さらにそれを Python で書き直した。すべてはこのプログラムを Google App Engine (以下、GAE) に載せるためにやったことだ。

Python はまだ勉強を始めたばかりなので、いまはまだ、たとえ短いプログラムだとしてもいきなり Python で書き始めることは難しい。まずは Ruby で書いて動くようにした上で、それを Python に書き直す、という段階を踏む必要があったのだ。

今回は、まずウェブアプリとしての概要を考えてみる。アプリ名は Blogger Glass になる予定(アプリ ID は bloggerglass)。予定というかすでに GAE の管理コンソールでアプリを作ってある。アプリ ID は先に決めておかないとメンドウだからね。

アプリのデザイン(意匠と設計)

外観

最初の版では iPhone や iPad 等のデバイスのことは考慮しない。まずは、Mac(や PC)で動くものに仕上げる。

外観はごくシンプル。ただ記事一覧のリストが出てくるだけ。Google の検索結果のような感じにしたい。というより、まんま Google をパクってみよう。つまり、(a) 1 ページに表示する項目数は制限しておき、(b) 記事数がそれを超えるなら複数ページに分割し、(c) 各ページの下部には後続ページへのリンクを置く。

Google に怒られたら、よくある「[1][2][3]...」という数字によるリンクに変えよう(いやまあ怒られなくても最終的には変えるつもりだ)。

iPhone/iPad 用では「Prev」「Next」ボタンによるナビゲーションになる。その場合、いま何ページが表示されているかをどうやってユーザにフィードバックするか。それは作ってみてから考えるよう。

リクエストの形式

いまのところアプリを構成する画面は 1 種類しかない。違いはリストが分割されている場合、リストのどのページを表示するか、という点だ。よって、リクエストの形式は 1 つで良いが、パラメータで表示するページを指定できなければならない。

http://bloggerglass.appspot.com/?page=10

リクエストに埋め込まれたパラメータは、GAE のリクエストハンドラでは以下のようにして読み取ることになる。上のリクエストでこのハンドラが呼び出されると、page には 10 が代入される。また、default_value を指定してあるので、パラメータなしでリクエストされたときは page には 0 が代入される(ページは 0 から数えることにする)。

(リクエストハンドラのサンプル)
 1: class MainHandler(webapp.RequestHandler):
 2:     def get(self):
 3:         page = int(self.request.get("page", default_value="0"))
ストレージは使わない方向でやってみる

前回の記事でも書いたように、最初のバージョン(プロトタイプというべきか)では取得したフィードのデータは使い捨てる。このため、リクエストごとに Blogger からフィードを取得することになる。さすがに毎回全記事分のデータを取得するのは無駄なので、ページ当たりの項目数を Blogger のフィードにデフォルトでふくまれている記事数(= 25)に一致させる。こうすれば、Blogger へのリクエスト時に start-index として page * 25 + 1 を指定すれば、ちょうど必要な数の記事データを取得できる。

次回は?

デザイン(意匠と設計)はこれぐらいにして、次回はコードを書き始めよう。フィードの取得から HTML を生成する部分はすでにできているのだから、あとはページ自体の生成とページ間遷移の仕組みだけだ。まずはページを作るところから。GAE 標準で使えるテンプレートを使って作ってみたい。この程度のアプリには大袈裟だけど、何事も練習だからな。

参考文献

Programming Google App Engine
Dan Sanderson
Oreilly & Associates Inc ( 2009-11-15 )
ISBN: 9780596522728

買ったのはこれの eBook 版。それも iPhone 用のアプリとして販売されているもの。アプリ版から ePub ファイルを取り出す方法はこちら(→ たった600円でオライリー本をiPadやKindleで読む。すてき。)を参照のこと。

関連リンク

関連記事

twitter より (2010-09-26)

  • 12:07  RT @deltam: 「コードがドキュメントです(キリッ」ではなくて「コードがドキュメントかつドキュメントがコードです(キリリッ」。英語が母国語の人がJavaなどのソースコードを読むときの感覚と日本人が読むときの感覚はだいぶ違ってると思う。
  • 13:15  フリスク、買うのも久しぶり。ピンクグレープフルーツって言ってもあんまり変わらん。ひたすらミントだわ。
  • 13:23  RT @masui: gitの入門記事で 'git ls-files' コマンドが解説されてるのを見ないのは何故だろう。 現在どのファイルがバージョン管理されてるか知りたいときはどうするのが普通なのか?
  • 17:04  コン猿スゲェ、というのはおいといて。これも立派な計算機だ。これがオモチャ(もともと玩具だってことだけど)に見えてしまうのは、われわれが「コンピュータ==プログラマブルな機械」だと思っているから。RT @masui: コン猿天才だな http://bit.ly/d7Er0A
  • 17:08  「プログラマブルな機械」というのは、比較的簡単にプログラムを変更できる機械、という意味。何が言いたいかっていうと、「プログラムできる機械ってスゲェよ」ってこと。だから、みんなプログラマになろうぜ。
Powered by twtr2src.

2010-09-26

Ruby で書いたフィルタを Python で書き直す #2

今回は、前回、後回しにした Atom 形式のフィードをパースする部分を Python で書いてみる。それには Python (2.5/2.6) が標準で備える SAX ライブラリを使う。そんなわけで、まずは「SAX って何(・д・)?」という疑問から始めよう。

SAX とは

Simple API for XML の略で、DOM と並んで XML 文書を扱うための API として広く利用されている。

(「SAXによるXML文書の操作」より)
SAXでは、DOMのようにXML文書をまるごとメモリに読み込んだあと処理するのではなく、XML文書の先頭から一行ずつ順番に処理をして行く。そのため、どんなに大きなXML文書を処理するときでも、メモリの使用量はそれほど負担にならず、処理も一般に高速だという利点がある。

DOM が言わば、XML 文書をメモリ上の構造に写し取る方法なのに対して、SAX は XML 文書を読み取りつつ(アプリケーションプログラムにとって)必要な部分だけを抽出して扱う方法だ。XML 文書の作成、編集のようなプログラムにとっては DOM が適している。一方、今回のフィルタのようなタイプのプログラムには、まさに SAX がピッタリとはまる。

SAX パーサの動作

以下に示すプログラムに XML データを入力すると、SAX のパーサの動作する様子が見てとれる。

(dummyHandler.py)
 1: #!/usr/bin/python
 2: # -*- coding: utf-8 -*-
 3: # dummyHandler.py: test the SAX parser's behavior.
 4: 
 5: from sys import stdin
 6: import xml.sax
 7: import xml.sax.handler
 8: 
 9: class DummyHandler(xml.sax.handler.ContentHandler):
10:     def startElement(self, name, attributes):
11:         print "start parse: %s" % name
12:         if name == "link":
13:             print "    rel = %s" % attributes['rel']
14: 
15:     def characters(self, data):
16:         print "start characters"
17: 
18:     def endElement(self, name):
19:         print "  end parse: %s" % name
20: 
21: parser = xml.sax.make_parser()
22: handler = DummyHandler()
23: parser.setContentHandler(handler)
24: parser.parse(stdin)

SAX によるパーサを利用するためには、アプリケーション側で ContentHandler のサブクラスを実装しなければならない。それを xml.sax.make_parser で作られるパーサにセットしてやると、パーサがデータを読み取る中で ContentHandler (のサブクラス)のメソッドが呼び出される。

つまり、SAX パーサは、XML データを読みつつ、要素の開始タグを見つけたらセットされたハンドラの startElement を呼び、要素の終了を検出したら endElement を呼ぶ、という動作をする。この他にも startDocumentcharactersprocessingInstruction なども ContentHandler のメソッドとして定義されており、必要ならこれらもハンドラで上書きする。

ちなみにこの DummyHandler は、ググって見つけた資料(→ Chapter 1: Python and XML) に載っていた例を真似て書いた。

Atom Syndication Format

続いて、扱うことになるデータの書式についても簡単に触れておこう。

Blogger のフィードは、特に指定しなければ Atom 形式になる。正確には「Atom 配信フォーマット」と呼ばれるものだ(→ Atom 参照)。

以下に Atom 形式のフィードのサンプルを示す。このブログのフィードから一部を切り出し、単純化したものだ(長い行はバックスラッシュで折り返している)。

(Atom 形式のフィードサンプル)
 1: <?xml version='1.0' encoding='UTF-8'?>
 2: <feed xmlns='http://www.w3.org/2005/Atom'>
 3:   <updated>2010-09-24T21:07:09.324+09:00</updated>
 4:   <title type='text'>lifeLOG + REPOsitory</title>
 5:   <author>
 6:     <name>mnbi</name>
 7:   </author>
 8:   <entry>
 9:     <published>2010-09-24T21:07:00.000+09:00</published>
10:     <updated>2010-09-24T21:07:09.333+09:00</updated>
11:     <category scheme='http://www.blogger.com/atom/ns#' \
          term='2. アプリの断片'/>
12:     <title type='text'>パイプとフィルタで複数のデータの流れを扱うには?</title>
13:     <content type='html'>
14:       前回示したパイプラインは単一のデータの流れを持つシンプルなフィ \
          ルタをつなげたものだった。本来の意味でのフィルタはこういうも \
          のだ。今回はこれを拡張して、最終結果の HTML を複数のファイル \
          に分割するプログラムを作る。各ページにはブログ記事のタイトル \
          (とリンク)が一定数ふくまれる。
15:     </content>
16:     <link rel='alternate' type='text/html' \
          href='http://logrepo.blogspot.com/2010/09/blog-post_24.html' \
          title='パイプとフィルタで複数のデータの流れを扱うには?'/>
17:     <author>
18:       <name>mnbi</name>
19:     </author>
20:   </entry>
21: </feed>

今回の題材となっているフィルタでは、このデータのうち 8 〜 20 行目の entry 要素だけが処理の対象だ。しかも、必要な情報はタイトルとブログ記事への URL のみだから、12 行目の title 要素と 16 行目の link 要素を抽出できれば十分。さらに言えば、記事のタイトルは、記事 URL を href 属性持つ link 要素の title 属性にもなっているから、この link だけを取り出せば事足りる。

Python による実装 (残り)

特定の要素を抽出する

↑で書いたように、link 要素だけを抽出しても目的は果たせるのだけど、今回は SAX を使う練習にもなるので、entry 要素および、そこに包含されている title 要素、link 要素を抽出してみる。

以下が、Python で書いた Atom 形式のフィードから記事のタイトルと URL を抽出するフィルタのコードになる。

(titles_atom.py)
 1: #!/usr/bin/python
 2: # -*- coding: utf-8 -*-
 3: # titles_atom.py: get post tiltes from Blogger Atom feed.
 4: 
 5: from sys import stdin, stdout
 6: import xml.sax
 7: import xml.sax.handler
 8: 
 9: class FeedHandler(xml.sax.handler.ContentHandler):
10:     def __init__(self):
11:         self.entries = []
12:         self.title = ""
13:         self.url = ""
14:         self.inTitle = False
15: 
16:     def startElement(self, name, attributes):
17:         if name == "entry":
18:             self.title = ""
19:             self.url = ""
20:         elif name == "title":
21:             self.inTitle = True
22:         elif name == "link" and attributes["rel"] == "alternate":
23:             self.url = attributes["href"]
24: 
25:     def characters(self, data):
26:         if self.inTitle:
27:             self.title += data
28: 
29:     def endElement(self, name):
30:         if name == "entry":
31:             self.entries.append((self.title, self.url))
32:         elif name == "title":
33:             self.inTitle = False
34: 
35: parser = xml.sax.make_parser()
36: handler = FeedHandler()
37: parser.setContentHandler(handler)
38: parser.parse(stdin)
39: 
40: for pair in handler.entries:
41:     line = '("%s" . "%s")\n' % pair
42:     stdout.write(line.encode('utf_8'))

FeedHandler の実装は単純なのでくどい説明は不要だろう。titleurl は一時変数で、entries が抽出した情報を蓄えておくリストになる。リストの各要素は記事のタイトルと URL のタプルになっている。

注意するのは、42 行目の line に対するエンコードの指定だ。これがないと、write メソッドでエラーが出る。pair が保持する文字列は Unicode 文字列だから、ファイル(標準出力)に書き出す際にエンコードが必要なのは当然なんだが、print を使うとエラーにはならない。print は 2 行目の coding を知っているということなのか。あるいは、print に対しては Python の構築時にデフォルトのエンコードが指定されているのか。

もちろん、UTF-8 を指定しているのは最終的な HTML を UTF-8 にしたいからだ。また、Mac OS X の「Terminal.app」なら UTF-8 にした日本語文字列をそのまま日本語として表示できるからフィルタの動作確認にも便利だ、という理由もある。

次の展開は?

これでようやく「ブログの記事一覧を作るプログラム」を Python (の標準添付ライブラリの範囲)で書き直すことができた。次の目標は、これを GAE に載せる、だ。

いまの作り方だと、Blogger から取得したフィードにふくまれるデータのほとんどを使い捨てている。そこが気になる点だ。取得したデータは GAE 上のストレージシステムに保持するべきだろうか? となると、Blogger で保管されているデータと二重に持つことになる。同期についても考慮しなければならない。まずは、取得したデータを使い捨てする方式で進めるか。

最終的にはブログの記事そのものも GAE 上のアプリ内で表示するようにしたい(→ 「Blogger で作ったブログを iOS デバイス対応にする」の「まとめ」参照)。記事一覧を表示して完成ではない。まだ、先は流そうだ。

関連リンク

関連記事