Made (make|compile)messages check for availability of gettext commands.
Refs #19584.
This commit is contained in:
parent
3f43f5f3b0
commit
7fca4416c7
|
@ -5,7 +5,7 @@ import os
|
|||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.utils import popen_wrapper
|
||||
from django.core.management.utils import find_command, popen_wrapper
|
||||
from django.utils._os import npath
|
||||
|
||||
def has_bom(fn):
|
||||
|
@ -16,6 +16,10 @@ def has_bom(fn):
|
|||
sample.startswith(codecs.BOM_UTF16_BE)
|
||||
|
||||
def compile_messages(stderr, locale=None):
|
||||
program = 'msgfmt'
|
||||
if find_command(program) is None:
|
||||
raise CommandError("Can't find %s. Make sure you have GNU gettext tools 0.15 or newer installed." % program)
|
||||
|
||||
basedirs = [os.path.join('conf', 'locale'), 'locale']
|
||||
if os.environ.get('DJANGO_SETTINGS_MODULE'):
|
||||
from django.conf import settings
|
||||
|
@ -42,7 +46,6 @@ def compile_messages(stderr, locale=None):
|
|||
if has_bom(fn):
|
||||
raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn)
|
||||
pf = os.path.splitext(fn)[0]
|
||||
program = 'msgfmt'
|
||||
args = [program, '--check-format', '-o', npath(pf + '.mo'), npath(pf + '.po')]
|
||||
output, errors, status = popen_wrapper(args)
|
||||
if status:
|
||||
|
|
|
@ -5,11 +5,11 @@ import re
|
|||
import sys
|
||||
from itertools import dropwhile
|
||||
from optparse import make_option
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
import django
|
||||
from django.core.management.base import CommandError, NoArgsCommand
|
||||
from django.core.management.utils import handle_extensions
|
||||
from django.core.management.utils import (handle_extensions, find_command,
|
||||
popen_wrapper)
|
||||
from django.utils.functional import total_ordering
|
||||
from django.utils.text import get_text_list
|
||||
from django.utils.jslex import prepare_js_for_gettext
|
||||
|
@ -18,6 +18,13 @@ plural_forms_re = re.compile(r'^(?P<value>"Plural-Forms.+?\\n")\s*$', re.MULTILI
|
|||
STATUS_OK = 0
|
||||
|
||||
|
||||
def check_programs(*programs):
|
||||
for program in programs:
|
||||
if find_command(program) is None:
|
||||
raise CommandError("Can't find %s. Make sure you have GNU "
|
||||
"gettext tools 0.15 or newer installed." % program)
|
||||
|
||||
|
||||
@total_ordering
|
||||
class TranslatableFile(object):
|
||||
def __init__(self, dirpath, file_name):
|
||||
|
@ -58,12 +65,24 @@ class TranslatableFile(object):
|
|||
work_file = os.path.join(self.dirpath, thefile)
|
||||
with open(work_file, "w") as fp:
|
||||
fp.write(src_data)
|
||||
cmd = (
|
||||
'xgettext -d %s -L C %s %s --keyword=gettext_noop '
|
||||
'--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
|
||||
'--keyword=pgettext:1c,2 --keyword=npgettext:1c,2,3 '
|
||||
'--from-code UTF-8 --add-comments=Translators -o - "%s"' %
|
||||
(domain, command.wrap, command.location, work_file))
|
||||
args = [
|
||||
'xgettext',
|
||||
'-d', domain,
|
||||
'--language=C',
|
||||
'--keyword=gettext_noop',
|
||||
'--keyword=gettext_lazy',
|
||||
'--keyword=ngettext_lazy:1,2',
|
||||
'--keyword=pgettext:1c,2',
|
||||
'--keyword=npgettext:1c,2,3',
|
||||
'--from-code=UTF-8',
|
||||
'--add-comments=Translators',
|
||||
'--output=-'
|
||||
]
|
||||
if command.wrap:
|
||||
args.append(command.wrap)
|
||||
if command.location:
|
||||
args.append(command.location)
|
||||
args.append(work_file)
|
||||
elif domain == 'django' and (file_ext == '.py' or file_ext in command.extensions):
|
||||
thefile = self.file
|
||||
orig_file = os.path.join(self.dirpath, self.file)
|
||||
|
@ -76,18 +95,32 @@ class TranslatableFile(object):
|
|||
with open(os.path.join(self.dirpath, thefile), "w") as fp:
|
||||
fp.write(content)
|
||||
work_file = os.path.join(self.dirpath, thefile)
|
||||
cmd = (
|
||||
'xgettext -d %s -L Python %s %s --keyword=gettext_noop '
|
||||
'--keyword=gettext_lazy --keyword=ngettext_lazy:1,2 '
|
||||
'--keyword=ugettext_noop --keyword=ugettext_lazy '
|
||||
'--keyword=ungettext_lazy:1,2 --keyword=pgettext:1c,2 '
|
||||
'--keyword=npgettext:1c,2,3 --keyword=pgettext_lazy:1c,2 '
|
||||
'--keyword=npgettext_lazy:1c,2,3 --from-code UTF-8 '
|
||||
'--add-comments=Translators -o - "%s"' %
|
||||
(domain, command.wrap, command.location, work_file))
|
||||
args = [
|
||||
'xgettext',
|
||||
'-d', domain,
|
||||
'--language=Python',
|
||||
'--keyword=gettext_noop',
|
||||
'--keyword=gettext_lazy',
|
||||
'--keyword=ngettext_lazy:1,2',
|
||||
'--keyword=ugettext_noop',
|
||||
'--keyword=ugettext_lazy',
|
||||
'--keyword=ungettext_lazy:1,2',
|
||||
'--keyword=pgettext:1c,2',
|
||||
'--keyword=npgettext:1c,2,3',
|
||||
'--keyword=pgettext_lazy:1c,2',
|
||||
'--keyword=npgettext_lazy:1c,2,3',
|
||||
'--from-code=UTF-8',
|
||||
'--add-comments=Translators',
|
||||
'--output=-'
|
||||
]
|
||||
if command.wrap:
|
||||
args.append(command.wrap)
|
||||
if command.location:
|
||||
args.append(command.location)
|
||||
args.append(work_file)
|
||||
else:
|
||||
return
|
||||
msgs, errors, status = _popen(cmd)
|
||||
msgs, errors, status = popen_wrapper(args)
|
||||
if errors:
|
||||
if status != STATUS_OK:
|
||||
if is_templatized:
|
||||
|
@ -109,15 +142,6 @@ class TranslatableFile(object):
|
|||
if is_templatized:
|
||||
os.unlink(work_file)
|
||||
|
||||
|
||||
def _popen(cmd):
|
||||
"""
|
||||
Friendly wrapper around Popen for Windows
|
||||
"""
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True)
|
||||
output, errors = p.communicate()
|
||||
return output, errors, p.returncode
|
||||
|
||||
def write_pot_file(potfile, msgs):
|
||||
"""
|
||||
Write the :param potfile: POT file with the :param msgs: contents,
|
||||
|
@ -225,8 +249,9 @@ class Command(NoArgsCommand):
|
|||
"is not created automatically, you have to create it by hand "
|
||||
"if you want to enable i18n for your project or application.")
|
||||
|
||||
check_programs('xgettext')
|
||||
# We require gettext version 0.15 or newer.
|
||||
output, errors, status = _popen('xgettext --version')
|
||||
output, errors, status = popen_wrapper(['xgettext', '--version'])
|
||||
if status != STATUS_OK:
|
||||
raise CommandError("Error running xgettext. Note that Django "
|
||||
"internationalization requires GNU gettext 0.15 or newer.")
|
||||
|
@ -248,6 +273,9 @@ class Command(NoArgsCommand):
|
|||
locale_dirs = filter(os.path.isdir, glob.glob('%s/*' % localedir))
|
||||
locales = [os.path.basename(l) for l in locale_dirs]
|
||||
|
||||
if locales:
|
||||
check_programs('msguniq', 'msgmerge', 'msgattrib')
|
||||
|
||||
try:
|
||||
for locale in locales:
|
||||
if self.verbosity > 0:
|
||||
|
@ -307,8 +335,13 @@ class Command(NoArgsCommand):
|
|||
|
||||
Uses mguniq, msgmerge, and msgattrib GNU gettext utilities.
|
||||
"""
|
||||
msgs, errors, status = _popen('msguniq %s %s --to-code=utf-8 "%s"' %
|
||||
(self.wrap, self.location, potfile))
|
||||
args = ['msguniq', '--to-code=utf-8']
|
||||
if self.wrap:
|
||||
args.append(self.wrap)
|
||||
if self.location:
|
||||
args.append(self.location)
|
||||
args.append(potfile)
|
||||
msgs, errors, status = popen_wrapper(args)
|
||||
if errors:
|
||||
if status != STATUS_OK:
|
||||
raise CommandError(
|
||||
|
@ -324,8 +357,13 @@ class Command(NoArgsCommand):
|
|||
if os.path.exists(pofile):
|
||||
with open(potfile, 'w') as fp:
|
||||
fp.write(msgs)
|
||||
msgs, errors, status = _popen('msgmerge %s %s -q "%s" "%s"' %
|
||||
(self.wrap, self.location, pofile, potfile))
|
||||
args = ['msgmerge', '-q']
|
||||
if self.wrap:
|
||||
args.append(self.wrap)
|
||||
if self.location:
|
||||
args.append(self.location)
|
||||
args.extend([pofile, potfile])
|
||||
msgs, errors, status = popen_wrapper(args)
|
||||
if errors:
|
||||
if status != STATUS_OK:
|
||||
raise CommandError(
|
||||
|
@ -340,9 +378,13 @@ class Command(NoArgsCommand):
|
|||
fp.write(msgs)
|
||||
|
||||
if self.no_obsolete:
|
||||
msgs, errors, status = _popen(
|
||||
'msgattrib %s %s -o "%s" --no-obsolete "%s"' %
|
||||
(self.wrap, self.location, pofile, pofile))
|
||||
args = ['msgattrib', '-o', pofile, '--no-obsolete']
|
||||
if self.wrap:
|
||||
args.append(self.wrap)
|
||||
if self.location:
|
||||
args.append(self.location)
|
||||
args.append(pofile)
|
||||
msgs, errors, status = popen_wrapper(args)
|
||||
if errors:
|
||||
if status != STATUS_OK:
|
||||
raise CommandError(
|
||||
|
|
|
@ -1,17 +1,27 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
from subprocess import PIPE, Popen
|
||||
import sys
|
||||
|
||||
from django.utils.encoding import force_text, DEFAULT_LOCALE_ENCODING
|
||||
from django.utils import six
|
||||
|
||||
from .base import CommandError
|
||||
|
||||
|
||||
def popen_wrapper(args):
|
||||
def popen_wrapper(args, os_err_exc_type=CommandError):
|
||||
"""
|
||||
Friendly wrapper around Popen.
|
||||
|
||||
Returns stdout output, stderr output and OS status code.
|
||||
"""
|
||||
try:
|
||||
p = Popen(args, shell=False, stdout=PIPE, stderr=PIPE,
|
||||
close_fds=os.name != 'nt', universal_newlines=True)
|
||||
except OSError as e:
|
||||
six.reraise(os_err_exc_type, os_err_exc_type('Error executing %s: %s' %
|
||||
(args[0], e.strerror)), sys.exc_info()[2])
|
||||
output, errors = p.communicate()
|
||||
return (
|
||||
output,
|
||||
|
@ -43,3 +53,27 @@ def handle_extensions(extensions=('html',), ignored=('py',)):
|
|||
if not ext.startswith('.'):
|
||||
ext_list[i] = '.%s' % ext_list[i]
|
||||
return set([x for x in ext_list if x.strip('.') not in ignored])
|
||||
|
||||
def find_command(cmd, path=None, pathext=None):
|
||||
if path is None:
|
||||
path = os.environ.get('PATH', []).split(os.pathsep)
|
||||
if isinstance(path, six.string_types):
|
||||
path = [path]
|
||||
# check if there are funny path extensions for executables, e.g. Windows
|
||||
if pathext is None:
|
||||
pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD').split(os.pathsep)
|
||||
# don't use extensions if the command ends with one of them
|
||||
for ext in pathext:
|
||||
if cmd.endswith(ext):
|
||||
pathext = ['']
|
||||
break
|
||||
# check if we find the command on PATH
|
||||
for p in path:
|
||||
f = os.path.join(p, cmd)
|
||||
if os.path.isfile(f):
|
||||
return f
|
||||
for ext in pathext:
|
||||
fext = f + ext
|
||||
if os.path.isfile(fext):
|
||||
return fext
|
||||
return None
|
||||
|
|
|
@ -131,8 +131,13 @@ class BasicExtractorTests(ExtractorTests):
|
|||
os.chdir(self.test_dir)
|
||||
shutil.copyfile('./code.sample', './code_sample.py')
|
||||
stdout = StringIO()
|
||||
try:
|
||||
management.call_command('makemessages', locale=LOCALE, stdout=stdout)
|
||||
finally:
|
||||
try:
|
||||
os.remove('./code_sample.py')
|
||||
except OSError:
|
||||
pass
|
||||
self.assertIn("code_sample.py:4", force_text(stdout.getvalue()))
|
||||
|
||||
def test_template_message_context_extractor(self):
|
||||
|
|
|
@ -2,35 +2,11 @@ import os
|
|||
import re
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from django.utils import six
|
||||
from django.core.management.utils import find_command
|
||||
|
||||
can_run_extraction_tests = False
|
||||
can_run_compilation_tests = False
|
||||
|
||||
def find_command(cmd, path=None, pathext=None):
|
||||
if path is None:
|
||||
path = os.environ.get('PATH', []).split(os.pathsep)
|
||||
if isinstance(path, six.string_types):
|
||||
path = [path]
|
||||
# check if there are funny path extensions for executables, e.g. Windows
|
||||
if pathext is None:
|
||||
pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD').split(os.pathsep)
|
||||
# don't use extensions if the command ends with one of them
|
||||
for ext in pathext:
|
||||
if cmd.endswith(ext):
|
||||
pathext = ['']
|
||||
break
|
||||
# check if we find the command on PATH
|
||||
for p in path:
|
||||
f = os.path.join(p, cmd)
|
||||
if os.path.isfile(f):
|
||||
return f
|
||||
for ext in pathext:
|
||||
fext = f + ext
|
||||
if os.path.isfile(fext):
|
||||
return fext
|
||||
return None
|
||||
|
||||
# checks if it can find xgettext on the PATH and
|
||||
# imports the extraction tests if yes
|
||||
xgettext_cmd = find_command('xgettext')
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import sys
|
||||
|
||||
from django.core import management
|
||||
from django.core.management.base import CommandError
|
||||
from django.test import TestCase
|
||||
from django.core.management import CommandError
|
||||
from django.core.management.utils import popen_wrapper
|
||||
from django.test import SimpleTestCase
|
||||
from django.utils import translation
|
||||
from django.utils.six import StringIO
|
||||
|
||||
|
||||
class CommandTests(TestCase):
|
||||
class CommandTests(SimpleTestCase):
|
||||
def test_command(self):
|
||||
out = StringIO()
|
||||
management.call_command('dance', stdout=out)
|
||||
|
@ -58,3 +59,9 @@ class CommandTests(TestCase):
|
|||
with translation.override('pl'):
|
||||
management.call_command('leave_locale_alone_true', stdout=out)
|
||||
self.assertEqual(out.getvalue(), "pl\n")
|
||||
|
||||
|
||||
class UtilsTests(SimpleTestCase):
|
||||
|
||||
def test_no_existent_external_program(self):
|
||||
self.assertRaises(CommandError, popen_wrapper, ['a_42_command_that_doesnt_exist_42'])
|
||||
|
|
Loading…
Reference in New Issue