八発白中

技術ブログ、改め雑記

巳年なのでPythonでLESSの自動コンパイルしてみた

これは普通の日記です。

今年は巳年なのでPythonを始めました。

別にこの記事に影響されるほど繊細でもないのですが、Perlは仕事でいくらでも書けるし、Rubyはあんまり好きじゃないので、Pythonやってみるかーという軽い気持ちです。

ただ、Pythonの本はなかなか良いのが見つからなくて、最初は初めてのPythonを読もうと思ったのだけど、読み終えるのに一年くらいかかりそうだったからやめました。

Pythonクックブックにも一度手をつけたけど結局読まず、最終的に読み進めたのはDive Into Pythonだけでした。無料で読めるし、とりあえず完成したPythonコードを突き出して (Diveして)、順に説明するという方式で、他の入門書より速く学べる。実際、文法を多少知らなくてもDiveしたPythonコードは雰囲気で何をしたいのかわかるし、読みやすさを売りにしたPythonらしい本だと思います。

既にプログラム経験がある人にはDive Into Pythonおすすめです。

もしくはこっちのほうがいいかな???

LESSの自動コンパイル

最初はFlaskでWebアプリケーションを書いていたのですが、LESSを使いたいと思って、lesscについて調べてたら、LESSファイルの変更を監視して自動でCSSにコンパイルするスクリプトを書いていました。

作ったのは下のスクリプトです。実行にNode.jsとlesscが要ります。

import os
import re
import shutil
import time
import sys

from watchdog.observers import Observer
from watchdog.tricks import Trick


class FileCompileHandler(Trick):

    def output_path(self, path):
        return re.sub(r"([^\.]+)(\..+?)?$", r"\1-compiled\2", path)

    def compile_file(self, file):
        pass

    def compile_path(self, path):
        if os.path.isdir(path):
            for file in os.listdir(path):
                self.compile_path(file)
        else:
            self.compile_file(path)

    def on_created(self, event):
        if not event.is_directory:
            self.compile_path(event.src_path)
        else:
            os.mkdir(self.output_path(event.src_path))

    def on_modified(self, event):
        if not event.is_directory:
            self.compile_path(event.src_path)

    def on_moved(self, event):
        self.on_deleted(event)
        self.compile_path(event.dest_path)

    def on_deleted(self, event):
        to_delete_path = self.output_path(event.src_path)
        if event.is_directory:
            shutil.rmtree(to_delete_path)
        else:
            os.remove(to_delete_path)
        print "Deleted: " + to_delete_path


class LESSCompileHandler(FileCompileHandler):

    def __init__(self,
                 patterns=['*.less'],
                 source_directory='.',
                 destination_directory=None,
                 lessc='lessc'):
        super(LESSCompileHandler, self).__init__(patterns=patterns)

        self.source_directory = os.path.abspath(source_directory)
        if destination_directory is not None:
            self.destination_directory = os.path.abspath(destination_directory)
        else:
            self.destination_directory = self.source_directory

        self.lessc = lessc

    def output_path(self, path):
        return re.sub(
            r"\.less$", '.css',
            path.replace(self.source_directory, self.destination_directory))

    def compile_file(self, file):
        output_file = self.output_path(file)
        os.system(' '.join([self.lessc, file, output_file]))
        print '[LESS] Compiled: %s -> %s' % (file, output_file)


def watch_and_compile(source_directory, destination_directory=None):
    event_handler = LESSCompileHandler(
        source_directory=source_directory,
        destination_directory=destination_directory)

    observer = Observer()
    observer.schedule(
        event_handler,
        path=event_handler.source_directory,
        recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


if __name__ == "__main__":
    watch_and_compile(*sys.argv[1:])

importしてもいいし、そのままスクリプトとして実行できます。以下のように実行すると、「static/less」以下にある".less"のファイルを監視し、コンパイル結果を「static/css」の同じ階層に出力します。

$ python less_compile.py static/less static/css

watchdog.tricks.Trickを継承しているので、tricks.yamlで設定を管理することもできます

# tricks.yaml
tricks:
  - less_compile.LESSCompileHandler:
      source_directory: static/less/
      destination_directory: static/css/

実行はwatchmedoコマンドです。

$ watchmedo tricks tricks.yaml

結構試行錯誤したので、2日くらいかかりました。

Python雑感

最後に、この10日くらいPythonを学んでみての雑感です。

読みやすい

自分が書いたコードにしても、他人のコードにしても、PerlCommon Lispより圧倒的に読みやすいです。これは言語の優劣の問題ではなく、たぶんPythonがシンタックス上の制限が多いためでしょう。書くのには自然と気を遣わせられて、きれいなコードを強制的に書かされているような感じです。大規模なプロジェクトだと活きそうですね。

前からClackCavemanのコードを読んだ外人に「Pythonっぽすぎるだろ」と渋い顔で言われることがままあって気になっていたのですが、実際に学んでみて理由がわかった気がします。なんでしょうか。とりあえず褒め言葉として受け取っておきます。

ちょっとしたことでもクラス定義しないといけないのが不便

僕はPerlCommon Lispでプログラムを書くときは、まずプロトタイプを作る目的でsubやlambdaを書いてとりあえず動かしてみる、というところから始めます。

今回のLESSの監視自動コンパイラを書こうと思ったときもそうしようと思ったのですが、Pythonではlambdaに複数の式を書くことはできないし(確かにひどい)、簡単な名前付き関数を書いていても煩雑になるばかりで、もういきなりクラス定義から始める有様でした。書きながら、いきなり泥沼に足を突っ込んだような心持ちでした。

メソッドコンビネーションがないのが不便

ファイルをコンパイルしたときのログなんかは、まとめて親クラスで定義してしまいたかったのですが、PythonにはCLOSのような高級なオブジェクトシステムがないのでbeforeとかafterメソッドを定義することができなさそうです。

何か専用のモジュールがあるんでしょうか? ちょっと不便です。

おわり

以上、日記でした。Pythonistaの方々、よろしくお願いします。