この手の規約を守るのは面倒くさいのですが、
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 件のコメント:
コメントを投稿