Merge lp:~henninge/launchpad/intltool-detection into lp:launchpad
- intltool-detection
- Merge into devel
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 | ||||
Related bugs: |
|
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.
Description of the change
Henning Eggers (henninge) wrote : | # |
Jeroen T. Vermeulen (jtv) wrote : | # |
"Pottery"? Nice name!
Some comments on the diff:
=== added file 'lib/lp/
--- lib/lp/
+++ lib/lp/
+import errno
+import os.path
+import re
+from subprocess import Popen, PIPE
+
+POTFILES_in = "POTFILES.in"
+STATUS_OK = 0
+
+def run_shell_
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.
+ env = os.environ
+ pipe = Popen(
+ cmd, shell=True, env=env, stdin=stdin, stdout=PIPE, stderr=PIPE)
+ if input_data:
+ try:
+ pipe.stdin.
+ 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_
+ return result_dirs
+
+
+def check_potfiles_
+ """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_
+
+ 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_
+ """Search the current directory and its subdiretories for intltool
+ structure.
+ """
+ return sorted(
Nice & compact.
+def get_translation
+ """Determine the transl...
Jeroen T. Vermeulen (jtv) wrote : | # |
Nice test suite for get_translation
Henning Eggers (henninge) wrote : | # |
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/
> --- lib/lp/
> +++ lib/lp/
>
> +import errno
> +import os.path
> +import re
> +from subprocess import Popen, PIPE
> +
> +POTFILES_in = "POTFILES.in"
> +STATUS_OK = 0
> +
> +def run_shell_
>
> 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_
> + return result_dirs
> +
> +
> +def check_potfiles_
> + """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_
> + """Search the current directory and its subdiretories for intltool
> + structure.
> + """
> + return sorted(
>
> Nice & compact.
Yes, that's one of the features I like about Python.
>
>
> +def get_translation
> + """Determine the translation domain by parsing various files.
>
> I find this very unclear. It says too much about _how_ it does its job, ...
Jeroen T. Vermeulen (jtv) wrote : | # |
Thanks for the changes.
Preview Diff
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' |
236 | Binary 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' |
238 | Binary 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' |
240 | Binary 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' |
242 | Binary 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' |
244 | Binary 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' |
246 | Binary 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' |
248 | Binary 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' |
250 | Binary 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' |
252 | Binary 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' |
254 | Binary 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' |
256 | Binary 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' |
258 | Binary 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' |
260 | Binary 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) |
= 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_translatio n_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.