Files
Yogstation/tools/ColonCatcher/ColonCatcher.py

163 lines
6.4 KiB
Python

# Colon Locater and Reporter, by RemieRichards V1.0 - 25/10/15
# Locates the byond operator ":", which is largely frowned upon due to the fact it ignores any type safety.
# This tool produces a .txt of "filenames line,line?,line totalcolons" (where line? represents a colon in a ternary operation) from all
# .dm files in the /code directory of an SS13 codebase, but can work on any Byond project if you modify scan_dir and real_dir
# the .txt take's todays date in reverse order and adds -colon_operator_log to the end, eg: "2015/10/25-colon_operator_log.txt"
import sys
import os
from datetime import date
#Climbs up from /tools/ColonCatcher and along to ../code
scan_dir = "code" #used later to truncate log file paths
real_dir = os.path.abspath("../../"+scan_dir)
#Scan a directory, scanning any dm files it finds
def colon_scan_dir(scan_dir):
if os.path.exists(scan_dir):
if os.path.isdir(scan_dir):
output_str = ""
files_scanned = 0
files_with_colons = 0
for root, dirs, files in os.walk(scan_dir):
for f in files:
print str(f)
scan_result = scan_dm_file(os.path.join(root, f))
files_scanned += 1
if scan_result:
output_str += scan_result+"\n"
files_with_colons += 1
output_str += str(files_with_colons) + "/" + str(files_scanned) + " files have colons in them"
todays_file = str(date.today())+"-colon_operator_log.txt"
output_file = open(todays_file, "w") #w so it overrides existing files for today, there should only really be one file per day
output_file.write(output_str)
#Scan one file, returning a string as a "report" or if there are no colons, False
def scan_dm_file(_file):
if not _file.endswith(".dm"):
return False
with open(_file, "r") as dm_file:
characters = dm_file.read()
line_num = 1
colon_count = 0
last_char = ""
in_embed_statement = 0 # [ ... ] num due to embeds in embeds
in_multiline_comment = 0 #/* ... */ num due to /* /* */ */
in_singleline_comment = False #// ... \n
in_string = False # " ... "
ternary_on_line = False #If there's a ? anywhere on the line, used to report "false"-positives
lines_with_colons = []
for char in characters:
#Line info
if char == "\n" or char == "\r":
if not in_string:
ternary_on_line = False #Stop any old ternary operation
line_num += 1
in_embed_statement = 0
#Not in a comment
if (not in_singleline_comment) and (in_multiline_comment == 0):
#Not in a string
if not in_string:
if last_char == "/":
if char == "/":
in_singleline_comment = True
if char == "*":
in_multiline_comment += 1
if char == "\"":
in_string = True
#In a string
else:
if char == "\"": #Only " ends a string, as byond supports multiline strings
if last_char != "\\": #make sure it's a real " not an escaped one (\")
in_string = False
#It's not an embedded statment if it's not in a string
if char == "[":
in_embed_statement += 1
if char == "]":
in_embed_statement -= 1
in_embed_statement = max(in_embed_statement,0)
#ternary statements, True when in_embed_statement+in_string OR when not in_string
if char == "?":
if in_string:
if in_embed_statement != 0:
ternary_on_line = True
else:
ternary_on_line = True
#A Colon!
#If we're in a string, but not embedded: Ok, it's just rawtext
#If we're in a string, and embedded but NOT in a ternary operation: Bad, guaranteed to be a : used to avoid typechecks
#If we're in a string, and embedded AND in a ternary operation: Potentially Bad, this could be a : used to avoid typechecks (bad) or the middle : of the ternary operation (generally ok)
if char == ":":
if not in_string:
colon_count += 1
data = str(line_num)
if ternary_on_line:
data += "?"
if not data in lines_with_colons: #only add the line twice if it's like: 76, 76? (eg: a "bad" colon and a ternary colon)
lines_with_colons.append(data)
else:
if in_embed_statement != 0:
colon_count += 1
data = str(line_num)
if ternary_on_line:
data += "?"
if not data in lines_with_colons:
lines_with_colons.append(data)
#In a comment
else:
if char == "/":
if last_char == "*":
in_multiline_comment -= 1
in_multiline_comment = max(in_multiline_comment,0)
if char == "\n" or char == "\r":
in_singleline_comment = False
if char != "": #Spaces aren't useful to us
last_char = char
if colon_count:
file_report = ".."+scan_dir+str(_file).split(scan_dir)[1]+" " #crop it down to ..\code\DIR\FILE.dm, everything else is developer specific
first = True
for line in lines_with_colons:
if first:
first = False
file_report += "Lines: "+line
else:
file_report += ", "+line
file_report += " Total Colons: "+str(colon_count)
return file_report
else:
return False
colon_scan_dir(real_dir)
print "Done!"