From 7e45d30aed2e71679978531f811610ccc6495b95 Mon Sep 17 00:00:00 2001 From: Birdee <85372418+BirdeeHub@users.noreply.github.com> Date: Tue, 19 May 2026 15:07:10 -0700 Subject: [PATCH] feat(lib.types.linkable): type for `"ln -s ${v} $out/mylink"` --- lib/types.nix | 25 +++++++++++++++++++++++++ wrapperModules/t/television/module.nix | 19 +++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/lib/types.nix b/lib/types.nix index 3ea07e52..bc904877 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -366,6 +366,31 @@ in if builtins.isPath res then builtins.path { path = res; } else res; }; + /** + Allows values that are, or resolve to under interpolation, an absolute-path-like value. + + A value is considered linkable if it matches any of the following: + - It is a Nix store path (e.g. `./.`, `"${./.}"` or a derivation) + - It is a plain string that starts with `"/"` and does not contain any newlines + + This is useful when wrapping commands that accept file or directory paths, + where you need to distinguish path-like values from other strings. + + For example, when you feel like you want `lib.types.either wlib.types.stringable lib.types.lines` + + You should instead use `lib.types.either wlib.types.linkable lib.types.lines` + */ + linkable = lib.mkOptionType { + name = "linkable"; + descriptionClass = "noun"; + description = "absolute path"; + inherit (wlib.types.stringable) merge; + check = + x: + lib.isStringLike x + && (if builtins.isString x then builtins.substring 0 1 x == "/" && !lib.hasInfix "\n" x else true); + }; + /** A single-line, non-empty string */ diff --git a/wrapperModules/t/television/module.nix b/wrapperModules/t/television/module.nix index 4e4a1ce0..95a6483f 100644 --- a/wrapperModules/t/television/module.nix +++ b/wrapperModules/t/television/module.nix @@ -11,12 +11,7 @@ let nullable = false; typeName = "TOML"; }; - isPathLike = - x: - builtins.isPath x - || (lib.isStringLike x && !builtins.isString x) - || (builtins.isString x && lib.hasPrefix "/" x) - || lib.isStorePath x; + isLinkable = wlib.types.linkable.check; in { imports = [ wlib.modules.default ]; @@ -27,7 +22,7 @@ in description = "Television configuration options."; }; channels = lib.mkOption { - type = types.lazyAttrsOf (types.either wlib.types.stringable tomlFmtType); + type = types.lazyAttrsOf (types.either wlib.types.linkable tomlFmtType); default = { }; description = "Television channels to install."; }; @@ -40,7 +35,7 @@ in ''; }; themes = lib.mkOption { - type = types.lazyAttrsOf (types.either wlib.types.stringable tomlFmtType); + type = types.lazyAttrsOf (types.either wlib.types.linkable tomlFmtType); default = { }; description = "Themes of television to install."; }; @@ -84,17 +79,17 @@ in key = "channel_${n}"; relPath = lib.mkOverride 0 "${config.binName}-channels/${n}.toml"; output = lib.mkOverride 0 config.configDrvOutput; - ${if isPathLike v then null else "content"} = builtins.toJSON v; + ${if isLinkable v then null else "content"} = builtins.toJSON v; "builder" = - if isPathLike v then ''ln -s ${v} "$2"'' else ''${pkgs.remarshal}/bin/json2toml "$1" "$2"''; + if isLinkable v then ''ln -s ${v} "$2"'' else ''${pkgs.remarshal}/bin/json2toml "$1" "$2"''; }) config.channels // builtins.mapAttrs (n: v: { key = "theme_${n}"; relPath = lib.mkOverride 0 "${config.binName}-themes/${n}.toml"; output = lib.mkOverride 0 config.configDrvOutput; - ${if isPathLike v then null else "content"} = builtins.toJSON v; + ${if isLinkable v then null else "content"} = builtins.toJSON v; "builder" = - if isPathLike v then ''ln -s ${v} "$2"'' else ''${pkgs.remarshal}/bin/json2toml "$1" "$2"''; + if isLinkable v then ''ln -s ${v} "$2"'' else ''${pkgs.remarshal}/bin/json2toml "$1" "$2"''; }) config.themes; meta.maintainers = [ wlib.maintainers.allen-liaoo ]; };