Merge lp:~henninge/launchpad/intltool-detection into lp:launchpad

Proposed by Henning Eggers
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~henninge/launchpad/intltool-detection
Merge into: lp:launchpad
Diff against target: 496 lines (+443/-0)
4 files modified
lib/devscripts/ec2test/account.py (+1/-0)
lib/lp/translations/pottery/detect_intltool.py (+215/-0)
lib/lp/translations/tests/test_pottery_detect_intltool.py (+209/-0)
scripts/rosetta/pottery-check-intltool.py (+18/-0)
To merge this branch: bzr merge lp:~henninge/launchpad/intltool-detection
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+16533@code.launchpad.net

Commit message

Added new code to detect po directories in intltool-based source packages and to determine the translation domain from build files.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :

= Details =

This branch provides a bunch of functions that are able to detect an intltool setup in a package directory. The top level function is "find_intltool_dirs" which returns a list of directories that have been detected to contain all files needed to generate a template file using intltool.

Another top-level function is "get_translation_domain" which extracts the translation domain for a given intltool (gettext) directory. It searches various files and performs variable substitution as necessary.

The script "scripts/rosetta/pottery-check-intltool.py" finds intltool directories in the current directory and displays their names along with their translation domain.

The code is somewhat limited as there are many different setups of source pacakges that use intltool and not all special cases are accounted for. But it will handle the most common situtations nicely.

The code is placed in a module called "pottery" because this is all about creating POT files.

== Implementation details ==

The function "run_shell_command" is taken from the Damned Lies source code.

== Tests ==

The nature of these functions requires that different directory structures are created to run the test on. These are packaged in a group of archives called intltool_*.tar.bz2 in the lib/lp/translations/test directory. The test unpacks and deletes them as required.

bin/test -vvct pottery

== Lint ==

No relevant lint.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (19.0 KiB)

"Pottery"? Nice name!

Some comments on the diff:

=== added file 'lib/lp/translations/pottery/detect_intltool.py'
--- lib/lp/translations/pottery/detect_intltool.py 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/pottery/detect_intltool.py 2009-12-23 11:58:18 +0000

+import errno
+import os.path
+import re
+from subprocess import Popen, PIPE
+
+POTFILES_in = "POTFILES.in"
+STATUS_OK = 0
+
+def run_shell_command(cmd, env=None, input_data=None, raise_on_error=False):

Double blank line above & below that little variable section please.

Also, POTFILES_in looks a bit weird and is only used in one place... I'd use a literal string for now and leave this kind of thing for later.

+ """Run a shell command and return the output and status.
+
+ Copied from Damned Lies source code.
+ """

