1#! /usr/bin/env python
2# encoding: utf-8
3# WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
4
5import os,re
6from waflib import Utils,Task,Errors,Logs,Node
7from waflib.TaskGen import feature,before_method
8re_bibunit=re.compile(r'\\(?P<type>putbib)\[(?P<file>[^\[\]]*)\]',re.M)
9def bibunitscan(self):
10	node=self.inputs[0]
11	nodes=[]
12	if not node:return nodes
13	code=node.read()
14	for match in re_bibunit.finditer(code):
15		path=match.group('file')
16		if path:
17			for k in('','.bib'):
18				Logs.debug('tex: trying %s%s',path,k)
19				fi=node.parent.find_resource(path+k)
20				if fi:
21					nodes.append(fi)
22			else:
23				Logs.debug('tex: could not find %s',path)
24	Logs.debug('tex: found the following bibunit files: %s',nodes)
25	return nodes
26exts_deps_tex=['','.ltx','.tex','.bib','.pdf','.png','.eps','.ps','.sty']
27exts_tex=['.ltx','.tex']
28re_tex=re.compile(r'\\(?P<type>usepackage|RequirePackage|include|bibliography([^\[\]{}]*)|putbib|includegraphics|input|import|bringin|lstinputlisting)(\[[^\[\]]*\])?{(?P<file>[^{}]*)}',re.M)
29g_bibtex_re=re.compile('bibdata',re.M)
30g_glossaries_re=re.compile('\\@newglossary',re.M)
31class tex(Task.Task):
32	bibtex_fun,_=Task.compile_fun('${BIBTEX} ${BIBTEXFLAGS} ${SRCFILE}',shell=False)
33	bibtex_fun.__doc__="""
34	Execute the program **bibtex**
35	"""
36	makeindex_fun,_=Task.compile_fun('${MAKEINDEX} ${MAKEINDEXFLAGS} ${SRCFILE}',shell=False)
37	makeindex_fun.__doc__="""
38	Execute the program **makeindex**
39	"""
40	makeglossaries_fun,_=Task.compile_fun('${MAKEGLOSSARIES} ${SRCFILE}',shell=False)
41	makeglossaries_fun.__doc__="""
42	Execute the program **makeglossaries**
43	"""
44	def exec_command(self,cmd,**kw):
45		kw['stdout']=kw['stderr']=None
46		return super(tex,self).exec_command(cmd,**kw)
47	def scan_aux(self,node):
48		nodes=[node]
49		re_aux=re.compile(r'\\@input{(?P<file>[^{}]*)}',re.M)
50		def parse_node(node):
51			code=node.read()
52			for match in re_aux.finditer(code):
53				path=match.group('file')
54				found=node.parent.find_or_declare(path)
55				if found and found not in nodes:
56					Logs.debug('tex: found aux node %r',found)
57					nodes.append(found)
58					parse_node(found)
59		parse_node(node)
60		return nodes
61	def scan(self):
62		node=self.inputs[0]
63		nodes=[]
64		names=[]
65		seen=[]
66		if not node:return(nodes,names)
67		def parse_node(node):
68			if node in seen:
69				return
70			seen.append(node)
71			code=node.read()
72			global re_tex
73			for match in re_tex.finditer(code):
74				multibib=match.group('type')
75				if multibib and multibib.startswith('bibliography'):
76					multibib=multibib[len('bibliography'):]
77					if multibib.startswith('style'):
78						continue
79				else:
80					multibib=None
81				for path in match.group('file').split(','):
82					if path:
83						add_name=True
84						found=None
85						for k in exts_deps_tex:
86							for up in self.texinputs_nodes:
87								Logs.debug('tex: trying %s%s',path,k)
88								found=up.find_resource(path+k)
89								if found:
90									break
91							for tsk in self.generator.tasks:
92								if not found or found in tsk.outputs:
93									break
94							else:
95								nodes.append(found)
96								add_name=False
97								for ext in exts_tex:
98									if found.name.endswith(ext):
99										parse_node(found)
100										break
101							if found and multibib and found.name.endswith('.bib'):
102								try:
103									self.multibibs.append(found)
104								except AttributeError:
105									self.multibibs=[found]
106						if add_name:
107							names.append(path)
108		parse_node(node)
109		for x in nodes:
110			x.parent.get_bld().mkdir()
111		Logs.debug("tex: found the following : %s and names %s",nodes,names)
112		return(nodes,names)
113	def check_status(self,msg,retcode):
114		if retcode!=0:
115			raise Errors.WafError('%r command exit status %r'%(msg,retcode))
116	def bibfile(self):
117		for aux_node in self.aux_nodes:
118			try:
119				ct=aux_node.read()
120			except EnvironmentError:
121				Logs.error('Error reading %s: %r',aux_node.abspath())
122				continue
123			if g_bibtex_re.findall(ct):
124				Logs.info('calling bibtex')
125				self.env.env={}
126				self.env.env.update(os.environ)
127				self.env.env.update({'BIBINPUTS':self.texinputs(),'BSTINPUTS':self.texinputs()})
128				self.env.SRCFILE=aux_node.name[:-4]
129				self.check_status('error when calling bibtex',self.bibtex_fun())
130		for node in getattr(self,'multibibs',[]):
131			self.env.env={}
132			self.env.env.update(os.environ)
133			self.env.env.update({'BIBINPUTS':self.texinputs(),'BSTINPUTS':self.texinputs()})
134			self.env.SRCFILE=node.name[:-4]
135			self.check_status('error when calling bibtex',self.bibtex_fun())
136	def bibunits(self):
137		try:
138			bibunits=bibunitscan(self)
139		except OSError:
140			Logs.error('error bibunitscan')
141		else:
142			if bibunits:
143				fn=['bu'+str(i)for i in range(1,len(bibunits)+1)]
144				if fn:
145					Logs.info('calling bibtex on bibunits')
146				for f in fn:
147					self.env.env={'BIBINPUTS':self.texinputs(),'BSTINPUTS':self.texinputs()}
148					self.env.SRCFILE=f
149					self.check_status('error when calling bibtex',self.bibtex_fun())
150	def makeindex(self):
151		self.idx_node=self.inputs[0].change_ext('.idx')
152		try:
153			idx_path=self.idx_node.abspath()
154			os.stat(idx_path)
155		except OSError:
156			Logs.info('index file %s absent, not calling makeindex',idx_path)
157		else:
158			Logs.info('calling makeindex')
159			self.env.SRCFILE=self.idx_node.name
160			self.env.env={}
161			self.check_status('error when calling makeindex %s'%idx_path,self.makeindex_fun())
162	def bibtopic(self):
163		p=self.inputs[0].parent.get_bld()
164		if os.path.exists(os.path.join(p.abspath(),'btaux.aux')):
165			self.aux_nodes+=p.ant_glob('*[0-9].aux')
166	def makeglossaries(self):
167		src_file=self.inputs[0].abspath()
168		base_file=os.path.basename(src_file)
169		base,_=os.path.splitext(base_file)
170		for aux_node in self.aux_nodes:
171			try:
172				ct=aux_node.read()
173			except EnvironmentError:
174				Logs.error('Error reading %s: %r',aux_node.abspath())
175				continue
176			if g_glossaries_re.findall(ct):
177				if not self.env.MAKEGLOSSARIES:
178					raise Errors.WafError("The program 'makeglossaries' is missing!")
179				Logs.warn('calling makeglossaries')
180				self.env.SRCFILE=base
181				self.check_status('error when calling makeglossaries %s'%base,self.makeglossaries_fun())
182				return
183	def texinputs(self):
184		return os.pathsep.join([k.abspath()for k in self.texinputs_nodes])+os.pathsep
185	def run(self):
186		env=self.env
187		if not env.PROMPT_LATEX:
188			env.append_value('LATEXFLAGS','-interaction=batchmode')
189			env.append_value('PDFLATEXFLAGS','-interaction=batchmode')
190			env.append_value('XELATEXFLAGS','-interaction=batchmode')
191		self.cwd=self.inputs[0].parent.get_bld()
192		Logs.info('first pass on %s',self.__class__.__name__)
193		cur_hash=self.hash_aux_nodes()
194		self.call_latex()
195		self.hash_aux_nodes()
196		self.bibtopic()
197		self.bibfile()
198		self.bibunits()
199		self.makeindex()
200		self.makeglossaries()
201		for i in range(10):
202			prev_hash=cur_hash
203			cur_hash=self.hash_aux_nodes()
204			if not cur_hash:
205				Logs.error('No aux.h to process')
206			if cur_hash and cur_hash==prev_hash:
207				break
208			Logs.info('calling %s',self.__class__.__name__)
209			self.call_latex()
210	def hash_aux_nodes(self):
211		try:
212			self.aux_nodes
213		except AttributeError:
214			try:
215				self.aux_nodes=self.scan_aux(self.inputs[0].change_ext('.aux'))
216			except IOError:
217				return None
218		return Utils.h_list([Utils.h_file(x.abspath())for x in self.aux_nodes])
219	def call_latex(self):
220		self.env.env={}
221		self.env.env.update(os.environ)
222		self.env.env.update({'TEXINPUTS':self.texinputs()})
223		self.env.SRCFILE=self.inputs[0].abspath()
224		self.check_status('error when calling latex',self.texfun())
225class latex(tex):
226	texfun,vars=Task.compile_fun('${LATEX} ${LATEXFLAGS} ${SRCFILE}',shell=False)
227class pdflatex(tex):
228	texfun,vars=Task.compile_fun('${PDFLATEX} ${PDFLATEXFLAGS} ${SRCFILE}',shell=False)
229class xelatex(tex):
230	texfun,vars=Task.compile_fun('${XELATEX} ${XELATEXFLAGS} ${SRCFILE}',shell=False)
231class dvips(Task.Task):
232	run_str='${DVIPS} ${DVIPSFLAGS} ${SRC} -o ${TGT}'
233	color='BLUE'
234	after=['latex','pdflatex','xelatex']
235class dvipdf(Task.Task):
236	run_str='${DVIPDF} ${DVIPDFFLAGS} ${SRC} ${TGT}'
237	color='BLUE'
238	after=['latex','pdflatex','xelatex']
239class pdf2ps(Task.Task):
240	run_str='${PDF2PS} ${PDF2PSFLAGS} ${SRC} ${TGT}'
241	color='BLUE'
242	after=['latex','pdflatex','xelatex']
243@feature('tex')
244@before_method('process_source')
245def apply_tex(self):
246	if not getattr(self,'type',None)in('latex','pdflatex','xelatex'):
247		self.type='pdflatex'
248	outs=Utils.to_list(getattr(self,'outs',[]))
249	self.env.PROMPT_LATEX=getattr(self,'prompt',1)
250	deps_lst=[]
251	if getattr(self,'deps',None):
252		deps=self.to_list(self.deps)
253		for dep in deps:
254			if isinstance(dep,str):
255				n=self.path.find_resource(dep)
256				if not n:
257					self.bld.fatal('Could not find %r for %r'%(dep,self))
258				if not n in deps_lst:
259					deps_lst.append(n)
260			elif isinstance(dep,Node.Node):
261				deps_lst.append(dep)
262	for node in self.to_nodes(self.source):
263		if self.type=='latex':
264			task=self.create_task('latex',node,node.change_ext('.dvi'))
265		elif self.type=='pdflatex':
266			task=self.create_task('pdflatex',node,node.change_ext('.pdf'))
267		elif self.type=='xelatex':
268			task=self.create_task('xelatex',node,node.change_ext('.pdf'))
269		task.env=self.env
270		if deps_lst:
271			for n in deps_lst:
272				if not n in task.dep_nodes:
273					task.dep_nodes.append(n)
274		if hasattr(self,'texinputs_nodes'):
275			task.texinputs_nodes=self.texinputs_nodes
276		else:
277			task.texinputs_nodes=[node.parent,node.parent.get_bld(),self.path,self.path.get_bld()]
278			lst=os.environ.get('TEXINPUTS','')
279			if self.env.TEXINPUTS:
280				lst+=os.pathsep+self.env.TEXINPUTS
281			if lst:
282				lst=lst.split(os.pathsep)
283			for x in lst:
284				if x:
285					if os.path.isabs(x):
286						p=self.bld.root.find_node(x)
287						if p:
288							task.texinputs_nodes.append(p)
289						else:
290							Logs.error('Invalid TEXINPUTS folder %s',x)
291					else:
292						Logs.error('Cannot resolve relative paths in TEXINPUTS %s',x)
293		if self.type=='latex':
294			if'ps'in outs:
295				tsk=self.create_task('dvips',task.outputs,node.change_ext('.ps'))
296				tsk.env.env=dict(os.environ)
297			if'pdf'in outs:
298				tsk=self.create_task('dvipdf',task.outputs,node.change_ext('.pdf'))
299				tsk.env.env=dict(os.environ)
300		elif self.type=='pdflatex':
301			if'ps'in outs:
302				self.create_task('pdf2ps',task.outputs,node.change_ext('.ps'))
303	self.source=[]
304def configure(self):
305	v=self.env
306	for p in'tex latex pdflatex xelatex bibtex dvips dvipdf ps2pdf makeindex pdf2ps makeglossaries'.split():
307		try:
308			self.find_program(p,var=p.upper())
309		except self.errors.ConfigurationError:
310			pass
311	v.DVIPSFLAGS='-Ppdf'
312