Python版Rake「tasktools」をCodeReposにコミットした

以前紹介したPython版Rakeもどきを改良してCodeReposに突っ込みました。まだ100行くらいです。自分では一番使っている自作モジュールなのでそろそろまとめようと思っていたのです。

http://coderepos.org/share/browser/lang/python/tasktools/trunk/tasktools.py

改良点

  • setuptoolsがあるときはそっちを使うようにした。
  • use_without_standardメソッドによってdistutils標準タスクを消すことができる。これによって--help-commandsの画面がすっきりする。また、この場合名前空間のセパレータに:を用いるようになった。
  • global_descriptionによってタスクファイル自体に説明がつけられるようになった。
  • load_pathメソッドで指定したディレクトリ以下を再帰的に検索し「tasks.py」という名前のファイルを読み込むことができるようになった。
  • --help-commandsで表示されるコマンドの並び順をソートするようにした。
  • --help-commandsでサブコマンドを一覧表示するようにした。

使用方法

以前書いたのとほとんど同じなんですが、まとめなおしておきます。

tasktoolsとは?

distutilsおよびsetuptoolsを拡張してextra commandを簡単に作成するためのユーティリティです。RubyにおけるRakeのようなものです。ビルド機能がほしい場合はdistutils, setuptoolsの標準ビルド機能、もしくはSConsと組み合わせるとハッピーになれます。

ちなみに、tasktoolsというのは同じくdistutilsの拡張であるsetuptoolsの命名規則に習っています。task機能を強化するからtasktoolsです。

チュートリアル

典型的なtasktoolsの使い方です。

まずtasks.pyというファイルを作成します。典型的には以下の様になります。

PYTHON:
  1. from __future__ import with_statement
  2. from tasktools import *
  3.  
  4. global_description(u"""
  5. サンプルタスクファイルです。
  6. """)
  7.  
  8. use_without_standard() # distutilsの標準コマンドを使用しないことを宣言します。
  9. load_path("./tasks") # "./tasks"以下のtasks.pyを再帰的に読み込みます
  10.  
  11.  
  12. with namespace("file") as ns:
  13.   class mktmpfile(Task):
  14.     u"""一時ファイルを作成します。
  15.     """
  16.     user_options = [("path=", "p", u"作成するパスです")]
  17.     def run(self):
  18.       print "create %s"%self.path
  19.  
  20.     def finalize_options(self):
  21.       if not self.path: self.path = "/tmp/tmp.txt"
  22.  
  23.   class mklogfile(Task):
  24.     u"""ログファイルを作成します。
  25.     """
  26.     def run(self):
  27.       print "create log file"
  28.  
  29.   class init(Task):
  30.     u"""ファイルを初期化します。
  31.     """
  32.     def run(self):
  33.       pass
  34.  
  35.     sub_commands = [("file:mktmpfile", None),
  36.                     ("file:mklogfile", None)]
  37.  
  38. if __name__ == "__main__":
  39.   run()


ではpython tasks.py --help-commandsと実行してみましょう

############################################################

サンプルタスクファイルです。

############################################################

Commands:
  file:init        ファイルを初期化します。
        sub commands:
                file:mktmpfile
                file:mklogfile
  file:mklogfile   ログファイルを作成します。
  file:mktmpfile   一時ファイルを作成します。

usage: tasks.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
  or: tasks.py --help [cmd1 cmd2 ...]
  or: tasks.py --help-commands
  or: tasks.py cmd --help

この様にglobal_descriptionで設定した説明と、定義したタスクの一覧が表示されます。

次に./tasks/tasks.pyを作成してみます。

PYTHON:
  1. from __future__ import with_statement
  2. from tasktools import *
  3.  
  4. use_without_standard()
  5. with namespace("subs") as ns:
  6.   class test1(Task):
  7.     u"""サブディレクトリで定義されたタスクです
  8.     """
  9.     def run(self):
  10.       print "sub test"
  11.  
  12. if __name__ == "__main__":
  13.   run()


