X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/d2f2500bddb2a3809cd8e96a64c091f331b8f274..adf87805bb2ffb1f297867dcee8dc21264209496:/third_party/transifex-client/txclib/commands.py?ds=sidebyside diff --git a/third_party/transifex-client/txclib/commands.py b/third_party/transifex-client/txclib/commands.py new file mode 100644 index 00000000..282287fc --- /dev/null +++ b/third_party/transifex-client/txclib/commands.py @@ -0,0 +1,576 @@ +# -*- coding: utf-8 -*- +""" +In this file we have all the top level commands for the transifex client. +Since we're using a way to automatically list them and execute them, when +adding code to this file you must take care of the following: + * Added functions must begin with 'cmd_' followed by the actual name of the + command being used in the command line (eg cmd_init) + * The description for each function that we display to the user is read from + the func_doc attribute which reads the doc string. So, when adding + docstring to a new function make sure you add an oneliner which is + descriptive and is meant to be seen by the user. + * When including libraries, it's best if you include modules instead of + functions because that way our function resolution will work faster and the + chances of overlapping are minimal + * All functions should use the OptionParser and should have a usage and + descripition field. +""" +import os +import re, shutil +import sys +from optparse import OptionParser, OptionGroup +import ConfigParser + + +from txclib import utils, project +from txclib.utils import parse_json, compile_json, relpath +from txclib.config import OrderedRawConfigParser +from txclib.exceptions import UnInitializedError +from txclib.parsers import delete_parser, help_parser, parse_csv_option, \ + status_parser, pull_parser, set_parser, push_parser, init_parser +from txclib.log import logger + + +def cmd_init(argv, path_to_tx): + "Initialize a new transifex project." + parser = init_parser() + (options, args) = parser.parse_args(argv) + if len(args) > 1: + parser.error("Too many arguments were provided. Aborting...") + if args: + path_to_tx = args[0] + else: + path_to_tx = os.getcwd() + + if os.path.isdir(os.path.join(path_to_tx,".tx")): + logger.info("tx: There is already a tx folder!") + reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") + while (reinit != 'y' and reinit != 'Y' and reinit != 'N' and reinit != 'n' and reinit != ''): + reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") + if not reinit or reinit in ['N', 'n', 'NO', 'no', 'No']: + return + # Clean the old settings + # FIXME: take a backup + else: + rm_dir = os.path.join(path_to_tx, ".tx") + shutil.rmtree(rm_dir) + + logger.info("Creating .tx folder...") + os.mkdir(os.path.join(path_to_tx,".tx")) + + # Handle the credentials through transifexrc + home = os.path.expanduser("~") + txrc = os.path.join(home, ".transifexrc") + config = OrderedRawConfigParser() + + default_transifex = "https://www.transifex.com" + transifex_host = options.host or raw_input("Transifex instance [%s]: " % default_transifex) + + if not transifex_host: + transifex_host = default_transifex + if not transifex_host.startswith(('http://', 'https://')): + transifex_host = 'https://' + transifex_host + + config_file = os.path.join(path_to_tx, ".tx", "config") + if not os.path.exists(config_file): + # The path to the config file (.tx/config) + logger.info("Creating skeleton...") + config = OrderedRawConfigParser() + config.add_section('main') + config.set('main', 'host', transifex_host) + # Touch the file if it doesn't exist + logger.info("Creating config file...") + fh = open(config_file, 'w') + config.write(fh) + fh.close() + + prj = project.Project(path_to_tx) + prj.getset_host_credentials(transifex_host, user=options.user, + password=options.password) + prj.save() + logger.info("Done.") + + +def cmd_set(argv, path_to_tx): + "Add local or remote files under transifex" + parser = set_parser() + (options, args) = parser.parse_args(argv) + + # Implement options/args checks + # TODO !!!!!!! + if options.local: + try: + expression = args[0] + except IndexError: + parser.error("Please specify an expression.") + if not options.resource: + parser.error("Please specify a resource") + if not options.source_language: + parser.error("Please specify a source language.") + if not '' in expression: + parser.error("The expression you have provided is not valid.") + if not utils.valid_slug(options.resource): + parser.error("Invalid resource slug. The format is "\ + ". and the valid characters include [_-\w].") + _auto_local(path_to_tx, options.resource, + source_language=options.source_language, + expression = expression, source_file=options.source_file, + execute=options.execute, regex=False) + if options.execute: + _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) + _set_mode(options.resource, options.mode, path_to_tx) + _set_type(options.resource, options.i18n_type, path_to_tx) + return + + if options.remote: + try: + url = args[0] + except IndexError: + parser.error("Please specify an remote url") + _auto_remote(path_to_tx, url) + _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) + _set_mode(options.resource, options.mode, path_to_tx) + return + + if options.is_source: + resource = options.resource + if not resource: + parser.error("You must specify a resource name with the" + " -r|--resource flag.") + + lang = options.language + if not lang: + parser.error("Please specify a source language.") + + if len(args) != 1: + parser.error("Please specify a file.") + + if not utils.valid_slug(resource): + parser.error("Invalid resource slug. The format is "\ + ". and the valid characters include [_-\w].") + + file = args[0] + # Calculate relative path + path_to_file = relpath(file, path_to_tx) + _set_source_file(path_to_tx, resource, options.language, path_to_file) + elif options.resource or options.language: + resource = options.resource + lang = options.language + + if len(args) != 1: + parser.error("Please specify a file") + + # Calculate relative path + path_to_file = relpath(args[0], path_to_tx) + + try: + _go_to_dir(path_to_tx) + except UnInitializedError, e: + utils.logger.error(e) + return + + if not utils.valid_slug(resource): + parser.error("Invalid resource slug. The format is "\ + ". and the valid characters include [_-\w].") + _set_translation(path_to_tx, resource, lang, path_to_file) + + _set_mode(options.resource, options.mode, path_to_tx) + _set_type(options.resource, options.i18n_type, path_to_tx) + _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) + + logger.info("Done.") + return + + +def _auto_local(path_to_tx, resource, source_language, expression, execute=False, + source_file=None, regex=False): + """Auto configure local project.""" + # The path everything will be relative to + curpath = os.path.abspath(os.curdir) + + # Force expr to be a valid regex expr (escaped) but keep intact + expr_re = utils.regex_from_filefilter(expression, curpath) + expr_rec = re.compile(expr_re) + + if not execute: + logger.info("Only printing the commands which will be run if the " + "--execute switch is specified.") + + # First, let's construct a dictionary of all matching files. + # Note: Only the last matching file of a language will be stored. + translation_files = {} + for root, dirs, files in os.walk(curpath): + for f in files: + f_path = os.path.abspath(os.path.join(root, f)) + match = expr_rec.match(f_path) + if match: + lang = match.group(1) + f_path = os.path.abspath(f_path) + if lang == source_language and not source_file: + source_file = f_path + else: + translation_files[lang] = f_path + + if not source_file: + raise Exception("Could not find a source language file. Please run" + " set --source manually and then re-run this command or provide" + " the source file with the -s flag.") + if execute: + logger.info("Updating source for resource %s ( %s -> %s )." % (resource, + source_language, relpath(source_file, path_to_tx))) + _set_source_file(path_to_tx, resource, source_language, + relpath(source_file, path_to_tx)) + else: + logger.info('\ntx set --source -r %(res)s -l %(lang)s %(file)s\n' % { + 'res': resource, + 'lang': source_language, + 'file': relpath(source_file, curpath)}) + + prj = project.Project(path_to_tx) + root_dir = os.path.abspath(path_to_tx) + + if execute: + try: + prj.config.get("%s" % resource, "source_file") + except ConfigParser.NoSectionError: + raise Exception("No resource with slug \"%s\" was found.\nRun 'tx set --auto" + "-local -r %s \"expression\"' to do the initial configuration." % resource) + + # Now let's handle the translation files. + if execute: + logger.info("Updating file expression for resource %s ( %s )." % (resource, + expression)) + # Eval file_filter relative to root dir + file_filter = relpath(os.path.join(curpath, expression), + path_to_tx) + prj.config.set("%s" % resource, "file_filter", file_filter) + else: + for (lang, f_path) in sorted(translation_files.items()): + logger.info('tx set -r %(res)s -l %(lang)s %(file)s' % { + 'res': resource, + 'lang': lang, + 'file': relpath(f_path, curpath)}) + + if execute: + prj.save() + + +def _auto_remote(path_to_tx, url): + """ + Initialize a remote release/project/resource to the current directory. + """ + logger.info("Auto configuring local project from remote URL...") + + type, vars = utils.parse_tx_url(url) + prj = project.Project(path_to_tx) + username, password = prj.getset_host_credentials(vars['hostname']) + + if type == 'project': + logger.info("Getting details for project %s" % vars['project']) + proj_info = utils.get_details('project_details', + username, password, + hostname = vars['hostname'], project = vars['project']) + resources = [ '.'.join([vars['project'], r['slug']]) for r in proj_info['resources'] ] + logger.info("%s resources found. Configuring..." % len(resources)) + elif type == 'release': + logger.info("Getting details for release %s" % vars['release']) + rel_info = utils.get_details('release_details', + username, password, hostname = vars['hostname'], + project = vars['project'], release = vars['release']) + resources = [] + for r in rel_info['resources']: + if r.has_key('project'): + resources.append('.'.join([r['project']['slug'], r['slug']])) + else: + resources.append('.'.join([vars['project'], r['slug']])) + logger.info("%s resources found. Configuring..." % len(resources)) + elif type == 'resource': + logger.info("Getting details for resource %s" % vars['resource']) + resources = [ '.'.join([vars['project'], vars['resource']]) ] + else: + raise("Url '%s' is not recognized." % url) + + for resource in resources: + logger.info("Configuring resource %s." % resource) + proj, res = resource.split('.') + res_info = utils.get_details('resource_details', + username, password, hostname = vars['hostname'], + project = proj, resource=res) + try: + source_lang = res_info['source_language_code'] + i18n_type = res_info['i18n_type'] + except KeyError: + raise Exception("Remote server seems to be running an unsupported version" + " of Transifex. Either update your server software of fallback" + " to a previous version of transifex-client.") + prj.set_remote_resource( + resource=resource, + host = vars['hostname'], + source_lang = source_lang, + i18n_type = i18n_type) + + prj.save() + + +def cmd_push(argv, path_to_tx): + "Push local files to remote server" + parser = push_parser() + (options, args) = parser.parse_args(argv) + force_creation = options.force_creation + languages = parse_csv_option(options.languages) + resources = parse_csv_option(options.resources) + skip = options.skip_errors + prj = project.Project(path_to_tx) + if not (options.push_source or options.push_translations): + parser.error("You need to specify at least one of the -s|--source," + " -t|--translations flags with the push command.") + + prj.push( + force=force_creation, resources=resources, languages=languages, + skip=skip, source=options.push_source, + translations=options.push_translations, + no_interactive=options.no_interactive + ) + logger.info("Done.") + + +def cmd_pull(argv, path_to_tx): + "Pull files from remote server to local repository" + parser = pull_parser() + (options, args) = parser.parse_args(argv) + if options.fetchall and options.languages: + parser.error("You can't user a language filter along with the"\ + " -a|--all option") + languages = parse_csv_option(options.languages) + resources = parse_csv_option(options.resources) + skip = options.skip_errors + minimum_perc = options.minimum_perc or None + + try: + _go_to_dir(path_to_tx) + except UnInitializedError, e: + utils.logger.error(e) + return + + # instantiate the project.Project + prj = project.Project(path_to_tx) + prj.pull( + languages=languages, resources=resources, overwrite=options.overwrite, + fetchall=options.fetchall, fetchsource=options.fetchsource, + force=options.force, skip=skip, minimum_perc=minimum_perc, + mode=options.mode + ) + logger.info("Done.") + + +def _set_source_file(path_to_tx, resource, lang, path_to_file): + """Reusable method to set source file.""" + proj, res = resource.split('.') + if not proj or not res: + raise Exception("\"%s.%s\" is not a valid resource identifier. It should" + " be in the following format project_slug.resource_slug." % + (proj, res)) + if not lang: + raise Exception("You haven't specified a source language.") + + try: + _go_to_dir(path_to_tx) + except UnInitializedError, e: + utils.logger.error(e) + return + + if not os.path.exists(path_to_file): + raise Exception("tx: File ( %s ) does not exist." % + os.path.join(path_to_tx, path_to_file)) + + # instantiate the project.Project + prj = project.Project(path_to_tx) + root_dir = os.path.abspath(path_to_tx) + + if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): + raise Exception("File must be under the project root directory.") + + logger.info("Setting source file for resource %s.%s ( %s -> %s )." % ( + proj, res, lang, path_to_file)) + + path_to_file = relpath(path_to_file, root_dir) + + prj = project.Project(path_to_tx) + + # FIXME: Check also if the path to source file already exists. + try: + try: + prj.config.get("%s.%s" % (proj, res), "source_file") + except ConfigParser.NoSectionError: + prj.config.add_section("%s.%s" % (proj, res)) + except ConfigParser.NoOptionError: + pass + finally: + prj.config.set("%s.%s" % (proj, res), "source_file", + path_to_file) + prj.config.set("%s.%s" % (proj, res), "source_lang", + lang) + + prj.save() + + +def _set_translation(path_to_tx, resource, lang, path_to_file): + """Reusable method to set translation file.""" + + proj, res = resource.split('.') + if not project or not resource: + raise Exception("\"%s\" is not a valid resource identifier. It should" + " be in the following format project_slug.resource_slug." % + resource) + + try: + _go_to_dir(path_to_tx) + except UnInitializedError, e: + utils.logger.error(e) + return + + # Warn the user if the file doesn't exist + if not os.path.exists(path_to_file): + logger.info("Warning: File '%s' doesn't exist." % path_to_file) + + # instantiate the project.Project + prj = project.Project(path_to_tx) + root_dir = os.path.abspath(path_to_tx) + + if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): + raise Exception("File must be under the project root directory.") + + if lang == prj.config.get("%s.%s" % (proj, res), "source_lang"): + raise Exception("tx: You cannot set translation file for the source language." + " Source languages contain the strings which will be translated!") + + logger.info("Updating translations for resource %s ( %s -> %s )." % (resource, + lang, path_to_file)) + path_to_file = relpath(path_to_file, root_dir) + prj.config.set("%s.%s" % (proj, res), "trans.%s" % lang, + path_to_file) + + prj.save() + + +def cmd_status(argv, path_to_tx): + "Print status of current project" + parser = status_parser() + (options, args) = parser.parse_args(argv) + resources = parse_csv_option(options.resources) + prj = project.Project(path_to_tx) + resources = prj.get_chosen_resources(resources) + resources_num = len(resources) + for idx, res in enumerate(resources): + p, r = res.split('.') + logger.info("%s -> %s (%s of %s)" % (p, r, idx + 1, resources_num)) + logger.info("Translation Files:") + slang = prj.get_resource_option(res, 'source_lang') + sfile = prj.get_resource_option(res, 'source_file') or "N/A" + lang_map = prj.get_resource_lang_mapping(res) + logger.info(" - %s: %s (%s)" % (utils.color_text(slang, "RED"), + sfile, utils.color_text("source", "YELLOW"))) + files = prj.get_resource_files(res) + fkeys = files.keys() + fkeys.sort() + for lang in fkeys: + local_lang = lang + if lang in lang_map.values(): + local_lang = lang_map.flip[lang] + logger.info(" - %s: %s" % (utils.color_text(local_lang, "RED"), + files[lang])) + logger.info("") + + +def cmd_help(argv, path_to_tx): + """List all available commands""" + parser = help_parser() + (options, args) = parser.parse_args(argv) + if len(args) > 1: + parser.error("Multiple arguments received. Exiting...") + + # Get all commands + fns = utils.discover_commands() + + # Print help for specific command + if len(args) == 1: + try: + fns[argv[0]](['--help'], path_to_tx) + except KeyError: + utils.logger.error("Command %s not found" % argv[0]) + # or print summary of all commands + + # the code below will only be executed if the KeyError exception is thrown + # becuase in all other cases the function called with --help will exit + # instead of return here + keys = fns.keys() + keys.sort() + + logger.info("Transifex command line client.\n") + logger.info("Available commands are:") + for key in keys: + logger.info(" %-15s\t%s" % (key, fns[key].func_doc)) + logger.info("\nFor more information run %s command --help" % sys.argv[0]) + + +def cmd_delete(argv, path_to_tx): + "Delete an accessible resource or translation in a remote server." + parser = delete_parser() + (options, args) = parser.parse_args(argv) + languages = parse_csv_option(options.languages) + resources = parse_csv_option(options.resources) + skip = options.skip_errors + force = options.force_delete + prj = project.Project(path_to_tx) + prj.delete(resources, languages, skip, force) + logger.info("Done.") + + +def _go_to_dir(path): + """Change the current working directory to the directory specified as + argument. + + Args: + path: The path to chdor to. + Raises: + UnInitializedError, in case the directory has not been initialized. + """ + if path is None: + raise UnInitializedError( + "Directory has not been initialzied. " + "Did you forget to run 'tx init' first?" + ) + os.chdir(path) + + +def _set_minimum_perc(resource, value, path_to_tx): + """Set the minimum percentage in the .tx/config file.""" + args = (resource, 'minimum_perc', value, path_to_tx, 'set_min_perc') + _set_project_option(*args) + + +def _set_mode(resource, value, path_to_tx): + """Set the mode in the .tx/config file.""" + args = (resource, 'mode', value, path_to_tx, 'set_default_mode') + _set_project_option(*args) + + +def _set_type(resource, value, path_to_tx): + """Set the i18n type in the .tx/config file.""" + args = (resource, 'type', value, path_to_tx, 'set_i18n_type') + _set_project_option(*args) + + +def _set_project_option(resource, name, value, path_to_tx, func_name): + """Save the option to the project config file.""" + if value is None: + return + if not resource: + logger.debug("Setting the %s for all resources." % name) + resources = [] + else: + logger.debug("Setting the %s for resource %s." % (name, resource)) + resources = [resource, ] + prj = project.Project(path_to_tx) + getattr(prj, func_name)(resources, value) + prj.save()