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