c8cd7526dccca38eb12d6729ce9ad1c44bcac81d
[bbaumbach/samba.git] / third_party / waf / waflib / extras / rst.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Jérôme Carretero, 2013 (zougloub)
4
5 """
6 reStructuredText support (experimental)
7
8 Example::
9
10         def configure(conf):
11                 conf.load('rst')
12                 if not conf.env.RST2HTML:
13                         conf.fatal('The program rst2html is required')
14
15         def build(bld):
16                 bld(
17                  features = 'rst',
18                  type     = 'rst2html', # rst2html, rst2pdf, ...
19                  source   = 'index.rst', # mandatory, the source
20                  deps     = 'image.png', # to give additional non-trivial dependencies
21                 )
22
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::
26
27         $ RST2HTML=/path/to/rst2html waf configure
28
29 This tool is experimental; don't hesitate to contribute to it.
30
31 """
32
33 import re
34 from waflib import Node, Utils, Task, Errors, Logs
35 from waflib.TaskGen import feature, before_method
36
37 rst_progs = "rst2html rst2xetex rst2latex rst2xml rst2pdf rst2s5 rst2man rst2odt rst2rtf".split()
38
39 def parse_rst_node(node, nodes, names, seen):
40         # TODO add extensibility, to handle custom rst include tags...
41         if node in seen:
42                 return
43         seen.append(node)
44         code = node.read()
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)
51                 if found:
52                         nodes.append(found)
53                         if itype == 'include':
54                                 parse_rst_node(found, nodes, names, seen)
55                 else:
56                         names.append(ipath)
57
58 class docutils(Task.Task):
59         """
60         Compile a rst file.
61         """
62
63         def scan(self):
64                 """
65                 A recursive regex-based scanner that finds rst dependencies.
66                 """
67
68                 nodes = []
69                 names = []
70                 seen = []
71
72                 node = self.inputs[0]
73
74                 if not node:
75                         return (nodes, names)
76
77                 parse_rst_node(node, nodes, names, seen)
78
79                 Logs.debug("rst: %s: found the following file deps: %s" % (repr(self), nodes))
80                 if names:
81                         Logs.warn("rst: %s: could not find the following file deps: %s" % (repr(self), names))
82
83                 return (nodes, names)
84
85         def check_status(self, msg, retcode):
86                 """
87                 Check an exit status and raise an error with a particular message
88
89                 :param msg: message to display if the code is non-zero
90                 :type msg: string
91                 :param retcode: condition
92                 :type retcode: boolean
93                 """
94                 if retcode != 0:
95                         raise Errors.WafError("%r command exit status %r" % (msg, retcode))
96
97         def run(self):
98                 """
99                 Runs the rst compilation using docutils
100                 """
101                 raise NotImplementedError()
102
103 class rst2html(docutils):
104         color = 'BLUE'
105
106         def __init__(self, *args, **kw):
107                 docutils.__init__(self, *args, **kw)
108                 self.command = self.generator.env.RST2HTML
109                 self.attributes = ['stylesheet']
110
111         def scan(self):
112                 nodes, names = docutils.scan(self)
113
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]
118                                 nodes.append(ssnode)
119                                 Logs.debug("rst: adding dep to %s %s" % (attribute, stylesheet))
120
121                 return nodes, names
122
123         def run(self):
124                 cwdn = self.outputs[0].parent
125                 src = self.inputs[0].path_from(cwdn)
126                 dst = self.outputs[0].path_from(cwdn)
127
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)]
135
136                 return self.exec_command(cmd, cwd=cwdn.abspath())
137
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']
143
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']
149
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']
155
156 class rst2pdf(docutils):
157         color = 'BLUE'
158         def run(self):
159                 cwdn = self.outputs[0].parent
160                 src = self.inputs[0].path_from(cwdn)
161                 dst = self.outputs[0].path_from(cwdn)
162
163                 cmd = self.generator.env.RST2PDF + [src, '-o', dst]
164                 cmd += Utils.to_list(getattr(self.generator, 'options', []))
165
166                 return self.exec_command(cmd, cwd=cwdn.abspath())
167
168
169 @feature('rst')
170 @before_method('process_source')
171 def apply_rst(self):
172         """
173         Create :py:class:`rst` or other rst-related task objects
174         """
175
176         if self.target:
177                 if isinstance(self.target, Node.Node):
178                         tgt = self.target
179                 elif isinstance(self.target, str):
180                         tgt = self.path.get_bld().make_node(self.target)
181                 else:
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))
183         else:
184                 tgt = None
185
186         tsk_type = getattr(self, 'type', None)
187
188         src = self.to_nodes(self.source)
189         assert len(src) == 1
190         src = src[0]
191
192         if tsk_type is not None and tgt is None:
193                 if tsk_type.startswith('rst2'):
194                         ext = tsk_type[4:]
195                 else:
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:
199                 out = tgt.name
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
204                 pass
205         else:
206                 self.bld.fatal("rst: Need to indicate task type or target name for %s" % self)
207
208         deps_lst = []
209
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)
214                         if not n:
215                                 self.bld.fatal('Could not find %r for %r' % (filename, self))
216                         if not n in deps_lst:
217                                 deps_lst.append(n)
218
219         try:
220                 task = self.create_task(self.type, src, tgt)
221         except KeyError:
222                 self.bld.fatal("rst: Task of type %s not implemented (created by %s)" % (self.type, self))
223
224         task.env = self.env
225
226         # add the manual dependencies
227         if deps_lst:
228                 try:
229                         lst = self.bld.node_deps[task.uid()]
230                         for n in deps_lst:
231                                 if not n in lst:
232                                         lst.append(n)
233                 except KeyError:
234                         self.bld.node_deps[task.uid()] = deps_lst
235
236         inst_to = getattr(self, 'install_path', None)
237         if inst_to:
238                 self.install_task = self.bld.install_files(inst_to, task.outputs[:], env=self.env)
239
240         self.source = []
241
242 def configure(self):
243         """
244         Try to find the rst programs.
245
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.
249         """
250         for p in rst_progs:
251                 self.find_program(p, mandatory=False)