1 | #!/usr/bin/env python |
---|
2 | """This is a pre-commit hook that checks whether the contents of PO files |
---|
3 | committed to the repository are encoded in UTF-8. |
---|
4 | """ |
---|
5 | |
---|
6 | import codecs |
---|
7 | import string |
---|
8 | import sys |
---|
9 | import subprocess |
---|
10 | from svn import core, fs, delta, repos |
---|
11 | |
---|
12 | # Set to the path of the 'msgfmt' executable to use msgfmt to check |
---|
13 | # the syntax of the po file |
---|
14 | |
---|
15 | USE_MSGFMT = None |
---|
16 | |
---|
17 | if USE_MSGFMT is not None: |
---|
18 | class MsgFmtChecker: |
---|
19 | def __init__(self): |
---|
20 | self.pipe = subprocess.Popen([USE_MSGFMT, "-c", "-o", "/dev/null", "-"], |
---|
21 | stdin=subprocess.PIPE, |
---|
22 | close_fds=sys.platform != "win32") |
---|
23 | self.io_error = 0 |
---|
24 | |
---|
25 | def write(self, data): |
---|
26 | if self.io_error: |
---|
27 | return |
---|
28 | try: |
---|
29 | self.pipe.stdin.write(data) |
---|
30 | except IOError: |
---|
31 | self.io_error = 1 |
---|
32 | |
---|
33 | def close(self): |
---|
34 | try: |
---|
35 | self.pipe.stdin.close() |
---|
36 | except IOError: |
---|
37 | self.io_error = 1 |
---|
38 | return self.pipe.wait() == 0 and not self.io_error |
---|
39 | else: |
---|
40 | class MsgFmtChecker: |
---|
41 | def write(self, data): |
---|
42 | pass |
---|
43 | def close(self): |
---|
44 | return 1 |
---|
45 | |
---|
46 | |
---|
47 | class ChangeReceiver(delta.Editor): |
---|
48 | def __init__(self, txn_root, base_root, pool): |
---|
49 | self.txn_root = txn_root |
---|
50 | self.base_root = base_root |
---|
51 | self.pool = pool |
---|
52 | |
---|
53 | def add_file(self, path, parent_baton, |
---|
54 | copyfrom_path, copyfrom_revision, file_pool): |
---|
55 | return [0, path] |
---|
56 | |
---|
57 | def open_file(self, path, parent_baton, base_revision, file_pool): |
---|
58 | return [0, path] |
---|
59 | |
---|
60 | def apply_textdelta(self, file_baton, base_checksum): |
---|
61 | file_baton[0] = 1 |
---|
62 | # no handler |
---|
63 | return None |
---|
64 | |
---|
65 | def close_file(self, file_baton, text_checksum): |
---|
66 | changed, path = file_baton |
---|
67 | if len(path) < 3 or path[-3:] != '.po' or not changed: |
---|
68 | # This is not a .po file, or it hasn't changed |
---|
69 | return |
---|
70 | |
---|
71 | try: |
---|
72 | # Read the file contents through a validating UTF-8 decoder |
---|
73 | subpool = core.svn_pool_create(self.pool) |
---|
74 | checker = MsgFmtChecker() |
---|
75 | try: |
---|
76 | stream = core.Stream(fs.file_contents(self.txn_root, path, subpool)) |
---|
77 | reader = codecs.getreader('UTF-8')(stream, 'strict') |
---|
78 | writer = codecs.getwriter('UTF-8')(checker, 'strict') |
---|
79 | while 1: |
---|
80 | data = reader.read(core.SVN_STREAM_CHUNK_SIZE) |
---|
81 | if not data: |
---|
82 | break |
---|
83 | writer.write(data) |
---|
84 | if not checker.close(): |
---|
85 | sys.exit("PO format check failed for '" + path + "'") |
---|
86 | except UnicodeError: |
---|
87 | sys.exit("PO file is not in UTF-8: '" + path + "'") |
---|
88 | finally: |
---|
89 | core.svn_pool_destroy(subpool) |
---|
90 | |
---|
91 | |
---|
92 | def check_po(pool, repos_path, txn): |
---|
93 | def authz_cb(root, path, pool): |
---|
94 | return 1 |
---|
95 | |
---|
96 | fs_ptr = repos.fs(repos.open(repos_path, pool)) |
---|
97 | txn_ptr = fs.open_txn(fs_ptr, txn, pool) |
---|
98 | txn_root = fs.txn_root(txn_ptr, pool) |
---|
99 | base_root = fs.revision_root(fs_ptr, fs.txn_base_revision(txn_ptr), pool) |
---|
100 | editor = ChangeReceiver(txn_root, base_root, pool) |
---|
101 | e_ptr, e_baton = delta.make_editor(editor, pool) |
---|
102 | repos.dir_delta(base_root, '', '', txn_root, '', |
---|
103 | e_ptr, e_baton, authz_cb, 0, 1, 0, 0, pool) |
---|
104 | |
---|
105 | |
---|
106 | if __name__ == '__main__': |
---|
107 | assert len(sys.argv) == 3 |
---|
108 | core.run_app(check_po, sys.argv[1], sys.argv[2]) |
---|