00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 import glob
00019 import os
00020 import re
00021 import sys
00022 import xml.etree.ElementTree as ET
00023
00024 def main():
00025 """ This is the main program. """
00026
00027
00028 robot_bases = ["loki", "magni"]
00029
00030
00031 launch_file_names = []
00032 for root, directory_names, file_names in os.walk("."):
00033
00034
00035
00036 for file_name in file_names:
00037 full_file_name = os.path.join(root, file_name)
00038 if file_name.endswith(".launch.xml") or \
00039 file_name.endswith(".launch"):
00040 launch_file_names.append(full_file_name)
00041
00042
00043
00044 executable_file_names = []
00045 for root, directory_names, file_names in os.walk("./bin"):
00046 for file_name in file_names:
00047 full_file_name = os.path.join(root, file_name)
00048 if os.access(full_file_name, os.X_OK) and \
00049 os.path.isfile(full_file_name) and \
00050 not full_file_name.endswith("~"):
00051 executable_file_names.append(full_file_name)
00052
00053
00054
00055 launch_file_names.sort()
00056 executable_file_names.sort()
00057
00058
00059 executable_files = []
00060 for executable_file_name in executable_file_names:
00061 executable_file = Executable_File.file_parse(executable_file_name)
00062 executable_files.append(executable_file)
00063
00064
00065 launch_files = []
00066 for launch_file_name in launch_file_names:
00067 launch_file = Launch_File.file_parse(launch_file_name, robot_bases)
00068 launch_files.append(launch_file)
00069
00070
00071 md_file = open("launches.md", "wa")
00072 md_file.write("# Ubiquity Launches\n\n")
00073
00074
00075 md_file.write("The following executables are available in `bin`:\n\n")
00076 for executable_file in executable_files:
00077 executable_file.summary_write(md_file)
00078
00079
00080 md_file.write("The following launch file directories are available:\n\n")
00081 for launch_file in launch_files:
00082 launch_file.summary_write(md_file)
00083
00084
00085 md_file.write("## Executables\n\n")
00086 for executable_file in executable_files:
00087 executable_file.section_write(md_file)
00088
00089
00090 md_file.write("## Launch File Directories\n\n")
00091 for launch_file in launch_files:
00092 launch_file.section_write(md_file)
00093
00094
00095 md_file.close()
00096
00097
00098 launch_files_table = {}
00099 for launch_file in launch_files:
00100 launch_files_table[launch_file.name] = launch_file
00101
00102
00103 for executable_file in executable_files:
00104 executable_file.visit(launch_files_table)
00105
00106
00107 for launch_file in launch_files:
00108 if not launch_file.visited:
00109 print("Launch File: '{0}' is unused".format(launch_file.name))
00110
00111 def macro_replace(match, macros):
00112 """ Replace "$(arg VALUE)" with the value from *macros* and return it.
00113 """
00114
00115
00116
00117 assert isinstance(macros, dict)
00118
00119
00120
00121 splits = match.group().split()
00122
00123
00124 command = splits[0][2:]
00125 value = splits[1][:-1]
00126
00127
00128 result = ""
00129
00130 if command == "arg" and value in macros:
00131
00132 result = macros[value]
00133 else:
00134
00135 result = "[{0}:{1}]".format(command, value)
00136 return result
00137
00138 class Executable_File:
00139 """ *Executable_File* is a class that represents an executable file.
00140 """
00141
00142 def __init__(self, name, summary, overview_lines, launch_base_name):
00143 """ *Executable_File*: Initialize the *Executable_File* object
00144 (i.e. *self*) with *name*, *summary*, *overview_lines*, and
00145 *launch_base_name*.
00146 """
00147
00148
00149 assert isinstance(name, str)
00150 assert isinstance(summary, str)
00151 assert isinstance(overview_lines, list)
00152 assert launch_base_name == None or isinstance(launch_base_name, str)
00153
00154
00155 self.name = name
00156 self.summary = summary
00157 self.overview_lines = overview_lines
00158 self.launch_base_name = launch_base_name
00159
00160 @staticmethod
00161 def file_parse(full_file_name):
00162 """ *Executable_File*: Parse *full_file_name* and scrape out
00163 the usable documentation.
00164 """
00165
00166
00167 assert isinstance(full_file_name, str)
00168 splits = full_file_name.split('/')
00169 executable_name = splits[2]
00170
00171
00172 in_file = open(full_file_name, "ra")
00173 lines = in_file.readlines()
00174 in_file.close()
00175
00176
00177
00178
00179 launch_base_name = None
00180 summary = ""
00181 overview_lines = []
00182 for line in lines:
00183
00184 if line.startswith("##"):
00185
00186 comment_line = line[2:].strip()
00187
00188
00189 if comment_line.startswith("Summary:"):
00190
00191 summary = comment_line[8:].strip()
00192 elif comment_line.startswith("Overview:"):
00193
00194 pass
00195 else:
00196
00197 overview_lines.append(comment_line)
00198
00199
00200 if line.startswith("roslaunch"):
00201
00202 splits = line.split(' ')
00203 launch_file_name = splits[2]
00204
00205
00206
00207 splits = launch_file_name.split('.')
00208 launch_base_name = splits[0]
00209
00210
00211 return Executable_File(
00212 executable_name, summary, overview_lines, launch_base_name)
00213
00214 def section_write(self, md_file):
00215 """ *Executable_File*: Write the section for the *Executable_File*
00216 object (i.e. *self*) out to *md_file*.
00217 """
00218
00219
00220 assert isinstance(md_file, file)
00221
00222
00223 name = self.name
00224 overview_lines = self.overview_lines
00225
00226
00227 md_file.write("### `{0}` Executable:\n\n".format(name))
00228 for overview_line in overview_lines:
00229 md_file.write("{0}\n".format(overview_line))
00230 md_file.write("\n")
00231
00232 def summary_write(self, md_file):
00233 """ *Executable_File*: Write the summary for the *Executable_File*
00234 object (i.e. *self*) out to *md_file*.
00235 """
00236
00237
00238 assert isinstance(md_file, file)
00239
00240
00241 name = self.name
00242 summary = self.summary
00243
00244
00245 md_file.write("* `{0}`: {1}\n\n".format(name, summary))
00246
00247 def visit(self, launch_files_table):
00248 """ *Executable_File*: Recursively visit the *Launch_File* object
00249 referenced by the *Executable_File* object (i.e. *self*).
00250 """
00251
00252
00253 assert isinstance(launch_files_table, dict)
00254
00255
00256 launch_base_name = self.launch_base_name
00257
00258
00259 if launch_base_name == None:
00260
00261 pass
00262 elif launch_base_name in launch_files_table:
00263
00264 launch_file = launch_files_table[launch_base_name]
00265 launch_file.visit(launch_files_table)
00266 else:
00267
00268 print("Executable '{0}' can not find launch file directory '{1}'".
00269 format(self.name, launch_base_name))
00270
00271 class Launch_File:
00272 def __init__(self, name,
00273 argument_comments, requireds, optionals, macros, includes, conditionals):
00274 """ *Launch_File*: Initialize the *Launch_File* object (i.e. *self*)
00275 with *name*, *argument_comments*, *requireds*, *optionals*,
00276 *macros*, *includes*, and *conditionals*.
00277 """
00278
00279
00280 assert isinstance(name, str)
00281 assert isinstance(argument_comments, dict)
00282 assert isinstance(requireds, list)
00283 assert isinstance(optionals, list)
00284 assert isinstance(macros, dict)
00285 assert isinstance(includes, list)
00286 assert isinstance(conditionals, list)
00287
00288
00289 self.name = name
00290 self.argument_comments = argument_comments
00291 self.requireds = requireds
00292 self.optionals = optionals
00293 self.macros = macros
00294 self.includes = includes
00295 self.conditionals = conditionals
00296 self.visited = False
00297
00298 @staticmethod
00299 def file_parse(full_file_name, robot_bases):
00300 """ *Launch_File*: Process one launch file.
00301 """
00302
00303
00304 assert isinstance(full_file_name, str)
00305 assert isinstance(robot_bases, list)
00306
00307
00308 root_path = full_file_name.split('/')
00309 assert len(root_path) >= 1, \
00310 "Root path '{0}' is too short".format(root_path)
00311 launch_file_name = root_path[1]
00312
00313
00314 xml_file = open(full_file_name, "ra")
00315 xml_text = xml_file.read()
00316 xml_file.close()
00317
00318
00319
00320 comments = re.findall("<!--.*?-->", xml_text, re.DOTALL)
00321
00322
00323
00324
00325
00326 argument_comments = {}
00327 for comment in comments:
00328
00329 comment = comment[4:-3]
00330
00331
00332 if not comment.startswith(' '):
00333
00334 colon_index = comment.find(':')
00335 if colon_index >= 0:
00336 argument_name = comment[:colon_index]
00337 comment = comment[colon_index + 1:]
00338
00339
00340
00341
00342 prefix = " "
00343 if argument_name == "Overview":
00344 prefix = ""
00345 lines = comment.split('\n')
00346 for index in range(len(lines)):
00347 lines[index] = prefix + lines[index].strip()
00348
00349 comment = '\n'.join(lines)
00350
00351
00352
00353 argument_comments[argument_name] = comment
00354
00355
00356
00357
00358 try:
00359 tree = ET.fromstring(xml_text)
00360 except ET.ParseError as error:
00361 position = error.position
00362 line = position[0]
00363 column = position[1]
00364 print("XML Error in file '{0}' at line:{1} column:{2}".
00365 format(full_file_name, line, column))
00366 sys.exit(1)
00367 requireds = []
00368 optionals = []
00369
00370
00371
00372 macros = {}
00373 includes = []
00374 conditionals = []
00375
00376
00377 for child in tree:
00378
00379 child_tag = child.tag
00380 attributes = child.attrib
00381 if child_tag == "arg":
00382
00383 name = attributes["name"]
00384 if "default" in attributes:
00385
00386 optionals.append(child)
00387 elif "value" in attributes:
00388
00389 value = attributes["value"]
00390 macros[name] = value
00391
00392 else:
00393
00394 requireds.append(child)
00395 elif child_tag == "include":
00396
00397 if "file" in attributes:
00398
00399 file_name = attributes["file"]
00400 file_name_previous = ""
00401 while file_name_previous != file_name:
00402 file_name_previous = file_name
00403 file_name = re.sub(r"\$\(arg .*?\)",
00404 lambda match: macro_replace(match, macros), file_name)
00405
00406
00407
00408
00409 if file_name.find("[arg:robot_base]") >= 0:
00410
00411
00412
00413
00414
00415
00416 for robot_base in robot_bases:
00417
00418
00419
00420 conditional_file_name = \
00421 re.sub(r"(\[arg:robot_base\])",
00422 robot_base, file_name)
00423
00424
00425
00426
00427 splits = conditional_file_name.split('/')
00428 include_xml_name = splits[-1]
00429 splits = include_xml_name.split('.')
00430 conditional_base_name = splits[0]
00431
00432
00433
00434
00435
00436 conditionals.append(conditional_base_name)
00437 else:
00438
00439 splits = file_name.split('/')
00440 include_xml_name = splits[-1]
00441 splits = include_xml_name.split('.')
00442 include_base_name = splits[0]
00443
00444
00445
00446
00447 includes.append(include_base_name)
00448
00449
00450 launch_file = Launch_File(launch_file_name, argument_comments,
00451 requireds, optionals, macros, includes, conditionals)
00452 return launch_file
00453
00454 def section_write(self, md_file):
00455 """ *Launch_File*: Write out the section for the *Launch_File* object
00456 (i.e. *self*) to *md_file*.
00457 """
00458
00459
00460 assert isinstance(md_file, file)
00461
00462
00463 name = self.name
00464 argument_comments = self.argument_comments
00465 requireds = self.requireds
00466 optionals = self.optionals
00467 macros = self.macros
00468
00469
00470 md_file.write("### `{0}` Launch File Directory\n\n".format(name))
00471
00472
00473 if "Overview" in argument_comments:
00474
00475 overview_comments = argument_comments["Overview"]
00476 md_file.write("{0}\n\n".format(overview_comments))
00477
00478
00479 arguments_count = len(requireds) + len(optionals)
00480 if arguments_count == 0:
00481 md_file.write("This launch file has no arguments.\n\n")
00482 elif arguments_count == 1:
00483 md_file.write("This launch file has the following argument:\n\n")
00484 else:
00485 md_file.write("This launch file has the following arguments:\n\n")
00486
00487
00488 for required in requireds:
00489 attributes = required.attrib
00490 name = attributes["name"]
00491 md_file.write("* {0} (Required):\n".format(name))
00492 if name in argument_comments:
00493 md_file.write("{0}\n".format(argument_comments[name]))
00494 md_file.write("\n")
00495
00496
00497 for optional in optionals:
00498 attributes = optional.attrib
00499 name = attributes["name"]
00500 default = attributes["default"]
00501 md_file.write("* {0} (Optional, default: '{1}'):\n".
00502 format(name, default))
00503 if name in argument_comments:
00504 md_file.write("{0}\n".format(argument_comments[name]))
00505 md_file.write("\n")
00506
00507 def show(self):
00508 """ *Launch_File*: Print short contents of the *Launch_File* object
00509 (i.e. *self*).
00510 """
00511
00512
00513 print("Name: {0}".format(self.name))
00514 for include in self.includes:
00515 print(" Include: {0}".format(include))
00516
00517 def summary_write(self, md_file):
00518 """ *Launch_File*: Write out the summary item for the *Launch_File*
00519 object (i.e. *self*) to *md_file*:
00520 """
00521
00522
00523 assert isinstance(md_file, file)
00524
00525
00526 name = self.name
00527 argument_comments = self.argument_comments
00528
00529
00530 if "Summary" in argument_comments:
00531 md_file.write("* `{0}`:\n{1}\n".
00532 format(name, argument_comments["Summary"]))
00533 else:
00534 md_file.write("* {0}: (No Summary Available)\n".format(name))
00535 md_file.write("\n")
00536
00537 def visit(self, launch_files_table):
00538 """ *Launch_File*: Recursively visit the *Launch_File* object
00539 (i.e. *self*) using *launch_files_table*.
00540 """
00541
00542
00543 assert isinstance(launch_files_table, dict)
00544
00545
00546 if not self.visited:
00547 self.visited = True
00548
00549
00550 for include in self.includes:
00551 if include in launch_files_table:
00552 child = launch_files_table[include]
00553 child.visit(launch_files_table)
00554 else:
00555 print("Launch file '{0}' references non-existant '{1}'".
00556 format(self.name, include))
00557
00558
00559
00560 for conditional in self.conditionals:
00561
00562 if conditional in launch_files_table:
00563 child = launch_files_table[conditional]
00564 child.visit(launch_files_table)
00565 else:
00566 print("Launch file '{0}' uses non-existant base file '{1}'".
00567 format(self.name, conditional))
00568
00569 if __name__ == "__main__":
00570 main()
00571