It does a lot of things you don't need though. AFAICS what you need here is about 4 lines of code, not a lot of functionality that's not being tested in our codebase. (It may be tested somewhere else, but that won't cause any alarm bells to go off on our end when it needs updating).

+ stdin = None
+ if input_data:
+ stdin = PIPE
+ if env:
+ os.environ.update(env)
+ env = os.environ
+ pipe = Popen(
+ cmd, shell=True, env=env, stdin=stdin, stdout=PIPE, stderr=PIPE)
+ if input_data:
+ try:
+ pipe.stdin.write(input_data)
+ except IOError, e:
+ if e.errno != errno.EPIPE:
+ raise
+ (output, errout) = pipe.communicate()
+ status = pipe.returncode
+ if raise_on_error and status != STATUS_OK:
+ raise OSError(status, errout)
+
+ return (status, output, errout)
+
+
+def find_potfiles_in():
+ """ Search the current directory and its subdirectories for POTFILES.in.

Extra space at beginning of docstring.

+
+ :returns: A list of names of directories that contain a file POTFILES.in.
+ """
+ result_dirs = []
+ for dirpath, dirnames, dirfiles in os.walk("."):
+ if POTFILES_in in dirfiles:
+ result_dirs.append(dirpath)
+ return result_dirs
+
+
+def check_potfiles_in(path):
+ """Check if the files listed in the POTFILES.in file exist."""
+ command = ("cd \"%(dir)s\" && rm -f missing notexist && "
+ "intltool-update -m" % { "dir" : path, })

This is a bit hard to read. Also, %(dir)s probably ought to be escaped.

(On a sidenote, when you execute commands like "cd %s" on a GNU system, it can be a good habit to use the "--" option to indicate that there are no command-line options after that point. Just in case you run across a "%s" that starts with a dash for whatever reason.)

+ (status, output, errs) = run_shell_command(command)
+
+ if status != 0:
+ return False

You're spelling STATUS_OK a bit inconsistently. :-)

+
+ notexist = os.path.join(path, "notexist")
+ return not os.access(notexist, os.R_OK)
+
+
+def find_intltool_dirs():
+ """Search the current directory and its subdiretories for intltool
+ structure.
+ """
+ return sorted(filter(check_potfiles_in, find_potfiles_in()))

Nice & compact.

+def get_translation_domain(dirname):
+ """Determine the transl...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Nice test suite for get_translation_domain by the way!

Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (14.5 KiB)

Good Morning Jeroen!

Thank you for staying up so late to do this review. Please find my
comments below.

Am 23.12.2009 17:18, Jeroen T. Vermeulen schrieb:
> "Pottery"? Nice name!

Yeah, Danilo and I were throwing ideas at another about where to put
this code. Eventually I said "I might even call it pottery" and he said
"I like that!" ;-)

>
> Some comments on the diff:
>
> === added file 'lib/lp/translations/pottery/detect_intltool.py'
> --- lib/lp/translations/pottery/detect_intltool.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/translations/pottery/detect_intltool.py 2009-12-23 11:58:18 +0000
>
> +import errno
> +import os.path
> +import re
> +from subprocess import Popen, PIPE
> +
> +POTFILES_in = "POTFILES.in"
> +STATUS_OK = 0
> +
> +def run_shell_command(cmd, env=None, input_data=None, raise_on_error=False):
>
> Double blank line above & below that little variable section please.
>
> Also, POTFILES_in looks a bit weird and is only used in one place... I'd use a literal string for now and leave this kind of thing for later.

At first I thought I'd need more of these but turned out I didn't. This
section is gone now.

>
>
> + """Run a shell command and return the output and status.
> +
> + Copied from Damned Lies source code.
> + """
>
> It does a lot of things you don't need though. AFAICS what you need here is about 4 lines of code, not a lot of functionality that's not being tested in our codebase. (It may be tested somewhere else, but that won't cause any alarm bells to go off on our end when it needs updating).

It's gone now as your already saw.

[...]

> +
> +def find_potfiles_in():
> + """ Search the current directory and its subdirectories for POTFILES.in.
>
> Extra space at beginning of docstring.

Gone.

>
>
> +
> + :returns: A list of names of directories that contain a file POTFILES.in.
> + """
> + result_dirs = []
> + for dirpath, dirnames, dirfiles in os.walk("."):
> + if POTFILES_in in dirfiles:
> + result_dirs.append(dirpath)
> + return result_dirs
> +
> +
> +def check_potfiles_in(path):
> + """Check if the files listed in the POTFILES.in file exist."""
> + command = ("cd \"%(dir)s\" && rm -f missing notexist && "
> + "intltool-update -m" % { "dir" : path, })
>
> This is a bit hard to read. Also, %(dir)s probably ought to be escaped.
>
> (On a sidenote, when you execute commands like "cd %s" on a GNU system, it can be a good habit to use the "--" option to indicate that there are no command-line options after that point. Just in case you run across a "%s" that starts with a dash for whatever reason.)

This has changed completely as you saw in the diff you admired ... ;)

[...]

> +
> +def find_intltool_dirs():
> + """Search the current directory and its subdiretories for intltool
> + structure.
> + """
> + return sorted(filter(check_potfiles_in, find_potfiles_in()))
>
> Nice & compact.

Yes, that's one of the features I like about Python.

>
>
> +def get_translation_domain(dirname):
> + """Determine the translation domain by parsing various files.
>
> I find this very unclear. It says too much about _how_ it does its job, ...

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Thanks for the changes.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/devscripts/ec2test/account.py'
2--- lib/devscripts/ec2test/account.py 2009-10-08 14:50:19 +0000
3+++ lib/devscripts/ec2test/account.py 2010-02-01 09:53:14 +0000
4@@ -24,6 +24,7 @@
5 255383312499, # gary
6 559320013529, # flacoste
7 200337130613, # mwhudson
8+ 889698597288, # henninge
9 # ...anyone else want in on the fun?
10 )
11
12
13=== added directory 'lib/lp/translations/pottery'
14=== added file 'lib/lp/translations/pottery/__init__.py'
15=== added file 'lib/lp/translations/pottery/detect_intltool.py'
16--- lib/lp/translations/pottery/detect_intltool.py 1970-01-01 00:00:00 +0000
17+++ lib/lp/translations/pottery/detect_intltool.py 2010-02-01 09:53:14 +0000
18@@ -0,0 +1,215 @@
19+#! /usr/bin/python2.5
20+#
21+# Copyright 2009 Canonical Ltd. This software is licensed under the
22+# GNU Affero General Public License version 3 (see the file LICENSE).
23+
24+"""Functions to detect if intltool can be used to generate a POT file for the
25+package in the current directory."""
26+
27+__metaclass__ = type
28+__all__ = [
29+ 'check_potfiles_in',
30+ 'get_translation_domain',
31+ 'find_intltool_dirs',
32+ 'find_potfiles_in',
33+ ]
34+
35+import errno
36+import os.path
37+import re
38+from subprocess import call
39+
40+
41+def find_potfiles_in():
42+ """Search the current directory and its subdirectories for POTFILES.in.
43+
44+ :returns: A list of names of directories that contain a file POTFILES.in.
45+ """
46+ result_dirs = []
47+ for dirpath, dirnames, dirfiles in os.walk("."):
48+ if "POTFILES.in" in dirfiles:
49+ result_dirs.append(dirpath)
50+ return result_dirs
51+
52+
53+def check_potfiles_in(path):
54+ """Check if the files listed in the POTFILES.in file exist."""
55+ current_path = os.getcwd()
56+
57+ try:
58+ os.chdir(path)
59+ except OSError, e:
60+ # Abort nicely if directory does not exist.
61+ if e.errno == errno.ENOENT:
62+ return False
63+ raise
64+ try:
65+ for unlink_name in ['missing', 'notexist']:
66+ try:
67+ os.unlink(unlink_name)
68+ except OSError, e:
69+ # It's ok if the files are missing.
70+ if e.errno != errno.ENOENT:
71+ raise
72+ devnull = open("/dev/null", "w")
73+ returncode = call(
74+ ["/usr/bin/intltool-update", "-m"],
75+ stdout=devnull, stderr=devnull)
76+ devnull.close()
77+ finally:
78+ os.chdir(current_path)
79+
80+ if returncode != 0:
81+ return False
82+
83+ notexist = os.path.join(path, "notexist")
84+ return not os.access(notexist, os.R_OK)
85+
86+
87+def find_intltool_dirs():
88+ """Search the current directory and its subdiretories for intltool
89+ structure.
90+ """
91+ return sorted(filter(check_potfiles_in, find_potfiles_in()))
92+
93+
94+def _try_substitution(path, substitution):
95+ """Try to find a substitution in the given config file.
96+
97+ :returns: The completed substitution or None if none was found.
98+ """
99+ subst_value = ConfigFile(path).getVariable(substitution.name)
100+ if subst_value is None:
101+ # No substitution found.
102+ return None
103+ return substitution.replace(subst_value)
104+
105+
106+def get_translation_domain(dirname):
107+ """Get the translation domain for this PO directory.
108+
109+ Imitates some of the behavior of intltool-update to find out which
110+ translation domain the build environment provides. The domain is usually
111+ defined in the GETTEXT_PACKAGE variable in one of the build files. Another
112+ variant is DOMAIN in the Makevars file. This function goes through the
113+ ordered list of these possible locations, the order having been copied
114+ from intltool-update, and tries to find a valid value.
115+
116+ If the found value contains a substitution, either autoconf style (@...@)
117+ or make style ($(...)), the search is continued in the same file and down
118+ the list of files, now searching for the substitution. Multiple
119+ substitutions or multi-level substitutions are not supported.
120+ """
121+ locations = [
122+ ('Makefile.in.in', 'GETTEXT_PACKAGE'),
123+ ('../configure.ac', 'GETTEXT_PACKAGE'),
124+ ('../configure.in', 'GETTEXT_PACKAGE'),
125+ ('Makevars', 'DOMAIN'),
126+ ]
127+ value = None
128+ substitution = None
129+ for filename, varname in locations:
130+ path = os.path.join(dirname, filename)
131+ if not os.access(path, os.R_OK):
132+ # Skip non-existent files.
133+ continue
134+ if substitution is None:
135+ value = ConfigFile(path).getVariable(varname)
136+ if value is not None:
137+ # Check if the value need a substitution.
138+ substitution = Substitution.get(value)
139+ if substitution is not None:
140+ # Try to substitute with value from current file but
141+ # avoid recursion.
142+ if substitution.name != varname:
143+ value = _try_substitution(path, substitution)
144+ else:
145+ # The value has not been found yet but is now stored
146+ # in the Substitution instance.
147+ value = None
148+ else:
149+ value = _try_substitution(path, substitution)
150+ if value is not None:
151+ # A value has been found.
152+ break
153+ if substitution is not None and not substitution.replaced:
154+ # Substitution failed.
155+ return None
156+ return value
157+
158+
159+class ConfigFile(object):
160+ """Represent a config file and return variables defined in it."""
161+
162+ def __init__(self, file_or_name):
163+ if isinstance(file_or_name, basestring):
164+ conf_file = file(file_or_name)
165+ else:
166+ conf_file = file_or_name
167+ self.content_lines = conf_file.readlines()
168+
169+ def getVariable(self, name):
170+ """Search the file for a variable definition with this name."""
171+ pattern = re.compile("^%s[ \t]*=[ \t]*([^\s]*)" % re.escape(name))
172+ variable = None
173+ for line in self.content_lines:
174+ result = pattern.match(line)
175+ if result is not None:
176+ variable = result.group(1)
177+ return variable
178+
179+
180+class Substitution(object):
181+ """Find and replace substitutions.
182+
183+ Variable texts may contain other variables which should be substituted
184+ for their value. These are either marked by surrounding @ signs (autoconf
185+ style) or preceded by a $ sign with optional () (make style).
186+
187+ This class identifies a single such substitution in a variable text and
188+ extract the name of the variable who's value is to be inserted. It also
189+ facilitates the actual replacement so that caller does not have to worry
190+ about the substitution style that is being used.
191+ """
192+
193+ autoconf_pattern = re.compile("@([^@]+)@")
194+ makefile_pattern = re.compile("\$\(?([^\s\)]+)\)?")
195+
196+ @staticmethod
197+ def get(variabletext):
198+ """Factory method.
199+
200+ Creates a Substitution instance and checks if it found a substitution.
201+
202+ :param variabletext: A variable value with possible substitution.
203+ :returns: A Substitution object or None if no substitution was found.
204+ """
205+ subst = Substitution(variabletext)
206+ if subst.name is not None:
207+ return subst
208+ return None
209+
210+ def _searchForPatterns(self):
211+ """Search for all the available patterns in variable text."""
212+ result = self.autoconf_pattern.search(self.text)
213+ if result is None:
214+ result = self.makefile_pattern.search(self.text)
215+ return result
216+
217+ def __init__(self, variabletext):
218+ """Extract substitution name from variable text."""
219+ self.text = variabletext
220+ self.replaced = False
221+ result = self._searchForPatterns()
222+ if result is None:
223+ self._replacement = None
224+ self.name = None
225+ else:
226+ self._replacement = result.group(0)
227+ self.name = result.group(1)
228+
229+ def replace(self, value):
230+ """Return a copy of the variable text with the substitution resolved.
231+ """
232+ self.replaced = True
233+ return self.text.replace(self._replacement, value)
234
235=== added file 'lib/lp/translations/tests/intltool_POTFILES_in_1.tar.bz2'
236Binary files lib/lp/translations/tests/intltool_POTFILES_in_1.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_POTFILES_in_1.tar.bz2 2010-02-01 09:53:14 +0000 differ
237=== added file 'lib/lp/translations/tests/intltool_POTFILES_in_2.tar.bz2'
238Binary files lib/lp/translations/tests/intltool_POTFILES_in_2.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_POTFILES_in_2.tar.bz2 2010-02-01 09:53:14 +0000 differ
239=== added file 'lib/lp/translations/tests/intltool_domain_configure_ac.tar.bz2'
240Binary files lib/lp/translations/tests/intltool_domain_configure_ac.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_configure_ac.tar.bz2 2010-02-01 09:53:14 +0000 differ
241=== added file 'lib/lp/translations/tests/intltool_domain_configure_in.tar.bz2'
242Binary files lib/lp/translations/tests/intltool_domain_configure_in.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_configure_in.tar.bz2 2010-02-01 09:53:14 +0000 differ
243=== added file 'lib/lp/translations/tests/intltool_domain_configure_in_substitute_version.tar.bz2'
244Binary files lib/lp/translations/tests/intltool_domain_configure_in_substitute_version.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_configure_in_substitute_version.tar.bz2 2010-02-01 09:53:14 +0000 differ
245=== added file 'lib/lp/translations/tests/intltool_domain_makefile_in_in.tar.bz2'
246Binary files lib/lp/translations/tests/intltool_domain_makefile_in_in.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makefile_in_in.tar.bz2 2010-02-01 09:53:14 +0000 differ
247=== added file 'lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute.tar.bz2'
248Binary files lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute.tar.bz2 2010-02-01 09:53:14 +0000 differ
249=== added file 'lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_broken.tar.bz2'
250Binary files lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_broken.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_broken.tar.bz2 2010-02-01 09:53:14 +0000 differ
251=== added file 'lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2'
252Binary files lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_file.tar.bz2 2010-02-01 09:53:14 +0000 differ
253=== added file 'lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2'
254Binary files lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makefile_in_in_substitute_same_name.tar.bz2 2010-02-01 09:53:14 +0000 differ
255=== added file 'lib/lp/translations/tests/intltool_domain_makevars.tar.bz2'
256Binary files lib/lp/translations/tests/intltool_domain_makevars.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_domain_makevars.tar.bz2 2010-02-01 09:53:14 +0000 differ
257=== added file 'lib/lp/translations/tests/intltool_full_ok.tar.bz2'
258Binary files lib/lp/translations/tests/intltool_full_ok.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_full_ok.tar.bz2 2010-02-01 09:53:14 +0000 differ
259=== added file 'lib/lp/translations/tests/intltool_single_ok.tar.bz2'
260Binary files lib/lp/translations/tests/intltool_single_ok.tar.bz2 1970-01-01 00:00:00 +0000 and lib/lp/translations/tests/intltool_single_ok.tar.bz2 2010-02-01 09:53:14 +0000 differ
261=== added file 'lib/lp/translations/tests/test_pottery_detect_intltool.py'
262--- lib/lp/translations/tests/test_pottery_detect_intltool.py 1970-01-01 00:00:00 +0000
263+++ lib/lp/translations/tests/test_pottery_detect_intltool.py 2010-02-01 09:53:14 +0000
264@@ -0,0 +1,209 @@
265+# Copyright 2009 Canonical Ltd. This software is licensed under the
266+# GNU Affero General Public License version 3 (see the file LICENSE).
267+
268+import os
269+import shutil
270+import tarfile
271+import tempfile
272+import unittest
273+
274+from StringIO import StringIO
275+from textwrap import dedent
276+
277+from canonical.launchpad.scripts.tests import run_script
278+from lp.translations.pottery.detect_intltool import (
279+ ConfigFile, check_potfiles_in, find_intltool_dirs, find_potfiles_in,
280+ get_translation_domain)
281+from lp.testing import TestCase
282+
283+class TestDetectIntltool(TestCase):
284+
285+ def setUp(self):
286+ super(TestDetectIntltool, self).setUp()
287+ # Determine test directory and create temporary working directory.
288+ self.curdir = os.getcwd()
289+ self.testdir = os.path.join(self.curdir, os.path.dirname(__file__))
290+ self.workdir = tempfile.mkdtemp()
291+ os.chdir(self.workdir)
292+
293+ def tearDown(self):
294+ # Remove temporary directory.
295+ os.chdir(self.curdir)
296+ shutil.rmtree(self.workdir)
297+ super(TestDetectIntltool, self).tearDown()
298+
299+ def _prepare_package(self, packagename):
300+ # Unpack the specified pacakge to run the test against it.
301+ # Change to this directory.
302+ packagepath = os.path.join(self.testdir, packagename+".tar.bz2")
303+ tar = tarfile.open(packagepath, "r:bz2")
304+ tar.extractall()
305+ tar.close()
306+ os.chdir(packagename)
307+
308+ def test_detect_potfiles_in(self):
309+ # Find POTFILES.in in a package with multiple dirs when only one has
310+ # POTFILES.in.
311+ self._prepare_package("intltool_POTFILES_in_1")
312+ dirs = find_potfiles_in()
313+ self.assertContentEqual(["./po-intltool"], dirs)
314+
315+ def test_detect_potfiles_in_module(self):
316+ # Find POTFILES.in in a package with POTFILES.in at different levels.
317+ self._prepare_package("intltool_POTFILES_in_2")
318+ dirs = find_potfiles_in()
319+ self.assertContentEqual(["./po", "./module1/po"], dirs)
320+
321+ def test_check_potfiles_in_content_ok(self):
322+ # Ideally all files listed in POTFILES.in exist in the source package.
323+ self._prepare_package("intltool_single_ok")
324+ self.assertTrue(check_potfiles_in("./po"))
325+
326+ def test_check_potfiles_in_content_ok_file_added(self):
327+ # If a file is not listed in POTFILES.in, the file is still good for
328+ # our purposes.
329+ self._prepare_package("intltool_single_ok")
330+ added_file = file("./src/sourcefile_new.c", "w")
331+ added_file.write("/* Test file. */")
332+ added_file.close()
333+ self.assertTrue(check_potfiles_in("./po"))
334+
335+ def test_check_potfiles_in_content_not_ok_file_removed(self):
336+ # If a file is missing that is listed in POTFILES.in, the file
337+ # intltool structure is probably broken and cannot be used for
338+ # our purposes.
339+ self._prepare_package("intltool_single_ok")
340+ os.remove("./src/sourcefile1.c")
341+ self.assertFalse(check_potfiles_in("./po"))
342+
343+ def test_check_potfiles_in_wrong_directory(self):
344+ # Passing in the wrong directory will cause the check to fail
345+ # gracefully and return False.
346+ self._prepare_package("intltool_single_ok")
347+ self.assertFalse(check_potfiles_in("./foo"))
348+
349+ def test_find_intltool_dirs(self):
350+ # Complete run: find all directories with intltool structure.
351+ self._prepare_package("intltool_full_ok")
352+ self.assertEqual(
353+ ["./po-module1", "./po-module2"], find_intltool_dirs())
354+
355+ def test_find_intltool_dirs_broken(self):
356+ # Complete run: part of the intltool structure is broken.
357+ self._prepare_package("intltool_full_ok")
358+ os.remove("./src/module1/sourcefile1.c")
359+ self.assertEqual(
360+ ["./po-module2"], find_intltool_dirs())
361+
362+ def test_get_translation_domain_makevars(self):
363+ # Find a translation domain in Makevars.
364+ self._prepare_package("intltool_domain_makevars")
365+ self.assertEqual(
366+ "translationdomain",
367+ get_translation_domain("po"))
368+
369+ def test_get_translation_domain_makefile_in_in(self):
370+ # Find a translation domain in Makefile.in.in.
371+ self._prepare_package("intltool_domain_makefile_in_in")
372+ self.assertEqual(
373+ "packagename-in-in",
374+ get_translation_domain("po"))
375+
376+ def test_get_translation_domain_configure_ac(self):
377+ # Find a translation domain in configure.ac.
378+ self._prepare_package("intltool_domain_configure_ac")
379+ self.assertEqual(
380+ "packagename-ac",
381+ get_translation_domain("po"))
382+
383+ def test_get_translation_domain_configure_in(self):
384+ # Find a translation domain in configure.in.
385+ self._prepare_package("intltool_domain_configure_in")
386+ self.assertEqual(
387+ "packagename-in",
388+ get_translation_domain("po"))
389+
390+ def test_get_translation_domain_makefile_in_in_substitute(self):
391+ # Find a translation domain in Makefile.in.in with substitution from
392+ # configure.ac.
393+ self._prepare_package("intltool_domain_makefile_in_in_substitute")
394+ self.assertEqual(
395+ "domainname-ac-in-in",
396+ get_translation_domain("po"))
397+
398+ def test_get_translation_domain_makefile_in_in_substitute_same_name(self):
399+ # Find a translation domain in Makefile.in.in with substitution from
400+ # configure.ac from a variable with the same name as in
401+ # Makefile.in.in.
402+ self._prepare_package(
403+ "intltool_domain_makefile_in_in_substitute_same_name")
404+ self.assertEqual(
405+ "packagename-ac-in-in",
406+ get_translation_domain("po"))
407+
408+ def test_get_translation_domain_makefile_in_in_substitute_same_file(self):
409+ # Find a translation domain in Makefile.in.in with substitution from
410+ # the same file.
411+ self._prepare_package(
412+ "intltool_domain_makefile_in_in_substitute_same_file")
413+ self.assertEqual(
414+ "domain-in-in-in-in",
415+ get_translation_domain("po"))
416+
417+ def test_get_translation_domain_makefile_in_in_substitute_broken(self):
418+ # Find no translation domain in Makefile.in.in when the substitution
419+ # cannot be fulfilled.
420+ self._prepare_package(
421+ "intltool_domain_makefile_in_in_substitute_broken")
422+ self.assertIs(None, get_translation_domain("po"))
423+
424+ def test_get_translation_domain_configure_in_substitute_version(self):
425+ # Find a translation domain in configure.in with Makefile-style
426+ # substitution from the same file.
427+ self._prepare_package(
428+ "intltool_domain_configure_in_substitute_version")
429+ self.assertEqual(
430+ "domainname-in42",
431+ get_translation_domain("po"))
432+
433+ def test_pottery_check_intltool_script(self):
434+ # Let the script run to see it works fine.
435+ self._prepare_package("intltool_full_ok")
436+
437+ return_code, stdout, stderr = run_script(
438+ 'scripts/rosetta/pottery-check-intltool.py', [])
439+
440+ self.assertEqual(dedent("""\
441+ ./po-module1 (packagename-module1)
442+ ./po-module2 (packagename-module2)
443+ """), stdout)
444+
445+
446+class TestConfigFile(TestCase):
447+
448+ def setUp(self):
449+ super(TestConfigFile, self).setUp()
450+ self.configfile = ConfigFile(StringIO(dedent("""\
451+ # Demo config file
452+ CCC
453+ AAA=
454+ BBB =
455+ CCC = ccc # comment
456+ DDD=dd.d
457+ """)))
458+
459+ def test_getVariable_exists(self):
460+ self.assertEqual('ccc', self.configfile.getVariable('CCC'))
461+ self.assertEqual('dd.d', self.configfile.getVariable('DDD'))
462+
463+ def test_getVariable_empty(self):
464+ self.assertEqual('', self.configfile.getVariable('AAA'))
465+ self.assertEqual('', self.configfile.getVariable('BBB'))
466+
467+ def test_getVariable_nonexistent(self):
468+ self.assertIs(None, self.configfile.getVariable('FFF'))
469+
470+
471+def test_suite():
472+ return unittest.TestLoader().loadTestsFromName(__name__)
473+
474
475=== added file 'scripts/rosetta/pottery-check-intltool.py'
476--- scripts/rosetta/pottery-check-intltool.py 1970-01-01 00:00:00 +0000
477+++ scripts/rosetta/pottery-check-intltool.py 2010-02-01 09:53:14 +0000
478@@ -0,0 +1,18 @@
479+#! /usr/bin/python2.5
480+#
481+# Copyright 2009 Canonical Ltd. This software is licensed under the
482+# GNU Affero General Public License version 3 (see the file LICENSE).
483+
484+"""Print a list of directories that contain a valid intltool structure."""
485+
486+import _pythonpath
487+
488+
489+from lp.translations.pottery.detect_intltool import (
490+ find_intltool_dirs, get_translation_domain)
491+
492+
493+if __name__ == "__main__":
494+ for dirname in find_intltool_dirs():
495+ translation_domain = get_translation_domain(dirname) or "<unknown>"
496+ print "%s (%s)" % (dirname, translation_domain)