1#!/usr/bin/env python
2# encoding: utf-8
3# Thomas Nagy, 2006-2018 (ita)
4
5"""
6Support for translation tools such as msgfmt and intltool
7
8Usage::
9
10	def configure(conf):
11		conf.load('gnu_dirs intltool')
12
13	def build(bld):
14		# process the .po files into .gmo files, and install them in LOCALEDIR
15		bld(features='intltool_po', appname='myapp', podir='po', install_path="${LOCALEDIR}")
16
17		# process an input file, substituting the translations from the po dir
18		bld(
19			features  = "intltool_in",
20			podir     = "../po",
21			style     = "desktop",
22			flags     = ["-u"],
23			source    = 'kupfer.desktop.in',
24			install_path = "${DATADIR}/applications",
25		)
26
27Usage of the :py:mod:`waflib.Tools.gnu_dirs` is recommended, but not obligatory.
28"""
29
30from __future__ import with_statement
31
32import os, re
33from waflib import Context, Task, Utils, Logs
34import waflib.Tools.ccroot
35from waflib.TaskGen import feature, before_method, taskgen_method
36from waflib.Logs import error
37from waflib.Configure import conf
38
39_style_flags = {
40	'ba': '-b',
41	'desktop': '-d',
42	'keys': '-k',
43	'quoted': '--quoted-style',
44	'quotedxml': '--quotedxml-style',
45	'rfc822deb': '-r',
46	'schemas': '-s',
47	'xml': '-x',
48}
49
50@taskgen_method
51def ensure_localedir(self):
52	"""
53	Expands LOCALEDIR from DATAROOTDIR/locale if possible, or falls back to PREFIX/share/locale
54	"""
55	# use the tool gnu_dirs to provide options to define this
56	if not self.env.LOCALEDIR:
57		if self.env.DATAROOTDIR:
58			self.env.LOCALEDIR = os.path.join(self.env.DATAROOTDIR, 'locale')
59		else:
60			self.env.LOCALEDIR = os.path.join(self.env.PREFIX, 'share', 'locale')
61
62@before_method('process_source')
63@feature('intltool_in')
64def apply_intltool_in_f(self):
65	"""
66	Creates tasks to translate files by intltool-merge::
67
68		def build(bld):
69			bld(
70				features  = "intltool_in",
71				podir     = "../po",
72				style     = "desktop",
73				flags     = ["-u"],
74				source    = 'kupfer.desktop.in',
75				install_path = "${DATADIR}/applications",
76			)
77
78	:param podir: location of the .po files
79	:type podir: string
80	:param source: source files to process
81	:type source: list of string
82	:param style: the intltool-merge mode of operation, can be one of the following values:
83	  ``ba``, ``desktop``, ``keys``, ``quoted``, ``quotedxml``, ``rfc822deb``, ``schemas`` and ``xml``.
84	  See the ``intltool-merge`` man page for more information about supported modes of operation.
85	:type style: string
86	:param flags: compilation flags ("-quc" by default)
87	:type flags: list of string
88	:param install_path: installation path
89	:type install_path: string
90	"""
91	try:
92		self.meths.remove('process_source')
93	except ValueError:
94		pass
95
96	self.ensure_localedir()
97
98	podir = getattr(self, 'podir', '.')
99	podirnode = self.path.find_dir(podir)
100	if not podirnode:
101		error("could not find the podir %r" % podir)
102		return
103
104	cache = getattr(self, 'intlcache', '.intlcache')
105	self.env.INTLCACHE = [os.path.join(str(self.path.get_bld()), podir, cache)]
106	self.env.INTLPODIR = podirnode.bldpath()
107	self.env.append_value('INTLFLAGS', getattr(self, 'flags', self.env.INTLFLAGS_DEFAULT))
108
109	if '-c' in self.env.INTLFLAGS:
110		self.bld.fatal('Redundant -c flag in intltool task %r' % self)
111
112	style = getattr(self, 'style', None)
113	if style:
114		try:
115			style_flag = _style_flags[style]
116		except KeyError:
117			self.bld.fatal('intltool_in style "%s" is not valid' % style)
118
119		self.env.append_unique('INTLFLAGS', [style_flag])
120
121	for i in self.to_list(self.source):
122		node = self.path.find_resource(i)
123
124		task = self.create_task('intltool', node, node.change_ext(''))
125		inst = getattr(self, 'install_path', None)
126		if inst:
127			self.add_install_files(install_to=inst, install_from=task.outputs)
128
129@feature('intltool_po')
130def apply_intltool_po(self):
131	"""
132	Creates tasks to process po files::
133
134		def build(bld):
135			bld(features='intltool_po', appname='myapp', podir='po', install_path="${LOCALEDIR}")
136
137	The relevant task generator arguments are:
138
139	:param podir: directory of the .po files
140	:type podir: string
141	:param appname: name of the application
142	:type appname: string
143	:param install_path: installation directory
144	:type install_path: string
145
146	The file LINGUAS must be present in the directory pointed by *podir* and list the translation files to process.
147	"""
148	try:
149		self.meths.remove('process_source')
150	except ValueError:
151		pass
152
153	self.ensure_localedir()
154
155	appname = getattr(self, 'appname', getattr(Context.g_module, Context.APPNAME, 'set_your_app_name'))
156	podir = getattr(self, 'podir', '.')
157	inst = getattr(self, 'install_path', '${LOCALEDIR}')
158
159	linguas = self.path.find_node(os.path.join(podir, 'LINGUAS'))
160	if linguas:
161		# scan LINGUAS file for locales to process
162		with open(linguas.abspath()) as f:
163			langs = []
164			for line in f.readlines():
165				# ignore lines containing comments
166				if not line.startswith('#'):
167					langs += line.split()
168		re_linguas = re.compile('[-a-zA-Z_@.]+')
169		for lang in langs:
170			# Make sure that we only process lines which contain locales
171			if re_linguas.match(lang):
172				node = self.path.find_resource(os.path.join(podir, re_linguas.match(lang).group() + '.po'))
173				task = self.create_task('po', node, node.change_ext('.mo'))
174
175				if inst:
176					filename = task.outputs[0].name
177					(langname, ext) = os.path.splitext(filename)
178					inst_file = inst + os.sep + langname + os.sep + 'LC_MESSAGES' + os.sep + appname + '.mo'
179					self.add_install_as(install_to=inst_file, install_from=task.outputs[0],
180						chmod=getattr(self, 'chmod', Utils.O644))
181
182	else:
183		Logs.pprint('RED', "Error no LINGUAS file found in po directory")
184
185class po(Task.Task):
186	"""
187	Compiles .po files into .gmo files
188	"""
189	run_str = '${MSGFMT} -o ${TGT} ${SRC}'
190	color   = 'BLUE'
191
192class intltool(Task.Task):
193	"""
194	Calls intltool-merge to update translation files
195	"""
196	run_str = '${INTLTOOL} ${INTLFLAGS} ${INTLCACHE_ST:INTLCACHE} ${INTLPODIR} ${SRC} ${TGT}'
197	color   = 'BLUE'
198
199@conf
200def find_msgfmt(conf):
201	"""
202	Detects msgfmt and sets the ``MSGFMT`` variable
203	"""
204	conf.find_program('msgfmt', var='MSGFMT')
205
206@conf
207def find_intltool_merge(conf):
208	"""
209	Detects intltool-merge
210	"""
211	if not conf.env.PERL:
212		conf.find_program('perl', var='PERL')
213	conf.env.INTLCACHE_ST = '--cache=%s'
214	conf.env.INTLFLAGS_DEFAULT = ['-q', '-u']
215	conf.find_program('intltool-merge', interpreter='PERL', var='INTLTOOL')
216
217def configure(conf):
218	"""
219	Detects the program *msgfmt* and set *conf.env.MSGFMT*.
220	Detects the program *intltool-merge* and set *conf.env.INTLTOOL*.
221	It is possible to set INTLTOOL in the environment, but it must not have spaces in it::
222
223		$ INTLTOOL="/path/to/the program/intltool" waf configure
224
225	If a C/C++ compiler is present, execute a compilation test to find the header *locale.h*.
226	"""
227	conf.find_msgfmt()
228	conf.find_intltool_merge()
229	if conf.env.CC or conf.env.CXX:
230		conf.check(header_name='locale.h')
231
232