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(task
, node
, nodes
, names
, seen
, dirs
=None):
40 # TODO add extensibility, to handle custom rst include tags...
42 dirs
= (node
.parent
,node
.get_bld().parent
)
48 re_rst
= re
.compile(r
'^\s*.. ((?P<subst>\|\S+\|) )?(?P<type>include|image|figure):: (?P<file>.*)$', re
.M
)
49 for match
in re_rst
.finditer(code
):
50 ipath
= match
.group('file')
51 itype
= match
.group('type')
52 Logs
.debug('rst: visiting %s: %s', itype
, ipath
)
55 Logs
.debug('rst: looking for %s in %s', ipath
, d
.abspath())
56 found
= d
.find_node(ipath
)
58 Logs
.debug('rst: found %s as %s', ipath
, found
.abspath())
59 nodes
.append((itype
, found
))
60 if itype
== 'include':
61 parse_rst_node(task
, found
, nodes
, names
, seen
)
64 names
.append((itype
, ipath
))
66 class docutils(Task
.Task
):
73 A recursive regex-based scanner that finds rst dependencies.
85 parse_rst_node(self
, node
, nodes
, names
, seen
)
87 Logs
.debug('rst: %r: found the following file deps: %r', self
, nodes
)
89 Logs
.warn('rst: %r: could not find the following file deps: %r', self
, names
)
91 return ([v
for (t
,v
) in nodes
], [v
for (t
,v
) in names
])
93 def check_status(self
, msg
, retcode
):
95 Check an exit status and raise an error with a particular message
97 :param msg: message to display if the code is non-zero
99 :param retcode: condition
100 :type retcode: boolean
103 raise Errors
.WafError('%r command exit status %r' % (msg
, retcode
))
107 Runs the rst compilation using docutils
109 raise NotImplementedError()
111 class rst2html(docutils
):
114 def __init__(self
, *args
, **kw
):
115 docutils
.__init
__(self
, *args
, **kw
)
116 self
.command
= self
.generator
.env
.RST2HTML
117 self
.attributes
= ['stylesheet']
120 nodes
, names
= docutils
.scan(self
)
122 for attribute
in self
.attributes
:
123 stylesheet
= getattr(self
.generator
, attribute
, None)
124 if stylesheet
is not None:
125 ssnode
= self
.generator
.to_nodes(stylesheet
)[0]
127 Logs
.debug('rst: adding dep to %s %s', attribute
, stylesheet
)
132 cwdn
= self
.outputs
[0].parent
133 src
= self
.inputs
[0].path_from(cwdn
)
134 dst
= self
.outputs
[0].path_from(cwdn
)
136 cmd
= self
.command
+ [src
, dst
]
137 cmd
+= Utils
.to_list(getattr(self
.generator
, 'options', []))
138 for attribute
in self
.attributes
:
139 stylesheet
= getattr(self
.generator
, attribute
, None)
140 if stylesheet
is not None:
141 stylesheet
= self
.generator
.to_nodes(stylesheet
)[0]
142 cmd
+= ['--%s' % attribute
, stylesheet
.path_from(cwdn
)]
144 return self
.exec_command(cmd
, cwd
=cwdn
.abspath())
146 class rst2s5(rst2html
):
147 def __init__(self
, *args
, **kw
):
148 rst2html
.__init
__(self
, *args
, **kw
)
149 self
.command
= self
.generator
.env
.RST2S5
150 self
.attributes
= ['stylesheet']
152 class rst2latex(rst2html
):
153 def __init__(self
, *args
, **kw
):
154 rst2html
.__init
__(self
, *args
, **kw
)
155 self
.command
= self
.generator
.env
.RST2LATEX
156 self
.attributes
= ['stylesheet']
158 class rst2xetex(rst2html
):
159 def __init__(self
, *args
, **kw
):
160 rst2html
.__init
__(self
, *args
, **kw
)
161 self
.command
= self
.generator
.env
.RST2XETEX
162 self
.attributes
= ['stylesheet']
164 class rst2pdf(docutils
):
167 cwdn
= self
.outputs
[0].parent
168 src
= self
.inputs
[0].path_from(cwdn
)
169 dst
= self
.outputs
[0].path_from(cwdn
)
171 cmd
= self
.generator
.env
.RST2PDF
+ [src
, '-o', dst
]
172 cmd
+= Utils
.to_list(getattr(self
.generator
, 'options', []))
174 return self
.exec_command(cmd
, cwd
=cwdn
.abspath())
178 @before_method('process_source')
181 Create :py:class:`rst` or other rst-related task objects
185 if isinstance(self
.target
, Node
.Node
):
187 elif isinstance(self
.target
, str):
188 tgt
= self
.path
.get_bld().make_node(self
.target
)
190 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
))
194 tsk_type
= getattr(self
, 'type', None)
196 src
= self
.to_nodes(self
.source
)
200 if tsk_type
is not None and tgt
is None:
201 if tsk_type
.startswith('rst2'):
204 self
.bld
.fatal("rst: Could not detect the output file extension for %s" % self
)
205 tgt
= src
.change_ext('.%s' % ext
)
206 elif tsk_type
is None and tgt
is not None:
208 ext
= out
[out
.rfind('.')+1:]
209 self
.type = 'rst2' + ext
210 elif tsk_type
is not None and tgt
is not None:
211 # the user knows what he wants
214 self
.bld
.fatal("rst: Need to indicate task type or target name for %s" % self
)
218 if getattr(self
, 'deps', None):
219 deps
= self
.to_list(self
.deps
)
220 for filename
in deps
:
221 n
= self
.path
.find_resource(filename
)
223 self
.bld
.fatal('Could not find %r for %r' % (filename
, self
))
224 if not n
in deps_lst
:
228 task
= self
.create_task(self
.type, src
, tgt
)
230 self
.bld
.fatal("rst: Task of type %s not implemented (created by %s)" % (self
.type, self
))
234 # add the manual dependencies
237 lst
= self
.bld
.node_deps
[task
.uid()]
242 self
.bld
.node_deps
[task
.uid()] = deps_lst
244 inst_to
= getattr(self
, 'install_path', None)
246 self
.install_task
= self
.add_install_files(install_to
=inst_to
, install_from
=task
.outputs
[:])
252 Try to find the rst programs.
254 Do not raise any error if they are not found.
255 You'll have to use additional code in configure() to die
256 if programs were not found.
259 self
.find_program(p
, mandatory
=False)