{ lib, config, ... }: let # Returns the uppercased first label or number of a string firstUpperAlnum = str: lib.mapNullable lib.head (builtins.match "[^A-Z0-9]*([A-Z0-9]).*" (lib.toUpper str)); userType = lib.types.submodule ( { name, ... }: { options = { departure = lib.mkOption { type = markerType; default = {}; }; }; config = { departure.style.label = lib.mkDefault (firstUpperAlnum name); }; }); markerType = lib.types.submodule { options = { location = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; }; style = { label = lib.mkOption { type = lib.types.nullOr (lib.types.strMatching "[0-9A-Z]"); default = null; }; color = lib.mkOption { type = colorType; default = "red"; }; }; }; }; colorType = lib.types.either (lib.types.strMatching "0x[0-9A-F]{3}[0-9A-F]{3}?") (lib.types.enum [ "black" "blue" "brown" "gray" "green" "orange" "purple" "red" "white" "yellow" ]); in { options = { users = lib.mkOption { type = lib.types.attrsOf userType; }; map.markers = lib.mkOption { type = lib.types.listOf markerType; }; }; config = { users = { manuel.departure = { location = "Switzerland"; }; ganondorf.departure = { location = "Argentinia"; style = { color = "green"; }; }; }; map = { markers = lib.filter (marker: marker.location != null) ( lib.concatMap (user: [ user.departure ]) (lib.attrValues config.users)); center = lib.mkIf (lib.length config.map.markers >= 1) null; zoom = lib.mkIf (lib.length config.map.markers >= 2) null; }; requestParams = let paramForMarker = marker: let attributes = lib.optional (marker.style.label != null) "label:${marker.style.label}" ++ [ "color:${marker.style.color}" "$(${config.scripts.geocode}/bin/geocode ${ lib.escapeShellArg marker.location })" ]; in "markers=\"${lib.concatStringsSep "|" attributes}\""; in builtins.map paramForMarker config.map.markers; }; }