1"""
2Verify that the MoinMoin source files conform (mostly) to PEP8 coding style.
3
4Additionally, we check that the files have no crlf (Windows style) line endings.
5
6@copyright: 2006 by Armin Rigo (originally only testing for tab chars),
7            2007 adapted and extended (calling the PEP8 checker for most stuff) by MoinMoin:ThomasWaldmann.
8@license: MIT licensed
9"""
10
11import os, re, time, stat
12
13import pep8
14
15from MoinMoin.conftest import moindir
16
17ROOT = str(moindir)
18
19EXCLUDE = [
20    '/contrib/DesktopEdition/setup_py2exe.py', # has crlf
21    '/contrib/TWikiDrawPlugin', # 3rd party java stuff
22    '/contrib/flup-server', # 3rd party WSGI adapters
23    '/MoinMoin/support', # 3rd party libs or non-broken stdlib stuff
24    '/MoinMoin/web/static/htdocs', # this is our dist static stuff
25    '/tests/wiki', # this is our test wiki
26    '/wiki/data/pages', # wiki pages, there may be .py attachments
27]
28
29TRAILING_SPACES = 'nochange' # 'nochange' or 'fix'
30                             # use 'fix' with extreme caution and in a separate changeset!
31FIX_TS_RE = re.compile(r' +$', re.M) # 'fix' mode: everything matching the trailing space re will be removed
32
33try:
34    import xattr
35    if not hasattr(xattr, "xattr"): # there seem to be multiple modules with that name
36        raise ImportError
37    def mark_file_ok(path, mtime):
38        x = xattr.xattr(path)
39        try:
40            x.set('user.moin-pep8-tested-mtime', '%d' % mtime)
41        except IOError:
42            # probably not supported
43            mark_file_ok = lambda path, mtime: None
44
45    def should_check_file(path, mtime):
46        x = xattr.xattr(path)
47        try:
48            mt = x.get('user.moin-pep8-tested-mtime')
49            mt = long(mt)
50            return mtime > mt
51        except IOError:
52            # probably not supported
53            should_check_file = lambda path, mtime: True
54        return True
55except ImportError:
56    def mark_file_ok(path, mtime):
57        pass
58    def should_check_file(path, mtime):
59        return True
60
61RECENTLY = time.time() - 7 * 24*60*60 # we only check stuff touched recently.
62#RECENTLY = 0 # check everything!
63
64# After doing a fresh clone, this procedure is recommended:
65# 1. Run the tests once to see if everything is OK (as cloning updates the mtime,
66#    it will test every file).
67# 2. Before starting to make new changes, use "touch" to change all timestamps
68#    to a time before <RECENTLY>.
69# 3. Regularly run the tests, they will run much faster now.
70
71def pep8_error_count(path):
72    # process_options initializes some data structures and MUST be called before each Checker().check_all()
73    pep8.process_options(['pep8', '--ignore=E202,E221,E222,E241,E301,E302,E401,E501,E701,W391,W601,W602', '--show-source', 'dummy_path'])
74    error_count = pep8.Checker(path).check_all()
75    return error_count
76
77def check_py_file(reldir, path, mtime):
78    if TRAILING_SPACES == 'fix':
79        f = file(path, 'rb')
80        data = f.read()
81        f.close()
82        fixed = FIX_TS_RE.sub('', data)
83
84        # Don't write files if there's no need for that,
85        # as altering timestamps can be annoying with some tools.
86        if fixed == data:
87            return
88
89        f = file(path, 'wb')
90        f.write(fixed)
91        f.close()
92    # Please read and follow PEP8 - rerun this test until it does not fail any more,
93    # any type of error is only reported ONCE (even if there are multiple).
94    error_count = pep8_error_count(path)
95    assert error_count == 0
96    mark_file_ok(path, mtime)
97
98def test_sourcecode():
99    def walk(reldir):
100        if reldir in EXCLUDE:
101            #print "Skippping %r..." % reldir
102            return
103        if reldir:
104            path = os.path.join(ROOT, *reldir.split('/'))
105        else:
106            path = ROOT
107        st = os.stat(path)
108        mode = st.st_mode
109        if stat.S_ISREG(mode): # is a regular file
110            if (path.lower().endswith('.py') and st.st_mtime >= RECENTLY and
111                should_check_file(path, st.st_mtime)):
112                yield check_py_file, reldir, path, st.st_mtime
113        elif stat.S_ISDIR(mode): # is a directory
114            for entry in os.listdir(path):
115                if not entry.startswith('.'):
116                    for _ in walk('%s/%s' % (reldir, entry)):
117                        yield _
118
119    global EXCLUDE
120    EXCLUDE = dict([(path, True) for path in EXCLUDE]) # dict lookup is faster
121    for _ in walk(''):
122        yield _
123
124