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