env.el 11.6 KB
Newer Older
1
;;; env.el --- functions to manipulate environment variables
Eric S. Raymond's avatar
Eric S. Raymond committed
2

3
;; Copyright (C) 1991, 1994, 2000, 2001, 2002, 2003, 2004,
Glenn Morris's avatar
Glenn Morris committed
4
;;   2005, 2006, 2007 Free Software Foundation, Inc.
Eric S. Raymond's avatar
Eric S. Raymond committed
5

Eric S. Raymond's avatar
Eric S. Raymond committed
6
;; Maintainer: FSF
7
;; Keywords: processes, unix
Eric S. Raymond's avatar
Eric S. Raymond committed
8

Erik Naggum's avatar
Erik Naggum committed
9
;; This file is part of GNU Emacs.
Jim Blandy's avatar
Jim Blandy committed
10

Erik Naggum's avatar
Erik Naggum committed
11 12 13 14
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
Jim Blandy's avatar
Jim Blandy committed
15

Erik Naggum's avatar
Erik Naggum committed
16 17 18 19
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
Jim Blandy's avatar
Jim Blandy committed
20

Erik Naggum's avatar
Erik Naggum committed
21 22
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
Lute Kamstra's avatar
Lute Kamstra committed
23 24
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
Jim Blandy's avatar
Jim Blandy committed
25

26 27
;;; Commentary:

Erik Naggum's avatar
Erik Naggum committed
28 29 30 31
;; UNIX processes inherit a list of name-to-string associations from their
;; parents called their `environment'; these are commonly used to control
;; program options.  This package permits you to set environment variables
;; to be passed to any sub-process run under Emacs.
32

Dave Love's avatar
Dave Love committed
33 34 35 36
;; Note that the environment string `process-environment' is not
;; decoded, but the args of `setenv' and `getenv' are normally
;; multibyte text and get coding conversion.

Eric S. Raymond's avatar
Eric S. Raymond committed
37 38
;;; Code:

