-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SQF and Config validation scripts (#1)
* Add basic validator scripts for sqf and configs Adds the basic sqf and config validator scripts from the arma project template by ACE. From https://github.com/acemod/arma-project-template Validator scripts are licensed under MIT. * Add Travis-ci yml configuration file for running validator scripts
- Loading branch information
Showing
3 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
language: python | ||
python: | ||
- '3.4' | ||
script: | ||
- python3 utils/sqf_validator.py | ||
- python3 utils/config_validator.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# From https://github.com/acemod/arma-project-template | ||
# License: MIT | ||
|
||
import fnmatch | ||
import os | ||
import re | ||
import ntpath | ||
import sys | ||
import argparse | ||
|
||
def check_config_style(filepath): | ||
bad_count_file = 0 | ||
def pushClosing(t): | ||
closingStack.append(closing.expr) | ||
closing << Literal( closingFor[t[0]] ) | ||
|
||
def popClosing(): | ||
closing << closingStack.pop() | ||
|
||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as file: | ||
content = file.read() | ||
|
||
# Store all brackets we find in this file, so we can validate everything on the end | ||
brackets_list = [] | ||
|
||
# To check if we are in a comment block | ||
isInCommentBlock = False | ||
checkIfInComment = False | ||
# Used in case we are in a line comment (//) | ||
ignoreTillEndOfLine = False | ||
# Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block. | ||
# If the next character is a /, it means we end our comment block. | ||
checkIfNextIsClosingBlock = False | ||
|
||
# We ignore everything inside a string | ||
isInString = False | ||
# Used to store the starting type of a string, so we can match that to the end of a string | ||
inStringType = ''; | ||
|
||
lastIsCurlyBrace = False | ||
checkForSemiColumn = False | ||
|
||
# Extra information so we know what line we find errors at | ||
lineNumber = 1 | ||
|
||
indexOfCharacter = 0 | ||
# Parse all characters in the content of this file to search for potential errors | ||
for c in content: | ||
if (lastIsCurlyBrace): | ||
lastIsCurlyBrace = False | ||
if c == '\n': # Keeping track of our line numbers | ||
lineNumber += 1 # so we can print accurate line number information when we detect a possible error | ||
if (isInString): # while we are in a string, we can ignore everything else, except the end of the string | ||
if (c == inStringType): | ||
isInString = False | ||
# if we are not in a comment block, we will check if we are at the start of one or count the () {} and [] | ||
elif (isInCommentBlock == False): | ||
|
||
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block | ||
if (checkIfInComment): | ||
checkIfInComment = False | ||
if c == '*': # if the next character after / is a *, we are at the start of a comment block | ||
isInCommentBlock = True | ||
elif (c == '/'): # Otherwise, will check if we are in an line comment | ||
ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it | ||
|
||
if (isInCommentBlock == False): | ||
if (ignoreTillEndOfLine): # we are in a line comment, just continue going through the characters until we find an end of line | ||
if (c == '\n'): | ||
ignoreTillEndOfLine = False | ||
else: # validate brackets | ||
if (c == '"' or c == "'"): | ||
isInString = True | ||
inStringType = c | ||
elif (c == '/'): | ||
checkIfInComment = True | ||
elif (c == '('): | ||
brackets_list.append('(') | ||
elif (c == ')'): | ||
if (len(brackets_list) > 0 and brackets_list[-1] in ['{', '[']): | ||
print("ERROR: Possible missing round bracket ')' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append(')') | ||
elif (c == '['): | ||
brackets_list.append('[') | ||
elif (c == ']'): | ||
if (len(brackets_list) > 0 and brackets_list[-1] in ['{', '(']): | ||
print("ERROR: Possible missing square bracket ']' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append(']') | ||
elif (c == '{'): | ||
brackets_list.append('{') | ||
elif (c == '}'): | ||
lastIsCurlyBrace = True | ||
if (len(brackets_list) > 0 and brackets_list[-1] in ['(', '[']): | ||
print("ERROR: Possible missing curly brace '}}' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append('}') | ||
#elif (c== '\t'): | ||
# print("ERROR: Tab detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
# bad_count_file += 1 | ||
|
||
else: # Look for the end of our comment block | ||
if (c == '*'): | ||
checkIfNextIsClosingBlock = True; | ||
elif (checkIfNextIsClosingBlock): | ||
if (c == '/'): | ||
isInCommentBlock = False | ||
elif (c != '*'): | ||
checkIfNextIsClosingBlock = False | ||
indexOfCharacter += 1 | ||
|
||
if brackets_list.count('[') != brackets_list.count(']'): | ||
print("ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2}".format(filepath,brackets_list.count('['),brackets_list.count(']'))) | ||
bad_count_file += 1 | ||
if brackets_list.count('(') != brackets_list.count(')'): | ||
print("ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2}".format(filepath,brackets_list.count('('),brackets_list.count(')'))) | ||
bad_count_file += 1 | ||
if brackets_list.count('{') != brackets_list.count('}'): | ||
print("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}".format(filepath,brackets_list.count('{'),brackets_list.count('}'))) | ||
bad_count_file += 1 | ||
return bad_count_file | ||
|
||
def main(): | ||
|
||
print("Validating Config Style") | ||
|
||
sqf_list = [] | ||
bad_count = 0 | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="") | ||
args = parser.parse_args() | ||
|
||
# Allow running from root directory as well as from inside the tools directory | ||
rootDir = "../addons" | ||
if (os.path.exists("addons")): | ||
rootDir = "addons" | ||
|
||
for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): | ||
for filename in fnmatch.filter(filenames, '*.cpp'): | ||
sqf_list.append(os.path.join(root, filename)) | ||
for filename in fnmatch.filter(filenames, '*.hpp'): | ||
sqf_list.append(os.path.join(root, filename)) | ||
|
||
for filename in sqf_list: | ||
bad_count = bad_count + check_config_style(filename) | ||
|
||
print("------\nChecked {0} files\nErrors detected: {1}".format(len(sqf_list), bad_count)) | ||
if (bad_count == 0): | ||
print("Config validation PASSED") | ||
else: | ||
print("Config validation FAILED") | ||
|
||
return bad_count | ||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# From https://github.com/acemod/arma-project-template | ||
# License: MIT | ||
|
||
import fnmatch | ||
import os | ||
import re | ||
import ntpath | ||
import sys | ||
import argparse | ||
|
||
def validKeyWordAfterCode(content, index): | ||
keyWords = ["for", "do", "count", "each", "forEach", "else", "and", "not", "isEqualTo", "in", "call", "spawn", "execVM", "catch", "remoteExec"]; | ||
for word in keyWords: | ||
try: | ||
subWord = content.index(word, index, index+len(word)) | ||
return True; | ||
except: | ||
pass | ||
return False | ||
|
||
def check_sqf_syntax(filepath): | ||
bad_count_file = 0 | ||
def pushClosing(t): | ||
closingStack.append(closing.expr) | ||
closing << Literal( closingFor[t[0]] ) | ||
|
||
def popClosing(): | ||
closing << closingStack.pop() | ||
|
||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as file: | ||
content = file.read() | ||
|
||
# Store all brackets we find in this file, so we can validate everything on the end | ||
brackets_list = [] | ||
|
||
# To check if we are in a comment block | ||
isInCommentBlock = False | ||
checkIfInComment = False | ||
# Used in case we are in a line comment (//) | ||
ignoreTillEndOfLine = False | ||
# Used in case we are in a comment block (/* */). This is true if we detect a * inside a comment block. | ||
# If the next character is a /, it means we end our comment block. | ||
checkIfNextIsClosingBlock = False | ||
|
||
# We ignore everything inside a string | ||
isInString = False | ||
# Used to store the starting type of a string, so we can match that to the end of a string | ||
inStringType = ''; | ||
|
||
lastIsCurlyBrace = False | ||
checkForSemiColumn = False | ||
|
||
# Extra information so we know what line we find errors at | ||
lineNumber = 1 | ||
|
||
indexOfCharacter = 0 | ||
# Parse all characters in the content of this file to search for potential errors | ||
for c in content: | ||
if (lastIsCurlyBrace): | ||
lastIsCurlyBrace = False | ||
checkForSemiColumn = True | ||
|
||
if c == '\n': # Keeping track of our line numbers | ||
lineNumber += 1 # so we can print accurate line number information when we detect a possible error | ||
if (isInString): # while we are in a string, we can ignore everything else, except the end of the string | ||
if (c == inStringType): | ||
isInString = False | ||
# if we are not in a comment block, we will check if we are at the start of one or count the () {} and [] | ||
elif (isInCommentBlock == False): | ||
|
||
# This means we have encountered a /, so we are now checking if this is an inline comment or a comment block | ||
if (checkIfInComment): | ||
checkIfInComment = False | ||
if c == '*': # if the next character after / is a *, we are at the start of a comment block | ||
isInCommentBlock = True | ||
elif (c == '/'): # Otherwise, will check if we are in an line comment | ||
ignoreTillEndOfLine = True # and an line comment is a / followed by another / (//) We won't care about anything that comes after it | ||
|
||
if (isInCommentBlock == False): | ||
if (ignoreTillEndOfLine): # we are in a line comment, just continue going through the characters until we find an end of line | ||
if (c == '\n'): | ||
ignoreTillEndOfLine = False | ||
else: # validate brackets | ||
if (c == '"' or c == "'"): | ||
isInString = True | ||
inStringType = c | ||
elif (c == '#'): | ||
ignoreTillEndOfLine = False | ||
elif (c == '/'): | ||
checkIfInComment = True | ||
elif (c == '('): | ||
brackets_list.append('(') | ||
elif (c == ')'): | ||
if (brackets_list[-1] in ['{', '[']): | ||
print("ERROR: Possible missing round bracket ')' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append(')') | ||
elif (c == '['): | ||
brackets_list.append('[') | ||
elif (c == ']'): | ||
if (brackets_list[-1] in ['{', '(']): | ||
print("ERROR: Possible missing square bracket ']' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append(']') | ||
elif (c == '{'): | ||
brackets_list.append('{') | ||
elif (c == '}'): | ||
lastIsCurlyBrace = True | ||
if (brackets_list[-1] in ['(', '[']): | ||
print("ERROR: Possible missing curly brace '}}' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
brackets_list.append('}') | ||
#elif (c== '\t'): | ||
# print("ERROR: Tab detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
# bad_count_file += 1 | ||
|
||
if (checkForSemiColumn): | ||
if (c not in [' ', '\t', '\n', '/']): # keep reading until no white space or comments | ||
checkForSemiColumn = False | ||
if (c not in [']', ')', '}', ';', ',', '&', '!', '|', '='] and not validKeyWordAfterCode(content, indexOfCharacter)): # , 'f', 'd', 'c', 'e', 'a', 'n', 'i']): | ||
print("ERROR: Possible missing semi-column ';' detected at {0} Line number: {1}".format(filepath,lineNumber)) | ||
bad_count_file += 1 | ||
|
||
else: # Look for the end of our comment block | ||
if (c == '*'): | ||
checkIfNextIsClosingBlock = True; | ||
elif (checkIfNextIsClosingBlock): | ||
if (c == '/'): | ||
isInCommentBlock = False | ||
elif (c != '*'): | ||
checkIfNextIsClosingBlock = False | ||
indexOfCharacter += 1 | ||
|
||
if brackets_list.count('[') != brackets_list.count(']'): | ||
print("ERROR: A possible missing square bracket [ or ] in file {0} [ = {1} ] = {2}".format(filepath,brackets_list.count('['),brackets_list.count(']'))) | ||
bad_count_file += 1 | ||
if brackets_list.count('(') != brackets_list.count(')'): | ||
print("ERROR: A possible missing round bracket ( or ) in file {0} ( = {1} ) = {2}".format(filepath,brackets_list.count('('),brackets_list.count(')'))) | ||
bad_count_file += 1 | ||
if brackets_list.count('{') != brackets_list.count('}'): | ||
print("ERROR: A possible missing curly brace {{ or }} in file {0} {{ = {1} }} = {2}".format(filepath,brackets_list.count('{'),brackets_list.count('}'))) | ||
bad_count_file += 1 | ||
return bad_count_file | ||
|
||
def main(): | ||
|
||
print("Validating SQF") | ||
|
||
sqf_list = [] | ||
bad_count = 0 | ||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument('-m','--module', help='only search specified module addon folder', required=False, default="") | ||
args = parser.parse_args() | ||
|
||
# Allow running from root directory as well as from inside the tools directory | ||
rootDir = "../addons" | ||
if (os.path.exists("addons")): | ||
rootDir = "addons" | ||
|
||
for root, dirnames, filenames in os.walk(rootDir + '/' + args.module): | ||
for filename in fnmatch.filter(filenames, '*.sqf'): | ||
sqf_list.append(os.path.join(root, filename)) | ||
|
||
for filename in sqf_list: | ||
if (not ("data." in filename or "objects." in filename or "clusters." in filename)): | ||
bad_count = bad_count + check_sqf_syntax(filename) | ||
|
||
print("------\nChecked {0} files\nErrors detected: {1}".format(len(sqf_list), bad_count)) | ||
if (bad_count == 0): | ||
print("SQF validation PASSED") | ||
else: | ||
print("SQF validation FAILED") | ||
|
||
return bad_count | ||
|
||
if __name__ == "__main__": | ||
sys.exit(main()) |