зеркало из https://github.com/mozilla/bedrock.git
Bug 774234: Add tests for l10n blocks and langs support.
Refactor template handling and parsing.
This commit is contained in:
Родитель
106ba2120f
Коммит
872e98b11e
|
@ -6,5 +6,5 @@ from django.conf.urls.defaults import *
|
|||
from mozorg.util import page
|
||||
|
||||
urlpatterns = patterns('',
|
||||
page('eula', 'eula/index.html'),
|
||||
)
|
||||
page('eula', 'legal/eula.html'),
|
||||
)
|
||||
|
|
|
@ -23,3 +23,18 @@ def secure_required(view_func):
|
|||
def server_error_view(request, template_name='500.html'):
|
||||
"""500 error handler that runs context processors."""
|
||||
return l10n_utils.render(request, template_name)
|
||||
|
||||
|
||||
# backported from Django 1.4
|
||||
# https://github.com/django/django/blob/master/django/utils/functional.py#L34
|
||||
class cached_property(object):
|
||||
"""
|
||||
Decorator that converts a method with a single self argument into a
|
||||
property cached on the instance.
|
||||
"""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance, type):
|
||||
res = instance.__dict__[self.func.__name__] = self.func(instance)
|
||||
return res
|
||||
|
|
|
@ -3,35 +3,27 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import datetime
|
||||
import errno
|
||||
import itertools
|
||||
import re
|
||||
import os, errno
|
||||
import os
|
||||
from os import path
|
||||
from optparse import make_option
|
||||
import codecs
|
||||
from contextlib import closing
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.conf import settings
|
||||
|
||||
from jinja2 import Environment, TemplateNotFound
|
||||
from jinja2.parser import Parser
|
||||
from jinja2 import Environment
|
||||
|
||||
from bedrock_util import cached_property
|
||||
|
||||
|
||||
def l10n_file(*args):
|
||||
return path.join(settings.ROOT, 'locale', *args)
|
||||
|
||||
|
||||
def l10n_tmpl(tmpl, lang):
|
||||
return l10n_file(lang, 'templates', tmpl)
|
||||
|
||||
|
||||
def app_tmpl(tmpl):
|
||||
app = tmpl[:tmpl.index('/')]
|
||||
return path.join(settings.ROOT, 'apps', app, 'templates', tmpl)
|
||||
|
||||
|
||||
def list_templates():
|
||||
"""List all the templates in all the installed apps"""
|
||||
|
||||
|
@ -45,27 +37,16 @@ def list_templates():
|
|||
name, ext = os.path.splitext(filename)
|
||||
|
||||
if ext in ['.txt', '.html']:
|
||||
full_path = os.path.join(root, filename)
|
||||
yield full_path.replace(tmpl_dir, '').lstrip('/')
|
||||
yield os.path.join(root, filename)
|
||||
|
||||
|
||||
def update_templates(langs):
|
||||
"""List templates with outdated/incorrect l10n blocks"""
|
||||
|
||||
for tmpl in list_templates():
|
||||
print "%s..." % tmpl
|
||||
|
||||
# Parse the reference template that will provide new content
|
||||
# and only get the blocks from it
|
||||
parser = L10nParser()
|
||||
blocks = list(parser.parse_template(app_tmpl(tmpl),
|
||||
only_blocks=True))
|
||||
|
||||
for lang in langs:
|
||||
if path.exists(l10n_tmpl(tmpl, lang)):
|
||||
update_template(tmpl, blocks, lang)
|
||||
else:
|
||||
copy_template(tmpl, blocks, lang)
|
||||
template = L10nTemplate(tmpl)
|
||||
print "%s..." % template.rel_path
|
||||
template.process(langs)
|
||||
|
||||
|
||||
def get_todays_version():
|
||||
|
@ -82,22 +63,102 @@ def ensure_dir_exists(path):
|
|||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST:
|
||||
pass
|
||||
else: raise
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def update_template(tmpl, blocks, lang):
|
||||
"""Detect outdated l10n blocks and update the template"""
|
||||
def write_block(block, dest, force_was=False):
|
||||
"""Write out a block to an l10n template"""
|
||||
dest.write('{%% l10n %s %%}\n' % block['name'])
|
||||
dest.write(block['main'])
|
||||
if block['was'] or force_was:
|
||||
dest.write('\n{% was %}')
|
||||
dest.write('\n%s' % block['was'] if block['was'] else '')
|
||||
dest.write('\n{% endl10n %}')
|
||||
dest.write('\n\n')
|
||||
|
||||
def get_ref_block(name):
|
||||
|
||||
class L10nTemplate(object):
|
||||
|
||||
def __init__(self, template=None, source=None):
|
||||
"""
|
||||
Initialize the template class.
|
||||
|
||||
:param template:
|
||||
Full path to a template file.
|
||||
:param source:
|
||||
Template text as a string if there is no template file.
|
||||
"""
|
||||
self.full_path = template
|
||||
self.source = source
|
||||
self.parser = L10nParser()
|
||||
|
||||
@cached_property
|
||||
def rel_path(self):
|
||||
"""
|
||||
Return the part of the template path after the 'templates' directory.
|
||||
"""
|
||||
args = self.full_path.split(path.sep)
|
||||
args = args[args.index('templates') + 1:]
|
||||
return path.join(*args)
|
||||
|
||||
def l10n_path(self, lang):
|
||||
"""
|
||||
Return the path to the localized version of the template for lang.
|
||||
"""
|
||||
return l10n_file(lang, 'templates', self.rel_path)
|
||||
|
||||
@cached_property
|
||||
def blocks(self):
|
||||
if self.full_path:
|
||||
blocks = self.parser.parse_template(self.full_path,
|
||||
only_blocks=True)
|
||||
elif self.source:
|
||||
blocks = self.parser.parse(self.source, only_blocks=True)
|
||||
return tuple(blocks)
|
||||
|
||||
def blocks_for_lang(self, lang):
|
||||
"""Filter blocks to only those that allow this locale or all locales."""
|
||||
return tuple(b for b in self.blocks
|
||||
if not b['locales'] or lang in b['locales'])
|
||||
|
||||
def process(self, langs):
|
||||
"""
|
||||
Update existing templates and create new ones for specified langs.
|
||||
"""
|
||||
for lang in langs:
|
||||
if path.exists(self.l10n_path(lang)):
|
||||
self.update(lang)
|
||||
else:
|
||||
self.copy(lang)
|
||||
|
||||
def copy(self, lang):
|
||||
"""Create a new l10n template by copying the l10n blocks"""
|
||||
blocks = self.blocks_for_lang(lang)
|
||||
if not blocks:
|
||||
return
|
||||
|
||||
dest_file = self.l10n_path(lang)
|
||||
|
||||
# Make sure the templates directory for this locale and app exists
|
||||
ensure_dir_exists(os.path.dirname(dest_file))
|
||||
|
||||
with codecs.open(dest_file, 'w', 'utf-8') as dest:
|
||||
dest.write('{# Version: %s #}\n\n' % get_todays_version())
|
||||
dest.write('{%% extends "%s" %%}\n\n' % self.rel_path)
|
||||
|
||||
for block in blocks:
|
||||
write_block(block, dest)
|
||||
|
||||
print '%s: %s' % (lang, self.rel_path)
|
||||
|
||||
def _get_ref_block(self, name, blocks=None):
|
||||
"""Return the reference block"""
|
||||
blocks = blocks or self.blocks
|
||||
return next((b for b in blocks if b['name'] == name), None)
|
||||
|
||||
for block in blocks:
|
||||
if block['name'] == name:
|
||||
return block
|
||||
|
||||
def transfer_content(l10n_block, ref_block):
|
||||
def _transfer_content(self, l10n_block, ref_block):
|
||||
"""Transfer any new content from the reference block"""
|
||||
|
||||
if ref_block:
|
||||
# Update if the l10n file is older than this block
|
||||
if l10n_block['version'] < ref_block['version']:
|
||||
|
@ -108,100 +169,62 @@ def update_template(tmpl, blocks, lang):
|
|||
l10n_block['main'] = ref_block['main']
|
||||
l10n_block['locales'] = ref_block['locales']
|
||||
|
||||
return l10n_block
|
||||
def update(self, lang):
|
||||
"""Detect outdated l10n blocks and update the template"""
|
||||
blocks = self.blocks_for_lang(lang)
|
||||
if not blocks:
|
||||
return
|
||||
|
||||
parser = L10nParser()
|
||||
file_version = None
|
||||
dest_tmpl = l10n_tmpl(tmpl, lang)
|
||||
halted = False
|
||||
written_blocks = []
|
||||
file_version = None
|
||||
parser = L10nParser()
|
||||
dest_tmpl = self.l10n_path(lang)
|
||||
written_blocks = []
|
||||
|
||||
# Make sure the templates directory for this locale and app exists
|
||||
ensure_dir_exists(os.path.dirname(dest_tmpl))
|
||||
# Make sure the templates directory for this locale and app exists
|
||||
ensure_dir_exists(os.path.dirname(dest_tmpl))
|
||||
|
||||
# Parse the l10n template, run through it and update it where
|
||||
# appropriate into a new template file
|
||||
with closing(StringIO()) as buffer:
|
||||
for token in parser.parse_template(dest_tmpl, strict=False,
|
||||
halt_on_content=True):
|
||||
if not token:
|
||||
# If False is returned, that means a content block
|
||||
# exists so we don't do anything to the template since
|
||||
# it's customized
|
||||
return
|
||||
elif token[0] == 'content':
|
||||
buffer.write(token[1])
|
||||
elif token[0] == 'version':
|
||||
buffer.write('{# Version: %s #}' % get_todays_version())
|
||||
file_version = token[1]
|
||||
elif token[0] == 'block':
|
||||
if not file_version:
|
||||
raise Exception('l10n file version tag does not exist '
|
||||
'before initial l10n block')
|
||||
# Parse the l10n template, run through it and update it where
|
||||
# appropriate into a new template file
|
||||
with closing(StringIO()) as buffer:
|
||||
for token in parser.parse_template(dest_tmpl, strict=False,
|
||||
halt_on_content=True):
|
||||
if not token:
|
||||
# If False is returned, that means a content block
|
||||
# exists so we don't do anything to the template since
|
||||
# it's customized
|
||||
return
|
||||
elif token[0] == 'content':
|
||||
buffer.write(token[1])
|
||||
elif token[0] == 'version':
|
||||
buffer.write('{# Version: %s #}' % get_todays_version())
|
||||
file_version = token[1]
|
||||
elif token[0] == 'block':
|
||||
if not file_version:
|
||||
raise Exception('l10n file version tag does not exist '
|
||||
'before initial l10n block')
|
||||
|
||||
# We have an l10n block, set its version and keep
|
||||
# track of it for later use
|
||||
l10n_block = token[1]
|
||||
l10n_block['version'] = file_version
|
||||
name = l10n_block['name']
|
||||
written_blocks.append(name)
|
||||
# We have an l10n block, set its version and keep
|
||||
# track of it for later use
|
||||
l10n_block = token[1]
|
||||
l10n_block['version'] = file_version
|
||||
name = l10n_block['name']
|
||||
written_blocks.append(name)
|
||||
|
||||
# Update the block and write it out
|
||||
l10n_block = transfer_content(l10n_block,
|
||||
get_ref_block(name))
|
||||
locales = l10n_block['locales']
|
||||
if not locales or lang in locales:
|
||||
# Update the block and write it out
|
||||
self._transfer_content(l10n_block,
|
||||
self._get_ref_block(name, blocks))
|
||||
write_block(l10n_block, buffer)
|
||||
|
||||
# Check for any missing blocks
|
||||
for block in blocks:
|
||||
if block['name'] not in written_blocks:
|
||||
locales = block['locales']
|
||||
if not locales or lang in locales:
|
||||
buffer.write('\n\n')
|
||||
# Check for any missing blocks
|
||||
for block in blocks:
|
||||
if block['name'] not in written_blocks:
|
||||
write_block(block, buffer)
|
||||
|
||||
# Write out the result to the l10n template
|
||||
with codecs.open(dest_tmpl, 'w', 'utf-8') as dest:
|
||||
dest.write(buffer.getvalue())
|
||||
# Write out the result to the l10n template
|
||||
with codecs.open(dest_tmpl, 'w', 'utf-8') as dest:
|
||||
dest.write(buffer.getvalue())
|
||||
|
||||
print '%s: %s' % (lang, tmpl)
|
||||
|
||||
def write_block(block, dest, force_was=False):
|
||||
"""Write out a block to an l10n template"""
|
||||
|
||||
dest.write('{%% l10n %s %%}\n' % block['name'])
|
||||
dest.write(block['main'])
|
||||
if block['was'] or force_was:
|
||||
dest.write('\n{% was %}')
|
||||
dest.write('\n%s' % block['was'] if block['was'] else '')
|
||||
dest.write('\n{% endl10n %}')
|
||||
|
||||
|
||||
def copy_template(tmpl, blocks, lang):
|
||||
"""Create a new l10n template by copying the l10n blocks"""
|
||||
|
||||
dest_file = l10n_tmpl(tmpl, lang)
|
||||
|
||||
#If there is only one block and not for current lang
|
||||
# no need to create the file.
|
||||
if len(blocks) == 1 and not lang in blocks[0]['locales']:
|
||||
return
|
||||
|
||||
if blocks:
|
||||
# Make sure the templates directory for this locale and app exists
|
||||
ensure_dir_exists(os.path.dirname(dest_file))
|
||||
|
||||
with codecs.open(dest_file, 'w', 'utf-8') as dest:
|
||||
dest.write('{# Version: %s #}\n\n' % get_todays_version())
|
||||
dest.write('{%% extends "%s" %%}\n\n' % tmpl)
|
||||
|
||||
for block in blocks:
|
||||
#Copy only for the allowed locales
|
||||
locales = block['locales']
|
||||
if not locales or lang in locales:
|
||||
write_block(block, dest)
|
||||
dest.write('\n\n')
|
||||
print '%s: %s' % (lang, self.rel_path)
|
||||
|
||||
|
||||
class L10nParser():
|
||||
|
@ -273,7 +296,7 @@ class L10nParser():
|
|||
|
||||
if not version:
|
||||
raise Exception('Invalid version metadata in '
|
||||
'template: %s '% self.tmpl)
|
||||
'template: %s' % self.tmpl)
|
||||
|
||||
yield ('version', version)
|
||||
self.scan_until('comment_end')
|
||||
|
@ -341,7 +364,7 @@ class L10nParser():
|
|||
if not block_version:
|
||||
raise Exception("Invalid l10n block declaration: "
|
||||
"bad version '%s' in %s"
|
||||
% (block_name, self.tmpl))
|
||||
% (block_version, self.tmpl))
|
||||
return block_version
|
||||
|
||||
def parse_block(self, strict=True):
|
||||
|
@ -353,48 +376,31 @@ class L10nParser():
|
|||
|
||||
self.scan_ignore('whitespace')
|
||||
|
||||
operator = self.scan_next('operator')
|
||||
if operator == ',' or operator == ';':
|
||||
self.scan_ignore('whitespace')
|
||||
version_str = self.scan_next('integer')
|
||||
if version_str:
|
||||
block_version = self.get_block_version(version_str)
|
||||
else: #We have new template
|
||||
self.scan_ignore('whitespace')
|
||||
locale_flag = self.scan_next('name')
|
||||
if self.scan_next('operator') == '=':
|
||||
past_token = None
|
||||
while True:
|
||||
token = self.scan_next('name')
|
||||
operator = self.scan_next('operator')
|
||||
if operator == ',':
|
||||
if past_token:
|
||||
token = '%s-%s' % (past_token, token)
|
||||
past_token = None
|
||||
locales.append(token)
|
||||
continue
|
||||
elif operator == '-':
|
||||
past_token = token
|
||||
continue
|
||||
elif not operator:
|
||||
if past_token:
|
||||
token = '%s-%s' % (past_token, token)
|
||||
past_token = None
|
||||
locales.append(token)
|
||||
break
|
||||
self.scan_ignore('whitespace')
|
||||
self.scan_next('operator')
|
||||
self.scan_ignore('whitespace')
|
||||
|
||||
version_str = self.scan_next('integer')
|
||||
block_version = self.get_block_version(version_str)
|
||||
# Grab the locales if provided
|
||||
prev_sub = False
|
||||
for _, token_type, token_value in self.tokens:
|
||||
if token_type in ['integer', 'block_end']:
|
||||
break
|
||||
if token_type == 'operator' and token_value in [',', '=']:
|
||||
continue
|
||||
if token_type == 'name' and token_value != 'locales':
|
||||
if prev_sub:
|
||||
locales[-1] += token_value
|
||||
prev_sub = False
|
||||
else:
|
||||
locales.append(token_value)
|
||||
if token_type == 'operator' and token_value == '-':
|
||||
locales[-1] += '-'
|
||||
prev_sub = True
|
||||
|
||||
if token_type == 'integer':
|
||||
block_version = self.get_block_version(token_value)
|
||||
self.scan_until('block_end')
|
||||
elif strict:
|
||||
raise Exception("Invalid l10n block declaration: "
|
||||
"missing date for block '%s' in %s"
|
||||
% (block_name, self.tmpl))
|
||||
|
||||
self.scan_until('block_end')
|
||||
(main, was_) = self.block_content()
|
||||
yield ('block', {'name': block_name,
|
||||
'version': block_version,
|
||||
|
@ -467,6 +473,6 @@ class Command(BaseCommand):
|
|||
langs = args
|
||||
else:
|
||||
langs = os.listdir(l10n_file())
|
||||
langs = filter(lambda x: x[0] != '.' , langs)
|
||||
langs = filter(lambda x: x[0] != '.', langs)
|
||||
|
||||
update_templates(langs)
|
||||
|
|
|
@ -30,10 +30,32 @@ class L10nBlockExtension(Extension):
|
|||
|
||||
# Block name is mandatory.
|
||||
name = parser.stream.expect('name').value
|
||||
locales = []
|
||||
|
||||
# Comma optional.
|
||||
parser.stream.skip_if('comma')
|
||||
|
||||
# Grab the locales if provided
|
||||
if parser.stream.current.type == 'name':
|
||||
parser.stream.skip() # locales
|
||||
parser.stream.skip() # assign (=)
|
||||
prev_sub = False
|
||||
while parser.stream.current.type not in ['integer', 'block_end']:
|
||||
parser.stream.skip_if('comma')
|
||||
parser.stream.skip_if('assign')
|
||||
token = parser.stream.current
|
||||
if token.type in ['integer', 'block_end']:
|
||||
break
|
||||
if token.type == 'name':
|
||||
if prev_sub:
|
||||
locales[-1] += token.value
|
||||
prev_sub = False
|
||||
else:
|
||||
locales.append(token.value)
|
||||
if token.type == 'sub':
|
||||
locales[-1] += '-'
|
||||
prev_sub = True
|
||||
parser.stream.next()
|
||||
|
||||
# Add version if provided.
|
||||
if parser.stream.current.type == 'integer':
|
||||
version = int(parser.parse_expression().value)
|
||||
|
@ -55,6 +77,7 @@ class L10nBlockExtension(Extension):
|
|||
node.set_lineno(lineno)
|
||||
node.name = '__l10n__{0}'.format(name)
|
||||
node.version = version # For debugging only, for now.
|
||||
node.locales = locales
|
||||
node.body = body
|
||||
# I *think*, `true` would mean that variable assignments inside this
|
||||
# block do not persist beyond this block (like a `with` block).
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
from tempfile import TemporaryFile
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
class TempFileMixin(object):
|
||||
"""Provide a method for getting a temp file that is removed when closed."""
|
||||
def tempfile(self, data=None):
|
||||
tempf = TemporaryFile()
|
||||
if data:
|
||||
tempf.write(dedent(data))
|
||||
tempf.seek(0)
|
||||
return tempf
|
|
@ -1,27 +1,42 @@
|
|||
# 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 codecs
|
||||
from os import path
|
||||
from StringIO import StringIO
|
||||
from textwrap import dedent
|
||||
|
||||
from django.utils import unittest
|
||||
|
||||
from l10n_utils.management.commands.l10n_check import (list_templates,
|
||||
L10nParser)
|
||||
from mock import MagicMock, patch
|
||||
|
||||
from l10n_utils.management.commands.l10n_check import (
|
||||
get_todays_version,
|
||||
L10nParser,
|
||||
L10nTemplate,
|
||||
list_templates,
|
||||
update_templates,
|
||||
)
|
||||
|
||||
|
||||
ROOT = path.join(path.dirname(path.abspath(__file__)), 'test_files')
|
||||
TEMPLATE_DIRS = (path.join(ROOT, 'templates'),)
|
||||
|
||||
|
||||
class TestL10nCheck(unittest.TestCase):
|
||||
def _get_block(self, blocks, name):
|
||||
"""Out of all blocks, grab the one with the specified name."""
|
||||
try:
|
||||
return filter(lambda b: b['name'] == name, blocks)[0]
|
||||
except IndexError:
|
||||
return None
|
||||
for b in blocks:
|
||||
if b['name'] == name:
|
||||
return b
|
||||
return None
|
||||
|
||||
def test_list_templates(self):
|
||||
"""Make sure we capture both html and txt templates."""
|
||||
TEMPLATES = ['mozorg/home.html',
|
||||
'mozorg/emails/other.txt']
|
||||
tmpls = filter(lambda tmpl: tmpl in TEMPLATES,
|
||||
list_templates())
|
||||
tmpls = [t for t in list_templates()
|
||||
if L10nTemplate(t).rel_path in TEMPLATES]
|
||||
assert len(tmpls) == len(TEMPLATES)
|
||||
|
||||
def test_parse_templates(self):
|
||||
|
@ -29,14 +44,15 @@ class TestL10nCheck(unittest.TestCase):
|
|||
correctly."""
|
||||
|
||||
parser = L10nParser()
|
||||
blocks = parser.parse('foo bar bizzle what? '
|
||||
'{% l10n baz, 20110914 %}'
|
||||
'mumble'
|
||||
'{% was %}'
|
||||
'wased'
|
||||
'{% endl10n %}'
|
||||
'qux',
|
||||
only_blocks=True)
|
||||
blocks = parser.parse("""
|
||||
foo bar bizzle what?
|
||||
{% l10n baz, 20110914 %}
|
||||
mumble
|
||||
{% was %}
|
||||
wased
|
||||
{% endl10n %}
|
||||
qux
|
||||
""", only_blocks=True)
|
||||
|
||||
baz = self._get_block(blocks, 'baz')
|
||||
|
||||
|
@ -44,16 +60,18 @@ class TestL10nCheck(unittest.TestCase):
|
|||
self.assertEqual(baz['was'], 'wased')
|
||||
self.assertEqual(baz['version'], 20110914)
|
||||
|
||||
blocks = parser.parse('foo bar bizzle what? '
|
||||
'{% l10n baz ; locales=ru,bn-IN ; 20110914 %}'
|
||||
'mumble'
|
||||
'{% endl10n %}'
|
||||
'qux',
|
||||
only_blocks=True)
|
||||
blocks = parser.parse("""
|
||||
foo bar bizzle what?
|
||||
{% l10n baz locales=ru,bn-IN,fr 20110914 %}
|
||||
mumble
|
||||
{% endl10n %}
|
||||
qux
|
||||
""", only_blocks=True)
|
||||
|
||||
baz = self._get_block(blocks, 'baz')
|
||||
self.assertEqual(baz['main'], 'mumble')
|
||||
self.assertEqual(baz['locales'], ['ru', 'bn-IN'])
|
||||
self.assertEqual(baz['locales'], ['ru', 'bn-IN', 'fr'])
|
||||
self.assertEqual(baz['version'], 20110914)
|
||||
|
||||
def test_content_halt(self):
|
||||
"""Make sure the parser will halt on the content block if told
|
||||
|
@ -68,6 +86,160 @@ class TestL10nCheck(unittest.TestCase):
|
|||
|
||||
self.assertEqual(last_token, False)
|
||||
|
||||
# I need help writing tests for copy_template and update_template,
|
||||
# which read files from the filesystem and write to it. I think I
|
||||
# need to mock those somehow.
|
||||
def test_filter_blocks(self):
|
||||
"""Should return a list of blocks appropriate for a given lang"""
|
||||
template = L10nTemplate(source="""
|
||||
{% l10n dude locales=fr,es-ES,ru 20121212 %}
|
||||
This aggression will not stand, man.
|
||||
{% endl10n %}
|
||||
{% l10n walter, locales=es-ES,ru 20121212 %}
|
||||
I'm stayin'. Finishin' my coffee.
|
||||
{% endl10n %}
|
||||
{% l10n donnie 20121212 %}
|
||||
Phone's ringing Dude.
|
||||
{% endl10n %}
|
||||
""")
|
||||
|
||||
lang_blocks = template.blocks_for_lang('fr')
|
||||
self.assertEqual(len(lang_blocks), 2)
|
||||
self.assertEqual(lang_blocks[0]['name'], 'dude')
|
||||
self.assertEqual(lang_blocks[1]['name'], 'donnie')
|
||||
|
||||
lang_blocks = template.blocks_for_lang('es-ES')
|
||||
self.assertEqual(len(lang_blocks), 3)
|
||||
self.assertEqual(lang_blocks[0]['name'], 'dude')
|
||||
self.assertEqual(lang_blocks[1]['name'], 'walter')
|
||||
self.assertEqual(lang_blocks[2]['name'], 'donnie')
|
||||
|
||||
lang_blocks = template.blocks_for_lang('pt-BR')
|
||||
self.assertEqual(len(lang_blocks), 1)
|
||||
self.assertEqual(lang_blocks[0]['name'], 'donnie')
|
||||
|
||||
@patch('l10n_utils.management.commands.l10n_check.settings.ROOT', ROOT)
|
||||
@patch('l10n_utils.management.commands.l10n_check.list_templates')
|
||||
@patch('l10n_utils.management.commands.l10n_check.L10nTemplate.copy')
|
||||
@patch('l10n_utils.management.commands.l10n_check.L10nTemplate.update')
|
||||
def test_process_template(self, update_mock, copy_mock, lt_mock):
|
||||
"""
|
||||
template.process() should update existing templates and create missing
|
||||
ones. It should only do so for the right locales.
|
||||
"""
|
||||
lt_mock.return_value = [
|
||||
path.join(TEMPLATE_DIRS[0], 'l10n_blocks_with_langs.html'),
|
||||
path.join(TEMPLATE_DIRS[0], 'l10n_blocks_without_langs.html'),
|
||||
]
|
||||
update_templates(['de'])
|
||||
copy_mock.assert_called_once_with('de')
|
||||
update_mock.assert_called_once_with('de')
|
||||
|
||||
def test_blocks_called_once(self):
|
||||
"""
|
||||
Test that the cached_property decorator really works in our situation.
|
||||
"""
|
||||
template = L10nTemplate(source="""
|
||||
{% l10n donnie 20121212 %}
|
||||
Phone's ringing Dude.
|
||||
{% endl10n %}
|
||||
""")
|
||||
with patch.object(template, 'parser') as mock_parser:
|
||||
template.blocks
|
||||
template.blocks_for_lang('de')
|
||||
template.blocks
|
||||
self.assertEqual(mock_parser.parse.call_count, 1)
|
||||
|
||||
def test_update_template_no_lang(self):
|
||||
"""
|
||||
template.update() should skip files without blocks for the given locale.
|
||||
"""
|
||||
template = L10nTemplate(path.join(TEMPLATE_DIRS[0],
|
||||
'l10n_blocks_with_langs.html'))
|
||||
# cause the template to be read and parsed before mocking open
|
||||
template.blocks
|
||||
codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open'
|
||||
open_mock = MagicMock(spec=file)
|
||||
with patch(codecs_open, open_mock):
|
||||
template.update('zh-TW')
|
||||
file_handle = open_mock.return_value.__enter__.return_value
|
||||
assert not file_handle.write.called
|
||||
template.update('de')
|
||||
assert file_handle.write.called
|
||||
|
||||
@patch('l10n_utils.management.commands.l10n_check.settings.ROOT', ROOT)
|
||||
def test_update_template(self):
|
||||
"""
|
||||
template.update() should update lang specific templates.
|
||||
"""
|
||||
template = L10nTemplate(path.join(TEMPLATE_DIRS[0],
|
||||
'l10n_blocks_with_langs.html'))
|
||||
# cause the template to be read and parsed before mocking open
|
||||
template.blocks
|
||||
codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open'
|
||||
open_mock = MagicMock(spec=file)
|
||||
open_buffer = StringIO()
|
||||
# for writing the new file
|
||||
open_mock.return_value.__enter__.return_value = open_buffer
|
||||
# for reading the old file
|
||||
open_mock().read.return_value = codecs.open(
|
||||
template.l10n_path('de')).read()
|
||||
|
||||
with patch(codecs_open, open_mock):
|
||||
template.update('de')
|
||||
|
||||
# braces doubled for .format()
|
||||
good_value = dedent("""\
|
||||
{{# Version: {0} #}}
|
||||
|
||||
{{% extends "l10n_blocks_with_langs.html" %}}
|
||||
|
||||
{{% l10n donnie %}}
|
||||
Phone's ringing Dude.
|
||||
{{% was %}}
|
||||
I am the walrus.
|
||||
{{% endl10n %}}\n\n
|
||||
""".format(get_todays_version()))
|
||||
self.assertEqual(open_buffer.getvalue(), good_value)
|
||||
|
||||
def test_copy_template_no_lang(self):
|
||||
"""
|
||||
template.copy() should skip files with no blocks for the given locale.
|
||||
:return:
|
||||
"""
|
||||
template = L10nTemplate(path.join(TEMPLATE_DIRS[0],
|
||||
'l10n_blocks_with_langs.html'))
|
||||
# cause the template to be read and parsed before mocking open
|
||||
template.blocks
|
||||
codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open'
|
||||
open_mock = MagicMock(spec=file)
|
||||
with patch(codecs_open, open_mock):
|
||||
template.copy('zh-TW')
|
||||
file_handle = open_mock.return_value.__enter__.return_value
|
||||
assert not file_handle.write.called
|
||||
template.copy('de')
|
||||
assert file_handle.write.called
|
||||
|
||||
def test_copy_template(self):
|
||||
"""
|
||||
template.copy() should create missing lang specific templates.
|
||||
"""
|
||||
template = L10nTemplate(path.join(TEMPLATE_DIRS[0],
|
||||
'l10n_blocks_without_langs.html'))
|
||||
# cause the template to be read and parsed before mocking open
|
||||
template.blocks
|
||||
codecs_open = 'l10n_utils.management.commands.l10n_check.codecs.open'
|
||||
open_mock = MagicMock(spec=file)
|
||||
open_buffer = StringIO()
|
||||
open_mock.return_value.__enter__.return_value = open_buffer
|
||||
with patch(codecs_open, open_mock):
|
||||
template.copy('de')
|
||||
|
||||
# braces doubled for .format()
|
||||
good_value = dedent("""\
|
||||
{{# Version: {0} #}}
|
||||
|
||||
{{% extends "l10n_blocks_without_langs.html" %}}
|
||||
|
||||
{{% l10n donnie %}}
|
||||
Phone's ringing Dude.
|
||||
{{% endl10n %}}\n
|
||||
""".format(get_todays_version()))
|
||||
self.assertEqual(open_buffer.getvalue(), good_value)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{# Version: 20120101 #}
|
||||
|
||||
{% extends "l10n_blocks_with_langs.html" %}
|
||||
|
||||
{% l10n donnie %}
|
||||
I am the walrus.
|
||||
{% endl10n %}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{% l10n dude locales=fr,es-ES,ru 20121212 %}
|
||||
This aggression will not stand, man.
|
||||
{% endl10n %}
|
||||
|
||||
{% l10n walter, locales=es-ES,ru 20121212 %}
|
||||
I'm stayin'. Finishin' my coffee.
|
||||
{% endl10n %}
|
||||
|
||||
{% l10n donnie locales=ru,de 20121212 %}
|
||||
Phone's ringing Dude.
|
||||
{% endl10n %}
|
|
@ -0,0 +1,11 @@
|
|||
{% l10n dude locales=fr,es-ES,ru 20121212 %}
|
||||
This aggression will not stand, man.
|
||||
{% endl10n %}
|
||||
|
||||
{% l10n walter, locales=es-ES,ru 20121212 %}
|
||||
I'm stayin'. Finishin' my coffee.
|
||||
{% endl10n %}
|
||||
|
||||
{% l10n donnie 20121212 %}
|
||||
Phone's ringing Dude.
|
||||
{% endl10n %}
|
|
@ -3,8 +3,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import os
|
||||
from tempfile import TemporaryFile
|
||||
from textwrap import dedent
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -12,6 +10,7 @@ from mock import patch
|
|||
from nose.tools import eq_
|
||||
|
||||
from l10n_utils.gettext import langfiles_for_path, parse_python, parse_template
|
||||
from l10n_utils.tests import TempFileMixin
|
||||
from mozorg.tests import TestCase
|
||||
|
||||
|
||||
|
@ -19,15 +18,6 @@ ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files')
|
|||
TEMPLATE_DIRS = (os.path.join(ROOT, 'templates'))
|
||||
|
||||
|
||||
class TempFileMixin(object):
|
||||
"""Provide a method for getting a temp file that is removed when closed."""
|
||||
def tempfile(self, data):
|
||||
tempf = TemporaryFile()
|
||||
tempf.write(dedent(data))
|
||||
tempf.seek(0)
|
||||
return tempf
|
||||
|
||||
|
||||
class TestParseTemplate(TempFileMixin, TestCase):
|
||||
@patch('l10n_utils.gettext.codecs')
|
||||
def test_single_lang_file_added(self, codecs_mock):
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.test.client import Client
|
|||
|
||||
from jingo import env
|
||||
from jinja2 import FileSystemLoader
|
||||
from jinja2.nodes import Block
|
||||
from mock import patch
|
||||
from nose.plugins.skip import SkipTest
|
||||
from nose.tools import eq_, ok_
|
||||
|
@ -22,6 +23,20 @@ ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_files')
|
|||
TEMPLATE_DIRS = (os.path.join(ROOT, 'templates'),)
|
||||
|
||||
|
||||
class TestL10nBlocks(TestCase):
|
||||
def test_l10n_block_locales(self):
|
||||
"""
|
||||
Parsing an l10n block with locales info should put that info
|
||||
on the node.
|
||||
"""
|
||||
tree = env.parse("""{% l10n dude locales=ru,es-ES,fr 20121212 %}
|
||||
This stuff is totally translated.
|
||||
{% endl10n %}""")
|
||||
l10n_block = tree.find(Block)
|
||||
self.assertEqual(l10n_block.locales, ['ru', 'es-ES', 'fr'])
|
||||
self.assertEqual(l10n_block.version, 20121212)
|
||||
|
||||
|
||||
@patch.object(env, 'loader', FileSystemLoader(TEMPLATE_DIRS))
|
||||
@patch.object(settings, 'ROOT_URLCONF', 'l10n_utils.tests.test_files.urls')
|
||||
@patch.object(settings, 'ROOT', ROOT)
|
||||
|
|
Загрузка…
Ссылка в новой задаче