1+ #!/usr/bin/env python3
2+
3+ import fnmatch
4+ import os
5+ import re
6+ import ntpath
7+ import sys
8+ import argparse
9+
10+ def check_config_style (filepath ):
11+ bad_count_file = 0
12+ def pushClosing (t ):
13+ closingStack .append (closing .expr )
14+ closing << Literal ( closingFor [t [0 ]] )
15+
16+ def popClosing ():
17+ closing << closingStack .pop ()
18+
19+ reIsClass = re .compile (r'^\s*class(.*)' )
20+ reIsClassInherit = re .compile (r'^\s*class(.*):' )
21+ reIsClassBody = re .compile (r'^\s*class(.*){' )
22+ reBadColon = re .compile (r'\s*class (.*) :' )
23+ reSpaceAfterColon = re .compile (r'\s*class (.*): ' )
24+ reSpaceBeforeCurly = re .compile (r'\s*class (.*) {' )
25+ reClassSingleLine = re .compile (r'\s*class (.*)[{;]' )
26+
27+ with open (filepath , 'r' , encoding = 'utf-8' , errors = 'ignore' ) as file :
28+ content = file .read ()
29+
30+ # Store all brackets we find in this file, so we can validate everything on the end
31+ brackets_list = []
32+
33+ # To check if we are in a comment block
34+ isInCommentBlock = False
35+ checkIfInComment = False
36+ # Used in case we are in a line comment (//)
37+ ignoreTillEndOfLine = False
38+ # Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block.
39+ # If the next character is a /, it means we end our comment block.
40+ checkIfNextIsClosingBlock = False
41+
42+ # We ignore everything inside a string
43+ isInString = False
44+ # Used to store the starting type of a string, so we can match that to the end of a string
45+ inStringType = '' ;
46+
47+ lastIsCurlyBrace = False
48+ checkForSemiColumn = False
49+
50+ # Extra information so we know what line we find errors at
51+ lineNumber = 1
52+
53+ indexOfCharacter = 0
54+ # Parse all characters in the content of this file to search for potential errors
55+ for c in content :
56+ if (lastIsCurlyBrace ):
57+ lastIsCurlyBrace = False
58+ if c == '\n ' : # Keeping track of our line numbers
59+ lineNumber += 1 # so we can print accurate line number information when we detect a possible error
60+ if (isInString ): # while we are in a string, we can ignore everything else, except the end of the string
61+ if (c == inStringType ):
62+ isInString = False
63+ # if we are not in a comment block, we will check if we are at the start of one or count the () {} and []
64+ elif (isInCommentBlock == False ):
65+
66+ # This means we have encountered a /, so we are now checking if this is an inline comment or a comment block
67+ if (checkIfInComment ):
68+ checkIfInComment = False
69+ if c == '*' : # if the next character after / is a *, we are at the start of a comment block
70+ isInCommentBlock = True
71+ elif (c == '/' ): # Otherwise, will check if we are in an line comment
72+ ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it
73+
74+ if (isInCommentBlock == False ):
75+ if (ignoreTillEndOfLine ): # we are in a line comment, just continue going through the characters until we find an end of line
76+ if (c == '\n ' ):
77+ ignoreTillEndOfLine = False
78+ else : # validate brackets
79+ if (c == '"' or c == "'" ):
80+ isInString = True
81+ inStringType = c
82+ elif (c == '/' ):
83+ checkIfInComment = True
84+ elif (c == '(' ):
85+ brackets_list .append ('(' )
86+ elif (c == ')' ):
87+ if (len (brackets_list ) > 0 and brackets_list [- 1 ] in ['{' , '[' ]):
88+ print ("ERROR: Possible missing round bracket ')' detected at {0} Line number: {1}" .format (filepath ,lineNumber ))
89+ bad_count_file += 1
90+ brackets_list .append (')' )
91+ elif (c == '[' ):
92+ brackets_list .append ('[' )
93+ elif (c == ']' ):
94+ if (len (brackets_list ) > 0 and brackets_list [- 1 ] in ['{' , '(' ]):
95+ print ("ERROR: Possible missing square bracket ']' detected at {0} Line number: {1}" .format (filepath ,lineNumber ))
96+ bad_count_file += 1
97+ brackets_list .append (']' )
98+ elif (c == '{' ):
99+ brackets_list .append ('{' )
100+ elif (c == '}' ):
101+ lastIsCurlyBrace = True
102+ if (len (brackets_list ) > 0 and brackets_list [- 1 ] in ['(' , '[' ]):
103+ print ("ERROR: Possible missing curly brace '}}' detected at {0} Line number: {1}" .format (filepath ,lineNumber ))
104+ bad_count_file += 1
105+ brackets_list .append ('}' )
106+ elif (c == '\t ' ):
107+ print ("ERROR: Tab detected at {0} Line number: {1}" .format (filepath ,lineNumber ))
108+ bad_count_file += 1
109+
110+ else : # Look for the end of our comment block
111+ if (c == '*' ):
112+ checkIfNextIsClosingBlock = True ;
113+ elif (checkIfNextIsClosingBlock ):
114+ if (c == '/' ):
115+ isInCommentBlock = False
116+ elif (c != '*' ):
117+ checkIfNextIsClosingBlock = False
118+ indexOfCharacter += 1
119+
120+ if brackets_list .count ('[' ) != brackets_list .count (']' ):
121+ print ("ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2}" .format (filepath ,brackets_list .count ('[' ),brackets_list .count (']' )))
122+ bad_count_file += 1
123+ if brackets_list .count ('(' ) != brackets_list .count (')' ):
124+ print ("ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2}" .format (filepath ,brackets_list .count ('(' ),brackets_list .count (')' )))
125+ bad_count_file += 1
126+ if brackets_list .count ('{' ) != brackets_list .count ('}' ):
127+ print ("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}" .format (filepath ,brackets_list .count ('{' ),brackets_list .count ('}' )))
128+ bad_count_file += 1
129+
130+ file .seek (0 )
131+ for lineNumber , line in enumerate (file .readlines ()):
132+ if reIsClass .match (line ):
133+ if reBadColon .match (line ):
134+ print (f"WARNING: bad class colon { filepath } Line number: { lineNumber + 1 } " )
135+ # bad_count_file += 1
136+ if reIsClassInherit .match (line ):
137+ if not reSpaceAfterColon .match (line ):
138+ print (f"WARNING: bad class missing space after colon { filepath } Line number: { lineNumber + 1 } " )
139+ if reIsClassBody .match (line ):
140+ if not reSpaceBeforeCurly .match (line ):
141+ print (f"WARNING: bad class inherit missing space before curly braces { filepath } Line number: { lineNumber + 1 } " )
142+ if not reClassSingleLine .match (line ):
143+ print (f"WARNING: bad class braces placement { filepath } Line number: { lineNumber + 1 } " )
144+ # bad_count_file += 1
145+
146+ return bad_count_file
147+
148+ def main ():
149+
150+ print ("Validating Config Style" )
151+
152+ sqf_list = []
153+ bad_count = 0
154+
155+ parser = argparse .ArgumentParser ()
156+ parser .add_argument ('-m' ,'--module' , help = 'only search specified module addon folder' , required = False , default = "" )
157+ args = parser .parse_args ()
158+
159+ for folder in ['addons' , 'optionals' ]:
160+ # Allow running from root directory as well as from inside the tools directory
161+ rootDir = "../" + folder
162+ if (os .path .exists (folder )):
163+ rootDir = folder
164+
165+ for root , dirnames , filenames in os .walk (rootDir + '/' + args .module ):
166+ for filename in fnmatch .filter (filenames , '*.cpp' ):
167+ sqf_list .append (os .path .join (root , filename ))
168+ for filename in fnmatch .filter (filenames , '*.hpp' ):
169+ sqf_list .append (os .path .join (root , filename ))
170+ for filename in fnmatch .filter (filenames , '*.rvmat' ):
171+ sqf_list .append (os .path .join (root , filename ))
172+ for filename in fnmatch .filter (filenames , '*.cfg' ):
173+ sqf_list .append (os .path .join (root , filename ))
174+
175+ for filename in sqf_list :
176+ bad_count = bad_count + check_config_style (filename )
177+
178+ print ("------\n Checked {0} files\n Errors detected: {1}" .format (len (sqf_list ), bad_count ))
179+ if (bad_count == 0 ):
180+ print ("Config validation PASSED" )
181+ else :
182+ print ("Config validation FAILED" )
183+
184+ return bad_count
185+
186+ if __name__ == "__main__" :
187+ sys .exit (main ())
0 commit comments