bug 1361037, part 4: move android check detection from checks to project config, r=flod, stas
authorAxel Hecht <axel@pike.org>
Fri, 12 May 2017 23:34:02 +0200
changeset 235 bb991de43bbaeca50e4ced3c42d69a059c6b3d0d
parent 234 1a9277b5b40cf434d6b85ccd546b7fbb91fcad05
child 236 e2174b93de6d89a60208bfdc389d5c5b1c7eb33b
push id48
push useraxel@mozilla.com
push dateFri, 26 May 2017 11:10:47 +0000
reviewersflod, stas
bugs1361037
bug 1361037, part 4: move android check detection from checks to project config, r=flod, stas MozReview-Commit-ID: JqnRAZz2CE6
compare_locales/checks.py
compare_locales/compare.py
compare_locales/tests/test_checks.py
--- a/compare_locales/checks.py
+++ b/compare_locales/checks.py
@@ -17,16 +17,19 @@ class Checker(object):
     '''Abstract class to implement checks per file type.
     '''
     pattern = None
 
     @classmethod
     def use(cls, file):
         return cls.pattern.match(file.file)
 
+    def __init__(self, extra_tests):
+        self.extra_tests = extra_tests
+
     def check(self, refEnt, l10nEnt):
         '''Given the reference and localized Entities, performs checks.
 
         This is a generator yielding tuples of
         - "warning" or "error", depending on what should be reported,
         - tuple of line, column info for the error within the string
         - description string to be shown in the report
         '''
@@ -186,17 +189,21 @@ class DTDChecker(Checker):
     pattern = re.compile('.*\.dtd$')
 
     eref = re.compile('&(%s);' % DTDParser.Name)
     tmpl = '''<!DOCTYPE elem [%s]>
 <elem>%s</elem>
 '''
     xmllist = set(('amp', 'lt', 'gt', 'apos', 'quot'))
 
-    def __init__(self, reference):
+    def __init__(self, extra_tests, reference):
+        super(DTDChecker, self).__init__(extra_tests)
+        self.processContent = False
+        if self.extra_tests is not None and 'android-dtd' in self.extra_tests:
+            self.processContent = True
         self.reference = reference
         self.__known_entities = None
 
     def known_entities(self, refValue):
         if self.__known_entities is None and self.reference is not None:
             self.__known_entities = set()
             for ent in self.reference:
                 self.__known_entities.update(self.entities_for_value(ent.val))
@@ -223,18 +230,16 @@ class DTDChecker(Checker):
     num = re.compile('^%s$' % numPattern)
     lengthPattern = '%s(em|px|ch|cm|in)' % numPattern
     length = re.compile('^%s$' % lengthPattern)
     spec = re.compile(r'((?:min\-)?(?:width|height))\s*:\s*%s' %
                       lengthPattern)
     style = re.compile(r'^%(spec)s\s*(;\s*%(spec)s\s*)*;?$' %
                        {'spec': spec.pattern})
 
-    processContent = None
-
     def check(self, refEnt, l10nEnt):
         """Try to parse the refvalue inside a dummy element, and keep
         track of entities that we need to define to make that work.
 
         Return a checker that offers just those entities.
         """
         refValue, l10nValue = refEnt.val, l10nEnt.val
         # find entities the refValue references,
@@ -258,17 +263,17 @@ class DTDChecker(Checker):
                    (0, 0),
                    "can't parse en-US value", 'xmlparse')
 
         # find entities the l10nValue references,
         # reusing markup from DTDParser.
         l10nlist = self.entities_for_value(l10nValue)
         missing = sorted(l10nlist - reflist)
         _entities = entities + ''.join('<!ENTITY %s "">' % s for s in missing)
-        if self.processContent is not None:
+        if self.processContent:
             self.texthandler.textcontent = ''
             parser.setContentHandler(self.texthandler)
         try:
             parser.parse(StringIO(self.tmpl % (_entities,
                          l10nValue.encode('utf-8'))))
             # also catch stray %
             # if this fails, we need to substract the entity definition
             parser.setContentHandler(self.defaulthandler)
@@ -342,27 +347,20 @@ class DTDChecker(Checker):
                         if u != ru:
                             msgs.append("units for %s don't match "
                                         "(%s != %s)" % (s, u, ru))
                 for s in refMap.iterkeys():
                     msgs.insert(0, '%s only in reference' % s)
                 if msgs:
                     yield ('warning', 0, ', '.join(msgs), 'css')
 
-        if self.processContent is not None:
-            for t in self.processContent(self.texthandler.textcontent):
+        if self.extra_tests is not None and 'android-dtd' in self.extra_tests:
+            for t in self.processAndroidContent(self.texthandler.textcontent):
                 yield t
 
