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