[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: add-to-list with lexical variables
From: |
Pascal J. Bourguignon |
Subject: |
Re: add-to-list with lexical variables |
Date: |
Sat, 08 Jun 2013 16:00:22 +0200 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/24.3 (gnu/linux) |
Hongxu Chen <leftcopy.chx@gmail.com> writes:
> Hi list,
>
> I am writing a snippet to add element into environment variables, and
> it is written as below:
>
> #+BEGIN_SRC elisp
> (defun no-dup-add-env-ele (env env-ele-string)
> (let* ((env-separator (if (string-equal system-type "windows-nt") ";" ":"))
> (env-list (split-string (getenv env) env-separator)))
> (if (string-match-p env-separator env-ele-string)
> (dolist (env-ele (split-string env-ele-string env-separator))
> (add-to-list 'env-list env-ele))
> (add-to-list 'env-list env-ele-string))
> (setenv env (mapconcat 'identity env-list ":"))))
> #+END_SRC
>
> 1. when I set `lexical-binding' to t and byte-compile the file, it
> would report this error:
>
> add-to-list cannot use lexical var `env-list'
>
> 2. And when I using `lexical-let*' instead, there would be an warning:
>
> Warning: assignment to free variable `env-list'
>
> 3. However after resetting `lexical-binding' to nil, byte-compiles well.
>
> So what are the differences?
The difference is not.
(not t) --> nil
(not nil) --> t
or:
not lexical binding is dynamic binding.
not dynamic binding is lexical binding.
Now, to add new elements to a list bound to some place, there's the
pushnew cl operator (a macro). push can be used to push
unconditionnaly, and pop to remove the first element from a list bound
to a place. That's how things have to be done with lexical binding.
(require 'cl)
(defun* pushnew/envvar (env-ele-string env &key (test (function equal)))
(let* ((env-separator (if (member system-type '(windows-nt ms-dos)) ";" ":"))
(env-list (split-string (or (getenv env) "") env-separator t)))
(dolist (env-ele (split-string env-ele-string env-separator))
(pushnew env-ele env-list :test test))
(setenv env (mapconcat 'identity env-list env-separator))))
(defun test/pushnew/envvar ()
(setenv "TEST" nil)
(assert (equal (pushnew/envvar "A:B:C" "TEST")
"C:B:A"))
(assert (equal (getenv "TEST")
"C:B:A"))
(assert (equal (pushnew/envvar "D:E:F" "TEST")
"F:E:D:C:B:A"))
(assert (equal (getenv "TEST")
"F:E:D:C:B:A"))
(assert (equal (pushnew/envvar "G" "TEST")
"G:F:E:D:C:B:A"))
(assert (equal (getenv "TEST")
"G:F:E:D:C:B:A"))
(assert (equal (pushnew/envvar "G:F:E:D:C:B:A" "TEST")
"G:F:E:D:C:B:A"))
(assert (equal (pushnew/envvar "X:F:Y:D:Z:B" "TEST")
"Z:Y:X:G:F:E:D:C:B:A"))
(assert (equal (pushnew/envvar "a:b:c:d" "TEST")
"d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
(assert (equal (pushnew/envvar "x:y:z" "TEST" :test (function equalp))
"d:c:b:a:Z:Y:X:G:F:E:D:C:B:A"))
:success)
(test/pushnew/envvar)
--> :success
You have to add a test argument, since for some environment variables,
and for some values, you may want to do case insensitive, or more
complex comparison.
For example, if you mount a MS-DOS file system on a case sensitive unix file
system, itself mounted a HFS+ case insensitive file system, you will
have to compare parts of the path case sensitively, and parts case
insensitively:
mount /dev/disk1s1 /Volumes/case-sensitive
mount /dev/disk2s1 /Volumes/case-sensitive/mnt/ms-dos
Now, /VOLUMES/case-sensitive/mnt/ms-dos/DESCENT
is the same path as:
/Volumes/case-sensitive/mnt/ms-dos/Descent
but not the same as:
/Volumes/case-sensitive/MNT/ms-dos/descent
Perhaps there are both
/Volumes/case-sensitive/MNT
and:
/Volumes/case-sensitive/mnt
on the case sensitive file system!
so you will have to write:
(pushnew/envvar "/Volumes/case-sensitive/mnt/ms-dos/Descent"
"PATH"
:test (function file-system-sensitive-path-equal-p))
with:
(defun file-system-sensitive-path-equal-p (a b)
(labels ((compare-path (curdir ac bc)
(cond
((null ac) (null bc))
((null bc) nil)
(t
(and (funcall (if (case-sensitive-file-system-at-path-p curdir)
(function equal)
(function equalp))
(car ac) (car ab))
(compare-path (path-append curdir (car ac))
(cdr ac) (cdr bc)))))))
(compare-path "/"
(split-path (expand-file-name a))
(split-path (expand-file-name b)))))
So that if there's already
/Volumes/case-sensitive/mnt/ms-dos/DESCENT
on PATH,
(pushnew/envvar "/VOLUMES/case-sensitive/mnt/ms-dos/Descent"
"PATH"
:test (function file-system-sensitive-path-equal-p))
won't add it, but:
(pushnew/envvar "/Volumes/case-sensitive/mnt/MS-DOS/DESCENT"
"PATH"
:test (function file-system-sensitive-path-equal-p))
will.
--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
You can take the lisper out of the lisp job, but you can't take the lisp out
of the lisper (; -- antifuchs