このファイルは./tasks.pyload_path("./tasks")と宣言しているので./tasks.pyを実行すると自動的に読み込まれます。 もう一度python tasks.py --help-commandsと実行してみましょう

############################################################

サンプルタスクファイルです。

############################################################

Commands:
  file:init        ファイルを初期化します。
        sub commands:
                file:mktmpfile
                file:mklogfile
  file:mklogfile   ログファイルを作成します。
  file:mktmpfile   一時ファイルを作成します。
  subs:test1       サブディレクトリで定義されたタスクです

usage: tasks.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
  or: tasks.py --help [cmd1 cmd2 ...]
  or: tasks.py --help-commands
  or: tasks.py cmd --help

確かにsubs:test1コマンドが追加されています。

ではタスクを実行してみましょう。python tasks.py file:initを実行してみます。

running file:init
running file:mktmpfile
create /tmp/tmp.txt
running file:mklogfile
create log file

おお、実行されましたね。

user_optionsを定義しているタスクではオプションも渡せます。python tasks.py file:mktmpfile --path=/tmp/change.txtを実行してみましょう。

running file:mktmpfile
create /tmp/change.txt

ちゃんとオプションが渡されていますね。

 
 


 
こんな感じです。distutilsの独自コマンドに関する説明は46 新しいDistutilsコマンドの作成を参照してください。正直使えないページですが・・・。一応説明しておくとinitialize_optionsはuser_optionsの定義から自動生成するようになっています。またdistutils.core.Commandを継承しているのでこのクラスの機能も使えます。

せっかくCodereposに突っ込んだのでバグなんかが見つかったらガンガン直しちゃってください。

Posted at 1am on 02/19/08 | no comments | Tags : read on

Python標準モジュールでRakeもどき

誰もが一度使うと便利さと気軽さに感動するRubyが誇るライブラリ、Rake

プログラムのビルドもそうなんですが、雑多なタスクを簡単に書けて、整理できるのがなんといっても魅力的。RailsなんかではDBの作成から何から、ばんばんRakeタスクにされていますよね。

 

さて、俺はPythonistasなので、PythonでRakeみたいなのがほしいわけです。ビルドに限っていえばPythonはSconsという素晴らしいツールがあります。C言語はおろか、JAVA、PDF、PostScriptなどなど、さらにはSubversionもサポートしていますし、並列コンパイルもでき、実績も多数で申し分ありません。

でも俺がしたいのは、雑多なタスクを放り込む、コレ。そういうのを簡単にやるライブラリってPythonではないんでしょうか。RubyではRakeが標準添付されるというのに。いえ、あります。前からあるんです。それがdistutils

distutils(およびその拡張のsetuptools)はPythonの標準的なパッケージ配布システムとして有名です。でも実は自分でコマンドを定義してRakeのように使うことができることはあまりしられていないのではないでしょうか。

distutilsでコマンドを定義する

distutilsでコマンドを定義するにはdistutils.core.Commandクラスを継承し、色々オーバーライドします。説明もつけられますし、オプションもとれて、sub_commandsで依存関係(コマンド分割)を設定することもできます。

PYTHON:
  1. class MyCommand(Command):
  2.   description = "コマンドの説明"
  3.   user_options = [("host=", "h", "hostname")] # オプション
  4.   sub_commands = [("pre_task", None)] # サブコマンド
  5.   def run(self): pass # 実行する内容
  6.   def initialize_options(self): # オプションの初期化
  7.     self.host = None
  8.   def finalize_options(self):
  9.     if not self.host: raise "Error"
  10.  
  11. setup(cmdclass={"my_command":MyCommand})


といった具合です。非常にPythonicなやり方ですね。ただ、いろいろオーバーライドしないといけないので面倒です。そこで、俺がコピペで使っている自家製テンプレの登場です。

もっと簡単にdistutilsでタスク

30行程度のテンプレを足して、簡単にタスクを定義できるようにしましょう。namespace機能はPython2.5以上限定です。

