path.fnl (4114B)
1 (import-macros { : define-tests : expect : expect= } :assert) 2 (local { : clone } (require :lib)) 3 4 (local Path 5 (let [mt {} 6 new (fn [components absolute? directory?] 7 (doto { : components : absolute? : directory? } 8 (setmetatable mt))) 9 parse 10 (fn [raw] 11 (if (= (type raw) :string) 12 (new (icollect [c (string.gmatch raw "([^/]+)")] c) 13 (string.find raw "^/") 14 (or (string.find raw "/$") 15 (string.find raw "/%.$") 16 (string.find raw "/%.%.$"))) 17 (new raw.components raw.absolute? raw.directory?))) 18 join 19 (fn [base leaf] 20 (let [addl (parse leaf)] 21 (if addl.absolute? 22 addl 23 (let [c (clone base.components)] 24 (each [_ v (ipairs addl.components)] 25 (table.insert c v)) 26 (new c base.absolute? addl.directory? ))))) 27 canonicalise 28 (fn [self] 29 (let [out []] 30 (each [_ component (ipairs self.components)] 31 (case component 32 ".." (table.remove out) 33 "." nil 34 c (table.insert out c))) 35 (new out self.absolute? self.directory?))) 36 ] 37 (tset mt :__div join) 38 (tset mt :__index { 39 :parent (fn [x] 40 (new (doto (clone x.components) (table.remove)) 41 x.absolute?)) 42 :join join 43 :canonicalise canonicalise 44 :relativise (fn [p] 45 (new p.components false p.directory?)) 46 }) 47 (tset mt :__tostring (fn [self] 48 (.. (if self.absolute? "/" "") 49 (table.concat self.components "/") 50 (if self.directory? "/" "") 51 ))) 52 53 54 parse)) 55 56 (define-tests :path 57 (let [p (Path "/etc/passwd") 58 q (Path "/var/spool/mail/")] 59 (expect= p.components ["etc" "passwd"]) 60 (expect p.absolute?) 61 (expect= (tostring (/ p "foo/bar/")) "/etc/passwd/foo/bar/") 62 (expect= (tostring (p:parent)) "/etc") 63 (expect q.directory?) 64 (let [c (/ p "subdir/")] 65 (expect= (tostring c) "/etc/passwd/subdir/") 66 (expect c.directory?)) 67 (let [c (/ p (Path "subdir/"))] 68 (expect= (tostring c) "/etc/passwd/subdir/") 69 (expect c.directory?)) 70 71 (let [p (Path "/etc/../var/log/../spool/foo")] 72 (expect= (tostring (p:canonicalise)) "/var/spool/foo")) 73 (let [p (Path "/etc/private/..")] 74 (expect p.absolute?) 75 (expect p.directory?)) 76 77 )) 78 79 80 (define-tests :dedot-path 81 (let [dedot-path #(-> "/" Path (/ $1) (: :canonicalise) tostring)] 82 (expect= (dedot-path "/hello") "/hello") 83 ;; relative pathnames are treated as if at root directory 84 (expect= (dedot-path "hello") "/hello") 85 (expect= (dedot-path "hello/goo") "/hello/goo") 86 ;; repeated / is treated as if single / 87 (expect= (dedot-path "/hello///goodbye") "/hello/goodbye") 88 (expect= (dedot-path "/hello/there/../goodbye") "/hello/goodbye") 89 ;; if the input ends in / then preserve it 90 (expect= (dedot-path "/hello/there/") "/hello/there/") 91 ;; if the final entry is . or .. then we must be desiring a directory 92 (expect= (dedot-path "/hello/there/..") "/hello/") 93 (expect= (dedot-path "/hello/there/../.") "/hello/") 94 95 (expect= (dedot-path "/./../hello/") "/hello/") 96 97 (expect= (dedot-path "/hello/there/ab") "/hello/there/ab") 98 (expect= (dedot-path "/hello/there/./goodbye") "/hello/there/goodbye") 99 100 (expect= (dedot-path "../../foo") "/foo") 101 )) 102 103 104 105 106 { : Path }