summaryrefslogtreecommitdiff
path: root/pbr/builddoc.py
blob: 3c9a9a1488404bc7c08becb060916ec339455b09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# Copyright 2011 OpenStack LLC.
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from distutils import log
import fnmatch
import os
import sys

try:
    import cStringIO
except ImportError:
    import io as cStringIO

try:
    from sphinx import apidoc
    from sphinx import application
    from sphinx import config
    from sphinx import setup_command
except Exception as e:
    # NOTE(dhellmann): During the installation of docutils, setuptools
    # tries to import pbr code to find the egg_info.writer hooks. That
    # imports this module, which imports sphinx, which imports
    # docutils, which is being installed. Because docutils uses 2to3
    # to convert its code during installation under python 3, the
    # import fails, but it fails with an error other than ImportError
    # (today it's a NameError on StandardError, an exception base
    # class). Convert the exception type here so it can be caught in
    # packaging.py where we try to determine if we can import and use
    # sphinx by importing this module.  See bug #1403510 for details.
    raise ImportError(str(e))
from pbr import git
from pbr import options


_rst_template = """%(heading)s
%(underline)s

.. automodule:: %(module)s
  :members:
  :undoc-members:
  :show-inheritance:
"""


def _find_modules(arg, dirname, files):
    for filename in files:
        if filename.endswith('.py') and filename != '__init__.py':
            arg["%s.%s" % (dirname.replace('/', '.'),
                           filename[:-3])] = True


class LocalBuildDoc(setup_command.BuildDoc):

    command_name = 'build_sphinx'
    builders = ['html', 'man']

    def _get_source_dir(self):
        option_dict = self.distribution.get_option_dict('build_sphinx')
        if 'source_dir' in option_dict:
            source_dir = os.path.join(option_dict['source_dir'][1], 'api')
        else:
            source_dir = 'doc/source/api'
        if not os.path.exists(source_dir):
            os.makedirs(source_dir)
        return source_dir

    def generate_autoindex(self, excluded_modules=None):
        log.info("[pbr] Autodocumenting from %s"
                 % os.path.abspath(os.curdir))
        modules = {}
        source_dir = self._get_source_dir()
        for pkg in self.distribution.packages:
            if '.' not in pkg:
                for dirpath, dirnames, files in os.walk(pkg):
                    _find_modules(modules, dirpath, files)

        def include(module):
            return not any(fnmatch.fnmatch(module, pat)
                           for pat in excluded_modules)

        module_list = sorted(mod for mod in modules.keys() if include(mod))
        autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
        with open(autoindex_filename, 'w') as autoindex:
            autoindex.write(""".. toctree::
   :maxdepth: 1

""")
            for module in module_list:
                output_filename = os.path.join(source_dir,
                                               "%s.rst" % module)
                heading = "The :mod:`%s` Module" % module
                underline = "=" * len(heading)
                values = dict(module=module, heading=heading,
                              underline=underline)

                log.info("[pbr] Generating %s"
                         % output_filename)
                with open(output_filename, 'w') as output_file:
                    output_file.write(_rst_template % values)
                autoindex.write("   %s.rst\n" % module)

    def _sphinx_tree(self):
            source_dir = self._get_source_dir()
            cmd = ['apidoc', '.', '-H', 'Modules', '-o', source_dir]
            apidoc.main(cmd + self.autodoc_tree_excludes)

    def _sphinx_run(self):
        if not self.verbose:
            status_stream = cStringIO.StringIO()
        else:
            status_stream = sys.stdout
        confoverrides = {}
        if self.version:
            confoverrides['version'] = self.version
        if self.release:
            confoverrides['release'] = self.release
        if self.today:
            confoverrides['today'] = self.today
        sphinx_config = config.Config(self.config_dir, 'conf.py', {}, [])
        sphinx_config.init_values()
        if self.builder == 'man' and len(sphinx_config.man_pages) == 0:
            return
        app = application.Sphinx(
            self.source_dir, self.config_dir,
            self.builder_target_dir, self.doctree_dir,
            self.builder, confoverrides, status_stream,
            freshenv=self.fresh_env, warningiserror=True)

        try:
            app.build(force_all=self.all_files)
        except Exception as err:
            from docutils import utils
            if isinstance(err, utils.SystemMessage):
                sys.stder.write('reST markup error:\n')
                sys.stderr.write(err.args[0].encode('ascii',
                                                    'backslashreplace'))
                sys.stderr.write('\n')
            else:
                raise

        if self.link_index:
            src = app.config.master_doc + app.builder.out_suffix
            dst = app.builder.get_outfilename('index')
            os.symlink(src, dst)

    def run(self):
        option_dict = self.distribution.get_option_dict('pbr')
        if git._git_is_installed():
            git.write_git_changelog(option_dict=option_dict)
            git.generate_authors(option_dict=option_dict)
        tree_index = options.get_boolean_option(option_dict,
                                                'autodoc_tree_index_modules',
                                                'AUTODOC_TREE_INDEX_MODULES')
        auto_index = options.get_boolean_option(option_dict,
                                                'autodoc_index_modules',
                                                'AUTODOC_INDEX_MODULES')
        if not os.getenv('SPHINX_DEBUG'):
            # NOTE(afazekas): These options can be used together,
            # but they do a very similar thing in a different way
            if tree_index:
                self._sphinx_tree()
            if auto_index:
                self.generate_autoindex(
                    set(option_dict.get(
                        "autodoc_exclude_modules",
                        [None, ""])[1].split()))

        for builder in self.builders:
            self.builder = builder
            self.finalize_options()
            self.project = self.distribution.get_name()
            self.version = self.distribution.get_version()
            self.release = self.distribution.get_version()
            if options.get_boolean_option(option_dict,
                                          'warnerrors', 'WARNERRORS'):
                self._sphinx_run()
            else:
                setup_command.BuildDoc.run(self)

    def initialize_options(self):
        # Not a new style class, super keyword does not work.
        setup_command.BuildDoc.initialize_options(self)

        # NOTE(dstanek): exclude setup.py from the autodoc tree index
        # builds because all projects will have an issue with it
        self.autodoc_tree_excludes = ['setup.py']

    def finalize_options(self):
        # Not a new style class, super keyword does not work.
        setup_command.BuildDoc.finalize_options(self)
        # Handle builder option from command line - override cfg
        option_dict = self.distribution.get_option_dict('build_sphinx')
        if 'command line' in option_dict.get('builder', [[]])[0]:
            self.builders = option_dict['builder'][1]
        # Allow builders to be configurable - as a comma separated list.
        if not isinstance(self.builders, list) and self.builders:
            self.builders = self.builders.split(',')

        # NOTE(dstanek): check for autodoc tree exclusion overrides
        # in the setup.cfg
        opt = 'autodoc_tree_excludes'
        option_dict = self.distribution.get_option_dict('pbr')
        if opt in option_dict:
            self.autodoc_tree_excludes = option_dict[opt][1]
            self.ensure_string_list(opt)


class LocalBuildLatex(LocalBuildDoc):
    builders = ['latex']
    command_name = 'build_sphinx_latex'