以前紹介した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 というファイルを作成します。典型的には以下の様になります。

 1from __future__ import with_statement
 2from tasktools import *
 3
 4global_description(u"""
 5サンプルタスクファイルです。
 6""")
 7
 8use_without_standard() # distutilsの標準コマンドを使用しないことを宣言します。
 9load_path("./tasks") # "./tasks"以下のtasks.pyを再帰的に読み込みます
10
11
12with 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
38if __name__ == "__main__":
39  run()

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

 1############################################################
 2
 3サンプルタスクファイルです。
 4
 5############################################################
 6
 7Commands:
 8  file:init        ファイルを初期化します。
 9        sub commands:
10                file:mktmpfile
11                file:mklogfile
12  file:mklogfile   ログファイルを作成します。
13  file:mktmpfile   一時ファイルを作成します。
14
15usage: tasks.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
16  or: tasks.py --help [cmd1 cmd2 ...]
17  or: tasks.py --help-commands
18  or: tasks.py cmd --help

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

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

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

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

 1############################################################
 2
 3サンプルタスクファイルです。
 4
 5############################################################
 6
 7Commands:
 8  file:init        ファイルを初期化します。
 9        sub commands:
10                file:mktmpfile
11                file:mklogfile
12  file:mklogfile   ログファイルを作成します。
13  file:mktmpfile   一時ファイルを作成します。
14  subs:test1       サブディレクトリで定義されたタスクです
15
16usage: tasks.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
17  or: tasks.py --help [cmd1 cmd2 ...]
18  or: tasks.py --help-commands
19  or: tasks.py cmd --help

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

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

1running file:init
2running file:mktmpfile
3create /tmp/tmp.txt
4running file:mklogfile
5create log file

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

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

1running file:mktmpfile
2create /tmp/change.txt

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


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

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


ScalaにはStreamという無限リストがあるんだけど、微妙に使いづらい、というか分かりづらい。Haskellでいうcycleはどうだ、とかよく忘れるのでメモ。

1def repeat[T](a:T) = Stream.const(a)
2def cycle[T](a:Iterable[T]) = Stream.const(a).flatMap(v=>v)
3def iterate[T](f:T => T,  x:T):Stream[T] = Stream.cons(x, iterate(f, f(x)))
4def replicate[T](n:int, elem:T) = Stream.make(n, elem)

こんな感じかな。cycleは結構使うから、Streamに標準でありそうな気がするんだけど、ないような。というわけで上のような定義となる。

 1repeat(1) take 10 print
 2// => 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, Stream.empty
 3
 4cycle(1 to 4) take 10 print
 5// => 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, Stream.empty
 6
 7iterate((x:int)=>x+1, 0) take 10 print
 8// => 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, Stream.empty
 9
10replicate(3, 1) take 10 print
11// => 1, 1, 1, Stream.empty

うんうん。


コレだけはガチ。続きは書くかもしれないけど、たぶん書かない。


P.S

Schemeが好きです。Gaucheが好きです。でもういんどうずだとGaucheさんはちょっとアレなので、社内ソフトがWindowsしか対応してないとか、データ保護ソフトがWindowsしか対応してないとか、社内システムがIE限定だとか、ういんどうずを使わざるを得ない人には使えません。Pythonサイコー