xref: /btstack/doc/manual/markdown_create_examples.py (revision ae3042838ecab39355f8220511b931dbf8f87c8e)
1#!/usr/bin/env python3
2import os, sys, getopt, re
3import subprocess
4
5
6
7# Defines the names of example groups. Preserves the order in which the example groups will be parsed.
8list_of_groups = [
9    "Hello World",
10    "GAP",
11
12    "Low Energy",
13    "Performance",
14
15    "Audio",
16    "SPP Server",
17    "Networking",
18    "HID",
19
20    "Dual Mode",
21    "SDP Queries",
22    "Phone Book Access",
23
24    "Testing"
25]
26
27# Defines which examples belong to a group. Example is defined as [example file, example title].
28list_of_examples = {
29    "Audio"       : [["a2dp_sink_demo"],["a2dp_source_demo"], ["avrcp_browsing_client"],
30                     ["hfp_ag_demo"], ["hfp_hf_demo"],
31                     ["hsp_ag_demo"], ["hsp_hs_demo"],
32                     ["sine_player"], ["mod_player"], ["audio_duplex"]],
33
34    "Dual Mode"   : [["spp_and_gatt_counter"], ["gatt_streamer_server"]],
35
36    "GAP"         : [["gap_inquiry"], ["gap_link_keys"]],
37
38    "Hello World" : [["led_counter"]],
39    "HID"         : [["hid_keyboard_demo"], ["hid_mouse_demo"], ["hid_host_demo"], ["hog_keyboard_demo"], ["hog_mouse_demo"], ["hog_boot_host_demo"]],
40
41    "Low Energy"  : [["gap_le_advertisements"], ["gatt_browser"], ["gatt_counter"], ["gatt_streamer_server"],
42                    ["gatt_battery_query"], ["gatt_device_information_query"], ["gatt_heart_rate_client"],
43                    ["nordic_spp_le_counter"], ["nordic_spp_le_streamer"], ["ublox_spp_le_counter"],
44                    ["sm_pairing_central"], ["sm_pairing_peripheral"],
45                    ["le_credit_based_flow_control_mode_client"], ["le_credit_based_flow_control_mode_server"],
46                    ["att_delayed_response"], ["ancs_client_demo"], ["le_mitm"]],
47
48    "Networking"    :  [["pan_lwip_http_server"], ["panu_demo"]],
49
50    "Performance" : [["le_streamer_client"], ["gatt_streamer_server"], ["le_credit_based_flow_control_mode_client"], ["le_credit_based_flow_control_mode_server"], ["spp_streamer_client"], ["spp_streamer"]],
51    "Phone Book Access" : [["pbap_client_demo"]],
52
53    "SDP Queries" : [["sdp_general_query"], ["sdp_rfcomm_query"], ["sdp_bnep_query"]],
54    "SPP Server"  : [["spp_counter"], ["spp_flowcontrol"]],
55    "Testing"     : [["dut_mode_classic"]]
56}
57
58lst_header = """
59"""
60
61lst_ending = """
62"""
63
64examples_header = """
65"""
66
67example_item = """
68    - [EXAMPLE_TITLE](#sec:EXAMPLE_LABELExample): EXAMPLE_DESC.
69"""
70example_section = """
71
72## EXAMPLE_DESC {#sec:EXAMPLE_LABELExample}
73
74Source Code: [EXAMPLE_TITLE.c](https://github.com/bluekitchen/btstack/tree/GIT_BRANCH/example/EXAMPLE_TITLE.c)
75
76"""
77example_subsection = """
78### SECTION_TITLE
79"""
80
81listing_reference = """[here](#lst:FILE_NAMELISTING_LABEL)
82"""
83
84listing_start = """
85
86~~~~ {#lst:FILE_NAMELISTING_LABEL .c caption="{LISTING_CAPTION}"}
87
88"""
89
90listing_ending = """
91~~~~
92
93"""
94
95def replacePlaceholder(template, title, label):
96    snippet = template.replace("API_TITLE", title).replace("API_LABEL", label)
97    return snippet
98
99def latexText(text, ref_prefix):
100    if not text:
101        return ""
102    brief = text.replace(" in the BTstack manual","")
103
104    refs = re.match(r'.*(Listing\s+)(\w+).*',brief)
105    if refs:
106        brief = brief.replace(refs.group(2), "[here](#lst:"+ref_prefix + refs.group(2)+")")
107
108    return brief
109
110def isEmptyCommentLine(line):
111    return re.match(r'(\s*\*\s*)\n',line)
112
113def isCommentLine(line):
114    return re.match(r'(\s*\*\s*).*',line)
115
116def isEndOfComment(line):
117    return re.match(r'\s*\*/.*', line)
118
119def isNewItem(line):
120    return re.match(r'(\s*\*\s*\-\s*)(.*)',line)
121
122def isTextTag(line):
123    return re.match(r'.*(@text).*', line)
124
125def isItemizeTag(line):
126    return re.match(r'(\s+\*\s+)(-\s)(.*)', line)
127
128def processTextLine(line, ref_prefix):
129    if isTextTag(line):
130        text_line_parts = re.match(r'.*(@text)(.*)', line)
131        return " " + latexText(text_line_parts.group(2), ref_prefix)
132
133    if isItemizeTag(line):
134        text_line_parts = re.match(r'(\s*\*\s*\-\s*)(.*)', line)
135        return "\n- " + latexText(text_line_parts.group(2), ref_prefix)
136
137    text_line_parts = re.match(r'(\s+\*\s+)(.*)', line)
138    if text_line_parts:
139        return " " + latexText(text_line_parts.group(2), ref_prefix)
140    return ""
141
142def getExampleTitle(example_path):
143    example_title = ''
144    with open(example_path, 'r') as fin:
145        for line in fin:
146            parts = re.match(r'.*(EXAMPLE_START)\((.*)\):\s*(.*)(\*/)?\n',line)
147            if parts:
148                example_title = parts.group(3).replace("_",r'\_')
149                continue
150    return example_title
151
152class State:
153    SearchExampleStart = 0
154    SearchListingStart = 1
155    SearchListingPause = 2
156    SearchListingResume = 3
157    SearchListingEnd = 4
158    SearchItemizeEnd = 5
159    ReachedExampleEnd = 6
160
161text_block = ''
162itemize_block = ''
163
164def writeTextBlock(aout, lstStarted):
165    global text_block
166    if text_block and not lstStarted:
167        aout.write(text_block)
168        text_block = ''
169
170def writeItemizeBlock(aout, lstStarted):
171    global itemize_block
172    if itemize_block and not lstStarted:
173        aout.write(itemize_block + "\n\n")
174        itemize_block = ''
175
176def writeListings(aout, infile_name, ref_prefix, git_branch_name):
177    global text_block, itemize_block
178    itemText = None
179    state = State.SearchExampleStart
180    code_in_listing = ""
181    code_identation = "    "
182    skip_code = 0
183
184    with open(infile_name, 'r') as fin:
185        for line in fin:
186            if state == State.SearchExampleStart:
187                parts = re.match(r'.*(EXAMPLE_START)\((.*)\):\s*(.*)(\*/)?\n',line)
188                if parts:
189                    label = parts.group(2).replace("_","")
190                    title = latexText(parts.group(2), ref_prefix)
191                    desc  = latexText(parts.group(3), ref_prefix)
192                    aout.write(example_section.replace("EXAMPLE_TITLE", title).replace("EXAMPLE_DESC", desc).replace("EXAMPLE_LABEL", label).replace("GIT_BRANCH", git_branch_name))
193                    state = State.SearchListingStart
194                continue
195
196            # detect @section
197            section_parts = re.match(r'.*(@section)\s*(.*)(:?\s*.?)\*?/?\n',line)
198            if section_parts:
199                aout.write("\n" + example_subsection.replace("SECTION_TITLE", section_parts.group(2)))
200                continue
201
202            # detect @subsection
203            subsection_parts = re.match(r'.*(@section)\s*(.*)(:?\s*.?)\*?/?\n',line)
204            if section_parts:
205                subsubsection = example_subsection.replace("SECTION_TITLE", section_parts.group(2)).replace('section', 'subsection')
206                aout.write("\n" + subsubsection)
207                continue
208
209            if isTextTag(line):
210                text_block = text_block + "\n\n" + processTextLine(line, ref_prefix)
211                continue
212
213            skip_code = 0
214            lstStarted = state != State.SearchListingStart
215            if text_block or itemize_block:
216                if isEndOfComment(line) or isEmptyCommentLine(line):
217                    skip_code = 1
218                    if itemize_block:
219                        # finish itemize
220                        writeItemizeBlock(aout, lstStarted)
221                    else:
222                        if isEmptyCommentLine(line):
223                            text_block = text_block + "\n\n"
224
225                        else:
226                            writeTextBlock(aout, lstStarted)
227
228
229                else:
230                    if isNewItem(line) and not itemize_block:
231                        skip_code = 1
232                        # finish text, start itemize
233                        writeTextBlock(aout, lstStarted)
234                        itemize_block = "\n " + processTextLine(line, ref_prefix)
235                        continue
236                    if itemize_block:
237                        skip_code = 1
238                        itemize_block = itemize_block + processTextLine(line, ref_prefix)
239                    elif isCommentLine(line):
240                        # append text
241                        skip_code = 1
242                        text_block = text_block + processTextLine(line, ref_prefix)
243                    else:
244                        skip_code = 0
245                #continue
246
247            if state == State.SearchListingStart:
248                parts = re.match(r'.*(LISTING_START)\((.*)\):\s*(.*)(\s+\*/).*',line)
249
250                if parts:
251                    lst_label = parts.group(2).replace("_","")
252                    lst_caption = latexText(parts.group(3), ref_prefix)
253                    listing = listing_start.replace("LISTING_CAPTION", lst_caption).replace("FILE_NAME", ref_prefix).replace("LISTING_LABEL", lst_label)
254                    if listing:
255                        aout.write("\n" + listing)
256                    state = State.SearchListingEnd
257                continue
258
259            if state == State.SearchListingEnd:
260                parts_end = re.match(r'.*(LISTING_END).*',line)
261                parts_pause = re.match(r'.*(LISTING_PAUSE).*',line)
262                end_comment_parts = re.match(r'.*(\*/)\s*\n', line);
263
264                if parts_end:
265                    aout.write(code_in_listing)
266                    code_in_listing = ""
267                    aout.write(listing_ending)
268                    state = State.SearchListingStart
269                    writeItemizeBlock(aout, 0)
270                    writeTextBlock(aout, 0)
271                elif parts_pause:
272                    code_in_listing = code_in_listing + code_identation + "...\n"
273                    state = State.SearchListingResume
274                elif not end_comment_parts:
275                    # aout.write(line)
276                    if not skip_code:
277                        code_in_listing = code_in_listing + code_identation + line.replace("    ", "  ")
278                continue
279
280            if state == State.SearchListingResume:
281                parts = re.match(r'.*(LISTING_RESUME).*',line)
282                if parts:
283                    state = State.SearchListingEnd
284                continue
285
286            parts = re.match(r'.*(EXAMPLE_END).*',line)
287            if parts:
288                if state != State.SearchListingStart:
289                    print("Formating error detected")
290                writeItemizeBlock(aout, 0)
291                writeTextBlock(aout, 0)
292                state = State.ReachedExampleEnd
293                print("Reached end of the example")
294
295
296# write list of examples
297def processExamples(intro_file, examples_folder, examples_ofile, git_branch_name):
298    with open(examples_ofile, 'w') as aout:
299        with open(intro_file, 'r') as fin:
300            for line in fin:
301                aout.write(line)
302
303        for group_title in list_of_groups:
304            if not group_title in list_of_examples: continue
305            examples = list_of_examples[group_title]
306            for example in examples:
307                example_path  = examples_folder + example[0] + ".c"
308                example_title = getExampleTitle(example_path)
309                example.append(example_title)
310
311        aout.write(examples_header)
312        aout.write("\n\n");
313
314        for group_title in list_of_groups:
315            if not group_title in list_of_examples: continue
316            examples = list_of_examples[group_title]
317
318            group_title = group_title + " example"
319            if len(examples) > 1:
320                group_title = group_title + "s"
321            group_title = group_title + ":"
322
323            aout.write("- " + group_title + "\n");
324            for example in examples:
325                ref_prefix = example[0].replace("_", "")
326                title = latexText(example[0], ref_prefix)
327                desc  = latexText(example[1], ref_prefix)
328                aout.write(example_item.replace("EXAMPLE_TITLE", title).replace("EXAMPLE_DESC", desc).replace("EXAMPLE_LABEL", ref_prefix))
329            aout.write("\n")
330        aout.write("\n")
331
332        for group_title in list_of_groups:
333            if not group_title in list_of_examples: continue
334            examples = list_of_examples[group_title]
335
336            for example in examples:
337                file_name = examples_folder + example[0] + ".c"
338                writeListings(aout, file_name, example[0].replace("_",""), git_branch_name)
339
340
341def main(argv):
342    btstackfolder = "../../"
343    git_branch_name = "master"
344
345    cmd = 'markdown_create_examples.py [-r <root_btstackfolder>] [-t <templatefolder>] [-o <output_markdownfolder>]'
346
347    try:
348        opts, args = getopt.getopt(argv,"r:t:o:",["rfolder=","tfolder=","ofolder="])
349    except getopt.GetoptError:
350        print (cmd)
351        sys.exit(2)
352    for opt, arg in opts:
353        if opt == '-h':
354            print (cmd)
355            sys.exit()
356        elif opt in ("-r", "--rfolder"):
357            btstackfolder = arg
358        elif opt in ("-t", "--tfolder"):
359            templatefolder = arg
360        elif opt in ("-o", "--ofolder"):
361            markdownfolder = arg
362
363
364    examples_folder    = btstackfolder + "example/"
365    examples_introfile = templatefolder + "examples_intro.md"
366    outputfile         = markdownfolder + "examples/examples.md"
367
368    print ('Input folder: ', examples_folder)
369    print ('Intro file:   ', examples_introfile)
370    print ('Output file:  ', outputfile)
371
372    try:
373        output = subprocess.check_output("git symbolic-ref --short HEAD", stderr=subprocess.STDOUT, timeout=3, shell=True)
374        git_branch_name = output.decode().rstrip()
375    except subprocess.CalledProcessError as exc:
376        print('GIT branch name: failed to get, use default value \"%s\""  ', git_branch_name, exc.returncode, exc.output)
377    else:
378        print ('GIT branch name :  %s' % git_branch_name)
379
380    processExamples(examples_introfile, examples_folder, outputfile, git_branch_name)
381
382if __name__ == "__main__":
383   main(sys.argv[1:])
384