PYTHON:
  1. from __future__ import with_statement
  2. from distutils.core import Command, setup
  3. from contextlib import contextmanager
  4.  
  5. _cmds = {}
  6. _namespace = []
  7. _get_ns = lambda:_namespace and "_".join(_namespace)+"_" or ""
  8. class CommandType(type):
  9.   def __new__(cls, class_name, class_bases, classdict):
  10.     d = dict(user_options=[], finalize_options=lambda s:None)
  11.     d.update(classdict)
  12.     def _(self):
  13.       [setattr(self,i[0].rstrip("="),None) for i in d["user_options"]]
  14.     d["initialize_options"] = _
  15.     d["boolean_options"] = [i for i,j,k in d["user_options"] if not i.endswith("=")]
  16.     def _(self):
  17.       map(self.run_command, self.get_sub_commands())
  18.       return classdict["run"](self)
  19.     d["run"] = _
  20.     name = _get_ns()+class_name.lower()
  21.     cls = type.__new__(cls, name, class_bases + (object,), d)
  22.     cls.description = cls.__doc__
  23.     if class_name != "BaseCommand" : _cmds[name] = cls
  24.     return cls
  25. class BaseCommand(Command): __metaclass__ = CommandType
  26.  
  27. @contextmanager
  28. def namespace(name):
  29.   _namespace.append(name)
  30.   yield _get_ns()
  31.   _namespace.pop()


これだけです。これでかなり簡単にタスクが作れるようになります。例を示しましょう。

PYTHON:
  1. # task.py
  2. with namespace("db") as ns:
  3.   with namespace("create") as ns:
  4.     class Test1(BaseCommand):
  5.       """test1 discription
  6.       """
  7.       user_options = [ ("host=", "h", "hostname"),
  8.                       ("port=", "p", "port"),
  9.                       ("force", "f", "force execute")]
  10.       def run(self):
  11.         print self.host
  12.         print self.force
  13.         print "hello, ", self.__class__.__name__
  14.  
  15.       sub_commands = [(ns+"test2",None)]
  16.  
  17.     class Test2(BaseCommand):
  18.       """test2 discription
  19.       """
  20.       def run(self):
  21.         print "hello, ", self.__class__.__name__
  22.  
  23.  
  24.   class Test3(BaseCommand):
  25.     """test3 discription
  26.     """
  27.     def run(self):
  28.       print "hello, ", self.__class__.__name__
  29.  
  30. class Test4(BaseCommand):
  31.   """test4 discription
  32.   """
  33.   user_options = [ ("host=", "h", "hostname") ]
  34.   def run(self):
  35.     print "hello, ", self.__class__.__name__
  36.  
  37.   def finalize_options(self):
  38.     if not self.host:
  39.       raise ValueError("host must not be None")
  40.  
  41.  
  42. setup(cmdclass=_cmds)


かなり直感的になったのではないでしょうか。withを使えば名前空間も結構簡単に実現できます。コマンドのヘルプを見てみましょう。

