testing/mozbase/mozprofile/mozprofile/prefs.py
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Wed, 19 Jan 2022 06:57:01 +0000
changeset 604799 184fd64dd78347ea17dbd5aef4a9d8e40fcc9f7f
parent 571772 35166f2e193636dd628014891e7e16672ce08c4a
permissions -rw-r--r--
no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD ach -> 977c0eee5774e3bd19a25900142d1dca3eb2ee29 af -> 162654a20c86192e124df9766ec5ecd54143d085 an -> d10834e1c7ba839ba9c372205f4371c4d3c19791 ar -> 3be417e9326ebc9e369fc77f1fd98481a39b07cf ast -> 41dcea23a9d890ee00cb4cc3797559fe461244bc az -> 0366cdbc61ed5620510c848b85cd593d376c75f3 be -> 62bb6ed3829ec227748a86410cf038052f673b54 bg -> 19ee7b31e0becafa054013a3c8c09844638f244c bn -> 010d5f74034cb2c18fd1100e0d0888a245d52a60 bo -> 97059fd9d1a83e254bf84da12f1612062e82d271 br -> a9e3b8ba10759c8a4f7da6b7970911ded7d04ad6 brx -> 573ec21d6a1aebf6a342533bb06b77c79b6e994b bs -> 9ebb98165b36860f938bfb97c1956090e8daf920 ca -> caf5515093962f3f92c92aea68dacd4c3e520a8d ca-valencia -> d8f8751d595bf879afe5bfa8e3854ee5b6d1b977 cak -> 0bbd8b94c9c02fa5ec6a3e5fd3bf3e69b0432829 ckb -> bd043d903599db44feb371bf29d4a3d510a48a0a cs -> ae677a6bee277f507e673c3fcd2d56088b2caafb cy -> c46474099266c675d8ddc8fbcecd23a8a11a174e da -> 4595dd6f7ed91af507076eb0a95a44f4d0646404 de -> d3fca46cd495000d26fd26d3a2776b8f4dae786c dsb -> 611248df8f629860d012cbc8f3a36acfd8744b46 el -> 22d62ecd4c214d52af254d87c2da7410bd571fb2 en-CA -> 501d80026e4fdeac79e20b41cb8087c7ec3c3ba2 en-GB -> ec99571b02068fd4bf160416b5b7e35379c899a2 eo -> 6f7723c2bfbf5e3aeb1134909464bbc099c86a4f es-AR -> e4e8f59ee9824548910f3281d02c5727caa36dd2 es-CL -> 08ee034825af181840c14c2bee5a7c20d0aa59ac es-ES -> 46729ef124e5cf1f0a3e85d50c16a069602cfed2 es-MX -> 43859d642bd044060a4d2089f4b30de8bb470e43 et -> 7cc4df8eca6d3b6c4e3073ed35770034e9be79ca eu -> 863a0a1a315b55c18694e29430c272baba355b4c fa -> 60cff0755aaf290a159cb6968c4909bf9ecae2fa ff -> ec175d67f9056e5fd2cb6568ee546336efb77d5c fi -> 6e37410128f2d675c3414ba9c6341af81b4521ff fr -> ce1d665cec91d4d29160860dba2350f22f2f4df7 fy-NL -> 5e4350e39e2a7797d6bd8e1c1bb0b15252397010 ga-IE -> 2d955174d7ab6068656b2f02e4d8c689bb2399bf gd -> 49032dc335b8b5e9ecd10435f878598bffa6541a gl -> d8761e8f84efac80d6a7482931a01cecd66dd60d gn -> aa33a53b688bed9cedeb8a3eaf0466b9451d2e18 gu-IN -> d2c670d798b925090af6321cb3270fa6f806108d he -> 3385ae23e3b569ba9c0c1aca4df25277b81a108e hi-IN -> 274838883f7f1f14d5f14d5116e7df11cc404911 hr -> a01b7ce3515d0b2f05e473625148f11a264da0a2 hsb -> f137d0e6746dc23beff4745474df87c33fae353b hu -> 39e18e316a85b5aaada8c560d51317b5e28b77d8 hy-AM -> 41e938f8ebf1581d1bc76bd088fc1ac43bb83e0b hye -> 5843bc7348ba6a533afffe08eedc48c623963542 ia -> 031f226bad1d97adf265f75fcb467a18c223c606 id -> 4d4de9751872149e1c99dcfce2b7ce4835b65589 is -> 5753814cd9664948b3ac5ace293b3d46304f0cf2 it -> 3448943cfadfc5a361bd367c7e6be58f86cc61a1 ja -> c4be75596e316966ab1dc9857a9be6b0a7bc2ef9 ja-JP-mac -> 476eca379b8b21857655d6c23908b3e2a1b09aee ka -> 2d66f0c2bd7a06ac1ad6ad02a09a26ff1e60e3cb kab -> 409893635bedbe52fa8b85784844e15756d4a8fd kk -> c68316baa4a0dd041abb4e8efd557339fb0d9cb4 km -> c04f31c98e50315177467bd4e079200d3b38b835 kn -> 0bc693a82c819a27e71f41828c040e7be4b92785 ko -> 88096d30e35f344fcb61f08900340b31fca79cc7 lij -> e46553ebd09bbabad095ccc4c8b7d147f895a475 lo -> 942d94da48979eb60bcd62d825f7f9337314999f lt -> 6a447408fe67e37ee56a85d8bea523219dac52e1 ltg -> 06e42a8ee9e5211fa8812542a6bd3c22e3c540ba lv -> 0059bd3f14ced8ec1ef0f2765b655fe4ee537e91 meh -> 54cb55f92ef9d93d0481b3806b725d08035d950f mk -> c473f0b380bd14c98754cb38cdc287c5b0df99a0 mr -> b3382076ee961b51f0e2a7369c08aaf03fb8574d ms -> ec4c315ae032575bed799d8faa57dd4441e5bd9e my -> 25d516d33a6c2f8881683c5530f468e2c25ab6eb nb-NO -> 490c2d401359fd2204ad7aa806a73107f3befc27 ne-NP -> 5676e8e0b93e18fb5eeb238953c512d4f407340c nl -> 3fbbd92a2f116150e9921f13e4c9fe37d6948464 nn-NO -> 7d8c6d43ad98b2a8a0dce7ef05234b66cb818244 oc -> 8379011f2b605f1597fc61e4585520911ca58567 pa-IN -> b28bb037d4687370eebb7a892982b31bddea32b0 pl -> 3193b4b820e7c37bdb9153ab9c525f18210a501a pt-BR -> ce1ab16cc1acd0fa7ae898feeba46c6d497fe981 pt-PT -> e0910097929ee117291ede6675c6e0add124221b rm -> cdce5683cd6621c0ca31eb6b6ffd209481fa3c85 ro -> ba7fe96d6c98e01a48f442d520c39d119c7e0727 ru -> dc6d46ab977eb359c21a0f6dfe5b22e1fa60fda0 sat -> 3ab6969e38e316491894b42527f2287dbbd7d626 sc -> 14a1b41f804a89dd00d978056ebfc966a638cbb8 scn -> 6729c4bfb82228012e51e2077cf8f2ce254e8fe6 sco -> 8aed4c4ac1dd708f213d350a46617cd3faca0a09 si -> 639820754f8563d1fccf89f48c4b155db6a3ae8d sk -> 588464d4ecd69c9a12ffd0e41ccb5e4a7f16e25e sl -> e7aa581fc059a4e59ae56eb03790d0d7e4129a42 son -> 1541de0c90d66751ae72c24d4fa8105a585cf203 sq -> 05efafdd60c5a69d6d75adf8ccb97c5b07aee239 sr -> e7c7978d2fd74f4e2098253a41b099604b88d61b sv-SE -> fbeae2bbc5837470c90793f92a1cc90b7d6fd51f szl -> 000ee773591f17bcd9fc5ed5eb504000a016ce5b ta -> 2545635e99da1c3bdfc3536ba8c6a68a7498d014 te -> 0c5e4eb71c5ffe04c651f377355ead0f976960cd tg -> 8c5d8e78f165f7aef1f2597a67944cf8c101f906 th -> b6c8c65ece954839a983d48aa92a348b84867fa7 tl -> c7615e24b3453bb59a41eb017db2e04c781c8782 tr -> 08e01536e9eab024fceabe205921d815094af748 trs -> 43debe20e58afdd09ddbcd2f7dd62039c1e8c5f8 uk -> a4d58069863410d7b878690855604f86b12c300b ur -> 354acd5f98b1a03c6960231c570fedb771588d86 uz -> f8e8cc127eaf07ab77bf51c37ec494d2721e8d2e vi -> e24285029832c0cc588e8e12aa2212dff982d623 wo -> 625cc2e007db82c90ecddd39e34f1969c4088e09 xh -> 09a4bf9ceb7fe52e7fd54b2c8a15c16c6a4c56f2 zh-CN -> 5c57a47c29f1c8105d8c268617a00c54e4a8ba8c zh-TW -> f86c570fdc59c21666014d9c81f61c9ebeac0d72

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

