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