-
-class PrincessAndroid(DTDChecker):
-    """Checker for the string values that Android puts into an XML container.
-
-    http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling  # noqa
-    has more info. Check for unescaped apostrophes and bad unicode escapes.
-    """
     quoted = re.compile("(?P<q>[\"']).*(?P=q)$")
 
     def unicode_escape(self, str):
         """Helper method to try to decode all unicode escapes in a string.
 
         This code uses the standard python decode for unicode-escape, but
         that's somewhat tricky, as its input needs to be ascii. To get to
         ascii, the unicode string gets converted to ascii with
@@ -380,25 +378,21 @@ class PrincessAndroid(DTDChecker):
         except UnicodeDecodeError, e:
             args = list(e.args)
             badstring = args[1][args[2]:args[3]]
             i = len(args[1][:args[2]].decode('unicode-escape'))
             args[2] = i
             args[3] = i + len(badstring)
             raise UnicodeDecodeError(*args)
 
-    @classmethod
-    def use(cls, file):
-        """Use this Checker only for DTD files in embedding/android."""
-        return (file.module in ("embedding/android",
-                                "mobile/android/base") and
-                cls.pattern.match(file.file))
+    def processAndroidContent(self, val):
+        """Check for the string values that Android puts into an XML container.
 
-    def processContent(self, val):
-        """Actual check code.
+        http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling  # noqa
+
         Check for unicode escapes and unescaped quotes and apostrophes,
         if string's not quoted.
         """
         # first, try to decode unicode escapes
         try:
             self.unicode_escape(val)
         except UnicodeDecodeError, e:
             yield ('error', e.args[2], e.args[4], 'android')
@@ -423,16 +417,14 @@ class PrincessAndroid(DTDChecker):
                           u"or \\u0022, or put string in apostrophes."
                 else:
                     msg = u"Apostrophes in Android DTDs need escaping with "\
                           u"\\' or \\u0027, or use \u2019, or put string in "\
                           u"quotes."
                 yield ('error', m.end(0)+offset, msg, 'android')
 
 
-def getChecker(file, reference=None):
+def getChecker(file, reference=None, extra_tests=None):
     if PropertiesChecker.use(file):
-        return PropertiesChecker()
-    if PrincessAndroid.use(file):
-        return PrincessAndroid(reference)
+        return PropertiesChecker(extra_tests)
     if DTDChecker.use(file):
-        return DTDChecker(reference)
+        return DTDChecker(extra_tests, reference)
     return None
--- a/compare_locales/compare.py
+++ b/compare_locales/compare.py
@@ -431,17 +431,17 @@ class ContentComparer:
         l10n_list = l10n_map.keys()
         l10n_list.sort()
         ar = AddRemove()
         ar.set_left(ref_list)
         ar.set_right(l10n_list)
         report = missing = obsolete = changed = unchanged = keys = 0
         missings = []
         skips = []
-        checker = getChecker(l10n, reference=ref[0])
+        checker = getChecker(l10n, reference=ref[0], extra_tests=extra_tests)
         for action, item_or_pair in ar:
             if action == 'delete':
                 # missing entity
                 _rv = self.notify('missingEntity', l10n, item_or_pair)
                 if _rv == "ignore":
                     continue
                 if _rv == "error":
                     # only add to missing entities for l10n-merge on error,
--- a/compare_locales/tests/test_checks.py
+++ b/compare_locales/tests/test_checks.py
@@ -252,17 +252,17 @@ class TestAndroid(unittest.TestCase):
                       (14, len(v) + 14), ())
 
     def test_android_dtd(self):
         """Testing the actual android checks. The logic is involved,
         so this is a lot of nitty gritty detail tests.
         """
         f = File("embedding/android/strings.dtd", "strings.dtd",
                  "embedding/android")
-        checker = getChecker(f)
+        checker = getChecker(f, extra_tests=['android-dtd'])
         # good string
         ref = self.getDTDEntity("plain string")
         l10n = self.getDTDEntity("plain localized string")
         self.assertEqual(tuple(checker.check(ref, l10n)),
                          ())
         # dtd warning
         l10n = self.getDTDEntity("plain localized string &ref;")
         self.assertEqual(tuple(checker.check(ref, l10n)),
@@ -328,17 +328,17 @@ class TestAndroid(unittest.TestCase):
         l10n = self.getDTDEntity(u"\u9690"*14+"\u006"+"  "+"\u0064")
         self.assertEqual(tuple(checker.check(ref, l10n)),
                          (('error', 14, 'truncated \\uXXXX escape',
                            'android'),))
 
     def test_android_prop(self):
         f = File("embedding/android/strings.properties", "strings.properties",
                  "embedding/android")
-        checker = getChecker(f)
+        checker = getChecker(f, extra_tests=['android-dtd'])
         # good plain string
         ref = self.getEntity("plain string")
         l10n = self.getEntity("plain localized string")
         self.assertEqual(tuple(checker.check(ref, l10n)),
                          ())
         # no dtd warning
         ref = self.getEntity("plain string")
         l10n = self.getEntity("plain localized string &ref;")