Daiki Ueno 56dcfb
#!/usr/bin/python3
Jens Petersen 845648
## -*- coding: utf-8 -*-
Jens Petersen 62265d
## Copyright (C) 2001, 2004, 2008, 2012 Red Hat, Inc.
Jens Petersen 845648
## Copyright (C) 2001 Trond Eivind Glomsrød <teg@redhat.com>
cvsdist 2b5159
Jens Petersen 62265d
## This program is free software: you can redistribute it and/or modify
cvsdist 2b5159
## it under the terms of the GNU General Public License as published by
Jens Petersen 62265d
## the Free Software Foundation, either version 3 of the License, or
cvsdist 2b5159
## (at your option) any later version.
cvsdist 2b5159
cvsdist 2b5159
## This program is distributed in the hope that it will be useful,
cvsdist 2b5159
## but WITHOUT ANY WARRANTY; without even the implied warranty of
cvsdist 2b5159
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
cvsdist 2b5159
## GNU General Public License for more details.
cvsdist 2b5159
cvsdist 2b5159
## You should have received a copy of the GNU General Public License
Jens Petersen 62265d
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
cvsdist 2b5159
cvsdist 2b5159
"""
cvsdist 2b5159
A msghack replacement
cvsdist 2b5159
"""
cvsdist 2b5159
cvsdist 2b5159
import sys
cvsdist 2b5159
cvsdist 2b5159
class GTMessage:
cvsdist 2b5159
    """
cvsdist 2b5159
    A class containing a message, its msgid and various references pointing at it
cvsdist 2b5159
    """
cvsdist 2b5159
cvsdist 2b5159
    def __init__(self,id=None,message=None,refs=[]):
cvsdist 2b5159
        """
cvsdist 2b5159
        The constructor for the GTMessage class
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @message The message
cvsdist 2b5159
        @id The messageid associated with the object
cvsdist 2b5159
        """
Daiki Ueno 5a1ae7
        self._message=message.strip()
Daiki Ueno 5a1ae7
        self._id=id.strip()
cvsdist 2b5159
        self._refs=[]
cvsdist 2b5159
        for ref in refs:
cvsdist 2b5159
            self._refs.append(ref)
cvsdist 2b5159
cvsdist 2b5159
    def __str__(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for ref in self._refs:
cvsdist 2b5159
            res=res+ref+"\n"
cvsdist 6b9110
        res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message)
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 2b5159
    def invertedStrings(self):
cvsdist 2b5159
        """
cvsdist 6b9110
        Returns a string representation, but with msgid and msgstr inverted.
cvsdist 6b9110
        Note: Don't invert the "" string
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for ref in self._refs:
cvsdist 2b5159
            res=res+ref+"\n"
cvsdist 6b9110
        if not self._id=="\"\"":
cvsdist 6b9110
            res=res+"msgid %s\nmsgstr %s\n" % (self._message,self._id)
cvsdist 6b9110
        else:
cvsdist 6b9110
            res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message)
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 2b5159
    def emptyMsgStrings(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object, but leave the msgstr
cvsdist 2b5159
        empty - create a pot file from a po file
cvsdist 6b9110
        Note: Won't remove the "" string
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for ref in self._refs:
cvsdist 2b5159
            res=res+ref+"\n"
cvsdist 6b9110
        if not self._id=="\"\"":
cvsdist 6b9110
            res=res+"msgid %s\nmsgstr \"\"\n" % (self._id)
cvsdist 6b9110
        else:
cvsdist 6b9110
            res=res+"msgid %s\nmsgstr %s\n" % (self._id,self._message)
cvsdist 2b5159
        return res
cvsdist 2b5159
        
cvsdist 31ff37
    def compareMessage(self,msg):
cvsdist 31ff37
        """
cvsdist 31ff37
        Return  if the messages have identical msgids, 0 otherwise
cvsdist 31ff37
        @self The object instance
cvsdist 31ff37
        @msg The message to compare to
cvsdist 31ff37
        """
cvsdist 31ff37
cvsdist 31ff37
        if self._id == msg._id:
cvsdist 31ff37
            return 1
cvsdist 31ff37
        return 0
cvsdist 31ff37
        
cvsdist 31ff37
cvsdist 2b5159
class GTMasterMessage:
cvsdist 2b5159
    """
