--- a/compare_locales/compare.py
+++ b/compare_locales/compare.py
@@ -1,29 +1,28 @@
# 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/.
'Mozilla l10n compare locales tool'
import codecs
import os
-import os.path
import shutil
import re
from difflib import SequenceMatcher
from collections import defaultdict
try:
from json import dumps
except:
from simplejson import dumps
from compare_locales import parser
-from compare_locales import paths
+from compare_locales import paths, mozpath
from compare_locales.checks import getChecker
class Tree(object):
def __init__(self, valuetype):
self.branches = dict()
self.valuetype = valuetype
self.value = None
@@ -378,19 +377,19 @@ class ContentComparer:
'''
self.other_observers.append(obs)
def set_merge_stage(self, merge_stage):
self.merge_stage = merge_stage
def merge(self, ref_entities, ref_map, ref_file, l10n_file, missing,
skips, ctx, canMerge, encoding):
- outfile = os.path.join(self.merge_stage, l10n_file.module,
+ outfile = mozpath.join(self.merge_stage, l10n_file.module,
l10n_file.file)
- outdir = os.path.dirname(outfile)
+ outdir = mozpath.dirname(outfile)
if not os.path.isdir(outdir):
os.makedirs(outdir)
if not canMerge:
shutil.copyfile(ref_file.fullpath, outfile)
print "copied reference to " + outfile
return
if skips:
# skips come in ordered by key name, we need them in file order
@@ -597,14 +596,14 @@ def compareApp(app, other_observer=None,
dir_comp = DirectoryCompare(reference)
dir_comp.setWatcher(comparer)
for _, localization in locales:
if merge_stage is not None:
locale_merge = merge_stage.format(ab_CD=localization.locale)
comparer.set_merge_stage(locale_merge)
if clobber:
# if clobber, remove the stage for the module if it exists
- clobberdir = os.path.join(locale_merge, module)
+ clobberdir = mozpath.join(locale_merge, module)
if os.path.exists(clobberdir):
shutil.rmtree(clobberdir)
print "clobbered " + clobberdir
dir_comp.compareWith(localization)
return comparer.observer
new file mode 100644
--- /dev/null
+++ b/compare_locales/mozpath.py
@@ -0,0 +1,137 @@
+# 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/.
+
+from __future__ import absolute_import
+
+import posixpath
+import os
+import re
+
+'''
+Like os.path, with a reduced set of functions, and with normalized path
+separators (always use forward slashes).
+Also contains a few additional utilities not found in os.path.
+'''
+
+
+def normsep(path):
+ '''
+ Normalize path separators, by using forward slashes instead of whatever
+ os.sep is.
+ '''
+ if os.sep != '/':
+ path = path.replace(os.sep, '/')
+ if os.altsep and os.altsep != '/':
+ path = path.replace(os.altsep, '/')
+ return path
+
+
+def relpath(path, start):
+ rel = normsep(os.path.relpath(path, start))
+ return '' if rel == '.' else rel
+
+
+def realpath(path):
+ return normsep(os.path.realpath(path))
+
+
+def abspath(path):
+ return normsep(os.path.abspath(path))
+
+
+def join(*paths):
+ return normsep(os.path.join(*paths))
+
+
+def normpath(path):
+ return posixpath.normpath(normsep(path))
+
+
+def dirname(path):
+ return posixpath.dirname(normsep(path))
+
+
+def commonprefix(paths):
+ return posixpath.commonprefix([normsep(path) for path in paths])
+
+
+def basename(path):
+ return os.path.basename(path)
+
+
+def splitext(path):
+ return posixpath.splitext(normsep(path))
+
+
+def split(path):
+ '''
+ Return the normalized path as a list of its components.
+ split('foo/bar/baz') returns ['foo', 'bar', 'baz']
+ '''
+ return normsep(path).split('/')
+
+
+def basedir(path, bases):
+ '''
+ Given a list of directories (bases), return which one contains the given
+ path. If several matches are found, the deepest base directory is returned.
+ basedir('foo/bar/baz', ['foo', 'baz', 'foo/bar']) returns 'foo/bar'
+ ('foo' and 'foo/bar' both match, but 'foo/bar' is the deepest match)
+ '''
+ path = normsep(path)
+ bases = [normsep(b) for b in bases]
+ if path in bases:
+ return path
+ for b in sorted(bases, reverse=True):
+ if b == '' or path.startswith(b + '/'):
+ return b
+
+
+re_cache = {}
+
+
+def match(path, pattern):
+ '''
+ Return whether the given path matches the given pattern.
+ An asterisk can be used to match any string, including the null string, in
+ one part of the path:
+ 'foo' matches '*', 'f*' or 'fo*o'
+ However, an asterisk matching a subdirectory may not match the null string:
+ 'foo/bar' does *not* match 'foo/*/bar'
+ If the pattern matches one of the ancestor directories of the path, the
+ patch is considered matching:
+ 'foo/bar' matches 'foo'
+ Two adjacent asterisks can be used to match files and zero or more
+ directories and subdirectories.
+ 'foo/bar' matches 'foo/**/bar', or '**/bar'
+ '''
+ if not pattern:
+ return True
+ if pattern not in re_cache:
+ p = re.escape(pattern)
+ p = re.sub(r'(^|\\\/)\\\*\\\*\\\/', r'\1(?:.+/)?', p)
+ p = re.sub(r'(^|\\\/)\\\*\\\*$', r'(?:\1.+)?', p)
+ p = p.replace(r'\*', '[^/]*') + '(?:/.*)?$'
+ re_cache[pattern] = re.compile(p)
+ return re_cache[pattern].match(path) is not None
+
+
+def rebase(oldbase, base, relativepath):
+ '''
+ Return relativepath relative to base instead of oldbase.
+ '''
+ if base == oldbase:
+ return relativepath
+ if len(base) < len(oldbase):
+ assert basedir(oldbase, [base]) == base
+ relbase = relpath(oldbase, base)
+ result = join(relbase, relativepath)
+ else:
+ assert basedir(base, [oldbase]) == oldbase
+ relbase = relpath(base, oldbase)
+ result = relpath(relativepath, relbase)
+ result = normpath(result)
+ if relativepath.endswith('/') and not result.endswith('/'):
+ result += '/'
+ return result
--- a/compare_locales/paths.py
+++ b/compare_locales/paths.py
@@ -1,40 +1,32 @@
# 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/.
-import os.path
import os
from ConfigParser import ConfigParser, NoSectionError, NoOptionError
-from urlparse import urlparse, urljoin
-from urllib import pathname2url, url2pathname
-from urllib2 import urlopen
from collections import defaultdict
-from compare_locales import util
+from compare_locales import util, mozpath
class L10nConfigParser(object):
'''Helper class to gather application information from ini files.
This class is working on synchronous open to read files or web data.
Subclass this and overwrite loadConfigs and addChild if you need async.
'''
def __init__(self, inipath, **kwargs):
"""Constructor for L10nConfigParsers
inipath -- l10n.ini path
Optional keyword arguments are fowarded to the inner ConfigParser as
defaults.
"""
- if os.path.isabs(inipath):
- self.inipath = 'file:%s' % pathname2url(inipath)
- else:
- pwdurl = 'file:%s/' % pathname2url(os.getcwd())
- self.inipath = urljoin(pwdurl, inipath)
+ self.inipath = mozpath.normpath(inipath)
# l10n.ini files can import other l10n.ini files, store the
# corresponding L10nConfigParsers
self.children = []
# we really only care about the l10n directories described in l10n.ini
self.dirs = []
# optional defaults to be passed to the inner ConfigParser (unused?)
self.defaults = kwargs
@@ -48,20 +40,20 @@ class L10nConfigParser(object):
return depth
def getFilters(self):
'''Get the test functions from this ConfigParser and all children.
Only works with synchronous loads, used by compare-locales, which
is local anyway.
'''
- filterurl = urljoin(self.inipath, 'filter.py')
+ filter_path = mozpath.join(mozpath.dirname(self.inipath), 'filter.py')
try:
l = {}
- execfile(url2pathname(urlparse(filterurl).path), {}, l)
+ execfile(filter_path, {}, l)
if 'test' in l and callable(l['test']):
filters = [l['test']]
else:
filters = []
except:
filters = []
for c in self.children:
@@ -71,24 +63,20 @@ class L10nConfigParser(object):
def loadConfigs(self):
"""Entry point to load the l10n.ini file this Parser refers to.
This implementation uses synchronous loads, subclasses might overload
this behaviour. If you do, make sure to pass a file-like object
to onLoadConfig.
"""
- self.onLoadConfig(urlopen(self.inipath))
-
- def onLoadConfig(self, inifile):
- """Parse a file-like object for the loaded l10n.ini file."""
cp = ConfigParser(self.defaults)
- cp.readfp(inifile)
+ cp.read(self.inipath)
depth = self.getDepth(cp)
- self.baseurl = urljoin(self.inipath, depth)
+ self.base = mozpath.join(mozpath.dirname(self.inipath), depth)
# create child loaders for any other l10n.ini files to be included
try:
for title, path in cp.items('includes'):
# skip default items
if title in self.defaults:
continue
# add child config parser
self.addChild(title, path, cp)
@@ -96,87 +84,83 @@ class L10nConfigParser(object):
pass
# try to load the "dirs" defined in the "compare" section
try:
self.dirs.extend(cp.get('compare', 'dirs').split())
except (NoOptionError, NoSectionError):
pass
# try to set "all_path" and "all_url"
try:
- self.all_path = cp.get('general', 'all')
- self.all_url = urljoin(self.baseurl, self.all_path)
+ self.all_path = mozpath.join(self.base, cp.get('general', 'all'))
except (NoOptionError, NoSectionError):
self.all_path = None
- self.all_url = None
return cp
def addChild(self, title, path, orig_cp):
"""Create a child L10nConfigParser and load it.
title -- indicates the module's name
path -- indicates the path to the module's l10n.ini file
orig_cp -- the configuration parser of this l10n.ini
"""
- cp = L10nConfigParser(urljoin(self.baseurl, path), **self.defaults)
+ cp = L10nConfigParser(mozpath.join(self.base, path), **self.defaults)
cp.loadConfigs()
self.children.append(cp)
def dirsIter(self):
"""Iterate over all dirs and our base path for this l10n.ini"""
- url = urlparse(self.baseurl)
- basepath = url2pathname(url.path)
for dir in self.dirs:
- yield dir, (basepath, dir)
+ yield dir, (self.base, dir)
def directories(self):
"""Iterate over all dirs and base paths for this l10n.ini as well
as the included ones.
"""
for t in self.dirsIter():
yield t
for child in self.children:
for t in child.directories():
yield t
def allLocales(self):
"""Return a list of all the locales of this project"""
- return util.parseLocales(urlopen(self.all_url).read())
+ return util.parseLocales(open(self.all_path).read())
class SourceTreeConfigParser(L10nConfigParser):
'''Subclassing L10nConfigParser to work with just the repos
checked out next to each other instead of intermingled like
we do for real builds.
'''
- def __init__(self, inipath, basepath, redirects):
+ def __init__(self, inipath, base, redirects):
'''Add additional arguments basepath.
basepath is used to resolve local paths via branchnames.
redirects is used in unified repository, mapping upstream
repos to local clones.
'''
L10nConfigParser.__init__(self, inipath)
- self.basepath = basepath
+ self.base = base
self.redirects = redirects
def addChild(self, title, path, orig_cp):
# check if there's a section with details for this include
# we might have to check a different repo, or even VCS
# for example, projects like "mail" indicate in
# an "include_" section where to find the l10n.ini for "toolkit"
details = 'include_' + title
if orig_cp.has_section(details):
branch = orig_cp.get(details, 'mozilla')
branch = self.redirects.get(branch, branch)
inipath = orig_cp.get(details, 'l10n.ini')
- path = self.basepath + '/' + branch + '/' + inipath
+ path = mozpath.join(self.base, branch, inipath)
else:
- path = urljoin(self.baseurl, path)
- cp = SourceTreeConfigParser(path, self.basepath, self.redirects,
+ path = mozpath.join(self.base, path)
+ cp = SourceTreeConfigParser(path, self.base, self.redirects,
**self.defaults)
cp.loadConfigs()
self.children.append(cp)
class File(object):
def __init__(self, fullpath, file, module=None, locale=None):
@@ -217,37 +201,37 @@ class EnumerateDir(object):
self.locale = locale
self.ignore_subdirs = ignore_subdirs
pass
def cloneFile(self, other):
'''
Return a File object that this enumerator would return, if it had it.
'''
- return File(os.path.join(self.basepath, other.file), other.file,
+ return File(mozpath.join(self.basepath, other.file), other.file,
self.module, self.locale)
def __iter__(self):
# our local dirs are given as a tuple of path segments, starting off
# with an empty sequence for the basepath.
dirs = [()]
while dirs:
dir = dirs.pop(0)
- fulldir = os.path.join(self.basepath, *dir)
+ fulldir = mozpath.join(self.basepath, *dir)
try:
entries = os.listdir(fulldir)
except OSError:
# we probably just started off in a non-existing dir, ignore
continue
entries.sort()
for entry in entries:
- leaf = os.path.join(fulldir, entry)
+ leaf = mozpath.join(fulldir, entry)
if os.path.isdir(leaf):
if entry not in self.ignore_dirs and \
- leaf not in [os.path.join(self.basepath, d)
+ leaf not in [mozpath.join(self.basepath, d)
for d in self.ignore_subdirs]:
dirs.append(dir + (entry,))
continue
yield File(leaf, '/'.join(dir + (entry,)),
self.module, self.locale)
class LocalesWrap(object):
@@ -255,30 +239,29 @@ class LocalesWrap(object):
def __init__(self, base, module, locales, ignore_subdirs=[]):
self.base = base
self.module = module
self.locales = locales
self.ignore_subdirs = ignore_subdirs
def __iter__(self):
for locale in self.locales:
- path = os.path.join(self.base, locale, self.module)
+ path = mozpath.join(self.base, locale, self.module)
yield (locale, EnumerateDir(path, self.module, locale,
self.ignore_subdirs))
class EnumerateApp(object):
reference = 'en-US'
def __init__(self, inipath, l10nbase, locales=None):
self.setupConfigParser(inipath)
self.modules = defaultdict(dict)
- self.l10nbase = os.path.abspath(l10nbase)
+ self.l10nbase = mozpath.abspath(l10nbase)
self.filters = []
- drive, tail = os.path.splitdrive(inipath)
self.addFilters(*self.config.getFilters())
self.locales = locales or self.config.allLocales()
self.locales.sort()
def setupConfigParser(self, inipath):
self.config = L10nConfigParser(inipath)
self.config.loadConfigs()
@@ -317,19 +300,19 @@ class EnumerateApp(object):
iterator over all locales in each iteration. Per locale, the locale
code and an directory enumerator will be given.
'''
dirmap = dict(self.config.directories())
mods = dirmap.keys()
mods.sort()
for mod in mods:
if self.reference == 'en-US':
- base = os.path.join(*(dirmap[mod] + ('locales', 'en-US')))
+ base = mozpath.join(*(dirmap[mod] + ('locales', 'en-US')))
else:
- base = os.path.join(self.l10nbase, self.reference, mod)
+ base = mozpath.join(self.l10nbase, self.reference, mod)
yield (mod, EnumerateDir(base, mod, self.reference),
LocalesWrap(self.l10nbase, mod, self.locales,
[m[len(mod)+1:] for m in mods if m.startswith(mod+'/')]))
class EnumerateSourceTreeApp(EnumerateApp):
'''Subclass EnumerateApp to work on side-by-side checked out
repos, and to no pay attention to how the source would actually
@@ -341,21 +324,8 @@ class EnumerateSourceTreeApp(EnumerateAp
self.basepath = basepath
self.redirects = redirects
EnumerateApp.__init__(self, inipath, l10nbase, locales)
def setupConfigParser(self, inipath):
self.config = SourceTreeConfigParser(inipath, self.basepath,
self.redirects)
self.config.loadConfigs()
-
-
-def get_base_path(mod, loc):
- 'statics for path patterns and conversion'
- __l10n = 'l10n/%(loc)s/%(mod)s'
- __en_US = 'mozilla/%(mod)s/locales/en-US'
- if loc == 'en-US':
- return __en_US % {'mod': mod}
- return __l10n % {'mod': mod, 'loc': loc}
-
-
-def get_path(mod, loc, leaf):
- return get_base_path(mod, loc) + '/' + leaf
new file mode 100644
--- /dev/null
+++ b/compare_locales/tests/test_apps.py
@@ -0,0 +1,76 @@
+import unittest
+import os
+import tempfile
+import shutil
+
+from compare_locales import mozpath
+from compare_locales.paths import EnumerateApp
+
+MAIL_INI = '''\
+[general]
+depth = ../..
+all = mail/locales/all-locales
+
+[compare]
+dirs = mail
+
+[includes]
+# non-central apps might want to use %(topsrcdir)s here, or other vars
+# RFE: that needs to be supported by compare-locales, too, though
+toolkit = mozilla/toolkit/locales/l10n.ini
+
+[include_toolkit]
+type = hg
+mozilla = mozilla-central
+repo = http://hg.mozilla.org/
+l10n.ini = toolkit/locales/l10n.ini
+'''
+
+
+MAIL_ALL_LOCALES = '''af
+de
+fr
+'''
+
+MAIL_FILTER_PY = '''
+def test(mod, path, entity = None):
+ if mod == 'toolkit' and path == 'ignored_path':
+ return 'ignore'
+ return 'error'
+'''
+
+TOOLKIT_INI = '''[general]
+depth = ../..
+
+[compare]
+dirs = toolkit
+'''
+
+
+class TestApp(unittest.TestCase):
+ def setUp(self):
+ self.stage = tempfile.mkdtemp()
+ mail = mozpath.join(self.stage, 'comm', 'mail', 'locales')
+ toolkit = mozpath.join(
+ self.stage, 'comm', 'mozilla', 'toolkit', 'locales')
+ os.makedirs(mail)
+ os.makedirs(toolkit)
+ with open(mozpath.join(mail, 'l10n.ini'), 'w') as f:
+ f.write(MAIL_INI)
+ with open(mozpath.join(mail, 'all-locales'), 'w') as f:
+ f.write(MAIL_ALL_LOCALES)
+ with open(mozpath.join(mail, 'filter.py'), 'w') as f:
+ f.write(MAIL_FILTER_PY)
+ with open(mozpath.join(toolkit, 'l10n.ini'), 'w') as f:
+ f.write(TOOLKIT_INI)
+
+ def tearDown(self):
+ shutil.rmtree(self.stage)
+
+ def test_app(self):
+ 'Test parsing a App'
+ app = EnumerateApp(
+ mozpath.join(self.stage, 'comm', 'mail', 'locales', 'l10n.ini'),
+ mozpath.join(self.stage, 'l10n-central'))
+ self.assertListEqual(app.locales, ['af', 'de', 'fr'])
+ self.assertEqual(len(app.config.children), 1)
--- a/compare_locales/tests/test_merge.py
+++ b/compare_locales/tests/test_merge.py
@@ -5,110 +5,111 @@
import unittest
import os
from tempfile import mkdtemp
import shutil
from compare_locales.parser import getParser
from compare_locales.paths import File
from compare_locales.compare import ContentComparer
+from compare_locales import mozpath
class ContentMixin(object):
extension = None # OVERLOAD
def reference(self, content):
- self.ref = os.path.join(self.tmp, "en-reference" + self.extension)
+ self.ref = mozpath.join(self.tmp, "en-reference" + self.extension)
open(self.ref, "w").write(content)
def localized(self, content):
- self.l10n = os.path.join(self.tmp, "l10n" + self.extension)
+ self.l10n = mozpath.join(self.tmp, "l10n" + self.extension)
open(self.l10n, "w").write(content)
class TestProperties(unittest.TestCase, ContentMixin):
extension = '.properties'
def setUp(self):
self.maxDiff = None
self.tmp = mkdtemp()
- os.mkdir(os.path.join(self.tmp, "merge"))
+ os.mkdir(mozpath.join(self.tmp, "merge"))
def tearDown(self):
shutil.rmtree(self.tmp)
del self.tmp
def testGood(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""foo = fooVal
bar = barVal
eff = effVal""")
self.localized("""foo = lFoo
bar = lBar
eff = lEff
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.properties", ""),
File(self.l10n, "l10n.properties", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 3
}},
'details': {}
}
)
- self.assert_(not os.path.exists(os.path.join(cc.merge_stage,
+ self.assert_(not os.path.exists(mozpath.join(cc.merge_stage,
'l10n.properties')))
def testMissing(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""foo = fooVal
bar = barVal
eff = effVal""")
self.localized("""bar = lBar
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.properties", ""),
File(self.l10n, "l10n.properties", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 1, 'missing': 2
}},
'details': {
'children': [
('l10n.properties',
{'value': {'missingEntity': [u'eff', u'foo']}}
)
]}
}
)
- mergefile = os.path.join(self.tmp, "merge", "l10n.properties")
+ mergefile = mozpath.join(self.tmp, "merge", "l10n.properties")
self.assertTrue(os.path.isfile(mergefile))
p = getParser(mergefile)
p.readFile(mergefile)
[m, n] = p.parse()
self.assertEqual(map(lambda e: e.key, m), ["bar", "eff", "foo"])
def testError(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""foo = fooVal
bar = %d barVal
eff = effVal""")
self.localized("""\
bar = %S lBar
eff = leffVal
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.properties", ""),
File(self.l10n, "l10n.properties", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 2, 'errors': 1, 'missing': 1
}},
@@ -118,34 +119,34 @@ eff = leffVal
{'value': {
'error': [u'argument 1 `S` should be `d` '
u'at line 1, column 7 for bar'],
'missingEntity': [u'foo']}}
)
]}
}
)
- mergefile = os.path.join(self.tmp, "merge", "l10n.properties")
+ mergefile = mozpath.join(self.tmp, "merge", "l10n.properties")
self.assertTrue(os.path.isfile(mergefile))
p = getParser(mergefile)
p.readFile(mergefile)
[m, n] = p.parse()
self.assertEqual([e.key for e in m], ["eff", "foo", "bar"])
self.assertEqual(m[n['bar']].val, '%d barVal')
def testObsolete(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""foo = fooVal
eff = effVal""")
self.localized("""foo = fooVal
other = obsolete
eff = leffVal
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.properties", ""),
File(self.l10n, "l10n.properties", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 1, 'obsolete': 1, 'unchanged': 1
}},
@@ -158,90 +159,90 @@ eff = leffVal
class TestDTD(unittest.TestCase, ContentMixin):
extension = '.dtd'
def setUp(self):
self.maxDiff = None
self.tmp = mkdtemp()
- os.mkdir(os.path.join(self.tmp, "merge"))
+ os.mkdir(mozpath.join(self.tmp, "merge"))
def tearDown(self):
shutil.rmtree(self.tmp)
del self.tmp
def testGood(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""<!ENTITY foo 'fooVal'>
<!ENTITY bar 'barVal'>
<!ENTITY eff 'effVal'>""")
self.localized("""<!ENTITY foo 'lFoo'>
<!ENTITY bar 'lBar'>
<!ENTITY eff 'lEff'>
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.dtd", ""),
File(self.l10n, "l10n.dtd", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 3
}},
'details': {}
}
)
self.assert_(
- not os.path.exists(os.path.join(cc.merge_stage, 'l10n.dtd')))
+ not os.path.exists(mozpath.join(cc.merge_stage, 'l10n.dtd')))
def testMissing(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""<!ENTITY foo 'fooVal'>
<!ENTITY bar 'barVal'>
<!ENTITY eff 'effVal'>""")
self.localized("""<!ENTITY bar 'lBar'>
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.dtd", ""),
File(self.l10n, "l10n.dtd", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'changed': 1, 'missing': 2
}},
'details': {
'children': [
('l10n.dtd',
{'value': {'missingEntity': [u'eff', u'foo']}}
)
]}
}
)
- mergefile = os.path.join(self.tmp, "merge", "l10n.dtd")
+ mergefile = mozpath.join(self.tmp, "merge", "l10n.dtd")
self.assertTrue(os.path.isfile(mergefile))
p = getParser(mergefile)
p.readFile(mergefile)
[m, n] = p.parse()
self.assertEqual(map(lambda e: e.key, m), ["bar", "eff", "foo"])
def testJunk(self):
self.assertTrue(os.path.isdir(self.tmp))
self.reference("""<!ENTITY foo 'fooVal'>
<!ENTITY bar 'barVal'>
<!ENTITY eff 'effVal'>""")
self.localized("""<!ENTITY foo 'fooVal'>
<!ENTY bar 'gimmick'>
<!ENTITY eff 'effVal'>
""")
cc = ContentComparer()
- cc.set_merge_stage(os.path.join(self.tmp, "merge"))
+ cc.set_merge_stage(mozpath.join(self.tmp, "merge"))
cc.compare(File(self.ref, "en-reference.dtd", ""),
File(self.l10n, "l10n.dtd", ""))
self.assertDictEqual(
cc.observer.toJSON(),
{'summary':
{None: {
'errors': 1, 'missing': 1, 'unchanged': 2
}},
@@ -253,17 +254,17 @@ class TestDTD(unittest.TestCase, Content
u'\'gimmick\'>" '
u'from line 2 column 1 to '
u'line 2 column 22'],
'missingEntity': [u'bar']}}
)
]}
}
)
- mergefile = os.path.join(self.tmp, "merge", "l10n.dtd")
+ mergefile = mozpath.join(self.tmp, "merge", "l10n.dtd")
self.assertTrue(os.path.isfile(mergefile))
p = getParser(mergefile)
p.readFile(mergefile)
[m, n] = p.parse()
self.assertEqual(map(lambda e: e.key, m), ["foo", "eff", "bar"])
if __name__ == '__main__':
new file mode 100644
--- /dev/null
+++ b/compare_locales/tests/test_mozpath.py
@@ -0,0 +1,138 @@
+# 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/.
+
+from compare_locales.mozpath import (
+ relpath,
+ join,
+ normpath,
+ dirname,
+ commonprefix,
+ basename,
+ split,
+ splitext,
+ basedir,
+ match,
+ rebase,
+)
+import unittest
+import os
+
+
+class TestPath(unittest.TestCase):
+ SEP = os.sep
+
+ def test_relpath(self):
+ self.assertEqual(relpath('foo', 'foo'), '')
+ self.assertEqual(relpath(self.SEP.join(('foo', 'bar')), 'foo/bar'), '')
+ self.assertEqual(relpath(self.SEP.join(('foo', 'bar')), 'foo'), 'bar')
+ self.assertEqual(relpath(self.SEP.join(('foo', 'bar', 'baz')), 'foo'),
+ 'bar/baz')
+ self.assertEqual(relpath(self.SEP.join(('foo', 'bar')), 'foo/bar/baz'),
+ '..')
+ self.assertEqual(relpath(self.SEP.join(('foo', 'bar')), 'foo/baz'),
+ '../bar')
+ self.assertEqual(relpath('foo/', 'foo'), '')
+ self.assertEqual(relpath('foo/bar/', 'foo'), 'bar')
+
+ def test_join(self):
+ self.assertEqual(join('foo', 'bar', 'baz'), 'foo/bar/baz')
+ self.assertEqual(join('foo', '', 'bar'), 'foo/bar')
+ self.assertEqual(join('', 'foo', 'bar'), 'foo/bar')
+ self.assertEqual(join('', 'foo', '/bar'), '/bar')
+
+ def test_normpath(self):
+ self.assertEqual(normpath(self.SEP.join(('foo', 'bar', 'baz',
+ '..', 'qux'))), 'foo/bar/qux')
+
+ def test_dirname(self):
+ self.assertEqual(dirname('foo/bar/baz'), 'foo/bar')
+ self.assertEqual(dirname('foo/bar'), 'foo')
+ self.assertEqual(dirname('foo'), '')
+ self.assertEqual(dirname('foo/bar/'), 'foo/bar')
+
+ def test_commonprefix(self):
+ self.assertEqual(commonprefix([self.SEP.join(('foo', 'bar', 'baz')),
+ 'foo/qux', 'foo/baz/qux']), 'foo/')
+ self.assertEqual(commonprefix([self.SEP.join(('foo', 'bar', 'baz')),
+ 'foo/qux', 'baz/qux']), '')
+
+ def test_basename(self):
+ self.assertEqual(basename('foo/bar/baz'), 'baz')
+ self.assertEqual(basename('foo/bar'), 'bar')
+ self.assertEqual(basename('foo'), 'foo')
+ self.assertEqual(basename('foo/bar/'), '')
+
+ def test_split(self):
+ self.assertEqual(split(self.SEP.join(('foo', 'bar', 'baz'))),
+ ['foo', 'bar', 'baz'])
+
+ def test_splitext(self):
+ self.assertEqual(splitext(self.SEP.join(('foo', 'bar', 'baz.qux'))),
+ ('foo/bar/baz', '.qux'))
+
+ def test_basedir(self):
+ foobarbaz = self.SEP.join(('foo', 'bar', 'baz'))
+ self.assertEqual(basedir(foobarbaz, ['foo', 'bar', 'baz']), 'foo')
+ self.assertEqual(basedir(foobarbaz, ['foo', 'foo/bar', 'baz']),
+ 'foo/bar')
+ self.assertEqual(basedir(foobarbaz, ['foo/bar', 'foo', 'baz']),
+ 'foo/bar')
+ self.assertEqual(basedir(foobarbaz, ['foo', 'bar', '']), 'foo')
+ self.assertEqual(basedir(foobarbaz, ['bar', 'baz', '']), '')
+
+ def test_match(self):
+ self.assertTrue(match('foo', ''))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/bar'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo'))
+ self.assertTrue(match('foo', '*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/bar/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/bar/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/bar/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/bar/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/*/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '*/bar/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '*/*/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '*/*/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/*/*'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/*/*.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/b*/*z.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/b*r/ba*z.qux'))
+ self.assertFalse(match('foo/bar/baz.qux', 'foo/b*z/ba*r.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '**'))
+ self.assertTrue(match('foo/bar/baz.qux', '**/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '**/bar/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/*.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '**/foo/bar/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/bar/baz.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/bar/*.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/*.qux'))
+ self.assertTrue(match('foo/bar/baz.qux', '**/*.qux'))
+ self.assertFalse(match('foo/bar/baz.qux', '**.qux'))
+ self.assertFalse(match('foo/bar', 'foo/*/bar'))
+ self.assertTrue(match('foo/bar/baz.qux', 'foo/**/bar/**'))
+ self.assertFalse(match('foo/nobar/baz.qux', 'foo/**/bar/**'))
+ self.assertTrue(match('foo/bar', 'foo/**/bar/**'))
+
+ def test_rebase(self):
+ self.assertEqual(rebase('foo', 'foo/bar', 'bar/baz'), 'baz')
+ self.assertEqual(rebase('foo', 'foo', 'bar/baz'), 'bar/baz')
+ self.assertEqual(rebase('foo/bar', 'foo', 'baz'), 'bar/baz')
+
+
+if os.altsep:
+ class TestAltPath(TestPath):
+ SEP = os.altsep
+
+ class TestReverseAltPath(TestPath):
+ def setUp(self):
+ sep = os.sep
+ os.sep = os.altsep
+ os.altsep = sep
+
+ def tearDown(self):
+ self.setUp()
+
+ class TestAltReverseAltPath(TestReverseAltPath):
+ SEP = os.altsep