all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: "Pascal J. Bourguignon" <pjb@informatimago.com>
To: help-gnu-emacs@gnu.org
Subject: Re: add-to-list with lexical variables
Date: Sat, 08 Jun 2013 16:00:22 +0200	[thread overview]
Message-ID: <87r4gcln2h.fsf@kuiper.lan.informatimago.com> (raw)
In-Reply-To: 87txl8u68b.fsf@gmail.com

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




  reply	other threads:[~2013-06-08 14:00 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-06-08 12:39 add-to-list with lexical variables Hongxu Chen
2013-06-08 14:00 ` Pascal J. Bourguignon [this message]
2013-06-08 15:19   ` Hongxu Chen
2013-06-08 20:34     ` Stefan Monnier
     [not found]   ` <mailman.1222.1370704797.22516.help-gnu-emacs@gnu.org>
2013-06-08 17:08     ` Pascal J. Bourguignon
2013-06-09  3:18       ` Hongxu 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

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

  git send-email \
    --in-reply-to=87r4gcln2h.fsf@kuiper.lan.informatimago.com \
    --to=pjb@informatimago.com \
    --cc=help-gnu-emacs@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 external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.