Update years of packaging copyright.
[debian/jabref.git] / syncLang.py
1 import sys
2 import os
3 import re
4
5 keyFiles = {}
6
7 # Builds a list of all translation keys in the list of lines.
8 def indexFile(lines):
9     allKeys = []
10     for line in lines:
11         comment = line.find("#")
12         if (comment != 0):
13             index = line.find("=")
14             while ((index > 0) and (line[index-1]=="\\")):
15                 index = line.find("=", index+1)
16             if (index > 0):
17                 allKeys.append(line[0:index])
18     allKeys.sort()
19     return allKeys
20         
21
22 # Finds all keys in the first list that are not present in the second list:
23 def findMissingKeys(first, second):
24     missing = []
25     for key in first:
26         if not key in second:
27             missing.append(key)
28     return missing
29
30
31 # Appends all the given keys to the file:
32 def appendMissingKeys(filename, keys):
33     file = open(filename, "a")
34     file.write("\n")
35     for key in keys:
36         file.write(key+"=\n")
37
38 def handleFileSet(mainFile, files, changeFiles):
39     f1 = open(mainFile)
40     lines = f1.readlines()
41     f1.close()
42     keys = indexFile(lines)
43     keysPresent = []
44     for i in range(0, len(files)):
45         f2 = open(files[i])
46         lines = f2.readlines()
47         f2.close()
48         keysPresent.append(indexFile(lines))
49         missing = findMissingKeys(keys, keysPresent[i])
50         
51         print "\n\nFile '"+files[i]+"'\n"
52         if len(missing) == 0:
53             print "----> No missing keys."
54         else:
55             print "----> Missing keys:"
56             for key in missing:
57                 print key
58
59             if changeFiles == 1:
60                 print "Update file?",
61                 if raw_input() in ['y', 'Y']:
62                     appendMissingKeys(files[i], missing)
63             print ""
64         
65         # See if this file has keys that are not in the main file:
66         redundant = findMissingKeys(keysPresent[i], keys)
67         if len(redundant) > 0:
68             print "----> Possible obsolete keys (not in English language file):"
69             for key in redundant:
70                 print key
71                 
72             #if changeFiles == 1:
73             #   print "Update file?",
74             #   if raw_input() in ['y', 'Y']:
75             #       removeRedundantKeys(files[i], redundant)
76             print ""
77         
78 def handleJavaCode(filename, lines, keyList, notTermList):
79     #Extract first string parameter from Globals.lang call. E.g., Globals.lang("Default")
80     reOnlyString = r'"((\\"|[^"])*)"[^"]*'
81     patt = re.compile(r'Globals\s*\.\s*lang\s*\(\s*' + reOnlyString)
82     #second pattern as Mr Dlib contribution indirectly uses Global.lang
83     patta = re.compile(r'LocalizationSupport.message\(' + reOnlyString)
84     pattOnlyString = re.compile(reOnlyString)
85
86     #Find multiline Globals lang statements. E.g.:
87     #Globals.lang("This is my string" +
88     # "with a long text")
89     patt2 = re.compile(r'Globals\s*\.\s*lang\s*\(([^)])*$')
90
91     pattPlus = re.compile(r'^\s*\+')
92
93     eList = list(enumerate(lines.split("\n")))
94     i = 0
95     while i < len(eList):
96         linenum, curline = eList[i]
97         
98         #Remove Java single line comments
99         if curline.find("http://") < 0:
100             curline = re.sub("//.*", "", curline)
101
102         while (curline != ""):
103             result = patt.search(curline)
104             if (not result):
105                 result = patta.search(curline)
106             result2 = patt2.search(curline)
107
108             found = ""
109
110             if result2 and curline.find('",') < 0:
111                 # not terminated
112                 # but it could be a multiline string
113                 if result:
114                     curText = result.group(1)
115                     searchForPlus = True
116                 else:
117                     curText = ""
118                     searchForPlus = False
119                 origI = i
120                 #inspect next line
121                 while i+1 < len(eList):
122                     linenum2, curline2 = eList[i+1]
123                     if (not searchForPlus) or pattPlus.search(curline2):
124                         #from now on, we always have to search for a plus
125                         searchForPlus = True
126
127                         #The current line has been handled here, therefore indicate to handle the next line
128                         i = i+1
129                         linenum = linenum2
130                         curline = curline2
131
132                         #Search for the occurence of a string
133                         result = pattOnlyString.search(curline2)
134                         if result:
135                             curText = curText + result.group(1)
136                             #check for several strings in this line
137                             if curline2.count('\"') > 2:
138                                 break
139                             #check for several arguments in the line
140                             if curline2.find('",') > 0:
141                                 break
142                             if curline2.endswith(")"):
143                                 break
144                         else:
145                             #plus sign at the beginning found, but no string
146                             break
147                     else:
148                         #no continuation found
149                         break
150
151                 if origI == i:
152                     print "%s:%d: Not terminated: %s"%(filename, linenum+1, curline)
153                 else:
154                     found = curText
155
156             if result or (found != ""):
157                 if (found == ""):
158                     #not a multiline string, found via the single line matching
159                     #full string in one line
160                     found = result.group(1)
161
162                 found = found.replace(" ", "_")
163                 #replace characters that need to be escaped in the language file
164                 found = found.replace("=", r"\=").replace(":",r"\:")
165                 #replace Java-escaped " to plain "
166                 found = found.replace(r'\"','"')
167                 #Java-escaped \ to plain \ need not to be converted - they have to be kept
168                 #e.g., "\\#" has to be contained as "\\#" in the key
169                 #found = found.replace('\\\\','\\')
170                 if (found != "") and (found not in keyList):
171                     keyList.append(found)
172                     keyFiles[found] = (filename, linenum)
173                     #print "Adding ", found
174                 #else:
175                 #   print "Not adding: "+found
176                     
177             #Prepare a possible second run (multiple Globals.lang on this line)
178             if result:
179                 lastPos = result.span()[1]
180                 #regular expression is greedy. It will match until Globals.lang("
181                 #therefore, we have to adjust lastPos
182                 lastPos = lastPos - 14
183                 if len(curline) <= lastPos:
184                     curline = ""
185                 else:
186                     curline = curline[lastPos:]
187             else:
188                 #terminate processing of this line, continue to next line
189                 curline = ""
190                 
191         i = i+1
192             
193 # Find all Java source files in the given directory, and read the lines of each,
194 # calling handleJavaCode on the contents:   
195 def handleDir(lists, dirname, fnames):
196     keyList, notTermList = lists    
197     for file in fnames:
198         if len(file) > 6 and file[(len(file)-5):len(file)] == ".java":
199             fl = open(dirname+os.sep+file)
200             lines = fl.read()
201             fl.close()
202             handleJavaCode(dirname + os.sep + file, lines, keyList, notTermList)
203             
204 # Go through subdirectories and call handleDir on all diroctories:                      
205 def traverseFileTree(dir):
206     keyList = []
207     notTermList = []
208     os.path.walk(dir, handleDir, (keyList, notTermList))
209     print "Keys found: "+str(len(keyList))
210     return keyList
211
212     
213 # Searches out all translation calls in the Java source files, and reports which
214 # are not present in the given resource file.
215 #
216 # arg: mainFile: a .properties file with the keys to sync with
217 def findNewKeysInJavaCode(mainFile, dir, update):
218     keystempo = []
219     keyListtempo = []
220     f1 = open(mainFile)
221     lines = f1.readlines()
222     f1.close()
223     keys = indexFile(lines)
224     keyList = traverseFileTree(dir)
225    
226     # Open the file again, for appending:
227     if update:
228         f1 = open(mainFile, "a")
229         f1.write("\n")
230         
231     # Look for keys that are used in the code, but not present in the language file:
232     for key in keyList:
233         value = key.replace("\\:",":").replace("\\=", "=")
234         if key not in keys:
235             fileName, lineNum = keyFiles[key]
236             print "%s:%i:Missing key: %s"%(fileName, lineNum + 1, value)
237             if update:
238                 f1.write(key+"="+value+"\n")
239     
240     # Look for keys in the language file that are not used in the code:
241     for key in keys:
242         if key not in keyList:
243             print "Possible obsolete key: "+key
244         
245     if update:
246         f1.close()
247     
248     
249 def lookForDuplicates(file, displayKeys):
250     duplicount = 0
251     f1 = open(file)
252     lines = f1.readlines()
253     f1.close()
254     mappings = {}
255     emptyVals = 0
256     for line in lines:
257         comment = line.find("#")
258         index = line.find("=")
259         if (comment != 0) and (index > 0):
260             key = line[0:index]
261             value = line[index+1:].strip()
262             if key in mappings:
263                 mappings[key].append(value)
264                 duplicount += 1
265                 if displayKeys:
266                   print "Duplicate: "+file+": "+key+" =",
267                   print mappings[key]
268             else:
269                 mappings[key] = [value]
270                 if value == "":
271                     emptyVals = emptyVals + 1
272                     if displayKeys:
273                       print "Empty value: "+file+": "+key
274                 #print "New: "+value
275     if duplicount > 0:
276         dupstring = str(duplicount)+" duplicates. "
277     else:
278         dupstring = ""
279     if emptyVals > 0:
280         emptStr = str(emptyVals)+" empty values. "
281     else:
282         emptStr = ""
283     if duplicount+emptyVals > 0:
284         okString = ""
285     else:
286         okString = "ok"
287     print file+": "+dupstring+emptStr+okString
288     #print file+": "+str(emptyVals)+" empty values."
289         
290         
291 ############# Main part ###################
292
293 if len(sys.argv) == 1:
294     print """This program must be run from the jabref base directory.
295     
296 Usage: syncLang.py option   
297 Option can be one of the following:
298  
299     -c: Search the language files for empty and duplicate translations. Display only
300         counts for duplicated and empty values in each language file.
301
302     -d: Search the language files for empty and duplicate translations. 
303         For each duplicate set found, a list will be printed showing the various 
304         translations for the same key. There is currently to option to remove duplicates
305         automatically.
306         
307     -s [-u]: Search the Java source files for language keys. All keys that are found in the source files
308         but not in "JabRef_en.properties" are listed. If the -u option is specified, these keys will
309         automatically be added to "JabRef_en.properties".
310         
311         The program will also list "Not terminated" keys. These are keys that are concatenated over 
312         more than one line, that the program is not (currently) able to resolve.
313         
314         Finally, the program will list "Possible obsolete keys". These are keys that are present in
315         "JabRef_en.properties", but could not be found in the Java source code. Note that the 
316         "Not terminated" keys will be likely to appear here, since they were not resolved.
317         
318     -t [-u]: Compare the contents of "JabRef_en.properties" and "Menu_en.properties" against the other 
319         language files. The program will list for all the other files which keys from the English
320         file are missing. Additionally, the program will list keys in the other files which are
321         not present in the English file - possible obsolete keys.
322         
323         If the -u option is specified, all missing keys will automatically be added to the files.
324         There is currently no option to remove obsolete keys automatically.
325 """
326     
327 elif (len(sys.argv) >= 2) and (sys.argv[1] == "-s"):
328     if (len(sys.argv) >= 3) and (sys.argv[2] == "-u"):
329         update = 1
330     else:
331         update = 0
332     findNewKeysInJavaCode("src/main/resources/resource/JabRef_en.properties", ".", update)
333     
334 elif (len(sys.argv) >= 2) and (sys.argv[1] == "-t"):
335     if (len(sys.argv) >= 3) and (sys.argv[2] == "-u"):
336         changeFiles = 1
337     else:
338         changeFiles = 0
339
340     filesJabRef = filter(lambda s: (s.startswith('JabRef_') and not (s.startswith('JabRef_en'))), os.listdir("src/main/resources/resource"));
341     filesJabRef = ["src/main/resources/resource/" + i for i in filesJabRef];
342     filesMenu = filter(lambda s: (s.startswith('Menu_') and not (s.startswith('Menu_en'))), os.listdir("src/main/resources/resource"));
343     filesMenu = ["src/main/resources/resource/" + i for i in filesMenu];
344
345     handleFileSet("src/main/resources/resource/JabRef_en.properties", filesJabRef, changeFiles)
346     handleFileSet("src/main/resources/resource/Menu_en.properties", filesMenu, changeFiles)
347
348 elif (len(sys.argv) >= 2) and ((sys.argv[1] == "-d") or (sys.argv[1] == "-c")):
349     files = filter(lambda s: (s.startswith('JabRef_') and not (s.startswith('JabRef_en'))), os.listdir("src/main/resources/resource"));
350     files.extend(filter(lambda s: (s.startswith('Menu_') and not (s.startswith('Menu_en'))), os.listdir("src/main/resources/resource")));
351     files = ["src/main/resources/resource/" + i for i in files];
352     for file in files:
353         lookForDuplicates(file, sys.argv[1] == "-d")