summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozprofile/mozprofile/prefs.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/mozprofile/mozprofile/prefs.py')
-rw-r--r--testing/mozbase/mozprofile/mozprofile/prefs.py263
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()