2024-06-02 16:25:48 -03:00
#!/usr/bin/env python3
2022-10-14 12:14:47 -03:00
'''
Reads two lua docs files and checks for differences
python . / libraries / AP_Scripting / tests / docs_check . py " ./libraries/AP_Scripting/docs/docs expected.lua " " ./libraries/AP_Scripting/docs/docs.lua "
AP_FLAKE8_CLEAN
'''
2024-05-02 16:52:19 -03:00
import optparse , sys , re
2022-10-14 12:14:47 -03:00
class method ( object ) :
2024-05-02 16:52:19 -03:00
def __init__ ( self , global_name , local_name , num_args , full_line , returns , params ) :
2022-10-14 12:14:47 -03:00
self . global_name = global_name
self . local_name = local_name
self . num_args = num_args
self . full_line = full_line
2024-05-02 16:52:19 -03:00
self . returns = returns
self . params = params
2024-06-02 16:25:48 -03:00
self . manual = False
for i in range ( len ( self . returns ) ) :
if self . returns [ i ] [ 0 ] == ' UNKNOWN ' :
self . manual = True
for i in range ( len ( self . params ) ) :
if self . params [ i ] [ 0 ] == ' UNKNOWN ' :
self . manual = True
2022-10-14 12:14:47 -03:00
def __str__ ( self ) :
ret_str = " %s \n " % ( self . full_line )
if len ( self . local_name ) :
ret_str + = " \t Class: %s \n " % ( self . global_name )
ret_str + = " \t Function: %s \n " % ( self . local_name )
else :
ret_str + = " \t Global: %s \n " % ( self . global_name )
2024-05-02 16:52:19 -03:00
ret_str + = " \t Num Args: %s \n " % ( self . num_args )
ret_str + = " \t Params: \n "
for param_type in self . params :
ret_str + = " \t \t %s \n " % param_type
ret_str + = " \t Returns: \n "
for return_type in self . returns :
ret_str + = " \t \t %s \n " % return_type
ret_str + = " \n "
2022-10-14 12:14:47 -03:00
return ret_str
2024-05-02 16:52:19 -03:00
def type_compare ( self , A , B ) :
if ( ( ( len ( A ) == 1 ) and ( A [ 0 ] == ' UNKNOWN ' ) ) or
( ( len ( B ) == 1 ) and ( B [ 0 ] == ' UNKNOWN ' ) ) ) :
# UNKNOWN is a special case used for manual bindings
return True
if len ( A ) != len ( B ) :
return False
for i in range ( len ( A ) ) :
if A [ i ] != B [ i ] :
return False
return True
def types_compare ( self , A , B ) :
if len ( A ) != len ( B ) :
return False
for i in range ( len ( A ) ) :
if not self . type_compare ( A [ i ] , B [ i ] ) :
return False
return True
def check_types ( self , other ) :
if not self . types_compare ( self . returns , other . returns ) :
return False
if not self . types_compare ( self . params , other . params ) :
return False
return True
2022-10-14 12:14:47 -03:00
def __eq__ ( self , other ) :
return ( self . global_name == other . global_name ) and ( self . local_name == other . local_name ) and ( self . num_args == other . num_args )
2024-06-02 16:25:48 -03:00
def is_overload ( self , other ) :
# this allows multiple function definitions with different params
2024-06-04 09:32:32 -03:00
white_list = [
" Parameter "
]
allow_override = other . manual or ( self . global_name in white_list )
return allow_override and ( self . global_name == other . global_name ) and ( self . local_name == other . local_name ) and ( self . num_args != other . num_args )
2024-06-02 16:25:48 -03:00
2024-05-02 16:52:19 -03:00
def get_return_type ( line ) :
try :
2024-06-04 09:32:32 -03:00
match = re . findall ( r " ^---@return ( \ w+( \ |( \ w+))*) " , line )
2024-05-02 16:52:19 -03:00
all_types = match [ 0 ] [ 0 ]
return all_types . split ( " | " )
except :
raise Exception ( " Could not get return type in: %s " % line )
def get_param_type ( line ) :
try :
2024-06-04 09:32:32 -03:00
match = re . findall ( r " ^---@param (?: \ w+ \ ??|...) ( \ w+( \ |( \ w+))*) " , line )
2024-05-02 16:52:19 -03:00
all_types = match [ 0 ] [ 0 ]
return all_types . split ( " | " )
except :
raise Exception ( " Could not get param type in: %s " % line )
2022-10-14 12:14:47 -03:00
def parse_file ( file_name ) :
methods = [ ]
2024-05-02 16:52:19 -03:00
returns = [ ]
params = [ ]
2022-10-14 12:14:47 -03:00
with open ( file_name ) as fp :
while True :
line = fp . readline ( )
if not line :
break
2024-05-02 16:52:19 -03:00
# Acuminate return and params to associate with next function
if line . startswith ( " ---@return " ) :
returns . append ( get_return_type ( line ) )
if line . startswith ( " ---@param " ) :
params . append ( get_param_type ( line ) )
2022-10-14 12:14:47 -03:00
# only consider functions
if not line . startswith ( " function " ) :
continue
# remove any comment
line = line . split ( ' -- ' , 1 ) [ 0 ]
# remove any trailing whitespace
line = line . strip ( )
# remove "function"
function_line = line . split ( ' ' , 1 ) [ 1 ]
# remove "end"
function_line = function_line . rsplit ( ' ' , 1 ) [ 0 ]
# remove spaces
function_line = function_line . replace ( " " , " " )
# get arguments
function_name , args = function_line . split ( " ( " , 1 )
2024-05-02 16:52:19 -03:00
args = args [ 0 : args . find ( " ) " ) ]
2022-10-14 12:14:47 -03:00
if len ( args ) == 0 :
num_args = 0
else :
num_args = args . count ( " , " ) + 1
2024-05-02 16:52:19 -03:00
if num_args != len ( params ) :
raise Exception ( " Missing \" ---@param \" for function: %s " , line )
2022-10-14 12:14:47 -03:00
# get global/class name and function name
local_name = " "
if function_name . count ( " : " ) == 1 :
global_name , local_name = function_name . split ( " : " , 1 )
else :
global_name = function_name
2024-05-02 16:52:19 -03:00
methods . append ( method ( global_name , local_name , num_args , line , returns , params ) )
returns = [ ]
params = [ ]
2022-10-14 12:14:47 -03:00
return methods
def compare ( expected_file_name , got_file_name ) :
# parse in methods
expected_methods = parse_file ( expected_file_name )
got_methods = parse_file ( got_file_name )
pass_check = True
# make sure each expected method is included once
for meth in expected_methods :
found = False
for got in got_methods :
if got == meth :
if found :
print ( " Multiple definitions of: " )
print ( meth )
pass_check = False
2024-05-02 16:52:19 -03:00
elif not meth . check_types ( got ) :
print ( " Type error: " )
print ( " Want: " )
print ( meth )
print ( " Got: " )
print ( got )
pass_check = False
2022-10-14 12:14:47 -03:00
found = True
if not found :
print ( " Missing definition of: " )
print ( meth )
pass_check = False
2024-06-04 09:32:32 -03:00
# White list of classes that are allowed unexpected definitions
white_list = [
# "virtual" class to bypass need for nil check when getting a parameter value, Parameter_ud is used internally, Parameter_ud_const exists only in the docs.
" Parameter_ud_const "
]
2022-10-14 12:14:47 -03:00
# make sure no unexpected methods are included
for got in got_methods :
2024-06-04 09:32:32 -03:00
if got . global_name in white_list :
# Dont check if in the white list
continue
2022-10-14 12:14:47 -03:00
found = False
for meth in expected_methods :
2024-06-02 16:25:48 -03:00
if ( got == meth ) or got . is_overload ( meth ) :
2022-10-14 12:14:47 -03:00
found = True
break
if not found :
print ( " Unexpected definition of: " )
print ( got )
pass_check = False
if not pass_check :
raise Exception ( " Docs do not match " )
else :
print ( " Docs check passed " )
if __name__ == ' __main__ ' :
parser = optparse . OptionParser ( __file__ )
opts , args = parser . parse_args ( )
if len ( args ) != 2 :
print ( ' Usage: %s " expected docs path " " current docs path " ' % parser . usage )
sys . exit ( 0 )
compare ( args [ 0 ] , args [ 1 ] )