Blame SOURCES/msghack.py

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