1#!/usr/bin/env python3
2#
3# Script to check if any GNOME games Vala coding guidelines are violated.
4#
5# Copyright (C) 2015 Sahil Sareen (ssareen [ AT ] gnome [ DOT ] org)
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the "Software"), to deal
9# in the Software without restriction, including without limitation the rights
10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# THE SOFTWARE.
24
25# =======================
26#     Sample usage
27# =======================
28#
29# To use it as a pre-commit hook, Update autogen.sh to symlink to this file
30#
31# if [ -d $srcdir/.git ]; then
32#   for HOOK in pre-commit pre-applypatch
33#   do
34#     if [ ! -L $srcdir/.git/hooks/$HOOK ]; then
35#       ln -s ../../../libgnome-games-support/style-checker \
36#         $srcdir/.git/hooks/$HOOK && echo "Enabled $HOOK style checker."
37#     fi
38#   done
39# fi
40#
41# Note: This can be used as the following hooks:
42# Client side
43#  - pre-commit
44#  - pre-applypatch
45#  - pre-push
46# Server side
47#  - pre-receive
48
49import sys
50import re
51import os
52
53
54def find(s, ch):
55    return [i for i, ltr in enumerate(s) if ltr == ch]
56
57
58def main():
59    print("Validating the diff ...")
60
61    p = os.popen('git diff --cached --unified=0', "r")
62
63    # Read the diff
64    lines = []
65    lineNum = 0
66    fileName = ''
67    nIssues = 0
68
69    while True:
70        line = p.readline()
71        if not line:
72            break
73        if line.startswith('@@'):
74            lineNumSearch = re.search(r'.* .* \+(\d+).*', line)
75            lineNum = int(lineNumSearch.groups()[0])
76        elif line.startswith('+'):
77            if line.startswith('+++'):
78                pos = line.rfind('/')
79                fileName = line[pos + 1: -1]
80            elif fileName.endswith('vala'):
81                lines += [(fileName, lineNum, line[1:])]
82                lineNum += 1
83
84    def getLineFileName(line):
85        return line[0]
86
87    def getLineNum(line):
88        return line[1]
89
90    def getLineData(line, pos=-1):
91        if pos == -1:
92            return line[2]
93        else:
94            return line[2][pos]
95
96    def getLineWidth(line):
97        return len(getLineData(line))
98
99    def printIssue(line, msg):
100        print("%s => Line %d %s"
101              % (getLineFileName(line), getLineNum(line), msg))
102
103    for lineNum in range(0, len(lines)):
104        currLine = lines[lineNum]
105
106        # Line Width
107        if getLineWidth(currLine) > 120:
108            printIssue(currLine, "is greater than 120 characters")
109            nIssues += 1
110
111        # Lines with trailing white-space or tabspace
112        if getLineData(currLine).endswith(' \n'):
113            printIssue(currLine, "has trailing whitespace")
114            nIssues += 1
115
116        if getLineData(currLine).endswith('\t\n'):
117            printIssue(currLine, "has trailing tabspace")
118            nIssues += 1
119
120        # Lines with tabspace
121        if '\t' in getLineData(currLine):
122            printIssue(currLine, "has tabspace")
123            nIssues += 1
124
125        # Single whitespace before "{"
126        indexes = find(getLineData(currLine), '{')
127        for index in indexes:
128            if index > 1:
129                if getLineData(currLine, index - 1) not in [' ', '_']:
130                    printIssue(currLine, "missing whitespace before {")
131                    nIssues += 1
132                elif getLineData(currLine, index - 2) == ' ' \
133                    and getLineData(currLine, index - 1) != '_' \
134                    and not (re.findall("\s*{",
135                             getLineData(currLine)) != [] and
136                             getLineData(currLine, 0) in ['\t', ' ']):
137                    printIssue(currLine, "multiple whitespace before {")
138                    nIssues += 1
139
140        # Single whitespace before "("
141        indexes = find(getLineData(currLine), '(')
142        for index in indexes:
143            if index > 1:
144                if getLineData(currLine, index - 1) != ' ' \
145                    and getLineData(currLine, index - 1) \
146                        not in ['_', '(', '&', '*', '-', '$', '!', '\t', '"']:
147                    printIssue(currLine, "missing whitespace before (")
148                    nIssues += 1
149                elif getLineData(currLine, index - 2) == ' ' \
150                    and getLineData(currLine, index - 1) not in ['_', '('] \
151                    and not (getLineData(currLine).startswith(" *") or
152                             getLineData(currLine).startswith("#") or
153                             (re.findall("\s*\(",
154                              getLineData(currLine)) != [] and
155                                 getLineData(currLine, 0) in ['\t', ' '])):
156                    printIssue(currLine, "multiple whitespace before (")
157                    nIssues += 1
158
159        # No whitespace between round brackets "(xyz)"
160        indexes = find(getLineData(currLine), '(')
161        for index in indexes:
162            if index > 1:
163                if getLineData(currLine, index + 1) == ' ':
164                    printIssue(currLine, "has whitespace after (")
165                    nIssues += 1
166
167        indexes = find(getLineData(currLine), ')')
168        for index in indexes:
169            if index > 1:
170                if getLineData(currLine, index - 1) == ' ':
171                    printIssue(currLine, "has whitespace before )")
172                    nIssues += 1
173
174    if nIssues != 0:
175        if nIssues == 1:
176            print("Guideline checker found one issue.")
177        else:
178            print("Guideline checker found " + str(nIssues) + " issues.")
179
180        if os.path.basename(__file__) == 'pre-commit':
181            print("To ignore use `git commit --no-verify`")
182            return -1
183        elif os.path.basename(__file__) == 'pre-push':
184            print("To ignore use `git push --no-verify`")
185            return -1
186        elif os.path.basename(__file__) == 'pre-receive':
187            print("Your push has been rejected.")
188            return -1
189        elif os.path.basename(__file__) == 'pre-applypatch':
190            print("We strongly recommend you to fix these.")
191            print("Continuing anyway....")
192    else:
193        print("Guideline checker found no issues.")
194
195    return 0
196
197if __name__ == "__main__":
198    sys.exit(main())
199