File: posix.lisp

package info (click to toggle)
mcvs 1.0.13-8
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 668 kB
  • ctags: 648
  • sloc: lisp: 5,091; ansic: 223; sh: 190; makefile: 58
file content (203 lines) | stat: -rw-r--r-- 7,199 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
;;; This source file is part of the Meta-CVS program, 
;;; which is distributed under the GNU license.
;;; Copyright 2002 Kaz Kylheku

(require "split")
(provide "posix")

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defconstant *up-dir* "..")
  (defconstant *this-dir* ".")
  (defconstant *path-sep* "/"))

(defvar *mcvs-editor* nil)

(defconstant *argument-limit* (* 64 1024))

(defun canonicalize-path (path)
"Simplifies a POSIX path by eliminating . components, splicing out as many ..
components as possible, and condensing multiple slashes. A trailing slash is
guaranteed to be preserved, if it follows something that could be a file or
directory.  Two values are returned, the simplified path and a boolean value
which is true if there are any .. components that could not be spliced out."
  (let ((split-path (split-fields path "/"))
        uncanceled-up)

    ;; First, if the path has at least two components,
    ;; replace the first empty one with the symbol :root
    ;; and the last empty one with :dir. These indicate a
    ;; leading and trailing /
    (when (and (> (length split-path) 1))
      (when (string= (first split-path) "")
        (setf (first split-path) :root))
      (when (string= (first (last split-path)) "")
        (setf (first (last split-path)) :dir)))

    ;; Next, squash out all of the . and empty components,
    ;; and replace .. components with :up symbol.
    (setf split-path (mapcan #'(lambda (item)
				 (cond
				   ((string= item "") nil)
				   ((string= item ".") nil)
				   ((string= item "..") (list :up))
				   (t (list item)))) 
			     split-path))
    (let (folded-path)
      ;; Now, we use a pushdown automaton to reduce the .. paths
      ;; The remaining stack is the reversed path.
      (dolist (item split-path)
        (case item
	  ((:up)
	    (case (first folded-path)
	      ((:root)) ;; do nothing
	      ((:up nil) (push item folded-path) (setf uncanceled-up t))
	      (otherwise (pop folded-path))))
	  ((:dir)
	    (case (first folded-path)
	      ((:root :up nil))
	      (otherwise (push (concatenate 'string (pop folded-path) "/")
	                       folded-path))))
          (otherwise
	    (push item folded-path))))
      (setf split-path (nreverse folded-path)))

    ;; If there are at least two components, remove a leading :root
    ;; and add a / to the first component. If there are 0 components
    ;; add a "." component.
    (if (zerop (length split-path))
      (push "." split-path)
      (when (eq (first split-path) :root)
	(pop split-path)
	(push (concatenate 'string "/" (or (pop split-path) "")) split-path)))

    ;; Map remaining symbols back to strings
    (setf split-path (mapcar #'(lambda (item)
				 (case item
				   ((:up) "..")
				   (otherwise item))) split-path))

    ;; Convert back to text
    (values (reduce #'(lambda (x y) (concatenate 'string x "/" y)) split-path)
            uncanceled-up)))

(defun basename (path)
"Splits the path into base name and directory, returned as two values.
If the path is / then . and / are returned. The rightmost slash is
used to determine the split between the path and the base name. If there
is a rightmost slash, then everything up to but not including that slash is
returned as the directory (second) value, and everything to the right is 
returned as the base name (first) value. If there is no rightmost slash,
then the directory is returned as NIL, and the path is the entire base name.
If the path has a trailing slash, then that trailing slash is part of the base
name, and does not count as the rightmost slash."
  (let* ((pos1 (position #\/ path :from-end t))
         (pos2 (position #\/ path :end pos1 :from-end t)))
    (cond
      ((string= path "/") 
        (values "." "/"))
      ((null pos1) 
        (values path nil))
      ((= (1+ pos1) (length path))
        (if (null pos2)
          (values path nil)
	  (values (subseq path (1+ pos2)) (subseq path 0 pos2))))
      (t
        (values (subseq path (1+ pos1)) (subseq path 0 pos1))))))

(defun suffix (path &optional (separator-char #\.))
  (multiple-value-bind (name dir) 
		       (basename path)
    (let ((pos (position separator-char name)))
      (cond
        ((eql pos 0)
	   (values nil name dir))
	(pos
	   (values (subseq name (1+ pos)) (subseq name 0 pos) dir))
	(t (values nil name dir))))))

(declaim (inline path-equal))
(defun path-equal (p1 p2)
  (string= p1 p2))

(defun path-prefix-equal (shorter longer)
  (let ((ls (length shorter)) (ll (length longer)))
    (cond
      ((> ls ll) nil)
      ((not (string= shorter longer :end2 ls)) nil)
      ((= ls ll) t)
      ((and (> ls 0) 
	    (char-equal (char shorter (1- ls)) #\/)
	    (char-equal (char longer (1- ls))) #\/) t)
      ((char-equal (char longer ls) #\/) t)
      (t nil))))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun path-cat (first-component &rest components)
    (reduce #'(lambda (x y) (format nil "~a/~a" x y)) components
	    :initial-value first-component)))

(defun path-absolute-p (path)
  (unless (zerop (length path))
    (char= (char path 0) #\/)))

(defun parse-posix-namestring (path)
  (let ((split-path (split-fields path "/")))
    (let ((dir (butlast split-path))
	  (name (first (last split-path))))
      (apply #'make-pathname 
	     `(,@(when dir 
		   `(:directory ,(if (string= "" (first dir))
				   `(:absolute ,@(rest dir))
				   `(:relative ,@dir))))
	       ,@(when name
		  `(:name ,name)))))))

(defun arglist-to-command-string (arglist)
"Convert list of strings, assumed to be an argument vector, into
a single command string that can be submitted to a POSIX command 
interpreter. This requires escaping of all shell meta-characters."
  (let ((command (make-array '(1024)
			     :element-type 'character
			     :adjustable t
			     :fill-pointer 0)))
    (dolist (arg arglist command)
      (dotimes (i (length arg))
	(let ((ch (char arg i)))
	  (when (find ch #(#\' #\" #\* #\[ #\] #\? 
			   #\$ #\{ #\} #\" #\space #\tab
			   #\( #\) #\< #\> #\| #\; #\&))
	    (vector-push-extend #\\ command))
	  (vector-push-extend ch command)))
	(vector-push-extend #\space command))))

(defun execute-program-xargs (fixed-args &optional extra-args fixed-trail-args)
  (let* ((fixed-size (reduce #'(lambda (x y)
				 (+ x (length y) 1))
			     (append fixed-args fixed-trail-args)
			     :initial-value 0))
	 (size fixed-size))
    (if extra-args
      (let ((chopped-arg ())
	    (combined-status t))
	(dolist (arg extra-args)
	  (push arg chopped-arg)
	  (when (> (incf size (1+ (length arg))) *argument-limit*)
	    (setf combined-status 
		  (and combined-status
		       (execute-program (append fixed-args 
						(nreverse chopped-arg)
						fixed-trail-args))))
	    (setf chopped-arg nil)
	    (setf size fixed-size)))
	(when chopped-arg
	  (execute-program (append fixed-args (nreverse chopped-arg)
				   fixed-trail-args)))
	combined-status)
      (execute-program (append fixed-args fixed-trail-args)))))

(defun invoke-editor-on (name)
  (let ((editor (or *mcvs-editor* 
		    (env-lookup "CVSEDITOR")
		    (env-lookup "VISUAL")
		    (env-lookup "EDITOR" "vi"))))
    (execute-program `(,editor ,name))))