unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
From: Sarah Morgensen <iskarian@mgsn.dev>
To: 50350@debbugs.gnu.org
Subject: [bug#50350] [PATCH core-updates] utils: Add helpers for list environment variables.
Date: Thu,  2 Sep 2021 22:29:46 -0700	[thread overview]
Message-ID: <40d8e502f754fd7bbbe3a916950a85d1f1c57073.1630646500.git.iskarian@mgsn.dev> (raw)

Add helpers 'getenv/list', 'setenv/list', 'setenv/list-extend', and
'setenv/list-delete' for list environment variables (such as search
paths).

* guix/build/utils.scm (getenv/list, setenv/list)
(setenv/list-extend, setenv/list-delete): New procedures.
* .dir-locals.el (scheme-mode): Indent them.
* tests/build-utils.scm ("getenv/list", "getenv/list: unset")
("setenv/list: ignore empty elements")
("setenv/list: unset if empty")
("setenv/list-extend: single element, prepend")
("setenv/list-extend: multiple elements, prepend")
("setenv/list-extend: multiple elements, append")
("setenv/list-delete: single deletion")
("setenv/list-delete: multiple deletions"): New tests.
---
Hello Guix,

I noticed that there are over 200 occurrences of this pattern in packages:

--8<---------------cut here---------------start------------->8---
  (setenv "PYTHONPATH"
          (string-append (getcwd) ":" (getenv "PYTHONPATH")))
--8<---------------cut here---------------end--------------->8---

This patch introduces some helper procedures for these kinds of cases. With
this patch, instead of the above you could write:

  (setenv/list-extend "PYTHONPATH" (getcwd))

Sometimes you want to add to the end of the path:

--8<---------------cut here---------------start------------->8---
  (setenv "GEM_PATH"
          (string-append (getenv "GEM_PATH") ":" new-gem))
--8<---------------cut here---------------end--------------->8---

With this patch, you could write instead:

  (setenv/list-extend "GEM_PATH" new-gem #:prepend? #f)

Adding include paths becomes much more readable in conjunction with
search-input-directory, with this:

--8<---------------cut here---------------start------------->8---
  (setenv "CPATH"
          (string-append (assoc-ref inputs "libtirpc")
                         "/include/tirpc/:"
                         (or (getenv "CPATH") "")))
--8<---------------cut here---------------end--------------->8---

becoming this:

  (setenv/list-extend "CPATH"
    (search-input-directory "/include/tirpc"))

A less common case, of removing a path:

--8<---------------cut here---------------start------------->8---
  (setenv "CPLUS_INCLUDE_PATH"
          (string-join
           (delete (string-append gcc "/include/c++")
                   (string-split (getenv "CPLUS_INCLUDE_PATH") #\:))
           ":"))
--8<---------------cut here---------------end--------------->8---

becomes this:

  (setenv/list-delete "CPLUS_INCLUDE_PATH"
    (string-append gcc "/include/c++"))

What do you all think?

(Bikeshed opportunity: I'm not in love with the names.  I originally named
these 'setenv/path' rather than 'setenv/list', because I wanted to avoid
confusion with Guix's search paths, but I'm not sure 'setenv/list' is actually
more clear.

I considered getenv*, setenv*, and so on, but I didn't think they were quite
clear enough either.

I did consider 'setenv/list-extend!' and 'setenv/list-delete!' since they do
modify the env var in place, but "setenv" should already imply that.

Finally, it might be better to have e.g. 'setenv/path-prepend!' and
'setenv/path-append!' rather than the single 'setenv/path-extend', but I could
not settle on memorable, representative names.  Using 'append' carries a
connotation that you are dealing with lists, because of 'append', but it also
accepts a single element.  Using 'extend'/'prepend' together seems confusing
to me, because I might reach for 'extend' to add to the beginning of the list
if I forget about 'prepend'.)

--
Sarah

 .dir-locals.el        |  4 ++++
 guix/build/utils.scm  | 56 +++++++++++++++++++++++++++++++++++++++++++
 tests/build-utils.scm | 53 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 113 insertions(+)

diff --git a/.dir-locals.el b/.dir-locals.el
index 919ed1d1c4..4b58220526 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -69,6 +69,10 @@
    (eval . (put 'add-before 'scheme-indent-function 2))
    (eval . (put 'add-after 'scheme-indent-function 2))
 
+   (eval . (put 'setenv/list 'scheme-indent-function 1))
+   (eval . (put 'setenv/list-extend 'scheme-indent-function 1))
+   (eval . (put 'setenv/list-delete 'scheme-indent-function 1))
+
    (eval . (put 'modify-services 'scheme-indent-function 1))
    (eval . (put 'with-directory-excursion 'scheme-indent-function 1))
    (eval . (put 'with-file-lock 'scheme-indent-function 1))
diff --git a/guix/build/utils.scm b/guix/build/utils.scm
index 3beb7da67a..d0ac33a64f 100644
--- a/guix/build/utils.scm
+++ b/guix/build/utils.scm
@@ -8,6 +8,7 @@
 ;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
 ;;; Copyright © 2020, 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -75,6 +76,11 @@
             find-files
             false-if-file-not-found
 
+            getenv/list
+            setenv/list
+            setenv/list-extend
+            setenv/list-delete
+
             search-path-as-list
             set-path-environment-variable
             search-path-as-string->list
@@ -521,6 +527,56 @@ also be included.  If FAIL-ON-ERROR? is true, raise an exception upon error."
           #f
           (apply throw args)))))
 
