{ lib, pkgs, sLib, info, geckoLib, ... }@args: let # (a -> b -> c) -> [a] -> [b] -> [c] mapProduct = f: xs: ys: builtins.concatMap (y: builtins.concatMap (x: f x y) xs) ys; # (Lib -> Profile -> c) -> ProfileSettings -> [c] mapLibAndProfile = f: sLib.compose [ (mapProduct f allLibs) builtins.attrValues ]; allLibs = sLib.compose [ builtins.attrValues (lib.filterAttrs (k: _: k != "profiles")) ] geckoLib; allLibs' = allLibs ++ [ geckoLib.profiles ]; submodule = { config, name, ... }: { options = { id = lib.mkOption { type = lib.types.ints.unsigned; default = 0; description = "Profile ID"; }; name = lib.mkOption { type = lib.types.str; default = name; description = "Profile name"; }; path = lib.mkOption { type = lib.types.str; default = name; description = "Profile path"; }; isDefault = lib.mkOption { type = lib.types.bool; default = config.id == 0; defaultText = "config.id == 0"; description = "Is profile the default?"; }; languagePacks = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; example = [ "en-GB" "de" ]; description = "The language packs to install."; }; } // builtins.foldl' (x: y: x // y.options) { } allLibs; }; writeProfilesIni = configPath: profileSettings: { name = "${configPath}/profiles.ini"; value.text = lib.generators.toINI { } ( lib.mapAttrs' ( _: v: lib.nameValuePair "Profile${builtins.toString v.id}" { Name = v.name; Path = v.path; IsRelative = 1; Default = if v.isDefault then 1 else 0; } ) profileSettings // { General = { StartWithLastProfile = 1; Version = 2; }; } ); }; in { options = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule submodule); default = { }; description = "Sets browser settings on a per-profile basis."; }; getDefaultProfileName = profileSettings: sLib.compose [ builtins.head (builtins.filter (x: profileSettings.${x}.isDefault)) builtins.attrNames ] profileSettings; process = { languagePacks, ... }: let extractVersion = sLib.compose [ (x: if x == [ ] then info.base.version else builtins.head x) (builtins.match "([^-]*)-.*") ] info.base.version; in { policies.ExtensionSettings = sLib.compose [ builtins.listToAttrs (builtins.map (lang: { name = "langpack-${lang}@firefox.mozilla.org"; value = { installation_mode = "force_installed"; install_url = "https://releases.mozilla.org/pub/firefox/releases/${extractVersion}/linux-x86_64/xpi/${lang}.xpi"; }; })) ] languagePacks; }; assertions = profiles: [ { assertion = profiles != { }; message = "No profiles are defined."; } { assertion = sLib.compose [ (x: x == 1) builtins.length (builtins.filter (x: profiles.${x}.isDefault)) builtins.attrNames ] profiles; message = "One, and only one, profile must be marked as 'isDefault'. If you have not set this option on any profile, then one and only one profile must have id '0'."; } ]; warnings = _: [ ]; evaluateAllProfiles = profiles: let processLevel = level: userConfig: sLib.compose [ sLib.recursiveMergeAll (x: [ userConfig ] ++ x) (builtins.map (myLib: (myLib.${level} or (_: { })) userConfig)) ] allLibs'; # Internal libraries can create their own configuration that needs to be merged # into the user configuration (the user's takes priority). Generate that config. finalConfig = builtins.mapAttrs ( _: sLib.compose [ (processLevel "postprocess") (processLevel "process") (processLevel "preprocess") ] ) profiles; in { assertions = mapLibAndProfile (x: x.assertions or (_: [ ])) finalConfig ++ geckoLib.profiles.assertions finalConfig; warnings = mapLibAndProfile (x: x.warnings or (_: [ ])) finalConfig ++ geckoLib.profiles.warnings finalConfig; profiles = lib.mapAttrs (_: v: { policies = v.policies; prefs = v.prefs; files = builtins.concatMap (x: (x.files or (_: [ ])) v) allLibs'; extFiles = builtins.concatMap (x: (x.extFiles or (_: [ ])) v) allLibs'; }) finalConfig; files = [ (writeProfilesIni info.configPath finalConfig) ] ++ mapLibAndProfile (x: x.files or (_: [ ])) finalConfig; }; }