From 226bc996597407aed9fced101234962a542d6bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Wed, 27 Apr 2022 10:02:49 +0200 Subject: [PATCH] lib/strings: add toShellVars A straightforward piece of plumbing to safely inject Nix variables into shell scripts: '' ${lib.toShellVars { inherit foo bar; }} cmd "$foo" --bar "$bar" '' --- lib/default.nix | 3 ++- lib/strings.nix | 60 ++++++++++++++++++++++++++++++++++++++++++++++ lib/tests/misc.nix | 20 ++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/lib/default.nix b/lib/default.nix index 1f06283790a8..6327c8518ff3 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -94,7 +94,8 @@ let concatImapStringsSep makeSearchPath makeSearchPathOutput makeLibraryPath makeBinPath optionalString hasInfix hasPrefix hasSuffix stringToCharacters stringAsChars escape - escapeShellArg escapeShellArgs escapeRegex escapeXML replaceChars lowerChars + escapeShellArg escapeShellArgs isValidPosixName toShellVar toShellVars + escapeRegex escapeXML replaceChars lowerChars upperChars toLower toUpper addContextFrom splitString removePrefix removeSuffix versionOlder versionAtLeast getName getVersion diff --git a/lib/strings.nix b/lib/strings.nix index 11066890ec3d..bad8f53fa565 100644 --- a/lib/strings.nix +++ b/lib/strings.nix @@ -17,6 +17,7 @@ rec { head isInt isList + isAttrs isString match parseDrvName @@ -324,6 +325,65 @@ rec { */ escapeShellArgs = concatMapStringsSep " " escapeShellArg; + /* Test whether the given name is a valid POSIX shell variable name. + + Type: string -> bool + + Example: + isValidPosixName "foo_bar000" + => true + isValidPosixName "0-bad.jpg" + => false + */ + isValidPosixName = name: match "[a-zA-Z_][a-zA-Z0-9_]*" name != null; + + /* Translate a Nix value into a shell variable declaration, with proper escaping. + + Supported value types are strings (mapped to regular variables), lists of strings + (mapped to Bash-style arrays) and attribute sets of strings (mapped to Bash-style + associative arrays). Note that "strings" include string-coercible values like paths. + + Strings are translated into POSIX sh-compatible code; lists and attribute sets + assume a shell that understands Bash syntax (e.g. Bash or ZSH). + + Type: string -> (string | listOf string | attrsOf string) -> string + + Example: + '' + ${toShellVar foo "some string"} + [[ "$foo" == "some string" ]] + '' + */ + toShellVar = name: value: + lib.throwIfNot (isValidPosixName name) "toShellVar: ${name} is not a valid shell variable name" ( + if isAttrs value then + "declare -A ${name}=(${ + concatStringsSep " " (lib.mapAttrsToList (n: v: + "[${escapeShellArg n}]=${escapeShellArg v}" + ) value) + })" + else if isList value then + "declare -a ${name}=(${escapeShellArgs value})" + else + "${name}=${escapeShellArg value}" + ); + + /* Translate an attribute set into corresponding shell variable declarations + using `toShellVar`. + + Type: attrsOf (string | listOf string | attrsOf string) -> string + + Example: + let + foo = "value"; + bar = foo; + in '' + ${toShellVars { inherit foo bar; }} + [[ "$foo" == "$bar" ]] + '' + */ + toShellVars = vars: concatStringsSep "\n" (lib.mapAttrsToList toShellVar vars); + /* Turn a string into a Nix expression representing that string Type: string -> string diff --git a/lib/tests/misc.nix b/lib/tests/misc.nix index fcccf89cc888..c5d1d431677a 100644 --- a/lib/tests/misc.nix +++ b/lib/tests/misc.nix @@ -251,6 +251,26 @@ runTests { expected = ""test" 'test' < & >"; }; + testToShellVars = { + expr = '' + ${toShellVars { + STRing01 = "just a 'string'"; + _array_ = [ "with" "more strings" ]; + assoc."with some" = '' + strings + possibly newlines + ''; + }} + ''; + expected = '' + STRing01='just a '\'''string'\'''' + declare -a _array_=('with' 'more strings') + declare -A assoc=(['with some']='strings + possibly newlines + ') + ''; + }; + # LISTS testFilter = {