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