{ 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: x: mapProduct f allLibs <| builtins.attrValues <| x; allLibs = 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: builtins.head <| builtins.filter (x: profileSettings.${x}.isDefault) <| builtins.attrNames <| profileSettings; process = { languagePacks, ... }: let extractVersion = (x: if x == [ ] then info.base.version else builtins.head x) <| builtins.match "([^-]*)-.*" <| info.base.version; in { policies.ExtensionSettings = 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 = (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.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 ( _: v: processLevel "postprocess" <| processLevel "process" <| processLevel "preprocess" <| v ) 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; }; }