mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-27 12:22:52 +00:00
280 lines
8.9 KiB
Python
280 lines
8.9 KiB
Python
![]() |
#!/usr/bin/env python
|
||
|
# -*- coding: UTF-8 -*-
|
||
|
|
||
|
# Extract the PO template of XScreenSaver's hacks configurations.
|
||
|
# Provide the target PO template path and one or more XScreenSaver
|
||
|
# source distribution packages. For example:
|
||
|
#
|
||
|
# cd $SOMEWHERE/kscreensaver/kxsconfig
|
||
|
# ./create-hacks-pot.py hacks.pot $OTHERWHERE/xscreensaver-*.tar.gz
|
||
|
#
|
||
|
# The created hacks.pot file in the current working directory
|
||
|
# should then be committed.
|
||
|
# Try to support last 3 years of XScreenSaver releases.
|
||
|
#
|
||
|
# Chusslove Illich <caslav.ilic@gmx.net>
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import glob
|
||
|
import re
|
||
|
import shutil
|
||
|
import tarfile
|
||
|
import xml.parsers.expat
|
||
|
|
||
|
|
||
|
def main ():
|
||
|
|
||
|
if len(sys.argv) < 3:
|
||
|
error("Usage: %s hacks.pot xscreensaver-X.Y.tar.gz..."
|
||
|
% os.path.basename(sys.argv[0]))
|
||
|
potpath = sys.argv[1]
|
||
|
xsdistpaths = sys.argv[2:]
|
||
|
|
||
|
xcfgex = XSCfgExtractor()
|
||
|
xcfgex.extract(potpath, xsdistpaths)
|
||
|
|
||
|
|
||
|
def error (msg):
|
||
|
|
||
|
print "%s [error]: %s" % (os.path.basename(sys.argv[0]), msg)
|
||
|
sys.exit(1)
|
||
|
|
||
|
|
||
|
class XSCfgExtractor:
|
||
|
|
||
|
def __init__ (self):
|
||
|
|
||
|
pass
|
||
|
|
||
|
|
||
|
def _prepare_xml_parser (self, relpath, releaseid):
|
||
|
|
||
|
self._parser = xml.parsers.expat.ParserCreate()
|
||
|
self._parser.UseForeignDTD() # to ignore non-default XML entities
|
||
|
|
||
|
self._parser.StartElementHandler = self._handler_start_element
|
||
|
self._parser.EndElementHandler = self._handler_end_element
|
||
|
self._parser.CharacterDataHandler = self._handler_character_data
|
||
|
|
||
|
# Data structures used in handlers
|
||
|
self._element_stack = []
|
||
|
self._releaseid = releaseid
|
||
|
self._relpath = relpath
|
||
|
self._chardata = []
|
||
|
|
||
|
return self._parser
|
||
|
|
||
|
|
||
|
def extract (self, potpath, distpaths):
|
||
|
|
||
|
# Extract messages from all source packages.
|
||
|
self.msgs = []
|
||
|
tmpdir = "./xscreensaver-unpacked"
|
||
|
if os.path.exists(tmpdir):
|
||
|
shutil.rmtree(tmpdir)
|
||
|
os.mkdir(tmpdir)
|
||
|
|
||
|
for distpath in distpaths:
|
||
|
# Extract release ID from the path.
|
||
|
try:
|
||
|
distbase = os.path.basename(distpath)
|
||
|
p3 = distbase.rfind(".tar")
|
||
|
p2 = distbase.rfind(".", 0, p3 - 1)
|
||
|
p1 = distbase.rfind("-", 0, p2 - 1)
|
||
|
majver = int(distbase[p1 + 1:p2])
|
||
|
minver = int(distbase[p2 + 1:p3])
|
||
|
except:
|
||
|
error("Archive file name '%s' not in the form '%s'."
|
||
|
% (distpath, "*-X.Y.tar.*"))
|
||
|
releaseid = (majver, minver)
|
||
|
|
||
|
# Unpack the source package and collect paths for extraction.
|
||
|
tar = tarfile.open(distpath)
|
||
|
tar.extractall(tmpdir)
|
||
|
tar.close()
|
||
|
srcdir = glob.glob(os.path.join(tmpdir, "*"))
|
||
|
if len(srcdir) != 1:
|
||
|
error("There should be only one top-level item "
|
||
|
"in archive '%s'." % distpath)
|
||
|
srcdir = srcdir[0]
|
||
|
if not os.path.isdir(srcdir):
|
||
|
error("The top-level item in archive '%s' "
|
||
|
"is not a directory." % distpath)
|
||
|
cfgdir = os.path.join(srcdir, "hacks", "config")
|
||
|
cfgpaths = glob.glob(os.path.join(cfgdir, "*.xml"))
|
||
|
cfgpaths.sort()
|
||
|
|
||
|
# Extract messages from collected paths.
|
||
|
for cfgpath in cfgpaths:
|
||
|
|
||
|
# Make the path relative to source directory.
|
||
|
relpath = cfgpath.replace(srcdir, "", 1)
|
||
|
if relpath.startswith(os.path.sep):
|
||
|
relpath = relpath[len(os.path.sep):]
|
||
|
|
||
|
# Parse file.
|
||
|
p = self._prepare_xml_parser(relpath, releaseid)
|
||
|
fh = open(cfgpath, "r")
|
||
|
p.ParseFile(fh)
|
||
|
fh.close()
|
||
|
|
||
|
shutil.rmtree(srcdir)
|
||
|
|
||
|
shutil.rmtree(tmpdir)
|
||
|
|
||
|
# Group messages by keys, preserving order of appearance.
|
||
|
# Add paths only from the latest release where the message appears.
|
||
|
self.msgs.sort(key=lambda m: [-v for v in m[4]])
|
||
|
# ...sort descending by release ID, stable.
|
||
|
msg_keys = []
|
||
|
msgs_by_key = {}
|
||
|
newest_releaseid = (0, 0)
|
||
|
for msgctxt, msgid, path, lno, releaseid in self.msgs:
|
||
|
msgkey = "%s\x04%s" % (msgctxt, msgid)
|
||
|
msg = msgs_by_key.get(msgkey)
|
||
|
if msg is None:
|
||
|
msg = (msgctxt, msgid, [], releaseid)
|
||
|
msgs_by_key[msgkey] = msg
|
||
|
msg_keys.append(msgkey)
|
||
|
if releaseid == msg[3]:
|
||
|
msg[2].append((path, lno))
|
||
|
newest_releaseid = max(newest_releaseid, releaseid)
|
||
|
|
||
|
# Format messages into lines.
|
||
|
msglines = []
|
||
|
for msg in [msgs_by_key[x] for x in msg_keys]:
|
||
|
msgctxt, msgid, srcrefs, releaseid = msg
|
||
|
if releaseid != newest_releaseid:
|
||
|
msglines.append("#. last-release: %d.%d\n" % releaseid)
|
||
|
if srcrefs:
|
||
|
srcrefstrs = ["%s:%s" % x for x in srcrefs]
|
||
|
msglines.append("#: %s\n" % " ".join(srcrefstrs))
|
||
|
if msgctxt is not None:
|
||
|
msglines.append("msgctxt \"%s\"\n" % poescape(msgctxt))
|
||
|
msglines.append("msgid \"%s\"\n" % poescape(msgid))
|
||
|
msglines.append("msgstr \"\"\n")
|
||
|
msglines.append("\n")
|
||
|
|
||
|
# Write POT file.
|
||
|
fh = open(potpath, "w")
|
||
|
fh.writelines([x.encode("utf-8") for x in msglines])
|
||
|
fh.close()
|
||
|
|
||
|
|
||
|
def _handler_start_element (self, name, attrs):
|
||
|
|
||
|
self._element_stack.append((name, attrs))
|
||
|
self._startel_lno = self._parser.CurrentLineNumber
|
||
|
|
||
|
releaseid = self._releaseid
|
||
|
path = self._relpath
|
||
|
lno = self._startel_lno
|
||
|
|
||
|
if 0: pass
|
||
|
|
||
|
elif name == "screensaver":
|
||
|
msgid = attrs.get("_label")
|
||
|
if msgid:
|
||
|
ctxt = "@item screen saver name"
|
||
|
self.msgs.append((ctxt, msgid, path, lno, releaseid))
|
||
|
self._ssname_lno = lno
|
||
|
self._ssname_pos = len(self.msgs) - 1
|
||
|
|
||
|
elif name == "_description":
|
||
|
self._chardata = []
|
||
|
# Message created on end element.
|
||
|
|
||
|
elif name == "number":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
ntype = attrs.get("type")
|
||
|
ctxt = None
|
||
|
if ntype == "slider":
|
||
|
ctxt = "@label:slider"
|
||
|
elif ntype == "spinbutton":
|
||
|
ctxt = "@label:spinbox"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
for limattr in ("_low-label", "_high-label"):
|
||
|
limlabel = attrs.get(limattr)
|
||
|
if limlabel:
|
||
|
if label:
|
||
|
ctxt = "@item:inrange %s" % label
|
||
|
else:
|
||
|
ctxt = "@item:inrange"
|
||
|
self.msgs.append((ctxt, limlabel, path, lno, releaseid))
|
||
|
|
||
|
elif name == "boolean":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
ctxt = "@option:check"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
|
||
|
elif name == "string":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
ctxt = "@label:textbox"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
|
||
|
elif name == "file":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
ctxt = "@label:chooser"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
|
||
|
elif name == "select":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
ctxt = "@title:group"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
|
||
|
elif name == "option":
|
||
|
label = attrs.get("_label")
|
||
|
if label:
|
||
|
prevlabel = self._element_stack[-2][1].get("_label")
|
||
|
if prevlabel:
|
||
|
ctxt = "@option:radio %s" % prevlabel
|
||
|
else:
|
||
|
ctxt = "@option:radio"
|
||
|
self.msgs.append((ctxt, label, path, lno, releaseid))
|
||
|
|
||
|
|
||
|
#print name, attrs, self.parser.CurrentLineNumber
|
||
|
|
||
|
|
||
|
def _handler_end_element (self, name):
|
||
|
|
||
|
name, attr = self._element_stack.pop()
|
||
|
releaseid = self._releaseid
|
||
|
path = self._relpath
|
||
|
lno = self._startel_lno
|
||
|
|
||
|
if name == "_description":
|
||
|
text = "".join(self._chardata)
|
||
|
rx = re.compile(r"\s+", re.U)
|
||
|
text = rx.sub(" ", text.strip())
|
||
|
ctxt = "@info screen saver description"
|
||
|
if text:
|
||
|
# Fudge position of the message,
|
||
|
# to have description right after the name in the PO file.
|
||
|
msg = (ctxt, text, path, self._ssname_lno, releaseid)
|
||
|
self.msgs.insert(self._ssname_pos + 1, msg)
|
||
|
|
||
|
|
||
|
def _handler_character_data (self, text):
|
||
|
|
||
|
self._chardata.append(text)
|
||
|
|
||
|
|
||
|
def poescape (text):
|
||
|
|
||
|
text = text.replace("\"", "\\\"")
|
||
|
text = text.replace("\n", "\\n")
|
||
|
text = text.replace("\t", "\\t")
|
||
|
|
||
|
return text
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|