1*b1cdbd2cSJim Jagielski#**************************************************************
2*b1cdbd2cSJim Jagielski#
3*b1cdbd2cSJim Jagielski#  Licensed to the Apache Software Foundation (ASF) under one
4*b1cdbd2cSJim Jagielski#  or more contributor license agreements.  See the NOTICE file
5*b1cdbd2cSJim Jagielski#  distributed with this work for additional information
6*b1cdbd2cSJim Jagielski#  regarding copyright ownership.  The ASF licenses this file
7*b1cdbd2cSJim Jagielski#  to you under the Apache License, Version 2.0 (the
8*b1cdbd2cSJim Jagielski#  "License"); you may not use this file except in compliance
9*b1cdbd2cSJim Jagielski#  with the License.  You may obtain a copy of the License at
10*b1cdbd2cSJim Jagielski#
11*b1cdbd2cSJim Jagielski#    http://www.apache.org/licenses/LICENSE-2.0
12*b1cdbd2cSJim Jagielski#
13*b1cdbd2cSJim Jagielski#  Unless required by applicable law or agreed to in writing,
14*b1cdbd2cSJim Jagielski#  software distributed under the License is distributed on an
15*b1cdbd2cSJim Jagielski#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16*b1cdbd2cSJim Jagielski#  KIND, either express or implied.  See the License for the
17*b1cdbd2cSJim Jagielski#  specific language governing permissions and limitations
18*b1cdbd2cSJim Jagielski#  under the License.
19*b1cdbd2cSJim Jagielski#
20*b1cdbd2cSJim Jagielski#**************************************************************
21*b1cdbd2cSJim Jagielski
22*b1cdbd2cSJim Jagielskifrom optparse import OptionParser
23*b1cdbd2cSJim Jagielskifrom sdf import SdfData
24*b1cdbd2cSJim Jagielskifrom pseudo import PseudoSet
25*b1cdbd2cSJim Jagielski
26*b1cdbd2cSJim Jagielskiimport sys
27*b1cdbd2cSJim Jagielskiimport os
28*b1cdbd2cSJim Jagielskiimport shutil
29*b1cdbd2cSJim Jagielski
30*b1cdbd2cSJim Jagielskiclass AbstractL10nTool:
31*b1cdbd2cSJim Jagielski    _options            = {}
32*b1cdbd2cSJim Jagielski    _args               = ""
33*b1cdbd2cSJim Jagielski    _resource_type      = ""
34*b1cdbd2cSJim Jagielski    _source_language    = "en-US"
35*b1cdbd2cSJim Jagielski
36*b1cdbd2cSJim Jagielski    ##### Implement these abstract methods
37*b1cdbd2cSJim Jagielski
38*b1cdbd2cSJim Jagielski    ##### Nameing scheme for the output files
39*b1cdbd2cSJim Jagielski    def get_outputfile_format_str(self):
40*b1cdbd2cSJim Jagielski        # filename,fileNoExt,language,extension,pathPrefix,pathPostFix,path
41*b1cdbd2cSJim Jagielski        #return "{path}/{fileNoExt}_{language}.{extension}"
42*b1cdbd2cSJim Jagielski        return self._options.pattern
43*b1cdbd2cSJim Jagielski
44*b1cdbd2cSJim Jagielski    ################################# Merge single files ###########################################
45*b1cdbd2cSJim Jagielski
46*b1cdbd2cSJim Jagielski    ##### Merge a single file
47*b1cdbd2cSJim Jagielski    def merge_file(self, inputfilename, outputfilename, parsed_file_ref, lang, is_forced_lang, sdfdata):
48*b1cdbd2cSJim Jagielski        pass
49*b1cdbd2cSJim Jagielski
50*b1cdbd2cSJim Jagielski    ##### Helper for parse-once-use-often like parsing a xml file is needed implement it here
51*b1cdbd2cSJim Jagielski    def parse_file(self, filename):
52*b1cdbd2cSJim Jagielski        return None
53*b1cdbd2cSJim Jagielski
54*b1cdbd2cSJim Jagielski    ################### Merge one big file containing all strings in all languages #################
55*b1cdbd2cSJim Jagielski    def merge_one_big_file(self, inputfile, outputfilename, parsed_file_ref, lang, sdfdata):
56*b1cdbd2cSJim Jagielski        pass
57*b1cdbd2cSJim Jagielski
58*b1cdbd2cSJim Jagielski    ################### Extract a single File ######################################################
59*b1cdbd2cSJim Jagielski    def extract_file(self, inputfile):
60*b1cdbd2cSJim Jagielski        pass
61*b1cdbd2cSJim Jagielski
62*b1cdbd2cSJim Jagielski    ################################################################################################
63*b1cdbd2cSJim Jagielski
64*b1cdbd2cSJim Jagielski    def format_outputfile(self, filename, language):
65*b1cdbd2cSJim Jagielski        extension = filename[filename.rfind('.')+1:]
66*b1cdbd2cSJim Jagielski        file = filename[:filename.rfind('.')]
67*b1cdbd2cSJim Jagielski        # Python 2.3.x friendly
68*b1cdbd2cSJim Jagielski        return self.get_outputfile_format_str().replace('[', '%(').replace(']',')s') % \
69*b1cdbd2cSJim Jagielski                { 'filename': filename, 'fileNoExt': file, 'language': language, 'extension': extension, 'path_prefix': self._options.path_prefix,
70*b1cdbd2cSJim Jagielski                  'path_postfix': self._options.path_postfix, 'path': self.get_path() }
71*b1cdbd2cSJim Jagielski
72*b1cdbd2cSJim Jagielski        #return self.get_outputfile_format_str().replace('[', '{').replace(']','}').format(
73*b1cdbd2cSJim Jagielski        #       filename=filename, fileNoExt=file, language=language, extension=extension, path_prefix=self._options.path_prefix,
74*b1cdbd2cSJim Jagielski        #       path_postfix=self._options.path_postfix, path=self.get_path())
75*b1cdbd2cSJim Jagielski
76*b1cdbd2cSJim Jagielski    def get_path(self):
77*b1cdbd2cSJim Jagielski        if self._options.outputfile.find('/') == -1:
78*b1cdbd2cSJim Jagielski            return ""
79*b1cdbd2cSJim Jagielski        else:
80*b1cdbd2cSJim Jagielski            return self._options.outputfile[:self._options.outputfile.rfind('/')]
81*b1cdbd2cSJim Jagielski
82*b1cdbd2cSJim Jagielski    def merge(self,  sdfdata):
83*b1cdbd2cSJim Jagielski        langset,forcedset, foundset = PseudoSet(), PseudoSet() , PseudoSet()
84*b1cdbd2cSJim Jagielski
85*b1cdbd2cSJim Jagielski        if self._options.languages:
86*b1cdbd2cSJim Jagielski            langset = PseudoSet(self._options.languages)
87*b1cdbd2cSJim Jagielski        if self._options.forcedlanguages:
88*b1cdbd2cSJim Jagielski            forcedset = PseudoSet(self._options.forcedlanguages)
89*b1cdbd2cSJim Jagielski        if sdfdata.get_languages_found_in_sdf():
90*b1cdbd2cSJim Jagielski            foundset = sdfdata.get_languages_found_in_sdf()
91*b1cdbd2cSJim Jagielski
92*b1cdbd2cSJim Jagielski        if self.has_multi_inputfiles():
93*b1cdbd2cSJim Jagielski            filelist = self.read_inputfile_list()
94*b1cdbd2cSJim Jagielski        else:
95*b1cdbd2cSJim Jagielski            filelist = self._options.inputfile
96*b1cdbd2cSJim Jagielski
97*b1cdbd2cSJim Jagielski        for inputfile in filelist:
98*b1cdbd2cSJim Jagielski            ref = self.parse_file(inputfile)
99*b1cdbd2cSJim Jagielski            # Don't write that files if there is no l10n present
100*b1cdbd2cSJim Jagielski            if ((langset & foundset) - forcedset):  # all langs given and found in sdf without enforced
101*b1cdbd2cSJim Jagielski                [self.merge_file(inputfile,self.format_outputfile(inputfile, lang), ref, lang, False, sdfdata) for lang in ((langset & foundset) - forcedset)]
102*b1cdbd2cSJim Jagielski            # Always write those files even if there is no l10n available
103*b1cdbd2cSJim Jagielski            if forcedset: # all enforced langs
104*b1cdbd2cSJim Jagielski                [self.merge_file(inputfile, self.format_outputfile(inputfile, lang), ref, lang, True, sdfdata)  for lang in forcedset]
105*b1cdbd2cSJim Jagielski            # In case a big file have to be written
106*b1cdbd2cSJim Jagielski            if ((langset & foundset) | forcedset): # all langs given ,found in sdf and enforced ones
107*b1cdbd2cSJim Jagielski                self.merge_one_big_file(inputfile, self.format_outputfile(inputfile, lang), ref, ((langset & foundset) | forcedset), sdfdata)
108*b1cdbd2cSJim Jagielski
109*b1cdbd2cSJim Jagielski    def has_multi_inputfiles(self):
110*b1cdbd2cSJim Jagielski        return self._options.inputfile[0] == '@'
111*b1cdbd2cSJim Jagielski
112*b1cdbd2cSJim Jagielski    def copy_file(self, inputfilename, outputfilename):
113*b1cdbd2cSJim Jagielski        try:
114*b1cdbd2cSJim Jagielski            os.remove(outputfilename)
115*b1cdbd2cSJim Jagielski        except:
116*b1cdbd2cSJim Jagielski            pass
117*b1cdbd2cSJim Jagielski
118*b1cdbd2cSJim Jagielski        try:
119*b1cdbd2cSJim Jagielski            os.remove(outputfilename)
120*b1cdbd2cSJim Jagielski        except:
121*b1cdbd2cSJim Jagielski            pass
122*b1cdbd2cSJim Jagielski
123*b1cdbd2cSJim Jagielski        try:
124*b1cdbd2cSJim Jagielski            shutil.copy(inputfilename, outputfilename)
125*b1cdbd2cSJim Jagielski        except IOError:
126*b1cdbd2cSJim Jagielski            print("ERROR: Can not copy file '" + inputfilename + "' to " + "'" + outputfilename + "'")
127*b1cdbd2cSJim Jagielski            sys.exit(-1)
128*b1cdbd2cSJim Jagielski
129*b1cdbd2cSJim Jagielski    def extract(self):
130*b1cdbd2cSJim Jagielski        try:
131*b1cdbd2cSJim Jagielski            f = open(self._options.outputfile, "w+")
132*b1cdbd2cSJim Jagielski            f.write(self.extract_file(self._options.inputfile))
133*b1cdbd2cSJim Jagielski        except IOError:
134*b1cdbd2cSJim Jagielski            print("ERROR: Can not write file " + self._options.outputfile)
135*b1cdbd2cSJim Jagielski        else:
136*b1cdbd2cSJim Jagielski            f.close()
137*b1cdbd2cSJim Jagielski
138*b1cdbd2cSJim Jagielski    # Parse the common options
139*b1cdbd2cSJim Jagielski    def parse_options(self):
140*b1cdbd2cSJim Jagielski        parser = OptionParser()
141*b1cdbd2cSJim Jagielski        parser.add_option("-i", "--inputfile",       dest="inputfile",       metavar="FILE", help="resource file to read"         )
142*b1cdbd2cSJim Jagielski        parser.add_option("-o", "--outputfile",      dest="outputfile",      metavar="FILE", help="extracted sdf or merged file"  )
143*b1cdbd2cSJim Jagielski        parser.add_option("-m", "--inputsdffile",    dest="input_sdf_file",  metavar="FILE", help="merge this sdf file"           )
144*b1cdbd2cSJim Jagielski        parser.add_option("-x", "--pathprefix",      dest="path_prefix",     metavar="PATH", help=""                              )
145*b1cdbd2cSJim Jagielski        parser.add_option("-y", "--pathpostfix",     dest="path_postfix",    metavar="PATH", help=""                              )
146*b1cdbd2cSJim Jagielski        parser.add_option("-p", "--projectname",     dest="project_name",    metavar="NAME", help=""                              )
147*b1cdbd2cSJim Jagielski        parser.add_option("-r", "--projectroot",     dest="project_root",    metavar="PATH", help=""                              )
148*b1cdbd2cSJim Jagielski        parser.add_option("-f", "--forcedlanguages", dest="forcedlanguages", metavar="ISOCODE[,ISOCODE]", help="Always merge those langs even if no l10n is available for those langs" )
149*b1cdbd2cSJim Jagielski        parser.add_option("-l", "--languages",       dest="languages",       metavar="ISOCODE[,ISOCODE]", help="Merge those langs if l10n is found for each")
150*b1cdbd2cSJim Jagielski        parser.add_option("-s", "--pattern",         dest="pattern",         metavar="", help=""                                  )
151*b1cdbd2cSJim Jagielski        parser.add_option("-q", "--quiet",           action="store_true",    dest="quietmode", help="",default=False)
152*b1cdbd2cSJim Jagielski        (self._options, self.args) = parser.parse_args()
153*b1cdbd2cSJim Jagielski
154*b1cdbd2cSJim Jagielski        # -l "de,pr,pt-BR" => [ "de" , "pt" , "pt-BR" ]
155*b1cdbd2cSJim Jagielski        parse_complex_arg = lambda arg: arg.split(",")
156*b1cdbd2cSJim Jagielski
157*b1cdbd2cSJim Jagielski        if self._options.forcedlanguages:
158*b1cdbd2cSJim Jagielski            self._options.forcedlanguages = parse_complex_arg(self._options.forcedlanguages)
159*b1cdbd2cSJim Jagielski        if self._options.languages:
160*b1cdbd2cSJim Jagielski            self._options.languages = parse_complex_arg(self._options.languages)
161*b1cdbd2cSJim Jagielski        self.test_options()
162*b1cdbd2cSJim Jagielski
163*b1cdbd2cSJim Jagielski    def __init__(self):
164*b1cdbd2cSJim Jagielski        self.parse_options()
165*b1cdbd2cSJim Jagielski        if self._options.input_sdf_file != None and len(self._options.input_sdf_file):
166*b1cdbd2cSJim Jagielski            sdfdata = SdfData(self._options.input_sdf_file)
167*b1cdbd2cSJim Jagielski            sdfdata.read()
168*b1cdbd2cSJim Jagielski            self.merge(sdfdata)
169*b1cdbd2cSJim Jagielski        else:
170*b1cdbd2cSJim Jagielski            self.extract()
171*b1cdbd2cSJim Jagielski
172*b1cdbd2cSJim Jagielski    def make_dirs(self, filename):
173*b1cdbd2cSJim Jagielski        dir = filename[:filename.rfind('/')]
174*b1cdbd2cSJim Jagielski        if os.path.exists(dir):
175*b1cdbd2cSJim Jagielski            if os.path.isfile(dir):
176*b1cdbd2cSJim Jagielski                print("ERROR: There is a file '"+dir+"' where I want create a directory")
177*b1cdbd2cSJim Jagielski                sys.exit(-1)
178*b1cdbd2cSJim Jagielski            else:
179*b1cdbd2cSJim Jagielski                return
180*b1cdbd2cSJim Jagielski        else:
181*b1cdbd2cSJim Jagielski            try:
182*b1cdbd2cSJim Jagielski                os.makedirs(dir)
183*b1cdbd2cSJim Jagielski            except IOError:
184*b1cdbd2cSJim Jagielski                print("Error: Can not create dir " + dir)
185*b1cdbd2cSJim Jagielski                sys.exit(-1)
186*b1cdbd2cSJim Jagielski
187*b1cdbd2cSJim Jagielski    def test_options(self):
188*b1cdbd2cSJim Jagielski        opt = self._options
189*b1cdbd2cSJim Jagielski        is_valid = lambda x: x != None and len(x) > 0
190*b1cdbd2cSJim Jagielski        return  is_valid(opt.project_root) and is_valid(opt.project_name) and is_valid(opt.languages) and \
191*b1cdbd2cSJim Jagielski                ( is_valid(opt.inputfile) and (( is_valid(opt.path_prefix) and is_valid(opt.path_postfix) ) or is_valid(opt.outputfile)) and \
192*b1cdbd2cSJim Jagielski                ( ( is_valid(opt.input_sdf_file) and ( is_valid(opt.outputfile) or  ( is_valid(opt.path_prefix) and is_valid(opt.path_postfix) ) or \
193*b1cdbd2cSJim Jagielski                ( is_valid(opt.inputfile) and is_valid(opt.outputFile)) ))))
194*b1cdbd2cSJim Jagielski        print("Strange options ...")
195*b1cdbd2cSJim Jagielski        sys.exit( -1 )
196*b1cdbd2cSJim Jagielski
197*b1cdbd2cSJim Jagielski    def read_inputfile_list(self):
198*b1cdbd2cSJim Jagielski        if self.has_multi_inputfiles():
199*b1cdbd2cSJim Jagielski            lines = []
200*b1cdbd2cSJim Jagielski            try:
201*b1cdbd2cSJim Jagielski                f = open(self._options.inputfile[1:], "r")
202*b1cdbd2cSJim Jagielski                lines = [line.strip('\n') for line in f.readlines()]
203*b1cdbd2cSJim Jagielski            except IOError:
204*b1cdbd2cSJim Jagielski                print("ERROR: Can not read file list " + self._options.inputfile[2:])
205*b1cdbd2cSJim Jagielski                sys.exit(-1)
206*b1cdbd2cSJim Jagielski            else:
207*b1cdbd2cSJim Jagielski                f.close()
208*b1cdbd2cSJim Jagielski            return lines
209*b1cdbd2cSJim Jagielski
210*b1cdbd2cSJim Jagielski    def get_filename_string(self, inputfile):
211*b1cdbd2cSJim Jagielski        absfile = os.path.realpath(os.path.abspath(inputfile))
212*b1cdbd2cSJim Jagielski        absroot = os.path.realpath(os.path.abspath(self._options.project_root))
213*b1cdbd2cSJim Jagielski        return absfile[len(absroot)+1:].replace('/','\\')
214