]>
git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/python/config/tools/sphinx4scons.py
1 """SCons.Tool.spinx4scons
3 Tool-specific initialization for the Sphinx document build system.
5 There normally shouldn't be any need to import this module directly.
6 It will usually be imported through the generic SCons.Tool.Tool()
9 It should be placed in e.g. ~/site_scons/site_tools/sphinx4scons/
10 directory. Then it may be loaded by placing
12 sphinx = Tool('sphinx4scons')
15 in your SConstruct file.
17 For further details, please see the SCons documentation on how to
18 install and enable custom tools.
22 # This package is provided under the Expat license
24 # Copyright (c) 2012 Orlando Wingbrant
26 # Permission is hereby granted, free of charge, to any person obtaining
27 # a copy of this software and associated documentation files (the
28 # "Software"), to deal in the Software without restriction, including
29 # without limitation the rights to use, copy, modify, merge, publish,
30 # distribute, sublicense, and/or sell copies of the Software, and to
31 # permit persons to whom the Software is furnished to do so, subject to
32 # the following conditions:
34 # The above copyright notice and this permission notice shall be included
35 # in all copies or substantial portions of the Software.
37 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
40 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
41 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
42 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
43 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
46 __author__
= "Orlando Wingbrant"
47 __email__
= "orlando@widesite.org"
48 __url__
= "https://bitbucket.org/wingbrant/sphinx4scons"
49 __license__
= "Expat license"
58 from sphinx
.util
.matching
import patfilter
, compile_matchers
59 from sphinx
.util
.osutil
import make_filename
62 class ToolSphinxWarning(SCons
.Warnings
.Warning):
66 class SphinxBuilderNotFound(ToolSphinxWarning
):
69 SCons
.Warnings
.enableWarningClass(ToolSphinxWarning
)
77 """Try to detect the sphinx-build script."""
79 return env
['SPHINXBUILD']
83 sphinx
= env
.WhereIs('sphinx-build')
87 raise SCons
.Errors
.StopError(
88 SphinxBuilderNotFound
,
89 "Could not detect sphinx-build script")
94 """Add Builders and construction variables to the Environment."""
96 env
['SPHINXBUILD'] = _detect(env
)
97 sphinx
= _create_sphinx_builder(env
)
100 # Additional command-line flags
103 # Tag definitions, each entry will appear on the command line preceded by -t
106 # Directory for doctrees
109 # Path to sphinx configuration file
112 # Config file override settings, each entry will be preceded by -D
115 # Default sphinx builder,
116 SPHINXBUILDER
= 'html',
119 SPHINXCOM
= "$SPHINXBUILD $_SPHINXOPTIONS ${SOURCE.attributes.root} ${TARGET.attributes.root}",
121 # Alternate console output when building sphinx documents
126 env
.AddMethod(Sphinx
, "Sphinx")
127 except AttributeError:
128 # Looks like we use a pre-0.98 version of SCons...
129 from SCons
.Script
.SConscript
import SConsEnvironment
130 SConsEnvironment
.Sphinx
= Sphinx
133 def Sphinx(env
, target
, source
, **kw
):
134 """A pseudo-builder wrapper for the sphinx builder."""
135 builder
= env
['BUILDERS']['Sphinx4Scons']
136 env_kw
= env
.Override(kw
)
137 options
= _get_sphinxoptions(env_kw
, target
, source
)
138 output
= builder(env
, target
, source
, _SPHINXOPTIONS
=options
, **kw
)
142 def _get_sphinxoptions(env
, target
, source
):
143 """Concatenates all the options for the sphinx command line."""
146 builder
= _get_sphinxbuilder(env
)
147 options
.append("-b %s" % env
.subst(builder
, target
=target
, source
=source
))
149 flags
= env
.get('options', env
.get('SPHINXFLAGS', ''))
150 options
.append(env
.subst(flags
, target
=target
, source
=source
))
152 tags
= env
.get('tags', env
.get('SPHINXTAGS', None))
154 if not SCons
.SCons
.Util
.is_List(tags
):
158 tag
= env
.subst(tag
, target
=target
, source
=source
)
159 options
.append("-t %s" % tag
)
161 settings
= env
.get('settings', env
.get('SPHINXSETTINGS', None))
162 if settings
is not None:
163 if not SCons
.SCons
.Util
.is_Dict(settings
):
164 raise TypeError('SPHINXSETTINGS and/or settings argument must be a dictionary')
165 for key
, value
in settings
.iteritems():
167 value
= env
.subst(value
, target
=target
, source
=source
)
168 options
.append('-D "%s=%s"' % (key
, value
))
170 doctree
= env
.get('doctree', env
.get("SPHINXDOCTREE", None))
171 if isinstance(doctree
, SCons
.Node
.FS
.Dir
):
172 options
.append("-d %s" % doctree
.get_abspath())
173 elif doctree
is not None and doctree
!= '':
174 doctree
= env
.subst(doctree
, target
=target
, source
=source
)
175 options
.append("-d %s" % env
.Dir(doctree
).get_abspath())
177 config
= _get_sphinxconfig_path(env
, None)
178 if config
is not None and config
!= '':
179 config
= env
.subst(config
, target
=target
, source
=source
)
180 options
.append("-c %s" % env
.Dir(config
).File('conf.py').rfile().dir.get_abspath())
181 return " ".join(options
)
184 def _create_sphinx_builder(env
):
186 sphinx
= env
['BUILDERS']['Sphinx4Scons']
188 fs
= SCons
.Node
.FS
.get_default_fs()
189 sphinx_com
= SCons
.Action
.Action('$SPHINXCOM', '$SPHINXCOMSTR')
190 sphinx
= SCons
.Builder
.Builder(action
=sphinx_com
,
191 emitter
=sphinx_emitter
,
192 target_factory
=fs
.Dir
,
193 source_factory
=fs
.Dir
195 env
['BUILDERS']['Sphinx4Scons'] = sphinx
199 def sphinx_emitter(target
, source
, env
):
200 target
[0].must_be_same(SCons
.Node
.FS
.Dir
)
201 targetnode
= target
[0]
203 source
[0].must_be_same(SCons
.Node
.FS
.Dir
)
206 configdir
= _get_sphinxconfig_path(env
, None)
210 confignode
= env
.Dir(configdir
)
212 srcinfo
= SourceInfo(srcnode
, confignode
, env
)
213 targets
, sources
= _get_emissions(env
, target
, srcinfo
)
214 env
.Clean(targets
, target
[0])
216 return targets
, sources
219 def sphinx_path(os_path
):
220 """Create sphinx-style path from os-style path."""
221 return os_path
.replace(os
.sep
, "/")
224 def os_path(sphinx_path
):
225 """Create os-style path from sphinx-style path."""
226 return sphinx_path
.replace("/", os
.sep
)
229 class SourceInfo(object):
231 Data container for all different kinds of source files used in
234 def __init__(self
, srcnode
, confignode
, env
):
235 self
.confignode
= confignode
236 self
.config
= self
._get
_config
(self
.confignode
, env
)
237 self
.templates
= self
._get
_templates
(self
.confignode
, self
.config
)
238 self
.statics
= self
._get
_statics
(self
.confignode
, self
.config
)
239 self
.srcnode
= srcnode
240 self
.sources
= self
._get
_sources
(self
.srcnode
, self
.config
)
242 self
.srcroot
= srcnode
243 if not srcnode
.duplicate
:
244 self
.srcroot
= srcnode
.srcnode().rdir()
247 def _get_config(self
, confignode
, env
):
249 execfile(confignode
.File('conf.py').rfile().get_abspath(), config
)
253 def _get_templates(self
, confignode
, config
):
254 """Returns template files defined in the project."""
256 for path
in config
.get('templates_path', []):
257 # Check if path is dir or file.
258 # We can't use FS.Entry since that will create nodes, and
259 # these nodes don't know about the source tree and will
260 # get disambiguated to files even if they are directories in the
262 p
= confignode
.File('conf.py').rfile().dir.srcnode().get_abspath()
263 p
= os
.path
.join(p
, os_path(path
))
264 if os
.path
.isfile(p
):
265 templates
.append(confignode
.File(path
))
266 elif os
.path
.isdir(p
):
267 node
= confignode
.Dir(path
)
268 for root
, dirs
, files
in os
.walk(p
):
269 mydir
= node
.Dir(os
.path
.relpath(root
, p
))
270 templates
+= [mydir
.File(f
) for f
in files
]
274 def _get_statics(self
, confignode
, config
):
275 """Returns static files, filtered through exclude_patterns."""
277 matchers
= compile_matchers(config
.get('exclude_patterns', []))
279 for path
in config
.get('html_static_path', []):
280 # Check _get_templates() why we use this construction.
281 p
= confignode
.File('conf.py').rfile().dir.srcnode().get_abspath()
282 p
= os
.path
.join(p
, os_path(path
))
283 if os
.path
.isfile(p
):
284 statics
.append(confignode
.File(path
))
285 elif os
.path
.isdir(p
):
286 node
= confignode
.Dir(path
)
287 for root
, dirs
, files
in os
.walk(p
):
288 relpath
= os
.path
.relpath(root
, p
)
289 for entry
in [d
for d
in dirs
if
290 self
._anymatch
(matchers
,
291 sphinx_path(os
.path
.join(relpath
, d
)))]:
293 statics
+= [node
.File(os_path(f
)) for f
in
294 self
._exclude
(matchers
,
295 [sphinx_path(os
.path
.join(relpath
, name
))
300 def _get_sources(self
, srcnode
, config
):
301 """Returns all source files in the project filtered through exclude_patterns."""
302 suffix
= config
.get('source_suffix', '.rst')
303 matchers
= compile_matchers(config
.get('exclude_patterns', []))
306 scannode
= srcnode
.srcnode().rdir()
308 for root
, dirs
, files
in os
.walk(scannode
.get_abspath()):
309 relpath
= os
.path
.relpath(root
, scannode
.get_abspath())
310 for entry
in [d
for d
in dirs
if
311 self
._anymatch
(matchers
,
312 sphinx_path(os
.path
.join(relpath
, d
)))]:
314 srcfiles
+= [srcnode
.File(os_path(f
)) for f
in
315 self
._exclude
(matchers
,
316 [sphinx_path(os
.path
.join(relpath
, name
))
317 for name
in files
if name
.endswith(suffix
)])]
321 def _exclude(self
, matchers
, items
):
323 for matcher
in matchers
:
324 result
= filter(lambda x
: not matcher(x
), result
)
328 def _anymatch(self
, matchers
, item
):
329 for matcher
in matchers
:
335 def _get_sphinxconfig_path(env
, default
):
336 path
= env
.get('config', env
.get('SPHINXCONFIG', None))
337 if path
is None or path
== '':
342 def _get_emissions(env
, target
, srcinfo
):
345 builder
= _get_sphinxbuilder(env
)
346 if builder
== 'changes':
347 targets
, sources
= _get_changes_emissions(env
, target
, srcinfo
)
348 if builder
== 'devhelp':
349 targets
, sources
= _get_help_emissions(env
, target
, srcinfo
,
351 elif builder
== 'dirhtml':
352 targets
, sources
= _get_dirhtml_emissions(env
, target
, srcinfo
)
353 elif builder
== 'doctest':
354 targets
, sources
= _get_doctest_emissions(env
, target
, srcinfo
)
355 elif builder
== 'epub':
356 targets
, sources
= _get_epub_emissions(env
, target
, srcinfo
)
357 elif builder
== 'html':
358 targets
, sources
= _get_serialize_emissions(env
, target
, srcinfo
)
359 elif builder
== 'htmlhelp':
360 targets
, sources
= _get_help_emissions(env
, target
, srcinfo
,
361 ['.hhp'], 'htmlhelp_basename')
362 elif builder
== 'gettext':
363 targets
, sources
= _get_gettext_emissions(env
, target
, srcinfo
)
364 elif builder
== 'json':
365 targets
, sources
= _get_serialize_emissions(env
, target
, srcinfo
,
367 ['globalcontext.json',
369 'self.environment.pickle'])
370 elif builder
== 'latex':
371 targets
, sources
= _get_latex_emissions(env
, target
, srcinfo
)
372 elif builder
== 'linkcheck':
373 targets
, sources
= _get_linkcheck_emissions(env
, target
, srcinfo
)
374 elif builder
== 'man':
375 targets
, sources
= _get_man_emissions(env
, target
, srcinfo
)
376 elif builder
== 'pickle':
377 targets
, sources
= _get_serialize_emissions(env
, target
, srcinfo
,
379 ['globalcontext.pickle',
380 'searchindex.pickle',
381 'environment.pickle'])
382 elif builder
== 'qthelp':
383 targets
, sources
= _get_help_emissions(env
, target
, srcinfo
,
385 elif builder
== 'singlehtml':
386 targets
, sources
= _get_singlehtml_emissions(env
, target
, srcinfo
)
387 elif builder
== 'texinfo':
388 targets
, sources
= _get_texinfo_emissions(env
, target
, srcinfo
)
389 elif builder
== 'text':
390 targets
, sources
= _get_text_emissions(env
, target
, srcinfo
)
392 sources
.append(srcinfo
.confignode
.File('conf.py'))
395 s
.attributes
.root
= srcinfo
.srcroot
398 t
.attributes
.root
= target
[0]
400 return targets
, sources
403 def _get_sphinxbuilder(env
):
404 builder
= env
.get('builder', env
["SPHINXBUILDER"])
405 if builder
is None or builder
== '':
406 raise SCons
.Errors
.UserError(("Missing construction variable " +
407 "SPHINXBUILDER or variable is empty."))
411 def _get_changes_emissions(env
, target
, srcinfo
):
413 sources
.extend(srcinfo
.sources
)
414 targets
= [target
[0].File("changes.html")]
415 return targets
, sources
418 def _get_dirhtml_emissions(env
, target
, srcinfo
):
419 suffix
= srcinfo
.config
.get('html_file_suffix', ".html")
421 def get_outfilename(pagename
):
422 pagename
= os
.path
.splitext(pagename
)[0]
424 #Special treatment of files named "index". Don't create directory.
425 if pagename
== 'index' or pagename
.endswith(os
.sep
+ 'index'):
426 outfilename
= pagename
+ suffix
428 outfilename
= os
.path
.join(pagename
, 'index' + suffix
)
432 sources
.extend(srcinfo
.sources
)
433 sources
.extend(srcinfo
.templates
)
434 sources
.extend(srcinfo
.statics
)
437 for s
in srcinfo
.sources
:
438 t
= os
.path
.relpath(str(s
), str(srcinfo
.srcroot
))
439 targets
.append(target
[0].File(get_outfilename(t
)))
441 for key
in srcinfo
.config
.get('html_additional_pages', {}):
442 t
= target
[0].File(get_outfilename(key
))
445 return targets
, sources
448 def _get_doctest_emissions(env
, target
, srcinfo
):
450 sources
.extend(srcinfo
.sources
)
451 targets
= [target
[0].File("output.txt")]
452 return targets
, sources
455 def _get_epub_emissions(env
, target
, srcinfo
):
456 epubPreFiles
= srcinfo
.config
.get('epub_pre_files', [])
457 epubPostFiles
= srcinfo
.config
.get('epub_post_files', [])
458 epubCover
= srcinfo
.config
.get('epub_cover', (None, None))
461 sources
.extend(srcinfo
.sources
)
462 sources
.extend([srcinfo
.srcroot
.File(os_path(f
[0])) for f
in epubPreFiles
])
463 sources
.extend([srcinfo
.srcroot
.File(os_path(f
[0])) for f
in epubPostFiles
])
464 if not (epubCover
[0] is None or epubCover
[0] == ''):
465 sources
.append(srcinfo
.srcroot
.File(os_path(epubCover
[0])))
466 if not (epubCover
[1] is None or epubCover
[1] == ''):
467 sources
.append(srcinfo
.srcroot
.File(os_path(epubCover
[1])))
469 t
= srcinfo
.config
.get('epub_basename',
470 srcinfo
.config
.get('project',
473 targets
= [target
[0].File("%s.epub" % make_filename(t
))]
475 return targets
, sources
478 def _get_gettext_emissions(env
, target
, srcinfo
):
480 sources
.extend(srcinfo
.sources
)
482 targets
= [os
.path
.relpath(str(s
), str(srcinfo
.srcroot
)) for s
in sources
]
483 targets
= [os
.path
.splitext(t
)[0] for t
in targets
]
484 targets
= set([t
.split(os
.sep
)[0] for t
in targets
])
485 targets
= [target
[0].File(t
+ ".pot") for t
in targets
]
487 return targets
, sources
490 def _get_help_emissions(env
, target
, srcinfo
, suffixes
, basenameConfigKey
='project'):
491 basename
= make_filename(
492 srcinfo
.config
.get(basenameConfigKey
, srcinfo
.config
['project']))
495 sources
.extend(srcinfo
.sources
)
496 sources
.extend(srcinfo
.templates
)
497 sources
.extend(srcinfo
.statics
)
499 targets
= [target
[0].File(basename
+ s
) for s
in suffixes
]
501 return targets
, sources
504 def _get_latex_emissions(env
, target
, srcinfo
):
506 sources
.extend(srcinfo
.sources
)
508 targets
= map(lambda x
: target
[0].File(os_path(x
[1])),
509 srcinfo
.config
.get('latex_documents'))
511 return targets
, sources
514 def _get_linkcheck_emissions(env
, target
, srcinfo
):
516 sources
.extend(srcinfo
.sources
)
517 targets
= [target
[0].File("output.txt")]
518 return targets
, sources
521 def _get_man_emissions(env
, target
, srcinfo
):
523 sources
.extend(srcinfo
.sources
)
524 targets
= map(lambda x
: target
[0].File(os_path("%s.%s" % (x
[1], x
[4]))),
525 srcinfo
.config
.get('man_pages'))
526 return targets
, sources
529 def _get_serialize_emissions(env
, target
, srcinfo
, suffix
=None, extrafiles
=[]):
531 suffix
= srcinfo
.config
.get('html_file_suffix', '.html')
534 sources
.extend(srcinfo
.sources
)
535 sources
.extend(srcinfo
.templates
)
536 sources
.extend(srcinfo
.statics
)
539 for s
in srcinfo
.sources
:
540 t
= os
.path
.splitext(str(s
))[0] + suffix
541 t
= os
.path
.relpath(t
, str(srcinfo
.srcroot
))
544 for key
in srcinfo
.config
.get('html_additional_pages', {}):
545 targets
.append(os_path("%s%s" % (key
, suffix
)))
547 targets
.extend(extrafiles
)
548 targets
= [target
[0].File(t
) for t
in targets
]
550 return targets
, sources
553 def _get_singlehtml_emissions(env
, target
, srcinfo
):
554 suffix
= srcinfo
.config
.get('html_file_suffix', ".html")
557 sources
.extend(srcinfo
.sources
)
558 sources
.extend(srcinfo
.templates
)
559 sources
.extend(srcinfo
.statics
)
561 t
= os
.path
.relpath(srcinfo
.config
['master_doc'] + suffix
,
562 str(srcinfo
.srcroot
))
563 targets
= [target
[0].File(t
)]
565 return targets
, sources
568 def _get_texinfo_emissions(env
, target
, srcinfo
):
569 suffix
= srcinfo
.config
.get('source_suffix', '.rst')
572 sources
.extend(srcinfo
.sources
)
573 sources
.extend(map(lambda x
: source
[0].File(os_path(x
+ suffix
)),
574 srcinfo
.config
.get('texinfo_appendices', [])))
576 targets
= map(lambda x
: target
[0].File(os_path("%s.texi" % x
[1])),
577 srcinfo
.config
.get('texinfo_documents'))
579 return targets
, sources
582 def _get_text_emissions(env
, target
, srcinfo
):
584 sources
.extend(srcinfo
.sources
)
588 t
= os
.path
.relpath(str(s
), str(srcinfo
.srcroot
))
589 t
= os
.path
.splitext(t
)[0] + ".txt"
590 targets
.append(target
[0].File(t
))
592 return targets
, sources