From d014e9b38b4fab4889f7a2f5e38f881e5b5abe1c Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Tue, 16 Feb 2016 11:25:00 -0800 Subject: [PATCH] Replaces gettext loc with custom Adds tool to extract strings Allows config file to specify locale --- .gitignore | 1 - .hgignore | 62 ------------------------- bin/extract-loc.py | 30 ++++++++++++ src/azure/cli/_argparse.py | 10 ++-- src/azure/cli/_locale.py | 30 ++++++++++++ src/azure/cli/_logging.py | 28 +++++------ src/azure/cli/locale/en-US/messages.txt | 60 ++++++++++++++++++++++++ src/azure/cli/main.py | 12 ++--- 8 files changed, 145 insertions(+), 88 deletions(-) delete mode 100644 .hgignore create mode 100644 bin/extract-loc.py create mode 100644 src/azure/cli/_locale.py create mode 100644 src/azure/cli/locale/en-US/messages.txt diff --git a/.gitignore b/.gitignore index 7107b3b14ec..864be54ace5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ env/ .ptvs/ # Build results -bin/ obj/ dist/ MANIFEST diff --git a/.hgignore b/.hgignore deleted file mode 100644 index f50f597da93..00000000000 --- a/.hgignore +++ /dev/null @@ -1,62 +0,0 @@ -syntax: glob -# Python cache -__pycache__/ -*.pyc - -# Virtual environment -env/ - -# PTVS analysis -.ptvs/ - -# Build results -bin/ -obj/ -dist/ -MANIFEST - -# Result of running python setup.py install/pip install -e -RECORD.txt -build/ -*.egg-info/ - -# Test results -TestResults/ - -# Credentials -credentials_real.json -testsettings_local.json -servicebus_settings_real.py -storage_settings_real.py -legacy_mgmt_settings_real.py -mgmt_settings_real.py -app_creds_real.py - -# User-specific files -*.suo -*.user -*.sln.docstates -.vs/ - -# Windows image file caches -Thumbs.db -ehthumbs.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Mac desktop service store files -.DS_Store - -.idea -src/build -*.iml -/doc/_build -/.vs/config/applicationhost.config - -# Azure deployment credentials -*.pubxml - diff --git a/bin/extract-loc.py b/bin/extract-loc.py new file mode 100644 index 00000000000..16df9469fae --- /dev/null +++ b/bin/extract-loc.py @@ -0,0 +1,30 @@ +#! /usr/bin/env python3 + +import os +import re +import subprocess +import sys + +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent / "src" / "azure" / "cli" +OUTPUT = ROOT / "locale" / "en-US" / "messages.txt" + +print('Extracting from:', ROOT) + +if not ROOT.is_dir(): + print("Failed to locate 'azure/cli'") + sys.exit(1) + +if not OUTPUT.parent.is_dir(): + os.makedirs(str(OUTPUT.parent)) + +with open(str(OUTPUT), 'w', encoding='utf-8-sig') as f_out: + for path in ROOT.rglob('*.py'): + with open(str(path), 'r', encoding='utf-8') as f: + content = f.read() + for m in re.finditer('[^\w_]_\(("(.+)"|\'(.+)\')\)', content): + print('# From', path, ':', m.span()[0], file=f_out) + print('KEY:', m.group(2) or m.group(3), file=f_out) + print(m.group(2) or m.group(3), file=f_out) + print(file=f_out) diff --git a/src/azure/cli/_argparse.py b/src/azure/cli/_argparse.py index 9a2482da021..46f041776b6 100644 --- a/src/azure/cli/_argparse.py +++ b/src/azure/cli/_argparse.py @@ -3,6 +3,7 @@ import os import sys +from ._locale import get_file as locale_get_file from ._logging import logging # Named arguments are prefixed with one of these strings @@ -63,15 +64,12 @@ class ArgumentParser(object): def __init__(self, prog): self.prog = prog self.noun_map = { - '$doc': 'azure-cli', + '$doc': 'azure-cli.txt', } self.help_args = { '--help', '-h' } self.complete_args = { '--complete' } self.global_args = { '--verbose', '--debug' } - self.doc_source = './' - self.doc_suffix = '.txt' - def add_command(self, handler, name=None, description=None, args=None): '''Registers a command that may be parsed by this parser. @@ -98,7 +96,7 @@ def add_command(self, handler, name=None, description=None, args=None): for n in nouns: full_name += n m = m.setdefault(n.lower(), { - '$doc': full_name + '$doc': full_name + ".txt" }) full_name += '.' m['$description'] = description or handler.__doc__ @@ -234,7 +232,7 @@ def _display_usage(self, nouns, noun_map, arguments, out=sys.stdout): print(' {0:<{1}} - {2}'.format(a, maxlen, d), file=out) print(file=out, flush=True) - doc_file = os.path.join(self.doc_source, noun_map['$doc'] + self.doc_suffix) + doc_file = locale_get_file(noun_map['$doc']) try: with open(doc_file, 'r') as f: print(f.read(), file=out, flush=True) diff --git a/src/azure/cli/_locale.py b/src/azure/cli/_locale.py new file mode 100644 index 00000000000..97faae764d4 --- /dev/null +++ b/src/azure/cli/_locale.py @@ -0,0 +1,30 @@ +import os.path + +from codecs import open + +def install(locale_dir): + mapping = [] + + with open(os.path.join(locale_dir, "messages.txt"), 'r', encoding='utf-8-sig') as f: + for i in f: + if not i or i.startswith('#') or not i.strip(): + continue + if i.startswith('KEY: '): + mapping.append((i[5:].strip(), None)) + else: + mapping[-1] = (mapping[-1][0], i.strip()) + + translations = dict(mapping) + def _(key): + return translations.get(key) or ''.format(key) + _.locale_dir = locale_dir + + __builtins__['_'] = _ + +def get_file(name): + try: + src = _.locale_dir + except (NameError, AttributeError): + raise RuntimeError("localizations not installed") + + return os.path.join(src, name) diff --git a/src/azure/cli/_logging.py b/src/azure/cli/_logging.py index a18c54bbda4..a3612cd7889 100644 --- a/src/azure/cli/_logging.py +++ b/src/azure/cli/_logging.py @@ -1,6 +1,8 @@ import logging as _logging import sys +__all__ = ['logging', 'configure_logging'] + _CODE_LEVEL = _logging.INFO + 1 class Logger(_logging.Logger): @@ -30,28 +32,26 @@ def _arg_name(arg): return a.lower() def configure_logging(argv, config): - # TODO: Configure logging handler to log messages to .py file - # Thinking here: - # INFO messages as Python code - # DEBUG messages (if -v) as comments - # WARNING/ERROR messages as clearly marked comments - - # Currently we just use the default format level = _logging.WARNING + + # Load logging info from config if config.get('verbose'): level = _logging.INFO if config.get('debug'): level = _logging.DEBUG + logfile = config.get('log') - logfile = None + # Load logging info from arguments + # Also remove any arguments consumed so that the parser does not + # have to explicitly ignore them. i = 0 while i < len(argv): arg = _arg_name(argv[i]) if arg in ('v', 'verbose'): - level = _logging.INFO + level = min(_logging.INFO, level) argv.pop(i) elif arg in ('debug',): - level = _logging.DEBUG + level = min(_logging.DEBUG, level) argv.pop(i) elif arg in ('log',): argv.pop(i) @@ -61,20 +61,22 @@ def configure_logging(argv, config): pass else: i += 1 - logging.setLevel(_logging.INFO) + # Configure the console output handler stderr_handler = _logging.StreamHandler(sys.stderr) stderr_handler.formatter = _logging.Formatter('%(levelname)s: %(message)s') - stderr_handler.level = level + logging.level = stderr_handler.level = level logging.handlers.append(stderr_handler) if logfile and logfile.lower().endswith('.py'): + # Configure a handler that logs code to a Python script py_handler = _logging.StreamHandler(open(logfile, 'w', encoding='utf-8')) py_handler.formatter = PyFileFormatter() py_handler.level = level if level == _logging.DEBUG else _logging.INFO logging.handlers.append(py_handler) elif logfile: + # Configure the handler that logs code to a text file log_handler = _logging.StreamHandler(open(logfile, 'w', encoding='utf-8')) log_handler.formatter = _logging.Formatter('[%(levelname)s:%(asctime)s] %(message)s') log_handler.level = level if level == _logging.DEBUG else _logging.INFO - logging.handlers.append(log_handler) \ No newline at end of file + logging.handlers.append(log_handler) diff --git a/src/azure/cli/locale/en-US/messages.txt b/src/azure/cli/locale/en-US/messages.txt new file mode 100644 index 00000000000..7c745152204 --- /dev/null +++ b/src/azure/cli/locale/en-US/messages.txt @@ -0,0 +1,60 @@ +# From D:\Repos\azure-cli\src\azure\cli\_argparse.py : 1208 +KEY: Argument {0} is required +Argument {0} is required + +# From D:\Repos\azure-cli\src\azure\cli\_argparse.py : 6848 +KEY: argument '{0}' does not take a value +argument '{0}' does not take a value + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1147 +KEY: user password or service principal secret, will prompt if not given. +user password or service principal secret, will prompt if not given. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1253 +KEY: If given, log in as a service principal rather than a user. +If given, log in as a service principal rather than a user. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1367 +KEY: A PEM encoded certificate private key file. +A PEM encoded certificate private key file. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1454 +KEY: A hex encoded thumbprint of the certificate. +A hex encoded thumbprint of the certificate. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1535 +KEY: Tenant domain or ID to log into. +Tenant domain or ID to log into. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1597 +KEY: do not prompt for confirmation of PII storage. +do not prompt for confirmation of PII storage. + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 1927 +KEY: Unknown environment {0} +Unknown environment {0} + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2091 +KEY: Tenant: +Tenant: + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2381 +KEY: Password: +Password: + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2883 +KEY: No subscriptions found for this account +No subscriptions found for this account + +# From D:\Repos\azure-cli\src\azure\cli\commands\login.py : 2991 +KEY: Setting subscription %s as default +Setting subscription %s as default + +# From D:\Repos\azure-cli\src\azure\cli\commands\storage.py : 268 +KEY: the resource group name +the resource group name + +# From D:\Repos\azure-cli\src\azure\cli\commands\storage.py : 332 +KEY: the subscription id +the subscription id + diff --git a/src/azure/cli/main.py b/src/azure/cli/main.py index f99512271fb..a090c8af4ae 100644 --- a/src/azure/cli/main.py +++ b/src/azure/cli/main.py @@ -1,9 +1,7 @@ -import gettext import os -gettext.install("az", os.path.join(os.path.abspath(__file__), '..', 'locale')) - from ._argparse import ArgumentParser +from ._locale import install as locale_install from ._logging import configure_logging, logging from ._session import Session @@ -13,6 +11,11 @@ # SESSION provides read-write session variables SESSION = Session() +# Load the user's preferred locale from their configuration +LOCALE = CONFIG.get('locale', 'en-US') +locale_install(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'locale', LOCALE)) + + def main(args): CONFIG.load(os.path.expanduser('~/az.json')) SESSION.load(os.path.expanduser('~/az.sess'), max_age=3600) @@ -22,9 +25,6 @@ def main(args): parser = ArgumentParser("az") import azure.cli.commands as commands - parser.doc_source = os.path.dirname(commands.__file__) - # TODO: detect language - parser.doc_suffix = '.en_US.txt' # Find the first noun on the command line and only load commands from that # module to improve startup time.