1#!/usr/local/bin/python3.8 2## -*- coding: utf-8 -*- 3## Copyright (C) 2001, 2004, 2008, 2012 Red Hat, Inc. 4## Copyright (C) 2001 Trond Eivind Glomsrød <teg@redhat.com> 5 6## This program is free software: you can redistribute it and/or modify 7## it under the terms of the GNU General Public License as published by 8## the Free Software Foundation, either version 3 of the License, or 9## (at your option) any later version. 10 11## This program is distributed in the hope that it will be useful, 12## but WITHOUT ANY WARRANTY; without even the implied warranty of 13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14## GNU General Public License for more details. 15 16## You should have received a copy of the GNU General Public License 17## along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19""" 20A msghack replacement 21""" 22 23import sys 24 25class GTMessage: 26 """ 27 A class containing a message, its msgid and various references pointing at it 28 """ 29 30 def __init__(self,id=None,message=None,refs=[]): 31 """ 32 The constructor for the GTMessage class 33 @self The object instance 34 @message The message 35 @id The messageid associated with the object 36 """ 37 self._message=message.strip() 38 self._id=id.strip() 39 self._refs=[] 40 for ref in refs: 41 self._refs.append(ref) 42 43 def __str__(self): 44 """ 45 Return a string representation of the object 46 @self The object instance 47 """ 48 res="" 49 for ref in self._refs: 50 res=res+ref+"\n" 51 res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message) 52 return res 53 54 def invertedStrings(self): 55 """ 56 Returns a string representation, but with msgid and msgstr inverted. 57 Note: Don't invert the "" string 58 @self The object instance 59 """ 60 res="" 61 for ref in self._refs: 62 res=res+ref+"\n" 63 if not self._id=="\"\"": 64 res=res+"msgid %s\nmsgstr %s\n" % (self._message,self._id) 65 else: 66 res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message) 67 return res 68 69 def emptyMsgStrings(self): 70 """ 71 Return a string representation of the object, but leave the msgstr 72 empty - create a pot file from a po file 73 Note: Won't remove the "" string 74 @self The object instance 75 """ 76 res="" 77 for ref in self._refs: 78 res=res+ref+"\n" 79 if not self._id=="\"\"": 80 res=res+"msgid %s\nmsgstr \"\"\n" % (self._id) 81 else: 82 res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message) 83 return res 84 85 def compareMessage(self,msg): 86 """ 87 Return if the messages have identical msgids, 0 otherwise 88 @self The object instance 89 @msg The message to compare to 90 """ 91 92 if self._id == msg._id: 93 return 1 94 return 0 95 96 97class GTMasterMessage: 98 """ 99 A class containing a message, its msgid and various references pointing at it 100 The difference between GTMessage and GTMasterMessage is that this class 101 can do less operations, but is able to store multiple msgstrs with identifiers 102 (usually language, like 'msgst(no)' 103 """ 104 105 def __init__(self,id=None,refs=[]): 106 """ 107 The constructor for the GTMessage class 108 @self The object instance 109 @id The messageid associated with the object 110 """ 111 self._id=id 112 self._refs=[] 113 self._messages=[] 114 for ref in refs: 115 self._refs.append(ref) 116 117 def addMessage(self,message,identifier): 118 """ 119 Add a new message and identifier to the GTMasterMessage object 120 @self The object instance 121 @message The message to append 122 @identifier The identifier of the message 123 """ 124 self._messages.append((identifier,message)) 125 126 def __str__(self): 127 """ 128 Return a string representation of the object 129 @self The object instance 130 """ 131 res="" 132 for ref in self._refs: 133 res=res+ref+"\n" 134 res=res+"msgid %s\n" % self._id 135 for message in self._messages: 136 res=res+"msgstr(%s) %s\n" %(message[0],message[1]) 137 res=res+"\n" 138 return res 139 140class GTFile: 141 """ 142 A class containing the GTMessages contained in a file 143 """ 144 145 def __init__(self,filename): 146 """ 147 The constructor of the GTMFile class 148 @self The object instance 149 @filename The file to initialize from 150 """ 151 self._filename=filename 152 self._messages=[] 153 self.readFile(filename) 154 155 def __str__(self): 156 """ 157 Return a string representation of the object 158 @self The object instance 159 """ 160 res="" 161 for message in self._messages: 162 res=res+str(message)+"\n" 163 return res 164 165 def invertedStrings(self): 166 """ 167 Return a string representation of the object, with msgid and msgstr 168 swapped. Will remove duplicates... 169 @self The object instance 170 """ 171 172 msght={} 173 msgar=[] 174 175 for message in self._messages: 176 if message._id=='""' and len(msgar)==0: 177 msgar.append(GTMessage(message._id,message._message,message._refs)) 178 continue 179 msg=GTMessage(message._message,message._id,message._refs) 180 if msg._id not in msght: 181 msght[msg._id]=msg 182 msgar.append(msg) 183 else: 184 msg2=msght[msg._id] 185 for ref in msg._refs: 186 msg2._refs.append(ref) 187 res="" 188 for message in msgar: 189 res=res+str(message)+"\n" 190 return res 191 192 def msgidDupes(self): 193 """ 194 Search for duplicates in the msgids. 195 @self The object instance 196 """ 197 msgids={} 198 res="" 199 for message in self._messages: 200 msgid=message._id 201 if msgid in msgids: 202 res=res+"Duplicate: %s\n" % (msgid) 203 else: 204 msgids[msgid]=1 205 return res 206 207 def getMsgstr(self,msgid): 208 """ 209 Return the msgstr matching the given id. 'None' if missing 210 @self The object instance 211 @msgid The msgid key 212 """ 213 214 for message in self._messages: 215 if msgid == message._id: 216 return message._message 217 return None 218 219 def emptyMsgStrings(self): 220 """ 221 Return a string representation of the object, but leave the msgstr 222 empty - create a pot file from a po file 223 @self The object instance 224 """ 225 226 res="" 227 for message in self._messages: 228 res=res+message.emptyMsgStrings()+"\n" 229 return res 230 231 232 def append(self,B): 233 """ 234 Append entries from dictionary B which aren't 235 already present in this dictionary 236 @self The object instance 237 @B the dictionary to append messages from 238 """ 239 240 for message in B._messages: 241 if not self.getMsgstr(message._id): 242 self._messages.append(message) 243 244 245 def readFile(self,filename): 246 """ 247 Read the contents of a file into the GTFile object 248 @self The object instance 249 @filename The name of the file to read 250 """ 251 252 file=open(filename,"r") 253 msgid="" 254 msgstr="" 255 refs=[] 256 lines=[] 257 inmsgid=0 258 inmsgstr=0 259 templines=file.readlines() 260 for line in templines: 261 lines.append(line.strip()) 262 for line in lines: 263 pos=line.find('"') 264 pos2=line.rfind('"') 265 if line and line[0]=="#": 266 refs.append(line.strip()) 267 if inmsgstr==0 and line[:6]=="msgstr": 268 msgstr="" 269 inmsgstr=1 270 inmsgid=0 271 if inmsgstr==1: 272 if pos==-1: 273 inmsgstr=0 274 #Handle entries with and without "" consistently 275 if msgid[:2]=='""' and len(msgid)>4: 276 msgid=msgid[2:] 277 if msgstr[:2]=='""' and len(msgstr)>4: 278 msgstr=msgstr[2:] 279 message=GTMessage(msgid,msgstr,refs) 280 self._messages.append(message) 281 msgstr="" 282 msgid="" 283 refs=[] 284 else: 285 msgstr=msgstr+line[pos:pos2+1]+"\n" 286 if inmsgid==0 and line[:5]=="msgid": 287 msgid="" 288 inmsgid=1 289 if inmsgid==1: 290 if pos==-1: 291 inmsgid=0 292 else: 293 msgid=msgid+line[pos:pos2+1]+"\n" 294 if msgstr and msgid: 295 message=GTMessage(msgid,msgstr,refs) 296 self._messages.append(message) 297 298 299class GTMaster: 300 """ 301 A class containing a master catalogue of gettext dictionaries 302 """ 303 304 def __init__(self,dicts): 305 """ 306 The constructor for the GTMaster class 307 @self The object instance 308 @dicts An array of dictionaries to merge 309 """ 310 self._messages=[] 311 self.createMaster(dicts) 312 313 def createMaster(self,dicts): 314 """ 315 Create the master catalogue 316 @self The object instance 317 @dicts An array of dictionaries to merge 318 """ 319 320 self._master=dicts[0] 321 self._dicts=dicts[1:] 322 323 for message in self._master._messages: 324 gtm=GTMasterMessage(message._id,message._refs) 325 gtm.addMessage(message._message,self._master._filename[:-3]) 326 for dict in self._dicts: 327 res=dict.getMsgstr(message._id) 328 if(res): 329 gtm.addMessage(res,dict._filename[:-3]) 330 self._messages.append(gtm) 331 332 def __str__(self): 333 """ 334 Return a string representation of the object 335 @self The object instance 336 """ 337 res="" 338 for message in self._messages: 339 res=res+str(message)+"\n" 340 return res 341 342def printUsage(): 343 "Print the usage messages" 344 print("Usage: " + str(sys.argv[0]) + " [OPTION] file.po [ref.po]\n\ 345This program can be used to alter .po files in ways no sane mind would think about.\n\ 346 -o result will be written to FILE\n\ 347 --invert invert a po file by switching msgid and msgstr\n\ 348 --master join any number of files in a master-formatted catalog\n\ 349 --empty empty the contents of the .po file, creating a .pot\n\ 350 --append append entries from ref.po that don't exist in file.po\n\ 351\n\ 352Note: It is just a replacement of msghack for backward support.\n") 353 354 355if __name__=="__main__": 356 output=None 357 res=None 358 if("-o") in sys.argv: 359 if (len(sys.argv)<=sys.argv.index("-o")+1): 360 print("file.po and ref.po are not specified!\n") 361 printUsage() 362 exit(1) 363 output=sys.argv[sys.argv.index("-o")+1] 364 sys.argv.remove("-o") 365 sys.argv.remove(output) 366 if("--invert") in sys.argv: 367 if (len(sys.argv)<=sys.argv.index("--invert")+1): 368 print("file.po is not specified!\n") 369 printUsage() 370 exit(1) 371 file=sys.argv[sys.argv.index("--invert")+1] 372 gtf=GTFile(file) 373 res1=gtf.msgidDupes() 374 if res1: 375 sys.stderr.write(res1) 376 sys.exit(1) 377 res=str(gtf.invertedStrings()) 378 elif("--empty") in sys.argv: 379 if (len(sys.argv)<=sys.argv.index("--empty")+1): 380 print("file.po is not specified!\n") 381 printUsage() 382 exit(1) 383 file=sys.argv[sys.argv.index("--empty")+1] 384 gtf=GTFile(file) 385 res=str(gtf.emptyMsgStrings()) 386 elif("--master") in sys.argv: 387 if (len(sys.argv)<=sys.argv.index("--master")+1): 388 print("file.po is not specified!\n") 389 printUsage() 390 exit(1) 391 loc=sys.argv.index("--master")+1 392 gtfs=[] 393 for file in sys.argv[loc:]: 394 gtfs.append(GTFile(file)) 395 master=GTMaster(gtfs) 396 res=str(master) 397 elif("--append") in sys.argv: 398 if (len(sys.argv)<=sys.argv.index("--append")+2): 399 print("file.po and/or ref.po are not specified!\n") 400 printUsage() 401 exit(1) 402 file=sys.argv[sys.argv.index("--append")+1] 403 file2=sys.argv[sys.argv.index("--append")+2] 404 gtf=GTFile(file) 405 gtf2=GTFile(file2) 406 gtf.append(gtf2) 407 res=str(gtf) 408 else: 409 #print("Not implemented: "+str(sys.argv)) 410 printUsage() 411 sys.exit(1) 412 if not output: 413 print(res) 414 else: 415 file=open(output,"w") 416 file.write(res) 417 sys.exit(0) 418