waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

rst.py (6951B)


      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(task, node, nodes, names, seen, dirs=None):
     40 	# TODO add extensibility, to handle custom rst include tags...
     41 	if dirs is None:
     42 		dirs = (node.parent,node.get_bld().parent)
     43 
     44 	if node in seen:
     45 		return
     46 	seen.append(node)
     47 	code = node.read()
     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)
     53 		found = False
     54 		for d in dirs:
     55 			Logs.debug('rst: looking for %s in %s', ipath, d.abspath())
     56 			found = d.find_node(ipath)
     57 			if found:
     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)
     62 				break
     63 		if not found:
     64 			names.append((itype, ipath))
     65 
     66 class docutils(Task.Task):
     67 	"""
     68 	Compile a rst file.
     69 	"""
     70 
     71 	def scan(self):
     72 		"""
     73 		A recursive regex-based scanner that finds rst dependencies.
     74 		"""
     75 
     76 		nodes = []
     77 		names = []
     78 		seen = []
     79 
     80 		node = self.inputs[0]
     81 
     82 		if not node:
     83 			return (nodes, names)
     84 
     85 		parse_rst_node(self, node, nodes, names, seen)
     86 
     87 		Logs.debug('rst: %r: found the following file deps: %r', self, nodes)
     88 		if names:
     89 			Logs.warn('rst: %r: could not find the following file deps: %r', self, names)
     90 
     91 		return ([v for (t,v) in nodes], [v for (t,v) in names])
     92 
     93 	def check_status(self, msg, retcode):
     94 		"""
     95 		Check an exit status and raise an error with a particular message
     96 
     97 		:param msg: message to display if the code is non-zero
     98 		:type msg: string
     99 		:param retcode: condition
    100 		:type retcode: boolean
    101 		"""
    102 		if retcode != 0:
    103 			raise Errors.WafError('%r command exit status %r' % (msg, retcode))
    104 
    105 	def run(self):
    106 		"""
    107 		Runs the rst compilation using docutils
    108 		"""
    109 		raise NotImplementedError()
    110 
    111 class rst2html(docutils):
    112 	color = 'BLUE'
    113 
    114 	def __init__(self, *args, **kw):
    115 		docutils.__init__(self, *args, **kw)
    116 		self.command = self.generator.env.RST2HTML
    117 		self.attributes = ['stylesheet']
    118 
    119 	def scan(self):
    120 		nodes, names = docutils.scan(self)
    121 
    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]
    126 				nodes.append(ssnode)
    127 				Logs.debug('rst: adding dep to %s %s', attribute, stylesheet)
    128 
    129 		return nodes, names
    130 
    131 	def run(self):
    132 		cwdn = self.outputs[0].parent
    133 		src = self.inputs[0].path_from(cwdn)
    134 		dst = self.outputs[0].path_from(cwdn)
    135 
    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)]
    143 
    144 		return self.exec_command(cmd, cwd=cwdn.abspath())
    145 
    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']
    151 
    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']
    157 
    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']
    163 
    164 class rst2pdf(docutils):
    165 	color = 'BLUE'
    166 	def run(self):
    167 		cwdn = self.outputs[0].parent
    168 		src = self.inputs[0].path_from(cwdn)
    169 		dst = self.outputs[0].path_from(cwdn)
    170 
    171 		cmd = self.generator.env.RST2PDF + [src, '-o', dst]
    172 		cmd += Utils.to_list(getattr(self.generator, 'options', []))
    173 
    174 		return self.exec_command(cmd, cwd=cwdn.abspath())
    175 
    176 
    177 @feature('rst')
    178 @before_method('process_source')
    179 def apply_rst(self):
    180 	"""
    181 	Create :py:class:`rst` or other rst-related task objects
    182 	"""
    183 
    184 	if self.target:
    185 		if isinstance(self.target, Node.Node):
    186 			tgt = self.target
    187 		elif isinstance(self.target, str):
    188 			tgt = self.path.get_bld().make_node(self.target)
    189 		else:
    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))
    191 	else:
    192 		tgt = None
    193 
    194 	tsk_type = getattr(self, 'type', None)
    195 
    196 	src = self.to_nodes(self.source)
    197 	assert len(src) == 1
    198 	src = src[0]
    199 
    200 	if tsk_type is not None and tgt is None:
    201 		if tsk_type.startswith('rst2'):
    202 			ext = tsk_type[4:]
    203 		else:
    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:
    207 		out = tgt.name
    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
    212 		pass
    213 	else:
    214 		self.bld.fatal("rst: Need to indicate task type or target name for %s" % self)
    215 
    216 	deps_lst = []
    217 
    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)
    222 			if not n:
    223 				self.bld.fatal('Could not find %r for %r' % (filename, self))
    224 			if not n in deps_lst:
    225 				deps_lst.append(n)
    226 
    227 	try:
    228 		task = self.create_task(self.type, src, tgt)
    229 	except KeyError:
    230 		self.bld.fatal("rst: Task of type %s not implemented (created by %s)" % (self.type, self))
    231 
    232 	task.env = self.env
    233 
    234 	# add the manual dependencies
    235 	if deps_lst:
    236 		try:
    237 			lst = self.bld.node_deps[task.uid()]
    238 			for n in deps_lst:
    239 				if not n in lst:
    240 					lst.append(n)
    241 		except KeyError:
    242 			self.bld.node_deps[task.uid()] = deps_lst
    243 
    244 	inst_to = getattr(self, 'install_path', None)
    245 	if inst_to:
    246 		self.install_task = self.add_install_files(install_to=inst_to, install_from=task.outputs[:])
    247 
    248 	self.source = []
    249 
    250 def configure(self):
    251 	"""
    252 	Try to find the rst programs.
    253 
    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.
    257 	"""
    258 	for p in rst_progs:
    259 		self.find_program(p, mandatory=False)
    260