fix git support for v1.5.3 (or higher) by setting "--work-tree"
[translate_toolkit.git] / storage / poheader.py
blob165227cf0756c613f3785a27a9f4db55b423472f
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright 2002-2006 Zuza Software Foundation
5 #
6 # This file is part of translate.
8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 """class that handles all header functions for a header in a po file"""
24 from translate.misc import dictutils
25 from translate import __version__
26 import re
27 import time
29 author_re = re.compile(r".*<\S+@\S+>.*\d{4,4}")
31 def parseheaderstring(input):
32 """Parses an input string with the definition of a PO header and returns
33 the interpreted values as a dictionary"""
34 headervalues = dictutils.ordereddict()
35 for line in input.split("\n"):
36 if not line or ":" not in line:
37 continue
38 key, value = line.split(":", 1)
39 #We don't want unicode keys
40 key = str(key.strip())
41 headervalues[key] = value.strip()
42 return headervalues
44 def tzstring():
45 """Returns the timezone as a string in the format [+-]0000, eg +0200."""
46 if time.daylight:
47 tzoffset = time.altzone
48 else:
49 tzoffset = time.timezone
51 hours, minutes = time.gmtime(abs(tzoffset))[3:5]
52 if tzoffset > 0:
53 hours *= -1
54 tz = str("%+d" % hours).zfill(3) + str(minutes).zfill(2)
55 return tz
57 def update(existing, add=False, **kwargs):
58 """Update an existing header dictionary with the values in kwargs, adding new values
59 only if add is true.
61 @return: Updated dictionary of header entries
62 @rtype: dict
63 """
64 headerargs = dictutils.ordereddict()
65 fixedargs = dictutils.cidict()
66 for key, value in kwargs.items():
67 key = key.replace("_", "-")
68 if key.islower():
69 key = key.title()
70 fixedargs[key] = value
71 removed = []
72 for key in poheader.header_order:
73 if existing.has_key(key):
74 if key in fixedargs:
75 headerargs[key] = fixedargs.pop(key)
76 else:
77 headerargs[key] = existing[key]
78 removed.append(key)
79 elif add and fixedargs.has_key(key):
80 headerargs[key] = fixedargs.pop(key)
81 for key, value in existing.iteritems():
82 if not key in removed:
83 headerargs[key] = value
84 if add:
85 for key in fixedargs:
86 headerargs[key] = fixedargs[key]
87 return headerargs
90 class poheader(object):
91 """This class implements functionality for manipulation of po file headers.
92 This class is a mix-in class and useless on its own. It must be used from all
93 classes which represent a po file"""
95 x_generator = "Translate Toolkit %s" % __version__.ver
97 header_order = [
98 "Project-Id-Version",
99 "Report-Msgid-Bugs-To",
100 "POT-Creation-Date",
101 "PO-Revision-Date",
102 "Last-Translator",
103 "Language-Team",
104 "MIME-Version",
105 "Content-Type",
106 "Content-Transfer-Encoding",
107 "Plural-Forms",
108 "X-Generator",
112 def makeheaderdict(self, charset="CHARSET", encoding="ENCODING", project_id_version=None, pot_creation_date=None, po_revision_date=None, last_translator=None, language_team=None, mime_version=None, plural_forms=None, report_msgid_bugs_to=None, **kwargs):
113 """create a header for the given filename. arguments are specially handled, kwargs added as key: value
114 pot_creation_date can be None (current date) or a value (datetime or string)
115 po_revision_date can be None (form), False (=pot_creation_date), True (=now), or a value (datetime or string)
117 @return: Dictionary with the header items
118 @rtype: dict
120 if project_id_version is None:
121 project_id_version = "PACKAGE VERSION"
122 if pot_creation_date is None or pot_creation_date == True:
123 pot_creation_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
124 if isinstance(pot_creation_date, time.struct_time):
125 pot_creation_date = time.strftime("%Y-%m-%d %H:%M", pot_creation_date) + tzstring()
126 if po_revision_date is None:
127 po_revision_date = "YEAR-MO-DA HO:MI+ZONE"
128 elif po_revision_date == False:
129 po_revision_date = pot_creation_date
130 elif po_revision_date == True:
131 po_revision_date = time.strftime("%Y-%m-%d %H:%M") + tzstring()
132 if isinstance(po_revision_date, time.struct_time):
133 po_revision_date = time.strftime("%Y-%m-%d %H:%M", po_revision_date) + tzstring()
134 if last_translator is None:
135 last_translator = "FULL NAME <EMAIL@ADDRESS>"
136 if language_team is None:
137 language_team = "LANGUAGE <LL@li.org>"
138 if mime_version is None:
139 mime_version = "1.0"
140 if report_msgid_bugs_to is None:
141 report_msgid_bugs_to = ""
143 defaultargs = dictutils.ordereddict()
144 defaultargs["Project-Id-Version"] = project_id_version
145 defaultargs["Report-Msgid-Bugs-To"] = report_msgid_bugs_to
146 defaultargs["POT-Creation-Date"] = pot_creation_date
147 defaultargs["PO-Revision-Date"] = po_revision_date
148 defaultargs["Last-Translator"] = last_translator
149 defaultargs["Language-Team"] = language_team
150 defaultargs["MIME-Version"] = mime_version
151 defaultargs["Content-Type"] = "text/plain; charset=%s" % charset
152 defaultargs["Content-Transfer-Encoding"] = encoding
153 if plural_forms:
154 defaultargs["Plural-Forms"] = plural_forms
155 defaultargs["X-Generator"] = self.x_generator
157 return update(defaultargs, add=True, **kwargs)
159 def header(self):
160 """Returns the header element, or None. Only the first element is allowed
161 to be a header. Note that this could still return an empty header element,
162 if present."""
163 if len(self.units) == 0:
164 return None
165 candidate = self.units[0]
166 if candidate.isheader():
167 return candidate
168 else:
169 return None
171 def parseheader(self):
172 """Parses the PO header and returns
173 the interpreted values as a dictionary"""
174 header = self.header()
175 if not header:
176 return {}
177 return parseheaderstring(header.target)
179 def updateheader(self, add=False, **kwargs):
180 """Updates the fields in the PO style header.
181 This will create a header if add == True"""
182 header = self.header()
183 if not header:
184 # FIXME: does not work for xliff files yet
185 if add and callable(getattr(self, "makeheader", None)):
186 header = self.makeheader(**kwargs)
187 self.units.insert(0, header)
188 else:
189 headeritems = update(self.parseheader(), add, **kwargs)
190 keys = headeritems.keys()
191 if not "Content-Type" in keys or "charset=CHARSET" in headeritems["Content-Type"]:
192 headeritems["Content-Type"] = "text/plain; charset=UTF-8"
193 if not "Content-Transfer-Encoding" in keys or "ENCODING" in headeritems["Content-Transfer-Encoding"]:
194 headeritems["Content-Transfer-Encoding"] = "8bit"
195 headerString = ""
196 for key, value in headeritems.items():
197 headerString += "%s: %s\n" % (key, value)
198 header.target = headerString
199 header.markfuzzy(False) # TODO: check why we do this?
200 return header
202 def getheaderplural(self):
203 """returns the nplural and plural values from the header"""
204 header = self.parseheader()
205 pluralformvalue = header.get('Plural-Forms', None)
206 if pluralformvalue is None:
207 return None, None
208 nplural = re.findall("nplurals=(.+?);", pluralformvalue)
209 plural = re.findall("plural=(.+?);?$", pluralformvalue)
210 if not nplural or nplural[0] == "INTEGER":
211 nplural = None
212 else:
213 nplural = nplural[0]
214 if not plural or plural[0] == "EXPRESSION":
215 plural = None
216 else:
217 plural = plural[0]
218 return nplural, plural
220 def updateheaderplural(self, nplurals, plural):
221 """update the Plural-Form PO header"""
222 if isinstance(nplurals, basestring):
223 nplurals = int(nplurals)
224 self.updateheader( Plural_Forms = "nplurals=%d; plural=%s;" % (nplurals, plural) )
226 def mergeheaders(self, otherstore):
227 """Merges another header with this header.
229 This header is assumed to be the template.
231 @type otherstore: L{base.TranslationStore}
235 newvalues = otherstore.parseheader()
236 retain = {
237 "Project_Id_Version": newvalues['Project-Id-Version'],
238 "PO_Revision_Date" : newvalues['PO-Revision-Date'],
239 "Last_Translator" : newvalues['Last-Translator'],
240 "Language_Team" : newvalues['Language-Team'],
242 # not necessarily there:
243 plurals = newvalues.get('Plural-Forms', None)
244 if plurals:
245 retain['Plural-Forms'] = plurals
246 self.updateheader(**retain)
248 def updatecontributor(self, name, email=None):
249 """Add contribution comments
251 header = self.header()
252 if not header:
253 return
254 prelines = []
255 contriblines = []
256 postlines = []
257 contribexists = False
258 incontrib = False
259 outcontrib = False
260 for line in header.getnotes("translator").split('\n'):
261 line = line.strip()
262 if line == u"FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.":
263 incontrib = True
264 continue
265 if author_re.match(line):
266 incontrib = True
267 contriblines.append(line)
268 continue
269 if line == "" and incontrib:
270 incontrib = False
271 outcontrib = True
272 if incontrib:
273 contriblines.append(line)
274 elif not outcontrib:
275 prelines.append(line)
276 else:
277 postlines.append(line)
279 year = time.strftime("%Y")
280 contribexists = False
281 for line in contriblines:
282 if name in line and (email is None or email in line):
283 contribexists = True
284 break
285 if not contribexists:
286 # Add a new contributor
287 if email:
288 contriblines.append("%s <%s>, %s" % (name, email, year))
289 else:
290 contriblines.append("%s, %s" % (name, year))
292 header.removenotes()
293 header.addnote("\n".join(prelines))
294 header.addnote("\n".join(contriblines))
295 header.addnote("\n".join(postlines))