diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /python/compare-locales/compare_locales/webapps.py | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'python/compare-locales/compare_locales/webapps.py')
-rw-r--r-- | python/compare-locales/compare_locales/webapps.py | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/python/compare-locales/compare_locales/webapps.py b/python/compare-locales/compare_locales/webapps.py new file mode 100644 index 000000000..42f5b5657 --- /dev/null +++ b/python/compare-locales/compare_locales/webapps.py @@ -0,0 +1,235 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +'''gaia-style web apps support + +This variant supports manifest.webapp localization as well as +.properties files with a naming scheme of locales/foo.*.properties. +''' + +from collections import defaultdict +import json +import os +import os.path +import re + +from compare_locales.paths import File, EnumerateDir +from compare_locales.compare import AddRemove, ContentComparer + + +class WebAppCompare(object): + '''For a given directory, analyze + /manifest.webapp + /locales/*.*.properties + + Deduce the present locale codes. + ''' + ignore_dirs = EnumerateDir.ignore_dirs + reference_locale = 'en-US' + + def __init__(self, basedir): + '''Constructor + :param basedir: Directory of the web app to inspect + ''' + self.basedir = basedir + self.manifest = Manifest(basedir, self.reference_locale) + self.files = FileComparison(basedir, self.reference_locale) + self.watcher = None + + def compare(self, locales): + '''Compare the manifest.webapp and the locales/*.*.properties + ''' + if not locales: + locales = self.locales() + self.manifest.compare(locales) + self.files.compare(locales) + + def setWatcher(self, watcher): + self.watcher = watcher + self.manifest.watcher = watcher + self.files.watcher = watcher + + def locales(self): + '''Inspect files on disk to find present languages. + :rtype: List of locales, sorted, including reference. + ''' + locales = set(self.manifest.strings.keys()) + locales.update(self.files.locales()) + locales = list(sorted(locales)) + return locales + + +class Manifest(object): + '''Class that helps with parsing and inspection of manifest.webapp. + ''' + + def __init__(self, basedir, reference_locale): + self.file = File(os.path.join(basedir, 'manifest.webapp'), + 'manifest.webapp') + self.reference_locale = reference_locale + self._strings = None + self.watcher = None + + @property + def strings(self): + if self._strings is None: + self._strings = self.load_and_parse() + return self._strings + + def load_and_parse(self): + try: + manifest = json.load(open(self.file.fullpath)) + except (ValueError, IOError), e: + if self.watcher: + self.watcher.notify('error', self.file, str(e)) + return False + return self.extract_manifest_strings(manifest) + + def extract_manifest_strings(self, manifest_fragment): + '''Extract localizable strings from a manifest dict. + This method is recursive, and returns a two-level dict, + first level being locale codes, second level being generated + key and localized value. Keys are generated by concatenating + each level in the json with a ".". + ''' + rv = defaultdict(dict) + localizable = manifest_fragment.pop('locales', {}) + if localizable: + for locale, keyvalue in localizable.iteritems(): + for key, value in keyvalue.iteritems(): + key = '.'.join(['locales', 'AB_CD', key]) + rv[locale][key] = value + for key, sub_manifest in manifest_fragment.iteritems(): + if not isinstance(sub_manifest, dict): + continue + subdict = self.extract_manifest_strings(sub_manifest) + if subdict: + for locale, keyvalue in subdict: + rv[locale].update((key + '.' + subkey, value) + for subkey, value + in keyvalue.iteritems()) + return rv + + def compare(self, locales): + strings = self.strings + if not strings: + return + # create a copy so that we can mock around with it + strings = strings.copy() + reference = strings.pop(self.reference_locale) + for locale in locales: + if locale == self.reference_locale: + continue + self.compare_strings(reference, + strings.get(locale, {}), + locale) + + def compare_strings(self, reference, l10n, locale): + add_remove = AddRemove() + add_remove.set_left(sorted(reference.keys())) + add_remove.set_right(sorted(l10n.keys())) + missing = obsolete = changed = unchanged = 0 + for op, item_or_pair in add_remove: + if op == 'equal': + if reference[item_or_pair[0]] == l10n[item_or_pair[1]]: + unchanged += 1 + else: + changed += 1 + else: + key = item_or_pair.replace('.AB_CD.', + '.%s.' % locale) + if op == 'add': + # obsolete entry + obsolete += 1 + self.watcher.notify('obsoleteEntity', self.file, key) + else: + # missing entry + missing += 1 + self.watcher.notify('missingEntity', self.file, key) + + +class FileComparison(object): + '''Compare the locales/*.*.properties files inside a webapp. + ''' + prop = re.compile('(?P<base>.*)\\.' + '(?P<locale>[a-zA-Z]+(?:-[a-zA-Z]+)*)' + '\\.properties$') + + def __init__(self, basedir, reference_locale): + self.basedir = basedir + self.reference_locale = reference_locale + self.watcher = None + self._reference = self._files = None + + def locales(self): + '''Get the locales present in the webapp + ''' + self.files() + locales = self._files.keys() + locales.sort() + return locales + + def compare(self, locales): + self.files() + for locale in locales: + l10n = self._files[locale] + filecmp = AddRemove() + filecmp.set_left(sorted(self._reference.keys())) + filecmp.set_right(sorted(l10n.keys())) + for op, item_or_pair in filecmp: + if op == 'equal': + self.watcher.compare(self._reference[item_or_pair[0]], + l10n[item_or_pair[1]]) + elif op == 'add': + # obsolete file + self.watcher.remove(l10n[item_or_pair]) + else: + # missing file + _path = '.'.join([item_or_pair, locale, 'properties']) + missingFile = File( + os.path.join(self.basedir, 'locales', _path), + 'locales/' + _path) + self.watcher.add(self._reference[item_or_pair], + missingFile) + + def files(self): + '''Read the list of locales from disk. + ''' + if self._reference: + return + self._reference = {} + self._files = defaultdict(dict) + path_list = self._listdir() + for path in path_list: + match = self.prop.match(path) + if match is None: + continue + locale = match.group('locale') + if locale == self.reference_locale: + target = self._reference + else: + target = self._files[locale] + fullpath = os.path.join(self.basedir, 'locales', path) + target[match.group('base')] = File(fullpath, 'locales/' + path) + + def _listdir(self): + 'Monkey-patch this for testing.' + return os.listdir(os.path.join(self.basedir, 'locales')) + + +def compare_web_app(basedir, locales, other_observer=None): + '''Compare gaia-style web app. + + Optional arguments are: + - other_observer. A object implementing + notify(category, _file, data) + The return values of that callback are ignored. + ''' + comparer = ContentComparer() + if other_observer is not None: + comparer.add_observer(other_observer) + webapp_comp = WebAppCompare(basedir) + webapp_comp.setWatcher(comparer) + webapp_comp.compare(locales) + return comparer.observer |