Hello community,

here is the log from the commit of package transifex-client for 
openSUSE:Factory checked in at 2017-05-06 18:30:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/transifex-client (Old)
 and      /work/SRC/openSUSE:Factory/.transifex-client.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "transifex-client"

Sat May  6 18:30:00 2017 rev:14 rq:492639 version:0.12.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/transifex-client/transifex-client.changes        
2016-09-27 13:43:58.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.transifex-client.new/transifex-client.changes   
2017-05-06 18:30:16.397962598 +0200
@@ -1,0 +2,7 @@
+Wed May  3 12:37:29 UTC 2017 - tchva...@suse.com
+
+- Version update to 0.12.4:
+  * XLIFF and tokens support
+- Switch to python3, no need to keep python2 around
+
+-------------------------------------------------------------------

Old:
----
  0.12.2.tar.gz

New:
----
  0.12.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ transifex-client.spec ++++++
--- /var/tmp/diff_new_pack.jj0nEV/_old  2017-05-06 18:30:17.497807404 +0200
+++ /var/tmp/diff_new_pack.jj0nEV/_new  2017-05-06 18:30:17.497807404 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package transifex-client
 #
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,18 +17,18 @@
 
 
 Name:           transifex-client
-Version:        0.12.2
+Version:        0.12.4
 Release:        0
 Summary:        Transifex Command-line Client
 License:        GPL-2.0
 Group:          Productivity/Text/Utilities
 Url:            https://github.com/transifex/transifex-client
 Source:         
https://github.com/transifex/transifex-client/archive/%{version}.tar.gz
-BuildRequires:  python-mock
-BuildRequires:  python-setuptools
-BuildRequires:  python-urllib3
-Requires:       python-setuptools
-Requires:       python-urllib3
+BuildRequires:  python3-mock
+BuildRequires:  python3-setuptools
+BuildRequires:  python3-urllib3
+Requires:       python3-setuptools
+Requires:       python3-urllib3
 BuildArch:      noarch
 
 %description
@@ -46,20 +46,20 @@
 %setup -q
 
 %build
-python setup.py build
+python3 setup.py build
 
 %install
-python setup.py install --prefix=%{_prefix} --root=%{buildroot}
+python3 setup.py install --prefix=%{_prefix} --root=%{buildroot}
 # remove pem file
-rm %{buildroot}/%{python_sitelib}/txclib/cacert.pem
+rm %{buildroot}/%{python3_sitelib}/txclib/cacert.pem
 
 %check
-python setup.py test
+python3 setup.py test
 
 %files
 %defattr(-,root,root)
-%doc README.rst LICENSE
+%doc README.md LICENSE
 %{_bindir}/tx
