qubes-installer-qubes-os/anaconda/tests/pylint/markup.py

200 lines
8.4 KiB
Python
Raw Normal View History

# Pango markup pylint module
#
# Copyright (C) 2014 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
# Red Hat Author(s): David Shea <dhsea@redhat.com>
#
import astroid
from pylint.checkers import BaseChecker
from pylint.checkers.utils import check_messages
from pylint.interfaces import IAstroidChecker
import types
import sys
import os
import xml.etree.ElementTree as ET
# markup_necessary not used yet
from pangocheck import markup_nodes, is_markup, markup_match #, markup_necessary
markupMethods = ["set_markup"]
escapeMethods = ["escape_markup"]
# Used for checking translations
podicts = None
i18n_funcs = ["_", "N_", "P_", "C_", "CN_", "CP_"]
i18n_ctxt_funcs = ["C_", "CN_", "CP_"]
class MarkupChecker(BaseChecker):
__implements__ = (IAstroidChecker,)
name = "pango-markup"
msgs = {"W9920" : ("Found invalid pango markup",
"invalid-markup",
"Pango markup could not be parsed"),
"W9921" : ("Found pango markup with invalid element %s",
"invalid-markup-element",
"Pango markup contains invalid elements"),
"W9922" : ("Found % in markup with unescaped parameters",
"unescaped-markup",
"Parameters passed to % in markup not escaped"),
"W9923" : ("Found invalid pango markup in %s translation",
"invalid-translated-markup",
"Translated Pango markup could not be parsed"),
"W9924" : ("Found pango markup with invalid element %s in %s translation",
"invalid-translated-markup-element",
"Translated pango markup contains invalid elements"),
"W9925" : ("Found mis-translated pango markup for language %s",
"invalid-pango-translation",
"The elements or attributes do not match between a pango markup string and its translation"),
"W9926" : ("Found unnecessary pango markup",
"unnecessary-markup",
"Pango markup could be expressed as attribute list"),
}
options = (('translate-markup',
{'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>',
'help' : "Check translations of markup strings"
}),
)
# Check a parsed markup string for invalid tags
def _validate_pango_markup(self, node, root, lang=None):
if root.tag not in markup_nodes:
if lang:
self.add_message("W9924", node=node, args=(root.tag, lang))
else:
self.add_message("W9921", node=node, args=(root.tag,))
else:
for child in root:
self._validate_pango_markup(node, child)
# Attempt to parse a markup string as XML
def _validate_pango_markup_string(self, node, string, lang=None):
try:
# QUIS CUSTODIET IPSOS CUSTODES
# pylint: disable=unescaped-markup
tree = ET.fromstring("<markup>%s</markup>" % string)
# Check if the markup is necessary
# TODO: Turn this on after it's possible to actually do
# anything about it. See https://bugzilla.gnome.org/show_bug.cgi?id=725681
#if not markup_necessary(tree):
# self.add_message("W9926", node=node)
except ET.ParseError:
if lang:
self.add_message("W9923", node=node, args=(lang,))
else:
self.add_message("W9920", node=node)
else:
# Check that all of the elements are valid for pango
self._validate_pango_markup(node, tree)
def __init__(self, linter=None):
BaseChecker.__init__(self, linter)
@check_messages("invalid-markup", "invalid-markup-element", "unescaped-markup", "invalid-translated-markup", "invalid-translated-markup-element", "invalid-pango-translation", "unnecessary-markup")
def visit_const(self, node):
if type(node.value) not in (types.StringType, types.UnicodeType):
return
if not is_markup(node.value):
return
self._validate_pango_markup_string(node, node.value)
# Check translated versions of the string if requested
if self.config.translate_markup:
global podicts
# Check if this is a translatable string
curr = node
i18nFunc = None
while curr.parent:
if isinstance(curr.parent, astroid.CallFunc) and \
getattr(curr.parent.func, "name", "") in i18n_funcs:
i18nFunc = curr.parent
break
curr = curr.parent
if i18nFunc:
# If not done already, import polib and read the translations
if not podicts:
try:
from translatepo import translate_all
except ImportError:
print("Unable to load po translation module")
sys.exit(99)
else:
podicts = translate_all(os.path.join(os.environ.get('top_srcdir', '.'), 'po'))
if i18nFunc.func.name in i18n_ctxt_funcs:
msgctxt = i18nFunc.args[0].value
else:
msgctxt = None
# Loop over all translations for the string
for podict in podicts.values():
try:
node_values = podict.get(node.value, msgctxt)
except KeyError:
continue
for value in node_values:
self._validate_pango_markup_string(node, value, podict.metadata['Language'])
# Check that the markup matches, roughly
if not markup_match(node.value, value):
self.add_message("W9925", node=node, args=(podict.metadata['Language'],))
# Check if this the left side of a % operation
curr = node
formatOp = None
while curr.parent:
if isinstance(curr.parent, astroid.BinOp) and curr.parent.op == "%" and \
curr.parent.left == curr:
formatOp = curr.parent
break
curr = curr.parent
# Check whether the right side of the % operation is escaped
if formatOp:
if isinstance(formatOp.right, astroid.CallFunc):
if getattr(formatOp.right.func, "name", "") not in escapeMethods:
self.add_message("W9922", node=formatOp.right)
# If a tuple, each item in the tuple must be escaped
elif isinstance(formatOp.right, astroid.Tuple):
for elt in formatOp.right.elts:
if not isinstance(elt, astroid.CallFunc) or\
getattr(elt.func, "name", "") not in escapeMethods:
self.add_message("W9922", node=elt)
# If a dictionary, each value must be escaped
elif isinstance(formatOp.right, astroid.Dict):
for item in formatOp.right.items:
if not isinstance(item[1], astroid.CallFunc) or\
getattr(item[1].func, "name", "") not in escapeMethods:
self.add_message("W9922", node=item[1])
else:
self.add_message("W9922", node=formatOp)
def register(linter):
"""required method to auto register this checker """
linter.register_checker(MarkupChecker(linter))