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