"""
user preferences
"""
from __future__ import absolute_import, print_function

import json
import mozfile
import os
import tokenize

from six.moves.configparser import SafeConfigParser as ConfigParser
from six import StringIO, string_types
import six

if six.PY3:

    def unicode(input):
        return input


__all__ = ("PreferencesReadError", "Preferences")


class PreferencesReadError(Exception):
    """read error for prefrences files"""


class Preferences(object):
    """assembly of preferences from various sources"""

    def __init__(self, prefs=None):
        self._prefs = []
        if prefs:
            self.add(prefs)

    def add(self, prefs, cast=False):
        """
        :param prefs:
        :param cast: whether to cast strings to value, e.g. '1' -> 1
        """
        # wants a list of 2-tuples
        if isinstance(prefs, dict):
            prefs = prefs.items()
        if cast:
            prefs = [(i, self.cast(j)) for i, j in prefs]
        self._prefs += prefs

    def add_file(self, path):
        """a preferences from a file

        :param path:
        """
        self.add(self.read(path))

    def __call__(self):
        return self._prefs

    @classmethod
    def cast(cls, value):
        """
        interpolate a preference from a string
        from the command line or from e.g. an .ini file, there is no good way to denote
        what type the preference value is, as natively it is a string

        - integers will get cast to integers
        - true/false will get cast to True/False
        - anything enclosed in single quotes will be treated as a string
          with the ''s removed from both sides
        """

        if not isinstance(value, string_types):
            return value  # no op
        quote = "'"
        if value == "true":
            return True
        if value == "false":
            return False
        try:
            return int(value)
        except ValueError:
            pass
        if value.startswith(quote) and value.endswith(quote):
            value = value[1:-1]
        return value

    @classmethod
    def read(cls, path):
        """read preferences from a file"""

        section = None  # for .ini files
        basename = os.path.basename(path)
        if ":" in basename:
            # section of INI file
            path, section = path.rsplit(":", 1)

        if not os.path.exists(path) and not mozfile.is_url(path):
            raise PreferencesReadError("'%s' does not exist" % path)

        if section:
            try:
                return cls.read_ini(path, section)
            except PreferencesReadError:
                raise
            except Exception as e:
                raise PreferencesReadError(str(e))

        # try both JSON and .ini format
        try:
            return cls.read_json(path)
        except Exception as e:
            try:
                return cls.read_ini(path)
            except Exception as f:
                for exception in e, f:
                    if isinstance(exception, PreferencesReadError):
                        raise exception
                raise PreferencesReadError("Could not recognize format of %s" % path)

    @classmethod
    def read_ini(cls, path, section=None):
        """read preferences from an .ini file"""

        parser = ConfigParser()
        parser.optionxform = str
        parser.readfp(mozfile.load(path))

        if section:
            if section not in parser.sections():
                raise PreferencesReadError("No section '%s' in %s" % (section, path))
            retval = parser.items(section, raw=True)
        else:
            retval = parser.defaults().items()

        # cast the preferences since .ini is just strings
        return [(i, cls.cast(j)) for i, j in retval]

    @classmethod
    def read_json(cls, path):
        """read preferences from a JSON blob"""

        prefs = json.loads(mozfile.load(path).read())

        if type(prefs) not in [list, dict]:
            raise PreferencesReadError("Malformed preferences: %s" % path)
        if isinstance(prefs, list):
            if [i for i in prefs if type(i) != list or len(i) != 2]:
                raise PreferencesReadError("Malformed preferences: %s" % path)
            values = [i[1] for i in prefs]
        elif isinstance(prefs, dict):
            values = prefs.values()
        else:
            raise PreferencesReadError("Malformed preferences: %s" % path)
        types = (bool, string_types, int)
        if [i for i in values if not any([isinstance(i, j) for j in types])]:
            raise PreferencesReadError("Only bool, string, and int values allowed")
        return prefs

    @classmethod
    def read_prefs(cls, path, pref_setter="user_pref", interpolation=None):
        """
        Read preferences from (e.g.) prefs.js

        :param path: The path to the preference file to read.
        :param pref_setter: The name of the function used to set preferences
                            in the preference file.
        :param interpolation: If provided, a dict that will be passed
                              to str.format to interpolate preference values.
        """

        marker = "##//"  # magical marker
        lines = [i.strip() for i in mozfile.load(path).readlines()]
        _lines = []
        for line in lines:
            # decode bytes in case of URL processing
            if isinstance(line, bytes):
                line = line.decode()
            if not line.startswith(pref_setter):
                continue
            if "//" in line:
                line = line.replace("//", marker)
            _lines.append(line)
        string = "\n".join(_lines)

        # skip trailing comments
        processed_tokens = []
        f_obj = StringIO(string)
        for token in tokenize.generate_tokens(f_obj.readline):
            if token[0] == tokenize.COMMENT:
                continue
            processed_tokens.append(
                token[:2]
            )  # [:2] gets around http://bugs.python.org/issue9974
        string = tokenize.untokenize(processed_tokens)

        retval = []

        def pref(a, b):
            if interpolation and isinstance(b, string_types):
                b = b.format(**interpolation)
            retval.append((a, b))

        lines = [i.strip().rstrip(";") for i in string.split("\n") if i.strip()]

        _globals = {"retval": retval, "true": True, "false": False}
        _globals[pref_setter] = pref
        for line in lines:
            try:
                eval(line, _globals, {})
            except SyntaxError:
                print(line)
                raise

        # de-magic the marker
        for index, (key, value) in enumerate(retval):
            if isinstance(value, string_types) and marker in value:
                retval[index] = (key, value.replace(marker, "//"))

        return retval

    @classmethod
    def write(cls, _file, prefs, pref_string="user_pref(%s, %s);"):
        """write preferences to a file"""

        if isinstance(_file, string_types):
            f = open(_file, "a")
        else:
            f = _file

        if isinstance(prefs, dict):
            # order doesn't matter
            prefs = prefs.items()

        # serialize -> JSON
        _prefs = [(json.dumps(k), json.dumps(v)) for k, v in prefs]

        # write the preferences
        for _pref in _prefs:
            print(unicode(pref_string % _pref), file=f)

        # close the file if opened internally
        if isinstance(_file, string_types):
            f.close()