1
# Copyright (C) 2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Merge logic for po_merge plugin."""
26
from bzrlib.lazy_import import lazy_import
27
lazy_import(globals(), """
41
class PoMerger(merge.PerFileMerger):
42
"""Merge .po files."""
44
def __init__(self, merger):
45
super(merge.PerFileMerger, self).__init__(merger)
46
# config options are cached locally until config files are (see
47
# http://pad.lv/832042)
49
# FIXME: We use the branch config as there is no tree config
51
self.conf = merger.this_branch.get_config_stack()
52
# Which dirs are targeted by the hook
53
self.po_dirs = self.conf.get('po_merge.po_dirs')
54
# Which files are targeted by the hook
55
self.po_glob = self.conf.get('po_merge.po_glob')
56
# Which .pot file should be used
57
self.pot_glob = self.conf.get('po_merge.pot_glob')
58
self.command = self.conf.get('po_merge.command', expand=False)
59
# file_matches() will set the following for merge_text()
60
self.pot_file_abspath = None
61
trace.mutter('PoMerger created')
63
def file_matches(self, params):
64
"""Return True if merge_matching should be called on this file."""
65
if not self.po_dirs or not self.command:
66
# Return early if there is no options defined
69
po_path = self.get_filepath(params, self.merger.this_tree)
70
for po_dir in self.po_dirs:
71
glob = osutils.pathjoin(po_dir, self.po_glob)
72
if fnmatch.fnmatch(po_path, glob):
73
trace.mutter('po %s matches: %s' % (po_path, glob))
76
trace.mutter('PoMerger did not match for %s and %s'
77
% (self.po_dirs, self.po_glob))
79
# Do we have the corresponding .pot file
80
for inv_entry in self.merger.this_tree.list_files(from_dir=po_dir,
82
trace.mutter('inv_entry: %r' % (inv_entry,))
83
pot_name, pot_file_id = inv_entry[0], inv_entry[3]
84
if fnmatch.fnmatch(pot_name, self.pot_glob):
85
relpath = osutils.pathjoin(po_dir, pot_name)
86
self.pot_file_abspath = self.merger.this_tree.abspath(relpath)
87
# FIXME: I can't find an easy way to know if the .pot file has
88
# conflicts *during* the merge itself. So either the actual
89
# content on disk is fine and msgmerge will work OR it's not
90
# and it will fail. Conversely, either the result is ok for the
91
# user and he's happy OR the user needs to resolve the
92
# conflicts in the .pot file and use remerge.
94
trace.mutter('will msgmerge %s using %s'
95
% (po_path, self.pot_file_abspath))
100
def _invoke(self, command):
101
trace.mutter('Will msgmerge: %s' % (command,))
102
# We use only absolute paths so we don't care about the cwd
103
proc = subprocess.Popen(cmdline.split(command),
104
stdout=subprocess.PIPE,
105
stderr=subprocess.PIPE,
106
stdin=subprocess.PIPE)
107
out, err = proc.communicate()
108
return proc.returncode, out, err
110
def merge_matching(self, params):
111
return self.merge_text(params)
113
def merge_text(self, params):
114
"""Calls msgmerge when .po files conflict.
116
This requires a valid .pot file to reconcile both sides.
118
# Create tmp files with the 'this' and 'other' content
119
tmpdir = tempfile.mkdtemp(prefix='po_merge')
121
env['this'] = osutils.pathjoin(tmpdir, 'this')
122
env['other'] = osutils.pathjoin(tmpdir, 'other')
123
env['result'] = osutils.pathjoin(tmpdir, 'result')
124
env['pot_file'] = self.pot_file_abspath
126
with osutils.open_file(env['this'], 'wb') as f:
127
f.writelines(params.this_lines)
128
with osutils.open_file(env['other'], 'wb') as f:
129
f.writelines(params.other_lines)
130
command = self.conf.expand_options(self.command, env)
131
retcode, out, err = self._invoke(command)
132
with osutils.open_file(env['result']) as f:
133
# FIXME: To avoid the list() construct below which means the
134
# whole 'result' file is kept in memory, there may be a way to
135
# use an iterator that will close the file when it's done, but
136
# there is still the issue of removing the tmp dir...
138
return 'success', list(f.readlines())
140
osutils.rmtree(tmpdir)
141
return 'not applicable', []