xref: /btstack/doc/manual/markdown_create_apis.py (revision 44ce5de30f3de9a42b0a94ec3bd8c1c3418ed9d6)
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