なぜPyInvokeを使うのか

Texファイルのビルダー(Makefileの代わり)にFabricというものを使っていました. Fabricの中身は完全にpythonのスクリプトです.

しかし, FabricはPython3に対応していません. 今の時代 Python2は書きたくない, ということで代わりになるものを探していましたが, なかなか代わりになるものが見つかりません.

twitterで教えてもらったinvoke というツールを使うと上手くできました.

PyInvokeではまったところと解決

最初にオメガ師匠のpyinvoke を触ってみた という記事を読んで真似しようとしてみました.

すると

$ invoke -l
Traceback (most recent call last):
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/tasks.py", line 144, in argspec
    context_arg = arg_names.pop(0)
IndexError: pop from empty list

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/yassu/.pyenv/versions/3.5.3/bin/invoke", line 11, in <module>
    sys.exit(program.run())
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/program.py", line 286, in run
    self._parse(argv)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/program.py", line 352, in _parse
    self.load_collection()
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/program.py", line 500, in load_collection
    coll = loader.load(coll_name) if coll_name else loader.load()
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/loader.py", line 53, in load
    module = imp.load_module(name, fd, path, desc)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/imp.py", line 234, in load_module
    return load_source(name, filename, file)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/imp.py", line 172, in load_source
    module = _load(spec)
  File "<frozen importlib._bootstrap>", line 693, in _load
  File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 673, in exec_module
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "/Users/yassu/dev/Tmp/tasks.py", line 5, in <module>
    @task
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/tasks.py", line 275, in task
    return Task(args[0], **kwargs)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/tasks.py", line 61, in __init__
    self.positional = self.fill_implicit_positionals(positional)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/tasks.py", line 152, in fill_implicit_positionals
    args, spec_dict = self.argspec(self.body)
  File "/Users/yassu/.pyenv/versions/3.5.3/lib/python3.5/site-packages/invoke/tasks.py", line 147, in argspec
    raise TypeError("Tasks must have an initial Context argument!")
TypeError: Tasks must have an initial Context argument!

こんなエラーが出てしまいました.

目が点になりましたよね.

このエラーについては解決方法がIssueに書いてました.

Travis: TypeError: Tasks must have an initial Context argument! #409

後方互換性の問題ですね. taskアノテーションをつけてるそれぞれの関数にctx という引数を与えてやればいいとのこと. この方法で上のブログのコードも動かすことができました.

TeX用の簡単な例

もともとの問題に帰って, TeX用の簡単な Taskファイルを作りましょう.

まずはinvokeをインストールしておきましょう.

$ pip install invoke

次のファイルにtasks.pyという名前をつけて, コンパイルしたいtexファイルと同じディレクトリに置きます.

from invoke import task

MAIN_BASENAME = 'main'
LATEX = 'platex'
DVIP = 'dvipdfmx'
VIEW_PDF = 'evince'


@task
def compile(ctx):
    """ コンパイルする """
    ctx.run('{} {}'.format(LATEX, MAIN_BASENAME + '.tex'))
    ctx.run('{} {}'.format(DVIP, MAIN_BASENAME + '.dvi'))


@task
def comp(ctx):
    """ コンパイルする """
    compile(ctx)


@task(compile)
def view(ctx):
    """ コンパイルして pdfファイルを開く """
    ctx.run('{} {}'.format(VIEW_PDF, MAIN_BASENAME + '.pdf'))


def get_junk_filenames():
    yield 'x.log'
    for filename in (MAIN_BASENAME + ext for ext in (
            '.aux', '.dvi', '.log', '.pdf')):
        yield filename


@task
def clean(ctx):
    """ コンパイル時にできたファイルを削除する """
    for filename in get_junk_filenames():
        ctx.run('rm {}'.format(filename))

MAIN_BASENAMEは コンパイルしたいtexファイルのBaseName(拡張子を取り除いた部分), VIEW_PDFはpdfを開くのに使用するコマンドです.

これらの値は使いたい値に変えてください.

これで, 準備は完了です.

tasks.pyが置いてあるディレクトリで

$ inv -l

と打つと

Available tasks:

  clean     コンパイル時にできたファイルを削除する
  comp      コンパイルする
  compile   コンパイルする
  view      コンパイルして pdfファイルを開く

と表示されます.

コマンドの通りに inv compile と打つと texファイルがコンパイルされます.

inv view と打つと, texファイルがコンパイルされた後に pdfで開くことができます.

inv clean で コンパイル時に作成されたいらないファイルたちを削除します.

PyInvokeの問題

問題もあります. 現在, InvokeはPython 3.5には対応していますが, Python3.6には対応していません. それに関する PRもあるようですが

Add support for python 3.6 #282

このPRは正常にテストに通っていませんね.

対応できないのは flake8が原因のようですが, このPRに最後に更新があったのは 2015年のようです.

その後, 状況はどうなのだろう? まだこのエラーは起こるのかな.

最後に

自分はPyInvokeを会社にいるときにtwitterで知り合いにすすめられて, そのまま帰ってきて少し触ってみて記事書いてるような状態なので, 間違いなどあるかもしれません.

間違いの指摘, コメントなどもしあればお願いいたします.