1#!/usr/bin/env python3 2import os, sys, getopt, re, pickle 3import subprocess 4 5class State: 6 SearchTitle = 0 7 SearchEndTitle = 1 8 SearchStartAPI = 2 9 SearchEndAPI = 3 10 DoneAPI = 4 11 12header_files = {} 13functions = {} 14typedefs = {} 15 16linenr = 0 17typedefFound = 0 18multiline_function_def = 0 19state = State.SearchStartAPI 20 21# if dash is used in api_header, the windmill theme will repeat the same API_TITLE twice in the menu (i.e: APIs/API_TITLE/API_TITLE) 22# if <h2> is used, this is avoided (i.e: APIs/API_TITLE), but reference {...} is not translated to HTML 23api_header = """ 24# API_TITLE API {#sec:API_LABEL_api} 25 26""" 27 28api_subheader = """ 29## API_TITLE API {#sec:API_LABEL_api} 30 31""" 32 33api_description = """ 34**FILENAME** DESCRIPTION 35 36""" 37 38code_ref = """GITHUB/FPATH#LLINENR""" 39 40def isEndOfComment(line): 41 parts = re.match(r'\s*\*\/.*', line) 42 if parts: 43 return True 44 return False 45 46def isStartOfComment(line): 47 parts = re.match(r'\s*\/\*\*.*', line) 48 if parts: 49 return True 50 return False 51 52def isComment(line): 53 parts = re.match(r'\s*\/\/.*', line) 54 if parts: 55 return True 56 parts = re.match(r'\s*\*.*', line) 57 if parts: 58 return True 59 60 return isStartOfComment(line) or isEndOfComment(line) 61 62def isTypedefStart(line): 63 return re.match(r'.*typedef\s+struct.*', line) 64 65def codeReference(fname, githuburl, filename_without_extension, filepath, linenr): 66 global code_ref 67 ref = code_ref.replace("GITHUB", githuburl) 68 ref = ref.replace("FPATH", filename_without_extension) 69 ref = ref.replace("LINENR", str(linenr)) 70 return ref 71 72def isTagAPI(line): 73 return re.match(r'(.*)(-\s*\')(APIs).*',line) 74 75def getSecondLevelIdentation(line): 76 indentation = "" 77 parts = re.match(r'(.*)(-\s*\')(APIs).*',line) 78 if parts: 79 # return double identation for the submenu 80 indentation = parts.group(1) + parts.group(1) + "- " 81 return indentation 82 83def filename_stem(filepath): 84 return os.path.splitext(os.path.basename(filepath))[0] 85 86def writeAPI(fout, fin, mk_codeidentation): 87 state = State.SearchStartAPI 88 89 for line in fin: 90 if state == State.SearchStartAPI: 91 parts = re.match('.*API_START.*',line) 92 if parts: 93 state = State.SearchEndAPI 94 continue 95 96 if state == State.SearchEndAPI: 97 parts = re.match('.*API_END.*',line) 98 if parts: 99 state = State.DoneAPI 100 continue 101 fout.write(mk_codeidentation + line) 102 continue 103 104 105 106def createIndex(fin, filename, api_filepath, api_title, api_label, githuburl): 107 global typedefs, functions 108 global linenr, multiline_function_def, typedefFound, state 109 110 111 for line in fin: 112 if state == State.DoneAPI: 113 continue 114 115 linenr = linenr + 1 116 117 if state == State.SearchStartAPI: 118 parts = re.match('.*API_START.*',line) 119 if parts: 120 state = State.SearchEndAPI 121 continue 122 123 if state == State.SearchEndAPI: 124 parts = re.match('.*API_END.*',line) 125 if parts: 126 state = State.DoneAPI 127 continue 128 129 if isComment(line): 130 continue 131 132 param = re.match(".*@brief.*", line) 133 if param: 134 continue 135 param = re.match(".*@param.*", line) 136 if param: 137 continue 138 param = re.match(".*@return.*", line) 139 if param: 140 continue 141 param = re.match(".*@result.*", line) 142 if param: 143 continue 144 param = re.match(".*@note.*", line) 145 if param: 146 continue 147 param = re.match(".*return.*", line) 148 if param: 149 continue 150 151 # search typedef struct begin 152 if isTypedefStart(line): 153 typedefFound = 1 154 155 # search typedef struct end 156 if typedefFound: 157 typedef = re.match(r'}\s*(.*);\n', line) 158 if typedef: 159 typedefFound = 0 160 typedefs[typedef.group(1)] = codeReference(typedef.group(1), githuburl, filename, api_filepath, linenr) 161 continue 162 163 ref_function = re.match(r'.*typedef\s+void\s+\(\s*\*\s*(.*?)\)\(.*', line) 164 if ref_function: 165 functions[ref_function.group(1)] = codeReference(ref_function.group(1), githuburl, filename, api_filepath, linenr) 166 continue 167 168 # filter callback 169 callback_function_definition = re.match(r'(.*?)\s*\(\s*\*.*\(*.*;\n', line) 170 if callback_function_definition: 171 continue 172 173 one_line_function_definition = re.match(r'(.*?)\s*\(.*\(*.*;\n', line) 174 if one_line_function_definition: 175 parts = one_line_function_definition.group(1).split(" "); 176 name = parts[len(parts)-1] 177 if len(name) == 0: 178 print(parts); 179 sys.exit(10) 180 # ignore typedef for callbacks 181 if parts[0] == 'typedef': 182 continue 183 functions[name] = codeReference( name, githuburl, filename, api_filepath, linenr) 184 continue 185 186 multi_line_function_definition = re.match(r'.(.*?)\s*\(.*\(*.*', line) 187 if multi_line_function_definition: 188 parts = multi_line_function_definition.group(1).split(" "); 189 190 name = parts[len(parts)-1] 191 if len(name) == 0: 192 print(parts); 193 sys.exit(10) 194 multiline_function_def = 1 195 functions[name] = codeReference(name, githuburl, filename, api_filepath, linenr) 196 197 198def findTitle(fin): 199 title = None 200 desc = "" 201 state = State.SearchTitle 202 203 for line in fin: 204 if state == State.SearchTitle: 205 if isStartOfComment(line): 206 continue 207 208 parts = re.match(r'.*(@title)(.*)', line) 209 if parts: 210 title = parts.group(2).strip() 211 state = State.SearchEndTitle 212 continue 213 214 if state == State.SearchEndTitle: 215 if (isEndOfComment(line)): 216 state = State.DoneAPI 217 break 218 219 parts = re.match(r'(\s*\*\s*)(.*\n)',line) 220 if parts: 221 desc = desc + parts.group(2) 222 return [title, desc] 223 224def main(argv): 225 global linenr, multiline_function_def, typedefFound, state 226 227 mk_codeidentation = " " 228 git_branch_name = "master" 229 btstackfolder = "../../" 230 githuburl_template = "https://github.com/bluekitchen/btstack/blob/" 231 githuburl = "master/src/" 232 markdownfolder = "docs-markdown/" 233 234 cmd = 'markdown_create_apis.py [-r <root_btstackfolder>] [-g <githuburl>] [-o <output_markdownfolder>]' 235 try: 236 opts, args = getopt.getopt(argv,"r:g:o:",["rfolder=","github=","ofolder="]) 237 except getopt.GetoptError: 238 print (cmd) 239 sys.exit(2) 240 for opt, arg in opts: 241 if opt == '-h': 242 print (cmd) 243 sys.exit() 244 elif opt in ("-r", "--rfolder"): 245 btstackfolder = arg 246 elif opt in ("-g", "--github"): 247 githuburl = arg 248 elif opt in ("-o", "--ofolder"): 249 markdownfolder = arg 250 251 apifile = markdownfolder + "appendix/apis.md" 252 # indexfile = markdownfolder + "api_index.md" 253 btstack_srcfolder = btstackfolder + "src/" 254 255 try: 256 output = subprocess.check_output("git symbolic-ref --short HEAD", stderr=subprocess.STDOUT, timeout=3, shell=True) 257 git_branch_name = output.decode().rstrip() 258 except subprocess.CalledProcessError as exc: 259 print('GIT branch name: failed to get, use default value \"%s\"" ', git_branch_name, exc.returncode, exc.output) 260 else: 261 print ('GIT branch name : %s' % git_branch_name) 262 263 githuburl = githuburl_template + git_branch_name 264 265 print ('BTstack src folder is : ' + btstack_srcfolder) 266 print ('API file is : ' + apifile) 267 print ('Github URL is : ' + githuburl) 268 269 # create a dictionary of header files {file_path : [title, description]} 270 # title and desctiption are extracted from the file 271 for root, dirs, files in os.walk(btstack_srcfolder, topdown=True): 272 for f in files: 273 if not f.endswith(".h"): 274 continue 275 276 if not root.endswith("/"): 277 root = root + "/" 278 279 header_filepath = root + f 280 281 with open(header_filepath, 'rt') as fin: 282 [header_title, header_desc] = findTitle(fin) 283 284 if header_title: 285 header_files[header_filepath] = [header_title, header_desc] 286 else: 287 print("No @title flag found. Skip %s" % header_filepath) 288 289 # create an >md file, for each header file in header_files dictionary 290 for header_filepath in sorted(header_files.keys()): 291 filename = str(header_filepath) 292 filename = filename.split("../../")[1] 293 294 filename_without_extension = filename_stem(header_filepath) # file name without .h 295 filetitle = header_files[header_filepath][0] 296 description = header_files[header_filepath][1] 297 298 if len(description) > 1: 299 description = ": " + description 300 301 header_description = api_description.replace("FILENAME", filename).replace("DESCRIPTION", description) 302 303 header_title = api_header.replace("API_TITLE", filetitle).replace("API_LABEL", filename_without_extension) 304 markdown_filepath = markdownfolder + "appendix/" + filename_without_extension + ".md" 305 306 with open(header_filepath, 'rt') as fin: 307 with open(markdown_filepath, 'wt') as fout: 308 fout.write(header_title) 309 fout.write(header_description) 310 writeAPI(fout, fin, mk_codeidentation) 311 312 with open(header_filepath, 'rt') as fin: 313 linenr = 0 314 typedefFound = 0 315 multiline_function_def = 0 316 state = State.SearchStartAPI 317 createIndex(fin, filename, markdown_filepath, header_title, filename_without_extension, githuburl) 318 319 # add API list to the navigation menu 320 with open("mkdocs-temp.yml", 'rt') as fin: 321 with open("mkdocs.yml", 'wt') as fout: 322 for line in fin: 323 fout.write(line) 324 325 if not isTagAPI(line): 326 continue 327 328 identation = getSecondLevelIdentation(line) 329 330 for header_filepath in sorted(header_files.keys()): 331 header_title = os.path.basename(header_filepath) 332 markdown_reference = "appendix/" + filename_stem(header_filepath) + ".md" 333 334 fout.write(identation + "'" + header_title + "': " + markdown_reference + "\n") 335 336 337 # create mkdocs-latex.yml with single appendix/apis.md reference for pdf generation 338 with open("mkdocs-temp.yml", 'rt') as fin: 339 with open("mkdocs-latex.yml", 'wt') as fout: 340 for line in fin: 341 if not isTagAPI(line): 342 fout.write(line) 343 continue 344 345 fout.write(" - 'APIs': appendix/apis.md\n") 346 347 # create single appendix/apis.md file for pdf generation 348 markdown_filepath = markdownfolder + "appendix/apis.md" 349 with open(markdown_filepath, 'wt') as fout: 350 fout.write("\n# APIs\n\n") 351 for header_filepath in sorted(header_files.keys()): 352 filename = os.path.basename(header_filepath) 353 filename_without_extension = filename_stem(header_filepath) # file name without 354 filetitle = header_files[header_filepath][0] 355 356 description = header_files[header_filepath][1] 357 358 if len(description) > 1: 359 description = ": " + description 360 361 header_description = api_description.replace("FILENAME", filename).replace("DESCRIPTION", description) 362 363 subheader_title = api_subheader.replace("API_TITLE", filetitle).replace("API_LABEL", filename_without_extension) 364 with open(header_filepath, 'rt') as fin: 365 fout.write(subheader_title) 366 fout.write(header_description) 367 writeAPI(fout, fin, mk_codeidentation) 368 369 370 references = functions.copy() 371 references.update(typedefs) 372 373 for function_name, url in references.items(): 374 print(function_name, url) 375 376 # with open(indexfile, 'w') as fout: 377 # for function, reference in references.items(): 378 # fout.write("[" + function + "](" + reference + ")\n") 379 380 pickle.dump(references, open("references.p", "wb" ) ) 381 382if __name__ == "__main__": 383 main(sys.argv[1:]) 384