uribench/software-engineering-handbook-tools

View on GitHub
handbook_tools/commands/build.py

Summary

Maintainability
A
0 mins
Test Coverage
"""
'build' sub-command of the 'handbook' command.

This module builds the Handbook from configuration files.
"""

import os
from urllib.request import pathname2url
from jinja2 import Template
import yaml
from handbook_tools.lib.command_base import CommandBase
from handbook_tools.lib.navigation_tree import NavigationTree
from handbook_tools.lib.navigation_tree_node import NavigationTreeNode

__version__ = '1.1.8'

class Build(CommandBase):
    """
    Build the Handbook from configuration.

    Usage:
      build [options]

    Options:
      -h, --help        Show this help message and exit
      --version         Show the version and exit
      --no-stop         Ignore 'stop' tags to scan the entire tree
      -f, --force       Overwrite existing target directory

    Examples:
      handbook build -h
      handbook build --version
      handbook build
      handbook --root=tests/fixtures/site build
      handbook build --no-stop
    """

    def __init__(self, command_args=None, global_args=None):
        """"""
        super().__init__(command_args, global_args, version=__version__)

        # navigation file name (auto-generated)
        self.navigation_filename = 'index.md'
        # optional authored metadata YAML files for the navigation files
        self.metadata_path = 'config/metadata/'
        # path to template files
        self.templates_path = 'config/templates/'
        # Jinja2 template file for the navigation files
        self.navigation_file_template = 'navigation-file-template.j2'
        self._process_args()
        self.navigation_tree = None

    def execute(self):
        """Entry point for the execution of this sub-command"""
        self.navigation_tree = NavigationTree(self.site_root, self.verbose, self.no_stop)
        self.navigation_tree.fail_on_existing_root_node_dir(self.force)
        self.navigation_tree.scan(self.node_performer)

    def node_performer(self, root_path, root_options, root_children_nodes):
        """Custom performer executed for each visited node"""
        os.mkdir(root_path)
        root_name = os.path.basename(root_path)
        self._create_index_file(root_path, root_options, root_name, root_children_nodes)

    def _process_args(self):
        """Process command_args"""
        # default values not set by docopt were set in CommandBase
        self.no_stop = self.args['--no-stop']
        self.force = self.args['--force']

    def _create_index_file(self, path, options, title, children_nodes):
        """"""
        template = self._load_template(self.templates_path, self.navigation_file_template)
        metadata_filename = options['id'] + '.yml'
        metadata_full_filename = os.path.join(self.site_root,
                                              *[self.metadata_path, metadata_filename])

        intro = []
        raw_guides = []
        raw_topics = []

        if os.path.exists(metadata_full_filename):
            metadata = self._load_metadata(metadata_full_filename)
            intro = metadata.get('intro', [])
            raw_guides = metadata.get('guides', [])
            raw_topics = metadata.get('topics', [])

        contents = self._format_contents(path, children_nodes)
        guides = self._format_metadata_list_items('/Guides', raw_guides)
        topics = self._format_metadata_list_items('/Topics', raw_topics)

        index_file_contents = template.render(title=title, intro=intro, contents=contents,
                                              guides=guides, topics=topics)
        self._write_index_file(path, index_file_contents)

    def _load_template(self, template_path, template_name):
        """"""
        try:
            template_full_filename = os.path.join(self.site_root, *[template_path, template_name])
            with open(template_full_filename) as template_file:
                return Template(template_file.read())
        except IOError as err:
            print('Error: operation failed: {}'.format(err.strerror))

    @staticmethod
    def _load_metadata(filename):
        """"""
        try:
            with open(filename, 'r') as metadata_file:
                metadata = yaml.load(metadata_file)
        except IOError as err:
            print('Error: operation failed: {}'.format(err.strerror))

        return metadata

    def _format_contents(self, path, children_nodes):
        """"""
        contents = []
        for node in children_nodes:
            child_node = NavigationTreeNode(node)
            if not child_node.options['stop']:
                path = path.replace(self.site_root, '')
                link = os.path.join(path, child_node.name)
                item = self._format_markdown_linked_item(child_node.name, link)
            else:
                item = child_node.name

            contents.append(item)

        return contents

    def _format_metadata_list_items(self, path, raw_items):
        """"""
        items = []
        for item in raw_items:
            item_text = os.path.basename(item)
            link = os.path.join(path, item)
            formated_item = self._format_markdown_linked_item(item_text, link)
            items.append(formated_item)

        return items

    @staticmethod
    def _format_markdown_linked_item(item, link):
        """"""
        link_url = pathname2url(link)
        item = '[{}]({})'.format(item, link_url)

        return item

    def _write_index_file(self, path, content):
        """"""
        index_full_filename = os.path.join(path, self.navigation_filename)
        try:
            with open(index_full_filename, 'a') as index_file:
                index_file.write(content)
        except IOError as err:
            print('Error: Operation failed: {}'.format(err.strerror))