cvsdist 2b5159
    A class containing a message, its msgid and various references pointing at it
cvsdist 2b5159
    The difference between GTMessage and GTMasterMessage is that this class
cvsdist 2b5159
    can do less operations, but is able to store multiple msgstrs with identifiers
cvsdist 2b5159
    (usually language, like 'msgst(no)'
cvsdist 2b5159
    """
cvsdist 2b5159
cvsdist 2b5159
    def __init__(self,id=None,refs=[]):
cvsdist 2b5159
        """
cvsdist 2b5159
        The constructor for the GTMessage class
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @id The messageid associated with the object
cvsdist 2b5159
        """
cvsdist 2b5159
        self._id=id
cvsdist 2b5159
        self._refs=[]
cvsdist 2b5159
        self._messages=[]
cvsdist 2b5159
        for ref in refs:
cvsdist 2b5159
            self._refs.append(ref)
cvsdist 2b5159
cvsdist 2b5159
    def addMessage(self,message,identifier):
cvsdist 2b5159
        """
cvsdist 2b5159
        Add a new message and identifier to the GTMasterMessage object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @message The message to append
cvsdist 2b5159
        @identifier The identifier of the message
cvsdist 2b5159
        """
cvsdist 2b5159
        self._messages.append((identifier,message))
cvsdist 2b5159
cvsdist 2b5159
    def __str__(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for ref in self._refs:
cvsdist 2b5159
            res=res+ref+"\n"
cvsdist 6b9110
        res=res+"msgid %s\n" % self._id
cvsdist 2b5159
        for message in self._messages:
cvsdist 6b9110
            res=res+"msgstr(%s) %s\n" %(message[0],message[1])
cvsdist 2b5159
        res=res+"\n"
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 2b5159
class GTFile:
cvsdist 2b5159
    """
cvsdist 2b5159
    A class containing the GTMessages contained in a file
cvsdist 2b5159
    """
cvsdist 2b5159
cvsdist 2b5159
    def __init__(self,filename):
cvsdist 2b5159
        """
cvsdist 2b5159
        The constructor of the GTMFile class
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @filename The  file to initialize from
cvsdist 2b5159
        """
cvsdist 2b5159
        self._filename=filename
cvsdist 2b5159
        self._messages=[]
cvsdist 2b5159
        self.readFile(filename)
cvsdist 2b5159
cvsdist 2b5159
    def __str__(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for message in self._messages:
cvsdist 2b5159
            res=res+str(message)+"\n"
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 2b5159
    def invertedStrings(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object, with msgid and msgstr
cvsdist 6b9110
        swapped. Will remove duplicates...
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 6b9110
cvsdist 6b9110
        msght={}
cvsdist 6b9110
        msgar=[]
cvsdist 6b9110
cvsdist 2b5159
        for message in self._messages:
cvsdist 6b9110
            if message._id=='""' and len(msgar)==0:
cvsdist 6b9110
                msgar.append(GTMessage(message._id,message._message,message._refs))
cvsdist 6b9110
                continue
cvsdist 6b9110
            msg=GTMessage(message._message,message._id,message._refs)
Daiki Ueno 5a1ae7
            if msg._id not in msght:
cvsdist 6b9110
                msght[msg._id]=msg
cvsdist 6b9110
                msgar.append(msg)
cvsdist 6b9110
            else:
cvsdist 6b9110
                msg2=msght[msg._id]
cvsdist 6b9110
                for ref in msg._refs:
cvsdist 6b9110
                    msg2._refs.append(ref)
cvsdist 6b9110
        res=""
cvsdist 6b9110
        for message in msgar:
cvsdist 6b9110
            res=res+str(message)+"\n"
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 31ff37
    def msgidDupes(self):
cvsdist 31ff37
        """
cvsdist 6b9110
        Search for duplicates in the msgids.
cvsdist 31ff37
        @self The object instance
cvsdist 31ff37
        """
cvsdist 31ff37
        msgids={}
cvsdist 31ff37
        res=""
cvsdist 31ff37
        for message in self._messages:
cvsdist 31ff37
            msgid=message._id
Daiki Ueno 5a1ae7
            if msgid in msgids:
cvsdist 31ff37
                res=res+"Duplicate: %s\n" % (msgid)
cvsdist 31ff37
            else:
cvsdist 31ff37
                msgids[msgid]=1
cvsdist 31ff37
        return res
cvsdist 31ff37
cvsdist 2b5159
    def getMsgstr(self,msgid):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return the msgstr matching the given id. 'None' if missing
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @msgid The msgid key
cvsdist 2b5159
        """
cvsdist 2b5159
cvsdist 2b5159
        for message in self._messages:
cvsdist 2b5159
            if msgid == message._id:
cvsdist 2b5159
                return message._message
cvsdist 2b5159
        return None
cvsdist 2b5159
cvsdist 2b5159
    def emptyMsgStrings(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object, but leave the msgstr
cvsdist 2b5159
        empty - create a pot file from a po file
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for message in self._messages:
cvsdist 2b5159
            res=res+message.emptyMsgStrings()+"\n"
cvsdist 2b5159
        return res
cvsdist 2b5159
cvsdist 31ff37
            
cvsdist 31ff37
    def append(self,B):
cvsdist 31ff37
        """
cvsdist 31ff37
        Append entries from dictionary B which aren't
cvsdist 31ff37
        already present in this dictionary
cvsdist 31ff37
        @self The object instance
cvsdist 31ff37
        @B the dictionary to append messages from
cvsdist 31ff37
        """
cvsdist 31ff37
cvsdist 31ff37
        for message in B._messages:
cvsdist 31ff37
            if not self.getMsgstr(message._id):
cvsdist 31ff37
                self._messages.append(message)
cvsdist 31ff37
                
cvsdist 31ff37
cvsdist 2b5159
    def readFile(self,filename):
cvsdist 2b5159
        """
cvsdist 2b5159
        Read the contents of a file into the GTFile object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @filename The name of the file to read
cvsdist 2b5159
        """
cvsdist 2b5159
        
cvsdist 2b5159
        file=open(filename,"r")
cvsdist 2b5159
        msgid=""
cvsdist 2b5159
        msgstr=""
cvsdist 2b5159
        refs=[]
cvsdist 2b5159
        lines=[]
cvsdist 2b5159
        inmsgid=0
cvsdist 2b5159
        inmsgstr=0
cvsdist 2b5159
        templines=file.readlines()
cvsdist 2b5159
        for line in templines:
Daiki Ueno 5a1ae7
            lines.append(line.strip())
cvsdist 2b5159
        for line in lines:
Daiki Ueno 5a1ae7
            pos=line.find('"')
Daiki Ueno 5a1ae7
            pos2=line.rfind('"')
cvsdist 2b5159
            if line and line[0]=="#":
Daiki Ueno 5a1ae7
                refs.append(line.strip())
cvsdist 2b5159
            if inmsgstr==0 and line[:6]=="msgstr":
cvsdist 2b5159
                msgstr=""
cvsdist 2b5159
                inmsgstr=1
cvsdist 2b5159
                inmsgid=0
cvsdist 2b5159
            if inmsgstr==1:
cvsdist 2b5159
                if pos==-1:
cvsdist 2b5159
                    inmsgstr=0
cvsdist 6b9110
                    #Handle entries with and without "" consistently
cvsdist 6b9110
                    if msgid[:2]=='""' and len(msgid)>4: 
cvsdist 6b9110
                        msgid=msgid[2:]
cvsdist 6b9110
                    if msgstr[:2]=='""' and len(msgstr)>4: 
cvsdist 6b9110
                        msgstr=msgstr[2:]
cvsdist 2b5159
                    message=GTMessage(msgid,msgstr,refs)
cvsdist 2b5159
                    self._messages.append(message)
cvsdist 2b5159
                    msgstr=""
cvsdist 2b5159
                    msgid=""
cvsdist 2b5159
                    refs=[]
cvsdist 2b5159
                else:
cvsdist 6b9110
                    msgstr=msgstr+line[pos:pos2+1]+"\n"
cvsdist 2b5159
            if inmsgid==0 and line[:5]=="msgid":
cvsdist 2b5159
                msgid=""
cvsdist 2b5159
                inmsgid=1
cvsdist 2b5159
            if inmsgid==1:
cvsdist 2b5159
                if pos==-1:
cvsdist 2b5159
                    inmsgid=0
cvsdist 2b5159
                else:
cvsdist 6b9110
                    msgid=msgid+line[pos:pos2+1]+"\n"
cvsdist 2b5159
        if msgstr and msgid:
cvsdist 6b9110
            message=GTMessage(msgid,msgstr,refs)
cvsdist 2b5159
            self._messages.append(message)
cvsdist 2b5159
cvsdist 2b5159
cvsdist 2b5159
class GTMaster:
cvsdist 2b5159
    """
cvsdist 2b5159
    A class containing a master catalogue of gettext dictionaries
cvsdist 2b5159
    """
cvsdist 2b5159
cvsdist 2b5159
    def __init__(self,dicts):
cvsdist 2b5159
        """
cvsdist 2b5159
        The constructor for the GTMaster class
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @dicts An array of dictionaries to merge
cvsdist 2b5159
        """
cvsdist 2b5159
        self._messages=[]
cvsdist 2b5159
        self.createMaster(dicts)
cvsdist 2b5159
cvsdist 2b5159
    def createMaster(self,dicts):
cvsdist 2b5159
        """
cvsdist 2b5159
        Create the master catalogue
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        @dicts An array of dictionaries to merge
cvsdist 2b5159
        """
cvsdist 2b5159
cvsdist 2b5159
        self._master=dicts[0]
cvsdist 2b5159
        self._dicts=dicts[1:]
cvsdist 2b5159
cvsdist 2b5159
        for message in self._master._messages:
cvsdist 2b5159
            gtm=GTMasterMessage(message._id,message._refs)
cvsdist 2b5159
            gtm.addMessage(message._message,self._master._filename[:-3])
cvsdist 2b5159
            for dict in self._dicts:
cvsdist 2b5159
                res=dict.getMsgstr(message._id)
cvsdist 2b5159
                if(res):
cvsdist 2b5159
                    gtm.addMessage(res,dict._filename[:-3])
cvsdist 2b5159
            self._messages.append(gtm)
cvsdist 2b5159
cvsdist 2b5159
    def __str__(self):
cvsdist 2b5159
        """
cvsdist 2b5159
        Return a string representation of the object
cvsdist 2b5159
        @self The object instance
cvsdist 2b5159
        """
cvsdist 2b5159
        res=""
cvsdist 2b5159
        for message in self._messages:
cvsdist 2b5159
            res=res+str(message)+"\n"
cvsdist 2b5159
        return res
cvsdist 31ff37
Ding-Yi Chen 19de18
def printUsage():
Ding-Yi Chen 19de18
    "Print the usage messages"
Daiki Ueno 56dcfb
    print("Usage: " + str(sys.argv[0]) + " [OPTION] file.po [ref.po]\n\
Ding-Yi Chen 19de18
This program can be used to alter .po files in ways no sane mind would think about.\n\
Ding-Yi Chen 19de18
    -o                result will be written to FILE\n\
Ding-Yi Chen 19de18
    --invert          invert a po file by switching msgid and msgstr\n\
Ding-Yi Chen 19de18
    --master          join any number of files in a master-formatted catalog\n\
Ding-Yi Chen 19de18
    --empty           empty the contents of the .po file, creating a .pot\n\
Ding-Yi Chen 19de18
    --append          append entries from ref.po that don't exist in file.po\n\
Ding-Yi Chen 19de18
\n\
Daiki Ueno 5a1ae7
Note: It is just a replacement of msghack for backward support.\n")
Ding-Yi Chen 19de18
cvsdist 2b5159
cvsdist 2b5159
if __name__=="__main__":
cvsdist 31ff37
    output=None
cvsdist 31ff37
    res=None
cvsdist 31ff37
    if("-o") in sys.argv:
Daiki Ueno 5a1ae7
        if (len(sys.argv)<=sys.argv.index("-o")+1):
Daiki Ueno 5a1ae7
                print("file.po and ref.po are not specified!\n")
Daiki Ueno 5a1ae7
                printUsage()
Daiki Ueno 5a1ae7
                exit(1)
Daiki Ueno 5a1ae7
        output=sys.argv[sys.argv.index("-o")+1]
cvsdist 31ff37
        sys.argv.remove("-o")
Daiki Ueno 5a1ae7
        sys.argv.remove(output)
cvsdist 2b5159
    if("--invert") in sys.argv:
Daiki Ueno 5a1ae7
        if (len(sys.argv)<=sys.argv.index("--invert")+1):
Daiki Ueno 5a1ae7
            print("file.po is not specified!\n")
Daiki Ueno 5a1ae7
            printUsage()
Daiki Ueno 5a1ae7
            exit(1)
Daiki Ueno 5a1ae7
        file=sys.argv[sys.argv.index("--invert")+1]
cvsdist 2b5159
        gtf=GTFile(file)
cvsdist 31ff37
        res1=gtf.msgidDupes()
cvsdist 31ff37
        if res1:
cvsdist 31ff37
            sys.stderr.write(res1)
cvsdist 31ff37
            sys.exit(1)
cvsdist 31ff37
        res=str(gtf.invertedStrings())
cvsdist 31ff37
    elif("--empty") in sys.argv:
Daiki Ueno 5a1ae7
        if (len(sys.argv)<=sys.argv.index("--empty")+1):
Daiki Ueno 5a1ae7
            print("file.po is not specified!\n")
Daiki Ueno 5a1ae7
            printUsage()
Daiki Ueno 5a1ae7
            exit(1)
Daiki Ueno 5a1ae7
        file=sys.argv[sys.argv.index("--empty")+1]
cvsdist 2b5159
        gtf=GTFile(file)
cvsdist 31ff37
        res=str(gtf.emptyMsgStrings())
cvsdist 31ff37
    elif("--master") in sys.argv:
Daiki Ueno 5a1ae7
        if (len(sys.argv)<=sys.argv.index("--master")+1):
Daiki Ueno 5a1ae7
            print("file.po is not specified!\n")
Daiki Ueno 5a1ae7
            printUsage()
Daiki Ueno 5a1ae7
            exit(1)
Daiki Ueno 5a1ae7
        loc=sys.argv.index("--master")+1
cvsdist 2b5159
        gtfs=[]
cvsdist 2b5159
        for file in sys.argv[loc:]:
cvsdist 2b5159
            gtfs.append(GTFile(file))
cvsdist 2b5159
        master=GTMaster(gtfs)
cvsdist 31ff37
        res=str(master)
cvsdist 31ff37
    elif("--append") in sys.argv:
Daiki Ueno 5a1ae7
        if (len(sys.argv)<=sys.argv.index("--append")+2):
Daiki Ueno 5a1ae7
            print("file.po and/or ref.po are not specified!\n")
Daiki Ueno 5a1ae7
            printUsage()
Daiki Ueno 5a1ae7
            exit(1)
Daiki Ueno 5a1ae7
        file=sys.argv[sys.argv.index("--append")+1]
cvsdist 31ff37
        file2=sys.argv[sys.argv.index("--append")+2]
cvsdist 31ff37
        gtf=GTFile(file)
cvsdist 31ff37
        gtf2=GTFile(file2)
cvsdist 31ff37
        gtf.append(gtf2)
cvsdist 31ff37
        res=str(gtf)
cvsdist 31ff37
    else:
Daiki Ueno 5a1ae7
        #print("Not implemented: "+str(sys.argv))
Daiki Ueno 5a1ae7
        printUsage()
cvsdist 31ff37
        sys.exit(1)
cvsdist 31ff37
    if not output:
Daiki Ueno 5a1ae7
        print(res)
cvsdist 31ff37
    else:
cvsdist 31ff37
        file=open(output,"w")
cvsdist 31ff37
        file.write(res)
cvsdist 31ff37
    sys.exit(0)