Monday, August 11, 2008

Translate Properties

This is a tool for translating Java properties files, where there are sources in many languages. I needed to translate some of these properties at work, so I knocked up this script to help me out. And it was pretty useful.

I will not go into the details of the code - you can check it out yourself if you're interested. It's pretty clean as code goes, although not so well commented as per usual.

Also, beware, it's GPL-ed! The full text of the license is available at the GNU site.

Anyway, the sources:
 
1 #!/usr/bin/python
2 #
3 # Copyright 2008 Konrad Siek
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 import os.path;
20 import getopt;
21 import sys;
22
23 sources = [];
24 empty = ["", "*", None];
25 startAt = 1;
26 output = sys.stdout;
27 sort = False;
28 ignore = False;
29 quiet = False;
30
31 def addSource (source):
32 if os.path.exists(source):
33 sources.append(source);
34 elif not quiet:
35 sys.stderr.write("Source: '" + source +
36 "' does not exist. Skipping...\n");
37
38 def setLine (line):
39 global startAt;
40 try:
41 number = int(line);
42 except ValueError:
43 if not quiet:
44 sys.stderr.write("Line value: " + line +
45 " is not a number. Ignoring...\n");
46 return;
47 if number <= 0:
48 if not quiet:
49 sys.stderr.write("Line value: " + line +
50 " must be greater than 0. Ignoring...\n");
51 return;
52 startAt = number;
53 return startAt;
54
55 def setOutput (path):
56 """
57 Sets the file, which will be used throughout the program to
58 write results. If this is not done, the module will write to
59 sys.stdout instead.
60
61 @param path: is the path to the file.
62 """
63 global output;
64 output = open(path, 'w');
65
66 def setSort():
67 global sort;
68 sort = True;
69
70 def setQuiet():
71 global quiet;
72 quiet = True;
73
74 def setIgnore():
75 global ignore;
76 ignore = True;
77
78 def printUsage ():
79 """
80 Print usage information for the script.
81 """
82
83 sys.stdout.write("Usage:\n");
84 sys.stdout.write("\t"+ sys.argv[0]
85 +" -s source [-s source ... ] "
86 +"[-l line ] target\n");
87 sys.stdout.write("\t"+ sys.argv[0] +" -h\n");
88 sys.stdout.write("\t-s source\t\t"
89 +"Select an output file to write to.\n");
90 sys.stdout.write("\t-l line\t\t"
91 +"Select a line in the target to start from.\n");
92 sys.stdout.write("\t-o path\t\t"
93 +"Output results to file, instead of stdout.\n");
94 sys.stdout.write("\t-i \t\t\t"
95 +"Ignore if all sources have no values "
96 +"(or value of '*').\n");
97 sys.stdout.write("\t-a \t\t\t"
98 +"Sort results.\n");
99 sys.stdout.write("\t-q \t\t\t"
100 +"Quiet mode.\n");
101 sys.stdout.write("\t-h \t\t\t"
102 +"Show this message.\n");
103 sys.stdout.write("");
104 sys.exit(0);
105
106 def fileToList(path):
107 file = open(path, 'r');
108 lines = file.readlines();
109 file.close();
110 list = [];
111 counter = 1;
112 for line in lines:
113 if (not line.startswith("#") and (len(line.strip())) > 0):
114 line = line.rstrip("\n");
115 key, separator, value = line.partition("=");
116 if value.find("=") >= 0:
117 sys.stderr.write("Warning: value of line "
118 +str(counter) + " in " + path);
119 sys.stderr.write(" contains an equals sign '=' ("
120 +line + ")\n");
121 list.append((counter, key, value));
122 counter += 1;
123 return list;
124
125 def loadSources():
126 lists = {};
127 for source in sources:
128 lists[source] = fileToList(source);
129 return lists;
130
131 def popByKey(target, list):
132 for i in range(0, len(list)):
133 line, key, value = list[i];
134 if key == target:
135 del list[i];
136 return line, key, value;
137 return None, None, None;
138
139 def dialog(property, sources):
140 sys.stdout.write(property+"\n");
141 for source, line, value in sources:
142 sys.stdout.write("\t"+source+"\t("+str(line)+"): "
143 +str(value)+"\n");
144 sys.stdout.write("$("+property+"):");
145
146 def save(results):
147 if sort:
148 results.sort();
149
150 printout(results);
151
152 def control(property, value, results):
153 line = sys.stdin.readline().strip();
154 if line == "=stop":
155 save(results);
156 exit(0);
157 elif line == "=skip":
158 pass
159 elif line == "":
160 results.append((property, value));
161 pass
162 else:
163 results.append((property, line));
164
165 def printout(list):
166 global output;
167 for key, value in list:
168 output.write(key+"="+value+"\n");
169
170 def outOfScope(target, list):
171 for i in range(0, len(list)):
172 line, key, value = list[i];
173 if key == target:
174 return (line < startAt);
175 return False;
176
177 def absent(occurances):
178 for key, line, value in occurances:
179 if empty.count(value.strip()) == 0:
180 return False;
181 return True;
182
183 def parse (target):
184 sources = loadSources();
185 workspace = fileToList(target);
186 results = [];
187
188 # Translate the lines, which were in the target
189 for line, key, value in workspace:
190 if line < startAt:
191 continue;
192 occurances = [(target, line, value)];
193 for s in sources:
194 list = sources[s];
195 l, k, v = popByKey(key, list);
196 occurances.append((s, l, v));
197 if ignore and absent(occurances):
198 continue;
199 dialog(key, occurances);
200 control(key, value, results);
201
202 # Translate the lines, which were in the sources, but not the target
203 for source in sources:
204 for line, key, value in sources[source]:
205 if not outOfScope(key, workspace):
206 occurances = [(target, line, value)];
207 for s in sources:
208 if source != s:
209 list = sources[s];
210 l, k, v = popByKey(key, list);
211 occurances.append((s, l, v));
212 dialog(key, occurances);
213 control(key, results);
214
215 save(results);
216
217 def resolveOptions (optionHandling, argumentHandler):
218 """
219 Handles all the options and parameters for the script with the
220 provided functions.
221
222 @param optionHandling: a dictionary, translating an option string
223 to a function.Depending on whether the function is parameterless
224 or has one parameter the option string will be just a letter or a
225 letter ending in a colon.
226
227 @param argumentHandler: a function used to handle all the arguments
228 - it takes one parameter.
229 """
230
231 string = "".join(["%s" % (i) for i in optionHandling.keys()]);
232 options, arguments = getopt.getopt(sys.argv[1:], string);
233
234 # Handle options.
235 for key, value in options :
236 if value != '':
237 optionHandling[key[1:]+":"](value);
238 else:
239 optionHandling[key[1:]]();
240
241 # Handle arguments.
242 if len(arguments) > 0 :
243 for argument in arguments:
244 argumentHandler(argument);
245
246 if __name__ == "__main__":
247 options = {"s:": addSource,
248 "h": printUsage,
249 "l:": setLine,
250 "o:": setOutput,
251 "a": setSort,
252 "q": setQuiet,
253 "i": setIgnore}
254
255 resolveOptions(options, parse);


The code is also available at GitHub as python/translate_properties.py.

No comments: