From b4c5fde8fbefefda8d17081d13fad3d4ba7c1458 Mon Sep 17 00:00:00 2001 From: Ivan Ivic Date: Fri, 23 Sep 2016 13:50:09 +0200 Subject: [PATCH] [SOL-2053] Added changed command which checks if the translation source files are up to date --- README.rst | 7 +++++ i18n/__init__.py | 4 +-- i18n/changed.py | 55 +++++++++++++++++++++++++++++++++++ tests/test_changed.py | 67 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 i18n/changed.py create mode 100644 tests/test_changed.py diff --git a/README.rst b/README.rst index aaf6b40..8eab6ac 100644 --- a/README.rst +++ b/README.rst @@ -13,6 +13,7 @@ Running commands from the edx-platform directory will default to loading the configuration at ``./conf/locale/config.yaml``. You can specify a different configuration file with the ``--config`` argument. +* ``i18n_tool changed`` * ``i18n_tool dummy`` * ``i18n_tool extract`` * ``i18n_tool generate`` @@ -31,6 +32,12 @@ Details of the config.yaml file are in `edx-platform/conf/locale/config.yaml Changes ======= +v0.3.4 +------ + +* ``i18n_tool changed`` command added. This command determines if the source translation + files are up-to-date. If they are not it returns a non-zero exit code. + v0.3.2 ------ diff --git a/i18n/__init__.py b/i18n/__init__.py index cb97831..7e3e962 100644 --- a/i18n/__init__.py +++ b/i18n/__init__.py @@ -4,7 +4,7 @@ import argparse import sys -__version__ = '0.3.3' +__version__ = '0.3.4' class Runner: @@ -49,4 +49,4 @@ def __call__(self, **kwargs): config.CONFIGURATION = config.Configuration(args.config) else: config.CONFIGURATION = config.Configuration(config.LOCALE_DIR.joinpath('config.yaml').normpath()) - self.run(args) + return self.run(args) diff --git a/i18n/changed.py b/i18n/changed.py new file mode 100644 index 0000000..a41fce7 --- /dev/null +++ b/i18n/changed.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +""" +Determine if the source translation files are up-to-date. +""" +from __future__ import print_function +from subprocess import CalledProcessError + +from i18n import Runner +from i18n.execute import execute + + +class Changed(Runner): + """ + Class used to check if the source translation files are up-to-date + """ + def run(self, args): + """ + Main entry point of script + """ + changes_detected = self.detect_changes() + message = self.get_message(changes_detected) + print(message) + return int(changes_detected) + + def detect_changes(self): + """ + Detect if changes have been made to the msgid or msgstr lines in the translation files. + + Returns: + boolean: True, if changes detected; otherwise, False. + + Note: + This method requires ``git`` to be installed on the executing machine. + """ + try: + execute('git diff --exit-code -G "^(msgid|msgstr)"') + return False + except CalledProcessError: + return True + + def get_message(self, changes_detected): + """ + Returns message depending on source translation files status. + """ + msg = 'Source translation files are current.' + + if changes_detected: + msg = 'Source translations are out-of-date! Please update them.' + + return msg + +main = Changed() # pylint: disable=invalid-name + +if __name__ == '__main__': + main() diff --git a/tests/test_changed.py b/tests/test_changed.py new file mode 100644 index 0000000..e5bb4b4 --- /dev/null +++ b/tests/test_changed.py @@ -0,0 +1,67 @@ +from os import remove +from shutil import copyfile +from unittest import TestCase +import ddt +import mock + +from i18n.changed import Changed + + +@ddt.ddt +class TestChanged(TestCase): + """ + Tests functionality of i18n/changed.py + """ + def setUp(self): + self.changed = Changed() + + def test_detect_changes(self): + """ + Verifies the detect_changes method can detect changes in translation source files. + """ + file_name = 'conf/locale/fake2/LC_MESSAGES/mako.po' + copy = 'conf/locale/fake2/LC_MESSAGES/mako_copy.po' + + self.assertFalse(self.changed.detect_changes()) + + copyfile(file_name, copy) # Copy the .po file + remove(file_name) # Make changes to the .po file + self.assertTrue(self.changed.detect_changes()) # Detect changes made to the .po file + copyfile(copy, file_name) # Return .po file to its previous state + remove(copy) # Delete copy of .po file + + def test_do_not_detect_changes(self): + """ + Verifies the detect_changes method doesn't detect changes in rows that do not start with msgid or msgstr. + """ + file_name = 'test_requirements.txt' + copy = 'test_requirements_copy.txt' + + copyfile(file_name, copy) # Copy the .txt file + remove(file_name) # Make changes to the .txt file + self.assertFalse(self.changed.detect_changes()) # Do not detect changes made to the .txt file + copyfile(copy, file_name) # Return .txt file to its previous state + remove(copy) # Delete copy of .txt file + + @ddt.data( + (False, 'Source translation files are current.'), + (True, 'Source translations are out-of-date! Please update them.') + ) + @ddt.unpack + def test_get_message(self, changes_detected, msg): + """ + Verifies that get_message method returns the correct message. + """ + self.assertEqual(self.changed.get_message(changes_detected), msg) + + @ddt.data( + (True, 1), + (False, 0) + ) + @ddt.unpack + def test_run(self, return_value, value): + """ + Verifies that run method returns the correct value. + """ + with mock.patch('i18n.changed.Changed.detect_changes', mock.Mock(return_value=return_value)): + self.assertEqual(self.changed.run(''), value)