summaryrefslogtreecommitdiffstats
path: root/testing/profiles/profile
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/profiles/profile
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/profiles/profile')
-rwxr-xr-xtesting/profiles/profile301
1 files changed, 301 insertions, 0 deletions
diff --git a/testing/profiles/profile b/testing/profiles/profile
new file mode 100755
index 0000000000..7ca300a0bd
--- /dev/null
+++ b/testing/profiles/profile
@@ -0,0 +1,301 @@
+#!/bin/sh
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+
+# 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/.
+
+# The beginning of this script is both valid shell and valid python,
+# such that the script starts with the shell and is reexecuted python
+'''which' mach > /dev/null 2>&1 && exec mach python "$0" "$@" ||
+echo "mach not found, either add it to your \$PATH or run this script via ./mach python testing/profiles/profile"; exit # noqa
+'''
+
+"""This script can be used to:
+
+ 1) Show all preferences for a given suite
+ 2) Diff preferences between two suites or profiles
+ 3) Sort preference files alphabetically for a given profile
+
+To use, either make sure that `mach` is on your $PATH, or run:
+ $ ./mach python testing/profiles/profile <args>
+
+For more details run:
+ $ ./profile -- --help
+"""
+
+import json
+import os
+import sys
+from argparse import ArgumentParser
+from itertools import chain
+
+from mozprofile import Profile
+from mozprofile.prefs import Preferences
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+try:
+ import jsondiff
+except ImportError:
+ from mozbuild.base import MozbuildObject
+ build = MozbuildObject.from_environment(cwd=here)
+ build.virtualenv_manager.install_pip_package("jsondiff")
+ import jsondiff
+
+
+FORMAT_STRINGS = {
+ 'names': (
+ '{pref}',
+ '{pref}',
+ ),
+ 'pretty': (
+ '{pref}: {value}',
+ '{pref}: {value_a} => {value_b}'
+ ),
+}
+
+
+def read_prefs(profile, pref_files=None):
+ """Read and return all preferences set in the given profile.
+
+ :param profile: Profile name relative to this `here`.
+ :returns: A dictionary of preferences set in the profile.
+ """
+ pref_files = pref_files or Profile.preference_file_names
+ prefs = {}
+ for name in pref_files:
+ path = os.path.join(here, profile, name)
+ if not os.path.isfile(path):
+ continue
+
+ try:
+ prefs.update(Preferences.read_json(path))
+ except ValueError:
+ prefs.update(Preferences.read_prefs(path))
+ return prefs
+
+
+def get_profiles(key):
+ """Return a list of profile names for key."""
+ with open(os.path.join(here, 'profiles.json'), 'r') as fh:
+ profiles = json.load(fh)
+
+ if '+' in key:
+ keys = key.split('+')
+ else:
+ keys = [key]
+
+ names = set()
+ for key in keys:
+ if key in profiles:
+ names.update(profiles[key])
+ elif os.path.isdir(os.path.join(here, key)):
+ names.add(key)
+
+ if not names:
+ raise ValueError('{} is not a recognized suite or profile'.format(key))
+ return names
+
+
+def read(key):
+ """Read preferences relevant to either a profile or suite.
+
+ :param key: Can either be the name of a profile, or the name of
+ a suite as defined in suites.json.
+ """
+ prefs = {}
+ for profile in get_profiles(key):
+ prefs.update(read_prefs(profile))
+ return prefs
+
+
+def format_diff(diff, fmt, limit_key):
+ """Format a diff."""
+ indent = ' '
+ if limit_key:
+ diff = {limit_key: diff[limit_key]}
+ indent = ''
+
+ if fmt == 'json':
+ print(json.dumps(diff, sort_keys=True, indent=2))
+ return 0
+
+ lines = []
+ for key, prefs in sorted(diff.items()):
+ if not limit_key:
+ lines.append("{}:".format(key))
+
+ for pref, value in sorted(prefs.items()):
+ context = {'pref': pref, 'value': repr(value)}
+
+ if isinstance(value, list):
+ context['value_a'] = repr(value[0])
+ context['value_b'] = repr(value[1])
+ text = FORMAT_STRINGS[fmt][1].format(**context)
+ else:
+ text = FORMAT_STRINGS[fmt][0].format(**context)
+
+ lines.append('{}{}'.format(indent, text))
+ lines.append('')
+ print('\n'.join(lines).strip())
+
+
+def diff(a, b, fmt, limit_key):
+ """Diff two profiles or suites.
+
+ :param a: The first profile or suite name.
+ :param b: The second profile or suite name.
+ """
+ prefs_a = read(a)
+ prefs_b = read(b)
+ res = jsondiff.diff(prefs_a, prefs_b, syntax='symmetric')
+ if not res:
+ return 0
+
+ if isinstance(res, list) and len(res) == 2:
+ res = {
+ jsondiff.Symbol('delete'): res[0],
+ jsondiff.Symbol('insert'): res[1],
+ }
+
+ # Post process results to make them JSON compatible and a
+ # bit more clear. Also calculate identical prefs.
+ results = {}
+ results['change'] = {k: v for k, v in res.items() if not isinstance(k, jsondiff.Symbol)}
+
+ symbols = [(k, v) for k, v in res.items() if isinstance(k, jsondiff.Symbol)]
+ results['insert'] = {k: v for sym, pref in symbols for k, v in pref.items()
+ if sym.label == 'insert'}
+ results['delete'] = {k: v for sym, pref in symbols for k, v in pref.items()
+ if sym.label == 'delete'}
+
+ same = set(prefs_a.keys()) - set(chain(*results.values()))
+ results['same'] = {k: v for k, v in prefs_a.items() if k in same}
+ return format_diff(results, fmt, limit_key)
+
+
+def read_with_comments(path):
+ with open(path, 'r') as fh:
+ lines = fh.readlines()
+
+ result = []
+ buf = []
+ for line in lines:
+ line = line.strip()
+ if not line:
+ continue
+
+ if line.startswith('//'):
+ buf.append(line)
+ continue
+
+ if buf:
+ result.append(buf + [line])
+ buf = []
+ continue
+
+ result.append([line])
+ return result
+
+
+def sort_file(path):
+ """Sort the given pref file alphabetically, preserving preceding comments
+ that start with '//'.
+
+ :param path: Path to the preference file to sort.
+ """
+ result = read_with_comments(path)
+ result = sorted(result, key=lambda x: x[-1])
+ result = chain(*result)
+
+ with open(path, 'w') as fh:
+ fh.write('\n'.join(result) + '\n')
+
+
+def sort(profile):
+ """Sort all prefs in the given profile alphabetically. This will preserve
+ comments on preceding lines.
+
+ :param profile: The name of the profile to sort.
+ """
+ pref_files = Profile.preference_file_names
+
+ for name in pref_files:
+ path = os.path.join(here, profile, name)
+ if os.path.isfile(path):
+ sort_file(path)
+
+
+def show(suite):
+ """Display all prefs set in profiles used by the given suite.
+
+ :param suite: The name of the suite to show preferences for. This must
+ be a key in suites.json.
+ """
+ for k, v in sorted(read(suite).items()):
+ print("{}: {}".format(k, repr(v)))
+
+
+def rm(profile, pref_file):
+ if pref_file == '-':
+ lines = sys.stdin.readlines()
+ else:
+ with open(pref_file, 'r') as fh:
+ lines = fh.readlines()
+
+ lines = [l.strip() for l in lines if l.strip()]
+ if not lines:
+ return
+
+ def filter_line(content):
+ return not any(line in content[-1] for line in lines)
+
+ path = os.path.join(here, profile, 'user.js')
+ contents = read_with_comments(path)
+ contents = filter(filter_line, contents)
+ contents = chain(*contents)
+ with open(path, 'w') as fh:
+ fh.write('\n'.join(contents))
+
+
+def cli(args=sys.argv[1:]):
+ parser = ArgumentParser()
+ subparsers = parser.add_subparsers(dest='func')
+ subparsers.required = True
+
+ diff_parser = subparsers.add_parser('diff')
+ diff_parser.add_argument('a', metavar='A',
+ help="Path to the first profile or suite name to diff.")
+ diff_parser.add_argument('b', metavar='B',
+ help="Path to the second profile or suite name to diff.")
+ diff_parser.add_argument('-f', '--format', dest='fmt', default='pretty',
+ choices=['pretty', 'json', 'names'],
+ help="Format to dump diff in (default: pretty)")
+ diff_parser.add_argument('-k', '--limit-key', default=None,
+ choices=['change', 'delete', 'insert', 'same'],
+ help="Restrict diff to the specified key.")
+ diff_parser.set_defaults(func=diff)
+
+ sort_parser = subparsers.add_parser('sort')
+ sort_parser.add_argument('profile', help="Path to profile to sort preferences.")
+ sort_parser.set_defaults(func=sort)
+
+ show_parser = subparsers.add_parser('show')
+ show_parser.add_argument('suite', help="Name of suite to show arguments for.")
+ show_parser.set_defaults(func=show)
+
+ rm_parser = subparsers.add_parser('rm')
+ rm_parser.add_argument('profile', help="Name of the profile to remove prefs from.")
+ rm_parser.add_argument('--pref-file', default='-', help="File containing a list of pref "
+ "substrings to delete (default: stdin)")
+ rm_parser.set_defaults(func=rm)
+
+ args = vars(parser.parse_args(args))
+ func = args.pop('func')
+ func(**args)
+
+
+if __name__ == '__main__':
+ sys.exit(cli())