<commit_before>{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.home-assistant;
  format = pkgs.formats.yaml {};

  # Render config attribute sets to YAML
  # Values that are null will be filtered from the output, so this is one way to have optional
  # options shown in settings.
  # We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
  # https://www.home-assistant.io/docs/configuration/secrets/
  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
    cp ${format.generate "configuration.yaml" filteredConfig} $out
    sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
  '';
  lovelaceConfig = cfg.lovelaceConfig or {};
  lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;

  # Components advertised by the home-assistant package
  availableComponents = cfg.package.availableComponents;

  # Components that were added by overriding the package
  explicitComponents = cfg.package.extraComponents;
  useExplicitComponent = component: elem component explicitComponents;

  # Given a component "platform", looks up whether it is used in the config
  # as `platform = "platform";`.
  #
  # For example, the component mqtt.sensor is used as follows:
  # config.sensor = [ {
  #   platform = "mqtt";
  #   ...
  # } ];
  usedPlatforms = config:
    if isAttrs config then
      optional (config ? platform) config.platform
      ++ concatMap usedPlatforms (attrValues config)
    else if isList config then
      concatMap usedPlatforms config
    else [ ];

  useComponentPlatform = component: elem component (usedPlatforms cfg.config);

  # Returns whether component is used in config, explicitly passed into package or
  # configured in the module.
  useComponent = component:
    hasAttrByPath (splitString "." component) cfg.config
    || useComponentPlatform component
    || useExplicitComponent component
    || builtins.elem component cfg.extraComponents;

  # Final list of components passed into the package to include required dependencies
  extraComponents = filter useComponent availableComponents;

  package = (cfg.package.override (oldArgs: {
    # Respect overrides that already exist in the passed package and
    # concat it with values passed via the module.
    extraComponents = oldArgs.extraComponents ++ extraComponents;
  }));

in {
  imports = [
    # Migrations in NixOS 22.05
    (mkRemovedOptionModule [ "services" "home-assistant" "applyDefaultConfig" ] "The default config was migrated into services.home-assistant.config")
    (mkRemovedOptionModule [ "services" "home-assistant" "autoExtraComponents" ] "Components are now parsed from services.home-assistant.config unconditionally")
    (mkRenamedOptionModule [ "services" "home-assistant" "port" ] [ "services" "home-assistant" "config" "http" "server_port" ])
  ];

  meta = {
    buildDocsInSandbox = false;
    maintainers = teams.home-assistant.members;
  };

  options.services.home-assistant = {
    # Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project.
    # https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision
    enable = mkEnableOption "Home Assistant. Please note that this installation method is unsupported upstream";

    configDir = mkOption {
      default = "/var/lib/hass";
      type = types.path;
      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
    };

    extraComponents = mkOption {
      type = types.listOf (types.enum availableComponents);
      default = [
        # List of components required to complete the onboarding
        "default_config"
        "met"
        "esphome"
      ];
      example = literalExpression ''
        [
          "analytics"
          "default_config"
          "esphome"
          "my"
          "shopping_list"
          "wled"
        ]
      '';
      description = ''
        List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package.

        The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>.
      '';
    };

    config = mkOption {
      type = types.submodule {<commit_msg>nixos/home-assistant: add extraPackages option<commit_after>{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.home-assistant;
  format = pkgs.formats.yaml {};

  # Render config attribute sets to YAML
  # Values that are null will be filtered from the output, so this is one way to have optional
  # options shown in settings.
  # We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
  # https://www.home-assistant.io/docs/configuration/secrets/
  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
    cp ${format.generate "configuration.yaml" filteredConfig} $out
    sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
  '';
  lovelaceConfig = cfg.lovelaceConfig or {};
  lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;

  # Components advertised by the home-assistant package
  availableComponents = cfg.package.availableComponents;

  # Components that were added by overriding the package
  explicitComponents = cfg.package.extraComponents;
  useExplicitComponent = component: elem component explicitComponents;

  # Given a component "platform", looks up whether it is used in the config
  # as `platform = "platform";`.
  #
  # For example, the component mqtt.sensor is used as follows:
  # config.sensor = [ {
  #   platform = "mqtt";
  #   ...
  # } ];
  usedPlatforms = config:
    if isAttrs config then
      optional (config ? platform) config.platform
      ++ concatMap usedPlatforms (attrValues config)
    else if isList config then
      concatMap usedPlatforms config
    else [ ];

  useComponentPlatform = component: elem component (usedPlatforms cfg.config);

  # Returns whether component is used in config, explicitly passed into package or
  # configured in the module.
  useComponent = component:
    hasAttrByPath (splitString "." component) cfg.config
    || useComponentPlatform component
    || useExplicitComponent component
    || builtins.elem component cfg.extraComponents;

  # Final list of components passed into the package to include required dependencies
  extraComponents = filter useComponent availableComponents;

  package = (cfg.package.override (oldArgs: {
    # Respect overrides that already exist in the passed package and
    # concat it with values passed via the module.
    extraComponents = oldArgs.extraComponents ++ extraComponents;
    extraPackages = ps: (oldArgs.extraPackages ps) ++ (cfg.extraPackages ps);
  }));
in {
  imports = [
    # Migrations in NixOS 22.05
    (mkRemovedOptionModule [ "services" "home-assistant" "applyDefaultConfig" ] "The default config was migrated into services.home-assistant.config")
    (mkRemovedOptionModule [ "services" "home-assistant" "autoExtraComponents" ] "Components are now parsed from services.home-assistant.config unconditionally")
    (mkRenamedOptionModule [ "services" "home-assistant" "port" ] [ "services" "home-assistant" "config" "http" "server_port" ])
  ];

  meta = {
    buildDocsInSandbox = false;
    maintainers = teams.home-assistant.members;
  };

  options.services.home-assistant = {
    # Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project.
    # https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision
    enable = mkEnableOption "Home Assistant. Please note that this installation method is unsupported upstream";

    configDir = mkOption {
      default = "/var/lib/hass";
      type = types.path;
      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
    };

    extraComponents = mkOption {
      type = types.listOf (types.enum availableComponents);
      default = [
        # List of components required to complete the onboarding
        "default_config"
        "met"
        "esphome"
      ];
      example = literalExpression ''
        [
          "analytics"
          "default_config"
          "esphome"
          "my"
          "shopping_list"
          "wled"
        ]
      '';
      description = ''
        List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package.

        The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>.
      '';
    };

    extraPackages = mkOption {
      type = types.functionTo (types.listOf types.package);
      default = _: [];
      defaultText = literalExpression ''
        python3Packages: with python3Packages; [];
      '';
      example = literalExpression ''
        python3Packages: with python3Packages; [
          # postgresql support
          psycopg2
        ];
      '';
      description = ''
        List of packages to add to propagatedBuildInputs.

        A popular example is <package>python3Packages.psycopg2</package>
        for PostgreSQL support in the recorder component.
      '';
    };

    config = mkOption {
      type = types.submodule {
{'fraction_non_alphanumeric': 0.10307911908646003, 'fraction_numerical': 0.0027528548123980424, 'mean_word_length': 3.231665228645384, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 21, 'https://': 8, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3432618', 'n_tokens_mistral': 2779, 'n_tokens_neox': 2705, 'n_words': 819}