diff options
Diffstat (limited to 'testing/mozbase/mozprofile/mozprofile/prefs.py')
-rw-r--r-- | testing/mozbase/mozprofile/mozprofile/prefs.py | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/testing/mozbase/mozprofile/mozprofile/prefs.py b/testing/mozbase/mozprofile/mozprofile/prefs.py new file mode 100644 index 0000000000..6c3b1173c8 --- /dev/null +++ b/testing/mozbase/mozprofile/mozprofile/prefs.py @@ -0,0 +1,263 @@ +# 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 +""" +import json +import os +import tokenize + +import mozfile +import six +from six import StringIO, string_types +from six.moves.configparser import SafeConfigParser as ConfigParser + +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 = [] + multi_line_pref = None + for line in lines: + # decode bytes in case of URL processing + if isinstance(line, bytes): + line = line.decode() + pref_start = line.startswith(pref_setter) + + # Handle preferences split over multiple lines + # Some lines may include brackets so do our best to ensure this + # is an actual expected end of function call by checking for a + # semi-colon as well. + if pref_start and not ");" in line: + multi_line_pref = line + continue + elif multi_line_pref: + multi_line_pref = multi_line_pref + line + if ");" in line: + if "//" in multi_line_pref: + multi_line_pref = multi_line_pref.replace("//", marker) + _lines.append(multi_line_pref) + multi_line_pref = None + continue + elif not pref_start: + 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() |