3 # Jérôme Carretero, 2013 (zougloub)
6 reStructuredText support (experimental)
12 if not conf.env.RST2HTML:
13 conf.fatal('The program rst2html is required')
18 type = 'rst2html', # rst2html, rst2pdf, ...
19 source = 'index.rst', # mandatory, the source
20 deps = 'image.png', # to give additional non-trivial dependencies
23 By default the tool looks for a set of programs in PATH.
24 The tools are defined in `rst_progs`.
25 To configure with a special program use::
27 $ RST2HTML=/path/to/rst2html waf configure
29 This tool is experimental; don't hesitate to contribute to it.
34 from waflib
import Node
, Utils
, Task
, Errors
, Logs
35 from waflib
.TaskGen
import feature
, before_method
37 rst_progs
= "rst2html rst2xetex rst2latex rst2xml rst2pdf rst2s5 rst2man rst2odt rst2rtf".split()
39 def parse_rst_node(node
, nodes
, names
, seen
):
40 # TODO add extensibility, to handle custom rst include tags...
45 re_rst
= re
.compile(r
'^\s*.. ((?P<subst>\|\S+\|) )?(?P<type>include|image|figure):: (?P<file>.*)$', re
.M
)
46 for match
in re_rst
.finditer(code
):
47 ipath
= match
.group('file')
48 itype
= match
.group('type')
49 Logs
.debug("rst: visiting %s: %s" % (itype
, ipath
))
50 found
= node
.parent
.find_resource(ipath
)
53 if itype
== 'include':
54 parse_rst_node(found
, nodes
, names
, seen
)
58 class docutils(Task
.Task
):
65 A recursive regex-based scanner that finds rst dependencies.
77 parse_rst_node(node
, nodes
, names
, seen
)
79 Logs
.debug("rst: %s: found the following file deps: %s" % (repr(self
), nodes
))
81 Logs
.warn("rst: %s: could not find the following file deps: %s" % (repr(self
), names
))
85 def check_status(self
, msg
, retcode
):
87 Check an exit status and raise an error with a particular message
89 :param msg: message to display if the code is non-zero
91 :param retcode: condition
92 :type retcode: boolean
95 raise Errors
.WafError("%r command exit status %r" % (msg
, retcode
))
99 Runs the rst compilation using docutils
101 raise NotImplementedError()
103 class rst2html(docutils
):
106 def __init__(self
, *args
, **kw
):
107 docutils
.__init
__(self
, *args
, **kw
)
108 self
.command
= self
.generator
.env
.RST2HTML
109 self
.attributes
= ['stylesheet']
112 nodes
, names
= docutils
.scan(self
)
114 for attribute
in self
.attributes
:
115 stylesheet
= getattr(self
.generator
, attribute
, None)
116 if stylesheet
is not None:
117 ssnode
= self
.generator
.to_nodes(stylesheet
)[0]
119 Logs
.debug("rst: adding dep to %s %s" % (attribute
, stylesheet
))
124 cwdn
= self
.outputs
[0].parent
125 src
= self
.inputs
[0].path_from(cwdn
)
126 dst
= self
.outputs
[0].path_from(cwdn
)
128 cmd
= self
.command
+ [src
, dst
]
129 cmd
+= Utils
.to_list(getattr(self
.generator
, 'options', []))
130 for attribute
in self
.attributes
:
131 stylesheet
= getattr(self
.generator
, attribute
, None)
132 if stylesheet
is not None:
133 stylesheet
= self
.generator
.to_nodes(stylesheet
)[0]
134 cmd
+= ['--%s' % attribute
, stylesheet
.path_from(cwdn
)]
136 return self
.exec_command(cmd
, cwd
=cwdn
.abspath())
138 class rst2s5(rst2html
):
139 def __init__(self
, *args
, **kw
):
140 rst2html
.__init
__(self
, *args
, **kw
)
141 self
.command
= self
.generator
.env
.RST2S5
142 self
.attributes
= ['stylesheet']
144 class rst2latex(rst2html
):
145 def __init__(self
, *args
, **kw
):
146 rst2html
.__init
__(self
, *args
, **kw
)
147 self
.command
= self
.generator
.env
.RST2LATEX
148 self
.attributes
= ['stylesheet']
150 class rst2xetex(rst2html
):
151 def __init__(self
, *args
, **kw
):
152 rst2html
.__init
__(self
, *args
, **kw
)
153 self
.command
= self
.generator
.env
.RST2XETEX
154 self
.attributes
= ['stylesheet']
156 class rst2pdf(docutils
):
159 cwdn
= self
.outputs
[0].parent
160 src
= self
.inputs
[0].path_from(cwdn
)
161 dst
= self
.outputs
[0].path_from(cwdn
)
163 cmd
= self
.generator
.env
.RST2PDF
+ [src
, '-o', dst
]
164 cmd
+= Utils
.to_list(getattr(self
.generator
, 'options', []))
166 return self
.exec_command(cmd
, cwd
=cwdn
.abspath())
170 @before_method('process_source')
173 Create :py:class:`rst` or other rst-related task objects
177 if isinstance(self
.target
, Node
.Node
):
179 elif isinstance(self
.target
, str):
180 tgt
= self
.path
.get_bld().make_node(self
.target
)
182 self
.bld
.fatal("rst: Don't know how to build target name %s which is not a string or Node for %s" % (self
.target
, self
))
186 tsk_type
= getattr(self
, 'type', None)
188 src
= self
.to_nodes(self
.source
)
192 if tsk_type
is not None and tgt
is None:
193 if tsk_type
.startswith('rst2'):
196 self
.bld
.fatal("rst: Could not detect the output file extension for %s" % self
)
197 tgt
= src
.change_ext('.%s' % ext
)
198 elif tsk_type
is None and tgt
is not None:
200 ext
= out
[out
.rfind('.')+1:]
201 self
.type = 'rst2' + ext
202 elif tsk_type
is not None and tgt
is not None:
203 # the user knows what he wants
206 self
.bld
.fatal("rst: Need to indicate task type or target name for %s" % self
)
210 if getattr(self
, 'deps', None):
211 deps
= self
.to_list(self
.deps
)
212 for filename
in deps
:
213 n
= self
.path
.find_resource(filename
)
215 self
.bld
.fatal('Could not find %r for %r' % (filename
, self
))
216 if not n
in deps_lst
:
220 task
= self
.create_task(self
.type, src
, tgt
)
222 self
.bld
.fatal("rst: Task of type %s not implemented (created by %s)" % (self
.type, self
))
226 # add the manual dependencies
229 lst
= self
.bld
.node_deps
[task
.uid()]
234 self
.bld
.node_deps
[task
.uid()] = deps_lst
236 inst_to
= getattr(self
, 'install_path', None)
238 self
.install_task
= self
.bld
.install_files(inst_to
, task
.outputs
[:], env
=self
.env
)
244 Try to find the rst programs.
246 Do not raise any error if they are not found.
247 You'll have to use additional code in configure() to die
248 if programs were not found.
251 self
.find_program(p
, mandatory
=False)