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('.*(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('(\s*\*\s*)\n',line) 112 113def isCommentLine(line): 114 return re.match('(\s*\*\s*).*',line) 115 116def isEndOfComment(line): 117 return re.match('\s*\*/.*', line) 118 119def isNewItem(line): 120 return re.match('(\s*\*\s*\-\s*)(.*)',line) 121 122def isTextTag(line): 123 return re.match('.*(@text).*', line) 124 125def isItemizeTag(line): 126 return re.match("(\s+\*\s+)(-\s)(.*)", line) 127 128def processTextLine(line, ref_prefix): 129 if isTextTag(line): 130 text_line_parts = re.match(".*(@text)(.*)", line) 131 return " " + latexText(text_line_parts.group(2), ref_prefix) 132 133 if isItemizeTag(line): 134 text_line_parts = re.match("(\s*\*\s*\-\s*)(.*)", line) 135 return "\n- " + latexText(text_line_parts.group(2), ref_prefix) 136 137 text_line_parts = re.match("(\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('.*(EXAMPLE_START)\((.*)\):\s*(.*)(\*/)?\n',line) 147 if parts: 148 example_title = parts.group(3).replace("_","\_") 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('.*(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('.*(@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('.*(@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('.*(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('.*(LISTING_END).*',line) 261 parts_pause = re.match('.*(LISTING_PAUSE).*',line) 262 end_comment_parts = re.match('.*(\*/)\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('.*(LISTING_RESUME).*',line) 282 if parts: 283 state = State.SearchListingEnd 284 continue 285 286 parts = re.match('.*(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