-%{python_sitelib}/*
+%{python3_sitelib}/*
 
 %changelog

++++++ 0.12.2.tar.gz -> 0.12.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/.gitignore 
new/transifex-client-0.12.4/.gitignore
--- old/transifex-client-0.12.2/.gitignore      2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/.gitignore      2017-02-06 17:05:52.000000000 
+0100
@@ -5,3 +5,7 @@
 *egg-info*
 /build
 /dist
+*.swp
+.tox/
+.coverage
+.eggs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/MANIFEST.in 
new/transifex-client-0.12.4/MANIFEST.in
--- old/transifex-client-0.12.2/MANIFEST.in     2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/MANIFEST.in     2017-02-06 17:05:52.000000000 
+0100
@@ -3,7 +3,7 @@
 include requirements.txt
 
 # Docs
-include LICENSE README.rst
+include LICENSE README.md
 recursive-include docs *
 
 # Tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/README.md 
new/transifex-client-0.12.4/README.md
--- old/transifex-client-0.12.2/README.md       2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/README.md       2017-02-06 17:05:52.000000000 
+0100
@@ -3,6 +3,7 @@
 
[![image](https://circleci.com/gh/transifex/transifex-client/tree/master.svg?style=shield&circle-token=33aafd984726261eff1b73278a0cf761382c478a)](https://circleci.com/gh/transifex/transifex-client/tree/master)
 
[![image](https://ci.appveyor.com/api/projects/status/github/transifex/transifex-client?branch=master&svg=true)](https://ci.appveyor.com/project/transifex/transifex-client/branch/master)
 
[![codecov](https://codecov.io/gh/transifex/transifex-client/branch/master/graph/badge.svg)](https://codecov.io/gh/transifex/transifex-client)
+[![PyPI 
version](https://badge.fury.io/py/transifex-client.svg)](https://badge.fury.io/py/transifex-client)
 
 Description
 ---
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/README.rst 
new/transifex-client-0.12.4/README.rst
--- old/transifex-client-0.12.2/README.rst      2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/README.rst      1970-01-01 01:00:00.000000000 
+0100
@@ -1,69 +0,0 @@
-.. image:: 
https://circleci.com/gh/transifex/transifex-client/tree/master.svg?style=shield&circle-token=33aafd984726261eff1b73278a0cf761382c478a
-    :target: https://circleci.com/gh/transifex/transifex-client/tree/master
-.. image:: 
https://ci.appveyor.com/api/projects/status/github/transifex/transifex-client?branch=master&svg=true
-    :target: 
https://ci.appveyor.com/project/transifex/transifex-client/branch/master
-.. image:: 
https://codecov.io/gh/transifex/transifex-client/branch/master/graph/badge.svg
-    :target: https://codecov.io/gh/transifex/transifex-client
-
-
-
-=============================
- Transifex Command-Line Tool
-=============================
-
-The Transifex Command-line Tool enables you to manage your translations within 
a project without the need of an elaborate UI system.
-
-You can use the command line tool to create new resources, map locale files to 
translations, and synchronize your Transifex project with your local 
repository. Translators and localization managers can use it to handle large 
volumes of translation files. The Transifex Command-line Tool can help to 
enable continuous integration workflows and can be run from CI servers like 
Jenkins and Bamboo.
-
-Check the full documentation at http://docs.transifex.com/client/
-
-Installing
-==========
-
-You can install the latest version of transifex-client running ``pip
-install transifex-client`` or ``easy_install transifex-client``.
-
-
-Build transifex-client for Windows
-==================================
-
-1. Download transifex-client sources via git or github archive.
-
-   a. ``git clone https://github.com/transifex/transifex-client.git``
-   b. Download and unpack 
https://github.com/transifex/transifex-client/archive/master.zip
-
-2. Download and install Python_.
-
-   At this step choose right version of python: 2 or 3 and x86 or x86-64 
instruction set.
-
-   Make sure pip marked for installation(default for latest installers).
-
-3. Install PyInstaller_.
-
-   Suppose that Python installed to ``C:\\Program Files\\Python35-32``
-
-   Make ``python.exe`` accessible via PATH environment variable or cd to 
directory containing python.exe.
-
-   ::
-
-     python -m pip install pyinstaller
-
-   This command will install ``PyInstaller`` package and its dependencies.
-
-4. Build ``transifex-client`` distribution.
-
-   Change directory to transifex-client folder and run command:
-
-   ::
-
-     python -m PyInstaller contrib/tx.spec
-     # or
-     pyinstaller contrib/tx.spec
-
-5. ``tx.exe``
-
-   ``dist/tx.exe`` will be created as the result of build process.
-
-
-.. _Python: https://www.python.org/downloads/windows/
-.. _PyInstaller: http://www.pyinstaller.org
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/codecov.yml 
new/transifex-client-0.12.4/codecov.yml
--- old/transifex-client-0.12.2/codecov.yml     2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/codecov.yml     2017-02-06 17:05:52.000000000 
+0100
@@ -14,7 +14,8 @@
   notify:
     slack:
       default:
-        url: 
https://hooks.slack.com/services/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
+        url:
+          
secret:4FCetOj2urXyoT02ONiv62804o/mPp++K5/L3QlnSVdeovhfAQWVlROA+ZKPGdi0atAN+8lfoZIfk8qPY5JgGQOkcPMCoxVL+OaAgLWJlBdB7j5YfHERyf0LH6neP8E0qAqzk8xYFv3wqGSmcxwx1DRS5qx3LwZNuIoyF2Nhsg4=
         threshold: null
         branches: null
         attachments: "sunburst, diff"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/contrib/tx_commands.sh 
new/transifex-client-0.12.4/contrib/tx_commands.sh
--- old/transifex-client-0.12.2/contrib/tx_commands.sh  2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/contrib/tx_commands.sh  2017-02-06 
17:05:52.000000000 +0100
@@ -6,12 +6,28 @@
 # Exit on fail
 set -e
 
+# Set up repo, tx config
 rm -rf txci
 git clone https://github.com/transifex/txci.git
 cd txci
 rm -rf .tx
 $TX init --host="https://www.transifex.com"; --user=$TRANSIFEX_USER 
--pass=$TRANSIFEX_PASSWORD
 $TX set --auto-local -r txci.$BRANCH -s en 
'locale/<lang>/LC_MESSAGES/django.po' -t PO --execute
+
+# push/pull without XLIFF
 $TX --traceback push -s
 $TX --traceback pull -l pt_BR -f
+
+# # Push dummy translation to pt_BR language
+# cp locale/en/LC_MESSAGES/django.po locale/pt_BR/LC_MESSAGES/django.po
+# yes | $TX --traceback push -t -l pt_BR -f
+#
+# # try to download translation xliff
+# $TX --traceback pull -l pt_BR -f --xliff
+# echo 'Checking if translation xlf file has been downloaded...'
+# ls locale/pt_BR/LC_MESSAGES/django.po.xlf
+#
+# # upload xliff
+# yes | $TX --traceback push -t -l pt_BR -f --xliff
+
 $TX --traceback delete -f
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/setup.py 
new/transifex-client-0.12.4/setup.py
--- old/transifex-client-0.12.2/setup.py        2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/setup.py        2017-02-06 17:05:52.000000000 
+0100
@@ -10,12 +10,13 @@
     with open(filename, 'r', encoding='UTF-8') as f:
         return f.read()
 
+
 setup(
     name="transifex-client",
     version=txclib.__version__,
     entry_points={'console_scripts': ['tx=txclib.cmdline:main']},
     description="A command line interface for Transifex",
-    long_description=get_file_content('README.rst'),
+    long_description=get_file_content('README.md'),
     author="Transifex",
     author_email="ad...@transifex.com",
     url="https://www.transifex.com";,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/tests/test_project.py 
new/transifex-client-0.12.4/tests/test_project.py
--- old/transifex-client-0.12.2/tests/test_project.py   2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/tests/test_project.py   2017-02-06 
17:05:52.000000000 +0100
@@ -8,12 +8,66 @@
     import simplejson as json
 from mock import Mock, patch
 
-from txclib.project import Project
+from txclib.project import Project, ProjectNotInit
 from txclib.config import Flipdict
 
 
 class TestProject(unittest.TestCase):
 
+    @patch('txclib.utils.find_dot_tx')
+    def test_get_tx_dir_path(self, m_find_dot_tx):
+        """Test _get_tx_dir_path function"""
+        expected_path = '/tmp/'
+        m_find_dot_tx.return_value = expected_path
+        p = Project(init=False)
+        path = p._get_tx_dir_path(path_to_tx=None)
+        self.assertEqual(path, expected_path)
+        m_find_dot_tx.assert_called_once_with()
+
+        expected_path = '/opt/'
+        path = p._get_tx_dir_path(path_to_tx=expected_path)
+        self.assertEqual(path, expected_path)
+        # make sure it has not been called twice
+        m_find_dot_tx.assert_called_once_with()
+
+    @patch('os.path.exists')
+    def test_get_config_file_path(self, m_exists):
+        """Test _get_config_file_path function"""
+        p = Project(init=False)
+        m_exists.return_value = True
+        p._get_config_file_path('/tmp/')
+        m_exists.assert_called_once_with('/tmp/.tx/config')
+
+        m_exists.return_value = False
+        with self.assertRaises(ProjectNotInit):
+            p._get_config_file_path('/tmp/')
+
+    @patch('txclib.utils.confirm')
+    @patch('txclib.config.configparser')
+    def test_getset_host_credentials(self, m_parser, m_confirm):
+        p = Project(init=False)
+        # let suppose a token has been set at the config
+        dummy_token = 'salala'
+        p.txrc = m_parser
+        p.txrc.add_section = Mock()
+        p.txrc.set = Mock()
+        p.txrc.get = Mock()
+        p.txrc.get.side_effect = ['api', dummy_token, None, None]
+        p.txrc_file = '/tmp'
+        username, password = p.getset_host_credentials('test')
+        self.assertEqual(username, 'api')
+        self.assertEqual(password, dummy_token)
+
+        # let's try to get credentials for someone without
+        # a token
+        p.txrc.get.side_effect = [
+            'username',
+            'passw0rdz'
+        ]
+        username, password = p.getset_host_credentials('test')
+        self.assertEqual(username, 'username')
+        self.assertEqual(password, 'passw0rdz')
+
     def test_extract_fields(self):
         """Test the functions that extract a field from a stats object."""
         stats = {
@@ -379,7 +433,6 @@
         """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), "utf-8"
-            files_keys = self.langs
             new_trans = self.p._new_translations_to_add
             for force in [True, False]:
                 res = new_trans(
@@ -510,8 +563,7 @@
             res = self.p._should_download('en', self.stats, None, True)
             self.assertEqual(res, True)
 
-            with patch.object(self.p, '_remote_is_newer') as local_file_mock:
-                local_file_mock = False
+            with patch.object(self.p, '_remote_is_newer'):
                 res = self.p._should_download('pt', self.stats, None, False)
                 self.assertEqual(res, True)
                 res = self.p._should_download('pt', self.stats, None, True)
@@ -565,7 +617,6 @@
 
     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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/__init__.py 
new/transifex-client-0.12.4/txclib/__init__.py
--- old/transifex-client-0.12.2/txclib/__init__.py      2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/txclib/__init__.py      2017-02-06 
17:05:52.000000000 +0100
@@ -1,4 +1,4 @@
 # -*- coding: utf-8 -*-
 
 # 
https://www.python.org/dev/peps/pep-0440/#examples-of-compliant-version-schemes
-__version__ = '0.12.2'
+__version__ = '0.12.4'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/cmdline.py 
new/transifex-client-0.12.4/txclib/cmdline.py
--- old/transifex-client-0.12.2/txclib/cmdline.py       2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/txclib/cmdline.py       2017-02-06 
17:05:52.000000000 +0100
@@ -104,6 +104,7 @@
     try:
         utils.exec_command(cmd, args[1:], path_to_tx)
     except SSLError as e:
+        logger.error("SSl error %s" % e)
         sys.exit(1)
     except utils.UnknownCommandError:
         logger.error("tx: Command %s not found" % cmd)
@@ -118,6 +119,7 @@
             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.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/commands.py 
new/transifex-client-0.12.4/txclib/commands.py
--- old/transifex-client-0.12.2/txclib/commands.py      2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/txclib/commands.py      2017-02-06 
17:05:52.000000000 +0100
@@ -18,7 +18,6 @@
 import re
 import shutil
 import sys
-from optparse import OptionParser, OptionGroup
 
 try:
     import configparser
@@ -28,7 +27,6 @@
 from six.moves import input
 
 from txclib import utils, project
-from txclib.utils import parse_json, compile_json, files_in_project
 from txclib.config import OrderedRawConfigParser
 from txclib.exceptions import UnInitializedError
 from txclib.parsers import delete_parser, help_parser, parse_csv_option, \
@@ -48,30 +46,26 @@
     else:
         path_to_tx = os.getcwd()
 
-    if os.path.isdir(os.path.join(path_to_tx, ".tx")):
+    save = options.save
+    # if we already have a config file and we are not told to override it
+    # in the args we have to ask
+    if os.path.isdir(os.path.join(path_to_tx, ".tx")) and not save:
         logger.info("tx: There is already a tx folder!")
-        reinit = 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 = input("Do you want to delete it and "
-                           "reinit the project? [y/N]: ")
-        if not reinit or reinit in ['N', 'n', 'NO', 'no', 'No']:
+        if not utils.confirm(
+            prompt='Do you want to delete it and reinit the project?',
+            default=False
+        ):
             return
         # Clean the old settings
         # FIXME: take a backup
         else:
+            save = True
             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 input("Transifex instance [%s]: " %
                                            default_transifex)
@@ -85,6 +79,7 @@
     if not os.path.exists(config_file):
         # The path to the config file (.tx/config)
         logger.info("Creating skeleton...")
+        # Handle the credentials through transifexrc
         config = OrderedRawConfigParser()
         config.add_section('main')
         config.set('main', 'host', transifex_host)
@@ -95,8 +90,9 @@
         fh.close()
 
     prj = project.Project(path_to_tx)
-    prj.getset_host_credentials(transifex_host, user=options.user,
-                                password=options.password)
+    prj.getset_host_credentials(transifex_host, username=options.user,
+                                password=options.password,
+                                token=options.token, save=save)
     prj.save()
     logger.info("Done.")
 
@@ -213,7 +209,7 @@
     # 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 f_path in files_in_project(curpath):
+    for f_path in utils.files_in_project(curpath):
         match = expr_rec.match(posix_path(f_path))
         if match:
             lang = match.group(1)
@@ -240,7 +236,6 @@
             'file': os.path.relpath(source_file, curpath)})
 
     prj = project.Project(path_to_tx)
-    root_dir = os.path.abspath(path_to_tx)
 
     if execute:
         try:
@@ -342,6 +337,7 @@
     languages = parse_csv_option(options.languages)
     resources = parse_csv_option(options.resources)
     skip = options.skip_errors
+    xliff = options.xliff
     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, "
@@ -351,7 +347,8 @@
         force=force_creation, resources=resources, languages=languages,
         skip=skip, source=options.push_source,
         translations=options.push_translations,
-        no_interactive=options.no_interactive
+        no_interactive=options.no_interactive,
+        xliff=xliff
     )
     logger.info("Done.")
 
@@ -366,6 +363,8 @@
     languages = parse_csv_option(options.languages)
     resources = parse_csv_option(options.resources)
     pseudo = options.pseudo
+    # Should we download as xliff?
+    xliff = options.xliff
     skip = options.skip_errors
     minimum_perc = options.minimum_perc or None
 
@@ -381,7 +380,7 @@
         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, pseudo=pseudo
+        mode=options.mode, pseudo=pseudo, xliff=xliff
     )
     logger.info("Done.")
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/parsers.py 
new/transifex-client-0.12.4/txclib/parsers.py
--- old/transifex-client-0.12.2/txclib/parsers.py       2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/txclib/parsers.py       2017-02-06 
17:05:52.000000000 +0100
@@ -67,6 +67,17 @@
                       help="Specify username for Transifex server.")
     parser.add_option("--pass", action="store", dest="password", default=None,
                       help="Specify password for Transifex server.")
+    parser.add_option(
+        "--force-save",
+        action="store_true",
+        dest="save",
+        default=False,
+        help="Override .transifexrc file with the given credentials."
+    )
+
+    parser.add_option("--token", action="store", dest="token", default=None,
+                      help="Specify an api token.\nYou can get one from"
+                      " user's settings")
     return parser
 
 
@@ -118,6 +129,9 @@
             "'reviewed'). See http://bit.ly/pullmode for available values."
         )
     )
+    parser.add_option("-x", "--xliff", action="store_true", dest="xliff",
+                      default=False, help="Apply this option to download "
+                      "file as xliff.")
     return parser
 
 
@@ -156,6 +170,9 @@
     parser.add_option("--no-interactive", action="store_true",
                       dest="no_interactive", default=False,
                       help="Don't require user input when forcing a push.")
+    parser.add_option("-x", "--xliff", action="store_true", dest="xliff",
+                      default=False, help="Apply this option to upload "
+                      "file as xliff.")
     return parser
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/project.py 
new/transifex-client-0.12.4/txclib/project.py
--- old/transifex-client-0.12.2/txclib/project.py       2016-08-09 
16:39:55.000000000 +0200
+++ new/transifex-client-0.12.4/txclib/project.py       2017-02-06 
17:05:52.000000000 +0100
@@ -11,6 +11,12 @@
 import six
 
 try:
+    import urlparse
+    from urllib import urlencode
+except:  # For Python 3
+    import urllib.parse as urlparse
+    from urllib.parse import urlencode
+try:
     import configparser
 except ImportError:
     import ConfigParser as configparser
@@ -24,8 +30,7 @@
 from txclib.log import logger
 from txclib.processors import visit_hostname
 from txclib.paths import posix_path, native_path, posix_sep
-
-
+from txclib.utils import confirm
 
 
 class ProjectNotInit(Exception):
@@ -147,36 +152,90 @@
             mask = os.umask(0o077)
             open(txrc_file, 'w').close()
             os.umask(mask)
+            if os.path.exists(txrc_file):
+                logger.info('Created %s ' % txrc_file)
+            else:
+                logger.info('Could not create %s ' % txrc_file)
         return txrc_file
 
     def validate_config(self):
         """To ensure the json structure is correctly formed."""
         pass
 
-    def getset_host_credentials(self, host, user=None, password=None):
+    def getset_host_credentials(
+        self,
+        host,
+        username=None,
+        password=None,
+        token=None,
+        save=False
+    ):
         """Read .transifexrc and report user,
-        pass for a specific host else ask the user for input.
+        pass or a token for a specific host else ask the user for input.
         """
-        try:
-            username = self.txrc.get(host, 'username')
-            passwd = self.txrc.get(host, 'password')
-        except (configparser.NoOptionError, configparser.NoSectionError):
-            logger.info("No entry found for host %s. Creating..." % host)
-            username = user or input("Please enter your transifex username: ")
-            while (not username):
-                username = input("Please enter your transifex username: ")
-            passwd = password
-            while (not passwd):
-                passwd = getpass.getpass()
+        # from_config is a flag that tells us if we got the credentials
+        # from the config file
+        from_config = False
+        # first check if a token has been given it should override everything
+        if token:
+            password = token
+            username = 'api'
+        # if neither a token nor a username or a password were given
+        # try to get them from the rc file
+        elif not (username and password):
+            try:
+                username = self.txrc.get(host, 'username')
+                password = self.txrc.get(host, 'password')
+            except (configparser.NoOptionError, configparser.NoSectionError):
+                # if the rc has no credentials, we have to ask the user and
+                # update the rc file
+                save = True
+                # Ask the user if they have an api token
+                if confirm(
+                    prompt="\nDid you know that you can create an api"
+                    "token under your transifex user's settings?\n"
+                    "(Read more at https://docs.transifex.com/api/";
+                    "introduction#authentication)\n"
+                    "So, do you have an api token?",
+                    default=False
+
+                ):
+                    token_msg = "Please enter your api token: "
+                    while not token:
+                        token = input(token_msg)
+                    # Since we got a token, we use api as the username
+                    # and the token as the password
+                    username = 'api'
+                    password = token
+                else:
+                    username_msg = "Please enter your transifex username: "
+                    while not username:
+                        username = input(username_msg)
+                    while (not password):
+                        password = getpass.getpass()
+            else:
+                from_config = True
+
+        # lets see if there is a default username or a password
+        # unless we got the files from the config
+        if not from_config:
+            try:
+                username = self.txrc.get(host, 'username')
+                password = self.txrc.get(host, 'password')
+            except (configparser.NoOptionError, configparser.NoSectionError):
+                # if we do not have defaults, save the give credentials
+                save = True
 
+        if save:
             logger.info("Updating %s file..." % self.txrc_file)
-            self.txrc.add_section(host)
+            if not self.txrc.has_section(host):
+                logger.info("No entry found for host %s. Creating..." % host)
+                self.txrc.add_section(host)
             self.txrc.set(host, 'username', username)
-            self.txrc.set(host, 'password', passwd)
-            self.txrc.set(host, 'token', '')
+            self.txrc.set(host, 'password', password)
             self.txrc.set(host, 'hostname', host)
-
-        return username, passwd
+            self.save()
+        return username, password
 
     def set_remote_resource(self, resource, source_lang, i18n_type, host,
                             file_filter=None):
@@ -261,7 +320,7 @@
             else:
                 return native_path(source_file)
 
-    def get_resource_files(self, resource):
+    def get_resource_files(self, resource, xliff=False):
         """Get a dict for all files assigned to a resource.
         First we calculate the files matching the file expression and
         then we apply all translation excpetions.
@@ -279,6 +338,9 @@
                 file_filter = self.config.get(resource, "file_filter")
             except configparser.NoOptionError:
                 file_filter = "$^"
+            if xliff:
+                # update the file-path in case of xliff option
+                file_filter += '.xlf'
             source_lang = self.config.get(resource, "source_lang")
             source_file = self.get_source_file(resource)
             expr_re = utils.regex_from_filefilter(file_filter, self.root)
@@ -380,11 +442,12 @@
 
     def pull(self, languages=[], resources=[], overwrite=True, fetchall=False,
              fetchsource=False, force=False, skip=False, minimum_perc=0,
-             mode=None, pseudo=False):
+             mode=None, pseudo=False, xliff=False):
         """Pull all translations file from transifex server."""
         self.minimum_perc = minimum_perc
         resource_list = self.get_chosen_resources(resources)
         skip_decode = False
+        params = {}
 
         if mode == 'reviewed':
             url = 'pull_reviewed_file'
@@ -472,8 +535,13 @@
             if pull_languages:
                 logger.debug("Pulling languages for: %s" % pull_languages)
                 msg = "Pulling translations for resource %s (source: %s)"
+                if xliff:
+                    msg += " [xliff format]"
                 logger.info(msg % (resource, sfile))
 
+            if xliff:
+                params.update({'file': 'xliff'})
+
             for lang in pull_languages:
                 local_lang = lang
                 if lang in list(lang_map.values()):
@@ -496,7 +564,9 @@
                     'force': force,
                     'mode': mode,
                 }
-                if not self._should_update_translation(**kwargs):
+
+                # xliff files should be always pulled
+                if not xliff and not self._should_update_translation(**kwargs):
                     msg = "Skipping '%s' translation (file: %s)."
                     logger.info(
                         msg % (utils.color_text(remote_lang, "RED"),
@@ -504,6 +574,9 @@
                     )
                     continue
 
+                if xliff:
+                    local_file += '.xlf'
+
                 if not overwrite:
                     local_file = ("%s.new" % local_file)
                 logger.warning(
@@ -512,7 +585,8 @@
                 )
                 try:
                     r, charset = self.do_url_request(
-                        url, language=remote_lang, skip_decode=skip_decode
+                        url, language=remote_lang, skip_decode=skip_decode,
+                        params=params
                     )
                 except Exception as e:
                     if isinstance(e, SSLError) or not skip:
@@ -562,20 +636,30 @@
                     )
 
                     r, charset = self.do_url_request(
-                        url, language=remote_lang, skip_decode=skip_decode
+                        url, language=remote_lang, skip_decode=skip_decode,
+                        params=params
                     )
+                    if xliff:
+                        local_file += '.xlf'
+
                     self._save_file(local_file, charset, r)
 
     def push(self, source=False, translations=False, force=False,
-             resources=[], languages=[], skip=False, no_interactive=False):
+             resources=[], languages=[], skip=False, no_interactive=False,
+             xliff=False):
         """Push all the resources"""
         resource_list = self.get_chosen_resources(resources)
         self.skip = skip
         self.force = force
+
+        params = {}
+        if xliff:
+            params.update({'file_type': 'xliff'})
+
         for resource in resource_list:
             push_languages = []
             project_slug, resource_slug = resource.split('.', 1)
-            files = self.get_resource_files(resource)
+            files = self.get_resource_files(resource, xliff=xliff)
             slang = self.get_resource_option(resource, 'source_lang')
             sfile = self.get_source_file(resource)
             lang_map = self.get_resource_lang_mapping(resource)
@@ -624,6 +708,7 @@
                         files=[("%s;%s" % (resource_slug, slang),
                                 self.get_full_path(sfile)
                                 )],
+                        params=params,
                     )
                 except Exception as e:
                     if isinstance(e, SSLError) or not skip:
@@ -695,7 +780,8 @@
                             'push_translation', multipart=True, method='PUT',
                             files=[("%s;%s" % (resource_slug, remote_lang),
                                     self.get_full_path(local_file)
-                                    )], language=remote_lang
+                                    )], language=remote_lang,
+                            params=params,
                         )
                         logger.debug("Translation %s pushed." % remote_lang)
                     except utils.HttpNotFound:
@@ -818,17 +904,17 @@
                 raise
 
     def do_url_request(self, api_call, multipart=False, data=None,
-                       files=[], method="GET", skip_decode=False, **kwargs):
+                       files=[], method="GET", skip_decode=False,
+                       params={}, **kwargs):
         """Issues a url request."""
         # Read the credentials from the config file (.transifexrc)
         host = self.url_info['host']
+        username, passwd = self.getset_host_credentials(host)
         try:
-            username = self.txrc.get(host, 'username')
-            passwd = self.txrc.get(host, 'password')
             hostname = self.txrc.get(host, 'hostname')
         except configparser.NoSectionError:
             raise Exception(
-                "No user credentials found for host %s. Edit"
+                "No entry found for host %s. Edit"
                 " ~/.transifexrc and add the appropriate"
                 " info in there." % host
             )
@@ -838,6 +924,18 @@
         kwargs.update(self.url_info)
         url = API_URLS[api_call] % kwargs
 
+        # in case of GET we need to add xliff option as get parameter
+        if params and method == 'GET':
+            # update url params
+            # in case we need to add extra params on a url, we first get the
+            # already existing query, create a dict which will be merged with
+            # the extra params and finally put it back in the url
+            url_parts = list(urlparse.urlparse(url))
+            query = dict(urlparse.parse_qsl(url_parts[4]))
+            query.update(params)
+            url_parts[4] = urlencode(query)
+            url = urlparse.urlunparse(url_parts)
+
         if multipart:
             for info, filename in files:
                 # FIXME: It works because we only pass to files argument
@@ -848,6 +946,9 @@
                     "language": info.split(';')[1],
                     "uploaded_file": (name, open(filename, 'rb').read())
                 }
+                # in case of PUT we add xliff option as form data
+                if method == 'PUT':
+                    data.update(params)
         return utils.make_request(
             method, hostname, url, username, passwd, data,
             skip_decode=skip_decode
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/transifex-client-0.12.2/txclib/utils.py 
new/transifex-client-0.12.4/txclib/utils.py
--- old/transifex-client-0.12.2/txclib/utils.py 2016-08-09 16:39:55.000000000 
+0200
+++ new/transifex-client-0.12.4/txclib/utils.py 2017-02-06 17:05:52.000000000 
+0100
@@ -1,4 +1,5 @@
 from __future__ import unicode_literals
+import functools
 import os
 import sys
 import re
@@ -6,18 +7,19 @@
 import urllib3
 import collections
 import six
-import ssl
 
 try:
-    from json import loads as parse_json, dumps as compile_json
+    from json import loads as parse_json
 except ImportError:
-    from simplejson import loads as parse_json, dumps as compile_json
+    from simplejson import loads as parse_json
 
 from email.parser import Parser
 from urllib3.exceptions import SSLError
 from six.moves import input
 from txclib.urls import API_URLS
-from txclib.exceptions import UnknownCommandError, HttpNotFound, 
HttpNotAuthorized
+from txclib.exceptions import (
+    UnknownCommandError, HttpNotFound, HttpNotAuthorized
+)
 from txclib.paths import posix_path, native_path, posix_sep
 from txclib.web import user_agent_identifier, certs_file
 from txclib.log import logger
@@ -105,7 +107,7 @@
 
 
 def make_request(method, host, url, username, password, fields=None,
-                 skip_decode=False):
+                 skip_decode=False, get_params={}):
 
     # Initialize http and https pool managers
     num_pools = 1
@@ -153,7 +155,9 @@
     response = None
     try:
         manager = managers[scheme]
-        response = manager.request(
+        # All arguments must be bytes, not unicode
+        encoded_request = encode_args(manager.request)
+        response = encoded_request(
             method,
             host + url,
             headers=dict(headers),
@@ -183,7 +187,6 @@
 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.
     """
     url = API_URLS[api_call] % kwargs
@@ -200,7 +203,6 @@
 def valid_slug(slug):
     """
     Check if a slug contains only valid characters.
-    
     Valid chars include [-_\w]
     """
     try:
@@ -258,7 +260,6 @@
 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
@@ -266,10 +267,10 @@
     valid_yes = ['Y', 'y', 'Yes', 'yes', ]
     valid_no = ['N', 'n', 'No', 'no', ]
     if default:
-        prompt = prompt + '[Y/n]'
+        prompt = prompt + ' [Y/n]: '
         valid_yes.append('')
     else:
-        prompt = prompt + '[y/N]'
+        prompt = prompt + ' [y/N]: '
         valid_no.append('')
 
     ans = input(prompt)
@@ -294,7 +295,6 @@
     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")
     """
@@ -308,7 +308,6 @@
 def files_in_project(curpath):
     """
     Iterate over the files in the project.
-    
     Return each file under ``curpath`` with its absolute name.
     """
     visited = set()
@@ -333,3 +332,47 @@
         )
         for removal in removals:
             dirs.remove(removal)
+
+
+def encode_args(func):
+    # we have to patch func in order to make tests work.
+    # sadly mock does not have the attributes needed for functools
+    # so we need to set the manually
+    if not hasattr(func, '__name__'):
+        functools.update_wrapper(func, str.split, ('__name__', ))
+
+    @functools.wraps(func)
+    def decorated(*args, **kwargs):
+        new_args = _encode_anything(args)
+        new_kwargs = _encode_anything(kwargs, keys_as_unicode=True)
+        return func(*new_args, **new_kwargs)
+    return decorated
+
+
+def _encode_anything(thing, encoding='utf-8', keys_as_unicode=False):
+    # Handle python versions
+    if sys.version_info.major == 3:
+        return thing
+
+    if isinstance(thing, str):
+        return thing
+    elif isinstance(thing, unicode):
+        return thing.encode(encoding)
+    elif isinstance(thing, list):
+        return [_encode_anything(item) for item in thing]
+    elif isinstance(thing, tuple):
+        return tuple(_encode_anything(list(thing)))
+    elif isinstance(thing, dict):
+        # I know this is weird, but when using kwargs in python-3, the keys
+        # should be str, not bytes
+        if keys_as_unicode:
+            return {key: _encode_anything(value)
+                    for key, value in thing.items()}
+        else:
+            return {_encode_anything(key): _encode_anything(value)
+                    for key, value in thing.items()}
+    elif thing is None:
+        return thing
+    else:
+        raise TypeError("Could not encode, unknown type: {}".
+                        format(type(thing)))


Reply via email to