+\f
+;;;
+;;; Multiple-valued environment variables.
+;;;
+
+(define* (setenv/list env-var lst #:key (separator #\:))
+  "Set environment variable ENV-VAR to the elements of LST separated by
+SEPARATOR.  Empty elements are ignored.  If ENV-VAR would be set to the empty
+string, unset ENV-VAR."
+  (let ((path (string-join (delete "" lst) (string separator))))
+    (if (string-null? path)
+        (unsetenv env-var)
+        (setenv env-var path))))
+
+(define* (getenv/list env-var #:key (separator #\:))
+  "Return a list of the SEPARATOR-separated elements of environment variable
+ENV-VAR, or the empty list if ENV-VAR is unset."
+  (or (and=> (getenv env-var)
+             (cut string-split <> separator))
+      '()))
+
+(define* (setenv/list-extend env-var list-or-str
+                             #:key (separator #\:) (prepend? #t))
+  "Add the element(s) LIST-OR-STR to the environment variable ENV-VAR using
+SEPARATOR between elements.  Empty elements are ignored.  Elements are placed
+at the beginning if PREPEND? is #t, or at the end otherwise."
+  (let* ((elements (match list-or-str
+                     ((? string? str) (list str))
+                     ((? list? lst) lst)))
+         (original (or (getenv env-var) ""))
+         (path-list (if prepend?
+                        (append elements (list original))
+                        (cons original elements))))
+    (when (not (null? elements))
+      (setenv/list env-var path-list #:separator separator))))
+
+(define* (setenv/list-delete env-var list-or-str #:key (separator #\:))
+  "Remove the element(s) LIST-OR-STR from the SEPARATOR-separated environment
+variable ENV-VAR, and set ENV-VAR to that value.  If ENV-VAR would be set to
+the empty string, unset ENV-VAR."
+  (let* ((elements (match list-or-str
+                     ((? string? str) (list str))
+                     ((? list? lst) lst)))
+         (original (getenv/list env-var #:separator separator))
+         (path-list (lset-difference string=? original elements))
+         (path (string-join path-list (string separator))))
+    (if (string-null? path)
+        (unsetenv env-var)
+        (setenv env-var path))))
+
 \f
 ;;;
 ;;; Search paths.
diff --git a/tests/build-utils.scm b/tests/build-utils.scm
index 6b131c0af8..b26bffd9a8 100644
--- a/tests/build-utils.scm
+++ b/tests/build-utils.scm
@@ -3,6 +3,7 @@
 ;;; Copyright © 2019 Ricardo Wurmus <rekado@elephly.net>
 ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
+;;; Copyright © 2021 Sarah Morgensen <iskarian@mgsn.dev>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -264,6 +265,58 @@ print('hello world')"))
          (lambda _
            (get-string-all (current-input-port))))))))
 
+(test-equal "setenv/list: ignore empty elements"
+  "one:three"
+  (with-environment-variable "TEST_SETENV" #f
+    (setenv/list "TEST_SETENV" '("one" "" "three"))
+    (getenv "TEST_SETENV")))
+
+(test-equal "setenv/list: unset if empty"
+  #f
+  (with-environment-variable "TEST_SETENV" #f
+    (setenv/list "TEST_SETENV" '())
+    (getenv "TEST_SETENV")))
+
+(test-equal "getenv/list"
+  '("one" "two" "three")
+  (with-environment-variable "TEST_SETENV" "one:two:three"
+    (getenv/list "TEST_SETENV")))
+
+(test-equal "getenv/list: unset"
+  '()
+  (with-environment-variable "TEST_SETENV" #f
+    (getenv/list "TEST_SETENV")))
+
+(test-equal "setenv/list-extend: single element, prepend"
+  "new:one:two"
+  (with-environment-variable "TEST_SETENV" "one:two"
+    (setenv/list-extend "TEST_SETENV" "new")
+    (getenv "TEST_SETENV")))
+
+(test-equal "setenv/list-extend: multiple elements, prepend"
+  "first:second:one:two"
+  (with-environment-variable "TEST_SETENV" "one:two"
+    (setenv/list-extend "TEST_SETENV" '("first" "second"))
+    (getenv "TEST_SETENV")))
+
+(test-equal "setenv/list-extend: multiple elements, append"
+  "one:two:first:second"
+  (with-environment-variable "TEST_SETENV" "one:two"
+    (setenv/list-extend "TEST_SETENV" '("first" "second") #:prepend? #f)
+    (getenv "TEST_SETENV")))
+
+(test-equal "setenv/list-delete: single deletion"
+  "one:two:three"
+  (with-environment-variable "TEST_SETENV" "bad:one:two:bad:three:bad"
+    (setenv/list-delete "TEST_SETENV" "bad")
+    (getenv "TEST_SETENV")))
+
+(test-equal "setenv/list-delete: multiple deletions"
+  "one:three"
+  (with-environment-variable "TEST_SETENV" "bad:one:two:bad:three:bad"
+    (setenv/list-delete "TEST_SETENV" '("bad" "two"))
+    (getenv "TEST_SETENV")))
+
 (test-equal "search-input-file: exception if not found"
   `((path)
     (file . "does-not-exist"))

base-commit: 693d75e859150601145b7f7303f61d4f48e76927
-- 
2.31.1





             reply	other threads:[~2021-09-03  5:30 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-03  5:29 Sarah Morgensen [this message]
2021-09-16  7:40 ` [bug#50350] [PATCH core-updates] utils: Add helpers for list environment variables Xinglu Chen

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://guix.gnu.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=40d8e502f754fd7bbbe3a916950a85d1f1c57073.1630646500.git.iskarian@mgsn.dev \
    --to=iskarian@mgsn.dev \
    --cc=50350@debbugs.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).