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