-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dcb04e2
commit defa199
Showing
3 changed files
with
205 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,9 @@ | ||
# These are supported funding model platforms | ||
|
||
github: [PulsarNeutronStar] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||
patreon: # Replace with a single Patreon username | ||
open_collective: # Replace with a single Open Collective username | ||
ko_fi: [24cvo] # Replace with a single Ko-fi username | ||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||
custom: # https://www.paypal.me/Vdauphin # Replace with a single custom sponsorship URL |
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,18 @@ | ||
name: SQF Validator | ||
|
||
on: | ||
push: | ||
branches: | ||
- master_daily | ||
- master | ||
|
||
jobs: | ||
run_python: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.9' | ||
architecture: 'x64' | ||
- run: python tools/sqf_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,178 @@ | ||
#!/usr/bin/env python3 | ||
|
||
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", "param", "select", "apply", "findIf", "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 | ||
checkForSemiColon = False | ||
|
||
# Extra information so we know what line we find errors at | ||
lineNumber = 0 | ||
|
||
indexOfCharacter = 0 | ||
# Parse all characters in the content of this file to search for potential errors | ||
for c in content: | ||
if (lastIsCurlyBrace): | ||
lastIsCurlyBrace = False | ||
# Test generates false positives with binary commands that take CODE as 2nd arg (e.g. findIf) | ||
checkForSemiColon = not re.search('findIf', content, re.IGNORECASE) | ||
|
||
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 = True | ||
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 (checkForSemiColon): | ||
if (c not in [' ', '\t', '\n', '/']): # keep reading until no white space or comments | ||
checkForSemiColon = False | ||
if (c not in [']', ')', '}', ';', ',', '&', '!', '|', '='] and not validKeyWordAfterCode(content, indexOfCharacter)): # , 'f', 'd', 'c', 'e', 'a', 'n', 'i']): | ||
print("ERROR: Possible missing semicolon ';' 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 = "./=BTC=co@30_Hearts_and_Minds.Altis" | ||
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: | ||
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()) |