普通文本  |  789行  |  30.38 KB

#!/usr/bin/python
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""" Parse suite control files and make HTML documentation from included tests.

This program will create a list of test cases found in suite files by parsing
through each suite control file and making a list of all of the jobs called from
it. Once it has a list of tests, it will parse the AutoTest control file for
each test and grab the doc strings. These doc strings, along with any
constraints in the suite control file, will be added to the original test
script. These new scripts will be placed in a stand alone directory. Doxygen
will then use these files for the sole purpose of producing HTML documentation
for all of the tests. Once HTML docs are created some post processing will be
done against the docs to change a few strings.

If this script is executed without a --src argument, it will assume it is being
executed from <ChromeOS>/src/third_party/autotest/files/utils/docgen/ directory.

Classes:

  DocCreator
    This class is responsible for all processing. It requires the following:
      - Absolute path of suite control files.
      - Absolute path of where to place temporary files it constructs from the
        control files and test scripts.
    This class makes the following assumptions:
      - Each master suite has a README.txt file with general instructions on
        test preparation and usage.
      - The control file for each test has doc strings with labels of:
        - PURPOSE: one line description of why this test exists.
        - CRITERIA: Pass/Failure conditions.
        - DOC: additional test details.
  ReadNode
    This class parses a node from a control file into a key/value pair. In this
    context, a node represents a syntactic construct of an abstract syntax tree.
    The root of the tree is the module object (in this case a control file). If
    suite=True, it will assume the node is from a suite control file.

Doxygen should already be configured with a configuration file called:
doxygen.conf. This file should live in the same directory with this program.
If you haven't installed doxygen, you'll need to install this program before
this script is executed. This program will automatically update the doxygen.conf
file to match self.src_tests and self.html.

TODO: (kdlucas@google.com) Update ReadNode class to use the replacement module
for the compiler module, as that has been deprecated.
"""

__author__ = 'kdlucas@google.com (Kelly Lucas)'
__version__ = '0.9.1'

import compiler
import fileinput
import glob
import logging
import optparse
import os
import shutil
import subprocess
import sys

import fs_find_tests