# python task.py --help-commands
Standard commands:
  build            build everything needed to install
  build_py         "build" pure Python modules (copy to build directory)
  build_ext        build C/C++ extensions (compile/link to build directory)
  build_clib       build C/C++ libraries used by Python extensions
  build_scripts    "build" scripts (copy and fixup #! line)
  clean            clean up temporary files from 'build' command
  install          install everything from build directory
  install_lib      install all Python modules (extensions and pure Python)
  install_headers  install C/C++ header files
  install_scripts  install scripts (Python or otherwise)
  install_data     install data files
  sdist            create a source distribution (tarball, zip file, etc.)
  register         register the distribution with the Python package index
  bdist            create a built (binary) distribution
  bdist_dumb       create a "dumb" built distribution
  bdist_rpm        create an RPM distribution
  bdist_wininst    create an executable installer for MS Windows

Extra commands:
  db_test3         test3 discription

  db_create_test2  test2 discription

  db_create_test1  test1 discription

  test4            test4 discription


usage: task.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
  or: task.py --help [cmd1 cmd2 ...]
  or: task.py --help-commands
  or: task.py cmd --help

こんな感じで、標準コマンドに加え「Extra commands」という形で定義したタスクが使えるようになります。もちろん、distutilsのビルド機能、パッケージ管理機能も使えますよ。

db_create_test1のヘルプを見てみましょう。

# python task.py db_create_test1 --help
Common commands: (see '--help-commands' for more)

  setup.py build      will build the package underneath 'build/'
  setup.py install    will install the package

Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message

Options for 'db_create_test1' command:
  --host (-h)   hostname
  --port (-p)   port
  --force (-f)  force execute

usage: task.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
  or: task.py --help [cmd1 cmd2 ...]
  or: task.py --help-commands
  or: task.py cmd --help

ちゃんとオプションが表示されていますね。最後に、db_create_test1タスクを実行してみましょう。

# python task.py db_create_test1 --host=localhost
running db_create_test1
running db_create_test2
hello,  Test2
localhost
None
hello,  Test1

サブコマンドのdb_create_test2が実行されていること、きちんとオプションがselfの属性として設定されていることがわかりますね。

 
 


 
 
標準的な機能だけでもRakeのようなタスクはつくれますが、30行程度の工夫で断然便利になります。Rakeと違いrakeコマンドのような外部コマンドも必要なく、pythonのスクリプト1つで実現できるのも手軽です。というわけで、雑多なタスクはdistutilsでまとめてみてはいかがでしょうか。

Posted at 4am on 01/23/08 | 3 comments | Tags : read on

PythonによるNESエミュレータ開発5

もうすぐ引越しです。頑張って部屋を片付けないと・・・

しばらくドタバタすると思し、これ以上作りこむモチベーションもないので、ここまで作ったものをあげておこうと思いました。

ダウンロード
pynes-0-0-1.zip

試し方

インストールはダウンロードしたzipファイルを展開するだけです。

必要なライブラリは

です。
両方ともeasy_install psycoeasy_install pygameでインストールできたはずです。

roms/以下に最低1つ以上ロムファイルを置いてください。現状、マッパーに対応してませんので、マッパー0のしか動く可能性はありません。現在動作を確認してるのは、前回あげさせていただいたTkShootくらいです。市販のはほとんど動かないんじゃないでしょうか。

一応参考までにあげておくと、動く可能性があるのはGolf,DonkeyKongなどです。

bin/pynesi.pyが起動用スクリプトです。コマンドラインから起動してください。起動したら、romファイルを番号で選択してください。

キーバーインドは

  • 十字キー : カーソル
  • スタート : テンキーの0
  • セレクト : テンキーのEnter
  • A : テンキーの3
  • B : テンキーの2

になってます。キーバーインドを代えたい方はsrc/pynes/pad.pyを適当に書き換えてください。

PYTHON:
  1. self.keymap1 = {
  2.       K_UP : NES_PAD_UP,
  3.       K_DOWN : NES_PAD_DOWN,
  4.       K_LEFT : NES_PAD_LEFT,
  5.       K_RIGHT : NES_PAD_RIGHT,
  6.       K_KP0 : NES_PAD_START,
  7.       K_KP_ENTER : NES_PAD_SELECT,
  8.       K_KP2 : NES_PAD_B,
  9.       K_KP3 : NES_PAD_A
  10.     }


ここです。

とにかく、めちゃくちゃ遅いので、固まったと思ってもしばらくすると画面がちゃんと切り替わったりします。

よもや話

かなり適当です。前回(PythonによるNESエミュレータ開発4)から変わってません。マッパーっぽいのが用意してありますが、これはダミーです。他のエミュのソースを参考に必要そうな部分に適当にいれただけです。

一応、速度を重視しているものの、わかりやすく書いてるつもりなんで、Pythonが分かっていてかつ、エミュレータの基本的な構造が知りたい人には参考になるかもしれません。

Posted at 12am on 03/02/07 | no comments | Tags : read on
Pages (3): [1] 2 3 »

About

about me
yuin()
文学部文化学科卒という生粋の文系趣味プログラマ。
ベンチャー企業でアルバイトを経て、某大手企業で働いてます。    
主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。
今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages