inasafe/inasafe

View on GitHub
scripts/create_api_docs.py

Summary

Maintainability
A
30 mins
Test Coverage
#!/usr/bin/env python
"""Generating rst file for api documentation."""

import os
from shutil import rmtree
import sys

__copyright__ = "Copyright 2016, The InaSAFE Project"
__license__ = "GPL version 3"
__email__ = "info@inasafe.org"
__revision__ = '$Format:%H$'

# Text
INDEX_HEADER = 'API documentation\n'
INDEX_HEADER += '=================\n'
INDEX_HEADER += 'This is the API documentation for the InaSAFE project.\n'
INDEX_HEADER += 'You can find out more about the InaSAFE project by visiting\n'
INDEX_HEADER += '`inasafe.org <http://www.inasafe.org/>`_.\n\n'
# known packages/directories/modules that don't have python files in it.
EXCLUDED_PACKAGES = [
    'i18n', 'test', 'converter_data', 'ui', 'resources', 'fixtures']


def create_top_level_index_entry(title, max_depth, subtitles):
    """Function for creating a text entry in index.rst for its content.

    :param title : Title for the content.
    :type title: str

    :param max_depth : Value for max_depth in the top level index content.
    :type max_depth: int

    :param subtitles : list of subtitles that is available.
    :type subtitles: list

    :return: A text for the content of top level index.
    :rtype: str
    """

    return_text = title + '\n'
    dash = '-' * len(title) + '\n'

    return_text += dash + '\n'
    return_text += '.. toctree::' + '\n'
    return_text += '   :maxdepth: ' + str(max_depth) + '\n\n'

    for subtitle in subtitles:
        return_text += '   ' + subtitle + '\n\n'
    return return_text


def create_package_level_rst_index_file(
        package_name, max_depth, modules, inner_packages=None):
    """Function for creating text for index for a package.

    :param package_name: name of the package
    :type package_name: str

    :param max_depth: Value for max_depth in the index file.
    :type max_depth: int

    :param modules: list of module in the package.
    :type modules: list

    :return: A text for the content of the index file.
    :rtype: str
    """
    if inner_packages is None:
        inner_packages = []
    return_text = 'Package::' + package_name
    dash = '=' * len(return_text)
    return_text += '\n' + dash + '\n\n'
    return_text += '.. toctree::' + '\n'
    return_text += '   :maxdepth: ' + str(max_depth) + '\n\n'
    upper_package = package_name.split('.')[-1]
    for module in modules:
        if module in EXCLUDED_PACKAGES:
            continue
        return_text += '   ' + upper_package + os.sep + module[:-3] + '\n'
    for inner_package in inner_packages:
        if inner_package in EXCLUDED_PACKAGES:
            continue
        return_text += '   ' + upper_package + os.sep + inner_package + '\n'

    return return_text


def create_module_rst_file(module_name):
    """Function for creating content in each .rst file for a module.

    :param module_name: name of the module.
    :type module_name: str

    :returns: A content for auto module.
    :rtype: str
    """

    return_text = 'Module:  ' + module_name
    dash = '=' * len(return_text)
    return_text += '\n' + dash + '\n\n'
    return_text += '.. automodule:: ' + module_name + '\n'
    return_text += '   :members:\n\n'

    return return_text


def create_dirs(path):
    """Shorter function for creating directory."""
    if not os.path.exists(path):
        os.makedirs(path)


def write_rst_file(file_directory, file_name, content):
    """Shorter procedure for creating rst file.

    :param file_directory: Directory of the filename.
    :type file_directory: str

    :param file_name: Name of the file.
    :type file_name: str

    :param content: The content of the file.
    :type content: str
    """
    create_dirs(os.path.split(os.path.join(file_directory, file_name))[0])
    try:
        fl = open(os.path.join(file_directory, file_name + '.rst'), 'w+')
        fl.write(content)
        fl.close()

    except Exception as e:
        print(('Creating %s failed' % os.path.join(
            file_directory, file_name + '.rst'), e))


def get_python_files_from_list(files, excluded_files=None):
    """Return list of python file from files, without excluded files.

    :param files: List of files.
    :type files: list

    :param excluded_files: List of excluded file names.
    :type excluded_files: list, None

    :returns: List of python file without the excluded file, not started with
        test.
    :rtype: list
    """
    if excluded_files is None:
        excluded_files = ['__init__.py']
    python_files = []
    for fl in files:
        if (fl.endswith('.py')
                and fl not in excluded_files
                and not fl.startswith('test')):
            python_files.append(fl)

    return python_files


def create_top_level_index(api_docs_path, packages, max_depth=2):
    """Create the top level index page (writing to file)

    :param api_docs_path: Path to the api-docs of inasafe documentation.
    :type api_docs_path: str

    :param packages: List of packages which want to be extracted their api/
    :type packages: list

    :param max_depth: The maximum depth of tree in the api docs.
    :type max_depth: int
    """
    page_text = INDEX_HEADER
    for package in packages:
        # Write top level index file entries for safe
        text = create_top_level_index_entry(
            title='Package %s' % package,
            max_depth=max_depth,
            subtitles=[package])

        page_text += '%s\n' % text
    write_rst_file(api_docs_path, 'index', page_text)


def get_inasafe_code_path(custom_inasafe_path=None):
    """Determine the path to inasafe location.

    :param custom_inasafe_path: Custom inasafe project location.
    :type custom_inasafe_path: str

    :returns: Path to inasafe source code.
    :rtype: str
    """

    inasafe_code_path = os.path.abspath(
        os.path.join(os.path.dirname(__file__), '..'))

    if custom_inasafe_path is not None:
        inasafe_code_path = custom_inasafe_path
    return inasafe_code_path


def clean_api_docs_dirs():
    """Empty previous api-docs directory.

    :returns: Path to api-docs directory.
    :rtype: str
    """
    inasafe_docs_path = os.path.abspath(
        os.path.join(
            os.path.dirname(__file__), '..', 'docs', 'api-docs'))
    if os.path.exists(inasafe_docs_path):
        rmtree(inasafe_docs_path)
    create_dirs(inasafe_docs_path)
    return inasafe_docs_path


def create_api_docs(code_path, api_docs_path, max_depth=2):
    """Function for generating .rst file for all .py file in dir_path folder.

    :param code_path: Path of the source code.
    :type code_path: str

    :param api_docs_path: Path of the api documentation directory.
    :type api_docs_path: str

    :param max_depth: Maximum depth for the index.
    :type max_depth: int
    """
    base_path = os.path.split(code_path)[0]
    for package, subpackages, candidate_files in os.walk(code_path):
        # Checking __init__.py file
        if '__init__.py' not in candidate_files:
            continue
        # Creating directory for the package
        package_relative_path = package.replace(base_path + os.sep, '')
        index_package_path = os.path.join(
            api_docs_path, package_relative_path)
        # calculate dir one up from package to store the index in
        index_base_path, package_base_name = os.path.split(index_package_path)

        if package_base_name in EXCLUDED_PACKAGES:
            continue

        full_package_name = package_relative_path.replace(os.sep, '.')
        new_rst_dir = os.path.join(api_docs_path, package_relative_path)
        create_dirs(new_rst_dir)

        # Create index_file for the directory
        modules = get_python_files_from_list(candidate_files)
        index_file_text = create_package_level_rst_index_file(
            package_name=full_package_name,
            max_depth=max_depth,
            modules=modules,
            inner_packages=subpackages)

        write_rst_file(
            file_directory=index_base_path,
            file_name=package_base_name,
            content=index_file_text)

        # Creating .rst file for each .py file
        for module in modules:
            module = module[:-3]  # strip .py off the end
            py_module_text = create_module_rst_file(
                '%s.%s' % (full_package_name, module))
            write_rst_file(
                file_directory=new_rst_dir,
                file_name=module,
                content=py_module_text)


def usage():
    """Helper function for telling how to use the script."""
    print('Usage:')
    print(('python %s [optional path to inasafe directory]' % sys.argv[0]))


def main():
    if len(sys.argv) > 2:
        usage()
        sys.exit()
    elif len(sys.argv) == 2:
        print(('Building rst files from %s' % sys.argv[1]))
        inasafe_code_path = os.path.abspath(sys.argv[1])
    else:
        inasafe_code_path = None

    print('Please make sure there is no unused source '
          'code file in inasafe-dev')

    inasafe_code_path = get_inasafe_code_path(inasafe_code_path)
    print('Cleaning api docs...')
    api_docs_path = clean_api_docs_dirs()
    max_depth = 2
    packages = ['safe', 'realtime']

    # creating top level index for api-docs
    print('Creating top level index page...')
    create_top_level_index(api_docs_path, packages, max_depth)

    for package in packages:
        print('Creating api docs for package %s...' % package)
        package_code_path = os.path.join(inasafe_code_path, package)
        create_api_docs(
            code_path=package_code_path,
            api_docs_path=api_docs_path,
            max_depth=max_depth)

    print('Done.')


if __name__ == '__main__':
    main()