From: David A. Velasco Date: Wed, 11 Dec 2013 07:51:34 +0000 (+0100) Subject: Removed unneeded Transifex client X-Git-Tag: oc-android-1.5.5~110 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/5274191c1e165a615645a4c10c8ca0220d347a3b?hp=--cc Removed unneeded Transifex client --- 5274191c1e165a615645a4c10c8ca0220d347a3b diff --git a/third_party/transifex-client/.gitignore b/third_party/transifex-client/.gitignore deleted file mode 100644 index 72bd50ca..00000000 --- a/third_party/transifex-client/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.tx -*pyc -*pyo -*~ -*egg-info* diff --git a/third_party/transifex-client/DEVELOPMENT.rst b/third_party/transifex-client/DEVELOPMENT.rst deleted file mode 100644 index 992e518f..00000000 --- a/third_party/transifex-client/DEVELOPMENT.rst +++ /dev/null @@ -1,21 +0,0 @@ -Releasing -========= - -To create a new release: - -1. Update local rep and update the version in ``setup.py``:: - - $ hg pull -u - $ vim setup.py - -2. Test:: - - $ python setup.py clean sdist - $ cd dist - $ tar zxf ... - $ cd transifex-client - ...test - -3. Package and upload on PyPI:: - - $ python setup.py clean sdist bdist_egg upload diff --git a/third_party/transifex-client/LICENSE b/third_party/transifex-client/LICENSE deleted file mode 100644 index db860a38..00000000 --- a/third_party/transifex-client/LICENSE +++ /dev/null @@ -1,343 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software - interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Program does not specify a -version number of this License, you may choose any version ever -published by the Free Software Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these -terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. - diff --git a/third_party/transifex-client/MANIFEST.in b/third_party/transifex-client/MANIFEST.in deleted file mode 100644 index 83126ac6..00000000 --- a/third_party/transifex-client/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include tx - -# Docs -include LICENSE README.rst -recursive-include docs * - diff --git a/third_party/transifex-client/README.rst b/third_party/transifex-client/README.rst deleted file mode 100644 index bb887503..00000000 --- a/third_party/transifex-client/README.rst +++ /dev/null @@ -1,30 +0,0 @@ - -============================= - Transifex Command-Line Tool -============================= - -The Transifex Command-line Client is a command line tool that enables -you to easily manage your translations within a project without the need -of an elaborate UI system. - -You can use the command line client to easily create new resources, map -locale files to translations and synchronize your Transifex project with -your local repository and vice verca. Translators and localization -managers can also use it to handle large volumes of translation files -easily and without much hassle. - -Check the full documentation at -http://help.transifex.com/user-guide/client/ - - -Installing -========== - -You can install the latest version of transifex-client running ``pip -install transifex-client`` or ``easy_install transifex-client`` -You can also install the `in-development version`_ of transifex-client -with ``pip install transifex-client==dev`` or ``easy_install -transifex-client==dev``. - -.. _in-development version: http://code.transifex.com/transifex-client/ - diff --git a/third_party/transifex-client/setup.py b/third_party/transifex-client/setup.py deleted file mode 100755 index 626303d5..00000000 --- a/third_party/transifex-client/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import glob -from codecs import BOM - -from setuptools import setup, find_packages -from setuptools.command.build_py import build_py as _build_py - -from txclib import get_version - -readme_file = open(u'README.rst') -long_description = readme_file.read() -readme_file.close() -if long_description.startswith(BOM): - long_description = long_description.lstrip(BOM) -long_description = long_description.decode('utf-8') - -package_data = { - '': ['LICENSE', 'README.rst'], -} - -scripts = ['tx'] - -install_requires = [] -try: - import json -except ImportError: - install_requires.append('simplejson') - -setup( - name="transifex-client", - version=get_version(), - scripts=scripts, - description="A command line interface for Transifex", - long_description=long_description, - author="Transifex", - author_email="info@transifex.com", - url="https://www.transifex.com", - license="GPLv2", - dependency_links = [ - ], - setup_requires = [ - ], - install_requires = install_requires, - tests_require = ["mock", ], - data_files=[ - ], - test_suite="tests", - zip_safe=False, - packages=['txclib', ], - include_package_data=True, - package_data = package_data, - keywords = ('translation', 'localization', 'internationalization',), -) diff --git a/third_party/transifex-client/tests/__init__.py b/third_party/transifex-client/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/third_party/transifex-client/tests/test_processors.py b/third_party/transifex-client/tests/test_processors.py deleted file mode 100644 index dd7d7d95..00000000 --- a/third_party/transifex-client/tests/test_processors.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Unit tests for processor functions. -""" - -import unittest -from urlparse import urlparse -from txclib.processors import hostname_tld_migration, hostname_ssl_migration - - -class TestHostname(unittest.TestCase): - """Test for hostname processors.""" - - def test_tld_migration_needed(self): - """ - Test the tld migration of Transifex, when needed. - """ - hostnames = [ - 'http://transifex.net', 'http://www.transifex.net', - 'https://fedora.transifex.net', - ] - for h in hostnames: - hostname = hostname_tld_migration(h) - self.assertTrue(hostname.endswith('com')) - orig_hostname = 'http://www.transifex.net/path/' - hostname = hostname_tld_migration(orig_hostname) - self.assertEqual(hostname, orig_hostname.replace('net', 'com', 1)) - - def test_tld_migration_needed(self): - """ - Test that unneeded tld migrations are detected correctly. - """ - hostnames = [ - 'https://www.transifex.com', 'http://fedora.transifex.com', - 'http://www.example.net/path/' - ] - for h in hostnames: - hostname = hostname_tld_migration(h) - self.assertEqual(hostname, h) - - def test_no_scheme_specified(self): - """ - Test that, if no scheme has been specified, the https one will be used. - """ - hostname = '//transifex.net' - hostname = hostname_ssl_migration(hostname) - self.assertTrue(hostname.startswith('https://')) - - def test_http_replacement(self): - """Test the replacement of http with https.""" - hostnames = [ - 'http://transifex.com', 'http://transifex.net/http/', - 'http://www.transifex.com/path/' - ] - for h in hostnames: - hostname = hostname_ssl_migration(h) - self.assertEqual(hostname[:8], 'https://') - self.assertEqual(hostname[7:], h[6:]) - - def test_no_http_replacement_needed(self): - """Test that http will not be replaces with https, when not needed.""" - for h in ['http://example.com', 'http://example.com/http/']: - hostname = hostname_ssl_migration(h) - self.assertEqual(hostname, hostname) diff --git a/third_party/transifex-client/tests/test_project.py b/third_party/transifex-client/tests/test_project.py deleted file mode 100644 index 3b421214..00000000 --- a/third_party/transifex-client/tests/test_project.py +++ /dev/null @@ -1,531 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import with_statement -import unittest -import contextlib -import itertools -try: - import json -except ImportError: - import simplejson as json -from mock import Mock, patch - -from txclib.project import Project -from txclib.config import Flipdict - - -class TestProject(unittest.TestCase): - - def test_extract_fields(self): - """Test the functions that extract a field from a stats object.""" - stats = { - 'completed': '80%', - 'last_update': '00:00', - 'foo': 'bar', - } - self.assertEqual( - stats['completed'], '%s%%' % Project._extract_completed(stats) - ) - self.assertEqual(stats['last_update'], Project._extract_updated(stats)) - - def test_specifying_resources(self): - """Test the various ways to specify resources in a project.""" - p = Project(init=False) - resources = [ - 'proj1.res1', - 'proj2.res2', - 'transifex.txn', - 'transifex.txo', - ] - with patch.object(p, 'get_resource_list') as mock: - mock.return_value = resources - cmd_args = [ - 'proj1.res1', '*1*', 'transifex*', '*r*', - '*o', 'transifex.tx?', 'transifex.txn', - ] - results = [ - ['proj1.res1', ], - ['proj1.res1', ], - ['transifex.txn', 'transifex.txo', ], - ['proj1.res1', 'proj2.res2', 'transifex.txn', 'transifex.txo', ], - ['transifex.txo', ], - ['transifex.txn', 'transifex.txo', ], - ['transifex.txn', ], - [], - ] - - for i, arg in enumerate(cmd_args): - resources = [arg] - self.assertEqual(p.get_chosen_resources(resources), results[i]) - - # wrong argument - resources = ['*trasnifex*', ] - self.assertRaises(Exception, p.get_chosen_resources, resources) - - -class TestProjectMinimumPercent(unittest.TestCase): - """Test the minimum-perc option.""" - - def setUp(self): - super(TestProjectMinimumPercent, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - - def test_cmd_option(self): - """Test command-line option.""" - self.p.minimum_perc = 20 - results = itertools.cycle([80, 90 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '12%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '20%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '30%'})) - - def test_global_only(self): - """Test only global option.""" - results = itertools.cycle([80, None ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_lower_than_global(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([80, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_higher_than_global(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([60, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_only(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([None, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_no_option(self): - """"Test the case there is nothing defined.""" - results = itertools.cycle([None, None ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertTrue(self.p._satisfies_min_translated({'completed': '0%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '10%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - -class TestProjectFilters(unittest.TestCase): - """Test filters used to decide whether to push/pull a translation or not.""" - - def setUp(self): - super(TestProjectFilters, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - self.stats = { - 'en': { - 'completed': '100%', 'last_update': '2011-11-01 15:00:00', - }, 'el': { - 'completed': '60%', 'last_update': '2011-11-01 15:00:00', - }, 'pt': { - 'completed': '70%', 'last_update': '2011-11-01 15:00:00', - }, - } - self.langs = self.stats.keys() - - def test_add_translation(self): - """Test filters for adding translations. - - We do not test here for minimum percentages. - """ - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - should_add = self.p._should_add_translation - for force in [True, False]: - for lang in self.langs: - self.assertTrue(should_add(lang, self.stats, force)) - - # unknown language - self.assertFalse(should_add('es', self.stats)) - - def test_update_translation(self): - """Test filters for updating a translation. - - We do not test here for minimum percentages. - """ - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - - should_update = self.p._should_update_translation - force = True - for lang in self.langs: - self.assertTrue(should_update(lang, self.stats, 'foo', force)) - - force = False # reminder - local_file = 'foo' - - # unknown language - self.assertFalse(should_update('es', self.stats, local_file)) - - # no local file - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.return_value = None - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_update(lang, self.stats, local_file) - ) - - # older local files - local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_update(lang, self.stats, local_file) - ) - - # newer local files - local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertFalse( - should_update(lang, self.stats, local_file) - ) - - def test_push_translation(self): - """Test filters for pushing a translation file.""" - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - - local_file = 'foo' - should_push = self.p._should_push_translation - force = True - for lang in self.langs: - self.assertTrue(should_push(lang, self.stats, local_file, force)) - - force = False # reminder - - # unknown language - self.assertTrue(should_push('es', self.stats, local_file)) - - # older local files - local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertFalse( - should_push(lang, self.stats, local_file) - ) - - # newer local files - local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_push(lang, self.stats, local_file) - ) - - -class TestProjectPull(unittest.TestCase): - """Test bits & pieces of the pull method.""" - - def setUp(self): - super(TestProjectPull, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - self.p.host = 'foo' - self.p.project_slug = 'foo' - self.p.resource_slug = 'foo' - self.stats = { - 'en': { - 'completed': '100%', 'last_update': '2011-11-01 15:00:00', - }, 'el': { - 'completed': '60%', 'last_update': '2011-11-01 15:00:00', - }, 'pt': { - 'completed': '70%', 'last_update': '2011-11-01 15:00:00', - }, - } - self.langs = self.stats.keys() - self.files = dict(zip(self.langs, itertools.repeat(None))) - self.details = {'available_languages': []} - for lang in self.langs: - self.details['available_languages'].append({'code': lang}) - self.slang = 'en' - self.lang_map = Flipdict() - - def test_new_translations(self): - """Test finding new transaltions to add.""" - with patch.object(self.p, 'do_url_request') as resource_mock: - resource_mock.return_value = json.dumps(self.details) - files_keys = self.langs - new_trans = self.p._new_translations_to_add - for force in [True, False]: - res = new_trans( - self.files, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set([])) - - with patch.object(self.p, '_should_add_translation') as filter_mock: - filter_mock.return_value = True - for force in [True, False]: - res = new_trans( - {'el': None}, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set(['pt'])) - for force in [True, False]: - res = new_trans( - {}, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set(['el', 'pt'])) - - files = {} - files['pt_PT'] = None - lang_map = {'pt': 'pt_PT'} - for force in [True, False]: - res = new_trans( - files, self.slang, lang_map, self.stats, force - ) - self.assertEquals(res, set(['el'])) - - def test_languages_to_pull_empty_initial_list(self): - """Test determining the languages to pull, when the initial - list is empty. - """ - languages = [] - force = False - - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['el', 'en', 'pt'])) - self.assertFalse(new) - - del self.files['el'] - self.files['el-gr'] = None - self.lang_map['el'] = 'el-gr' - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['el', 'en', 'pt'])) - self.assertFalse(new) - - def test_languages_to_pull_with_initial_list(self): - """Test determining the languages to pull, then there is a - language selection from the user. - """ - languages = ['el', 'en'] - self.lang_map['el'] = 'el-gr' - del self.files['el'] - self.files['el-gr'] = None - force = False - - with patch.object(self.p, '_should_add_translation') as mock: - mock.return_value = True - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', 'el-gr', ])) - self.assertFalse(new) - - mock.return_value = False - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', 'el-gr', ])) - self.assertFalse(new) - - del self.files['el-gr'] - mock.return_value = True - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', ])) - self.assertEquals(new, set(['el', ])) - - mock.return_value = False - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', ])) - self.assertEquals(new, set([])) - - def test_in_combination_with_force_option(self): - """Test the minumum-perc option along with -f.""" - with patch.object(self.p, 'get_resource_option') as mock: - mock.return_value = 70 - - res = self.p._should_download('de', self.stats, None, False) - self.assertEquals(res, False) - res = self.p._should_download('el', self.stats, None, False) - self.assertEquals(res, False) - res = self.p._should_download('el', self.stats, None, True) - self.assertEquals(res, False) - res = self.p._should_download('en', self.stats, None, False) - self.assertEquals(res, True) - res = self.p._should_download('en', self.stats, None, True) - self.assertEquals(res, True) - - with patch.object(self.p, '_remote_is_newer') as local_file_mock: - local_file_mock = False - res = self.p._should_download('pt', self.stats, None, False) - self.assertEquals(res, True) - res = self.p._should_download('pt', self.stats, None, True) - self.assertEquals(res, True) - - -class TestFormats(unittest.TestCase): - """Tests for the supported formats.""" - - def setUp(self): - self.p = Project(init=False) - - def test_extensions(self): - """Test returning the correct extension for a format.""" - sample_formats = { - 'PO': {'file-extensions': '.po, .pot'}, - 'QT': {'file-extensions': '.ts'}, - } - extensions = ['.po', '.ts', '', ] - with patch.object(self.p, "do_url_request") as mock: - mock.return_value = json.dumps(sample_formats) - for (type_, ext) in zip(['PO', 'QT', 'NONE', ], extensions): - extension = self.p._extension_for(type_) - self.assertEquals(extension, ext) - - -class TestOptions(unittest.TestCase): - """Test the methods related to parsing the configuration file.""" - - def setUp(self): - self.p = Project(init=False) - - def test_get_option(self): - """Test _get_option method.""" - with contextlib.nested( - patch.object(self.p, 'get_resource_option'), - patch.object(self.p, 'config', create=True) - ) as (rmock, cmock): - rmock.return_value = 'resource' - cmock.has_option.return_value = 'main' - cmock.get.return_value = 'main' - self.assertEqual(self.p._get_option(None, None), 'resource') - rmock.return_value = None - cmock.has_option.return_value = 'main' - cmock.get.return_value = 'main' - self.assertEqual(self.p._get_option(None, None), 'main') - cmock.has_option.return_value = None - self.assertIs(self.p._get_option(None, None), None) - - -class TestConfigurationOptions(unittest.TestCase): - """Test the various configuration options.""" - - def test_i18n_type(self): - p = Project(init=False) - type_string = 'type' - i18n_type = 'PO' - with patch.object(p, 'config', create=True) as config_mock: - p.set_i18n_type([], i18n_type) - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - self.assertEquals('main', calls[0][1][0]) - p.set_i18n_type(['transifex.txo'], 'PO') - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - p.set_i18n_type(['transifex.txo', 'transifex.txn'], 'PO') - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - self.assertEquals('set', calls[1][0]) - - -class TestStats(unittest.TestCase): - """Test the access to the stats objects.""" - - def setUp(self): - self.stats = Mock() - self.stats.__getitem__ = Mock() - self.stats.__getitem__.return_value = '12%' - - def test_field_used_per_mode(self): - """Test the fields used for each mode.""" - Project._extract_completed(self.stats, 'translate') - self.stats.__getitem__.assert_called_with('completed') - Project._extract_completed(self.stats, 'reviewed') - self.stats.__getitem__.assert_called_with('reviewed_percentage') - diff --git a/third_party/transifex-client/tx b/third_party/transifex-client/tx deleted file mode 100755 index dfb4a4c1..00000000 --- a/third_party/transifex-client/tx +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from optparse import OptionParser, OptionValueError -import os -import sys - -from txclib import utils -from txclib import get_version -from txclib.log import set_log_level, logger - -reload(sys) # WTF? Otherwise setdefaultencoding doesn't work - -# This block ensures that ^C interrupts are handled quietly. -try: - import signal - - def exithandler(signum,frame): - signal.signal(signal.SIGINT, signal.SIG_IGN) - signal.signal(signal.SIGTERM, signal.SIG_IGN) - sys.exit(1) - - signal.signal(signal.SIGINT, exithandler) - signal.signal(signal.SIGTERM, exithandler) - if hasattr(signal, 'SIGPIPE'): - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - -except KeyboardInterrupt: - sys.exit(1) - -# When we open file with f = codecs.open we specifi FROM what encoding to read -# This sets the encoding for the strings which are created with f.read() -sys.setdefaultencoding('utf-8') - - -def main(argv): - """ - Here we parse the flags (short, long) and we instantiate the classes. - """ - usage = "usage: %prog [options] command [cmd_options]" - description = "This is the Transifex command line client which"\ - " allows you to manage your translations locally and sync"\ - " them with the master Transifex server.\nIf you'd like to"\ - " check the available commands issue `%prog help` or if you"\ - " just want help with a specific command issue `%prog help"\ - " command`" - - parser = OptionParser( - usage=usage, version=get_version(), description=description - ) - parser.disable_interspersed_args() - parser.add_option( - "-d", "--debug", action="store_true", dest="debug", - default=False, help=("enable debug messages") - ) - parser.add_option( - "-q", "--quiet", action="store_true", dest="quiet", - default=False, help="don't print status messages to stdout" - ) - parser.add_option( - "-r", "--root", action="store", dest="root_dir", type="string", - default=None, help="change root directory (default is cwd)" - ) - parser.add_option( - "--traceback", action="store_true", dest="trace", default=False, - help="print full traceback on exceptions" - ) - parser.add_option( - "--disable-colors", action="store_true", dest="color_disable", - default=(os.name == 'nt' or not sys.stdout.isatty()), - help="disable colors in the output of commands" - ) - (options, args) = parser.parse_args() - - if len(args) < 1: - parser.error("No command was given") - - utils.DISABLE_COLORS = options.color_disable - - # set log level - if options.quiet: - set_log_level('WARNING') - elif options.debug: - set_log_level('DEBUG') - - # find .tx - path_to_tx = options.root_dir or utils.find_dot_tx() - - - cmd = args[0] - try: - utils.exec_command(cmd, args[1:], path_to_tx) - except utils.UnknownCommandError: - logger.error("tx: Command %s not found" % cmd) - except SystemExit: - sys.exit() - except: - import traceback - if options.trace: - traceback.print_exc() - else: - formatted_lines = traceback.format_exc().splitlines() - logger.error(formatted_lines[-1]) - sys.exit(1) - -# Run baby :) ... run -if __name__ == "__main__": - # sys.argv[0] is the name of the script that we’re running. - main(sys.argv[1:]) diff --git a/third_party/transifex-client/txclib/__init__.py b/third_party/transifex-client/txclib/__init__.py deleted file mode 100644 index 814773c8..00000000 --- a/third_party/transifex-client/txclib/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Copyright (C) 2010 by Indifex (www.indifex.com), see AUTHORS. -License: BSD, see LICENSE for details. - -For further information visit http://code.indifex.com/transifex-client -""" - - -VERSION = (0, 9, 0, 'devel') - -def get_version(): - version = '%s.%s' % (VERSION[0], VERSION[1]) - if VERSION[2]: - version = '%s.%s' % (version, VERSION[2]) - if VERSION[3] != 'final': - version = '%s %s' % (version, VERSION[3]) - return version diff --git a/third_party/transifex-client/txclib/commands.py b/third_party/transifex-client/txclib/commands.py deleted file mode 100644 index 282287fc..00000000 --- a/third_party/transifex-client/txclib/commands.py +++ /dev/null @@ -1,576 +0,0 @@ -# -*- coding: utf-8 -*- -""" -In this file we have all the top level commands for the transifex client. -Since we're using a way to automatically list them and execute them, when -adding code to this file you must take care of the following: - * Added functions must begin with 'cmd_' followed by the actual name of the - command being used in the command line (eg cmd_init) - * The description for each function that we display to the user is read from - the func_doc attribute which reads the doc string. So, when adding - docstring to a new function make sure you add an oneliner which is - descriptive and is meant to be seen by the user. - * When including libraries, it's best if you include modules instead of - functions because that way our function resolution will work faster and the - chances of overlapping are minimal - * All functions should use the OptionParser and should have a usage and - descripition field. -""" -import os -import re, shutil -import sys -from optparse import OptionParser, OptionGroup -import ConfigParser - - -from txclib import utils, project -from txclib.utils import parse_json, compile_json, relpath -from txclib.config import OrderedRawConfigParser -from txclib.exceptions import UnInitializedError -from txclib.parsers import delete_parser, help_parser, parse_csv_option, \ - status_parser, pull_parser, set_parser, push_parser, init_parser -from txclib.log import logger - - -def cmd_init(argv, path_to_tx): - "Initialize a new transifex project." - parser = init_parser() - (options, args) = parser.parse_args(argv) - if len(args) > 1: - parser.error("Too many arguments were provided. Aborting...") - if args: - path_to_tx = args[0] - else: - path_to_tx = os.getcwd() - - if os.path.isdir(os.path.join(path_to_tx,".tx")): - logger.info("tx: There is already a tx folder!") - reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") - while (reinit != 'y' and reinit != 'Y' and reinit != 'N' and reinit != 'n' and reinit != ''): - reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") - if not reinit or reinit in ['N', 'n', 'NO', 'no', 'No']: - return - # Clean the old settings - # FIXME: take a backup - else: - rm_dir = os.path.join(path_to_tx, ".tx") - shutil.rmtree(rm_dir) - - logger.info("Creating .tx folder...") - os.mkdir(os.path.join(path_to_tx,".tx")) - - # Handle the credentials through transifexrc - home = os.path.expanduser("~") - txrc = os.path.join(home, ".transifexrc") - config = OrderedRawConfigParser() - - default_transifex = "https://www.transifex.com" - transifex_host = options.host or raw_input("Transifex instance [%s]: " % default_transifex) - - if not transifex_host: - transifex_host = default_transifex - if not transifex_host.startswith(('http://', 'https://')): - transifex_host = 'https://' + transifex_host - - config_file = os.path.join(path_to_tx, ".tx", "config") - if not os.path.exists(config_file): - # The path to the config file (.tx/config) - logger.info("Creating skeleton...") - config = OrderedRawConfigParser() - config.add_section('main') - config.set('main', 'host', transifex_host) - # Touch the file if it doesn't exist - logger.info("Creating config file...") - fh = open(config_file, 'w') - config.write(fh) - fh.close() - - prj = project.Project(path_to_tx) - prj.getset_host_credentials(transifex_host, user=options.user, - password=options.password) - prj.save() - logger.info("Done.") - - -def cmd_set(argv, path_to_tx): - "Add local or remote files under transifex" - parser = set_parser() - (options, args) = parser.parse_args(argv) - - # Implement options/args checks - # TODO !!!!!!! - if options.local: - try: - expression = args[0] - except IndexError: - parser.error("Please specify an expression.") - if not options.resource: - parser.error("Please specify a resource") - if not options.source_language: - parser.error("Please specify a source language.") - if not '' in expression: - parser.error("The expression you have provided is not valid.") - if not utils.valid_slug(options.resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - _auto_local(path_to_tx, options.resource, - source_language=options.source_language, - expression = expression, source_file=options.source_file, - execute=options.execute, regex=False) - if options.execute: - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - _set_mode(options.resource, options.mode, path_to_tx) - _set_type(options.resource, options.i18n_type, path_to_tx) - return - - if options.remote: - try: - url = args[0] - except IndexError: - parser.error("Please specify an remote url") - _auto_remote(path_to_tx, url) - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - _set_mode(options.resource, options.mode, path_to_tx) - return - - if options.is_source: - resource = options.resource - if not resource: - parser.error("You must specify a resource name with the" - " -r|--resource flag.") - - lang = options.language - if not lang: - parser.error("Please specify a source language.") - - if len(args) != 1: - parser.error("Please specify a file.") - - if not utils.valid_slug(resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - - file = args[0] - # Calculate relative path - path_to_file = relpath(file, path_to_tx) - _set_source_file(path_to_tx, resource, options.language, path_to_file) - elif options.resource or options.language: - resource = options.resource - lang = options.language - - if len(args) != 1: - parser.error("Please specify a file") - - # Calculate relative path - path_to_file = relpath(args[0], path_to_tx) - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - if not utils.valid_slug(resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - _set_translation(path_to_tx, resource, lang, path_to_file) - - _set_mode(options.resource, options.mode, path_to_tx) - _set_type(options.resource, options.i18n_type, path_to_tx) - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - - logger.info("Done.") - return - - -def _auto_local(path_to_tx, resource, source_language, expression, execute=False, - source_file=None, regex=False): - """Auto configure local project.""" - # The path everything will be relative to - curpath = os.path.abspath(os.curdir) - - # Force expr to be a valid regex expr (escaped) but keep intact - expr_re = utils.regex_from_filefilter(expression, curpath) - expr_rec = re.compile(expr_re) - - if not execute: - logger.info("Only printing the commands which will be run if the " - "--execute switch is specified.") - - # First, let's construct a dictionary of all matching files. - # Note: Only the last matching file of a language will be stored. - translation_files = {} - for root, dirs, files in os.walk(curpath): - for f in files: - f_path = os.path.abspath(os.path.join(root, f)) - match = expr_rec.match(f_path) - if match: - lang = match.group(1) - f_path = os.path.abspath(f_path) - if lang == source_language and not source_file: - source_file = f_path - else: - translation_files[lang] = f_path - - if not source_file: - raise Exception("Could not find a source language file. Please run" - " set --source manually and then re-run this command or provide" - " the source file with the -s flag.") - if execute: - logger.info("Updating source for resource %s ( %s -> %s )." % (resource, - source_language, relpath(source_file, path_to_tx))) - _set_source_file(path_to_tx, resource, source_language, - relpath(source_file, path_to_tx)) - else: - logger.info('\ntx set --source -r %(res)s -l %(lang)s %(file)s\n' % { - 'res': resource, - 'lang': source_language, - 'file': relpath(source_file, curpath)}) - - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if execute: - try: - prj.config.get("%s" % resource, "source_file") - except ConfigParser.NoSectionError: - raise Exception("No resource with slug \"%s\" was found.\nRun 'tx set --auto" - "-local -r %s \"expression\"' to do the initial configuration." % resource) - - # Now let's handle the translation files. - if execute: - logger.info("Updating file expression for resource %s ( %s )." % (resource, - expression)) - # Eval file_filter relative to root dir - file_filter = relpath(os.path.join(curpath, expression), - path_to_tx) - prj.config.set("%s" % resource, "file_filter", file_filter) - else: - for (lang, f_path) in sorted(translation_files.items()): - logger.info('tx set -r %(res)s -l %(lang)s %(file)s' % { - 'res': resource, - 'lang': lang, - 'file': relpath(f_path, curpath)}) - - if execute: - prj.save() - - -def _auto_remote(path_to_tx, url): - """ - Initialize a remote release/project/resource to the current directory. - """ - logger.info("Auto configuring local project from remote URL...") - - type, vars = utils.parse_tx_url(url) - prj = project.Project(path_to_tx) - username, password = prj.getset_host_credentials(vars['hostname']) - - if type == 'project': - logger.info("Getting details for project %s" % vars['project']) - proj_info = utils.get_details('project_details', - username, password, - hostname = vars['hostname'], project = vars['project']) - resources = [ '.'.join([vars['project'], r['slug']]) for r in proj_info['resources'] ] - logger.info("%s resources found. Configuring..." % len(resources)) - elif type == 'release': - logger.info("Getting details for release %s" % vars['release']) - rel_info = utils.get_details('release_details', - username, password, hostname = vars['hostname'], - project = vars['project'], release = vars['release']) - resources = [] - for r in rel_info['resources']: - if r.has_key('project'): - resources.append('.'.join([r['project']['slug'], r['slug']])) - else: - resources.append('.'.join([vars['project'], r['slug']])) - logger.info("%s resources found. Configuring..." % len(resources)) - elif type == 'resource': - logger.info("Getting details for resource %s" % vars['resource']) - resources = [ '.'.join([vars['project'], vars['resource']]) ] - else: - raise("Url '%s' is not recognized." % url) - - for resource in resources: - logger.info("Configuring resource %s." % resource) - proj, res = resource.split('.') - res_info = utils.get_details('resource_details', - username, password, hostname = vars['hostname'], - project = proj, resource=res) - try: - source_lang = res_info['source_language_code'] - i18n_type = res_info['i18n_type'] - except KeyError: - raise Exception("Remote server seems to be running an unsupported version" - " of Transifex. Either update your server software of fallback" - " to a previous version of transifex-client.") - prj.set_remote_resource( - resource=resource, - host = vars['hostname'], - source_lang = source_lang, - i18n_type = i18n_type) - - prj.save() - - -def cmd_push(argv, path_to_tx): - "Push local files to remote server" - parser = push_parser() - (options, args) = parser.parse_args(argv) - force_creation = options.force_creation - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - prj = project.Project(path_to_tx) - if not (options.push_source or options.push_translations): - parser.error("You need to specify at least one of the -s|--source," - " -t|--translations flags with the push command.") - - prj.push( - force=force_creation, resources=resources, languages=languages, - skip=skip, source=options.push_source, - translations=options.push_translations, - no_interactive=options.no_interactive - ) - logger.info("Done.") - - -def cmd_pull(argv, path_to_tx): - "Pull files from remote server to local repository" - parser = pull_parser() - (options, args) = parser.parse_args(argv) - if options.fetchall and options.languages: - parser.error("You can't user a language filter along with the"\ - " -a|--all option") - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - minimum_perc = options.minimum_perc or None - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - # instantiate the project.Project - prj = project.Project(path_to_tx) - prj.pull( - languages=languages, resources=resources, overwrite=options.overwrite, - fetchall=options.fetchall, fetchsource=options.fetchsource, - force=options.force, skip=skip, minimum_perc=minimum_perc, - mode=options.mode - ) - logger.info("Done.") - - -def _set_source_file(path_to_tx, resource, lang, path_to_file): - """Reusable method to set source file.""" - proj, res = resource.split('.') - if not proj or not res: - raise Exception("\"%s.%s\" is not a valid resource identifier. It should" - " be in the following format project_slug.resource_slug." % - (proj, res)) - if not lang: - raise Exception("You haven't specified a source language.") - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - if not os.path.exists(path_to_file): - raise Exception("tx: File ( %s ) does not exist." % - os.path.join(path_to_tx, path_to_file)) - - # instantiate the project.Project - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): - raise Exception("File must be under the project root directory.") - - logger.info("Setting source file for resource %s.%s ( %s -> %s )." % ( - proj, res, lang, path_to_file)) - - path_to_file = relpath(path_to_file, root_dir) - - prj = project.Project(path_to_tx) - - # FIXME: Check also if the path to source file already exists. - try: - try: - prj.config.get("%s.%s" % (proj, res), "source_file") - except ConfigParser.NoSectionError: - prj.config.add_section("%s.%s" % (proj, res)) - except ConfigParser.NoOptionError: - pass - finally: - prj.config.set("%s.%s" % (proj, res), "source_file", - path_to_file) - prj.config.set("%s.%s" % (proj, res), "source_lang", - lang) - - prj.save() - - -def _set_translation(path_to_tx, resource, lang, path_to_file): - """Reusable method to set translation file.""" - - proj, res = resource.split('.') - if not project or not resource: - raise Exception("\"%s\" is not a valid resource identifier. It should" - " be in the following format project_slug.resource_slug." % - resource) - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - # Warn the user if the file doesn't exist - if not os.path.exists(path_to_file): - logger.info("Warning: File '%s' doesn't exist." % path_to_file) - - # instantiate the project.Project - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): - raise Exception("File must be under the project root directory.") - - if lang == prj.config.get("%s.%s" % (proj, res), "source_lang"): - raise Exception("tx: You cannot set translation file for the source language." - " Source languages contain the strings which will be translated!") - - logger.info("Updating translations for resource %s ( %s -> %s )." % (resource, - lang, path_to_file)) - path_to_file = relpath(path_to_file, root_dir) - prj.config.set("%s.%s" % (proj, res), "trans.%s" % lang, - path_to_file) - - prj.save() - - -def cmd_status(argv, path_to_tx): - "Print status of current project" - parser = status_parser() - (options, args) = parser.parse_args(argv) - resources = parse_csv_option(options.resources) - prj = project.Project(path_to_tx) - resources = prj.get_chosen_resources(resources) - resources_num = len(resources) - for idx, res in enumerate(resources): - p, r = res.split('.') - logger.info("%s -> %s (%s of %s)" % (p, r, idx + 1, resources_num)) - logger.info("Translation Files:") - slang = prj.get_resource_option(res, 'source_lang') - sfile = prj.get_resource_option(res, 'source_file') or "N/A" - lang_map = prj.get_resource_lang_mapping(res) - logger.info(" - %s: %s (%s)" % (utils.color_text(slang, "RED"), - sfile, utils.color_text("source", "YELLOW"))) - files = prj.get_resource_files(res) - fkeys = files.keys() - fkeys.sort() - for lang in fkeys: - local_lang = lang - if lang in lang_map.values(): - local_lang = lang_map.flip[lang] - logger.info(" - %s: %s" % (utils.color_text(local_lang, "RED"), - files[lang])) - logger.info("") - - -def cmd_help(argv, path_to_tx): - """List all available commands""" - parser = help_parser() - (options, args) = parser.parse_args(argv) - if len(args) > 1: - parser.error("Multiple arguments received. Exiting...") - - # Get all commands - fns = utils.discover_commands() - - # Print help for specific command - if len(args) == 1: - try: - fns[argv[0]](['--help'], path_to_tx) - except KeyError: - utils.logger.error("Command %s not found" % argv[0]) - # or print summary of all commands - - # the code below will only be executed if the KeyError exception is thrown - # becuase in all other cases the function called with --help will exit - # instead of return here - keys = fns.keys() - keys.sort() - - logger.info("Transifex command line client.\n") - logger.info("Available commands are:") - for key in keys: - logger.info(" %-15s\t%s" % (key, fns[key].func_doc)) - logger.info("\nFor more information run %s command --help" % sys.argv[0]) - - -def cmd_delete(argv, path_to_tx): - "Delete an accessible resource or translation in a remote server." - parser = delete_parser() - (options, args) = parser.parse_args(argv) - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - force = options.force_delete - prj = project.Project(path_to_tx) - prj.delete(resources, languages, skip, force) - logger.info("Done.") - - -def _go_to_dir(path): - """Change the current working directory to the directory specified as - argument. - - Args: - path: The path to chdor to. - Raises: - UnInitializedError, in case the directory has not been initialized. - """ - if path is None: - raise UnInitializedError( - "Directory has not been initialzied. " - "Did you forget to run 'tx init' first?" - ) - os.chdir(path) - - -def _set_minimum_perc(resource, value, path_to_tx): - """Set the minimum percentage in the .tx/config file.""" - args = (resource, 'minimum_perc', value, path_to_tx, 'set_min_perc') - _set_project_option(*args) - - -def _set_mode(resource, value, path_to_tx): - """Set the mode in the .tx/config file.""" - args = (resource, 'mode', value, path_to_tx, 'set_default_mode') - _set_project_option(*args) - - -def _set_type(resource, value, path_to_tx): - """Set the i18n type in the .tx/config file.""" - args = (resource, 'type', value, path_to_tx, 'set_i18n_type') - _set_project_option(*args) - - -def _set_project_option(resource, name, value, path_to_tx, func_name): - """Save the option to the project config file.""" - if value is None: - return - if not resource: - logger.debug("Setting the %s for all resources." % name) - resources = [] - else: - logger.debug("Setting the %s for resource %s." % (name, resource)) - resources = [resource, ] - prj = project.Project(path_to_tx) - getattr(prj, func_name)(resources, value) - prj.save() diff --git a/third_party/transifex-client/txclib/config.py b/third_party/transifex-client/txclib/config.py deleted file mode 100644 index a9d055f5..00000000 --- a/third_party/transifex-client/txclib/config.py +++ /dev/null @@ -1,115 +0,0 @@ -import ConfigParser - - -class OrderedRawConfigParser( ConfigParser.RawConfigParser ): - """ - Overload standard Class ConfigParser.RawConfigParser - """ - def write(self, fp): - """Write an .ini-format representation of the configuration state.""" - if self._defaults: - fp.write("[%s]\n" % DEFAULTSECT) - for key in sorted( self._defaults ): - fp.write( "%s = %s\n" % (key, str( self._defaults[ key ] - ).replace('\n', '\n\t')) ) - fp.write("\n") - for section in self._sections: - fp.write("[%s]\n" % section) - for key in sorted( self._sections[section] ): - if key != "__name__": - fp.write("%s = %s\n" % - (key, str( self._sections[section][ key ] - ).replace('\n', '\n\t'))) - fp.write("\n") - - optionxform = str - - -_NOTFOUND = object() - - -class Flipdict(dict): - """An injective (one-to-one) python dict. Ensures that each key maps - to a unique value, and each value maps back to that same key. - - Code mostly taken from here: - http://code.activestate.com/recipes/576968-flipdict-python-dict-that-also-maintains-a-one-to-/ - """ - - def __init__(self, *args, **kw): - self._flip = dict.__new__(self.__class__) - setattr(self._flip, "_flip", self) - for key, val in dict(*args, **kw).iteritems(): - self[key] = val - - @property - def flip(self): - """The inverse mapping.""" - return self._flip - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, dict(self)) - - __str__ = __repr__ - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, keys, value=None): - return cls(dict.fromkeys(keys, value)) - - def __setitem__(self, key, val): - k = self._flip.get(val, _NOTFOUND) - if not (k is _NOTFOUND or k==key): - raise KeyError('(key,val) would erase mapping for value %r' % val) - - v = self.get(key, _NOTFOUND) - if v is not _NOTFOUND: - dict.__delitem__(self._flip, v) - - dict.__setitem__(self, key, val) - dict.__setitem__(self._flip, val, key) - - def setdefault(self, key, default = None): - # Copied from python's UserDict.DictMixin code. - try: - return self[key] - except KeyError: - self[key] = default - return default - - def update(self, other = None, **kwargs): - # Copied from python's UserDict.DictMixin code. - # Make progressively weaker assumptions about "other" - if other is None: - pass - elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups - for k, v in other.iteritems(): - self[k] = v - elif hasattr(other, 'keys'): - for k in other.keys(): - self[k] = other[k] - else: - for k, v in other: - self[k] = v - if kwargs: - self.update(kwargs) - - def __delitem__(self, key): - val = dict.pop(self, key) - dict.__delitem__(self._flip, val) - - def pop(self, key, *args): - val = dict.pop(self, key, *args) - dict.__delitem__(self._flip, val) - return val - - def popitem(self): - key, val = dict.popitem(self) - dict.__delitem__(self._flip, val) - return key, val - - def clear(self): - dict.clear(self) - dict.clear(self._flip) diff --git a/third_party/transifex-client/txclib/exceptions.py b/third_party/transifex-client/txclib/exceptions.py deleted file mode 100644 index 8766a018..00000000 --- a/third_party/transifex-client/txclib/exceptions.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Exception classes for the tx client. -""" - - -class UnInitializedError(Exception): - """The project directory has not been initialized.""" - - -class UnknownCommandError(Exception): - """The provided command is not supported.""" diff --git a/third_party/transifex-client/txclib/http_utils.py b/third_party/transifex-client/txclib/http_utils.py deleted file mode 100644 index 3243149c..00000000 --- a/third_party/transifex-client/txclib/http_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -HTTP-related utility functions. -""" - -from __future__ import with_statement -import gzip -try: - import cStringIO as StringIO -except ImportError: - import StringIO - - -def _gzip_decode(gzip_data): - """ - Unzip gzipped data and return them. - - :param gzip_data: Gzipped data. - :returns: The actual data. - """ - try: - gzip_data = StringIO.StringIO(gzip_data) - gzip_file = gzip.GzipFile(fileobj=gzip_data) - data = gzip_file.read() - return data - finally: - gzip_data.close() - - -def http_response(response): - """ - Return the response of a HTTP request. - - If the response has been gzipped, gunzip it first. - - :param response: The raw response of a HTTP request. - :returns: A response suitable to be used by clients. - """ - metadata = response.info() - data = response.read() - response.close() - if metadata.get('content-encoding') == 'gzip': - return _gzip_decode(data) - else: - return data diff --git a/third_party/transifex-client/txclib/log.py b/third_party/transifex-client/txclib/log.py deleted file mode 100644 index 9baf3220..00000000 --- a/third_party/transifex-client/txclib/log.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Add logging capabilities to tx-client. -""" - -import sys -import logging - -_logger = logging.getLogger('txclib') -_logger.setLevel(logging.INFO) - -_formatter = logging.Formatter('%(message)s') - -_error_handler = logging.StreamHandler(sys.stderr) -_error_handler.setLevel(logging.ERROR) -_error_handler.setFormatter(_formatter) -_logger.addHandler(_error_handler) - -_msg_handler = logging.StreamHandler(sys.stdout) -_msg_handler.setLevel(logging.DEBUG) -_msg_handler.setFormatter(_formatter) -_msg_filter = logging.Filter() -_msg_filter.filter = lambda r: r.levelno < logging.ERROR -_msg_handler.addFilter(_msg_filter) -_logger.addHandler(_msg_handler) - -logger = _logger - - -def set_log_level(level): - """Set the level for the logger. - - Args: - level: A string among DEBUG, INFO, WARNING, ERROR, CRITICAL. - """ - logger.setLevel(getattr(logging, level)) diff --git a/third_party/transifex-client/txclib/parsers.py b/third_party/transifex-client/txclib/parsers.py deleted file mode 100644 index fd3237d2..00000000 --- a/third_party/transifex-client/txclib/parsers.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -from optparse import OptionParser, OptionGroup - - -class EpilogParser(OptionParser): - def format_epilog(self, formatter): - return self.epilog - - -def delete_parser(): - """Return the command-line parser for the delete command.""" - usage = "usage: %prog [tx_options] delete OPTION [OPTIONS]" - description = ( - "This command deletes translations for a resource in the remote server." - ) - epilog = ( - "\nExamples:\n" - " To delete a translation:\n " - "$ tx delete -r project.resource -l \n\n" - " To delete a resource:\n $ tx delete -r project.resource\n" - ) - parser = EpilogParser(usage=usage, description=description, epilog=epilog) - parser.add_option( - "-r", "--resource", action="store", dest="resources", default=None, - help="Specify the resource you want to delete (defaults to all)" - ) - parser.add_option( - "-l","--language", action="store", dest="languages", - default=None, help="Specify the translation you want to delete" - ) - parser.add_option( - "--skip", action="store_true", dest="skip_errors", default=False, - help="Don't stop on errors." - ) - parser.add_option( - "-f","--force", action="store_true", dest="force_delete", - default=False, help="Delete an entity forcefully." - ) - return parser - - -def help_parser(): - """Return the command-line parser for the help command.""" - usage="usage: %prog help command" - description="Lists all available commands in the transifex command"\ - " client. If a command is specified, the help page of the specific"\ - " command is displayed instead." - - parser = OptionParser(usage=usage, description=description) - return parser - - -def init_parser(): - """Return the command-line parser for the init command.""" - usage="usage: %prog [tx_options] init " - description="This command initializes a new project for use with"\ - " transifex. It is recommended to execute this command in the"\ - " top level directory of your project so that you can include"\ - " all files under it in transifex. If no path is provided, the"\ - " current working dir will be used." - parser = OptionParser(usage=usage, description=description) - parser.add_option("--host", action="store", dest="host", - default=None, help="Specify a default Transifex host.") - parser.add_option("--user", action="store", dest="user", - default=None, help="Specify username for Transifex server.") - parser.add_option("--pass", action="store", dest="password", - default=None, help="Specify password for Transifex server.") - return parser - - -def pull_parser(): - """Return the command-line parser for the pull command.""" - usage="usage: %prog [tx_options] pull [options]" - description="This command pulls all outstanding changes from the remote"\ - " Transifex server to the local repository. By default, only the"\ - " files that are watched by Transifex will be updated but if you"\ - " want to fetch the translations for new languages as well, use the"\ - " -a|--all option. (Note: new translations are saved in the .tx folder"\ - " and require the user to manually rename them and add then in "\ - " transifex using the set_translation command)." - parser = OptionParser(usage=usage,description=description) - parser.add_option("-l","--language", action="store", dest="languages", - default=[], help="Specify which translations you want to pull" - " (defaults to all)") - parser.add_option("-r","--resource", action="store", dest="resources", - default=[], help="Specify the resource for which you want to pull" - " the translations (defaults to all)") - parser.add_option("-a","--all", action="store_true", dest="fetchall", - default=False, help="Fetch all translation files from server (even new" - " ones)") - parser.add_option("-s","--source", action="store_true", dest="fetchsource", - default=False, help="Force the fetching of the source file (default:" - " False)") - parser.add_option("-f","--force", action="store_true", dest="force", - default=False, help="Force download of translations files.") - parser.add_option("--skip", action="store_true", dest="skip_errors", - default=False, help="Don't stop on errors. Useful when pushing many" - " files concurrently.") - parser.add_option("--disable-overwrite", action="store_false", - dest="overwrite", default=True, - help="By default transifex will fetch new translations files and"\ - " replace existing ones. Use this flag if you want to disable"\ - " this feature") - parser.add_option("--minimum-perc", action="store", type="int", - dest="minimum_perc", default=0, - help="Specify the minimum acceptable percentage of a translation " - "in order to download it.") - parser.add_option( - "--mode", action="store", dest="mode", help=( - "Specify the mode of the translation file to pull (e.g. " - "'reviewed'). See http://bit.ly/txcmod1 for available values." - ) - ) - return parser - - -def push_parser(): - """Return the command-line parser for the push command.""" - usage="usage: %prog [tx_options] push [options]" - description="This command pushes all local files that have been added to"\ - " Transifex to the remote server. All new translations are merged"\ - " with existing ones and if a language doesn't exists then it gets"\ - " created. If you want to push the source file as well (either"\ - " because this is your first time running the client or because"\ - " you just have updated with new entries), use the -f|--force option."\ - " By default, this command will push all files which are watched by"\ - " Transifex but you can filter this per resource or/and language." - parser = OptionParser(usage=usage, description=description) - parser.add_option("-l","--language", action="store", dest="languages", - default=None, help="Specify which translations you want to push" - " (defaults to all)") - parser.add_option("-r","--resource", action="store", dest="resources", - default=None, help="Specify the resource for which you want to push" - " the translations (defaults to all)") - parser.add_option("-f","--force", action="store_true", dest="force_creation", - default=False, help="Push source files without checking modification" - " times.") - parser.add_option("--skip", action="store_true", dest="skip_errors", - default=False, help="Don't stop on errors. Useful when pushing many" - " files concurrently.") - parser.add_option("-s", "--source", action="store_true", dest="push_source", - default=False, help="Push the source file to the server.") - - parser.add_option("-t", "--translations", action="store_true", dest="push_translations", - default=False, help="Push the translation files to the server") - parser.add_option("--no-interactive", action="store_true", dest="no_interactive", - default=False, help="Don't require user input when forcing a push.") - return parser - - -def set_parser(): - """Return the command-line parser for the set command.""" - usage="usage: %prog [tx_options] set [options] [args]" - description="This command can be used to create a mapping between files"\ - " and projects either using local files or using files from a remote"\ - " Transifex server." - epilog="\nExamples:\n"\ - " To set the source file:\n $ tx set -r project.resource --source -l en \n\n"\ - " To set a single translation file:\n $ tx set -r project.resource -l de \n\n"\ - " To automatically detect and assign the source files and translations:\n"\ - " $ tx set --auto-local -r project.resource 'expr' --source-lang en\n\n"\ - " To set a specific file as a source and auto detect translations:\n"\ - " $ tx set --auto-local -r project.resource 'expr' --source-lang en"\ - " --source-file \n\n"\ - " To set a remote release/resource/project:\n"\ - " $ tx set --auto-remote \n" - parser = EpilogParser(usage=usage, description=description, epilog=epilog) - parser.add_option("--auto-local", action="store_true", dest="local", - default=False, help="Used when auto configuring local project.") - parser.add_option("--auto-remote", action="store_true", dest="remote", - default=False, help="Used when adding remote files from Transifex" - " server.") - parser.add_option("-r","--resource", action="store", dest="resource", - default=None, help="Specify the slug of the resource that you're" - " setting up (This must be in the following format:" - " `project_slug.resource_slug`).") - parser.add_option( - "--source", action="store_true", dest="is_source", default=False, - help=( - "Specify that the given file is a source file " - "[doesn't work with the --auto-* commands]." - ) - ) - parser.add_option("-l","--language", action="store", dest="language", - default=None, help="Specify which translations you want to pull" - " [doesn't work with the --auto-* commands].") - parser.add_option("-t", "--type", action="store", dest="i18n_type", - help=( - "Specify the i18n type of the resource(s). This is only needed, if " - "the resource(s) does not exist yet in Transifex. For a list of " - "available i18n types, see " - "http://help.transifex.com/features/formats.html" - ) - ) - parser.add_option("--minimum-perc", action="store", dest="minimum_perc", - help=( - "Specify the minimum acceptable percentage of a translation " - "in order to download it." - ) - ) - parser.add_option( - "--mode", action="store", dest="mode", help=( - "Specify the mode of the translation file to pull (e.g. " - "'reviewed'). See http://help.transifex.com/features/client/" - "index.html#defining-the-mode-of-the-translated-file for the" - "available values." - ) - ) - group = OptionGroup(parser, "Extended options", "These options can only be" - " used with the --auto-local command.") - group.add_option("-s","--source-language", action="store", - dest="source_language", - default=None, help="Specify the source language of a resource" - " [requires --auto-local].") - group.add_option("-f","--source-file", action="store", dest="source_file", - default=None, help="Specify the source file of a resource [requires" - " --auto-local].") - group.add_option("--execute", action="store_true", dest="execute", - default=False, help="Execute commands [requires --auto-local].") - parser.add_option_group(group) - return parser - - -def status_parser(): - """Return the command-line parser for the status command.""" - usage="usage: %prog [tx_options] status [options]" - description="Prints the status of the current project by reading the"\ - " data in the configuration file." - parser = OptionParser(usage=usage,description=description) - parser.add_option("-r","--resource", action="store", dest="resources", - default=[], help="Specify resources") - return parser - - -def parse_csv_option(option): - """Return a list out of the comma-separated option or an empty list.""" - if option: - return option.split(',') - else: - return [] diff --git a/third_party/transifex-client/txclib/processors.py b/third_party/transifex-client/txclib/processors.py deleted file mode 100644 index dc3a73f9..00000000 --- a/third_party/transifex-client/txclib/processors.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Module for API-related calls. -""" - -import urlparse - - -def hostname_tld_migration(hostname): - """ - Migrate transifex.net to transifex.com. - - :param hostname: The hostname to migrate (if needed). - :returns: A hostname with the transifex.com domain (if needed). - """ - parts = urlparse.urlparse(hostname) - if parts.hostname.endswith('transifex.net'): - hostname = hostname.replace('transifex.net', 'transifex.com', 1) - return hostname - - -def hostname_ssl_migration(hostname): - """ - Migrate Transifex hostnames to use HTTPS. - - :param hostname: The hostname to migrate (if needed). - :returns: A https hostname (if needed). - """ - parts = urlparse.urlparse(hostname) - is_transifex = ( - parts.hostname[-14:-3] == '.transifex.' or - parts.hostname == 'transifex.net' or - parts.hostname == 'transifex.com' - ) - is_https = parts.scheme == 'https' - if is_transifex and not is_https: - if not parts.scheme: - hostname = 'https:' + hostname - else: - hostname = hostname.replace(parts.scheme, 'https', 1) - return hostname - - -def visit_hostname(hostname): - """ - Have a chance to visit a hostname before actually using it. - - :param hostname: The original hostname. - :returns: The hostname with the necessary changes. - """ - for processor in [hostname_ssl_migration, hostname_tld_migration, ]: - hostname = processor(hostname) - return hostname diff --git a/third_party/transifex-client/txclib/project.py b/third_party/transifex-client/txclib/project.py deleted file mode 100644 index 88bb46bf..00000000 --- a/third_party/transifex-client/txclib/project.py +++ /dev/null @@ -1,1233 +0,0 @@ -# -*- coding: utf-8 -*- -import base64 -import copy -import getpass -import os -import re -import fnmatch -import urllib2 -import datetime, time -import ConfigParser - -from txclib.web import * -from txclib.utils import * -from txclib.urls import API_URLS -from txclib.config import OrderedRawConfigParser, Flipdict -from txclib.log import logger -from txclib.http_utils import http_response -from txclib.processors import visit_hostname - - -class ProjectNotInit(Exception): - pass - - -class Project(object): - """ - Represents an association between the local and remote project instances. - """ - - def __init__(self, path_to_tx=None, init=True): - """ - Initialize the Project attributes. - """ - if init: - self._init(path_to_tx) - - def _init(self, path_to_tx=None): - instructions = "Run 'tx init' to initialize your project first!" - try: - self.root = self._get_tx_dir_path(path_to_tx) - self.config_file = self._get_config_file_path(self.root) - self.config = self._read_config_file(self.config_file) - self.txrc_file = self._get_transifex_file() - self.txrc = self._get_transifex_config(self.txrc_file) - except ProjectNotInit, e: - logger.error('\n'.join([unicode(e), instructions])) - raise - - def _get_config_file_path(self, root_path): - """Check the .tx/config file exists.""" - config_file = os.path.join(root_path, ".tx", "config") - logger.debug("Config file is %s" % config_file) - if not os.path.exists(config_file): - msg = "Cannot find the config file (.tx/config)!" - raise ProjectNotInit(msg) - return config_file - - def _get_tx_dir_path(self, path_to_tx): - """Check the .tx directory exists.""" - root_path = path_to_tx or find_dot_tx() - logger.debug("Path to tx is %s." % root_path) - if not root_path: - msg = "Cannot find any .tx directory!" - raise ProjectNotInit(msg) - return root_path - - def _read_config_file(self, config_file): - """Parse the config file and return its contents.""" - config = OrderedRawConfigParser() - try: - config.read(config_file) - except Exception, err: - msg = "Cannot open/parse .tx/config file: %s" % err - raise ProjectNotInit(msg) - return config - - def _get_transifex_config(self, txrc_file): - """Read the configuration from the .transifexrc file.""" - txrc = OrderedRawConfigParser() - try: - txrc.read(txrc_file) - except Exception, e: - msg = "Cannot read global configuration file: %s" % e - raise ProjectNotInit(msg) - self._migrate_txrc_file(txrc) - return txrc - - def _migrate_txrc_file(self, txrc): - """Migrate the txrc file, if needed.""" - for section in txrc.sections(): - orig_hostname = txrc.get(section, 'hostname') - hostname = visit_hostname(orig_hostname) - if hostname != orig_hostname: - msg = "Hostname %s should be changed to %s." - logger.info(msg % (orig_hostname, hostname)) - if (sys.stdin.isatty() and sys.stdout.isatty() and - confirm('Change it now? ', default=True)): - txrc.set(section, 'hostname', hostname) - msg = 'Hostname changed' - logger.info(msg) - else: - hostname = orig_hostname - self._save_txrc_file(txrc) - return txrc - - def _get_transifex_file(self, directory=None): - """Fetch the path of the .transifexrc file. - - It is in the home directory ofthe user by default. - """ - if directory is None: - directory = os.path.expanduser('~') - txrc_file = os.path.join(directory, ".transifexrc") - logger.debug(".transifexrc file is at %s" % directory) - if not os.path.exists(txrc_file): - msg = "No authentication data found." - logger.info(msg) - mask = os.umask(077) - open(txrc_file, 'w').close() - os.umask(mask) - return txrc_file - - def validate_config(self): - """ - To ensure the json structure is correctly formed. - """ - pass - - def getset_host_credentials(self, host, user=None, password=None): - """ - Read .transifexrc and report user,pass for a specific host else ask the - user for input. - """ - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - logger.info("No entry found for host %s. Creating..." % host) - username = user or raw_input("Please enter your transifex username: ") - while (not username): - username = raw_input("Please enter your transifex username: ") - passwd = password - while (not passwd): - passwd = getpass.getpass() - - logger.info("Updating %s file..." % self.txrc_file) - self.txrc.add_section(host) - self.txrc.set(host, 'username', username) - self.txrc.set(host, 'password', passwd) - self.txrc.set(host, 'token', '') - self.txrc.set(host, 'hostname', host) - - return username, passwd - - def set_remote_resource(self, resource, source_lang, i18n_type, host, - file_filter="translations%(proj)s.%(res)s.%(extension)s"): - """ - Method to handle the add/conf of a remote resource. - """ - if not self.config.has_section(resource): - self.config.add_section(resource) - - p_slug, r_slug = resource.split('.') - file_filter = file_filter.replace("", r"%s" % os.path.sep) - self.url_info = { - 'host': host, - 'project': p_slug, - 'resource': r_slug - } - extension = self._extension_for(i18n_type)[1:] - - self.config.set(resource, 'source_lang', source_lang) - self.config.set( - resource, 'file_filter', - file_filter % {'proj': p_slug, 'res': r_slug, 'extension': extension} - ) - if host != self.config.get('main', 'host'): - self.config.set(resource, 'host', host) - - def get_resource_host(self, resource): - """ - Returns the host that the resource is configured to use. If there is no - such option we return the default one - """ - if self.config.has_option(resource, 'host'): - return self.config.get(resource, 'host') - return self.config.get('main', 'host') - - def get_resource_lang_mapping(self, resource): - """ - Get language mappings for a specific resource. - """ - lang_map = Flipdict() - try: - args = self.config.get("main", "lang_map") - for arg in args.replace(' ', '').split(','): - k,v = arg.split(":") - lang_map.update({k:v}) - except ConfigParser.NoOptionError: - pass - except (ValueError, KeyError): - raise Exception("Your lang map configuration is not correct.") - - if self.config.has_section(resource): - res_lang_map = Flipdict() - try: - args = self.config.get(resource, "lang_map") - for arg in args.replace(' ', '').split(','): - k,v = arg.split(":") - res_lang_map.update({k:v}) - except ConfigParser.NoOptionError: - pass - except (ValueError, KeyError): - raise Exception("Your lang map configuration is not correct.") - - # merge the lang maps and return result - lang_map.update(res_lang_map) - - return lang_map - - - def get_resource_files(self, resource): - """ - Get a dict for all files assigned to a resource. First we calculate the - files matching the file expression and then we apply all translation - excpetions. The resulting dict will be in this format: - - { 'en': 'path/foo/en/bar.po', 'de': 'path/foo/de/bar.po', 'es': 'path/exceptions/es.po'} - - NOTE: All paths are relative to the root of the project - """ - tr_files = {} - if self.config.has_section(resource): - try: - file_filter = self.config.get(resource, "file_filter") - except ConfigParser.NoOptionError: - file_filter = "$^" - source_lang = self.config.get(resource, "source_lang") - source_file = self.get_resource_option(resource, 'source_file') or None - expr_re = regex_from_filefilter(file_filter, self.root) - expr_rec = re.compile(expr_re) - for root, dirs, files in os.walk(self.root): - for f in files: - f_path = os.path.abspath(os.path.join(root, f)) - match = expr_rec.match(f_path) - if match: - lang = match.group(1) - if lang != source_lang: - f_path = relpath(f_path, self.root) - if f_path != source_file: - tr_files.update({lang: f_path}) - - for (name, value) in self.config.items(resource): - if name.startswith("trans."): - lang = name.split('.')[1] - # delete language which has same file - if value in tr_files.values(): - keys = [] - for k, v in tr_files.iteritems(): - if v == value: - keys.append(k) - if len(keys) == 1: - del tr_files[keys[0]] - else: - raise Exception("Your configuration seems wrong."\ - " You have multiple languages pointing to"\ - " the same file.") - # Add language with correct file - tr_files.update({lang:value}) - - return tr_files - - return None - - def get_resource_option(self, resource, option): - """ - Return the requested option for a specific resource - - If there is no such option, we return None - """ - - if self.config.has_section(resource): - if self.config.has_option(resource, option): - return self.config.get(resource, option) - return None - - def get_resource_list(self, project=None): - """ - Parse config file and return tuples with the following format - - [ (project_slug, resource_slug), (..., ...)] - """ - - resource_list= [] - for r in self.config.sections(): - if r == 'main': - continue - p_slug, r_slug = r.split('.', 1) - if project and p_slug != project: - continue - resource_list.append(r) - - return resource_list - - def save(self): - """ - Store the config dictionary in the .tx/config file of the project. - """ - self._save_tx_config() - self._save_txrc_file() - - def _save_tx_config(self, config=None): - """Save the local config file.""" - if config is None: - config = self.config - fh = open(self.config_file,"w") - config.write(fh) - fh.close() - - def _save_txrc_file(self, txrc=None): - """Save the .transifexrc file.""" - if txrc is None: - txrc = self.txrc - mask = os.umask(077) - fh = open(self.txrc_file, 'w') - txrc.write(fh) - fh.close() - os.umask(mask) - - def get_full_path(self, relpath): - if relpath[0] == "/": - return relpath - else: - return os.path.join(self.root, relpath) - - def pull(self, languages=[], resources=[], overwrite=True, fetchall=False, - fetchsource=False, force=False, skip=False, minimum_perc=0, mode=None): - """Pull all translations file from transifex server.""" - self.minimum_perc = minimum_perc - resource_list = self.get_chosen_resources(resources) - - if mode == 'reviewed': - url = 'pull_reviewed_file' - elif mode == 'translator': - url = 'pull_translator_file' - elif mode == 'developer': - url = 'pull_developer_file' - else: - url = 'pull_file' - - for resource in resource_list: - logger.debug("Handling resource %s" % resource) - self.resource = resource - project_slug, resource_slug = resource.split('.') - files = self.get_resource_files(resource) - slang = self.get_resource_option(resource, 'source_lang') - sfile = self.get_resource_option(resource, 'source_file') - lang_map = self.get_resource_lang_mapping(resource) - host = self.get_resource_host(resource) - logger.debug("Language mapping is: %s" % lang_map) - if mode is None: - mode = self._get_option(resource, 'mode') - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - logger.debug("URL data are: %s" % self.url_info) - - stats = self._get_stats_for_resource() - - - try: - file_filter = self.config.get(resource, 'file_filter') - except ConfigParser.NoOptionError: - file_filter = None - - # Pull source file - pull_languages = set([]) - new_translations = set([]) - - if fetchall: - new_translations = self._new_translations_to_add( - files, slang, lang_map, stats, force - ) - if new_translations: - msg = "New translations found for the following languages: %s" - logger.info(msg % ', '.join(new_translations)) - - existing, new = self._languages_to_pull( - languages, files, lang_map, stats, force - ) - pull_languages |= existing - new_translations |= new - logger.debug("Adding to new translations: %s" % new) - - if fetchsource: - if sfile and slang not in pull_languages: - pull_languages.add(slang) - elif slang not in new_translations: - new_translations.add(slang) - - if pull_languages: - logger.debug("Pulling languages for: %s" % pull_languages) - msg = "Pulling translations for resource %s (source: %s)" - logger.info(msg % (resource, sfile)) - - for lang in pull_languages: - local_lang = lang - if lang in lang_map.values(): - remote_lang = lang_map.flip[lang] - else: - remote_lang = lang - if languages and lang not in pull_languages: - logger.debug("Skipping language %s" % lang) - continue - if lang != slang: - local_file = files.get(lang, None) or files[lang_map[lang]] - else: - local_file = sfile - logger.debug("Using file %s" % local_file) - - kwargs = { - 'lang': remote_lang, - 'stats': stats, - 'local_file': local_file, - 'force': force, - 'mode': mode, - } - if not self._should_update_translation(**kwargs): - msg = "Skipping '%s' translation (file: %s)." - logger.info( - msg % (color_text(remote_lang, "RED"), local_file) - ) - continue - - if not overwrite: - local_file = ("%s.new" % local_file) - logger.warning( - " -> %s: %s" % (color_text(remote_lang, "RED"), local_file) - ) - try: - r = self.do_url_request(url, language=remote_lang) - except Exception,e: - if not skip: - raise e - else: - logger.error(e) - continue - base_dir = os.path.split(local_file)[0] - mkdir_p(base_dir) - fd = open(local_file, 'wb') - fd.write(r) - fd.close() - - if new_translations: - msg = "Pulling new translations for resource %s (source: %s)" - logger.info(msg % (resource, sfile)) - for lang in new_translations: - if lang in lang_map.keys(): - local_lang = lang_map[lang] - else: - local_lang = lang - remote_lang = lang - if file_filter: - local_file = relpath(os.path.join(self.root, - file_filter.replace('', local_lang)), os.curdir) - else: - trans_dir = os.path.join(self.root, ".tx", resource) - if not os.path.exists(trans_dir): - os.mkdir(trans_dir) - local_file = relpath(os.path.join(trans_dir, '%s_translation' % - local_lang, os.curdir)) - - if lang != slang: - satisfies_min = self._satisfies_min_translated( - stats[remote_lang], mode - ) - if not satisfies_min: - msg = "Skipping language %s due to used options." - logger.info(msg % lang) - continue - logger.warning( - " -> %s: %s" % (color_text(remote_lang, "RED"), local_file) - ) - r = self.do_url_request(url, language=remote_lang) - - base_dir = os.path.split(local_file)[0] - mkdir_p(base_dir) - fd = open(local_file, 'wb') - fd.write(r) - fd.close() - - def push(self, source=False, translations=False, force=False, resources=[], languages=[], - skip=False, no_interactive=False): - """ - Push all the resources - """ - resource_list = self.get_chosen_resources(resources) - self.skip = skip - self.force = force - for resource in resource_list: - push_languages = [] - project_slug, resource_slug = resource.split('.') - files = self.get_resource_files(resource) - slang = self.get_resource_option(resource, 'source_lang') - sfile = self.get_resource_option(resource, 'source_file') - lang_map = self.get_resource_lang_mapping(resource) - host = self.get_resource_host(resource) - logger.debug("Language mapping is: %s" % lang_map) - logger.debug("Using host %s" % host) - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - - logger.info("Pushing translations for resource %s:" % resource) - - stats = self._get_stats_for_resource() - - if force and not no_interactive: - answer = raw_input("Warning: By using --force, the uploaded" - " files will overwrite remote translations, even if they" - " are newer than your uploaded files.\nAre you sure you" - " want to continue? [y/N] ") - - if not answer in ["", 'Y', 'y', "yes", 'YES']: - return - - if source: - if sfile == None: - logger.error("You don't seem to have a proper source file" - " mapping for resource %s. Try without the --source" - " option or set a source file first and then try again." % - resource) - continue - # Push source file - try: - logger.warning("Pushing source file (%s)" % sfile) - if not self._resource_exists(stats): - logger.info("Resource does not exist. Creating...") - fileinfo = "%s;%s" % (resource_slug, slang) - filename = self.get_full_path(sfile) - self._create_resource(resource, project_slug, fileinfo, filename) - self.do_url_request( - 'push_source', multipart=True, method="PUT", - files=[( - "%s;%s" % (resource_slug, slang) - , self.get_full_path(sfile) - )], - ) - except Exception, e: - if not skip: - raise - else: - logger.error(e) - else: - try: - self.do_url_request('resource_details') - except Exception, e: - code = getattr(e, 'code', None) - if code == 404: - msg = "Resource %s doesn't exist on the server." - logger.error(msg % resource) - continue - - if translations: - # Check if given language codes exist - if not languages: - push_languages = files.keys() - else: - push_languages = [] - f_langs = files.keys() - for l in languages: - if l in lang_map.keys(): - l = lang_map[l] - push_languages.append(l) - if l not in f_langs: - msg = "Warning: No mapping found for language code '%s'." - logger.error(msg % color_text(l,"RED")) - logger.debug("Languages to push are %s" % push_languages) - - # Push translation files one by one - for lang in push_languages: - local_lang = lang - if lang in lang_map.values(): - remote_lang = lang_map.flip[lang] - else: - remote_lang = lang - - local_file = files[local_lang] - - kwargs = { - 'lang': remote_lang, - 'stats': stats, - 'local_file': local_file, - 'force': force, - } - if not self._should_push_translation(**kwargs): - msg = "Skipping '%s' translation (file: %s)." - logger.info(msg % (color_text(lang, "RED"), local_file)) - continue - - msg = "Pushing '%s' translations (file: %s)" - logger.warning( - msg % (color_text(remote_lang, "RED"), local_file) - ) - try: - self.do_url_request( - 'push_translation', multipart=True, method='PUT', - files=[( - "%s;%s" % (resource_slug, remote_lang), - self.get_full_path(local_file) - )], language=remote_lang - ) - logger.debug("Translation %s pushed." % remote_lang) - except Exception, e: - if not skip: - raise e - else: - logger.error(e) - - def delete(self, resources=[], languages=[], skip=False, force=False): - """Delete translations.""" - resource_list = self.get_chosen_resources(resources) - self.skip = skip - self.force = force - - if not languages: - delete_func = self._delete_resource - else: - delete_func = self._delete_translations - - for resource in resource_list: - project_slug, resource_slug = resource.split('.') - host = self.get_resource_host(resource) - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - logger.debug("URL data are: %s" % self.url_info) - project_details = parse_json( - self.do_url_request('project_details', project=self) - ) - teams = project_details['teams'] - stats = self._get_stats_for_resource() - delete_func(project_details, resource, stats, languages) - - def _delete_resource(self, project_details, resource, stats, *args): - """Delete a resource from Transifex.""" - project_slug, resource_slug = resource.split('.') - project_resource_slugs = [ - r['slug'] for r in project_details['resources'] - ] - logger.info("Deleting resource %s:" % resource) - if resource_slug not in project_resource_slugs: - if not self.skip: - msg = "Skipping: %s : Resource does not exist." - logger.info(msg % resource) - return - if not self.force: - slang = self.get_resource_option(resource, 'source_lang') - for language in stats: - if language == slang: - continue - if int(stats[language]['translated_entities']) > 0: - msg = ( - "Skipping: %s : Unable to delete resource because it " - "has a not empty %s translation.\nPlease use -f or " - "--force option to delete this resource." - ) - logger.info(msg % (resource, language)) - return - try: - self.do_url_request('delete_resource', method="DELETE") - self.config.remove_section(resource) - self.save() - msg = "Deleted resource %s of project %s." - logger.info(msg % (resource_slug, project_slug)) - except Exception, e: - msg = "Unable to delete resource %s of project %s." - logger.error(msg % (resource_slug, project_slug)) - if not self.skip: - raise - - def _delete_translations(self, project_details, resource, stats, languages): - """Delete the specified translations for the specified resource.""" - logger.info("Deleting translations from resource %s:" % resource) - for language in languages: - self._delete_translation(project_details, resource, stats, language) - - def _delete_translation(self, project_details, resource, stats, language): - """Delete a specific translation from the specified resource.""" - project_slug, resource_slug = resource.split('.') - if language not in stats: - if not self.skip: - msg = "Skipping %s: Translation does not exist." - logger.warning(msg % (language)) - return - if not self.force: - teams = project_details['teams'] - if language in teams: - msg = ( - "Skipping %s: Unable to delete translation because it is " - "associated with a team.\nPlease use -f or --force option " - "to delete this translation." - ) - logger.warning(msg % language) - return - if int(stats[language]['translated_entities']) > 0: - msg = ( - "Skipping %s: Unable to delete translation because it " - "is not empty.\nPlease use -f or --force option to delete " - "this translation." - ) - logger.warning(msg % language) - return - try: - self.do_url_request( - 'delete_translation', language=language, method="DELETE" - ) - msg = "Deleted language %s from resource %s of project %s." - logger.info(msg % (language, resource_slug, project_slug)) - except Exception, e: - msg = "Unable to delete translation %s" - logger.error(msg % language) - if not self.skip: - raise - - def do_url_request(self, api_call, multipart=False, data=None, - files=[], encoding=None, method="GET", **kwargs): - """ - Issues a url request. - """ - # Read the credentials from the config file (.transifexrc) - host = self.url_info['host'] - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - token = self.txrc.get(host, 'token') - hostname = self.txrc.get(host, 'hostname') - except ConfigParser.NoSectionError: - raise Exception("No user credentials found for host %s. Edit" - " ~/.transifexrc and add the appropriate info in there." % - host) - - # Create the Url - kwargs['hostname'] = hostname - kwargs.update(self.url_info) - url = (API_URLS[api_call] % kwargs).encode('UTF-8') - logger.debug(url) - - opener = None - headers = None - req = None - - if multipart: - opener = urllib2.build_opener(MultipartPostHandler) - for info,filename in files: - data = { "resource" : info.split(';')[0], - "language" : info.split(';')[1], - "uploaded_file" : open(filename,'rb') } - - urllib2.install_opener(opener) - req = RequestWithMethod(url=url, data=data, method=method) - else: - req = RequestWithMethod(url=url, data=data, method=method) - if encoding: - req.add_header("Content-Type",encoding) - - base64string = base64.encodestring('%s:%s' % (username, passwd))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - req.add_header("Accept-Encoding", "gzip,deflate") - req.add_header("User-Agent", user_agent_identifier()) - - try: - response = urllib2.urlopen(req, timeout=300) - return http_response(response) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - elif 200 <= e.code < 300: - return None - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - - def _should_update_translation(self, lang, stats, local_file, force=False, - mode=None): - """Whether a translation should be udpated from Transifex. - - We use the following criteria for that: - - If user requested to force the download. - - If language exists in Transifex. - - If the local file is older than the Transifex's file. - - If the user requested a x% completion. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - local_file: The local translation file. - force: A boolean flag. - mode: The mode for the translation. - Returns: - True or False. - """ - return self._should_download(lang, stats, local_file, force) - - def _should_add_translation(self, lang, stats, force=False, mode=None): - """Whether a translation should be added from Transifex. - - We use the following criteria for that: - - If user requested to force the download. - - If language exists in Transifex. - - If the user requested a x% completion. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - force: A boolean flag. - mode: The mode for the translation. - Returns: - True or False. - """ - return self._should_download(lang, stats, None, force) - - def _should_download(self, lang, stats, local_file=None, force=False, - mode=None): - """Return whether a translation should be downloaded. - - If local_file is None, skip the timestamps check (the file does - not exist locally). - """ - try: - lang_stats = stats[lang] - except KeyError, e: - logger.debug("No lang %s in statistics" % lang) - return False - - satisfies_min = self._satisfies_min_translated(lang_stats, mode) - if not satisfies_min: - return False - - if force: - logger.debug("Downloading translation due to -f") - return True - - if local_file is not None: - remote_update = self._extract_updated(lang_stats) - if not self._remote_is_newer(remote_update, local_file): - logger.debug("Local is newer than remote for lang %s" % lang) - return False - return True - - def _should_push_translation(self, lang, stats, local_file, force=False): - """Return whether a local translation file should be - pushed to Trasnifex. - - We use the following criteria for that: - - If user requested to force the upload. - - If language exists in Transifex. - - If local file is younger than the remote file. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - local_file: The local translation file. - force: A boolean flag. - Returns: - True or False. - """ - if force: - logger.debug("Push translation due to -f.") - return True - try: - lang_stats = stats[lang] - except KeyError, e: - logger.debug("Language %s does not exist in Transifex." % lang) - return True - if local_file is not None: - remote_update = self._extract_updated(lang_stats) - if self._remote_is_newer(remote_update, local_file): - msg = "Remote translation is newer than local file for lang %s" - logger.debug(msg % lang) - return False - return True - - def _generate_timestamp(self, update_datetime): - """Generate a UNIX timestamp from the argument. - - Args: - update_datetime: The datetime in the format used by Transifex. - Returns: - A float, representing the timestamp that corresponds to the - argument. - """ - time_format = "%Y-%m-%d %H:%M:%S" - return time.mktime( - datetime.datetime( - *time.strptime(update_datetime, time_format)[0:5] - ).utctimetuple() - ) - - def _get_time_of_local_file(self, path): - """Get the modified time of the path_. - - Args: - path: The path we want the mtime for. - Returns: - The time as a timestamp or None, if the file does not exist - """ - if not os.path.exists(path): - return None - return time.mktime(time.gmtime(os.path.getmtime(path))) - - def _satisfies_min_translated(self, stats, mode=None): - """Check whether a translation fulfills the filter used for - minimum translated percentage. - - Args: - perc: The current translation percentage. - Returns: - True or False - """ - cur = self._extract_completed(stats, mode) - option_name = 'minimum_perc' - if self.minimum_perc is not None: - minimum_percent = self.minimum_perc - else: - global_minimum = int( - self.get_resource_option('main', option_name) or 0 - ) - resource_minimum = int( - self.get_resource_option( - self.resource, option_name - ) or global_minimum - ) - minimum_percent = resource_minimum - return cur >= minimum_percent - - def _remote_is_newer(self, remote_updated, local_file): - """Check whether the remote translation is newer that the local file. - - Args: - remote_updated: The date and time the translation was last - updated remotely. - local_file: The local file. - Returns: - True or False. - """ - if remote_updated is None: - logger.debug("No remote time") - return False - remote_time = self._generate_timestamp(remote_updated) - local_time = self._get_time_of_local_file( - self.get_full_path(local_file) - ) - logger.debug( - "Remote time is %s and local %s" % (remote_time, local_time) - ) - if local_time is not None and remote_time < local_time: - return False - return True - - @classmethod - def _extract_completed(cls, stats, mode=None): - """Extract the information for the translated percentage from the stats. - - Args: - stats: The stats object for a language as returned by Transifex. - mode: The mode of translations requested. - Returns: - The percentage of translation as integer. - """ - if mode == 'reviewed': - key = 'reviewed_percentage' - else: - key = 'completed' - try: - return int(stats[key][:-1]) - except KeyError, e: - return 0 - - @classmethod - def _extract_updated(cls, stats): - """Extract the information for the last update of a translation. - - Args: - stats: The stats object for a language as returned by Transifex. - Returns: - The last update field. - """ - try: - return stats['last_update'] - except KeyError, e: - return None - - def _new_translations_to_add(self, files, slang, lang_map, - stats, force=False): - """Return a list of translations which are new to the - local installation. - """ - new_translations = [] - timestamp = time.time() - langs = stats.keys() - logger.debug("Available languages are: %s" % langs) - - for lang in langs: - lang_exists = lang in files.keys() - lang_is_source = lang == slang - mapped_lang_exists = ( - lang in lang_map and lang_map[lang] in files.keys() - ) - if lang_exists or lang_is_source or mapped_lang_exists: - continue - if self._should_add_translation(lang, stats, force): - new_translations.append(lang) - return set(new_translations) - - def _get_stats_for_resource(self): - """Get the statistics information for a resource.""" - try: - r = self.do_url_request('resource_stats') - logger.debug("Statistics response is %s" % r) - stats = parse_json(r) - except urllib2.HTTPError, e: - logger.debug("Resource not found: %s" % e) - stats = {} - except Exception,e: - logger.debug("Network error: %s" % e) - raise - return stats - - def get_chosen_resources(self, resources): - """Get the resources the user selected. - - Support wildcards in the resources specified by the user. - - Args: - resources: A list of resources as specified in command-line or - an empty list. - Returns: - A list of resources. - """ - configured_resources = self.get_resource_list() - if not resources: - return configured_resources - - selected_resources = [] - for resource in resources: - found = False - for full_name in configured_resources: - if fnmatch.fnmatch(full_name, resource): - selected_resources.append(full_name) - found = True - if not found: - msg = "Specified resource '%s' does not exist." - raise Exception(msg % resource) - logger.debug("Operating on resources: %s" % selected_resources) - return selected_resources - - def _languages_to_pull(self, languages, files, lang_map, stats, force): - """Get a set of langauges to pull. - - Args: - languages: A list of languages the user selected in cmd. - files: A dictionary of current local translation files. - Returns: - A tuple of a set of existing languages and new translations. - """ - if not languages: - pull_languages = set([]) - pull_languages |= set(files.keys()) - mapped_files = [] - for lang in pull_languages: - if lang in lang_map.flip: - mapped_files.append(lang_map.flip[lang]) - pull_languages -= set(lang_map.flip.keys()) - pull_languages |= set(mapped_files) - return (pull_languages, set([])) - else: - pull_languages = [] - new_translations = [] - f_langs = files.keys() - for l in languages: - if l not in f_langs and not (l in lang_map and lang_map[l] in f_langs): - if self._should_add_translation(l, stats, force): - new_translations.append(l) - else: - if l in lang_map.keys(): - l = lang_map[l] - pull_languages.append(l) - return (set(pull_languages), set(new_translations)) - - def _extension_for(self, i18n_type): - """Return the extension used for the specified type.""" - try: - res = parse_json(self.do_url_request('formats')) - return res[i18n_type]['file-extensions'].split(',')[0] - except Exception,e: - logger.error(e) - return '' - - def _resource_exists(self, stats): - """Check if resource exists. - - Args: - stats: The statistics dict as returned by Tx. - Returns: - True, if the resource exists in the server. - """ - return bool(stats) - - def _create_resource(self, resource, pslug, fileinfo, filename, **kwargs): - """Create a resource. - - Args: - resource: The full resource name. - pslug: The slug of the project. - fileinfo: The information of the resource. - filename: The name of the file. - Raises: - URLError, in case of a problem. - """ - multipart = True - method = "POST" - api_call = 'create_resource' - - host = self.url_info['host'] - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - token = self.txrc.get(host, 'token') - hostname = self.txrc.get(host, 'hostname') - except ConfigParser.NoSectionError: - raise Exception("No user credentials found for host %s. Edit" - " ~/.transifexrc and add the appropriate info in there." % - host) - - # Create the Url - kwargs['hostname'] = hostname - kwargs.update(self.url_info) - kwargs['project'] = pslug - url = (API_URLS[api_call] % kwargs).encode('UTF-8') - - opener = None - headers = None - req = None - - i18n_type = self._get_option(resource, 'type') - if i18n_type is None: - logger.error( - "Please define the resource type in .tx/config (eg. type = PO)." - " More info: http://bit.ly/txcl-rt" - ) - - opener = urllib2.build_opener(MultipartPostHandler) - data = { - "slug": fileinfo.split(';')[0], - "name": fileinfo.split(';')[0], - "uploaded_file": open(filename,'rb'), - "i18n_type": i18n_type - } - urllib2.install_opener(opener) - req = RequestWithMethod(url=url, data=data, method=method) - - base64string = base64.encodestring('%s:%s' % (username, passwd))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - - try: - fh = urllib2.urlopen(req) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - raw = fh.read() - fh.close() - return raw - - def _get_option(self, resource, option): - """Get the value for the option in the config file. - - If the option is not in the resource section, look for it in - the project. - - Args: - resource: The resource name. - option: The option the value of which we are interested in. - Returns: - The option value or None, if it does not exist. - """ - value = self.get_resource_option(resource, option) - if value is None: - if self.config.has_option('main', option): - return self.config.get('main', option) - return value - - def set_i18n_type(self, resources, i18n_type): - """Set the type for the specified resources.""" - self._set_resource_option(resources, key='type', value=i18n_type) - - def set_min_perc(self, resources, perc): - """Set the minimum percentage for the resources.""" - self._set_resource_option(resources, key='minimum_perc', value=perc) - - def set_default_mode(self, resources, mode): - """Set the default mode for the specified resources.""" - self._set_resource_option(resources, key='mode', value=mode) - - def _set_resource_option(self, resources, key, value): - """Set options in the config file. - - If resources is empty. set the option globally. - """ - if not resources: - self.config.set('main', key, value) - return - for r in resources: - self.config.set(r, key, value) diff --git a/third_party/transifex-client/txclib/urls.py b/third_party/transifex-client/txclib/urls.py deleted file mode 100644 index 0bb74fde..00000000 --- a/third_party/transifex-client/txclib/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -# These are the Transifex API urls - -API_URLS = { - 'get_resources': '%(hostname)s/api/2/project/%(project)s/resources/', - 'project_details': '%(hostname)s/api/2/project/%(project)s/?details', - 'resource_details': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/', - 'release_details': '%(hostname)s/api/2/project/%(project)s/release/%(release)s/', - 'pull_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file', - 'pull_reviewed_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=reviewed', - 'pull_translator_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=translated', - 'pull_developer_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=default', - 'resource_stats': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/stats/', - 'create_resource': '%(hostname)s/api/2/project/%(project)s/resources/', - 'push_source': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/content/', - 'push_translation': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/', - 'delete_translation': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/', - 'formats': '%(hostname)s/api/2/formats/', - 'delete_resource': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/', -} - - diff --git a/third_party/transifex-client/txclib/utils.py b/third_party/transifex-client/txclib/utils.py deleted file mode 100644 index 318bee91..00000000 --- a/third_party/transifex-client/txclib/utils.py +++ /dev/null @@ -1,259 +0,0 @@ -import os, sys, re, errno -try: - from json import loads as parse_json, dumps as compile_json -except ImportError: - from simplejson import loads as parse_json, dumps as compile_json -import urllib2 # This should go and instead use do_url_request everywhere - -from urls import API_URLS -from txclib.log import logger -from txclib.exceptions import UnknownCommandError - - -def find_dot_tx(path = os.path.curdir, previous = None): - """ - Return the path where .tx folder is found. - - The 'path' should be a DIRECTORY. - This process is functioning recursively from the current directory to each - one of the ancestors dirs. - """ - path = os.path.abspath(path) - if path == previous: - return None - joined = os.path.join(path, ".tx") - if os.path.isdir(joined): - return path - else: - return find_dot_tx(os.path.dirname(path), path) - - -################################################# -# Parse file filter expressions and create regex - -def regex_from_filefilter(file_filter, root_path = os.path.curdir): - """ - Create proper regex from expression - """ - # Force expr to be a valid regex expr (escaped) but keep intact - expr_re = re.escape(os.path.join(root_path, file_filter)) - expr_re = expr_re.replace("\\", '').replace( - '', '([^%(sep)s]+)' % { 'sep': re.escape(os.path.sep)}) - - return "^%s$" % expr_re - - -TX_URLS = { - 'resource': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/resource/(?P(\w|-)+)/?$', - 'release': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/r/(?P(\w|-)+)/?$', - 'project': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/?$', -} - - -def parse_tx_url(url): - """ - Try to match given url to any of the valid url patterns specified in - TX_URLS. If not match is found, we raise exception - """ - for type in TX_URLS.keys(): - pattern = TX_URLS[type] - m = re.match(pattern, url) - if m: - return type, m.groupdict() - - raise Exception("tx: Malformed url given. Please refer to our docs: http://bit.ly/txautor") - - -def get_details(api_call, username, password, *args, **kwargs): - """ - Get the tx project info through the API. - - This function can also be used to check the existence of a project. - """ - import base64 - url = (API_URLS[api_call] % (kwargs)).encode('UTF-8') - - req = urllib2.Request(url=url) - base64string = base64.encodestring('%s:%s' % (username, password))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - - try: - fh = urllib2.urlopen(req) - raw = fh.read() - fh.close() - remote_project = parse_json(raw) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - return remote_project - - -def valid_slug(slug): - """ - Check if a slug contains only valid characters. - - Valid chars include [-_\w] - """ - try: - a, b = slug.split('.') - except ValueError: - return False - else: - if re.match("^[A-Za-z0-9_-]*$", a) and re.match("^[A-Za-z0-9_-]*$", b): - return True - return False - - -def discover_commands(): - """ - Inspect commands.py and find all available commands - """ - import inspect - from txclib import commands - - command_table = {} - fns = inspect.getmembers(commands, inspect.isfunction) - - for name, fn in fns: - if name.startswith("cmd_"): - command_table.update({ - name.split("cmd_")[1]:fn - }) - - return command_table - - -def exec_command(command, *args, **kwargs): - """ - Execute given command - """ - commands = discover_commands() - try: - cmd_fn = commands[command] - except KeyError: - raise UnknownCommandError - cmd_fn(*args,**kwargs) - - -def mkdir_p(path): - try: - if path: - os.makedirs(path) - except OSError, exc: # Python >2.5 - if exc.errno == errno.EEXIST: - pass - else: - raise - - -def confirm(prompt='Continue?', default=True): - """ - Prompt the user for a Yes/No answer. - - Args: - prompt: The text displayed to the user ([Y/n] will be appended) - default: If the default value will be yes or no - """ - valid_yes = ['Y', 'y', 'Yes', 'yes', ] - valid_no = ['N', 'n', 'No', 'no', ] - if default: - prompt = prompt + '[Y/n]' - valid_yes.append('') - else: - prompt = prompt + '[y/N]' - valid_no.append('') - - ans = raw_input(prompt) - while (ans not in valid_yes and ans not in valid_no): - ans = raw_input(prompt) - - return ans in valid_yes - - -# Stuff for command line colored output - -COLORS = [ - 'BLACK', 'RED', 'GREEN', 'YELLOW', - 'BLUE', 'MAGENTA', 'CYAN', 'WHITE' -] - -DISABLE_COLORS = False - - -def color_text(text, color_name, bold=False): - """ - This command can be used to colorify command line output. If the shell - doesn't support this or the --disable-colors options has been set, it just - returns the plain text. - - Usage: - print "%s" % color_text("This text is red", "RED") - """ - if color_name in COLORS and not DISABLE_COLORS: - return '\033[%s;%sm%s\033[0m' % ( - int(bold), COLORS.index(color_name) + 30, text) - else: - return text - - -############################################## -# relpath implementation taken from Python 2.7 - -if not hasattr(os.path, 'relpath'): - if os.path is sys.modules.get('ntpath'): - def relpath(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - if start_list[0].lower() != path_list[0].lower(): - unc_path, rest = os.path.splitunc(path) - unc_start, rest = os.path.splitunc(start) - if bool(unc_path) ^ bool(unc_start): - raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" - % (path, start)) - else: - raise ValueError("path is on drive %s, start on drive %s" - % (path_list[0], start_list[0])) - # Work out how much of the filepath is shared by start and path. - for i in range(min(len(start_list), len(path_list))): - if start_list[i].lower() != path_list[i].lower(): - break - else: - i += 1 - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) - - else: - # default to posixpath definition - def relpath(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - - # Work out how much of the filepath is shared by start and path. - i = len(os.path.commonprefix([start_list, path_list])) - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) -else: - from os.path import relpath diff --git a/third_party/transifex-client/txclib/web.py b/third_party/transifex-client/txclib/web.py deleted file mode 100644 index a3cb3f00..00000000 --- a/third_party/transifex-client/txclib/web.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -import urllib2 -import itertools, mimetools, mimetypes -import platform -from txclib import get_version - -# Helper class to enable urllib2 to handle PUT/DELETE requests as well -class RequestWithMethod(urllib2.Request): - """Workaround for using DELETE with urllib2""" - def __init__(self, url, method, data=None, headers={}, - origin_req_host=None, unverifiable=False): - self._method = method - urllib2.Request.__init__(self, url, data=data, headers=headers, - origin_req_host=None, unverifiable=False) - - def get_method(self): - return self._method - -import urllib -import os, stat -from cStringIO import StringIO - -class Callable: - def __init__(self, anycallable): - self.__call__ = anycallable - -# Controls how sequences are uncoded. If true, elements may be given multiple -# values by assigning a sequence. -doseq = 1 - -class MultipartPostHandler(urllib2.BaseHandler): - handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first - - def http_request(self, request): - data = request.get_data() - if data is not None and type(data) != str: - v_files = [] - v_vars = [] - try: - for(key, value) in data.items(): - if type(value) == file: - v_files.append((key, value)) - else: - v_vars.append((key, value)) - except TypeError: - systype, value, traceback = sys.exc_info() - raise TypeError, "not a valid non-string sequence or mapping object", traceback - - if len(v_files) == 0: - data = urllib.urlencode(v_vars, doseq) - else: - boundary, data = self.multipart_encode(v_vars, v_files) - - contenttype = 'multipart/form-data; boundary=%s' % boundary - if(request.has_header('Content-Type') - and request.get_header('Content-Type').find('multipart/form-data') != 0): - print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') - request.add_unredirected_header('Content-Type', contenttype) - - request.add_data(data) - - return request - - def multipart_encode(vars, files, boundary = None, buf = None): - if boundary is None: - boundary = mimetools.choose_boundary() - if buf is None: - buf = StringIO() - for(key, value) in vars: - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"' % key) - buf.write('\r\n\r\n' + value + '\r\n') - for(key, fd) in files: - file_size = os.fstat(fd.fileno())[stat.ST_SIZE] - filename = fd.name.split('/')[-1] - contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) - buf.write('Content-Type: %s\r\n' % contenttype) - # buffer += 'Content-Length: %s\r\n' % file_size - fd.seek(0) - buf.write('\r\n' + fd.read() + '\r\n') - buf.write('--' + boundary + '--\r\n\r\n') - buf = buf.getvalue() - return boundary, buf - multipart_encode = Callable(multipart_encode) - - https_request = http_request - - -def user_agent_identifier(): - """Return the user agent for the client.""" - client_info = (get_version(), platform.system(), platform.machine()) - return "txclient/%s (%s %s)" % client_info