Skip to content

Commit 2e35a31

Browse files
committed
More unicode enhancements
1 parent ebbcef5 commit 2e35a31

File tree

9 files changed

+480
-67
lines changed

9 files changed

+480
-67
lines changed

default.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
testdebug = False # TODO: check
2323
testTasks = False # TODO: check
2424
branch = 'master'
25-
build = '1015'
25+
build = '1016'
2626

2727
from resources.lib.utils.debugger import startdebugger
2828

@@ -134,8 +134,8 @@ def start():
134134

135135

136136
def main():
137-
xbmc.log(msg=_('$$$ [kodi.callbacks] - Staring kodi.callbacks ver: %s (build %s)') % (str(_addonversion_), build),
138-
level=xbmc.LOGNOTICE)
137+
msg = _(u'$$$ [kodi.callbacks] - Staring kodi.callbacks ver: %s (build %s)').encode('utf-8') % (str(_addonversion_), build)
138+
xbmc.log(msg=msg, level=xbmc.LOGNOTICE)
139139
if branch != 'master':
140140
xbmcaddon.Addon().setSetting('installed branch', branch)
141141
start()

resources/lib/nongit/poutil_old.py

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright (C) 2016 KenV99
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
20+
import os
21+
import codecs
22+
import fnmatch
23+
import re
24+
import operator
25+
import xbmcaddon
26+
import threading
27+
import copy
28+
from resources.lib.utils.kodipathtools import translatepath
29+
from resources.lib.kodilogging import KodiLogger
30+
klogger = KodiLogger()
31+
log = klogger.log
32+
33+
try:
34+
addonid = xbmcaddon.Addon().getAddonInfo('id')
35+
except RuntimeError:
36+
addonid = 'script.service.kodi.callbacks'
37+
if addonid == '':
38+
addonid = 'script.service.kodi.callbacks'
39+
40+
class KodiPo(object):
41+
42+
_instance = None
43+
_lock = threading.Lock()
44+
45+
def __new__(cls):
46+
if KodiPo._instance is None:
47+
with KodiPo._lock:
48+
if KodiPo._instance is None:
49+
KodiPo._instance = super(KodiPo, cls).__new__(cls)
50+
KodiPo.cls_init()
51+
return KodiPo._instance
52+
53+
@classmethod
54+
def cls_init(cls):
55+
cls.pofn = translatepath('special://addon/resources/language/English/strings.po')
56+
cls.isStub = xbmcaddon.Addon().getAddonInfo('id') == ''
57+
cls.podict = PoDict()
58+
cls.podict.read_from_file(cls.pofn)
59+
cls.updateAlways = False
60+
61+
62+
def __init__(self):
63+
KodiPo._instance = self
64+
65+
def _(self, strToId, update=False):
66+
self.getLocalizedString(strToId, update)
67+
68+
def getLocalizedString(self, strToId, update=False):
69+
idFound, strid = self.podict.has_msgid(strToId)
70+
if idFound:
71+
if self.podict.savethread.is_alive():
72+
self.podict.savethread.join()
73+
ret = xbmcaddon.Addon(addonid).getLocalizedString(int(strid))
74+
if ret == u'': # Occurs with stub or undefined number
75+
if not self.isStub:
76+
log(_('Localized string not found for: [%s]') % str(strToId))
77+
ret = strToId
78+
return ret
79+
else:
80+
if update is True or self.updateAlways is True:
81+
82+
log(msg=_('Localized string added to po for: [%s]') % strToId)
83+
self.updatePo(strid, strToId)
84+
else:
85+
log(msg=_('Localized string id not found for: [%s]') % strToId)
86+
return strToId
87+
88+
def getLocalizedStringId(self, strToId, update=False):
89+
idFound, strid = self.podict.has_msgid(strToId)
90+
if idFound:
91+
return strid
92+
else:
93+
if update is True or self.updateAlways is True:
94+
self.updatePo(strid, strToId)
95+
log(msg=_('Localized string added to po for: [%s]') % strToId)
96+
return strid
97+
else:
98+
log(msg=_('Localized string not found for: [%s]') % strToId)
99+
return 32168
100+
101+
def updatePo(self, strid, txt):
102+
self.podict.addentry(strid, txt)
103+
self.podict.write_to_file(self.pofn)
104+
105+
class PoDict(object):
106+
107+
_instance = None
108+
_lock = threading.Lock()
109+
_rlock = threading.RLock()
110+
111+
def __new__(cls):
112+
if PoDict._instance is None:
113+
with PoDict._lock:
114+
if PoDict._instance is None:
115+
PoDict._instance = super(PoDict, cls).__new__(cls)
116+
return PoDict._instance
117+
118+
def __init__(self):
119+
PoDict._instance = self
120+
self.dict_msgctxt = dict()
121+
self.dict_msgid = dict()
122+
self.chkdict = dict()
123+
self.remsgid = re.compile(r'"([^"\\]*(?:\\.[^"\\]*)*)"')
124+
self.savethread = threading.Thread()
125+
126+
def get_new_key(self):
127+
if len(self.dict_msgctxt) > 0:
128+
with PoDict._rlock:
129+
mmax = max(self.dict_msgctxt.iteritems(), key=operator.itemgetter(0))[0]
130+
else:
131+
mmax = '32000'
132+
try:
133+
int_key = int(mmax)
134+
except ValueError:
135+
int_key = -1
136+
return int_key + 1
137+
138+
def addentry(self, str_msgctxt, str_msgid):
139+
with PoDict._lock:
140+
self.dict_msgctxt[str_msgctxt] = str_msgid
141+
self.dict_msgid[str_msgid] = str_msgctxt
142+
143+
def has_msgctxt(self, str_msgctxt): # Returns the English string associated with the id provided
144+
with PoDict._lock:
145+
if str_msgctxt in self.dict_msgctxt.keys():
146+
return [True, self.dict_msgctxt[str_msgctxt]]
147+
else:
148+
return [False, None]
149+
150+
def has_msgid(self, str_msgid): # Returns the id in .po as a string i.e. "32000"
151+
with PoDict._lock:
152+
if str_msgid in self.dict_msgid.keys():
153+
return [True, self.dict_msgid[str_msgid]]
154+
else:
155+
return [False, str(self.get_new_key())]
156+
157+
def read_from_file(self, url):
158+
if url is None:
159+
log(loglevel=klogger.LOGERROR, msg='No URL to Read PoDict From')
160+
return
161+
if os.path.exists(url):
162+
try:
163+
with codecs.open(url, 'r', 'UTF-8') as f:
164+
poin = f.readlines()
165+
i = 0
166+
while i < len(poin):
167+
line = poin[i]
168+
if line[0:7] == 'msgctxt':
169+
t = re.findall(r'".+"', line)
170+
if not t[0].startswith('"Addon'):
171+
str_msgctxt = t[0][2:7]
172+
i += 1
173+
line2 = poin[i]
174+
str_msgid = ''
175+
while not line2.startswith('msgstr'):
176+
str_msgid += self.remsgid.findall(line2)[0]
177+
i += 1
178+
line2 = poin[i]
179+
str_msgid = str_msgid.decode('unicode_escape')
180+
self.dict_msgctxt[str_msgctxt] = str_msgid
181+
self.dict_msgid[str_msgid] = str_msgctxt
182+
self.chkdict[str_msgctxt] = False
183+
else:
184+
i += 1
185+
i += 1
186+
except Exception as e:
187+
log(loglevel=klogger.LOGERROR, msg='Error reading po: %s' % e.message)
188+
else:
189+
log(loglevel=klogger.LOGERROR, msg='Could not locate po at %s' % url)
190+
191+
def write_to_file(self, url):
192+
if self.savethread is not None:
193+
assert isinstance(self.savethread, threading.Thread)
194+
if self.savethread.is_alive():
195+
self.savethread.join()
196+
with PoDict._lock:
197+
tmp = copy.copy(self.dict_msgctxt)
198+
self.savethread = threading.Thread(target=PoDict._write_to_file, args=[tmp, url])
199+
self.savethread.start()
200+
201+
@staticmethod
202+
def _write_to_file(dict_msgctxt, url):
203+
fo = codecs.open(url, 'wb', 'UTF-8')
204+
PoDict.write_po_header(fo)
205+
str_max = max(dict_msgctxt.iteritems(), key=operator.itemgetter(0))[0]
206+
str_min = min(dict_msgctxt.iteritems(), key=operator.itemgetter(0))[0]
207+
208+
fo.write('msgctxt "Addon Summary"\n')
209+
fo.write('msgid "Callbacks for Kodi"\n')
210+
fo.write('msgstr ""\n\n')
211+
fo.write('msgctxt "Addon Description"\n')
212+
fo.write('msgid "Provides user definable actions for specific events within Kodi. Credit to Yesudeep Mangalapilly (gorakhargosh on github) and contributors for watchdog and pathtools modules."\n')
213+
fo.write('msgstr ""\n\n')
214+
fo.write('msgctxt "Addon Disclaimer"\n')
215+
fo.write('msgid "For bugs, requests or general questions visit the Kodi forums."\n')
216+
fo.write('msgstr ""\n\n')
217+
fo.write('#Add-on messages id=%s to %s\n\n' % (str_min, str_max))
218+
last = int(str_min) - 1
219+
for str_msgctxt in sorted(dict_msgctxt):
220+
if not str_msgctxt.startswith('Addon'):
221+
if int(str_msgctxt) != last + 1:
222+
fo.write('#empty strings from id %s to %s\n\n' % (str(last + 1), str(int(str_msgctxt) - 1)))
223+
PoDict.write_to_po(fo, str_msgctxt, PoDict.format_string_forpo(dict_msgctxt[str_msgctxt]))
224+
last = int(str_msgctxt)
225+
fo.close()
226+
227+
@staticmethod
228+
def format_string_forpo(mstr):
229+
out = ''
230+
for (i, x) in enumerate(mstr):
231+
if i == 1 and x == r'"':
232+
out += "\\" + x
233+
elif x == r'"' and mstr[i-1] != "\\":
234+
out += "\\" + x
235+
else:
236+
out += x
237+
return out
238+
239+
@staticmethod
240+
def write_po_header(fo):
241+
fo.write('# Kodi Media Center language file\n')
242+
fo.write('# Addon Name: Kodi Callbacks\n')
243+
fo.write('# Addon id: script.service.kodi.callbacks\n')
244+
fo.write('# Addon Provider: KenV99\n')
245+
fo.write('msgid ""\n')
246+
fo.write('msgstr ""\n')
247+
fo.write('"Project-Id-Version: XBMC Addons\\n"\n')
248+
fo.write('"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\\n"\n')
249+
fo.write('"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"\n')
250+
fo.write('"MIME-Version: 1.0\\n"\n')
251+
fo.write('"Content-Type: text/plain; charset=UTF-8\\n"\n')
252+
fo.write('"Content-Transfer-Encoding: 8bit\\n"\n')
253+
fo.write('"Language: en\\n"')
254+
fo.write('"Plural-Forms: nplurals=2; plural=(n != 1);\\n"\n\n')
255+
256+
@staticmethod
257+
def write_to_po(fileobject, int_num, str_msg):
258+
w = r'"#' + str(int_num) + r'"'
259+
fileobject.write('msgctxt ' + w + '\n')
260+
fileobject.write(splitstring(str_msg))
261+
fileobject.write('msgstr ' + r'""' + '\n')
262+
fileobject.write('\n')
263+
264+
def createreport(self):
265+
cnt = 0
266+
reportpo = []
267+
for x in self.chkdict:
268+
if not self.chkdict[x]:
269+
if cnt == 0:
270+
reportpo = ['No usage found for the following pairs:']
271+
msgid = self.dict_msgctxt[x]
272+
reportpo.append(' %s:%s' % (x, msgid))
273+
cnt += 1
274+
ret = '\n '.join(reportpo)
275+
return ret
276+
277+
def splitstring(s):
278+
ret = []
279+
s = s.replace('\n', '~@\n')
280+
split = s.split('\n')
281+
for i in xrange(0, len(split)):
282+
split[i] = split[i].replace('~@', '\n').encode('unicode_escape')
283+
if i == 0:
284+
if (len(split) == 2 and split[i+1] == '') or split[i] == '\\n' or len(split) == 1:
285+
ret.append('msgid "%s"\n' % split[i])
286+
else:
287+
ret.append('msgid ""\n')
288+
ret.append('"%s"\n' % split[i])
289+
elif i == len(split) - 1:
290+
if split[i] != '':
291+
ret.append('"%s"\n' % split[i])
292+
else:
293+
ret.append('"%s"\n' % split[i])
294+
ret = ''.join(ret)
295+
return ret
296+
297+
class UpdatePo(object):
298+
299+
def __init__(self, root_directory_to_scan, current_working_English_strings_po, exclude_directories=None, exclude_files=None):
300+
if exclude_directories is None:
301+
exclude_directories = []
302+
if exclude_files is None:
303+
exclude_files = []
304+
self.root_directory_to_scan = root_directory_to_scan
305+
self.current_working_English_strings_po = current_working_English_strings_po
306+
self.podict = PoDict()
307+
self.podict.read_from_file(self.current_working_English_strings_po)
308+
self.exclude_directories = exclude_directories
309+
self.exclude_files = exclude_files
310+
self.find_localizer = re.compile(r'^(\S+?)\s*=\s*kodipo.getLocalizedString\s*$', flags=re.MULTILINE)
311+
312+
def getFileList(self):
313+
files_to_scan = []
314+
exclusions = []
315+
for direct in self.exclude_directories:
316+
for root, ___, filenames in os.walk(os.path.join(self.root_directory_to_scan, direct)):
317+
for filename in filenames:
318+
exclusions.append(os.path.join(root, filename))
319+
for root, ___, filenames in os.walk(self.root_directory_to_scan):
320+
for filename in fnmatch.filter(filenames, '*.py'):
321+
if os.path.split(filename)[1] in self.exclude_files:
322+
continue
323+
elif os.path.join(root, filename) in exclusions:
324+
continue
325+
else:
326+
files_to_scan.append(os.path.join(root, filename))
327+
return files_to_scan
328+
329+
def scanPyFilesForStrings(self):
330+
files = self.getFileList()
331+
lstrings = []
332+
for myfile in files:
333+
finds = []
334+
with open(myfile, 'r') as f:
335+
lines = ''.join(f.readlines())
336+
try:
337+
finds = self.find_localizer.findall(lines)
338+
except re.error:
339+
pass
340+
finally:
341+
if len(finds) != 1:
342+
log(msg='Skipping file: %s, localizer not found' % myfile)
343+
else:
344+
findstr = r"%s\('(.+?)'\)" % finds[0]
345+
find = re.compile(findstr)
346+
finds = []
347+
try:
348+
finds = find.findall(lines)
349+
except re.error:
350+
pass
351+
lstrings += finds
352+
return lstrings
353+
354+
def updateStringsPo(self):
355+
lstrings = self.scanPyFilesForStrings()
356+
for s in lstrings:
357+
found, strid = self.podict.has_msgid(s.decode('unicode_escape'))
358+
if found is False:
359+
self.podict.addentry(strid, s)
360+
self.podict.write_to_file(self.current_working_English_strings_po)
361+
362+
kodipo = KodiPo()
363+
_ = kodipo.getLocalizedString
364+
__ = kodipo.getLocalizedStringId

0 commit comments

Comments
 (0)