mirror of
https://github.com/nextcloud/android.git
synced 2024-12-18 06:51:55 +03:00
576 lines
21 KiB
Python
576 lines
21 KiB
Python
# -*- 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()
|