root/translations/lib/types/pot.py @ 514:6ce8502fb54a

Revision 514:6ce8502fb54a, 14.3 KB (checked in by Ignacio Vazquez-Abrams <ivazquez@…>, 17 months ago)

Tarball file filter fix

Line 
1import os, commands, re
2from django.contrib.contenttypes.models import ContentType
3from django.conf import settings
4from translations.lib.types import (TransManagerMixin, TransManagerError)
5from translations.models import POFile, Language
6from translations.lib.utils import (run_command, CommandError)
7
8class POTStatsError(Exception):
9
10    def __init__(self, language):
11        self.language = language
12
13    def __str__(self):
14        return "Could not calculate the statistics using the '%s' " \
15               "language." % (self.language)
16
17class FileFilterError(Exception):
18
19    def __str__(self):
20        return "The file filter should allows the POTFILES.in file" \
21               " for intltool POT-based projects."
22
23class POTManager(TransManagerMixin):
24    """ A browser class for POT files. """
25
26    def __init__(self, file_set, path, source_lang, file_filter,
27        filepath=None):
28        self.file_set = file_set
29        if filepath is None:
30            filepath = path
31        self.path = filepath
32        self.source_lang = source_lang
33        self.file_filter = file_filter
34        self.msgmerge_path = os.path.join(settings.MSGMERGE_DIR, 
35                                     os.path.basename(path))
36
37    def get_file_path(self, filename, is_msgmerged=False):
38        # All the files should be in the file_set, except the intltool
39        # POT file that is created by the system
40        if filename in self.file_set or \
41           filename.endswith('.pot') and is_msgmerged:
42            if is_msgmerged:
43                file_path = os.path.join(self.msgmerge_path, filename)
44            else:
45                file_path = os.path.join(self.path, filename)
46        else:
47            raise IOError("File not found.")
48        return file_path
49
50    def get_file_content(self, filename, is_msgmerged=False):
51        file_path = self.get_file_path(filename, is_msgmerged)
52        filef = file(file_path, 'rb')
53        file_content = filef.read()
54        filef.close()
55        return file_content
56
57    def get_po_files(self):
58        """ Return a list of PO filenames """
59
60        po_files = []
61        for filename in self.file_set:
62            if filename.endswith('.po'):
63                po_files.append(filename)
64        po_files.sort()
65        return po_files
66
67    def get_langfiles(self, lang):
68        """ Return a list with the PO filenames for a specificy language """
69
70        files=[]
71        for filepath in self.get_po_files():
72            if self.guess_language(filepath) == lang:
73                files.append(filepath)
74        return files
75
76    def guess_language(self, filepath):
77        """ Guess a language from a filepath """
78
79        if 'LC_MESSAGES' in filepath:
80            fp = filepath.split('LC_MESSAGES')
81            return os.path.basename(fp[0][:-1:])
82        else:
83            return os.path.basename(filepath[:-3:])
84
85    def get_langs(self):
86        """ Return all langs tha have a po file for a object """
87
88        langs = []
89        for filepath in self.get_po_files():
90            langs.append(self.guess_language(filepath))
91        langs.sort()
92        return langs
93
94
95    def po_file_stats(self, pofile):
96        """ Calculate stats for a POT/PO file """
97        error = False
98        pofile = os.path.join(self.msgmerge_path, pofile)
99
100        command = "LC_ALL=C LANG=C LANGUAGE=C msgfmt --statistics" \
101                  " -o /dev/null %s" % pofile
102        (error, output) = commands.getstatusoutput(command)
103
104        if error:
105            error = True
106   
107        r_tr = re.search(r"([0-9]+) translated", output)
108        r_un = re.search(r"([0-9]+) untranslated", output)
109        r_fz = re.search(r"([0-9]+) fuzzy", output)
110
111        if r_tr: translated = r_tr.group(1)
112        else: translated = 0
113        if r_un: untranslated = r_un.group(1)
114        else: untranslated = 0
115        if r_fz: fuzzy = r_fz.group(1)
116        else: fuzzy = 0
117
118        return {'translated' : int(translated),
119                'fuzzy' : int(fuzzy),
120                'untranslated' : int(untranslated),
121                'error' : error,}
122
123    def calculate_file_stats(self, filename, try_to_merge):
124        """
125        Return the statistics of a specificy file for an object after
126        merging the file with the source translation file (POT), if possible.
127        """
128        # We might want to skip the msgmerge setting try_to_merge as False
129        if try_to_merge:
130            source_file = self.get_source_file_for_pofile(filename)
131            (is_msgmerged, file_path) = self.msgmerge(filename, source_file)
132        else:
133            is_msgmerged=False
134            file_path = os.path.join(self.path, filename)
135
136        #Copy the current file (non-msgmerged) to the static dir
137        if not is_msgmerged:
138            self.copy_file_to_static_dir(filename)
139
140        postats = self.po_file_stats(file_path)
141
142        return {'trans': postats['translated'],
143                'fuzzy': postats['fuzzy'],
144                'untrans': postats['untranslated'],
145                'error': postats['error'],
146                'is_msgmerged': is_msgmerged}
147
148    def create_lang_stats(self, lang, object, try_to_merge=True):
149        """Set the statistics of a specificy language for an object."""
150
151        for filename in self.get_langfiles(lang):
152            self.create_file_stats(filename, object, try_to_merge)
153
154    def create_file_stats(self, filename, object, try_to_merge=True):
155        """Set the statistics of a specificy file for an object."""
156        lang_code = self.guess_language(filename)
157        try:
158            stats = self.calculate_file_stats(filename, try_to_merge)
159            ctype = ContentType.objects.get_for_model(object)
160            s = POFile.objects.get(object_id=object.id, content_type=ctype,
161                                   filename=filename)
162            if not s.language:
163                try:
164                    l = Language.objects.by_code_or_alias(code=lang_code)
165                    s.language=l
166                except Language.DoesNotExist:
167                    pass
168        except POTStatsError:
169            # TODO: It should probably be raised when a checkout of a
170            # module has a problem. Needs to decide what to do when it
171            # happens
172            pass
173        except POFile.DoesNotExist:
174            try:
175                l = Language.objects.by_code_or_alias(code=lang_code)
176            except Language.DoesNotExist:
177                l = None
178            s = POFile.objects.create(language=l, filename=filename,
179                                        object=object)
180        s.set_stats(trans=stats['trans'], fuzzy=stats['fuzzy'], 
181                    untrans=stats['untrans'], error=stats['error'])
182        s.is_msgmerged = stats['is_msgmerged']
183        s.language_code = lang_code
184        return s.save()
185
186    def stats_for_lang_object(self, lang, object):
187        """Return statistics for an object in a specific language."""
188        try:
189            ctype = ContentType.objects.get_for_model(object)
190            return POFile.objects.filter(language=lang, content_type=ctype, 
191                                         object_id=object.id)[0]
192        except IndexError:
193            return None
194
195    def get_stats(self, object):
196        """ Return a list of statistics of languages for an object."""
197        return POFile.objects.by_object_total(object)
198
199    def delete_stats_for_object(self, object):
200        """ Delete all lang statistics of an object."""
201        ctype = ContentType.objects.get_for_model(object)
202        POFile.objects.filter(object_id=object.id, content_type=ctype).delete()
203
204    def set_source_stats(self, object, is_msgmerged):
205        """Set the source file (pot) in the database"""
206
207        ctype = ContentType.objects.get_for_model(object)
208        potfiles=self.get_source_files()
209        for potfile in potfiles:
210            p, created = POFile.objects.get_or_create(filename=potfile,
211                                                      is_pot=True,
212                                                      content_type=ctype,
213                                                      object_id=object.id,
214                                                      is_msgmerged=is_msgmerged)
215            stats = self.po_file_stats(potfile)
216            p.set_stats(trans=stats['translated'], 
217                        fuzzy=stats['fuzzy'], 
218                        untrans=stats['untranslated'], 
219                        error=stats['error'])
220
221            p.save()
222
223    def get_source_stats(self, object):
224        """
225        Return a list of the source file (pot) statistics from the database
226        """
227        try:
228            ctype = ContentType.objects.get_for_model(object)
229            return POFile.objects.filter(object_id=object.id, 
230                                         content_type=ctype, is_pot=True)
231        except POFile.DoesNotExist:
232            return None
233
234    def get_source_files(self):
235        """
236        Return a list with the source files (pot) paths
237
238        Try to find it in the file_set passed to the PO file instace.
239        If it still fauls, try to find the POT file in the filesystem.
240        """
241        pofiles=[]
242        for filename in self.file_set:
243            if filename.endswith('.pot'):
244                pofiles.append(filename)
245
246        # If there is no POT in the file_set, try to find it in
247        # the file system
248        if not pofiles:
249            filename = self.get_intltool_source_file(self.msgmerge_path)
250            if filename:
251                pofiles.append(filename)
252
253        return pofiles
254
255    def get_intltool_source_file(self, po_dir):
256        """Return the POT file that might be created by intltool"""
257        for root, dirs, files in os.walk(po_dir):
258            for filename in files:
259                if filename.endswith('.pot'):
260                    # Get the relative path
261                    rel_path = root.split(os.path.basename(self.path))[1]
262                    # Return the relative path of the POT file without
263                    # the / in the start of the POT file path
264                    return os.path.join(rel_path, filename)[1:]
265
266    def get_source_file_for_pofile(self, filename):
267        """
268        Find the related source file (POT) for a pofile when it has multiple
269        source files.
270
271        This method gets a filename as parameter and tries to discover the
272        related POT file using two methods:
273        1. Trying to find a POT file with the same base path that the pofile.
274           Example: /foo/bar.pot and /foo/baz.po match on this method.
275
276        2. Trying to find a POT file with the same domain that the pofile in any
277           directory.
278           Example: /foo/bar.pot and /foo/baz/bar.po match on this method.
279           The domain in this case is 'bar'.
280
281        If no POT is found the method returns None.
282        """
283
284        # For filename='/foo/bar.po'
285        fb = os.path.basename(filename) # 'bar.po'
286        fp = filename.split(fb)[0]        # '/foo/'
287
288        source_files = self.get_source_files()
289
290        # Find the POT with the same domain or path that the filename,
291        # if the component has more that one POT file
292        if len(source_files) > 1:
293            for source in source_files:
294                sb = os.path.basename(source)[:-1] # *.po instead *.pot
295                pb = source.split(sb)[0]
296                if pb==fp or sb==fb:
297                    return source
298        elif len(source_files) == 1:
299            return source_files[0]
300        else:
301            return None
302
303    def copy_file_to_static_dir(self, filename):
304        """Copy a file to the destination"""
305        import shutil
306
307        dest = os.path.join(self.msgmerge_path, filename)
308
309        if not os.path.exists(os.path.dirname(dest)):
310            os.makedirs(os.path.dirname(dest))
311
312        shutil.copyfile(os.path.join(self.path, filename), dest)
313
314    def msgmerge(self, pofile, potfile):
315        """
316        Merge two files and save the output at the settings.MSGMERGE_DIR.
317        In case that error, copy the source file (pofile) to the
318        destination without merging.
319        """
320        is_msgmerged = True
321        outpo = os.path.join(self.msgmerge_path, pofile)
322
323        try:
324        # TODO: Find a library to avoid call msgmerge by command
325            command = "msgmerge -o %(outpo)s %(pofile)s %(potfile)s" % {
326                    'outpo' : outpo,
327                    'pofile' : os.path.join(self.path, pofile),
328                    'potfile' : os.path.join(self.msgmerge_path, potfile),}
329           
330            (error, output) = commands.getstatusoutput(command)
331        except:
332            error = True
333
334        if error:
335            # TODO: Log this. output var can be used.
336            is_msgmerged = False
337
338        return (is_msgmerged, outpo)
339
340    def guess_po_dir(self):
341        """ Guess the po/ diretory to run intltool """
342        for filename in self.file_set:
343            if 'POTFILES.in' in filename:
344                if self.file_filter:
345                    if re.compile(self.file_filter).match(filename):
346                        return os.path.join(self.path, 
347                                      os.path.dirname(filename))
348        raise FileFilterError
349
350    def intltool_update(self):
351        """
352        Create a new POT file using "intltool-update -p" from the
353        source files. Return False if it fails.
354        """
355        po_dir = self.guess_po_dir()
356        try:
357            command = "cd \"%(dir)s\" && rm -f missing notexist && " \
358                      "intltool-update -p" % { "dir" : po_dir, }
359            (error, output) = commands.getstatusoutput(command)
360        except:
361            error = True
362
363        # Copy the potfile if it exist to the merged files directory
364        potfile = self.get_intltool_source_file(po_dir)
365        if potfile:
366            self.copy_file_to_static_dir(potfile)
367
368        if error:
369            # TODO: Log this. output var can be used.
370            return False
371
372        return True
373
374    def msgfmt_check(self, po_contents):
375        """
376        Run a `msgfmt -c` on a file (file object).
377        Raises a ValueError in case the file has errors.
378        """
379        try:
380            p = run_command('msgfmt -o /dev/null -c -', _input=po_contents)
381        except CommandError:
382            # TODO: Figure out why gettext is not working here
383            raise ValueError, "Your file does not" \
384                            " pass by the check for correctness" \
385                            " (msgfmt -c). Please run this command" \
386                            " on your system to see the errors."
Note: See TracBrowser for help on using the browser.