この手の規約を守るのは面倒くさいのですが、
Eclipse(PyDev)に特化させることで かなり効率が上がったので、紹介します。
まず、【ここ】を参考にして、PEP8チェックをunittestに組み込んでみました。
それだけだと芸が無いので、テスト結果の出力形式をPyDev用に編集しました。
PyDevは、トレースバックのログから、エラー箇所へのリンクを作りますよね?
これはログの出力結果のパターンから リンクを生成しているように思えたので、PEP8のログを整形してトレースバックログに似せてみました。
(この辺、Pythonは改造が簡単で良いですね)
すると、トレースバックのログと同様に、PEP8に違反したところへのリンクができました!
これで、PEP8違反箇所の修正が、かなり簡単になります。
以下、ソースです
(長いので、ダブルクリックしてコピー&ペーストしましょう)。
# -*- coding: utf-8 -*- from os import path as os_path import pep8 import unittest #============================================================================ # BASE_DIR = PEP8チェック対象のルートディレクトリ _SELF_DIR = os_path.dirname(os_path.abspath(__file__)) _PARENT_DIR = os_path.dirname(_SELF_DIR) BASE_DIR = _PARENT_DIR #============================================================================ # テスト実行時のパラメータ NEW_LINE = '\n' LINES = '-' * 50 DECODE_LIST = ['utf-8', 'mbcs'] ENCODE_FOR_ECLIPSE = 'utf-8' ENCODE_FOR_CMD = 'mbcs' DEF_ENCODE = ENCODE_FOR_ECLIPSE #============================================================================ # PEP8実行時のパラメータのデフォルト定義 OUTPUT_STATICS = False #EXCLUDE_PATTERNS = ['wtforms', 'dateutil'] EXCLUDE_PATTERNS = [] #IGNORE_PATTERNS = ['E501'] IGNORE_PATTERNS = [] ARGLIST = [ '--statistics', '--filename=*.py', '--exclude=' + ','.join(EXCLUDE_PATTERNS), '--show-source', '--repeat', '--ignore=' + ','.join(IGNORE_PATTERNS), #'-v', ] #============================================================================ class StoreMessages(object): u"""pep8.message の出力タイミングを遅らせるためのクラスです 以下のように使うことで、test実行中にpep8からprintされてしまうtextを self.textに溜め込みます。 >>> sm = StoreMessage() >>> pep8.message = sm.message こうすることで、assertError時のメッセージに、pep8エラー内容を含めることを 目的にしています。 """ def __init__(self, decode_list=DECODE_LIST): self.text = u'' self.decode_list = decode_list def message(self, text=u''): for code in self.decode_list: try: self.text += text.decode(code) + NEW_LINE break except UnicodeDecodeError: pass else: self.text += text.decode(code) + NEW_LINE def get_message(self, code=None): _code = code or DEF_ENCODE return self.text.encode(_code) #============================================================================ def report_error(self, line_number, offset, text, check): u""" pep8.Checker.report_errorの代替の関数です。ほぼ完コピです。 以下のように使うことで、pep8で出力するエラー内容を整形します。 >>> pep8.Checker.report_error = report_error これにより、tracebackのメッセージと同じ形になるので、 eclipse上でpep8のエラーをクリックできるようにします。 """ code = text[:4] if pep8.ignore_code(code): return if pep8.options.quiet == 1 and not self.file_errors: pep8.message(self.filename) if code in pep8.options.counters: pep8.options.counters[code] += 1 else: pep8.options.counters[code] = 1 pep8.options.messages[code] = text[5:] if pep8.options.quiet or code in self.expected: # Don't care about expected errors or warnings return self.file_errors += 1 if pep8.options.counters[code] == 1 or pep8.options.repeat: pep8.message(LINES) pep8.message((' File "%s", line %s, column %d' + NEW_LINE + ' %s:') % (self.filename, self.line_offset + line_number, offset + 1, text)) if pep8.options.show_source: line = self.lines[line_number - 1] pep8.message(line.rstrip()) pep8.message(' ' * offset + '^') if pep8.options.show_pep8: pep8.message(check.__doc__.lstrip('\n').rstrip()) def do_pep8(base_dir, opt_list=ARGLIST, output_statics=OUTPUT_STATICS): u""""base_dir以下のファイルに対して、pep8のチェックを行います""" store = StoreMessages() pep8_message = pep8.message pep8.message = store.message pep8_report_error = pep8.Checker.report_error pep8.Checker.report_error = report_error arglist = opt_list[:] arglist.append(base_dir) options, args = pep8.process_options(arglist) runner = pep8.input_file for path in args: if os_path.isdir(path): pep8.input_dir(path, runner=runner) elif not pep8.excluded(path): options.counters['files'] += 1 runner(path) if output_statics: store.message(LINES) for statics_mes in pep8.get_statistics(): store.message(statics_mes) store.message(LINES) pep8.message = pep8_message pep8.Checker.report_error = pep8_report_error errors = pep8.get_count('E') warnings = pep8.get_count('W') message = 'pep8: %d errors / %d warnings' % (errors, warnings) message += NEW_LINE + store.get_message() return errors, warnings, message class Test_pep8(unittest.TestCase): def test_pep8(self): errors, warnings, message = do_pep8(BASE_DIR) self.assertEqual(errors + warnings, 0, message) def main(): print u'対象フォルダ:', BASE_DIR print u'パラメータ:', ARGLIST DEF_ENCODE = ENCODE_FOR_CMD unittest.main() if __name__ == '__main__': main() # EOF
<<設定について>>
BASE_DIR:ここで指定されたパスを起点にPEP8のチェックをします。
このソース例だと、このモジュールのおいてあるフォルダの上の階層を指定していますが、
これは、TESTケースは1つのパッケージに集めると想定だからです。
(特に、GoogleAppEngineの開発では、本番に単体テストのコードをアップロードさせないようにする為にも、一つのパッケージに集まると思います)
EXCLUDE_PATTERNS = PEP8のチェックから外したいディレクトリをリストで指定します。
GAEだと、wtformsとかの外部のライブラリをソースディレクトリに含める必要があります。規約は無視しているライブラリを、PEP8チェックから外すためのパラメータです。
IGNORE_PATTERNS = PEP8のチェック対象外としてい項目をしていします。
イッシーは、E501(1行の文字数制限)とか、嫌いなので外してたりします。
ENCODE_FOR_ECLIPSE = 外部のテストランナから呼ばれた時の出力文字コード
ENCODE_FOR_CMD = コマンドプロンプトから実行した時の出力文字コード
Windowsのコマンドプロンプトの文字コードは、mbcsなのですが、
PyDevのプロンプトはutf-8なので、分けてます。
Windows + PyDevじゃない人は、ほかの設定のほうがよいかも。
DECODE_LIST = PEP8が出力してくるログをデコードする際の文字コードのリスト
リスト順にデコードします。
<<ソースの適当解説>>
49~117行目は、pep8の出力を整形するためのコードです。
123~127行目で、既存のpep8のコードを整形するためのコードで上書きしてます。
そのままだと気持ちが悪いので、149~150行目で、元に戻しています。
でも、なんかもっと良い書き方がありそうですねー。ご意見募集中です。
0 件のコメント:
コメントを投稿