tpathname.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://hg6vgqziawt5s4dj.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) LICENSE --- tpathname.lisp (37231B) --- 1 ;;;; ------------------------------------------------------------------------- 2 ;;;; Portability layer around Common Lisp pathnames 3 ;; This layer allows for portable manipulation of pathname objects themselves, 4 ;; which all is necessary prior to any access the filesystem or environment. 5 6 (uiop/package:define-package :uiop/pathname 7 (:nicknames :asdf/pathname) ;; deprecated. Used by ceramic 8 (:use :uiop/common-lisp :uiop/package :uiop/utility :uiop/os) 9 (:export 10 ;; Making and merging pathnames, portably 11 #:normalize-pathname-directory-component #:denormalize-pathname-directory-component 12 #:merge-pathname-directory-components #:*unspecific-pathname-type* #:make-pathname* 13 #:make-pathname-component-logical #:make-pathname-logical 14 #:merge-pathnames* 15 #:nil-pathname #:*nil-pathname* #:with-pathname-defaults 16 ;; Predicates 17 #:pathname-equal #:logical-pathname-p #:physical-pathname-p #:physicalize-pathname 18 #:absolute-pathname-p #:relative-pathname-p #:hidden-pathname-p #:file-pathname-p 19 ;; Directories 20 #:pathname-directory-pathname #:pathname-parent-directory-pathname 21 #:directory-pathname-p #:ensure-directory-pathname 22 ;; Parsing filenames 23 #:split-name-type #:parse-unix-namestring #:unix-namestring 24 #:split-unix-namestring-directory-components 25 ;; Absolute and relative pathnames 26 #:subpathname #:subpathname* 27 #:ensure-absolute-pathname 28 #:pathname-root #:pathname-host-pathname 29 #:subpathp #:enough-pathname #:with-enough-pathname #:call-with-enough-pathname 30 ;; Checking constraints 31 #:ensure-pathname ;; implemented in filesystem.lisp to accommodate for existence constraints 32 ;; Wildcard pathnames 33 #:*wild* #:*wild-file* #:*wild-file-for-directory* #:*wild-directory* 34 #:*wild-inferiors* #:*wild-path* #:wilden 35 ;; Translate a pathname 36 #:relativize-directory-component #:relativize-pathname-directory 37 #:directory-separator-for-host #:directorize-pathname-host-device 38 #:translate-pathname* 39 #:*output-translation-function*)) 40 (in-package :uiop/pathname) 41 42 ;;; Normalizing pathnames across implementations 43 44 (with-upgradability () 45 (defun normalize-pathname-directory-component (directory) 46 "Convert the DIRECTORY component from a format usable by the underlying 47 implementation's MAKE-PATHNAME and other primitives to a CLHS-standard format 48 that is a list and not a string." 49 (cond 50 #-(or cmucl sbcl scl) ;; these implementations already normalize directory components. 51 ((stringp directory) `(:absolute ,directory)) 52 ((or (null directory) 53 (and (consp directory) (member (first directory) '(:absolute :relative)))) 54 directory) 55 #+gcl 56 ((consp directory) 57 (cons :relative directory)) 58 (t 59 (parameter-error (compatfmt "~@<~S: Unrecognized pathname directory component ~S~@:>") 60 'normalize-pathname-directory-component directory)))) 61 62 (defun denormalize-pathname-directory-component (directory-component) 63 "Convert the DIRECTORY-COMPONENT from a CLHS-standard format to a format usable 64 by the underlying implementation's MAKE-PATHNAME and other primitives" 65 directory-component) 66 67 (defun merge-pathname-directory-components (specified defaults) 68 "Helper for MERGE-PATHNAMES* that handles directory components" 69 (let ((directory (normalize-pathname-directory-component specified))) 70 (ecase (first directory) 71 ((nil) defaults) 72 (:absolute specified) 73 (:relative 74 (let ((defdir (normalize-pathname-directory-component defaults)) 75 (reldir (cdr directory))) 76 (cond 77 ((null defdir) 78 directory) 79 ((not (eq :back (first reldir))) 80 (append defdir reldir)) 81 (t 82 (loop :with defabs = (first defdir) 83 :with defrev = (reverse (rest defdir)) 84 :while (and (eq :back (car reldir)) 85 (or (and (eq :absolute defabs) (null defrev)) 86 (stringp (car defrev)))) 87 :do (pop reldir) (pop defrev) 88 :finally (return (cons defabs (append (reverse defrev) reldir))))))))))) 89 90 ;; Giving :unspecific as :type argument to make-pathname is not portable. 91 ;; See CLHS make-pathname and 19.2.2.2.3. 92 ;; This will be :unspecific if supported, or NIL if not. 93 (defparameter *unspecific-pathname-type* 94 #+(or abcl allegro clozure cmucl genera lispworks sbcl scl) :unspecific 95 #+(or clasp clisp ecl mkcl gcl xcl #|These haven't been tested:|# cormanlisp mcl mezzano) nil 96 "Unspecific type component to use with the underlying implementation's MAKE-PATHNAME") 97 98 (defun make-pathname* (&rest keys &key directory host device name type version defaults 99 #+scl &allow-other-keys) 100 "Takes arguments like CL:MAKE-PATHNAME in the CLHS, and 101 tries hard to make a pathname that will actually behave as documented, 102 despite the peculiarities of each implementation. DEPRECATED: just use MAKE-PATHNAME." 103 (declare (ignore host device directory name type version defaults)) 104 (apply 'make-pathname keys)) 105 106 (defun make-pathname-component-logical (x) 107 "Make a pathname component suitable for use in a logical-pathname" 108 (typecase x 109 ((eql :unspecific) nil) 110 #+clisp (string (string-upcase x)) 111 #+clisp (cons (mapcar 'make-pathname-component-logical x)) 112 (t x))) 113 114 (defun make-pathname-logical (pathname host) 115 "Take a PATHNAME's directory, name, type and version components, 116 and make a new pathname with corresponding components and specified logical HOST" 117 (make-pathname 118 :host host 119 :directory (make-pathname-component-logical (pathname-directory pathname)) 120 :name (make-pathname-component-logical (pathname-name pathname)) 121 :type (make-pathname-component-logical (pathname-type pathname)) 122 :version (make-pathname-component-logical (pathname-version pathname)))) 123 124 (defun merge-pathnames* (specified &optional (defaults *default-pathname-defaults*)) 125 "MERGE-PATHNAMES* is like MERGE-PATHNAMES except that 126 if the SPECIFIED pathname does not have an absolute directory, 127 then the HOST and DEVICE both come from the DEFAULTS, whereas 128 if the SPECIFIED pathname does have an absolute directory, 129 then the HOST and DEVICE both come from the SPECIFIED pathname. 130 This is what users want on a modern Unix or Windows operating system, 131 unlike the MERGE-PATHNAMES behavior. 132 Also, if either argument is NIL, then the other argument is returned unmodified; 133 this is unlike MERGE-PATHNAMES which always merges with a pathname, 134 by default *DEFAULT-PATHNAME-DEFAULTS*, which cannot be NIL." 135 (when (null specified) (return-from merge-pathnames* defaults)) 136 (when (null defaults) (return-from merge-pathnames* specified)) 137 #+scl 138 (ext:resolve-pathname specified defaults) 139 #-scl 140 (let* ((specified (pathname specified)) 141 (defaults (pathname defaults)) 142 (directory (normalize-pathname-directory-component (pathname-directory specified))) 143 (name (or (pathname-name specified) (pathname-name defaults))) 144 (type (or (pathname-type specified) (pathname-type defaults))) 145 (version (or (pathname-version specified) (pathname-version defaults)))) 146 (labels ((unspecific-handler (p) 147 (if (typep p 'logical-pathname) #'make-pathname-component-logical #'identity))) 148 (multiple-value-bind (host device directory unspecific-handler) 149 (ecase (first directory) 150 ((:absolute) 151 (values (pathname-host specified) 152 (pathname-device specified) 153 directory 154 (unspecific-handler specified))) 155 ((nil :relative) 156 (values (pathname-host defaults) 157 (pathname-device defaults) 158 (merge-pathname-directory-components directory (pathname-directory defaults)) 159 (unspecific-handler defaults)))) 160 (make-pathname :host host :device device :directory directory 161 :name (funcall unspecific-handler name) 162 :type (funcall unspecific-handler type) 163 :version (funcall unspecific-handler version)))))) 164 165 (defun logical-pathname-p (x) 166 "is X a logical-pathname?" 167 (typep x 'logical-pathname)) 168 169 (defun physical-pathname-p (x) 170 "is X a pathname that is not a logical-pathname?" 171 (and (pathnamep x) (not (logical-pathname-p x)))) 172 173 (defun physicalize-pathname (x) 174 "if X is a logical pathname, use translate-logical-pathname on it." 175 ;; Ought to be the same as translate-logical-pathname, except the latter borks on CLISP 176 (let ((p (when x (pathname x)))) 177 (if (logical-pathname-p p) (translate-logical-pathname p) p))) 178 179 (defun nil-pathname (&optional (defaults *default-pathname-defaults*)) 180 "A pathname that is as neutral as possible for use as defaults 181 when merging, making or parsing pathnames" 182 ;; 19.2.2.2.1 says a NIL host can mean a default host; 183 ;; see also "valid physical pathname host" in the CLHS glossary, that suggests 184 ;; strings and lists of strings or :unspecific 185 ;; But CMUCL decides to die on NIL. 186 ;; MCL has issues with make-pathname, nil and defaulting 187 (declare (ignorable defaults)) 188 #.`(make-pathname :directory nil :name nil :type nil :version nil 189 :device (or #+(and mkcl os-unix) :unspecific) 190 :host (or #+cmucl lisp::*unix-host* #+(and mkcl os-unix) "localhost") 191 #+scl ,@'(:scheme nil :scheme-specific-part nil 192 :username nil :password nil :parameters nil :query nil :fragment nil) 193 ;; the default shouldn't matter, but we really want something physical 194 #-mcl ,@'(:defaults defaults))) 195 196 (defvar *nil-pathname* (nil-pathname (physicalize-pathname (user-homedir-pathname))) 197 "A pathname that is as neutral as possible for use as defaults 198 when merging, making or parsing pathnames") 199 200 (defmacro with-pathname-defaults ((&optional defaults) &body body) 201 "Execute BODY in a context where the *DEFAULT-PATHNAME-DEFAULTS* is as specified, 202 where leaving the defaults NIL or unspecified means a (NIL-PATHNAME), except 203 on ABCL, Genera and XCL, where it remains unchanged for it doubles as current-directory." 204 `(let ((*default-pathname-defaults* 205 ,(or defaults 206 #-(or abcl genera xcl) '*nil-pathname* 207 #+(or abcl genera xcl) '*default-pathname-defaults*))) 208 ,@body))) 209 210 211 ;;; Some pathname predicates 212 (with-upgradability () 213 (defun pathname-equal (p1 p2) 214 "Are the two pathnames P1 and P2 reasonably equal in the paths they denote?" 215 (when (stringp p1) (setf p1 (pathname p1))) 216 (when (stringp p2) (setf p2 (pathname p2))) 217 (flet ((normalize-component (x) 218 (unless (member x '(nil :unspecific :newest (:relative)) :test 'equal) 219 x))) 220 (macrolet ((=? (&rest accessors) 221 (flet ((frob (x) 222 (reduce 'list (cons 'normalize-component accessors) 223 :initial-value x :from-end t))) 224 `(equal ,(frob 'p1) ,(frob 'p2))))) 225 (or (and (null p1) (null p2)) 226 (and (pathnamep p1) (pathnamep p2) 227 (and (=? pathname-host) 228 #-(and mkcl os-unix) (=? pathname-device) 229 (=? normalize-pathname-directory-component pathname-directory) 230 (=? pathname-name) 231 (=? pathname-type) 232 #-mkcl (=? pathname-version))))))) 233 234 (defun absolute-pathname-p (pathspec) 235 "If PATHSPEC is a pathname or namestring object that parses as a pathname 236 possessing an :ABSOLUTE directory component, return the (parsed) pathname. 237 Otherwise return NIL" 238 (and pathspec 239 (typep pathspec '(or null pathname string)) 240 (let ((pathname (pathname pathspec))) 241 (and (eq :absolute (car (normalize-pathname-directory-component 242 (pathname-directory pathname)))) 243 pathname)))) 244 245 (defun relative-pathname-p (pathspec) 246 "If PATHSPEC is a pathname or namestring object that parses as a pathname 247 possessing a :RELATIVE or NIL directory component, return the (parsed) pathname. 248 Otherwise return NIL" 249 (and pathspec 250 (typep pathspec '(or null pathname string)) 251 (let* ((pathname (pathname pathspec)) 252 (directory (normalize-pathname-directory-component 253 (pathname-directory pathname)))) 254 (when (or (null directory) (eq :relative (car directory))) 255 pathname)))) 256 257 (defun hidden-pathname-p (pathname) 258 "Return a boolean that is true if the pathname is hidden as per Unix style, 259 i.e. its name starts with a dot." 260 (and pathname (equal (first-char (pathname-name pathname)) #\.))) 261 262 (defun file-pathname-p (pathname) 263 "Does PATHNAME represent a file, i.e. has a non-null NAME component? 264 265 Accepts NIL, a string (converted through PARSE-NAMESTRING) or a PATHNAME. 266 267 Note that this does _not_ check to see that PATHNAME points to an 268 actually-existing file. 269 270 Returns the (parsed) PATHNAME when true" 271 (when pathname 272 (let ((pathname (pathname pathname))) 273 (unless (and (member (pathname-name pathname) '(nil :unspecific "") :test 'equal) 274 (member (pathname-type pathname) '(nil :unspecific "") :test 'equal)) 275 pathname))))) 276 277 278 ;;; Directory pathnames 279 (with-upgradability () 280 (defun pathname-directory-pathname (pathname) 281 "Returns a new pathname with same HOST, DEVICE, DIRECTORY as PATHNAME, 282 and NIL NAME, TYPE and VERSION components" 283 (when pathname 284 (make-pathname :name nil :type nil :version nil :defaults pathname))) 285 286 (defun pathname-parent-directory-pathname (pathname) 287 "Returns a new pathname that corresponds to the parent of the current pathname's directory, 288 i.e. removing one level of depth in the DIRECTORY component. e.g. if pathname is 289 Unix pathname /foo/bar/baz/file.type then return /foo/bar/" 290 (when pathname 291 (make-pathname :name nil :type nil :version nil 292 :directory (merge-pathname-directory-components 293 '(:relative :back) (pathname-directory pathname)) 294 :defaults pathname))) 295 296 (defun directory-pathname-p (pathname) 297 "Does PATHNAME represent a directory? 298 299 A directory-pathname is a pathname _without_ a filename. The three 300 ways that the filename components can be missing are for it to be NIL, 301 :UNSPECIFIC or the empty string. 302 303 Note that this does _not_ check to see that PATHNAME points to an 304 actually-existing directory." 305 (when pathname 306 ;; I tried using Allegro's excl:file-directory-p, but this cannot be done, 307 ;; because it rejects apparently legal pathnames as 308 ;; ill-formed. [2014/02/10:rpg] 309 (let ((pathname (pathname pathname))) 310 (flet ((check-one (x) 311 (member x '(nil :unspecific) :test 'equal))) 312 (and (not (wild-pathname-p pathname)) 313 (check-one (pathname-name pathname)) 314 (check-one (pathname-type pathname)) 315 t))))) 316 317 (defun ensure-directory-pathname (pathspec &optional (on-error 'error)) 318 "Converts the non-wild pathname designator PATHSPEC to directory form." 319 (cond 320 ((stringp pathspec) 321 (ensure-directory-pathname (pathname pathspec))) 322 ((not (pathnamep pathspec)) 323 (call-function on-error (compatfmt "~@<Invalid pathname designator ~S~@:>") pathspec)) 324 ((wild-pathname-p pathspec) 325 (call-function on-error (compatfmt "~@<Can't reliably convert wild pathname ~3i~_~S~@:>") pathspec)) 326 ((directory-pathname-p pathspec) 327 pathspec) 328 (t 329 (handler-case 330 (make-pathname :directory (append (or (normalize-pathname-directory-component 331 (pathname-directory pathspec)) 332 (list :relative)) 333 (list (file-namestring pathspec))) 334 :name nil :type nil :version nil :defaults pathspec) 335 (error (c) (call-function on-error (compatfmt "~@<error while trying to create a directory pathname for ~S: ~A~@:>") pathspec c))))))) 336 337 338 ;;; Parsing filenames 339 (with-upgradability () 340 (declaim (ftype function ensure-pathname)) ; forward reference 341 342 (defun split-unix-namestring-directory-components 343 (unix-namestring &key ensure-directory dot-dot) 344 "Splits the path string UNIX-NAMESTRING, returning four values: 345 A flag that is either :absolute or :relative, indicating 346 how the rest of the values are to be interpreted. 347 A directory path --- a list of strings and keywords, suitable for 348 use with MAKE-PATHNAME when prepended with the flag value. 349 Directory components with an empty name or the name . are removed. 350 Any directory named .. is read as DOT-DOT, or :BACK if it's NIL (not :UP). 351 A last-component, either a file-namestring including type extension, 352 or NIL in the case of a directory pathname. 353 A flag that is true iff the unix-style-pathname was just 354 a file-namestring without / path specification. 355 ENSURE-DIRECTORY forces the namestring to be interpreted as a directory pathname: 356 the third return value will be NIL, and final component of the namestring 357 will be treated as part of the directory path. 358 359 An empty string is thus read as meaning a pathname object with all fields nil. 360 361 Note that colon characters #\: will NOT be interpreted as host specification. 362 Absolute pathnames are only appropriate on Unix-style systems. 363 364 The intention of this function is to support structured component names, 365 e.g., \(:file \"foo/bar\"\), which will be unpacked to relative pathnames." 366 (check-type unix-namestring string) 367 (check-type dot-dot (member nil :back :up)) 368 (if (and (not (find #\/ unix-namestring)) (not ensure-directory) 369 (plusp (length unix-namestring))) 370 (values :relative () unix-namestring t) 371 (let* ((components (split-string unix-namestring :separator "/")) 372 (last-comp (car (last components)))) 373 (multiple-value-bind (relative components) 374 (if (equal (first components) "") 375 (if (equal (first-char unix-namestring) #\/) 376 (values :absolute (cdr components)) 377 (values :relative nil)) 378 (values :relative components)) 379 (setf components (remove-if #'(lambda (x) (member x '("" ".") :test #'equal)) 380 components)) 381 (setf components (substitute (or dot-dot :back) ".." components :test #'equal)) 382 (cond 383 ((equal last-comp "") 384 (values relative components nil nil)) ; "" already removed from components 385 (ensure-directory 386 (values relative components nil nil)) 387 (t 388 (values relative (butlast components) last-comp nil))))))) 389 390 (defun split-name-type (filename) 391 "Split a filename into two values NAME and TYPE that are returned. 392 We assume filename has no directory component. 393 The last . if any separates name and type from from type, 394 except that if there is only one . and it is in first position, 395 the whole filename is the NAME with an empty type. 396 NAME is always a string. 397 For an empty type, *UNSPECIFIC-PATHNAME-TYPE* is returned." 398 (check-type filename string) 399 (assert (plusp (length filename))) 400 (destructuring-bind (name &optional (type *unspecific-pathname-type*)) 401 (split-string filename :max 2 :separator ".") 402 (if (equal name "") 403 (values filename *unspecific-pathname-type*) 404 (values name type)))) 405 406 (defun parse-unix-namestring (name &rest keys &key type defaults dot-dot ensure-directory 407 &allow-other-keys) 408 "Coerce NAME into a PATHNAME using standard Unix syntax. 409 410 Unix syntax is used whether or not the underlying system is Unix; 411 on such non-Unix systems it is reliably usable only for relative pathnames. 412 This function is especially useful to manipulate relative pathnames portably, 413 where it is of crucial to possess a portable pathname syntax independent of the underlying OS. 414 This is what PARSE-UNIX-NAMESTRING provides, and why we use it in ASDF. 415 416 When given a PATHNAME object, just return it untouched. 417 When given NIL, just return NIL. 418 When given a non-null SYMBOL, first downcase its name and treat it as a string. 419 When given a STRING, portably decompose it into a pathname as below. 420 421 #\\/ separates directory components. 422 423 The last #\\/-separated substring is interpreted as follows: 424 1- If TYPE is :DIRECTORY or ENSURE-DIRECTORY is true, 425 the string is made the last directory component, and NAME and TYPE are NIL. 426 if the string is empty, it's the empty pathname with all slots NIL. 427 2- If TYPE is NIL, the substring is a file-namestring, and its NAME and TYPE 428 are separated by SPLIT-NAME-TYPE. 429 3- If TYPE is a string, it is the given TYPE, and the whole string is the NAME. 430 431 Directory components with an empty name or the name \".\" are removed. 432 Any directory named \"..\" is read as DOT-DOT, 433 which must be one of :BACK or :UP and defaults to :BACK. 434 435 HOST, DEVICE and VERSION components are taken from DEFAULTS, 436 which itself defaults to *NIL-PATHNAME*, also used if DEFAULTS is NIL. 437 No host or device can be specified in the string itself, 438 which makes it unsuitable for absolute pathnames outside Unix. 439 440 For relative pathnames, these components (and hence the defaults) won't matter 441 if you use MERGE-PATHNAMES* but will matter if you use MERGE-PATHNAMES, 442 which is an important reason to always use MERGE-PATHNAMES*. 443 444 Arbitrary keys are accepted, and the parse result is passed to ENSURE-PATHNAME 445 with those keys, removing TYPE DEFAULTS and DOT-DOT. 446 When you're manipulating pathnames that are supposed to make sense portably 447 even though the OS may not be Unixish, we recommend you use :WANT-RELATIVE T 448 to throw an error if the pathname is absolute" 449 (block nil 450 (check-type type (or null string (eql :directory))) 451 (when ensure-directory 452 (setf type :directory)) 453 (etypecase name 454 ((or null pathname) (return name)) 455 (symbol 456 (setf name (string-downcase name))) 457 (string)) 458 (multiple-value-bind (relative path filename file-only) 459 (split-unix-namestring-directory-components 460 name :dot-dot dot-dot :ensure-directory (eq type :directory)) 461 (multiple-value-bind (name type) 462 (cond 463 ((or (eq type :directory) (null filename)) 464 (values nil nil)) 465 (type 466 (values filename type)) 467 (t 468 (split-name-type filename))) 469 (apply 'ensure-pathname 470 (make-pathname 471 :directory (unless file-only (cons relative path)) 472 :name name :type type 473 :defaults (or #-mcl defaults *nil-pathname*)) 474 (remove-plist-keys '(:type :dot-dot :defaults) keys)))))) 475 476 (defun unix-namestring (pathname) 477 "Given a non-wild PATHNAME, return a Unix-style namestring for it. 478 If the PATHNAME is NIL or a STRING, return it unchanged. 479 480 This only considers the DIRECTORY, NAME and TYPE components of the pathname. 481 This is a portable solution for representing relative pathnames, 482 But unless you are running on a Unix system, it is not a general solution 483 to representing native pathnames. 484 485 An error is signaled if the argument is not NULL, a STRING or a PATHNAME, 486 or if it is a PATHNAME but some of its components are not recognized." 487 (etypecase pathname 488 ((or null string) pathname) 489 (pathname 490 (with-output-to-string (s) 491 (flet ((err () (parameter-error "~S: invalid unix-namestring ~S" 492 'unix-namestring pathname))) 493 (let* ((dir (normalize-pathname-directory-component (pathname-directory pathname))) 494 (name (pathname-name pathname)) 495 (name (and (not (eq name :unspecific)) name)) 496 (type (pathname-type pathname)) 497 (type (and (not (eq type :unspecific)) type))) 498 (cond 499 ((member dir '(nil :unspecific))) 500 ((eq dir '(:relative)) (princ "./" s)) 501 ((consp dir) 502 (destructuring-bind (relabs &rest dirs) dir 503 (or (member relabs '(:relative :absolute)) (err)) 504 (when (eq relabs :absolute) (princ #\/ s)) 505 (loop :for x :in dirs :do 506 (cond 507 ((member x '(:back :up)) (princ "../" s)) 508 ((equal x "") (err)) 509 ;;((member x '("." "..") :test 'equal) (err)) 510 ((stringp x) (format s "~A/" x)) 511 (t (err)))))) 512 (t (err))) 513 (cond 514 (name 515 (unless (and (stringp name) (or (null type) (stringp type))) (err)) 516 (format s "~A~@[.~A~]" name type)) 517 (t 518 (or (null type) (err))))))))))) 519 520 ;;; Absolute and relative pathnames 521 (with-upgradability () 522 (defun subpathname (pathname subpath &key type) 523 "This function takes a PATHNAME and a SUBPATH and a TYPE. 524 If SUBPATH is already a PATHNAME object (not namestring), 525 and is an absolute pathname at that, it is returned unchanged; 526 otherwise, SUBPATH is turned into a relative pathname with given TYPE 527 as per PARSE-UNIX-NAMESTRING with :WANT-RELATIVE T :TYPE TYPE, 528 then it is merged with the PATHNAME-DIRECTORY-PATHNAME of PATHNAME." 529 (or (and (pathnamep subpath) (absolute-pathname-p subpath)) 530 (merge-pathnames* (parse-unix-namestring subpath :type type :want-relative t) 531 (pathname-directory-pathname pathname)))) 532 533 (defun subpathname* (pathname subpath &key type) 534 "returns NIL if the base pathname is NIL, otherwise like SUBPATHNAME." 535 (and pathname 536 (subpathname (ensure-directory-pathname pathname) subpath :type type))) 537 538 (defun pathname-root (pathname) 539 "return the root directory for the host and device of given PATHNAME" 540 (make-pathname :directory '(:absolute) 541 :name nil :type nil :version nil 542 :defaults pathname ;; host device, and on scl, *some* 543 ;; scheme-specific parts: port username password, not others: 544 . #.(or #+scl '(:parameters nil :query nil :fragment nil)))) 545 546 (defun pathname-host-pathname (pathname) 547 "return a pathname with the same host as given PATHNAME, and all other fields NIL" 548 (make-pathname :directory nil 549 :name nil :type nil :version nil :device nil 550 :defaults pathname ;; host device, and on scl, *some* 551 ;; scheme-specific parts: port username password, not others: 552 . #.(or #+scl '(:parameters nil :query nil :fragment nil)))) 553 554 (defun ensure-absolute-pathname (path &optional defaults (on-error 'error)) 555 "Given a pathname designator PATH, return an absolute pathname as specified by PATH 556 considering the DEFAULTS, or, if not possible, use CALL-FUNCTION on the specified ON-ERROR behavior, 557 with a format control-string and other arguments as arguments" 558 (cond 559 ((absolute-pathname-p path)) 560 ((stringp path) (ensure-absolute-pathname (pathname path) defaults on-error)) 561 ((not (pathnamep path)) (call-function on-error "not a valid pathname designator ~S" path)) 562 ((let ((default-pathname (if (pathnamep defaults) defaults (call-function defaults)))) 563 (or (if (absolute-pathname-p default-pathname) 564 (absolute-pathname-p (merge-pathnames* path default-pathname)) 565 (call-function on-error "Default pathname ~S is not an absolute pathname" 566 default-pathname)) 567 (call-function on-error "Failed to merge ~S with ~S into an absolute pathname" 568 path default-pathname)))) 569 (t (call-function on-error 570 "Cannot ensure ~S is evaluated as an absolute pathname with defaults ~S" 571 path defaults)))) 572 573 (defun subpathp (maybe-subpath base-pathname) 574 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that 575 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH." 576 (and (pathnamep maybe-subpath) (pathnamep base-pathname) 577 (absolute-pathname-p maybe-subpath) (absolute-pathname-p base-pathname) 578 (directory-pathname-p base-pathname) (not (wild-pathname-p base-pathname)) 579 (pathname-equal (pathname-root maybe-subpath) (pathname-root base-pathname)) 580 (with-pathname-defaults (*nil-pathname*) 581 (let ((enough (enough-namestring maybe-subpath base-pathname))) 582 (and (relative-pathname-p enough) (pathname enough)))))) 583 584 (defun enough-pathname (maybe-subpath base-pathname) 585 "if MAYBE-SUBPATH is a pathname that is under BASE-PATHNAME, return a pathname object that 586 when used with MERGE-PATHNAMES* with defaults BASE-PATHNAME, returns MAYBE-SUBPATH." 587 (let ((sub (when maybe-subpath (pathname maybe-subpath))) 588 (base (when base-pathname (ensure-absolute-pathname (pathname base-pathname))))) 589 (or (and base (subpathp sub base)) sub))) 590 591 (defun call-with-enough-pathname (maybe-subpath defaults-pathname thunk) 592 "In a context where *DEFAULT-PATHNAME-DEFAULTS* is bound to DEFAULTS-PATHNAME (if not null, 593 or else to its current value), call THUNK with ENOUGH-PATHNAME for MAYBE-SUBPATH 594 given DEFAULTS-PATHNAME as a base pathname." 595 (let ((enough (enough-pathname maybe-subpath defaults-pathname)) 596 (*default-pathname-defaults* (or defaults-pathname *default-pathname-defaults*))) 597 (funcall thunk enough))) 598 599 (defmacro with-enough-pathname ((pathname-var &key (pathname pathname-var) 600 (defaults *default-pathname-defaults*)) 601 &body body) 602 "Shorthand syntax for CALL-WITH-ENOUGH-PATHNAME" 603 `(call-with-enough-pathname ,pathname ,defaults #'(lambda (,pathname-var) ,@body)))) 604 605 606 ;;; Wildcard pathnames 607 (with-upgradability () 608 (defparameter *wild* (or #+cormanlisp "*" :wild) 609 "Wild component for use with MAKE-PATHNAME") 610 (defparameter *wild-directory-component* (or :wild) 611 "Wild directory component for use with MAKE-PATHNAME") 612 (defparameter *wild-inferiors-component* (or :wild-inferiors) 613 "Wild-inferiors directory component for use with MAKE-PATHNAME") 614 (defparameter *wild-file* 615 (make-pathname :directory nil :name *wild* :type *wild* 616 :version (or #-(or allegro abcl xcl) *wild*)) 617 "A pathname object with wildcards for matching any file with TRANSLATE-PATHNAME") 618 (defparameter *wild-file-for-directory* 619 (make-pathname :directory nil :name *wild* :type (or #-(or clisp gcl) *wild*) 620 :version (or #-(or allegro abcl clisp gcl xcl) *wild*)) 621 "A pathname object with wildcards for matching any file with DIRECTORY") 622 (defparameter *wild-directory* 623 (make-pathname :directory `(:relative ,*wild-directory-component*) 624 :name nil :type nil :version nil) 625 "A pathname object with wildcards for matching any subdirectory") 626 (defparameter *wild-inferiors* 627 (make-pathname :directory `(:relative ,*wild-inferiors-component*) 628 :name nil :type nil :version nil) 629 "A pathname object with wildcards for matching any recursive subdirectory") 630 (defparameter *wild-path* 631 (merge-pathnames* *wild-file* *wild-inferiors*) 632 "A pathname object with wildcards for matching any file in any recursive subdirectory") 633 634 (defun wilden (path) 635 "From a pathname, return a wildcard pathname matching any file in any subdirectory of given pathname's directory" 636 (merge-pathnames* *wild-path* path))) 637 638 639 ;;; Translate a pathname 640 (with-upgradability () 641 (defun relativize-directory-component (directory-component) 642 "Given the DIRECTORY-COMPONENT of a pathname, return an otherwise similar relative directory component" 643 (let ((directory (normalize-pathname-directory-component directory-component))) 644 (cond 645 ((stringp directory) 646 (list :relative directory)) 647 ((eq (car directory) :absolute) 648 (cons :relative (cdr directory))) 649 (t 650 directory)))) 651 652 (defun relativize-pathname-directory (pathspec) 653 "Given a PATHNAME, return a relative pathname with otherwise the same components" 654 (let ((p (pathname pathspec))) 655 (make-pathname 656 :directory (relativize-directory-component (pathname-directory p)) 657 :defaults p))) 658 659 (defun directory-separator-for-host (&optional (pathname *default-pathname-defaults*)) 660 "Given a PATHNAME, return the character used to delimit directory names on this host and device." 661 (let ((foo (make-pathname :directory '(:absolute "FOO") :defaults pathname))) 662 (last-char (namestring foo)))) 663 664 #-scl 665 (defun directorize-pathname-host-device (pathname) 666 "Given a PATHNAME, return a pathname that has representations of its HOST and DEVICE components 667 added to its DIRECTORY component. This is useful for output translations." 668 (os-cond 669 ((os-unix-p) 670 (when (physical-pathname-p pathname) 671 (return-from directorize-pathname-host-device pathname)))) 672 (let* ((root (pathname-root pathname)) 673 (wild-root (wilden root)) 674 (absolute-pathname (merge-pathnames* pathname root)) 675 (separator (directory-separator-for-host root)) 676 (root-namestring (namestring root)) 677 (root-string 678 (substitute-if #\/ 679 #'(lambda (x) (or (eql x #\:) 680 (eql x separator))) 681 root-namestring))) 682 (multiple-value-bind (relative path filename) 683 (split-unix-namestring-directory-components root-string :ensure-directory t) 684 (declare (ignore relative filename)) 685 (let ((new-base (make-pathname :defaults root :directory `(:absolute ,@path)))) 686 (translate-pathname absolute-pathname wild-root (wilden new-base)))))) 687 688 #+scl 689 (defun directorize-pathname-host-device (pathname) 690 (let ((scheme (ext:pathname-scheme pathname)) 691 (host (pathname-host pathname)) 692 (port (ext:pathname-port pathname)) 693 (directory (pathname-directory pathname))) 694 (flet ((specificp (x) (and x (not (eq x :unspecific))))) 695 (if (or (specificp port) 696 (and (specificp host) (plusp (length host))) 697 (specificp scheme)) 698 (let ((prefix "")) 699 (when (specificp port) 700 (setf prefix (format nil ":~D" port))) 701 (when (and (specificp host) (plusp (length host))) 702 (setf prefix (strcat host prefix))) 703 (setf prefix (strcat ":" prefix)) 704 (when (specificp scheme) 705 (setf prefix (strcat scheme prefix))) 706 (assert (and directory (eq (first directory) :absolute))) 707 (make-pathname :directory `(:absolute ,prefix ,@(rest directory)) 708 :defaults pathname))) 709 pathname))) 710 711 (defun* (translate-pathname*) (path absolute-source destination &optional root source) 712 "A wrapper around TRANSLATE-PATHNAME to be used by the ASDF output-translations facility. 713 PATH is the pathname to be translated. 714 ABSOLUTE-SOURCE is an absolute pathname to use as source for translate-pathname, 715 DESTINATION is either a function, to be called with PATH and ABSOLUTE-SOURCE, 716 or a relative pathname, to be merged with ROOT and used as destination for translate-pathname 717 or an absolute pathname, to be used as destination for translate-pathname. 718 In that last case, if ROOT is non-NIL, PATH is first transformated by DIRECTORIZE-PATHNAME-HOST-DEVICE." 719 (declare (ignore source)) 720 (cond 721 ((functionp destination) 722 (funcall destination path absolute-source)) 723 ((eq destination t) 724 path) 725 ((not (pathnamep destination)) 726 (parameter-error "~S: Invalid destination" 'translate-pathname*)) 727 ((not (absolute-pathname-p destination)) 728 (translate-pathname path absolute-source (merge-pathnames* destination root))) 729 (root 730 (translate-pathname (directorize-pathname-host-device path) absolute-source destination)) 731 (t 732 (translate-pathname path absolute-source destination)))) 733 734 (defvar *output-translation-function* 'identity 735 "Hook for output translations. 736 737 This function needs to be idempotent, so that actions can work 738 whether their inputs were translated or not, 739 which they will be if we are composing operations. e.g. if some 740 create-lisp-op creates a lisp file from some higher-level input, 741 you need to still be able to use compile-op on that lisp file."))