mirror of
https://github.com/nextcloud/android.git
synced 2024-11-27 17:46:37 +03:00
Merge branch 'develop' into refactor_remote_operation_to_download_file
This commit is contained in:
commit
6daaf70be8
24 changed files with 21 additions and 3878 deletions
|
@ -28,7 +28,7 @@
|
|||
<string name="prefs_feedback">Rückmeldungen</string>
|
||||
<string name="prefs_imprint">Impressum</string>
|
||||
<string name="recommend_subject">Probiere %1$s auf Deinem Smartphone!</string>
|
||||
<string name="recommend_text">Ich möchte Dich zum Benutzen von %1$s auf Deinem Smartphone einladen!\nLade es hier herunter: %2$s</string>
|
||||
<string name="recommend_text">Ich möchte Dich zu %1$s für Dein Smartphone einladen!\nLade es hier herunter: %2$s</string>
|
||||
<string name="auth_check_server">Überprüfe den Server</string>
|
||||
<string name="auth_host_url">Adresse des Servers</string>
|
||||
<string name="auth_username">Benutzername</string>
|
||||
|
@ -94,7 +94,7 @@
|
|||
<string name="sync_fail_in_favourites_content">Inhalte von %1$d konnte nicht synchronisiert werden (%2$d Konflikte)</string>
|
||||
<string name="sync_foreign_files_forgotten_ticker">Einige lokale Dateien wurden vergessen</string>
|
||||
<string name="sync_foreign_files_forgotten_content">%1$d Dateien aus dem Verzeichnis %2$s konnten nicht kopiert werden nach</string>
|
||||
<string name="sync_foreign_files_forgotten_explanation">\"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.</string>
|
||||
<string name="sync_foreign_files_forgotten_explanation">\"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.</string>
|
||||
<string name="sync_current_folder_was_removed">Das Verzeichnis %1$s existiert nicht mehr</string>
|
||||
<string name="foreign_files_move">Verschiebe alle</string>
|
||||
<string name="foreign_files_success">Alle Dateien wurden verschoben</string>
|
||||
|
@ -152,9 +152,9 @@
|
|||
<string name="auth_oauth_error">Autorisierung nicht erfolgreich</string>
|
||||
<string name="auth_oauth_error_access_denied">Zugriff durch den Autorisierungsserver abgelehnt</string>
|
||||
<string name="auth_wtf_reenter_URL">Unerwarteter Zustand; bitte gib die URL des Servers nochmals ein</string>
|
||||
<string name="auth_expired_oauth_token_toast">Ihre Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen</string>
|
||||
<string name="auth_expired_oauth_token_toast">Deine Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen</string>
|
||||
<string name="auth_expired_basic_auth_toast">Bitte gib dein aktuelles Passwort ein</string>
|
||||
<string name="auth_expired_saml_sso_token_toast">Ihre Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen</string>
|
||||
<string name="auth_expired_saml_sso_token_toast">Deine Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen</string>
|
||||
<string name="auth_connecting_auth_server">Verbinde mit dem Authentifizierung-Server…</string>
|
||||
<string name="auth_unsupported_auth_method">Der Server unterstützt diese Authentifizierung-Methode nicht</string>
|
||||
<string name="auth_unsupported_multiaccount">%1$s unterstützt nicht mehrere Benutzerkonten</string>
|
||||
|
|
|
@ -24,7 +24,10 @@
|
|||
<string name="prefs_log_summary_history">Honek gordetako erregistroak bistaratzen ditu.</string>
|
||||
<string name="prefs_log_delete_history_button">Ezabatu historia</string>
|
||||
<string name="prefs_help">Laguntza</string>
|
||||
<string name="prefs_recommend">Lagun bati aholkatu</string>
|
||||
<string name="prefs_imprint">Imprint</string>
|
||||
<string name="recommend_subject">Probatu %1$s zure telefono adimentsuan!</string>
|
||||
<string name="recommend_text">Nik %1$s zure telefono adimentsuan erabitzera gonbidatu nahi zaitut!\nDeskargatu hemen: %2$s</string>
|
||||
<string name="auth_check_server">Egiaztatu zerbitzaria</string>
|
||||
<string name="auth_host_url">Zerbitzariaren helbidea</string>
|
||||
<string name="auth_username">Erabiltzaile izena</string>
|
||||
|
@ -90,6 +93,8 @@
|
|||
<string name="sync_fail_in_favourites_content">%1$d fitxategien edukiak ezin dira sinkronizatu (%2$d gatazka)</string>
|
||||
<string name="sync_foreign_files_forgotten_ticker">Bertako fitxategi batzuk ahaztu dira</string>
|
||||
<string name="sync_foreign_files_forgotten_content">%2$s karpetako %1$d fitxategi ezin dira dira kopiatu</string>
|
||||
<string name="sync_foreign_files_forgotten_explanation">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.</string>
|
||||
<string name="sync_current_folder_was_removed">%1$s karpeta dagoeneko ez da existitzen</string>
|
||||
<string name="foreign_files_move">Mugitu denak</string>
|
||||
<string name="foreign_files_success">Fitxategi guztiak mugitu dira</string>
|
||||
<string name="foreign_files_fail">Fitxategi batzuk ezin dira mugitu</string>
|
||||
|
@ -115,6 +120,7 @@
|
|||
<string name="media_err_unsupported">Onartzen ez de euskarri kodeka</string>
|
||||
<string name="media_err_io">Euskarri fitxategia ezin da bihurtu</string>
|
||||
<string name="media_err_malformed">Euskarri fitxategia ezin da kodetu</string>
|
||||
<string name="media_err_timeout">Erreproduzitzen saiatzean denbora iraungitu da</string>
|
||||
<string name="media_err_invalid_progressive_playback">Euskarri fitxategia ezin da jariotu</string>
|
||||
<string name="media_err_unknown">Euskarri fitxategia ezin erreproduzitu stock euskarri erreproduzigailuarekin</string>
|
||||
<string name="media_err_security_ex">Segurtasun errorea %1$s erreproduzitzen saiatzean</string>
|
||||
|
@ -129,12 +135,15 @@
|
|||
<string name="auth_connection_established">Konexioa ezarri da</string>
|
||||
<string name="auth_testing_connection">Konexioa probatzen...</string>
|
||||
<string name="auth_not_configured_title">gaizki egindako server konfigurazioa</string>
|
||||
<string name="auth_account_not_new">Erabiltzaile eta zerbitzari hauendako dagoeneko kontu bat existitzen da gailu honetan</string>
|
||||
<string name="auth_account_not_the_same">Sartutako erabiltzaileak ez du bat egiten kontu honetako erabiltzailearekin</string>
|
||||
<string name="auth_unknown_error_title">Errore ezezagun bat gertatu da</string>
|
||||
<string name="auth_unknown_host_title">Ezin izan da hostalaria aurkitu</string>
|
||||
<string name="auth_incorrect_path_title">ez da serveren instalaziorik aurkitu</string>
|
||||
<string name="auth_timeout_title">Zerbitzariak denbora asko hartu du erantzuteko</string>
|
||||
<string name="auth_incorrect_address_title">Gaizki sortutako URLa</string>
|
||||
<string name="auth_ssl_general_error_title">SSL abiaratzeak huts egin du</string>
|
||||
<string name="auth_ssl_unverified_server_title">Ezin izan da SSL zerbitzariaren identitaea egiaztatu</string>
|
||||
<string name="auth_bad_oc_version_title">server zerbitzari bertsio ezezaguna</string>
|
||||
<string name="auth_wrong_connection_title">Ezin izan da konexioa egin</string>
|
||||
<string name="auth_secure_connection">Konexio segurua ezarri da</string>
|
||||
|
@ -142,7 +151,12 @@
|
|||
<string name="auth_oauth_error">Baimena ez da lortu</string>
|
||||
<string name="auth_oauth_error_access_denied">Sarrera autorizazio zerbitzariak ukatua</string>
|
||||
<string name="auth_wtf_reenter_URL">Egoera esperogabea, mesedez idatzi berriz zerbitzari URLa</string>
|
||||
<string name="auth_expired_oauth_token_toast">Zure baimena iraungitu da.\nMesedez, baimendu berriz</string>
|
||||
<string name="auth_expired_basic_auth_toast">Mesedez, sartu oraingo pasahitza</string>
|
||||
<string name="auth_expired_saml_sso_token_toast">Zure saioa iraungitu da. Mesdez konektatu berriro</string>
|
||||
<string name="auth_connecting_auth_server">Konektatzen autentikazio zerbitzarira...</string>
|
||||
<string name="auth_unsupported_auth_method">Zerbitzariak ez du autentikazio metodo hau onartzen</string>
|
||||
<string name="auth_unsupported_multiaccount">%1$s ez du kontu anitzak onartzen</string>
|
||||
<string name="fd_keep_in_sync">Mantendu fitxategia eguneratuta</string>
|
||||
<string name="common_rename">Berrizendatu</string>
|
||||
<string name="common_remove">Ezabatu</string>
|
||||
|
@ -160,9 +174,11 @@
|
|||
<string name="sync_file_fail_msg">Urruneko fitxategia ezin izan da arakatu</string>
|
||||
<string name="sync_file_nothing_to_do_msg">Fitxategi edukiak dagoeneko sinkronizaturik</string>
|
||||
<string name="create_dir_fail_msg">Karpeta ezin da sortu</string>
|
||||
<string name="filename_forbidden_characters">Debekatutako karaktereak: / \\ < > : \" | ? *</string>
|
||||
<string name="wait_a_moment">Itxaron momentu bat</string>
|
||||
<string name="filedisplay_unexpected_bad_get_content">Ezusteko arazoa; mesedez, saiatu beste app batekin fitxategia hautatzeko</string>
|
||||
<string name="filedisplay_no_file_selected">Ez da fitxategirik hautatu</string>
|
||||
<string name="oauth_check_onoff">Saioa hasi oAuth2-rekin</string>
|
||||
<string name="oauth_login_connection">Konektatzen oAuth2 zerbitzarira...</string>
|
||||
<string name="ssl_validator_header">Lekuaren identitatea ezin da egiaztatu</string>
|
||||
<string name="ssl_validator_reason_cert_not_trusted">- Zerbitzariaren ziurtagiria ez da fidagarria</string>
|
||||
|
@ -202,6 +218,7 @@
|
|||
<string name="preview_image_description">Irudi aurreikuspena</string>
|
||||
<string name="preview_image_error_unknown_format">Ezin da irudi hau erakutsi</string>
|
||||
<string name="error__upload__local_file_not_copied">%1$s ezin da %2$s bertako karpetara kopiatu</string>
|
||||
<string name="actionbar_failed_instant_upload">UnekoIgoerak huts egin du</string>
|
||||
<string name="failed_upload_headline_text">Uneko igoerek huts egin dute</string>
|
||||
<string name="failed_upload_headline_hint">Huts egindako igoeren laburpena</string>
|
||||
<string name="failed_upload_all_cb">Hautatu dena</string>
|
||||
|
|
5
third_party/transifex-client/.gitignore
vendored
5
third_party/transifex-client/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
|||
.tx
|
||||
*pyc
|
||||
*pyo
|
||||
*~
|
||||
*egg-info*
|
21
third_party/transifex-client/DEVELOPMENT.rst
vendored
21
third_party/transifex-client/DEVELOPMENT.rst
vendored
|
@ -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
|
343
third_party/transifex-client/LICENSE
vendored
343
third_party/transifex-client/LICENSE
vendored
|
@ -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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 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.
|
||||
|
6
third_party/transifex-client/MANIFEST.in
vendored
6
third_party/transifex-client/MANIFEST.in
vendored
|
@ -1,6 +0,0 @@
|
|||
include tx
|
||||
|
||||
# Docs
|
||||
include LICENSE README.rst
|
||||
recursive-include docs *
|
||||
|
30
third_party/transifex-client/README.rst
vendored
30
third_party/transifex-client/README.rst
vendored
|
@ -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/
|
||||
|
56
third_party/transifex-client/setup.py
vendored
56
third_party/transifex-client/setup.py
vendored
|
@ -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',),
|
||||
)
|
|
@ -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)
|
531
third_party/transifex-client/tests/test_project.py
vendored
531
third_party/transifex-client/tests/test_project.py
vendored
|
@ -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')
|
||||
|
109
third_party/transifex-client/tx
vendored
109
third_party/transifex-client/tx
vendored
|
@ -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:])
|
19
third_party/transifex-client/txclib/__init__.py
vendored
19
third_party/transifex-client/txclib/__init__.py
vendored
|
@ -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
|
576
third_party/transifex-client/txclib/commands.py
vendored
576
third_party/transifex-client/txclib/commands.py
vendored
|
@ -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 '<lang>' 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 <project_slug>"\
|
||||
".<resource_slug> 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 <project_slug>"\
|
||||
".<resource_slug> 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 <project_slug>"\
|
||||
".<resource_slug> 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 <lang> 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()
|
115
third_party/transifex-client/txclib/config.py
vendored
115
third_party/transifex-client/txclib/config.py
vendored
|
@ -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)
|
|
@ -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."""
|
|
@ -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
|
37
third_party/transifex-client/txclib/log.py
vendored
37
third_party/transifex-client/txclib/log.py
vendored
|
@ -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))
|
241
third_party/transifex-client/txclib/parsers.py
vendored
241
third_party/transifex-client/txclib/parsers.py
vendored
|
@ -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 <lang_code>\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 <path>"
|
||||
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 <file>\n\n"\
|
||||
" To set a single translation file:\n $ tx set -r project.resource -l de <file>\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 <file>\n\n"\
|
||||
" To set a remote release/resource/project:\n"\
|
||||
" $ tx set --auto-remote <transifex-url>\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 []
|
|
@ -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
|
1233
third_party/transifex-client/txclib/project.py
vendored
1233
third_party/transifex-client/txclib/project.py
vendored
File diff suppressed because it is too large
Load diff
21
third_party/transifex-client/txclib/urls.py
vendored
21
third_party/transifex-client/txclib/urls.py
vendored
|
@ -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/',
|
||||
}
|
||||
|
||||
|
259
third_party/transifex-client/txclib/utils.py
vendored
259
third_party/transifex-client/txclib/utils.py
vendored
|
@ -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 <lang> expression
|
||||
"""
|
||||
# Force expr to be a valid regex expr (escaped) but keep <lang> intact
|
||||
expr_re = re.escape(os.path.join(root_path, file_filter))
|
||||
expr_re = expr_re.replace("\\<lang\\>", '<lang>').replace(
|
||||
'<lang>', '([^%(sep)s]+)' % { 'sep': re.escape(os.path.sep)})
|
||||
|
||||
return "^%s$" % expr_re
|
||||
|
||||
|
||||
TX_URLS = {
|
||||
'resource': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\w|-)+)/resource/(?P<resource>(\w|-)+)/?$',
|
||||
'release': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\w|-)+)/r/(?P<release>(\w|-)+)/?$',
|
||||
'project': '(?P<hostname>https?://(\w|\.|:|-)+)/projects/p/(?P<project>(\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
|
94
third_party/transifex-client/txclib/web.py
vendored
94
third_party/transifex-client/txclib/web.py
vendored
|
@ -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
|
Loading…
Reference in a new issue