Skip to content

Instantly share code, notes, and snippets.

@gergelypolonkai
Created December 22, 2016 08:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gergelypolonkai/1a16a47e5a1971ca33e58bdfd88c5059 to your computer and use it in GitHub Desktop.
Save gergelypolonkai/1a16a47e5a1971ca33e58bdfd88c5059 to your computer and use it in GitHub Desktop.
Finding untranslated Python strings
#! /usr/bin/env python3
import ast
import gettext
from gettext import gettext as _
import sys
def get_func_name(node):
cls = node.__class__.__name__
if cls == 'Call':
return get_func_name(node.func)
elif cls == 'Attribute':
return '{}.{}'.format(
get_func_name(node.value),
node.attr)
elif cls == 'Name':
return get_func_name(node.id)
elif cls == 'str':
return node
elif cls == 'Str':
return "<String literal>"
elif cls == 'Subscript':
return '{}[{}]'.format(get_func_name(node.value),
get_func_name(node.slice))
elif cls == 'Index':
return get_func_name(node.value)
else:
print('ERROR: Unknown class: {}'.format(cls))
class ShowStrings(ast.NodeVisitor):
TRANSLATION_FUNCTIONS = [
'_', # gettext.gettext is often imported under this name
'gettext',
'gettext.gettext',
# FIXME: this list is pretty much incomplete
]
UNTRANSLATED = 'untranslated 9'
def __init__(self, filename=None):
super(ShowStrings, self).__init__()
self.in_call = []
self.filename = filename or '<parsed string>'
def visit_with_trace(self, node, func):
self.in_call.append((func, node.lineno, node.col_offset))
self.visit(node)
self.in_call.pop()
def visit_Str(self, node):
# TODO: make it possible to ignore untranslated strings
# TODO: make this ignore docstrings
# if we are not in a translator function, issue a warning
if not self.in_call or \
self.in_call[-1][0] not in self.TRANSLATION_FUNCTIONS:
try:
funcname = self.in_call[-1][0]
except IndexError:
funcname = None
funcall_msg = "outside a function call" if funcname is None \
else "inside a call to {funcname}".format(
funcname=funcname)
print("WARNING: Untranslated string found at "
"{filename}:{line}:{col} {funcall_msg}".format(
filename=self.filename,
line=node.lineno,
col=node.col_offset,
funcall_msg=funcall_msg))
def visit_Call(self, node):
# if we are in a translator function, issue a warninc
if self.in_call and self.in_call[-1][0] in self.TRANSLATION_FUNCTIONS:
print("WARNING: function call within a translation function at "
"{filename}:{line}:{col}".format(filename=self.filename,
line=node.lineno,
col=node.col_offset))
funcname = get_func_name(node)
for arg in node.args:
self.visit_with_trace(arg, funcname)
for kwarg in node.keywords:
self.visit_with_trace(kwarg.value, funcname)
def generic_visit(self, node):
# if we are inside a translator function, issue a warning
if self.in_call and self.in_call[-1][0] in self.TRANSLATION_FUNCTIONS:
# Some ast nodes, like Add don’t have position information
if hasattr(node, 'lineno'):
print("WARNING: something not a string ({klass}) found in a "
"translation function at {filename}:{line}:{col}".format(
filename=self.filename,
klass=node.__class__.__name__,
line=node.lineno,
col=node.col_offset))
else:
print("WARNING: something not a string ({klass}) found in a "
"translation function. Position unknown; function call "
"is at {filename}:{line}:{col}".format(
filename=self.filename,
klass=node.__class__.__name__,
line=self.in_call[-1][1],
col=self.in_call[-1][2]))
super(ShowStrings, self).generic_visit(node)
def tst(*args, **kwargs):
pass
def actual_tests():
_('translated 1')
tst(_('translated 2'))
tst(gettext.gettext('translated 3'))
tst(_('translated 4') + 'native 1')
tst('native 2'
'native 3')
tst(_('native 4' + 'native 5'))
tst('native 6', b='native 7')
tst(_(tst('hello!')))
if __name__ == '__main__':
try:
filename = sys.argv[1]
except IndexError:
filename = __file__
print("INFO: No filename specified, checking myself.")
with open(filename, 'r') as f:
code = f.read()
root = ast.parse(code)
show_strings = ShowStrings(filename=filename)
show_strings.visit(root)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment