From 386d56af2817cbeeaea174b3f0c2431f66d75565 Mon Sep 17 00:00:00 2001 From: Seth Flynn Date: Sat, 10 May 2025 22:27:44 -0400 Subject: [PATCH] modules: add `users..files..{generator,value}` options --- README.md | 9 ++++++++ modules/common/user.nix | 49 ++++++++++++++++++++++++++++++++--------- tests/basic.nix | 19 ++++++++++++++-- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7941173..a744515 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,15 @@ may use to manage individual users' homes by leveraging the module system. # This can be used to generate config files with various # formats expected by different programs. ".config/bar".source = pkgs.writeTextFile "file-foo" "file contents"; + + # You can also use generators to transform Nix values + ".config/baz" = { + # Works with `pkgs.formats` too! + generator = lib.generators.toJSON { }; + value = { + some = "contents"; + }; + }; }; }; } diff --git a/modules/common/user.nix b/modules/common/user.nix index 3a80057..ac593d7 100644 --- a/modules/common/user.nix +++ b/modules/common/user.nix @@ -10,10 +10,10 @@ }: let inherit (lib.attrsets) mapAttrsToList; inherit (lib.strings) concatMapStringsSep concatLines; - inherit (lib.modules) mkIf mkDefault mkDerivedConfig; + inherit (lib.modules) mkIf mkDefault mkDerivedConfig mkMerge; inherit (lib.options) mkOption literalExpression mkEnableOption; inherit (lib.strings) hasPrefix; - inherit (lib.types) attrsOf bool lines listOf nullOr package path str submodule oneOf int; + inherit (lib.types) anything attrsOf bool either functionTo lines listOf nullOr package path str submodule oneOf int; inherit (builtins) isList; cfg = config; @@ -58,6 +58,22 @@ description = "Path of the source file or directory"; }; + generator = lib.mkOption { + type = nullOr (functionTo (either options.source.type options.text.type)); + default = null; + description = "Function that when applied to `value` will create the `text` of the file."; + example = literalExpression "lib.generators.toGitINI"; + }; + + value = lib.mkOption { + type = nullOr (attrsOf anything); + default = null; + description = "Value passed to the `generator`."; + example = { + user.email = "me@example.com"; + }; + }; + executable = mkOption { type = bool; default = false; @@ -90,14 +106,27 @@ }; }; - config = { - target = mkDefault name; - source = mkIf (config.text != null) (mkDerivedConfig options.text (text: - pkgs.writeTextFile { - inherit name text; - inherit (config) executable; - })); - }; + config = let + generatedValue = config.generator config.value; + in + mkMerge [ + { + target = mkDefault name; + source = mkIf (config.text != null) (mkDerivedConfig options.text (text: + pkgs.writeTextFile { + inherit name text; + inherit (config) executable; + })); + } + + (lib.mkIf (config.generator != null && options.source.type.check generatedValue) { + source = mkDefault generatedValue; + }) + + (lib.mkIf (config.generator != null && options.text.type.check generatedValue) { + text = mkDefault generatedValue; + }) + ]; }); in { imports = [ diff --git a/tests/basic.nix b/tests/basic.nix index 029f6c3..8f5698b 100644 --- a/tests/basic.nix +++ b/tests/basic.nix @@ -6,6 +6,7 @@ in nodes = { node1 = { self, + lib, pkgs, ... }: { @@ -22,8 +23,20 @@ in alice = { enable = true; packages = [pkgs.hello]; - files.".config/foo" = { - text = "Hello world!"; + files = { + ".config/foo" = { + text = "Hello world!"; + }; + + ".config/bar.json" = { + generator = lib.generators.toJSON {}; + value = {bar = true;}; + }; + + ".config/baz.toml" = { + generator = (pkgs.formats.toml {}).generate "baz.toml"; + value = {baz = true;}; + }; }; }; }; @@ -47,6 +60,8 @@ in # Test file created by Hjem machine.succeed("[ -L ~alice/.config/foo ]") + machine.succeed("[ -L ~alice/.config/bar.json ]") + machine.succeed("[ -L ~alice/.config/baz.toml ]") # Test regular files, created by systemd-tmpfiles machine.succeed("[ -d ~alice/user_tmpfiles_created ]")