Commit 4620f50f by Steve Strassmann

addressed Cale's comments; switched to path.py paths

parent 87fbfdce
import os, json import os, json
from path import path
# BASE_DIR is the working directory to execute django-admin commands from. # BASE_DIR is the working directory to execute django-admin commands from.
# Typically this should be the 'mitx' directory. # Typically this should be the 'mitx' directory.
BASE_DIR = os.path.normpath(os.path.dirname(os.path.abspath(__file__))+'/..') #BASE_DIR = os.path.normpath(os.path.dirname(os.path.abspath(__file__))+'/..')
BASE_DIR = path(__file__).abspath().dirname().joinpath('..').normpath()
# LOCALE_DIR contains the locale files. # LOCALE_DIR contains the locale files.
# Typically this should be 'mitx/conf/locale' # Typically this should be 'mitx/conf/locale'
LOCALE_DIR = os.path.join(BASE_DIR, 'conf', 'locale') LOCALE_DIR = BASE_DIR.joinpath('conf', 'locale')
class Configuration: class Configuration:
""" """
...@@ -16,10 +18,10 @@ class Configuration: ...@@ -16,10 +18,10 @@ class Configuration:
_source_locale = 'en' _source_locale = 'en'
def __init__(self, filename): def __init__(self, filename):
self.filename = filename self._filename = filename
self.config = self.get_config(self.filename) self._config = self.read_config(filename)
def get_config(self, filename): def read_config(self, filename):
""" """
Returns data found in config file (as dict), or raises exception if file not found Returns data found in config file (as dict), or raises exception if file not found
""" """
...@@ -28,28 +30,31 @@ class Configuration: ...@@ -28,28 +30,31 @@ class Configuration:
with open(filename) as stream: with open(filename) as stream:
return json.load(stream) return json.load(stream)
def get_locales(self): @property
def locales(self):
""" """
Returns a list of locales declared in the configuration file, Returns a list of locales declared in the configuration file,
e.g. ['en', 'fr', 'es'] e.g. ['en', 'fr', 'es']
Each locale is a string. Each locale is a string.
""" """
return self.config['locales'] return self._config['locales']
def get_source_locale(self): @property
def source_locale(self):
""" """
Returns source language. Returns source language.
Source language is English. Source language is English.
""" """
return self._source_locale return self._source_locale
def get_dummy_locale(self): @property
def dummy_locale(self):
""" """
Returns a locale to use for the dummy text, e.g. 'fr'. Returns a locale to use for the dummy text, e.g. 'fr'.
Throws exception if no dummy-locale is declared. Throws exception if no dummy-locale is declared.
The locale is a string. The locale is a string.
""" """
dummy = self.config.get('dummy-locale', None) dummy = self._config.get('dummy-locale', None)
if not dummy: if not dummy:
raise Exception('Could not read dummy-locale from configuration file.') raise Exception('Could not read dummy-locale from configuration file.')
return dummy return dummy
...@@ -59,15 +64,16 @@ class Configuration: ...@@ -59,15 +64,16 @@ class Configuration:
Returns the name of the directory holding the po files for locale. Returns the name of the directory holding the po files for locale.
Example: mitx/conf/locale/fr/LC_MESSAGES Example: mitx/conf/locale/fr/LC_MESSAGES
""" """
return os.path.join(LOCALE_DIR, locale, 'LC_MESSAGES') return LOCALE_DIR.joinpath(locale, 'LC_MESSAGES')
def get_source_messages_dir(self): @property
def source_messages_dir(self):
""" """
Returns the name of the directory holding the source-language po files (English). Returns the name of the directory holding the source-language po files (English).
Example: mitx/conf/locale/en/LC_MESSAGES Example: mitx/conf/locale/en/LC_MESSAGES
""" """
return self.get_messages_dir(self.get_source_locale()) return self.get_messages_dir(self.source_locale)
CONFIGURATION = Configuration(os.path.normpath(os.path.join(LOCALE_DIR, 'config'))) CONFIGURATION = Configuration(LOCALE_DIR.joinpath('config').normpath())
...@@ -14,7 +14,7 @@ def get_default_logger(): ...@@ -14,7 +14,7 @@ def get_default_logger():
LOG = get_default_logger() LOG = get_default_logger()
def execute (command, working_directory=BASE_DIR, log=LOG): def execute(command, working_directory=BASE_DIR, log=LOG):
""" """
Executes shell command in a given working_directory. Executes shell command in a given working_directory.
Command is a string to pass to the shell. Command is a string to pass to the shell.
......
...@@ -23,22 +23,22 @@ from execute import execute, create_dir_if_necessary, remove_file, LOG ...@@ -23,22 +23,22 @@ from execute import execute, create_dir_if_necessary, remove_file, LOG
# BABEL_CONFIG contains declarations for Babel to extract strings from mako template files # BABEL_CONFIG contains declarations for Babel to extract strings from mako template files
# Use relpath to reduce noise in logs # Use relpath to reduce noise in logs
BABEL_CONFIG = os.path.relpath(LOCALE_DIR + '/babel.cfg', BASE_DIR) BABEL_CONFIG = BASE_DIR.relpathto(LOCALE_DIR.joinpath('babel.cfg'))
# Strings from mako template files are written to BABEL_OUT # Strings from mako template files are written to BABEL_OUT
# Use relpath to reduce noise in logs # Use relpath to reduce noise in logs
BABEL_OUT = os.path.relpath(CONFIGURATION.get_source_messages_dir() + '/mako.po', BASE_DIR) BABEL_OUT = BASE_DIR.relpathto(CONFIGURATION.source_messages_dir.joinpath('mako.po'))
SOURCE_WARN = 'This English source file is machine-generated. Do not check it into github' SOURCE_WARN = 'This English source file is machine-generated. Do not check it into github'
def main (): def main ():
create_dir_if_necessary(LOCALE_DIR) create_dir_if_necessary(LOCALE_DIR)
source_msgs_dir = CONFIGURATION.get_source_messages_dir() source_msgs_dir = CONFIGURATION.source_messages_dir
remove_file(os.path.join(source_msgs_dir, 'django.po')) remove_file(source_msgs_dir.joinpath('django.po'))
generated_files = ('django-partial.po', 'djangojs.po', 'mako.po') generated_files = ('django-partial.po', 'djangojs.po', 'mako.po')
for filename in generated_files: for filename in generated_files:
remove_file(os.path.join(source_msgs_dir, filename)) remove_file(source_msgs_dir.joinpath(filename))
# Extract strings from mako templates # Extract strings from mako templates
...@@ -55,13 +55,13 @@ def main (): ...@@ -55,13 +55,13 @@ def main ():
execute(make_django_cmd, working_directory=BASE_DIR) execute(make_django_cmd, working_directory=BASE_DIR)
# makemessages creates 'django.po'. This filename is hardcoded. # makemessages creates 'django.po'. This filename is hardcoded.
# Rename it to django-partial.po to enable merging into django.po later. # Rename it to django-partial.po to enable merging into django.po later.
os.rename(os.path.join(source_msgs_dir, 'django.po'), os.rename(source_msgs_dir.joinpath('django.po'),
os.path.join(source_msgs_dir, 'django-partial.po')) source_msgs_dir.joinpath('django-partial.po'))
execute(make_djangojs_cmd, working_directory=BASE_DIR) execute(make_djangojs_cmd, working_directory=BASE_DIR)
for filename in generated_files: for filename in generated_files:
LOG.info('Cleaning %s' % filename) LOG.info('Cleaning %s' % filename)
po = pofile(os.path.join(source_msgs_dir, filename)) po = pofile(source_msgs_dir.joinpath(filename))
# replace default headers with edX headers # replace default headers with edX headers
fix_header(po) fix_header(po)
# replace default metadata with edX metadata # replace default metadata with edX metadata
......
...@@ -19,25 +19,35 @@ from polib import pofile ...@@ -19,25 +19,35 @@ from polib import pofile
from config import BASE_DIR, CONFIGURATION from config import BASE_DIR, CONFIGURATION
from execute import execute, remove_file, LOG from execute import execute, remove_file, LOG
def merge(locale, target='django.po'): def merge(locale, target='django.po', fail_if_missing=True):
""" """
For the given locale, merge django-partial.po, messages.po, mako.po -> django.po For the given locale, merge django-partial.po, messages.po, mako.po -> django.po
target is the resulting filename
If fail_if_missing is True, and the files to be merged are missing,
throw an Exception.
If fail_if_missing is False, and the files to be merged are missing,
just return silently.
""" """
LOG.info('Merging locale={0}'.format(locale)) LOG.info('Merging locale={0}'.format(locale))
locale_directory = CONFIGURATION.get_messages_dir(locale) locale_directory = CONFIGURATION.get_messages_dir(locale)
files_to_merge = ('django-partial.po', 'messages.po', 'mako.po') files_to_merge = ('django-partial.po', 'messages.po', 'mako.po')
validate_files(locale_directory, files_to_merge) try:
validate_files(locale_directory, files_to_merge)
except Exception, e:
if not fail_if_missing:
return
raise e
# merged file is merged.po # merged file is merged.po
merge_cmd = 'msgcat -o merged.po ' + ' '.join(files_to_merge) merge_cmd = 'msgcat -o merged.po ' + ' '.join(files_to_merge)
execute(merge_cmd, working_directory=locale_directory) execute(merge_cmd, working_directory=locale_directory)
# clean up redunancies in the metadata # clean up redunancies in the metadata
merged_filename = os.path.join(locale_directory, 'merged.po') merged_filename = locale_directory.joinpath('merged.po')
clean_metadata(merged_filename) clean_metadata(merged_filename)
# rename merged.po -> django.po (default) # rename merged.po -> django.po (default)
django_filename = os.path.join(locale_directory, target) django_filename = locale_directory.joinpath(target)
os.rename(merged_filename, django_filename) # can't overwrite file on Windows os.rename(merged_filename, django_filename) # can't overwrite file on Windows
def clean_metadata(file): def clean_metadata(file):
...@@ -45,25 +55,25 @@ def clean_metadata(file): ...@@ -45,25 +55,25 @@ def clean_metadata(file):
Clean up redundancies in the metadata caused by merging. Clean up redundancies in the metadata caused by merging.
This reads in a PO file and simply saves it back out again. This reads in a PO file and simply saves it back out again.
""" """
po = pofile(file) pofile(file).save()
po.save()
def validate_files(dir, files_to_merge): def validate_files(dir, files_to_merge):
""" """
Asserts that the given files exist. Asserts that the given files exist.
files_to_merge is a list of file names (no directories). files_to_merge is a list of file names (no directories).
dir is the directory in which the files should appear. dir is the directory (a path object from path.py) in which the files should appear.
raises an Exception if any of the files are not in dir. raises an Exception if any of the files are not in dir.
""" """
for path in files_to_merge: for path in files_to_merge:
pathname = os.path.join(dir, path) pathname = dir.joinpath(path)
if not os.path.exists(pathname): if not pathname.exists():
raise Exception("File not found: {0}".format(pathname)) raise Exception("I18N: Cannot generate because file not found: {0}".format(pathname))
def main (): def main ():
for locale in CONFIGURATION.get_locales(): for locale in CONFIGURATION.locales:
merge(locale) merge(locale)
# Dummy text is not required. Don't raise exception if files are missing.
merge(CONFIGURATION.dummy_locale, fail_if_missing=False)
compile_cmd = 'django-admin.py compilemessages' compile_cmd = 'django-admin.py compilemessages'
execute(compile_cmd, working_directory=BASE_DIR) execute(compile_cmd, working_directory=BASE_DIR)
......
...@@ -3,5 +3,4 @@ from test_extract import TestExtract ...@@ -3,5 +3,4 @@ from test_extract import TestExtract
from test_generate import TestGenerate from test_generate import TestGenerate
from test_converter import TestConverter from test_converter import TestConverter
from test_dummy import TestDummy from test_dummy import TestDummy
from test_validate import TestValidate import test_validate
...@@ -11,7 +11,7 @@ class TestConfiguration(TestCase): ...@@ -11,7 +11,7 @@ class TestConfiguration(TestCase):
def test_config(self): def test_config(self):
config_filename = os.path.normpath(os.path.join(LOCALE_DIR, 'config')) config_filename = os.path.normpath(os.path.join(LOCALE_DIR, 'config'))
config = Configuration(config_filename) config = Configuration(config_filename)
self.assertEqual(config.get_source_locale(), 'en') self.assertEqual(config.source_locale, 'en')
def test_no_config(self): def test_no_config(self):
config_filename = os.path.normpath(os.path.join(LOCALE_DIR, 'no_such_file')) config_filename = os.path.normpath(os.path.join(LOCALE_DIR, 'no_such_file'))
...@@ -25,9 +25,9 @@ class TestConfiguration(TestCase): ...@@ -25,9 +25,9 @@ class TestConfiguration(TestCase):
Also check values of dummy_locale and source_locale. Also check values of dummy_locale and source_locale.
""" """
self.assertIsNotNone(CONFIGURATION) self.assertIsNotNone(CONFIGURATION)
locales = CONFIGURATION.get_locales() locales = CONFIGURATION.locales
self.assertIsNotNone(locales) self.assertIsNotNone(locales)
self.assertIsInstance(locales, list) self.assertIsInstance(locales, list)
self.assertIn('en', locales) self.assertIn('en', locales)
self.assertEqual('fr', CONFIGURATION.get_dummy_locale()) self.assertEqual('fr', CONFIGURATION.dummy_locale)
self.assertEqual('en', CONFIGURATION.get_source_locale()) self.assertEqual('en', CONFIGURATION.source_locale)
...@@ -39,7 +39,7 @@ class TestExtract(TestCase): ...@@ -39,7 +39,7 @@ class TestExtract(TestCase):
Fails assertion if one of the files doesn't exist. Fails assertion if one of the files doesn't exist.
""" """
for filename in self.generated_files: for filename in self.generated_files:
path = os.path.join(CONFIGURATION.get_source_messages_dir(), filename) path = os.path.join(CONFIGURATION.source_messages_dir, filename)
exists = os.path.exists(path) exists = os.path.exists(path)
self.assertTrue(exists, msg='Missing file: %s' % filename) self.assertTrue(exists, msg='Missing file: %s' % filename)
if exists: if exists:
......
...@@ -21,8 +21,8 @@ class TestGenerate(TestCase): ...@@ -21,8 +21,8 @@ class TestGenerate(TestCase):
""" """
Tests merge script on English source files. Tests merge script on English source files.
""" """
filename = os.path.join(CONFIGURATION.get_source_messages_dir(), random_name()) filename = os.path.join(CONFIGURATION.source_messages_dir, random_name())
generate.merge(CONFIGURATION.get_source_locale(), target=filename) generate.merge(CONFIGURATION.source_locale, target=filename)
self.assertTrue(os.path.exists(filename)) self.assertTrue(os.path.exists(filename))
os.remove(filename) os.remove(filename)
...@@ -35,7 +35,7 @@ class TestGenerate(TestCase): ...@@ -35,7 +35,7 @@ class TestGenerate(TestCase):
after start of test suite) after start of test suite)
""" """
generate.main() generate.main()
for locale in CONFIGURATION.get_locales(): for locale in CONFIGURATION.locales:
for filename in ('django', 'djangojs'): for filename in ('django', 'djangojs'):
mofile = filename+'.mo' mofile = filename+'.mo'
path = os.path.join(CONFIGURATION.get_messages_dir(locale), mofile) path = os.path.join(CONFIGURATION.get_messages_dir(locale), mofile)
......
...@@ -4,31 +4,28 @@ from nose.plugins.skip import SkipTest ...@@ -4,31 +4,28 @@ from nose.plugins.skip import SkipTest
from config import LOCALE_DIR from config import LOCALE_DIR
from execute import call, LOG from execute import call, LOG
class TestValidate(TestCase): def test_po_files():
""" """
Call GNU msgfmt -c on each .po file to validate its format. This is a generator. It yields all of the .po files under root, and tests each one.
""" """
for (dirpath, dirnames, filenames) in os.walk(LOCALE_DIR):
def test_validate(self): for name in filenames:
# Skip this test for now because it's very noisy print name
raise SkipTest() (base, ext) = os.path.splitext(name)
for file in self.get_po_files(): if ext.lower() == '.po':
# Use relative paths to make output less noisy. yield validate_po_file, os.path.join(dirpath, name)
rfile = os.path.relpath(file, LOCALE_DIR)
(out, err) = call(['msgfmt','-c', rfile], log=None, working_directory=LOCALE_DIR)
if err != '':
LOG.warn('\n'+err)
def get_po_files(self, root=LOCALE_DIR):
"""
This is a generator. It yields all of the .po files under root.
"""
for (dirpath, dirnames, filenames) in os.walk(root):
for name in filenames:
(base, ext) = os.path.splitext(name)
if ext.lower() == '.po':
yield os.path.join(dirpath, name)
def validate_po_file(filename):
"""
Call GNU msgfmt -c on each .po file to validate its format.
"""
# Skip this test for now because it's very noisy
raise SkipTest()
# Use relative paths to make output less noisy.
rfile = os.path.relpath(filename, LOCALE_DIR)
(out, err) = call(['msgfmt','-c', rfile], log=None, working_directory=LOCALE_DIR)
if err != '':
LOG.warn('\n'+err)
...@@ -13,7 +13,9 @@ def push(): ...@@ -13,7 +13,9 @@ def push():
execute('tx push -s') execute('tx push -s')
def pull(): def pull():
execute('tx pull') for locale in CONFIGURATION.locales:
if locale != CONFIGURATION.source_locale:
execute('tx pull -l %s' % locale)
clean_translated_locales() clean_translated_locales()
...@@ -22,8 +24,8 @@ def clean_translated_locales(): ...@@ -22,8 +24,8 @@ def clean_translated_locales():
Strips out the warning from all translated po files Strips out the warning from all translated po files
about being an English source file. about being an English source file.
""" """
for locale in CONFIGURATION.get_locales(): for locale in CONFIGURATION.locales:
if locale != CONFIGURATION.get_source_locale(): if locale != CONFIGURATION.source_locale:
clean_locale(locale) clean_locale(locale)
def clean_locale(locale): def clean_locale(locale):
...@@ -34,7 +36,7 @@ def clean_locale(locale): ...@@ -34,7 +36,7 @@ def clean_locale(locale):
""" """
dirname = CONFIGURATION.get_messages_dir(locale) dirname = CONFIGURATION.get_messages_dir(locale)
for filename in ('django-partial.po', 'djangojs.po', 'mako.po'): for filename in ('django-partial.po', 'djangojs.po', 'mako.po'):
clean_file(os.path.join(dirname, filename)) clean_file(dirname.joinpath(filename))
def clean_file(file): def clean_file(file):
""" """
......
...@@ -33,6 +33,7 @@ paramiko==1.9.0 ...@@ -33,6 +33,7 @@ paramiko==1.9.0
path.py==3.0.1 path.py==3.0.1
Pillow==1.7.8 Pillow==1.7.8
pip pip
polib==1.0.3
pygments==1.5 pygments==1.5
pygraphviz==1.1 pygraphviz==1.1
pymongo==2.4.1 pymongo==2.4.1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment