Omitting default options

Hi,

I have a module that builds a pre-commit configuration file. It looks like this:

{ config, lib, pkgs, ... }:
let
  inherit (lib) mkOption types;
  hook = types.submodule (
    { config, name, ... }:
    {
      options = {
        enable = mkOption {
          type = types.bool;
          description = "Enable/disable this hook from being included in the config";
          default = true;
        };
        id = mkOption {
          type = types.str;
          description = "The id of the hook - used in pre-commit-config.yaml";
          default = name;

        };
        name = mkOption {
          type = types.str;
          description = "The name of the hook - shown during hook execution";
          default = name;
        };
        entry = mkOption {
          type = types.str;
          description = "The entry point - the executable to run";
          default = "";
        };
        language = mkOption {
          type = types.str;
          description = "The language of the hook - tells pre-commit how to install the hook";
          default = "system";
        };
        files = mkOption {
          type = types.str;
          description = "The pattern of files to run on";
          default = "";
        };
        exclude = mkOption {
          type = types.str;
          description = "Exclude files that were matched by `files`";
          default = "^$";
        };
        types = mkOption {
          type = types.listOf types.str;
          description = "List of file types to run on (AND)";
          default = [ "file" ];
        };
        types_or = mkOption {
          type = types.listOf types.str;
          description = "List of file types to run on (OR)";
          default = [ ];
        };
        exclude_types = mkOption {
          type = types.listOf types.str;
          description = "The pattern of files to exclude.";
          default = [ ];
        };
        always_run = mkOption {
          type = types.bool;
          description = "If true this hook will run even if there are no matching files";
          default = false;
        };
        fail_fast = mkOption {
          type = types.bool;
          description = "If true pre-commit will stop running hooks if this hook fails";
          default = false;
        };
        verbose = mkOption {
          type = types.bool;
          description = "If true, forces the output of the hook to be printed even when the hook passes";
          default = false;
        };
        pass_filenames = mkOption {
          type = types.bool;
          description = "if false no filenames will be passed to the hook.";
          default = true;
        };
        require_serial = mkOption {
          type = types.bool;
          description = "if true this hook will execute using a single process instead of in parallel.";
          default = false;
        };
        description = mkOption {
          type = types.str;
          description = "Description of the hook. Used for metadata purposes only";
          default = "";
        };
        language_version = mkOption {
          type = types.str;
          description = "Sets version of language used to run hook";
          default = "default";
        };
        args = mkOption {
          type = types.listOf types.str;
          description = "list of additional parameters to pass to the hook";
          default = [ ];
        };
        stages = mkOption {
          type = types.listOf types.str;
          description = "Confines the hook to the given stages";
          default = [ "commit" ];
        };
      };
    }
  );

  repo = types.submodule (
    { config, name, ... }:
    {
      options = {
        repo = mkOption {
          type = types.str;
          description = "The address of the repository";
          default = name;
        };
        hooks = mkOption {
          type = types.attrsOf hook;
          description = "The hooks to include in the configuration";
          default = { };
        };
      };
    }
  );
in
{
  options = {
    default_stages = mkOption {
      type = types.listOf types.str;
      description = "The default stages to use for hooks";
      default = [ "commit" ];
    };
    repos = mkOption {
      type = types.attrsOf repo;
      description = "The repositories to use";
      default = { };
    };
  };
}

The default values are simply the ones that pre-commit uses when parsing the configuration. Thus, it’s redundant to reproduce the default values here, it would be better if they were just omitted entirely. The problem is that I need these options to be optional but, if not specified, the generated config should omit the attribute name. Just leaving the default to "" for example still includes the attribute name in the resulting config with the value set to an empty string.

Is there a way to force the config to be generated without options that were not explicitly given values?

The ‘non-explicitly given’ values are the default values (i.e. use this value when not specified). It seems like your looking to have this set to a sentinel value (that wouldn’t be used for a specified value) ? Perhaps something like:

files = lib.mkOption {
   type = lib.types.nullOr lib.types.str;
   description = "The pattern of files to run on";
   default = null;
};

Wherein your implementation would then use something along the lines of:

outputFileStr + lib.optionalString (files != null) "patten = ${files}\n"
1 Like

I’m not following this:

outputFileStr + lib.optionalString (files != null) "patten = ${files}\n"

Where are you in the code? Unfortunately, even with the default being null, the attribute still shows up in the config (with a value of null). My goal was to get the attribute name dropped in the case of it not being explicitly set. I think I may need to just filter the output to remove the null values after the config is generated.

This accomplished what I was going for:

pkgs.lib.filterAttrsRecursive (name: value: value != null) config

Thanks for the idea of using null, it made the next step easier.