»Core Development>Code coverage>Tools/i18n/msgfmt.py

Python code coverage for Tools/i18n/msgfmt.py

1n/a#! /usr/bin/env python3
2n/a# Written by Martin v. Löwis <loewis@informatik.hu-berlin.de>
4n/a"""Generate binary message catalog from textual translation description.
6n/aThis program converts a textual Uniforum-style message catalog (.po file) into
7n/aa binary GNU catalog (.mo file). This is essentially the same function as the
8n/aGNU msgfmt program, however, it is a simpler implementation.
10n/aUsage: msgfmt.py [OPTIONS] filename.po
13n/a -o file
14n/a --output-file=file
15n/a Specify the output file to write to. If omitted, output will go to a
16n/a file named filename.mo (based off the input file name).
18n/a -h
19n/a --help
20n/a Print this message and exit.
22n/a -V
23n/a --version
24n/a Display version information and exit.
27n/aimport os
28n/aimport sys
29n/aimport ast
30n/aimport getopt
31n/aimport struct
32n/aimport array
33n/afrom email.parser import HeaderParser
35n/a__version__ = "1.1"
37n/aMESSAGES = {}
41n/adef usage(code, msg=''):
42n/a print(__doc__, file=sys.stderr)
43n/a if msg:
44n/a print(msg, file=sys.stderr)
45n/a sys.exit(code)
49n/adef add(id, str, fuzzy):
50n/a "Add a non-fuzzy translation to the dictionary."
51n/a global MESSAGES
52n/a if not fuzzy and str:
53n/a MESSAGES[id] = str
57n/adef generate():
58n/a "Return the generated output."
59n/a global MESSAGES
60n/a # the keys are sorted in the .mo file
61n/a keys = sorted(MESSAGES.keys())
62n/a offsets = []
63n/a ids = strs = b''
64n/a for id in keys:
65n/a # For each string, we need size and file offset. Each string is NUL
66n/a # terminated; the NUL does not count into the size.
67n/a offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id])))
68n/a ids += id + b'\0'
69n/a strs += MESSAGES[id] + b'\0'
70n/a output = ''
71n/a # The header is 7 32-bit unsigned integers. We don't use hash tables, so
72n/a # the keys start right after the index tables.
73n/a # translated string.
74n/a keystart = 7*4+16*len(keys)
75n/a # and the values start after the keys
76n/a valuestart = keystart + len(ids)
77n/a koffsets = []
78n/a voffsets = []
79n/a # The string table first has the list of keys, then the list of values.
80n/a # Each entry has first the size of the string, then the file offset.
81n/a for o1, l1, o2, l2 in offsets:
82n/a koffsets += [l1, o1+keystart]
83n/a voffsets += [l2, o2+valuestart]
84n/a offsets = koffsets + voffsets
85n/a output = struct.pack("Iiiiiii",
86n/a 0x950412de, # Magic
87n/a 0, # Version
88n/a len(keys), # # of entries
89n/a 7*4, # start of key index
90n/a 7*4+len(keys)*8, # start of value index
91n/a 0, 0) # size and offset of hash table
92n/a output += array.array("i", offsets).tostring()
93n/a output += ids
94n/a output += strs
95n/a return output
99n/adef make(filename, outfile):
100n/a ID = 1
101n/a STR = 2
103n/a # Compute .mo name from .po name and arguments
104n/a if filename.endswith('.po'):
105n/a infile = filename
106n/a else:
107n/a infile = filename + '.po'
108n/a if outfile is None:
109n/a outfile = os.path.splitext(infile)[0] + '.mo'
111n/a try:
112n/a lines = open(infile, 'rb').readlines()
113n/a except IOError as msg:
114n/a print(msg, file=sys.stderr)
115n/a sys.exit(1)
117n/a section = None
118n/a fuzzy = 0
120n/a # Start off assuming Latin-1, so everything decodes without failure,
121n/a # until we know the exact encoding
122n/a encoding = 'latin-1'
124n/a # Parse the catalog
125n/a lno = 0
126n/a for l in lines:
127n/a l = l.decode(encoding)
128n/a lno += 1
129n/a # If we get a comment line after a msgstr, this is a new entry
130n/a if l[0] == '#' and section == STR:
131n/a add(msgid, msgstr, fuzzy)
132n/a section = None
133n/a fuzzy = 0
134n/a # Record a fuzzy mark
135n/a if l[:2] == '#,' and 'fuzzy' in l:
136n/a fuzzy = 1
137n/a # Skip comments
138n/a if l[0] == '#':
139n/a continue
140n/a # Now we are in a msgid section, output previous section
141n/a if l.startswith('msgid') and not l.startswith('msgid_plural'):
142n/a if section == STR:
143n/a add(msgid, msgstr, fuzzy)
144n/a if not msgid:
145n/a # See whether there is an encoding declaration
146n/a p = HeaderParser()
147n/a charset = p.parsestr(msgstr.decode(encoding)).get_content_charset()
148n/a if charset:
149n/a encoding = charset
150n/a section = ID
151n/a l = l[5:]
152n/a msgid = msgstr = b''
153n/a is_plural = False
154n/a # This is a message with plural forms
155n/a elif l.startswith('msgid_plural'):
156n/a if section != ID:
157n/a print('msgid_plural not preceded by msgid on %s:%d' % (infile, lno),
158n/a file=sys.stderr)
159n/a sys.exit(1)
160n/a l = l[12:]
161n/a msgid += b'\0' # separator of singular and plural
162n/a is_plural = True
163n/a # Now we are in a msgstr section
164n/a elif l.startswith('msgstr'):
165n/a section = STR
166n/a if l.startswith('msgstr['):
167n/a if not is_plural:
168n/a print('plural without msgid_plural on %s:%d' % (infile, lno),
169n/a file=sys.stderr)
170n/a sys.exit(1)
171n/a l = l.split(']', 1)[1]
172n/a if msgstr:
173n/a msgstr += b'\0' # Separator of the various plural forms
174n/a else:
175n/a if is_plural:
176n/a print('indexed msgstr required for plural on %s:%d' % (infile, lno),
177n/a file=sys.stderr)
178n/a sys.exit(1)
179n/a l = l[6:]
180n/a # Skip empty lines
181n/a l = l.strip()
182n/a if not l:
183n/a continue
184n/a l = ast.literal_eval(l)
185n/a if section == ID:
186n/a msgid += l.encode(encoding)
187n/a elif section == STR:
188n/a msgstr += l.encode(encoding)
189n/a else:
190n/a print('Syntax error on %s:%d' % (infile, lno), \
191n/a 'before:', file=sys.stderr)
192n/a print(l, file=sys.stderr)
193n/a sys.exit(1)
194n/a # Add last entry
195n/a if section == STR:
196n/a add(msgid, msgstr, fuzzy)
198n/a # Compute output
199n/a output = generate()
201n/a try:
202n/a open(outfile,"wb").write(output)
203n/a except IOError as msg:
204n/a print(msg, file=sys.stderr)
208n/adef main():
209n/a try:
210n/a opts, args = getopt.getopt(sys.argv[1:], 'hVo:',
211n/a ['help', 'version', 'output-file='])
212n/a except getopt.error as msg:
213n/a usage(1, msg)
215n/a outfile = None
216n/a # parse options
217n/a for opt, arg in opts:
218n/a if opt in ('-h', '--help'):
219n/a usage(0)
220n/a elif opt in ('-V', '--version'):
221n/a print("msgfmt.py", __version__)
222n/a sys.exit(0)
223n/a elif opt in ('-o', '--output-file'):
224n/a outfile = arg
225n/a # do it
226n/a if not args:
227n/a print('No input file given', file=sys.stderr)
228n/a print("Try `msgfmt --help' for more information.", file=sys.stderr)
229n/a return
231n/a for filename in args:
232n/a make(filename, outfile)
235n/aif __name__ == '__main__':
236n/a main()