mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
Bug 1393590 - [mach] Use description field for settings instead of gettext locales, r=gps
This preserves ./mach settings' --list option. If --list is passed in, we call splitlines() on the description and print only the first line. The full multi-line description will be printed otherwise. This also displays the type and/or choices of the option as appropriate. MozReview-Commit-ID: 7UMsN9qslWt --HG-- extra : rebase_source : 4bc9554d8652e02e290c6a190634f1a72cdbadc3
This commit is contained in:
parent
4cd937870c
commit
508e9aad6b
@ -43,8 +43,8 @@ decorator in an existing mach command module. E.g:
|
||||
@SettingsProvider
|
||||
class ArbitraryClassName(object):
|
||||
config_settings = [
|
||||
('foo.bar', 'string'),
|
||||
('foo.baz', 'int', 0, set([0,1,2])),
|
||||
('foo.bar', 'string', "A helpful description"),
|
||||
('foo.baz', 'int', "Another description", 0, {'choices': set([0,1,2])}),
|
||||
]
|
||||
|
||||
``@SettingsProvider``'s must specify a variable called ``config_settings``
|
||||
@ -55,11 +55,15 @@ Each tuple is of the form:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
('<section>.<option>', '<type>', default, extra)
|
||||
('<section>.<option>', '<type>', '<description>', default, extra)
|
||||
|
||||
``type`` is a string and can be one of:
|
||||
string, boolean, int, pos_int, path
|
||||
|
||||
``description`` is a string explaining how to define the settings and
|
||||
where they get used. Descriptions should ideally be multi-line paragraphs
|
||||
where the first line acts as a short description.
|
||||
|
||||
``default`` is optional, and provides a default value in case none was
|
||||
specified by any of the configuration files.
|
||||
|
||||
@ -77,7 +81,7 @@ as the option name. For example:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
('foo.*', 'string')
|
||||
('foo.*', 'string', 'desc')
|
||||
|
||||
This allows configuration files like this:
|
||||
|
||||
@ -88,20 +92,16 @@ This allows configuration files like this:
|
||||
arbitrary2 = some other string
|
||||
|
||||
|
||||
Documenting Settings
|
||||
====================
|
||||
Finding Settings
|
||||
================
|
||||
|
||||
All settings must at least be documented in the en_US locale. Otherwise,
|
||||
running ``mach settings`` will raise. Mach uses gettext to perform localization.
|
||||
|
||||
A handy command exists to generate the localization files:
|
||||
You can see which settings are available as well as their description and
|
||||
expected values by running:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
mach settings locale-gen <section>
|
||||
|
||||
You'll be prompted to add documentation for all options in section with the
|
||||
en_US locale. To add documentation in another locale, pass in ``--locale``.
|
||||
./mach settings # or
|
||||
./mach settings --list
|
||||
|
||||
|
||||
Accessing Settings
|
||||
@ -122,9 +122,9 @@ For example:
|
||||
@SettingsProvider
|
||||
class ExampleSettings(object):
|
||||
config_settings = [
|
||||
('a.b', 'string', 'default'),
|
||||
('foo.bar', 'string'),
|
||||
('foo.baz', 'int', 0, {'choices': set([0,1,2])}),
|
||||
('a.b', 'string', 'desc', 'default'),
|
||||
('foo.bar', 'string', 'desc',),
|
||||
('foo.baz', 'int', 'desc', 0, {'choices': set([0,1,2])}),
|
||||
]
|
||||
|
||||
@CommandProvider
|
||||
|
@ -4,23 +4,15 @@
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
from textwrap import TextWrapper
|
||||
|
||||
from mach.config import TYPE_CLASSES
|
||||
from mach.decorators import (
|
||||
CommandArgument,
|
||||
CommandProvider,
|
||||
Command,
|
||||
SubCommand,
|
||||
)
|
||||
|
||||
POLIB_NOT_FOUND = """
|
||||
Could not detect the 'polib' package on the local system.
|
||||
Please run:
|
||||
|
||||
pip install polib
|
||||
""".lstrip()
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class Settings(object):
|
||||
@ -39,94 +31,27 @@ class Settings(object):
|
||||
help='Show settings in a concise list')
|
||||
def settings(self, short=None):
|
||||
"""List available settings."""
|
||||
if short:
|
||||
for section in sorted(self._settings):
|
||||
for option in sorted(self._settings[section]._settings):
|
||||
short, full = self._settings.option_help(section, option)
|
||||
print('%s.%s -- %s' % (section, option, short))
|
||||
return
|
||||
|
||||
types = {v: k for k, v in TYPE_CLASSES.items()}
|
||||
wrapper = TextWrapper(initial_indent='# ', subsequent_indent='# ')
|
||||
for section in sorted(self._settings):
|
||||
print('[%s]' % section)
|
||||
for i, section in enumerate(sorted(self._settings)):
|
||||
if not short:
|
||||
print('%s[%s]' % ('' if i == 0 else '\n', section))
|
||||
|
||||
for option in sorted(self._settings[section]._settings):
|
||||
short, full = self._settings.option_help(section, option)
|
||||
print(wrapper.fill(full))
|
||||
meta = self._settings[section].get_meta(option)
|
||||
desc = meta['description']
|
||||
|
||||
if option != '*':
|
||||
print(';%s =' % option)
|
||||
print('')
|
||||
if short:
|
||||
print('%s.%s -- %s' % (section, option, desc.splitlines()[0]))
|
||||
continue
|
||||
|
||||
@SubCommand('settings', 'locale-gen',
|
||||
description='Generate or update gettext .po and .mo locale files.')
|
||||
@CommandArgument('sections', nargs='*',
|
||||
help='A list of strings in the form of either <section> or '
|
||||
'<section>.<option> to translate. By default, prompt to '
|
||||
'translate all applicable options.')
|
||||
@CommandArgument('--locale', default='en_US',
|
||||
help='Locale to generate, defaults to en_US.')
|
||||
@CommandArgument('--overwrite', action='store_true', default=False,
|
||||
help='Overwrite pre-existing entries in .po files.')
|
||||
def locale_gen(self, sections, **kwargs):
|
||||
try:
|
||||
import polib
|
||||
except ImportError:
|
||||
print(POLIB_NOT_FOUND)
|
||||
return 1
|
||||
if option == '*':
|
||||
option = '<option>'
|
||||
|
||||
self.was_prompted = False
|
||||
if 'choices' in meta:
|
||||
value = "{%s}" % ', '.join(meta['choices'])
|
||||
else:
|
||||
value = '<%s>' % types[meta['type_cls']]
|
||||
|
||||
sections = sections or self._settings
|
||||
for section in sections:
|
||||
self._gen_section(section, **kwargs)
|
||||
|
||||
if not self.was_prompted:
|
||||
print("All specified options already have an {} translation. "
|
||||
"To overwrite existing translations, pass --overwrite."
|
||||
.format(kwargs['locale']))
|
||||
|
||||
def _gen_section(self, section, **kwargs):
|
||||
if '.' in section:
|
||||
section, option = section.split('.')
|
||||
return self._gen_option(section, option, **kwargs)
|
||||
|
||||
for option in sorted(self._settings[section]._settings):
|
||||
self._gen_option(section, option, **kwargs)
|
||||
|
||||
def _gen_option(self, section, option, locale, overwrite):
|
||||
import polib
|
||||
|
||||
meta = self._settings[section]._settings[option]
|
||||
|
||||
localedir = os.path.join(meta['localedir'], locale, 'LC_MESSAGES')
|
||||
if not os.path.isdir(localedir):
|
||||
os.makedirs(localedir)
|
||||
|
||||
path = os.path.join(localedir, '{}.po'.format(section))
|
||||
if os.path.isfile(path):
|
||||
po = polib.pofile(path)
|
||||
else:
|
||||
po = polib.POFile()
|
||||
|
||||
optionid = '{}.{}'.format(section, option)
|
||||
for name in ('short', 'full'):
|
||||
msgid = '{}.{}'.format(optionid, name)
|
||||
entry = po.find(msgid)
|
||||
if not entry:
|
||||
entry = polib.POEntry(msgid=msgid)
|
||||
po.append(entry)
|
||||
|
||||
if entry in po.translated_entries() and not overwrite:
|
||||
continue
|
||||
|
||||
self.was_prompted = True
|
||||
|
||||
msgstr = raw_input("Translate {} to {}:\n"
|
||||
.format(msgid, locale))
|
||||
entry.msgstr = msgstr
|
||||
|
||||
if self.was_prompted:
|
||||
mopath = os.path.join(localedir, '{}.mo'.format(section))
|
||||
po.save(path)
|
||||
po.save_as_mofile(mopath)
|
||||
print(wrapper.fill(desc))
|
||||
print(';%s=%s' % (option, value))
|
||||
|
@ -12,22 +12,11 @@ them. This metadata includes type, default value, valid values, etc.
|
||||
The main interface to config data is the ConfigSettings class. 1 or more
|
||||
ConfigProvider classes are associated with ConfigSettings and define what
|
||||
settings are available.
|
||||
|
||||
Descriptions of individual config options can be translated to multiple
|
||||
languages using gettext. Each option has associated with it a domain and locale
|
||||
directory. By default, the domain is the section the option is in and the
|
||||
locale directory is the "locale" directory beneath the directory containing the
|
||||
module that defines it.
|
||||
|
||||
People implementing ConfigProvider instances are expected to define a complete
|
||||
gettext .po and .mo file for the en_US locale. The |mach settings locale-gen|
|
||||
command can be used to populate these files.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import collections
|
||||
import gettext
|
||||
import os
|
||||
import sys
|
||||
from functools import wraps
|
||||
@ -40,14 +29,6 @@ else:
|
||||
str_type = basestring
|
||||
|
||||
|
||||
TRANSLATION_NOT_FOUND = """
|
||||
No translation files detected for {section}, there must at least be a
|
||||
translation for the 'en_US' locale. To generate these files, run:
|
||||
|
||||
mach settings locale-gen {section}
|
||||
""".lstrip()
|
||||
|
||||
|
||||
class ConfigException(Exception):
|
||||
pass
|
||||
|
||||
@ -336,7 +317,7 @@ class ConfigSettings(collections.Mapping):
|
||||
self._config.write(fh)
|
||||
|
||||
@classmethod
|
||||
def _format_metadata(cls, provider, section, option, type_cls,
|
||||
def _format_metadata(cls, provider, section, option, type_cls, description,
|
||||
default=DefaultValue, extra=None):
|
||||
"""Formats and returns the metadata for a setting.
|
||||
|
||||
@ -350,6 +331,9 @@ class ConfigSettings(collections.Mapping):
|
||||
|
||||
type -- a ConfigType-derived type defining the type of the setting.
|
||||
|
||||
description -- str describing how to use the setting and where it
|
||||
applies.
|
||||
|
||||
Each setting has the following optional parameters:
|
||||
|
||||
default -- The default value for the setting. If None (the default)
|
||||
@ -362,11 +346,8 @@ class ConfigSettings(collections.Mapping):
|
||||
type_cls = TYPE_CLASSES[type_cls]
|
||||
|
||||
meta = {
|
||||
'short': '%s.short' % option,
|
||||
'full': '%s.full' % option,
|
||||
'description': description,
|
||||
'type_cls': type_cls,
|
||||
'domain': section,
|
||||
'localedir': provider.config_settings_locale_directory,
|
||||
}
|
||||
|
||||
if default != DefaultValue:
|
||||
@ -410,25 +391,6 @@ class ConfigSettings(collections.Mapping):
|
||||
|
||||
self._settings[section_name] = section
|
||||
|
||||
def option_help(self, section, option):
|
||||
"""Obtain the translated help messages for an option."""
|
||||
|
||||
meta = self[section].get_meta(option)
|
||||
|
||||
# Providers should always have an en_US translation. If they don't,
|
||||
# they are coded wrong and this will raise.
|
||||
default = gettext.translation(meta['domain'], meta['localedir'],
|
||||
['en_US'])
|
||||
|
||||
t = gettext.translation(meta['domain'], meta['localedir'],
|
||||
fallback=True)
|
||||
t.add_fallback(default)
|
||||
|
||||
short = t.ugettext('%s.%s.short' % (section, option))
|
||||
full = t.ugettext('%s.%s.full' % (section, option))
|
||||
|
||||
return (short, full)
|
||||
|
||||
def _finalize(self):
|
||||
if self._finalized:
|
||||
return
|
||||
|
@ -338,13 +338,9 @@ def SettingsProvider(cls):
|
||||
raise MachError('@SettingsProvider must contain a config_settings attribute. It '
|
||||
'may either be a list of tuples, or a callable that returns a list '
|
||||
'of tuples. Each tuple must be of the form:\n'
|
||||
'(<section>.<option>, <type_cls>, <default>, <choices>)\n'
|
||||
'(<section>.<option>, <type_cls>, <description>, <default>, <choices>)\n'
|
||||
'as specified by ConfigSettings._format_metadata.')
|
||||
|
||||
if not hasattr(cls, 'config_settings_locale_directory'):
|
||||
cls_dir = os.path.dirname(inspect.getfile(cls))
|
||||
cls.config_settings_locale_directory = os.path.join(cls_dir, 'locale')
|
||||
|
||||
Registrar.register_settings_provider(cls)
|
||||
return cls
|
||||
|
||||
|
@ -22,7 +22,11 @@ from .decorators import SettingsProvider
|
||||
@SettingsProvider
|
||||
class DispatchSettings():
|
||||
config_settings = [
|
||||
('alias.*', 'string'),
|
||||
('alias.*', 'string', """
|
||||
Create a command alias of the form `<alias>=<command> <args>`.
|
||||
Aliases can also be used to set default arguments:
|
||||
<command>=<command> <args>
|
||||
""".strip()),
|
||||
]
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -1,9 +0,0 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid "alias.*.short"
|
||||
msgstr "Create a command alias"
|
||||
|
||||
msgid "alias.*.full"
|
||||
msgstr "Create a command alias of the form `<alias> = <command> <args>`."
|
@ -43,27 +43,27 @@ bar = value2
|
||||
@SettingsProvider
|
||||
class Provider1(object):
|
||||
config_settings = [
|
||||
('foo.bar', StringType),
|
||||
('foo.baz', PathType),
|
||||
('foo.bar', StringType, 'desc'),
|
||||
('foo.baz', PathType, 'desc'),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class ProviderDuplicate(object):
|
||||
config_settings = [
|
||||
('dupesect.foo', StringType),
|
||||
('dupesect.foo', StringType),
|
||||
('dupesect.foo', StringType, 'desc'),
|
||||
('dupesect.foo', StringType, 'desc'),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class Provider2(object):
|
||||
config_settings = [
|
||||
('a.string', StringType),
|
||||
('a.boolean', BooleanType),
|
||||
('a.pos_int', PositiveIntegerType),
|
||||
('a.int', IntegerType),
|
||||
('a.path', PathType),
|
||||
('a.string', StringType, 'desc'),
|
||||
('a.boolean', BooleanType, 'desc'),
|
||||
('a.pos_int', PositiveIntegerType, 'desc'),
|
||||
('a.int', IntegerType, 'desc'),
|
||||
('a.path', PathType, 'desc'),
|
||||
]
|
||||
|
||||
|
||||
@ -72,27 +72,27 @@ class Provider3(object):
|
||||
@classmethod
|
||||
def config_settings(cls):
|
||||
return [
|
||||
('a.string', 'string'),
|
||||
('a.boolean', 'boolean'),
|
||||
('a.pos_int', 'pos_int'),
|
||||
('a.int', 'int'),
|
||||
('a.path', 'path'),
|
||||
('a.string', 'string', 'desc'),
|
||||
('a.boolean', 'boolean', 'desc'),
|
||||
('a.pos_int', 'pos_int', 'desc'),
|
||||
('a.int', 'int', 'desc'),
|
||||
('a.path', 'path', 'desc'),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class Provider4(object):
|
||||
config_settings = [
|
||||
('foo.abc', StringType, 'a', {'choices': set('abc')}),
|
||||
('foo.xyz', StringType, 'w', {'choices': set('xyz')}),
|
||||
('foo.abc', StringType, 'desc', 'a', {'choices': set('abc')}),
|
||||
('foo.xyz', StringType, 'desc', 'w', {'choices': set('xyz')}),
|
||||
]
|
||||
|
||||
|
||||
@SettingsProvider
|
||||
class Provider5(object):
|
||||
config_settings = [
|
||||
('foo.*', 'string'),
|
||||
('foo.bar', 'string'),
|
||||
('foo.*', 'string', 'desc'),
|
||||
('foo.bar', 'string', 'desc'),
|
||||
]
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -1,8 +0,0 @@
|
||||
msgid "build.threads.short"
|
||||
msgstr "Thread Count"
|
||||
|
||||
msgid "build.threads.full"
|
||||
msgstr "The number of threads to use when performing CPU intensive tasks. "
|
||||
"This constrols the level of parallelization. The default value is "
|
||||
"the number of cores in your machine."
|
||||
|
Binary file not shown.
@ -1,10 +0,0 @@
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
msgid "runprefs.*.short"
|
||||
msgstr "Pass a pref into Firefox when using `mach run`"
|
||||
|
||||
msgid "runprefs.*.full"
|
||||
msgstr ""
|
||||
"Pass a pref into Firefox when using `mach run`, of the form foo.bar=value"
|
@ -1279,7 +1279,11 @@ class Install(MachCommandBase):
|
||||
@SettingsProvider
|
||||
class RunSettings():
|
||||
config_settings = [
|
||||
('runprefs.*', 'string'),
|
||||
('runprefs.*', 'string', """
|
||||
Pass a pref into Firefox when using `mach run`, of the form `foo.bar=value`.
|
||||
Prefs will automatically be cast into the appropriate type. Integers can be
|
||||
single quoted to force them to be strings.
|
||||
""".strip()),
|
||||
]
|
||||
|
||||
@CommandProvider
|
||||
|
Loading…
Reference in New Issue
Block a user