Blame SOURCES/msghack.py

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