39 40
(eval-when-compile (require 'cl))

41 42 43 44 45
;; History list for environment variable names.
(defvar read-envvar-name-history nil)

(defun read-envvar-name (prompt &optional mustmatch)
  "Read environment variable name, prompting with PROMPT.
46 47
Optional second arg MUSTMATCH, if non-nil, means require existing envvar name.
If it is also not t, RET does not exit if it does non-null completion."
48
  (completing-read prompt
Dave Love's avatar
Dave Love committed
49 50 51 52 53 54 55 56
		   (mapcar (lambda (enventry)
			     (list (if enable-multibyte-characters
				       (decode-coding-string
					(substring enventry 0
						   (string-match "=" enventry))
					locale-coding-system t)
				     (substring enventry 0
						(string-match "=" enventry)))))
57
			   (append process-environment
58
				   (frame-parameter (frame-with-environment) 'environment)))
59 60 61 62 63
		   nil mustmatch nil 'read-envvar-name-history))

;; History list for VALUE argument to setenv.
(defvar setenv-history nil)

64 65 66 67 68 69

(defun substitute-env-vars (string)
  "Substitute environment variables referred to in STRING.
`$FOO' where FOO is an environment variable name means to substitute
the value of that variable.  The variable name should be terminated
with a character not a letter, digit or underscore; otherwise, enclose
70 71 72 73
the entire variable name in braces.  For instance, in `ab$cd-x',
`$cd' is treated as an environment variable.

Use `$$' to insert a single dollar sign."
74
  (let ((start 0))
75
    (while (string-match
Dave Love's avatar
Dave Love committed
76
	    (eval-when-compile
77
	      (rx (or (and "$" (submatch (1+ (regexp "[[:alnum:]_]"))))
Dave Love's avatar
Dave Love committed
78 79
		      (and "${" (submatch (minimal-match (0+ anything))) "}")
		      "$$")))
80 81 82 83 84 85 86 87 88 89 90 91 92 93
	    string start)
      (cond ((match-beginning 1)
	     (let ((value (getenv (match-string 1 string))))
	       (setq string (replace-match (or value "") t t string)
		     start (+ (match-beginning 0) (length value)))))
	    ((match-beginning 2)
	     (let ((value (getenv (match-string 2 string))))
	       (setq string (replace-match (or value "") t t string)
		     start (+ (match-beginning 0) (length value)))))
	    (t
	     (setq string (replace-match "$" t t string)
		   start (+ (match-beginning 0) 1)))))
    string))

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127

(defun setenv-internal (env variable value keep-empty)
  "Set VARIABLE to VALUE in ENV, adding empty entries if KEEP-EMPTY.
Changes ENV by side-effect, and returns its new value."
  (let ((pattern (concat "\\`" (regexp-quote variable) "\\(=\\|\\'\\)"))
	(case-fold-search nil)
	(scan env)
	prev found)
    ;; Handle deletions from the beginning of the list specially.
    (if (and (null value)
	     (not keep-empty)
	     env
	     (stringp (car env))
	     (string-match pattern (car env)))
	(cdr env)
      ;; Try to find existing entry for VARIABLE in ENV.
      (while (and scan (stringp (car scan)))
	(when (string-match pattern (car scan))
	  (if value
	      (setcar scan (concat variable "=" value))
	    (if keep-empty
		(setcar scan variable)
	      (setcdr prev (cdr scan))))
	  (setq found t
		scan nil))
	(setq prev scan
	      scan (cdr scan)))
      (if (and (not found) (or value keep-empty))
	  (cons (if value
		    (concat variable "=" value)
		  variable)
		env)
	env))))

128
;; Fixme: Should the environment be recoded if LC_CTYPE &c is set?
129

Karoly Lorentey's avatar
Karoly Lorentey committed
130
(defun setenv (variable &optional value substitute-env-vars frame)
Jim Blandy's avatar
Jim Blandy committed
131
  "Set the value of the environment variable named VARIABLE to VALUE.
132
VARIABLE should be a string.  VALUE is optional; if not provided or
133
nil, the environment variable VARIABLE will be removed.
134

135 136 137 138 139
Interactively, a prefix argument means to unset the variable, and
otherwise the current value (if any) of the variable appears at
the front of the history list when you type in the new value.
This function always replaces environment variables in the new
value when called interactively.
140

141 142 143 144
SUBSTITUTE-ENV-VARS, if non-nil, means to substitute environment
variables in VALUE with `substitute-env-vars', which see.
This is normally used only for interactive calls.

145 146 147 148 149 150 151 152 153 154
If optional parameter FRAME is non-nil, this function modifies
only the frame-local value of VARIABLE on FRAME, ignoring
`process-environment'.  Note that frames on the same terminal
device usually share their environment, so calling `setenv' on
one of them affects the others as well.

If FRAME is nil, `setenv' changes the global value of VARIABLE by
modifying `process-environment'.  Note that the global value
overrides any frame-local values.

155 156 157
The return value is the new value of VARIABLE, or nil if
it was removed from the environment.

Dave Love's avatar
Dave Love committed
158 159
As a special case, setting variable `TZ' calls `set-time-zone-rule' as
a side-effect."
160 161
  (interactive
   (if current-prefix-arg
162
       (list (read-envvar-name "Clear environment variable: " 'exact) nil)
163 164 165
     (let* ((var (read-envvar-name "Set environment variable: " nil))
	    (value (getenv var)))
       (when value
Kim F. Storm's avatar
Kim F. Storm committed
166
	 (add-to-history 'setenv-history value))
167
       ;; Here finally we specify the args to give call setenv with.
168
       (list var
169 170 171 172
	     (read-from-minibuffer (format "Set %s to value: " var)
				   nil nil nil 'setenv-history
				   value)
	     t))))
Dave Love's avatar
Dave Love committed
173
  (if (and (multibyte-string-p variable) locale-coding-system)
Kenichi Handa's avatar
Kenichi Handa committed
174 175 176 177 178
      (let ((codings (find-coding-systems-string (concat variable value))))
	(unless (or (eq 'undecided (car codings))
		    (memq (coding-system-base locale-coding-system) codings))
	  (error "Can't encode `%s=%s' with `locale-coding-system'"
		 variable (or value "")))))
179 180 181
  (and value
       substitute-env-vars
       (setq value (substitute-env-vars value)))
Dave Love's avatar
Dave Love committed
182 183 184 185
  (if (multibyte-string-p variable)
      (setq variable (encode-coding-string variable locale-coding-system)))
  (if (and value (multibyte-string-p value))
      (setq value (encode-coding-string value locale-coding-system)))
Jim Blandy's avatar
Jim Blandy committed
186
  (if (string-match "=" variable)
187
      (error "Environment variable name `%s' contains `='" variable))
188 189 190 191 192
  (if (string-equal "TZ" variable)
      (set-time-zone-rule value))
  (if (null frame)
      (setq process-environment (setenv-internal process-environment
						 variable value t))
193
    (setq frame (frame-with-environment frame))
194 195 196 197
    (set-frame-parameter frame 'environment
			 (setenv-internal (frame-parameter frame 'environment)
					  variable value nil)))
  value)
Jim Blandy's avatar
Jim Blandy committed
198

199
(defun getenv (variable &optional frame)
200 201 202 203
  "Get the value of environment variable VARIABLE.
VARIABLE should be a string.  Value is nil if VARIABLE is undefined in
the environment.  Otherwise, value is a string.

204
If optional parameter FRAME is non-nil, then it should be a
205 206
frame.  This function will look up VARIABLE in its 'environment
parameter.
207

208
Otherwise, this function searches `process-environment' for
209 210
VARIABLE.  If it is not found there, then it continues the search
in the environment list of the selected frame."
211
  (interactive (list (read-envvar-name "Get environment variable: " t)))
Dave Love's avatar
Dave Love committed
212 213 214
  (let ((value (getenv-internal (if (multibyte-string-p variable)
				    (encode-coding-string
				     variable locale-coding-system)
215 216
				  variable)
				frame)))
Dave Love's avatar
Dave Love committed
217 218
    (if (and enable-multibyte-characters value)
	(setq value (decode-coding-string value locale-coding-system)))
219 220 221 222
    (when (interactive-p)
      (message "%s" (if value value "Not set")))
    value))

223
(defun environment (&optional frame)
224 225 226 227 228 229
  "Return a list of environment variables with their values.
Each entry in the list is a string of the form NAME=VALUE.

The returned list can not be used to change environment
variables, only read them.  See `setenv' to do that.

230 231 232
If optional parameter FRAME is non-nil, then it should be a
frame.  The function returns the environment of that frame.

233 234 235
The list is constructed by concatenating the elements of
`process-environment' and the 'environment parameter of the
selected frame, and removing duplicated and empty values.
236 237 238 239

Non-ASCII characters are encoded according to the initial value of
`locale-coding-system', i.e. the elements must normally be decoded for use.
See `setenv' and `getenv'."
240
  (let* ((env (append process-environment
241
		      (frame-parameter (frame-with-environment frame)
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
				       'environment)
		      nil))
	 (scan env)
	 prev seen)
    ;; Remove unset variables from the beginning of the list.
    (while (and env
		(or (not (stringp (car env)))
		    (not (string-match "=" (car env)))))
      (or (member (car env) seen)
	  (setq seen (cons (car env) seen)))
      (setq env (cdr env)
	    scan env))
    (let (name)
      (while scan
	(cond ((or (not (stringp (car scan)))
		   (not (string-match "=" (car scan))))
258
	       ;; Unset variable.
259 260 261 262 263 264
	       (or (member (car scan) seen)
		   (setq seen (cons (car scan) seen)))
	       (setcdr prev (cdr scan)))
	      ((member (setq name (substring (car scan) 0 (string-match "=" (car scan)))) seen)
	       ;; Duplicated variable.
	       (setcdr prev (cdr scan)))
265 266
	      (t
	       ;; New variable.
267 268 269
	       (setq seen (cons name seen))))
	(setq prev scan
	      scan (cdr scan))))
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    env))

(defmacro let-environment (varlist &rest body)
  "Evaluate BODY with environment variables set according to VARLIST.
The environment variables are then restored to their previous
values.
The value of the last form in BODY is returned.

Each element of VARLIST is either a string (which variable is
then removed from the environment), or a list (NAME
VALUEFORM) (which sets NAME to the value of VALUEFORM, a string).
All the VALUEFORMs are evaluated before any variables are set."
  (declare (indent 2))
  (let ((old-env (make-symbol "old-env"))
	(name (make-symbol "name"))
	(value (make-symbol "value"))
	(entry (make-symbol "entry"))
	(frame (make-symbol "frame")))
    `(let ((,frame (selected-frame))
	    ,old-env)
       ;; Evaluate VALUEFORMs and replace them in VARLIST with their values.
       (dolist (,entry ,varlist)
	 (unless (stringp ,entry)
	   (if (cdr (cdr ,entry))
	       (error "`let-environment' bindings can have only one value-form"))
	   (setcdr ,entry (eval (cadr ,entry)))))
       ;; Set the variables.
       (dolist (,entry ,varlist)
	 (let ((,name (if (stringp ,entry) ,entry (car ,entry)))
	       (,value (if (consp ,entry) (cdr ,entry))))
	   (setq ,old-env (cons (cons ,name (getenv ,name)) ,old-env))
	   (setenv ,name ,value)))
       (unwind-protect
	   (progn ,@body)
	 ;; Restore old values.
	 (with-selected-frame (if (frame-live-p ,frame)
				  ,frame
				(selected-frame))
	   (dolist (,entry ,old-env)
	     (setenv (car ,entry) (cdr ,entry))))))))

311 312
(provide 'env)

Miles Bader's avatar
Miles Bader committed
313
;;; arch-tag: b7d6a8f7-bc81-46db-8e39-8d721d4ed0b8
314
;;; env.el ends here