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