class DocCreator(object):
    """Process suite control files to combine docstrings and create HTML docs.

    The DocCreator class is designed to parse AutoTest suite control files to
    find all of the tests referenced, and build HTML documentation based on the
    docstrings in those files. It will cross reference the test control file
    and any parameters passed through the suite file, with the original test
    case. DocCreator relies on doxygen to actually generate the HTML documents.

    The workflow is as follows:
        - Parse the suite file(s) and generate a test list.
        - Locate the test source, and grab the docstrings from the associated
          AutoTest control file.
        - Combine the docstring from the control file with any parameters passed
          in from the suite control file, with the original test case.
        - Write a new test file with the combined docstrings to src_tests.
        - Create HTML documentation by running doxygen against the tests stored
          in self.src_tests.

    Implements the following methods:
        - GetTests() - Parse suite control files, create a dictionary of tests.
        - ParseControlFiles() - Runs through all tests and parses control files
        - _CleanDir() - Remove any files in a direcory and create an empty one.
        - _GetDoctString() - Parses docstrings and joins it with constraints.
        - _CreateTest() - Add docstrings and constraints to existing test script
          to form a new test script.
        - CreateMainPage() - Create a mainpage.txt file based on contents of the
          suite README file.
        - _ConfigDoxygen - Updates doxygen.conf to match some attributes this
          script was run with.
        - RunDoxygen() - Executes the doxygen program.
        - CleanDocs() - Changes some text in the HTML files to conform to our
          naming conventions and style.

    Depends upon class ReadNode.
    """
    def __init__(self, options, args, logger):
        """Parse command line arguments and set some initial variables."""

        self.options = options
        self.args = args
        self.logger = logger

        # Make parameters a little shorter by making the following assignments.
        if options.all_tests:
            self.suite = 'suite_All'
        else:
            self.suite = self.options.suite

        self.autotest_root = self.options.autotest_dir
        self.debug = self.options.debug
        self.docversion = self.options.docversion
        self.doxyconf = self.options.doxyconf
        self.html = '%s_%s' % (self.suite, self.options.html)
        self.latex = self.options.latex
        self.layout = self.options.layout
        self.logfile = self.options.logfile
        self.readme = self.options.readme
        self.src_tests = '%s_%s' % (self.suite, self.options.src_tests)

        self.testcase = {}
        self.testcase_src = {}

        self.site_dir = os.path.join(self.autotest_root, 'client', 'site_tests')
        self.test_dir = os.path.join(self.autotest_root, 'client', 'tests')
        self.suite_dir = os.path.join(self.site_dir, self.suite)

        self.logger.debug('Executing with debug level: %s', self.debug)
        self.logger.debug('Writing to logfile: %s', self.logfile)
        self.logger.debug('New test directory: %s', self.src_tests)
        self.logger.debug('Test suite: %s', self.suite)

        self.suitename = {
                          'suite_All': 'All Existing Autotest Tests',
                          'suite_Factory': 'Factory Testing',
                          'suite_HWConfig': 'Hardware Configuration',
                          'suite_HWQual': 'Hardware Qualification',
                         }

    def GetAllTests(self):
        """Create list of all discovered tests."""
        for path in [ 'server/tests', 'server/site_tests', 'client/tests',
                      'client/site_tests']:
            test_path = os.path.join(self.autotest_root, path)
            if not os.path.exists(test_path):
                continue
            self.logger.info("Scanning %s", test_path)
            tests, tests_src = fs_find_tests.GetTestsFromFS(test_path,
                                                            self.logger)
            test_intersection = set(self.testcase) & set(tests)
            if test_intersection:
                self.logger.warning("Duplicates found: %s", test_intersection)
            self.testcase.update(tests)
            self.testcase_src.update(tests_src)

    def GetTestsFromSuite(self):
        """Create list of tests invoked by a suite."""

        suite_search = os.path.join(self.suite_dir, 'control.*')
        for suitefile in glob.glob(suite_search):
            self.logger.debug('Scanning %s for tests', suitefile)
            if os.path.isfile(suitefile):
                try:
                    suite = compiler.parseFile(suitefile)
                except SyntaxError, e:
                    self.logger.error('Error parsing (gettests): %s\n%s',
                                      suitefile, e)
                    raise SystemExit

            # Walk through each node found in the control file, which in our
            # case will be a call to a test. compiler.walk() will walk through
            # each component node, and call the appropriate function in class
            # ReadNode. The returned key should be a string, and the name of a
            # test. visitor.value should be any extra arguments found in the
            # suite file that are used with that test case.
            for n in suite.node.nodes:
                visitor = ReadNode(suite=True)
                compiler.walk(n, visitor)
                if len(visitor.key) > 1:
                    filtered_input = ''
                    # Lines in value should start with '  -' for bullet item.
                    if visitor.value:
                        lines = visitor.value.split('\n')
                        for line in lines:
                            if line.startswith('  -'):
                                filtered_input += line + '\n'
                    # A test could be called multiple times, so see if the key
                    # already exists, and if so append the new value.
                    if visitor.key in self.testcase:
                        s = self.testcase[visitor.key] + filtered_input
                        self.testcase[visitor.key] = s
                    else:
                        self.testcase[visitor.key] = filtered_input

    def GetTests(self):
        """Create dictionary of tests based on suite control file contents."""
        if self.options.all_tests:
            self.GetAllTests()
        else:
            self.GetTestsFromSuite()

    def _CleanDir(self, directory):
        """Ensure the directory is available and empty.

        Args:
            directory: string, path of directory
        """

        if os.path.isdir(directory):
            try:
                shutil.rmtree(directory)
            except IOError, err:
                self.logger.error('Error cleaning %s\n%s', directory, err)
        try:
            os.makedirs(directory)
        except IOError, err:
            self.logger.error('Error creating %s\n%s', directory, err)
            self.logger.error('Check your permissions of %s', directory)
            raise SystemExit

    def LocateTest(self, test_name):
        """Determine the full path location of the test."""
        if test_name in self.testcase_src:
            return os.path.join(self.testcase_src[test_name], test_name)

        test_dir = os.path.join(self.site_dir, test_name)
        if not os.path.isdir(test_dir):
            test_dir = os.path.join(self.test_dir, test_name)
        if os.path.isdir(test_dir):
            return test_dir

        self.logger.warning('Cannot find test: %s', test)
        return None


    def ParseControlFiles(self):
        """Get docstrings from control files and add them to new test scripts.

        This method will cycle through all of the tests and attempt to find
        their control file. If found, it will parse the docstring from the
        control file, add this to any parameters found in the suite file, and
        add this combined docstring to the original test. These new tests will
        be written in the self.src_tests directory.
        """
        # Clean some target directories.
        for d in [self.src_tests, self.html]:
            self._CleanDir(d)

        for test in self.testcase:
            test_dir = self.LocateTest(test)
            if test_dir:
                control_file = os.path.join(test_dir, 'control')
                test_file = os.path.join(test_dir, test + '.py')
                docstring = self._GetDocString(control_file, test)
                self._CreateTest(test_file, docstring, test)

    def _GetDocString(self, control_file, test):
        """Get the docstrings from control file and join to suite file params.

        Args:
            control_file: string, absolute path to test control file.
            test: string, name of test.
        Returns:
            string: combined docstring with needed markup language for doxygen.
        """

        # Doxygen needs the @package marker.
        package_doc = '## @package '
        # To allow doxygen to use special commands, we must use # for comments.
        comment = '# '
        endlist = '  .\n'
        control_dict = {}
        output = []
        temp = []
        tempstring = ''
        docstring = ''
        keys = ['\\brief\n', '<H3>Pass/Fail Criteria:</H3>\n',
                '<H3>Author</H3>\n', '<H3>Test Duration</H3>\n',
                '<H3>Category</H3>\n', '<H3>Test Type</H3>\n',
                '<H3>Test Class</H3>\n', '<H3>Notest</H3>\n',
               ]

        if not os.path.isfile(control_file):
            self.logger.error('Cannot find: %s', control_file)
            return None
        try:
            control = compiler.parseFile(control_file)
        except SyntaxError, e:
            self.logger.error('Error parsing (docstring): %s\n%s',
                              control_file, e)
            return None

        for n in control.node.nodes:
            visitor = ReadNode()
            compiler.walk(n, visitor)
            control_dict[visitor.key] = visitor.value

        for k in keys:
            if k in control_dict:
                if len(control_dict[k]) > 1:
                    if k != test:
                        temp.append(k)
                    temp.append(control_dict[k])
                    if control_dict[k]:
                        temp.append(endlist)
                    # Add constraints and extra args after the Criteria section.
                    if 'Criteria:' in k:
                        if self.testcase[test]:
                            temp.append('<H3>Arguments:</H3>\n')
                            temp.append(self.testcase[test])
                            # '.' character at the same level as the '-' tells
                            # doxygen this is the end of the list.
                            temp.append(endlist)

        output.append(package_doc + test + '\n')
        tempstring = "".join(temp)
        lines = tempstring.split('\n')
        for line in lines:
            # Doxygen requires a '#' character to add special doxygen commands.
            comment_line = comment + line + '\n'
            output.append(comment_line)

        docstring = "".join(output)

        return docstring


    def _CreateTest(self, test_file, docstring, test):
        """Create a new test with the combined docstrings from multiple sources.

        Args:
            test_file: string, file name of new test to write.
            docstring: string, the docstring to add to the existing test.
            test: string, name of the test.

        This method is used to create a temporary copy of a new test, that will
        be a combination of the original test plus the docstrings from the
        control file, and any constraints from the suite control file.
        """

        class_def = 'class ' + test
        pathname = os.path.join(self.src_tests, test + '.py')

        # Open the test and write out new test with added docstrings
        try:
            f = open(test_file, 'r')
        except IOError, err:
            self.logger.error('Error while reading %s\n%s', test_file, err)
            return
        lines = f.readlines()
        f.close()

        try:
            f = open(pathname, 'w')
        except IOError, err:
            self.logger.error('Error creating %s\n%s', pathname, err)
            return

        for line in lines:
            if class_def in line and docstring:
                f.write(docstring)
                f.write('\n')
            f.write(line)
        f.close()

    def CreateMainPage(self, current_dir):
        """Create a main page to provide content for index.html.

        This method assumes a file named README.txt is located in your suite
        directory with general instructions on setting up and using the suite.
        If your README file is in another file, ensure you pass a --readme
        option with the correct filename. To produce a better looking
        landing page, use the '-' character for list items. This method assumes
        os commands start with '$'.
        """

        # Define some strings that Doxygen uses for specific formatting.
        cstart = '/**'
        cend = '**/'
        mp = '@mainpage'
        section_begin = '@section '
        vstart = '@verbatim '
        vend = ' @endverbatim\n'

        # Define some characters we expect to delineate sections in the README.
        sec_char = '=========='
        command_prompt = '$ '
        crosh_prompt = 'crosh>'
        command_cont = '\\'

        command = False
        comment = False
        section = False
        sec_ctr = 0

        if self.options.all_tests:
            readme_file = os.path.join(current_dir, self.readme)
        else:
            readme_file = os.path.join(self.suite_dir, self.readme)
        mainpage_file = os.path.join(self.src_tests, 'mainpage.txt')

        try:
            f = open(readme_file, 'r')
        except IOError, err:
            self.logger.error('Error opening %s\n%s', readme_file, err)
            return
        try:
            fw = open(mainpage_file, 'w')
        except IOError, err:
            self.logger.error('Error opening %s\n%s', mainpage_file, err)
            return

        lines = f.readlines()
        f.close()

        fw.write(cstart)
        fw.write('\n')
        fw.write(mp)
        fw.write('\n')

        for line in lines:
            if sec_char in line:
                comment = True
                section = not section
            elif section:
                sec_ctr += 1
                section_name = ' section%d ' % sec_ctr
                fw.write(section_begin + section_name + line)
            else:
                # comment is used to denote when we should start recording text
                # from the README file. Some of the initial text is not needed.
                if comment:
                    if command_prompt in line or crosh_prompt in line:
                        line = line.rstrip()
                        if line[-1] == command_cont:
                            fw.write(vstart + line[:-1])
                            command = True
                        else:
                            fw.write(vstart + line + vend)
                    elif command:
                        line = line.strip()
                        if line[-1] == command_cont:
                          fw.write(line)
                        else:
                          fw.write(line + vend)
                          command = False
                    else:
                        fw.write(line)

        fw.write('\n')
        fw.write(cend)
        fw.close()

    def _ConfigDoxygen(self):
        """Set Doxygen configuration to match our options."""

        doxy_config = {
                       'ALPHABETICAL_INDEX': 'YES',
                       'EXTRACT_ALL': 'YES',
                       'EXTRACT_LOCAL_METHODS': 'YES',
                       'EXTRACT_PRIVATE': 'YES',
                       'EXTRACT_STATIC': 'YES',
                       'FILE_PATTERNS': '*.py *.txt',
                       'FULL_PATH_NAMES ': 'YES',
                       'GENERATE_TREEVIEW': 'YES',
                       'HTML_DYNAMIC_SECTIONS': 'YES',
                       'HTML_FOOTER': 'footer.html',
                       'HTML_HEADER': 'header.html',
                       'HTML_OUTPUT ': self.html,
                       'INLINE_SOURCES': 'YES',
                       'INPUT ': self.src_tests,
                       'JAVADOC_AUTOBRIEF': 'YES',
                       'LATEX_OUTPUT ': self.latex,
                       'LAYOUT_FILE ': self.layout,
                       'OPTIMIZE_OUTPUT_JAVA': 'YES',
                       'PROJECT_NAME ': self.suitename[self.suite],
                       'PROJECT_NUMBER': self.docversion,
                       'SOURCE_BROWSER': 'YES',
                       'STRIP_CODE_COMMENTS': 'NO',
                       'TAB_SIZE': '4',
                       'USE_INLINE_TREES': 'YES',
                      }

        doxy_layout = {
                       'tab type="mainpage"': 'title="%s"' %
                         self.suitename[self.suite],
                       'tab type="namespaces"': 'title="Tests"',
                       'tab type="namespacemembers"': 'title="Test Functions"',
                      }

        for line in fileinput.input(self.doxyconf, inplace=1):
            for k in doxy_config:
                if line.startswith(k):
                    line = '%s = %s\n' % (k, doxy_config[k])
            sys.stdout.write(line)

        for line in fileinput.input('header.html', inplace=1):
            if line.startswith('<H2>'):
                line = '<H2>%s</H2>\n' % self.suitename[self.suite]
            sys.stdout.write(line)

        for line in fileinput.input(self.layout, inplace=1):
            for k in doxy_layout:
                if line.find(k) != -1:
                    line = line.replace('title=""', doxy_layout[k])
            sys.stdout.write(line.rstrip() + '\n')

    def RunDoxygen(self, doxyargs):
        """Execute Doxygen on the files in the self.src_tests directory.

        Args:
          doxyargs: string, any command line args to be passed to doxygen.
        """

        doxycmd = 'doxygen %s' % doxyargs

        p = subprocess.Popen(doxycmd, shell=True, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        stdout, stderr = p.communicate()
        if p.returncode:
            self.logger.error('Error while running %s', doxycmd)
            self.logger.error(stdout)
            self.logger.error(stderr)
        else:
            self.logger.info('%s successfully ran', doxycmd)

    def CreateDocs(self):
        """Configure and execute Doxygen to create HTML docuements."""

        # First run doxygen with args to create default configuration files.
        # Create layout xml file.
        doxyargs = '-l %s' % self.layout
        self.RunDoxygen(doxyargs)

        # Create doxygen configuration file.
        doxyargs = '-g %s' % self.doxyconf
        self.RunDoxygen(doxyargs)

        # Edit the configuration files to match our options.
        self._ConfigDoxygen()

        # Run doxygen with configuration file as argument.
        self.RunDoxygen(self.doxyconf)

    def PostProcessDocs(self, current_dir):
        """Run some post processing on the newly created docs."""

        # Key = original string, value = replacement string.
        replace = {
                   '>Package': '>Test',
                  }

        docpages = os.path.join(self.html, '*.html')
        files = glob.glob(docpages)
        for file in files:
            for line in fileinput.input(file, inplace=1):
                for k in replace:
                    if line.find(k) != -1:
                        line = line.replace(k, replace[k])
                print line,

        logo_image = 'customLogo.gif'
        html_root = os.path.join(current_dir, self.html)
        shutil.copy(os.path.join(current_dir, logo_image), html_root)

        # Copy under dashboard.
        if self.options.dashboard:
            dashboard_root = os.path.join(self.autotest_root, 'results',
                                          'dashboard', 'testdocs')
            if not os.path.isdir(dashboard_root):
                try:
                    os.makedirs(dashboard_root)
                except e:
                    self.logger.error('Error creating %s:%s', dashboard_root, e)
                    return
            os.system('cp -r %s/* %s' % (html_root, dashboard_root))
            os.system('find %s -type d -exec chmod 755 {} \;' % dashboard_root)
            os.system('find %s -type f -exec chmod 644 {} \;' % dashboard_root)

        self.logger.info('Sanitized documentation completed.')


class ReadNode(object):
    """Parse a compiler node object from a control file.

    Args:
        suite: boolean, set to True if parsing nodes from a suite control file.
    """

    def __init__(self, suite=False):
        self.key = ''
        self.value = ''
        self.testdef = False
        self.suite = suite
        self.bullet = '  - '

    def visitName(self, n):
        if n.name == 'job':
            self.testdef = True

    def visitConst(self, n):
        if self.testdef:
            self.key = str(n.value)
            self.testdef = False
        else:
            self.value += str(n.value) + '\n'

    def visitKeyword(self, n):
        if n.name != 'constraints':
            self.value += self.bullet + n.name + ': '
        for item in n.expr:
            if isinstance(item, compiler.ast.Const):
                for i in item:
                    self.value += self.bullet + str(i) + '\n'
                self.value += '  .\n'
            else:
                self.value += str(item) + '\n'


    def visitAssName(self, n):
        # To remove section from appearing in the documentation, set value = ''.
        sections = {
                    'AUTHOR': '',
                    'CRITERIA': '<H3>Pass/Fail Criteria:</H3>\n',
                    'DOC': '<H3>Notes</H3>\n',
                    'NAME': '',
                    'PURPOSE': '\\brief\n',
                    'TIME': '<H3>Test Duration</H3>\n',
                    'TEST_CATEGORY': '<H3>Category</H3>\n',
                    'TEST_CLASS': '<H3>Test Class</H3>\n',
                    'TEST_TYPE': '<H3>Test Type</H3>\n',
                   }

        if not self.suite:
            self.key = sections.get(n.name, n.name)


def ParseOptions(current_dir):
    """Common processing of command line options."""

    desc="""%prog will scan AutoTest suite control files to build a list of
    test cases called in the suite, and build HTML documentation based on
    the docstrings it finds in the tests, control files, and suite control
    files.
    """
    parser = optparse.OptionParser(description=desc,
                                   prog='CreateDocs',
                                   version=__version__,
                                   usage='%prog')
    parser.add_option('--alltests',
                      help='Scan for all tests',
                      action='store_true',
                      default=False,
                      dest='all_tests')
    parser.add_option('--autotest_dir',
                      help='path to autotest root directory'
                           ' [default: %default]',
                      default=None,
                      dest='autotest_dir')
    parser.add_option('--dashboard',
                      help='Copy output under dashboard',
                      action='store_true',
                      default=False,
                      dest='dashboard')
    parser.add_option('--debug',
                      help='Debug level [default: %default]',
                      default='debug',
                      dest='debug')
    parser.add_option('--docversion',
                      help='Specify a version for the documentation'
                           '[default: %default]',
                      default=None,
                      dest='docversion')
    parser.add_option('--doxy',
                      help='doxygen configuration file [default: %default]',
                      default=os.path.join(current_dir, 'doxygen.conf'),
                      dest='doxyconf')
    parser.add_option('--html',
                      help='path to store html docs [default: %default]',
                      default='html',
                      dest='html')
    parser.add_option('--latex',
                      help='path to store latex docs [default: %default]',
                      default='latex',
                      dest='latex')
    parser.add_option('--layout',
                      help='doxygen layout file [default: %default]',
                      default=os.path.join(current_dir, 'doxygenLayout.xml'),
                      dest='layout')
    parser.add_option('--log',
                      help='Logfile for program output [default: %default]',
                      default=os.path.join(current_dir, 'docCreator.log'),
                      dest='logfile')
    parser.add_option('--readme',
                      help='filename of suite documentation'
                           '[default: %default]',
                      default='README.txt',
                      dest='readme')
    parser.add_option('--suite',
                      help='Directory name of suite [default: %default]',
                      type='choice',
                      default='suite_HWQual',
                      choices = [
                                 'suite_Factory',
                                 'suite_HWConfig',
                                 'suite_HWQual',
                                ],
                      dest='suite')
    parser.add_option('--tests',
                      help='Absolute path of temporary test files'
                           ' [default: %default]',
                      default='testsource',
                      dest='src_tests')
    return parser.parse_args()


def CheckOptions(options, logger):
    """Verify required command line options."""

    if not options.autotest_dir:
        logger.error('You must supply --autotest_dir')
        raise SystemExit

    if not os.path.isfile(options.doxyconf):
        logger.error('Unable to locate --doxy: %s', options.doxyconf)
        raise SystemExit

    if not os.path.isfile(options.layout):
        logger.error('Unable to locate --layout: %s', options.layout)
        raise SystemExit


def SetLogger(namespace, options):
    """Create a logger with some good formatting options.

    Args:
        namespace: string, name associated with this logger.
    Returns:
        Logger object.
    This method assumes logfile and debug are already set.
    This logger will write to stdout as well as a log file.
    """

    loglevel = {'debug': logging.DEBUG,
                'info': logging.INFO,
                'warning': logging.WARNING,
                'error': logging.ERROR,
                'critical': logging.CRITICAL,
               }

    logger = logging.getLogger(namespace)
    c = logging.StreamHandler()
    h = logging.FileHandler(
        os.path.join(os.path.abspath('.'), options.logfile))
    hf = logging.Formatter(
        '%(asctime)s %(process)d %(levelname)s: %(message)s')
    cf = logging.Formatter('%(levelname)s: %(message)s')
    logger.addHandler(h)
    logger.addHandler(c)
    h.setFormatter(hf)
    c.setFormatter(cf)

    logger.setLevel(loglevel.get(options.debug, logging.INFO))

    return logger


def main():
    current_dir = os.path.dirname(sys.argv[0])
    options, args = ParseOptions(current_dir)
    logger = SetLogger('docCreator', options)
    CheckOptions(options, logger)
    doc = DocCreator(options, args, logger)
    doc.GetTests()
    doc.ParseControlFiles()
    doc.CreateMainPage(current_dir)
    doc.CreateDocs()
    doc.PostProcessDocs(current_dir)


if __name__ == '__main__':
    main()