diff --git a/.gitignore b/.gitignore index ded6067..87f9141 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store *.py[cod] # C extensions diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..faa67fa --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include README.md LICENSE +recursive-include localizable *.py \ No newline at end of file diff --git a/localizable/__init__.py b/localizable/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/localizable/localizable.py b/localizable/localizable.py new file mode 100644 index 0000000..01775ed --- /dev/null +++ b/localizable/localizable.py @@ -0,0 +1,103 @@ +# Adapted from Transifex: https://github.com/transifex/transifex/blob/master/transifex/resources/formats/strings.py +# -*- coding: utf-8 -*- +# GPLv2 + +""" +Apple strings file handler/compiler +""" + +from __future__ import absolute_import +import codecs, re, chardet + + +""" +Handler for Apple STRINGS translation files. + +Apple strings files *must* be encoded in cls.ENCODING encoding. +""" + +format_encoding = 'UTF-16' + +def _unescape_key(s): + return s.replace('\\\n', '') + +def _unescape(s): + s = s.replace('\\\n', '') + return s.replace('\\"', '"').replace(r'\n', '\n').replace(r'\r', '\r') + +def _get_content(filename=None, content=None): + if content is not None: + if chardet.detect(content)['encoding'].startswith(format_encoding): + encoding = format_encoding + else: + encoding = 'UTF-8' + if isinstance(content, str): + content.decode(encoding) + else: + return content + if filename is None: + return None + return _get_content_from_file(filename, format_encoding) + +def _get_content_from_file(filename, encoding): + f = open(filename, 'r') + try: + content = f.read() + if chardet.detect(content)['encoding'].startswith(format_encoding): + #f = f.decode(format_encoding) + encoding = format_encoding + else: + #f = f.decode(default_encoding) + encoding = 'utf-8' + f.close() + f = codecs.open(filename, 'r', encoding=encoding) + return f.read() + except IOError, e: + print "Error opening file %s with encoding %s: %s" %\ + (filename, format_encoding, e.message) + except Exception, e: + print "Unhandled exception: %s" % e.message + finally: + f.close() + +def parse_strings(content="", filename=None): + """Parse an apple .strings file and create a stringset with + all entries in the file. + + See + http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPInternational/Articles/StringsFiles.html + for details. + """ + if filename is not None: + content = _get_content(filename=filename) + + stringset = [] + f = content + if f.startswith(u'\ufeff'): + f = f.lstrip(u'\ufeff') + #regex for finding all comments in a file + cp = r'(?:/\*(?P(?:[^*]|(?:\*+[^*/]))*\**)\*/)' + p = re.compile(r'(?:%s[ \t]*[\n]|[\r\n]|[\r]){0,1}(?P(("(?P[^"\\]*(?:\\.[^"\\]*)*)")|(?P\w+))\s*=\s*"(?P[^"\\]*(?:\\.[^"\\]*)*)"\s*;)'%cp, re.DOTALL|re.U) + #c = re.compile(r'\s*/\*(.|\s)*?\*/\s*', re.U) + c = re.compile(r'//[^\n]*\n|/\*(?:.|[\r\n])*?\*/', re.U) + ws = re.compile(r'\s+', re.U) + end=0 + start = 0 + for i in p.finditer(f): + start = i.start('line') + end_ = i.end() + key = i.group('key') + comment = i.group('comment') or '' + if not key: + key = i.group('property') + value = i.group('value') + while end < start: + m = c.match(f, end, start) or ws.match(f, end, start) + if not m or m.start() != end: + print "Invalid syntax: %s" %\ + f[end:start] + end = m.end() + end = end_ + key = _unescape_key(key) + stringset.append({'key': key, 'value': _unescape(value), 'comment': comment}) + return stringset \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dcee154 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +chardet==2.1.1 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..87abb0a --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +import os, re +from distutils.core import setup + +def get_requirements(filename): + """ + Read requirements and dependency links from a file passed by parameter + and return them as two lists in a tuple. + """ + def add_dependency_link(line): + link = re.sub(r'\s*-[ef]\s+', '', line) + filename = os.path.basename(link.split('://')[1]) + url = link.split(filename)[0] + if url not in dependency_links: + dependency_links.append(url) + + requirements = [] + dependency_links = [] + for line in open(filename, 'r').read().split('\n'): + if re.match(r'(\s*#)|(\s*$)', line): + continue + if re.match(r'\s*-e\s+', line): + # TODO support version numbers + requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', line)) + add_dependency_link(line) + elif re.match(r'\s*-f\s+', line): + add_dependency_link(line) + else: + requirements.append(line) + return requirements, dependency_links + +requirements, dependency_links = get_requirements('requirements.txt') + +setup(name='localizable', + version='0.1', + description='Localizable.strings parser for Apple-specific localization files', + author='Chris Ballinger', + author_email='chris@chatsecure.org', + url='https://github.com/chrisballinger/python-localizable', + license="GPLv2", + long_description=open('README.md').read(), + packages=['localizable'], + install_requires=requirements, + ) \ No newline at end of file