Module: system-internals Synopsis: Abstract modeling of locations Author: Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define constant $microsoft-separators = #['\\', '/']; define constant $extension-separator = '.'; define constant $volume-separator = ':'; define constant $unc-prefix = "\\\\"; define constant $alternative-unc-prefix = "//"; define sealed abstract class () end class ; define sealed class () sealed constant slot locator-host :: , required-init-keyword: host:; end class ; define sealed method make (class == , #key name :: false-or() = #f, host :: false-or() = #f) => (locator :: ) next-method(class, host: host | name) end method make; define sealed method locator-name (locator :: ) => (name :: ) locator.locator-host end method locator-name; define sealed method \= (locator1 :: , locator2 :: ) => (equal? :: ) case-insensitive=(locator1.locator-host, locator2.locator-host) end method \=; define sealed method locator-as-string (class :: subclass(), locator :: ) => (string :: ) concatenate-as(class, $unc-prefix, locator.locator-host) end method locator-as-string; define sealed class () sealed constant slot locator-drive :: , required-init-keyword: drive:; end class ; define sealed method make (class == , #key name :: false-or() = #f, volume :: false-or() = name, drive :: false-or() = #f) => (locator :: ) if (volume) unless (volume.size == 1) locator-error("Invalid drive specification %=", volume) end; next-method(class, drive: volume[0]) else next-method() end end method make; define sealed method locator-volume (locator :: ) => (volume :: ) make(, size: 1, fill: locator.locator-drive) end method locator-volume; define sealed method locator-name (locator :: ) => (name :: ) locator.locator-volume end method locator-name; define sealed method \= (locator1 :: , locator2 :: ) => (equal? :: ) case-insensitive=(locator1.locator-drive, locator2.locator-drive) end method \=; define sealed method locator-as-string (class :: subclass(), locator :: ) => (string :: ) concatenate-as(class, make(, size: 1, fill: locator.locator-drive), delimiter-to-string($volume-separator)) end method locator-as-string; define sealed abstract class () end class ; define sealed method string-as-locator (class == , string :: ) => (locator :: ) let pos = find-delimiters-from-end(string, $microsoft-separators); if (pos == string.size - 1) string-as-locator(, string) else string-as-locator(, string) end end method string-as-locator; define sealed class (, ) sealed constant slot locator-server :: false-or() = #f, init-keyword: server:; sealed constant slot locator-relative? :: = #f, init-keyword: relative?:; sealed constant slot locator-path :: , required-init-keyword: path:; end class ; define sealed method make (class == , #key server :: false-or() = #f, path :: false-or() = #f, relative? :: = #f, directory :: false-or() = #f, name :: false-or()) => (locator :: ) let path = if (name | directory) concatenate(if (directory) directory.locator-path else #[] end, if (name) vector(name) else #[] end) else path end; next-method(class, server: server, path: canonicalize-path(path), relative?: relative?) end method make; define sealed method locator-name (locator :: ) => (name :: false-or()) let path = locator.locator-path; unless (empty?(path)) path[size(path) - 1] end end method locator-name; define sealed method \= (locator1 :: , locator2 :: ) => (equal? :: ) locator1.locator-relative? = locator2.locator-relative? & locator1.locator-server = locator2.locator-server & locator1.locator-path.size = locator2.locator-path.size & every?(case-insensitive=, locator1.locator-path, locator2.locator-path) end method \=; define sealed method string-as-locator (class == , string :: ) => (locator :: ) let unc? = prefix-equal?(string, $unc-prefix) | prefix-equal?(string, $alternative-unc-prefix); let volume? = ~unc? & string.size > 1 & string[1] == $volume-separator; let (server, next-pos) = case unc? => let start = $unc-prefix.size; let pos = find-delimiters(string, $microsoft-separators, start: start); if (pos) let host = copy-sequence(string, start: start, end: pos); values(make(, host: host), pos) else locator-error("Invalid directory %=", string) end; volume? => values(make(, drive: string[0]), 2); otherwise => values(#f, 0); end; let (path, relative?) = parse-path(string, start: next-pos, test: rcurry(member?, $microsoft-separators)); make(, server: server, path: path, relative?: relative?) end method string-as-locator; define sealed method locator-as-string (class :: subclass(), locator :: ) => (string :: ) let server = locator.locator-server; let directory-string = path-to-string(locator.locator-path, class: class, separator: $microsoft-separators[0], relative?: locator.locator-relative?); if (server) concatenate-as(class, as(class, server), directory-string) else directory-string end end method locator-as-string; define sealed method locator-test (locator :: ) => (test :: ) case-insensitive= end method locator-test; define sealed class (, ) sealed constant slot locator-directory :: false-or() = #f, init-keyword: directory:; sealed constant slot locator-base :: false-or() = #f, init-keyword: base:; sealed constant slot locator-extension :: false-or() = #f, init-keyword: extension:; end class ; define sealed method make (class == , #key directory :: false-or() = #f, base :: false-or(), extension :: false-or(), name :: false-or()) => (locator :: ) let directory = unless (directory & current-directory-locator?(directory)) directory end; let pos = name & find-delimiter-from-end(name, $extension-separator); let base = base | if (pos) copy-sequence(name, end: pos) else name end; let extension = extension | if (pos) copy-sequence(name, start: pos + 1) end; if (~base) locator-error("Attemped to create a file locator without a base") end; next-method(class, directory: directory, base: base, extension: extension) end method make; define sealed method locator-name (locator :: ) => (name :: false-or()) let base = locator.locator-base; let extension = locator.locator-extension; if (extension) concatenate(base | "", delimiter-to-string($extension-separator), extension) else base end end method locator-name; define sealed method \= (locator1 :: , locator2 :: ) => (equal? :: ) locator1.locator-directory = locator2.locator-directory & case-insensitive=(locator1.locator-base, locator2.locator-base) & case-insensitive=(locator1.locator-extension, locator2.locator-extension) end method \=; define sealed method locator-as-string (class :: subclass(), locator :: ) => (string :: ) let directory = locator.locator-directory; let name = locator.locator-name; if (directory) concatenate-as(class, as(, directory), name) else as(class, name) end end method locator-as-string; define sealed method string-as-locator (class == , string :: ) => (locator :: ) let pos = find-delimiters-from-end(string, $microsoft-separators); let (directory, name) = if (pos) values(as(, copy-sequence(string, end: pos + 1)), copy-sequence(string, start: pos + 1)) else values(#f, string) end; make(, directory: directory, name: name) end method string-as-locator;