[Python][Hyper Estraier] Hyper Estraierビューワ ScandiumRider 0.2リリース

ScandiumRiderとはなんですか?

ScandiumRiderとは、Hyper Estraierのインデックスを検索し、結果を表示するCUIアプリケーションです。

0.1からの変更点

  1. 検索条件入力画面はなくなりました。検索条件は、コマンドラインから入力します。
  2. 検索結果一覧画面に、レコード件数と選択中のレコードのインデックスを表示するようにしました。
  3. ドキュメントの表示に、外部のページャを使用するようにしました。現在は、/usr/bin/lessです。
  4. メタデータを表示できるようになりました。
  5. インストールにsetuptoolsを使用するようにしました。

動作環境

以下の環境で、動作を確認しています。

名前 バージョン
Ubuntu Linux 6.10
Hyper Estraier 1.4.10
estraiernative 0.2
Python 2.4.4c1

ダウンロード

以下のリンクからダウンロードできます。

インストール

ScandiumRiderには、estraiernative (http://www.sure-shot.jp/python-hyperestraier/) が必要です。先に、estraiernativeをインストールして下さい。

ScandiumRider-0.2.tar.gzを展開した後、以下のようにしてインストールします。

$ sudo python setup.py install

使い方

検索方法

コマンドラインで、以下のようにして検索します。

$ scr  <検索フレーズ>...

例えば、mail.heというインデックスに対して"hyperestraier"を検索するときは、以下のようにします。

$ scr mail.he hyperestraier
検索結果一覧画面

該当するドキュメントが見つかると、下の検索結果一覧画面が表示されます(結果が0件の場合、表示されません)。

検索結果一覧画面は、以下のように操作します。

  • "j"キーでカーソルを下に移動します(要するにviキーバインドです)。
  • "k"キーでカーソルを上に移動します。
  • エンターキーで選択しているドキュメントを表示します(下図)。

  • "i"キーで選択しているドキュメントのメタデータを表示します(下図)。

  • "q"キーで終了します。

今後の予定

  1. ドキュメントを表示するビューワを設定できるようにします。
  2. メールに添付ファイルがあった場合、添付ファイルを表示できるようにします。

[Python] setuptoolsを使用すると、スクリプト本体はパッケージのディレクトリ/EGG-INFO/scriptsにインストールされる。

はじめに

この記事の内容は、Ubuntu Linux 6.10, Python 2.4.4c1で確認しました。

結論

以下のように、setup.pyでsetuptoolsを用い、フロントエンドとなるスクリプトpydumpfsをsetup関数のscripts引数に指定したとします。

#! python
# -*- coding: utf-8 -*-

import ez_setup
ez_setup.use_setuptools()

from setuptools import setup, find_packages

setup(name="pydumpfs", version="0.1", packages=find_packages("src"), 
    package_dir={"": "src"}, test_suite="pydumpfs.tests", scripts=["pydumpfs"])

このとき、

$ sudo python setup.py install

を実行すると、/usr/bin/にpydumpfsというスクリプトがインストールされますが、これはsetuptoolsが作成したラッパーで、実際のスクリプトは/usr/lib/python2.4/site-packages/pydumpfs-0.1-py2.4.egg/EGG-INFO/scriptsにインストールされます。

詳細

/usr/bin/pydumpfsは、以下のようになっています。

#!/usr/bin/python
# EASY-INSTALL-SCRIPT: 'pydumpfs==0.1','pydumpfs'
__requires__ = 'pydumpfs==0.1'
import pkg_resources
pkg_resources.run_script('pydumpfs==0.1', 'pydumpfs')

/usr/lib/python2.4/site-packages/setuptools-0.6c6-py2.4.egg/pkg_resources.pyを読むと、run_scriptは以下のようになってます。

def run_script(dist_spec, script_name):
    """Locate distribution `dist_spec` and run its `script_name` script"""
    ns = sys._getframe(1).f_globals
    name = ns['__name__']
    ns.clear()
    ns['__name__'] = name
    require(dist_spec)[0].run_script(script_name, ns)

require関数は、パッケージの名前とバージョン番号から、「何か」(このコードではそれが何かわかりません)をリストとして返す関数のようです。同じファイルで、以下のように定義されています。

working_set = WorkingSet()
(中略)
require = working_set.require

WorkingSetクラスのrequireメソッドは、以下のように定義されています。

    def require(self, *requirements):
        """Ensure that distributions matching `requirements` are activated

        `requirements` must be a string or a (possibly-nested) sequence
        thereof, specifying the distributions and versions required.  The
        return value is a sequence of the distributions that needed to be
        activated to fulfill the requirements; all relevant distributions are
        included, even if they were already activated in this working set.
        """

        needed = self.resolve(parse_requirements(requirements))

        for dist in needed:
            self.add(dist)

        return needed

この関数の戻り値となるneededが何か、まだ分かりません。次にresolveメソッド(以下)を読んでみますが...。

    def resolve(self, requirements, env=None, installer=None):
        """List all distributions needed to (recursively) meet `requirements`

        `requirements` must be a sequence of ``Requirement`` objects.  `env`,
        if supplied, should be an ``Environment`` instance.  If
        not supplied, it defaults to all distributions available within any
        entry or distribution in the working set.  `installer`, if supplied,
        will be invoked with each requirement that cannot be met by an
        already-installed distribution; it should return a ``Distribution`` or
        ``None``.
        """

        requirements = list(requirements)[::-1]  # set up the stack
        processed = {}  # set of processed requirements
        best = {}  # key -> dist
        to_activate = []

        while requirements:
            req = requirements.pop(0)   # process dependencies breadth-first
            if req in processed:
                # Ignore cyclic or redundant dependencies
                continue
            dist = best.get(req.key)
            if dist is None:
                # Find the best distribution and add it to the map
                dist = self.by_key.get(req.key)
                if dist is None:
                    if env is None:
                        env = Environment(self.entries)
                    dist = best[req.key] = env.best_match(req, self, installer)
                    if dist is None:
                        raise DistributionNotFound(req)  # XXX put more info here
                to_activate.append(dist)
            if dist not in req:
                # Oops, the "best" so far conflicts with a dependency
                raise VersionConflict(dist,req) # XXX put more info here
            requirements.extend(dist.requires(req.extras)[::-1])
            processed[req] = True

        return to_activate    # return list of distros to activate

これを読んでみても、何をしているのか分かりません。どうやら、さらにEnvironmentクラスのbest_matchメソッドを読まないといけないようですが、これ以上は深追いしないことにします。

さて、require関数が返した「何か」のrun_scriptメソッドを呼び出すことで、実際のスクリプトを実行しているようですが、run_scriptメソッドを持っているクラスは、pkg_resources.pyの中に3つあります。

ひとつ目はIMetadataProviderクラスです。このクラスのrun_scriptメソッド(以下)は、実装がないので、これが呼び出されることはないと思われます。

class IMetadataProvider:
(中略)
    def run_script(script_name, namespace):
        """Execute the named script in the supplied namespace dictionary"""

ふたつ目はWorkingSetクラスです(以下)。

class WorkingSet(object):
(中略)
    def run_script(self, requires, script_name):
        """Locate distribution for `requires` and run `script_name` script"""
        ns = sys._getframe(1).f_globals
        name = ns['__name__']
        ns.clear()
        ns['__name__'] = name
        self.require(requires)[0].run_script(script_name, ns)

三つめはNullProviderクラスです。

class NullProvider:
(中略)
    def run_script(self,script_name,namespace):
        script = 'scripts/'+script_name
        if not self.has_metadata(script):
            raise ResolutionError("No script named %r" % script_name)
        script_text = self.get_metadata(script).replace('\r\n','\n')
        script_text = script_text.replace('\r','\n')
        script_filename = self._fn(self.egg_info,script)
        namespace['__file__'] = script_filename
        if os.path.exists(script_filename):
            execfile(script_filename, namespace, namespace)
        else:
            from linecache import cache
            cache[script_filename] = (
                len(script_text), 0, script_text.split('\n'), script_filename
            )
            script_code = compile(script_text,script_filename,'exec')
            exec script_code in namespace, namespace

どうもコードの内容からすると、最終的に呼び出されるのはNullProviderクラスのrun_scriptメソッドではないかという気がします。

どう動いているのかはっきりさせることはできませんでしたが、setuptoolsが/usr/binにインストールするスクリプトは「プロキシ」であって、実際のスクリプトはパッケージの中にインストールされるようです。