#!/usr/bin/python
#
#  Check various C source code policy rules and issue warnings for offenders
#

#  Check debug log macro calls in C code for consistency.  Allowed forms:
#
#    DUK_D(DUK_DPRINT(...))
#    DUK_DD(DUK_DDPRINT(...))
#    DUK_DDD(DUK_DDDPRINT(...))
#
#  The calls may span multiple lines, but the wrapper (DUK_D) and the log
#  macro (DUK_DPRINT) must be on the same line.
#
#  Usage:
#
#    $ python check_debuglog_calls.py src/*.c
#

import os
import sys
import re

class Problem:
	filename = None
	linenumber = None
	line = None
	reason = None

	def __init__(self, filename, linenumber, line, reason):
		self.filename = filename
		self.linenumber = linenumber
		self.line = line
		self.reason = reason


re_debuglog_callsite = re.compile(r'^.*?(DUK_D+PRINT).*?$')
re_trailing_ws = re.compile(r'^.*?\s$')
re_only_ws = re.compile(r'^\s*$')

debuglog_wrappers = {
	'DUK_DPRINT': 'DUK_D',
	'DUK_DDPRINT': 'DUK_DD',
	'DUK_DDDPRINT': 'DUK_DDD'
}

problems = []

def checkDebugLogCalls(line):
	if 'DPRINT' not in line:
		return

	m = re_debuglog_callsite.match(line)
	if m is None:
		return

	log_macro = m.group(1)
	log_wrapper = debuglog_wrappers[log_macro]
	if log_wrapper + '(' in line:
		return

	raise Exception('invalid debug log call form')

def checkTrailingWhitespace(line):
	if len(line) > 0 and line[-1] == '\n':
		line = line[:-1]

	m = re_trailing_ws.match(line)
	if m is None:
		return

	raise Exception('trailing whitespace')

def checkCarriageReturns(line):
	if not '\x0d' in line:
		return

	raise Exception('carriage return')

def checkMixedIndent(line):
	if not '\x20\x09' in line:
		return

	# Mixed tab/space are only allowed after non-whitespace characters
	idx = line.index('\x20\x09')
	tmp = line[0:idx]
	m = re_only_ws.match(tmp)
	if m is None:
		return

	raise Exception('mixed space/tab indent (idx %d)' % idx)

def checkFixme(line):
	if not 'FIXME' in line:
		return

	raise Exception('FIXME on line')

def processFile(filename, checkers):
	f = open(filename, 'rb')

	linenumber = 0
	for line in f:
		linenumber += 1
		for fun in checkers:
			try:
				fun(line)
			except Exception as e:
				problems.append(Problem(filename, linenumber, line, str(e)))
	f.close()

def main():
	checkers = []
	checkers.append(checkDebugLogCalls)
	checkers.append(checkTrailingWhitespace)
	checkers.append(checkCarriageReturns)
	checkers.append(checkMixedIndent)
	checkers.append(checkFixme)

	for filename in sys.argv[1:]:
		processFile(filename, checkers)

	if len(problems) > 0:
		for i in problems:
			tmp = 'vim +' + str(i.linenumber)
			while len(tmp) < 10:
				tmp = tmp + ' '
			tmp += ' ' + str(i.filename) + ' : ' + str(i.reason)
			while len(tmp) < 80:
				tmp = tmp + ' '
			tmp += ' - ' + i.line.strip()
			print(tmp)

		print '*** Total: %d problems' % len(problems)
		sys.exit(1)

	sys.exit(0)

if __name__ == '__main__':
	main()

@@ -476,16 +476,32 @@
 mport re
+%0Aimport optparse
 %0A%0Aclass 
@@ -1215,32 +1215,237 @@
 line:%0A%09%09return%0A%0A
+%09# exclude '#define DUK_DPRINT...' macros in duk_debug.h%0A%09if len(line) %3E= 1 and line%5B0%5D == '#':%0A%09%09return%0A%0A%09# exclude a few comment lines in duk_debug.h%0A%09if len(line) %3E= 3 and line%5B0:3%5D == ' * ':%0A%09%09return%0A%0A
 %09raise Exception
@@ -2433,16 +2433,203 @@
 main():%0A
+%09parser = optparse.OptionParser()%0A%09parser.add_option('--dump-vim-commands', dest='dump_vim_commands', default=False, help='Dump oneline vim command')%0A%09(opts, args) = parser.parse_args()%0A%0A
 %09checker
@@ -2839,20 +2839,12 @@
  in 
-sys.argv%5B1:%5D
+args
 :%0A%09%09
@@ -3149,16 +3149,16 @@
 t(tmp)%0A%0A
-
 %09%09print 
@@ -3198,16 +3198,191 @@
 oblems)%0A
+%0A%09%09if opts.dump_vim_commands:%0A%09%09%09cmds = %5B%5D%0A%09%09%09for i in problems:%0A%09%09%09%09cmds.append('vim +' + str(i.linenumber) + ' %22' + i.filename + '%22')%0A%09%09%09print ''%0A%09%09%09print('; '.join(cmds))%0A%0A
 %09%09sys.ex

Add --dump-vim-command option for code policy