diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e1d851e498..5668477f6b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -28,7 +28,7 @@
Rückmeldungen
Impressum
Probiere %1$s auf Deinem Smartphone!
- Ich möchte Dich zum Benutzen von %1$s auf Deinem Smartphone einladen!\nLade es hier herunter: %2$s
+ Ich möchte Dich zu %1$s für Dein Smartphone einladen!\nLade es hier herunter: %2$s
Überprüfe den Server
Adresse des Servers
Benutzername
@@ -94,7 +94,7 @@
Inhalte von %1$d konnte nicht synchronisiert werden (%2$d Konflikte)
Einige lokale Dateien wurden vergessen
%1$d Dateien aus dem Verzeichnis %2$s konnten nicht kopiert werden nach
- \"Mit Version 1.3.16 werden Dateien die von diesem Gerät aus hochgeladen werden in den lokalen Ordner %1$s kopiert um Datenverlust zu vermeiden, wenn eine einzelne Datei mit mehreren Accounts synchronisiert wird.\n\nInfolge dieser Änderung wurden alle Dateien, die mit vorherigen Versionen dieser App hochgeladen wurden, in den Ordner %2$s verschoben. Jedoch ist während der Account-Synchronisation ein Fehler aufgetreten, der das Abschließen dieses Vorgangs verhindert. Du kannst die Datei(en) entweder wie sie sind belassen und den Link zu %3$s entfernen oder die Datei(en) in den %1$s Ordner verschieben und den Link zu %4$s beibehalten.\n\nUnten befindet sich eine Liste der lokalen Datei(en) und der mit ihnen verbundenen Remote-Datei(en) in %5$s.
+ \"Mit Version 1.3.16 werden Dateien die von diesem Gerät aus hochgeladen werden in den lokalen Ordner %1$s kopiert, um Datenverlust zu vermeiden, wenn eine einzelne Datei mit mehreren Accounts synchronisiert wird.\n\nInfolge dieser Änderung wurden alle Dateien, die mit vorherigen Versionen dieser App hochgeladen wurden, in den Ordner %2$s verschoben. Jedoch ist während der Account-Synchronisation ein Fehler aufgetreten, der das Abschließen dieses Vorgangs verhindert. Du kannst die Datei(en) entweder wie sie sind belassen und den Link zu %3$s entfernen, oder die Datei(en) in den %1$s Ordner verschieben, und den Link zu %4$s beibehalten.\n\nUnten befindet sich eine Liste der lokalen Datei(en) und der mit ihnen verbundenen Remote-Datei(en) in %5$s.
Das Verzeichnis %1$s existiert nicht mehr
Verschiebe alle
Alle Dateien wurden verschoben
@@ -152,9 +152,9 @@
Autorisierung nicht erfolgreich
Zugriff durch den Autorisierungsserver abgelehnt
Unerwarteter Zustand; bitte gib die URL des Servers nochmals ein
- Ihre Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen
+ Deine Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen
Bitte gib dein aktuelles Passwort ein
- Ihre Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen
+ Deine Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen
Verbinde mit dem Authentifizierung-Server…
Der Server unterstützt diese Authentifizierung-Methode nicht
%1$s unterstützt nicht mehrere Benutzerkonten
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 221b0b8b92..4938ab57c4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -24,7 +24,10 @@
Honek gordetako erregistroak bistaratzen ditu.
Ezabatu historia
Laguntza
+ Lagun bati aholkatu
Imprint
+ Probatu %1$s zure telefono adimentsuan!
+ Nik %1$s zure telefono adimentsuan erabitzera gonbidatu nahi zaitut!\nDeskargatu hemen: %2$s
Egiaztatu zerbitzaria
Zerbitzariaren helbidea
Erabiltzaile izena
@@ -90,6 +93,8 @@
%1$d fitxategien edukiak ezin dira sinkronizatu (%2$d gatazka)
Bertako fitxategi batzuk ahaztu dira
%2$s karpetako %1$d fitxategi ezin dira dira kopiatu
+ 1.3.16 bertsioan, gailu honetatik igotzen diren fitxategiak bertako %1$s karpetara mugitzen dira datu galera ekiditzeko fitxategi bat kontu ezberdinekin sinkronizatzen denean.\n\nAldaketa hau dela eta, programa honen aurreko bertsioetan igotako fitxategi guztiak %2$s karpetara kopiatu dira. Hala ere, errore batek hau burutzea ekidin du kontuaren sinkronizazioa egiten ari zen bitartean. Orain fitxategiak dauden bezala utz ditzakezu eta %3$s rako lotura ezabatu, edo fitxategiak %1$s karpetara mugi ditzakezu eta %4$srako lotura mantendu.\n\nBehean bertako fitxategien zerrenda eta %5$s era lotuta zeuden urruneko fitxategiena.
+ %1$s karpeta dagoeneko ez da existitzen
Mugitu denak
Fitxategi guztiak mugitu dira
Fitxategi batzuk ezin dira mugitu
@@ -115,6 +120,7 @@
Onartzen ez de euskarri kodeka
Euskarri fitxategia ezin da bihurtu
Euskarri fitxategia ezin da kodetu
+ Erreproduzitzen saiatzean denbora iraungitu da
Euskarri fitxategia ezin da jariotu
Euskarri fitxategia ezin erreproduzitu stock euskarri erreproduzigailuarekin
Segurtasun errorea %1$s erreproduzitzen saiatzean
@@ -129,12 +135,15 @@
Konexioa ezarri da
Konexioa probatzen...
gaizki egindako server konfigurazioa
+ Erabiltzaile eta zerbitzari hauendako dagoeneko kontu bat existitzen da gailu honetan
+ Sartutako erabiltzaileak ez du bat egiten kontu honetako erabiltzailearekin
Errore ezezagun bat gertatu da
Ezin izan da hostalaria aurkitu
ez da serveren instalaziorik aurkitu
Zerbitzariak denbora asko hartu du erantzuteko
Gaizki sortutako URLa
SSL abiaratzeak huts egin du
+ Ezin izan da SSL zerbitzariaren identitaea egiaztatu
server zerbitzari bertsio ezezaguna
Ezin izan da konexioa egin
Konexio segurua ezarri da
@@ -142,7 +151,12 @@
Baimena ez da lortu
Sarrera autorizazio zerbitzariak ukatua
Egoera esperogabea, mesedez idatzi berriz zerbitzari URLa
+ Zure baimena iraungitu da.\nMesedez, baimendu berriz
Mesedez, sartu oraingo pasahitza
+ Zure saioa iraungitu da. Mesdez konektatu berriro
+ Konektatzen autentikazio zerbitzarira...
+ Zerbitzariak ez du autentikazio metodo hau onartzen
+ %1$s ez du kontu anitzak onartzen
Mantendu fitxategia eguneratuta
Berrizendatu
Ezabatu
@@ -160,9 +174,11 @@
Urruneko fitxategia ezin izan da arakatu
Fitxategi edukiak dagoeneko sinkronizaturik
Karpeta ezin da sortu
+ Debekatutako karaktereak: / \\ < > : \" | ? *
Itxaron momentu bat
Ezusteko arazoa; mesedez, saiatu beste app batekin fitxategia hautatzeko
Ez da fitxategirik hautatu
+ Saioa hasi oAuth2-rekin
Konektatzen oAuth2 zerbitzarira...
Lekuaren identitatea ezin da egiaztatu
- Zerbitzariaren ziurtagiria ez da fidagarria
@@ -202,6 +218,7 @@
Irudi aurreikuspena
Ezin da irudi hau erakutsi
%1$s ezin da %2$s bertako karpetara kopiatu
+ UnekoIgoerak huts egin du
Uneko igoerek huts egin dute
Huts egindako igoeren laburpena
Hautatu dena
diff --git a/third_party/transifex-client/.gitignore b/third_party/transifex-client/.gitignore
deleted file mode 100644
index 72bd50ca99..0000000000
--- 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 992e518fcf..0000000000
--- 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 db860a38a8..0000000000
--- 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 83126ac6ae..0000000000
--- 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 bb887503fe..0000000000
--- 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 626303d517..0000000000
--- 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 e69de29bb2..0000000000
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 dd7d7d95b2..0000000000
--- 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 3b4212143c..0000000000
--- 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 dfb4a4c1d1..0000000000
--- 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 814773c884..0000000000
--- 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 282287fc4e..0000000000
--- 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 a9d055f5b8..0000000000
--- 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 8766a01871..0000000000
--- 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 3243149c0b..0000000000
--- 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 9baf3220b2..0000000000
--- 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 fd3237d20e..0000000000
--- 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 dc3a73f9de..0000000000
--- 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 88bb46bf34..0000000000
--- 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 0bb74fde3a..0000000000
--- 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 318bee9170..0000000000
--- 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 a3cb3f0056..0000000000
--- 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