--- /dev/null
+#! /usr/bin/env python
+
+# Copyright (C) 2010 Christian Dywan <christian@twotoasts.de>
+# Copyright (C) 2010 Arno Renevier <arno@renevier.net>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# See the file COPYING for the full license text.
+#
+# check-style: Verify C source code according to coding style.
+
+import glob, re, string, subprocess, sys, os
+
+if len (sys.argv) < 2:
+ name = os.path.basename (sys.argv[0])
+ print ('Usage:\n ' + name + ' FILENAMES\n'
+ ' Pass "-" to read stdin, eg. "cat my-feature.diff | ' + name + ' -"\n'
+ ' Pass "." to mean "git diff ^HEAD | ' + name + ' -"')
+ sys.exit (1)
+
+# Coding style violations
+violations = [
+ ['.{101}', 'Line longer than 100 columns'],
+ ['^[ ]{1,3}[^ ]+', 'Indentation is less than 4 spaces'],
+ # FIXME: Don't match empty strings
+ # FIXME: Don't match indented function arguments
+ # ['^(?!(([ ]{4})*[^ ]+))', 'Indentation is not 4 spaces'],
+ ['.*[ ]+$', 'Trailing whitespace'],
+ [r"\t+", 'Tabs instead of spaces'],
+ ["[^0-9],[^ ][^0-9]", 'No space after comma'],
+ # ['(([A-Z][a-z]+)+)\*?[ ][a-z]{2,}', 'Good variable name'],
+ # ['(g?char|g(boolean|pointer))\*?[ ][a-z]{2,}', 'Good variable name'],
+ # ['(g?int|guint)[ ][a-z]+', 'Good iterator name'],
+ # ['(struct)[ ]+[_]?([A-Z][a-z]+)+', 'Good type name'],
+ ['^\s*\w+(?<!\\breturn)\s+\*\s*\w*\s*[;,]', 'Space between type and asterisk'],
+ ["(\w[+|-|*|/|<|>|=]{1,2}\w)", 'No space around operators'],
+ ["\/\*[^ *\n]", 'No space after open comment'],
+ ['[^ *]\*\/', 'No space before close comment'],
+ ['\)\{', 'No space between ) and {'],
+ [';[^ \s]', 'No space or newline after semicolon'],
+ # ['(if)( \([^ ].*[^ ]\))$', 'Good if style'],
+ ['^#\s+(if(n?def)?|define|else|elif)[ ].*$', 'Space between # and cpp'],
+ [r'^\s*\*\w+(\+\+|--);', 'Invalid increment, use (*i)++ or *i += 1'],
+ ['asctime|ctime|getgrgid|getprgnam|getlogin \
+ |getpwnam|getpwuid|gmtime|localtime \
+ |rand|readdir|strtok|ttyname', 'Not thread-safe posix, use _r variant'],
+]
+# No validation for strings, comments, includes
+omissions = [
+ [r'["]{1}.*["]', 'STRING'],
+ ["'\\\?.'", 'CHAR'],
+ ["^\s*\/\*.*\*\/\s*$", 'COMMENT'],
+ ['#include <.*>', 'INCLUDE'],
+]
+
+# Output format
+fmt = '%s - %d: %s'
+
+# Pre-compile expressions
+for violation in violations:
+ violation[0] = re.compile (violation[0])
+for omission in omissions:
+ omission[0] = re.compile (omission[0])
+
+for filename_or_glob in sys.argv[1:]:
+ if filename_or_glob == '-':
+ handles = [sys.stdin]
+ else:
+ handles = []
+ for filename in glob.glob (filename_or_glob):
+ if os.path.isdir (filename):
+ gitdiff = subprocess.Popen (['git', 'diff', '^HEAD',
+ '--relative', filename], stdout=subprocess.PIPE)
+ handles.append (gitdiff.stdout)
+ else:
+ handles.append (open (filename))
+ if not handles:
+ print (filename_or_glob + ' not found')
+ sys.exit (1)
+
+ for handle in handles:
+ previous = ''
+ i = 0
+ mode = ''
+ filename = handle.name
+ comment = False
+ curly = []
+
+ for line in handle:
+ line = line[:-1]
+ i += 1
+
+ # Parse diff, only validate modified lines
+ if i == 1 and 'diff' in line:
+ mode = 'diff'
+ if mode == 'diff':
+ if line[:3] == '+++':
+ filename = line[6:]
+ comment = False
+ curly = []
+ continue
+ if line[:2] == '@@':
+ i = int (line.split (' ')[2].split (',')[0][1:]) - 1
+ curly = []
+ if line[0] == '-':
+ i = i -1
+ if line[0] != '+':
+ continue
+ line = line[1:]
+
+ # Spurious blank lines
+ if previous == line == '':
+ print (fmt % (filename, i, 'Spurious blank line'))
+ previous = line
+ continue
+ previous = line
+
+ # Skip multi-line comment blocks
+ if '/*' in line and not '*/' in line:
+ comment = True
+ if comment:
+ if '*/' in line and not '/*' in line:
+ comment = False
+ continue
+
+ cleaned = line
+ for omission in omissions:
+ cleaned = omission[0].sub (omission[1], cleaned)
+
+ # Validate curly bracket indentation
+ if '{' in cleaned and not '}' in cleaned:
+ curly.append ((cleaned.index ('{'), cleaned))
+ if '}' in cleaned and not '{' in cleaned and not '},' in cleaned:
+ if len (curly) == 0 or curly[-1][0] != cleaned.index ('}'):
+ print (fmt % (filename, i, 'Misindented curly bracket'))
+ print (curly[-1][1])
+ print (line)
+ curly.pop()
+ continue
+ curly.pop()
+
+ # Validate preprocessor indentation
+ # FIXME: Don't warn if the *following* line is a curly
+ cpp = cleaned.find ('#if')
+ if cpp != -1:
+ if len (curly) != 0 and cpp != curly[-1][0] + 4:
+ print (fmt % (filename, i, 'Misindented preprocessor #if'))
+ print (curly[-1][1])
+ print (line)
+
+ violated = False
+ for violation in violations:
+ if violation[0].search (cleaned):
+ violated = True
+ print (fmt % (filename, i, violation[1]))
+ if violated:
+ print (line)
+