1#!/usr/bin/python 2# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6""" Parse suite control files and make HTML documentation from included tests. 7 8This program will create a list of test cases found in suite files by parsing 9through each suite control file and making a list of all of the jobs called from 10it. Once it has a list of tests, it will parse the AutoTest control file for 11each test and grab the doc strings. These doc strings, along with any 12constraints in the suite control file, will be added to the original test 13script. These new scripts will be placed in a stand alone directory. Doxygen 14will then use these files for the sole purpose of producing HTML documentation 15for all of the tests. Once HTML docs are created some post processing will be 16done against the docs to change a few strings. 17 18If this script is executed without a --src argument, it will assume it is being 19executed from <ChromeOS>/src/third_party/autotest/files/utils/docgen/ directory. 20 21Classes: 22 23 DocCreator 24 This class is responsible for all processing. It requires the following: 25 - Absolute path of suite control files. 26 - Absolute path of where to place temporary files it constructs from the 27 control files and test scripts. 28 This class makes the following assumptions: 29 - Each master suite has a README.txt file with general instructions on 30 test preparation and usage. 31 - The control file for each test has doc strings with labels of: 32 - PURPOSE: one line description of why this test exists. 33 - CRITERIA: Pass/Failure conditions. 34 - DOC: additional test details. 35 ReadNode 36 This class parses a node from a control file into a key/value pair. In this 37 context, a node represents a syntactic construct of an abstract syntax tree. 38 The root of the tree is the module object (in this case a control file). If 39 suite=True, it will assume the node is from a suite control file. 40 41Doxygen should already be configured with a configuration file called: 42doxygen.conf. This file should live in the same directory with this program. 43If you haven't installed doxygen, you'll need to install this program before 44this script is executed. This program will automatically update the doxygen.conf 45file to match self.src_tests and self.html. 46 47TODO: (kdlucas@google.com) Update ReadNode class to use the replacement module 48for the compiler module, as that has been deprecated. 49""" 50 51__author__ = 'kdlucas@google.com (Kelly Lucas)' 52__version__ = '0.9.1' 53 54import compiler 55import fileinput 56import glob 57import logging 58import optparse 59import os 60import shutil 61import subprocess 62import sys 63 64import fs_find_tests 65 66 67class DocCreator(object): 68 """Process suite control files to combine docstrings and create HTML docs. 69 70 The DocCreator class is designed to parse AutoTest suite control files to 71 find all of the tests referenced, and build HTML documentation based on the 72 docstrings in those files. It will cross reference the test control file 73 and any parameters passed through the suite file, with the original test 74 case. DocCreator relies on doxygen to actually generate the HTML documents. 75 76 The workflow is as follows: 77 - Parse the suite file(s) and generate a test list. 78 - Locate the test source, and grab the docstrings from the associated 79 AutoTest control file. 80 - Combine the docstring from the control file with any parameters passed 81 in from the suite control file, with the original test case. 82 - Write a new test file with the combined docstrings to src_tests. 83 - Create HTML documentation by running doxygen against the tests stored 84 in self.src_tests. 85 86 Implements the following methods: 87 - GetTests() - Parse suite control files, create a dictionary of tests. 88 - ParseControlFiles() - Runs through all tests and parses control files 89 - _CleanDir() - Remove any files in a direcory and create an empty one. 90 - _GetDoctString() - Parses docstrings and joins it with constraints. 91 - _CreateTest() - Add docstrings and constraints to existing test script 92 to form a new test script. 93 - CreateMainPage() - Create a mainpage.txt file based on contents of the 94 suite README file. 95 - _ConfigDoxygen - Updates doxygen.conf to match some attributes this 96 script was run with. 97 - RunDoxygen() - Executes the doxygen program. 98 - CleanDocs() - Changes some text in the HTML files to conform to our 99 naming conventions and style. 100 101 Depends upon class ReadNode. 102 """ 103 def __init__(self, options, args, logger): 104 """Parse command line arguments and set some initial variables.""" 105 106 self.options = options 107 self.args = args 108 self.logger = logger 109 110 # Make parameters a little shorter by making the following assignments. 111 if options.all_tests: 112 self.suite = 'suite_All' 113 else: 114 self.suite = self.options.suite 115 116 self.autotest_root = self.options.autotest_dir 117 self.debug = self.options.debug 118 self.docversion = self.options.docversion 119 self.doxyconf = self.options.doxyconf 120 self.html = '%s_%s' % (self.suite, self.options.html) 121 self.latex = self.options.latex 122 self.layout = self.options.layout 123 self.logfile = self.options.logfile 124 self.readme = self.options.readme 125 self.src_tests = '%s_%s' % (self.suite, self.options.src_tests) 126 127 self.testcase = {} 128 self.testcase_src = {} 129 130 self.site_dir = os.path.join(self.autotest_root, 'client', 'site_tests') 131 self.test_dir = os.path.join(self.autotest_root, 'client', 'tests') 132 self.suite_dir = os.path.join(self.site_dir, self.suite) 133 134 self.logger.debug('Executing with debug level: %s', self.debug) 135 self.logger.debug('Writing to logfile: %s', self.logfile) 136 self.logger.debug('New test directory: %s', self.src_tests) 137 self.logger.debug('Test suite: %s', self.suite) 138 139 self.suitename = { 140 'suite_All': 'All Existing Autotest Tests', 141 'suite_Factory': 'Factory Testing', 142 'suite_HWConfig': 'Hardware Configuration', 143 'suite_HWQual': 'Hardware Qualification', 144 } 145 146 def GetAllTests(self): 147 """Create list of all discovered tests.""" 148 for path in [ 'server/tests', 'server/site_tests', 'client/tests', 149 'client/site_tests']: 150 test_path = os.path.join(self.autotest_root, path) 151 if not os.path.exists(test_path): 152 continue 153 self.logger.info("Scanning %s", test_path) 154 tests, tests_src = fs_find_tests.GetTestsFromFS(test_path, 155 self.logger) 156 test_intersection = set(self.testcase) & set(tests) 157 if test_intersection: 158 self.logger.warning("Duplicates found: %s", test_intersection) 159 self.testcase.update(tests) 160 self.testcase_src.update(tests_src) 161 162 def GetTestsFromSuite(self): 163 """Create list of tests invoked by a suite.""" 164 165 suite_search = os.path.join(self.suite_dir, 'control.*') 166 for suitefile in glob.glob(suite_search): 167 self.logger.debug('Scanning %s for tests', suitefile) 168 if os.path.isfile(suitefile): 169 try: 170 suite = compiler.parseFile(suitefile) 171 except SyntaxError, e: 172 self.logger.error('Error parsing (gettests): %s\n%s', 173 suitefile, e) 174 raise SystemExit 175 176 # Walk through each node found in the control file, which in our 177 # case will be a call to a test. compiler.walk() will walk through 178 # each component node, and call the appropriate function in class 179 # ReadNode. The returned key should be a string, and the name of a 180 # test. visitor.value should be any extra arguments found in the 181 # suite file that are used with that test case. 182 for n in suite.node.nodes: 183 visitor = ReadNode(suite=True) 184 compiler.walk(n, visitor) 185 if len(visitor.key) > 1: 186 filtered_input = '' 187 # Lines in value should start with ' -' for bullet item. 188 if visitor.value: 189 lines = visitor.value.split('\n') 190 for line in lines: 191 if line.startswith(' -'): 192 filtered_input += line + '\n' 193 # A test could be called multiple times, so see if the key 194 # already exists, and if so append the new value. 195 if visitor.key in self.testcase: 196 s = self.testcase[visitor.key] + filtered_input 197 self.testcase[visitor.key] = s 198 else: 199 self.testcase[visitor.key] = filtered_input 200 201 def GetTests(self): 202 """Create dictionary of tests based on suite control file contents.""" 203 if self.options.all_tests: 204 self.GetAllTests() 205 else: 206 self.GetTestsFromSuite() 207 208 def _CleanDir(self, directory): 209 """Ensure the directory is available and empty. 210 211 Args: 212 directory: string, path of directory 213 """ 214 215 if os.path.isdir(directory): 216 try: 217 shutil.rmtree(directory) 218 except IOError, err: 219 self.logger.error('Error cleaning %s\n%s', directory, err) 220 try: 221 os.makedirs(directory) 222 except IOError, err: 223 self.logger.error('Error creating %s\n%s', directory, err) 224 self.logger.error('Check your permissions of %s', directory) 225 raise SystemExit 226 227 def LocateTest(self, test_name): 228 """Determine the full path location of the test.""" 229 if test_name in self.testcase_src: 230 return os.path.join(self.testcase_src[test_name], test_name) 231 232 test_dir = os.path.join(self.site_dir, test_name) 233 if not os.path.isdir(test_dir): 234 test_dir = os.path.join(self.test_dir, test_name) 235 if os.path.isdir(test_dir): 236 return test_dir 237 238 self.logger.warning('Cannot find test: %s', test) 239 return None 240 241 242 def ParseControlFiles(self): 243 """Get docstrings from control files and add them to new test scripts. 244 245 This method will cycle through all of the tests and attempt to find 246 their control file. If found, it will parse the docstring from the 247 control file, add this to any parameters found in the suite file, and 248 add this combined docstring to the original test. These new tests will 249 be written in the self.src_tests directory. 250 """ 251 # Clean some target directories. 252 for d in [self.src_tests, self.html]: 253 self._CleanDir(d) 254 255 for test in self.testcase: 256 test_dir = self.LocateTest(test) 257 if test_dir: 258 control_file = os.path.join(test_dir, 'control') 259 test_file = os.path.join(test_dir, test + '.py') 260 docstring = self._GetDocString(control_file, test) 261 self._CreateTest(test_file, docstring, test) 262 263 def _GetDocString(self, control_file, test): 264 """Get the docstrings from control file and join to suite file params. 265 266 Args: 267 control_file: string, absolute path to test control file. 268 test: string, name of test. 269 Returns: 270 string: combined docstring with needed markup language for doxygen. 271 """ 272 273 # Doxygen needs the @package marker. 274 package_doc = '## @package ' 275 # To allow doxygen to use special commands, we must use # for comments. 276 comment = '# ' 277 endlist = ' .\n' 278 control_dict = {} 279 output = [] 280 temp = [] 281 tempstring = '' 282 docstring = '' 283 keys = ['\\brief\n', '<H3>Pass/Fail Criteria:</H3>\n', 284 '<H3>Author</H3>\n', '<H3>Test Duration</H3>\n', 285 '<H3>Category</H3>\n', '<H3>Test Type</H3>\n', 286 '<H3>Test Class</H3>\n', '<H3>Notest</H3>\n', 287 ] 288 289 if not os.path.isfile(control_file): 290 self.logger.error('Cannot find: %s', control_file) 291 return None 292 try: 293 control = compiler.parseFile(control_file) 294 except SyntaxError, e: 295 self.logger.error('Error parsing (docstring): %s\n%s', 296 control_file, e) 297 return None 298 299 for n in control.node.nodes: 300 visitor = ReadNode() 301 compiler.walk(n, visitor) 302 control_dict[visitor.key] = visitor.value 303 304 for k in keys: 305 if k in control_dict: 306 if len(control_dict[k]) > 1: 307 if k != test: 308 temp.append(k) 309 temp.append(control_dict[k]) 310 if control_dict[k]: 311 temp.append(endlist) 312 # Add constraints and extra args after the Criteria section. 313 if 'Criteria:' in k: 314 if self.testcase[test]: 315 temp.append('<H3>Arguments:</H3>\n') 316 temp.append(self.testcase[test]) 317 # '.' character at the same level as the '-' tells 318 # doxygen this is the end of the list. 319 temp.append(endlist) 320 321 output.append(package_doc + test + '\n') 322 tempstring = "".join(temp) 323 lines = tempstring.split('\n') 324 for line in lines: 325 # Doxygen requires a '#' character to add special doxygen commands. 326 comment_line = comment + line + '\n' 327 output.append(comment_line) 328 329 docstring = "".join(output) 330 331 return docstring 332 333 334 def _CreateTest(self, test_file, docstring, test): 335 """Create a new test with the combined docstrings from multiple sources. 336 337 Args: 338 test_file: string, file name of new test to write. 339 docstring: string, the docstring to add to the existing test. 340 test: string, name of the test. 341 342 This method is used to create a temporary copy of a new test, that will 343 be a combination of the original test plus the docstrings from the 344 control file, and any constraints from the suite control file. 345 """ 346 347 class_def = 'class ' + test 348 pathname = os.path.join(self.src_tests, test + '.py') 349 350 # Open the test and write out new test with added docstrings 351 try: 352 f = open(test_file, 'r') 353 except IOError, err: 354 self.logger.error('Error while reading %s\n%s', test_file, err) 355 return 356 lines = f.readlines() 357 f.close() 358 359 try: 360 f = open(pathname, 'w') 361 except IOError, err: 362 self.logger.error('Error creating %s\n%s', pathname, err) 363 return 364 365 for line in lines: 366 if class_def in line and docstring: 367 f.write(docstring) 368 f.write('\n') 369 f.write(line) 370 f.close() 371 372 def CreateMainPage(self, current_dir): 373 """Create a main page to provide content for index.html. 374 375 This method assumes a file named README.txt is located in your suite 376 directory with general instructions on setting up and using the suite. 377 If your README file is in another file, ensure you pass a --readme 378 option with the correct filename. To produce a better looking 379 landing page, use the '-' character for list items. This method assumes 380 os commands start with '$'. 381 """ 382 383 # Define some strings that Doxygen uses for specific formatting. 384 cstart = '/**' 385 cend = '**/' 386 mp = '@mainpage' 387 section_begin = '@section ' 388 vstart = '@verbatim ' 389 vend = ' @endverbatim\n' 390 391 # Define some characters we expect to delineate sections in the README. 392 sec_char = '==========' 393 command_prompt = '$ ' 394 crosh_prompt = 'crosh>' 395 command_cont = '\\' 396 397 command = False 398 comment = False 399 section = False 400 sec_ctr = 0 401 402 if self.options.all_tests: 403 readme_file = os.path.join(current_dir, self.readme) 404 else: 405 readme_file = os.path.join(self.suite_dir, self.readme) 406 mainpage_file = os.path.join(self.src_tests, 'mainpage.txt') 407 408 try: 409 f = open(readme_file, 'r') 410 except IOError, err: 411 self.logger.error('Error opening %s\n%s', readme_file, err) 412 return 413 try: 414 fw = open(mainpage_file, 'w') 415 except IOError, err: 416 self.logger.error('Error opening %s\n%s', mainpage_file, err) 417 return 418 419 lines = f.readlines() 420 f.close() 421 422 fw.write(cstart) 423 fw.write('\n') 424 fw.write(mp) 425 fw.write('\n') 426 427 for line in lines: 428 if sec_char in line: 429 comment = True 430 section = not section 431 elif section: 432 sec_ctr += 1 433 section_name = ' section%d ' % sec_ctr 434 fw.write(section_begin + section_name + line) 435 else: 436 # comment is used to denote when we should start recording text 437 # from the README file. Some of the initial text is not needed. 438 if comment: 439 if command_prompt in line or crosh_prompt in line: 440 line = line.rstrip() 441 if line[-1] == command_cont: 442 fw.write(vstart + line[:-1]) 443 command = True 444 else: 445 fw.write(vstart + line + vend) 446 elif command: 447 line = line.strip() 448 if line[-1] == command_cont: 449 fw.write(line) 450 else: 451 fw.write(line + vend) 452 command = False 453 else: 454 fw.write(line) 455 456 fw.write('\n') 457 fw.write(cend) 458 fw.close() 459 460 def _ConfigDoxygen(self): 461 """Set Doxygen configuration to match our options.""" 462 463 doxy_config = { 464 'ALPHABETICAL_INDEX': 'YES', 465 'EXTRACT_ALL': 'YES', 466 'EXTRACT_LOCAL_METHODS': 'YES', 467 'EXTRACT_PRIVATE': 'YES', 468 'EXTRACT_STATIC': 'YES', 469 'FILE_PATTERNS': '*.py *.txt', 470 'FULL_PATH_NAMES ': 'YES', 471 'GENERATE_TREEVIEW': 'YES', 472 'HTML_DYNAMIC_SECTIONS': 'YES', 473 'HTML_FOOTER': 'footer.html', 474 'HTML_HEADER': 'header.html', 475 'HTML_OUTPUT ': self.html, 476 'INLINE_SOURCES': 'YES', 477 'INPUT ': self.src_tests, 478 'JAVADOC_AUTOBRIEF': 'YES', 479 'LATEX_OUTPUT ': self.latex, 480 'LAYOUT_FILE ': self.layout, 481 'OPTIMIZE_OUTPUT_JAVA': 'YES', 482 'PROJECT_NAME ': self.suitename[self.suite], 483 'PROJECT_NUMBER': self.docversion, 484 'SOURCE_BROWSER': 'YES', 485 'STRIP_CODE_COMMENTS': 'NO', 486 'TAB_SIZE': '4', 487 'USE_INLINE_TREES': 'YES', 488 } 489 490 doxy_layout = { 491 'tab type="mainpage"': 'title="%s"' % 492 self.suitename[self.suite], 493 'tab type="namespaces"': 'title="Tests"', 494 'tab type="namespacemembers"': 'title="Test Functions"', 495 } 496 497 for line in fileinput.input(self.doxyconf, inplace=1): 498 for k in doxy_config: 499 if line.startswith(k): 500 line = '%s = %s\n' % (k, doxy_config[k]) 501 sys.stdout.write(line) 502 503 for line in fileinput.input('header.html', inplace=1): 504 if line.startswith('<H2>'): 505 line = '<H2>%s</H2>\n' % self.suitename[self.suite] 506 sys.stdout.write(line) 507 508 for line in fileinput.input(self.layout, inplace=1): 509 for k in doxy_layout: 510 if line.find(k) != -1: 511 line = line.replace('title=""', doxy_layout[k]) 512 sys.stdout.write(line.rstrip() + '\n') 513 514 def RunDoxygen(self, doxyargs): 515 """Execute Doxygen on the files in the self.src_tests directory. 516 517 Args: 518 doxyargs: string, any command line args to be passed to doxygen. 519 """ 520 521 doxycmd = 'doxygen %s' % doxyargs 522 523 p = subprocess.Popen(doxycmd, shell=True, stdout=subprocess.PIPE, 524 stderr=subprocess.PIPE) 525 stdout, stderr = p.communicate() 526 if p.returncode: 527 self.logger.error('Error while running %s', doxycmd) 528 self.logger.error(stdout) 529 self.logger.error(stderr) 530 else: 531 self.logger.info('%s successfully ran', doxycmd) 532 533 def CreateDocs(self): 534 """Configure and execute Doxygen to create HTML docuements.""" 535 536 # First run doxygen with args to create default configuration files. 537 # Create layout xml file. 538 doxyargs = '-l %s' % self.layout 539 self.RunDoxygen(doxyargs) 540 541 # Create doxygen configuration file. 542 doxyargs = '-g %s' % self.doxyconf 543 self.RunDoxygen(doxyargs) 544 545 # Edit the configuration files to match our options. 546 self._ConfigDoxygen() 547 548 # Run doxygen with configuration file as argument. 549 self.RunDoxygen(self.doxyconf) 550 551 def PostProcessDocs(self, current_dir): 552 """Run some post processing on the newly created docs.""" 553 554 # Key = original string, value = replacement string. 555 replace = { 556 '>Package': '>Test', 557 } 558 559 docpages = os.path.join(self.html, '*.html') 560 files = glob.glob(docpages) 561 for file in files: 562 for line in fileinput.input(file, inplace=1): 563 for k in replace: 564 if line.find(k) != -1: 565 line = line.replace(k, replace[k]) 566 print line, 567 568 logo_image = 'customLogo.gif' 569 html_root = os.path.join(current_dir, self.html) 570 shutil.copy(os.path.join(current_dir, logo_image), html_root) 571 572 # Copy under dashboard. 573 if self.options.dashboard: 574 dashboard_root = os.path.join(self.autotest_root, 'results', 575 'dashboard', 'testdocs') 576 if not os.path.isdir(dashboard_root): 577 try: 578 os.makedirs(dashboard_root) 579 except e: 580 self.logger.error('Error creating %s:%s', dashboard_root, e) 581 return 582 os.system('cp -r %s/* %s' % (html_root, dashboard_root)) 583 os.system('find %s -type d -exec chmod 755 {} \;' % dashboard_root) 584 os.system('find %s -type f -exec chmod 644 {} \;' % dashboard_root) 585 586 self.logger.info('Sanitized documentation completed.') 587 588 589class ReadNode(object): 590 """Parse a compiler node object from a control file. 591 592 Args: 593 suite: boolean, set to True if parsing nodes from a suite control file. 594 """ 595 596 def __init__(self, suite=False): 597 self.key = '' 598 self.value = '' 599 self.testdef = False 600 self.suite = suite 601 self.bullet = ' - ' 602 603 def visitName(self, n): 604 if n.name == 'job': 605 self.testdef = True 606 607 def visitConst(self, n): 608 if self.testdef: 609 self.key = str(n.value) 610 self.testdef = False 611 else: 612 self.value += str(n.value) + '\n' 613 614 def visitKeyword(self, n): 615 if n.name != 'constraints': 616 self.value += self.bullet + n.name + ': ' 617 for item in n.expr: 618 if isinstance(item, compiler.ast.Const): 619 for i in item: 620 self.value += self.bullet + str(i) + '\n' 621 self.value += ' .\n' 622 else: 623 self.value += str(item) + '\n' 624 625 626 def visitAssName(self, n): 627 # To remove section from appearing in the documentation, set value = ''. 628 sections = { 629 'AUTHOR': '', 630 'CRITERIA': '<H3>Pass/Fail Criteria:</H3>\n', 631 'DOC': '<H3>Notes</H3>\n', 632 'NAME': '', 633 'PURPOSE': '\\brief\n', 634 'TIME': '<H3>Test Duration</H3>\n', 635 'TEST_CATEGORY': '<H3>Category</H3>\n', 636 'TEST_CLASS': '<H3>Test Class</H3>\n', 637 'TEST_TYPE': '<H3>Test Type</H3>\n', 638 } 639 640 if not self.suite: 641 self.key = sections.get(n.name, n.name) 642 643 644def ParseOptions(current_dir): 645 """Common processing of command line options.""" 646 647 desc="""%prog will scan AutoTest suite control files to build a list of 648 test cases called in the suite, and build HTML documentation based on 649 the docstrings it finds in the tests, control files, and suite control 650 files. 651 """ 652 parser = optparse.OptionParser(description=desc, 653 prog='CreateDocs', 654 version=__version__, 655 usage='%prog') 656 parser.add_option('--alltests', 657 help='Scan for all tests', 658 action='store_true', 659 default=False, 660 dest='all_tests') 661 parser.add_option('--autotest_dir', 662 help='path to autotest root directory' 663 ' [default: %default]', 664 default=None, 665 dest='autotest_dir') 666 parser.add_option('--dashboard', 667 help='Copy output under dashboard', 668 action='store_true', 669 default=False, 670 dest='dashboard') 671 parser.add_option('--debug', 672 help='Debug level [default: %default]', 673 default='debug', 674 dest='debug') 675 parser.add_option('--docversion', 676 help='Specify a version for the documentation' 677 '[default: %default]', 678 default=None, 679 dest='docversion') 680 parser.add_option('--doxy', 681 help='doxygen configuration file [default: %default]', 682 default=os.path.join(current_dir, 'doxygen.conf'), 683 dest='doxyconf') 684 parser.add_option('--html', 685 help='path to store html docs [default: %default]', 686 default='html', 687 dest='html') 688 parser.add_option('--latex', 689 help='path to store latex docs [default: %default]', 690 default='latex', 691 dest='latex') 692 parser.add_option('--layout', 693 help='doxygen layout file [default: %default]', 694 default=os.path.join(current_dir, 'doxygenLayout.xml'), 695 dest='layout') 696 parser.add_option('--log', 697 help='Logfile for program output [default: %default]', 698 default=os.path.join(current_dir, 'docCreator.log'), 699 dest='logfile') 700 parser.add_option('--readme', 701 help='filename of suite documentation' 702 '[default: %default]', 703 default='README.txt', 704 dest='readme') 705 parser.add_option('--suite', 706 help='Directory name of suite [default: %default]', 707 type='choice', 708 default='suite_HWQual', 709 choices = [ 710 'suite_Factory', 711 'suite_HWConfig', 712 'suite_HWQual', 713 ], 714 dest='suite') 715 parser.add_option('--tests', 716 help='Absolute path of temporary test files' 717 ' [default: %default]', 718 default='testsource', 719 dest='src_tests') 720 return parser.parse_args() 721 722 723def CheckOptions(options, logger): 724 """Verify required command line options.""" 725 726 if not options.autotest_dir: 727 logger.error('You must supply --autotest_dir') 728 raise SystemExit 729 730 if not os.path.isfile(options.doxyconf): 731 logger.error('Unable to locate --doxy: %s', options.doxyconf) 732 raise SystemExit 733 734 if not os.path.isfile(options.layout): 735 logger.error('Unable to locate --layout: %s', options.layout) 736 raise SystemExit 737 738 739def SetLogger(namespace, options): 740 """Create a logger with some good formatting options. 741 742 Args: 743 namespace: string, name associated with this logger. 744 Returns: 745 Logger object. 746 This method assumes logfile and debug are already set. 747 This logger will write to stdout as well as a log file. 748 """ 749 750 loglevel = {'debug': logging.DEBUG, 751 'info': logging.INFO, 752 'warning': logging.WARNING, 753 'error': logging.ERROR, 754 'critical': logging.CRITICAL, 755 } 756 757 logger = logging.getLogger(namespace) 758 c = logging.StreamHandler() 759 h = logging.FileHandler( 760 os.path.join(os.path.abspath('.'), options.logfile)) 761 hf = logging.Formatter( 762 '%(asctime)s %(process)d %(levelname)s: %(message)s') 763 cf = logging.Formatter('%(levelname)s: %(message)s') 764 logger.addHandler(h) 765 logger.addHandler(c) 766 h.setFormatter(hf) 767 c.setFormatter(cf) 768 769 logger.setLevel(loglevel.get(options.debug, logging.INFO)) 770 771 return logger 772 773 774def main(): 775 current_dir = os.path.dirname(sys.argv[0]) 776 options, args = ParseOptions(current_dir) 777 logger = SetLogger('docCreator', options) 778 CheckOptions(options, logger) 779 doc = DocCreator(options, args, logger) 780 doc.GetTests() 781 doc.ParseControlFiles() 782 doc.CreateMainPage(current_dir) 783 doc.CreateDocs() 784 doc.PostProcessDocs(current_dir) 785 786 787if __name__ == '__main__': 788 main() 789