nextcloud-android/third_party/transifex-client/txclib/commands.py
2012-07-21 22:10:36 +02:00

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()