1 ! Copyright (C) 2007, 2009 Slava Pestov, Daniel Ehrenberg.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors arrays combinators combinators.short-circuit
4 io.directories io.files io.files.info io.pathnames kernel locals
5 make peg.ebnf regexp regexp.combinators sequences splitting
6 strings system unicode ;
9 : not-path-separator ( -- sep )
10 os windows? R/ [^\\/\\]/ R/ [^\\/]/ ? ; foldable
14 Character = "\\" .:c => [[ c 1string <literal> ]]
15 | !(","|"}") . => [[ 1string <literal> ]]
17 RangeCharacter = !("]") .
19 Range = RangeCharacter:a "-" RangeCharacter:b => [[ a b <char-range> ]]
20 | RangeCharacter => [[ 1string <literal> ]]
22 StartRange = .:a "-" RangeCharacter:b => [[ a b <char-range> ]]
23 | . => [[ 1string <literal> ]]
25 Ranges = StartRange:s Range*:r => [[ r s prefix ]]
27 CharClass = "^"?:n Ranges:e => [[ e <or> n [ <not> ] when ]]
29 AlternationBody = Concatenation:c "," AlternationBody:a => [[ a c prefix ]]
30 | Concatenation => [[ 1array ]]
32 Element = "*" => [[ not-path-separator <zero-or-more> ]]
33 | "?" => [[ not-path-separator ]]
34 | "[" CharClass:c "]" => [[ c ]]
35 | "{" AlternationBody:b "}" => [[ b <or> ]]
38 Concatenation = Element* => [[ <sequence> ]]
42 Main = Concatenation End
46 : glob-matches? ( input glob -- ? )
47 [ >case-fold ] bi@ <glob> matches? ;
49 : glob-pattern? ( string -- ? )
50 [ "\\*?[{" member? ] any? ;
55 ! TODO: handle two more test cases
56 ! TODO: make case-fold an option, off by default
57 ! TODO: maybe make case-fold an option on regexp
59 DEFER: glob-directory%
61 : glob-entries ( path -- entries )
62 directory-entries [ name>> "." head? ] reject ;
64 : ?glob-directory% ( root remaining entry -- )
68 empty? [ , ] [ drop ] if
71 :: glob-wildcard% ( root globs -- )
72 globs ?second :> next-glob
73 next-glob dup pair? [ second ] [ drop f ] if :> next-glob-regexp
75 root glob-entries [| entry |
76 root entry name>> append-path
78 { [ next-glob not ] [ dup , ] }
79 { [ next-glob empty? ] [ entry directory? [ dup , ] when ] }
82 entry name>> >case-fold next-glob-regexp matches?
86 [ entry name>> next-glob = ]
102 { [ entry directory? ] [ next-glob ] } 0&& [
103 globs glob-directory%
109 :: glob-pattern% ( root globs -- )
110 globs unclip second :> ( remaining glob )
112 root glob-entries [| entry |
113 entry name>> >case-fold glob matches? [
114 root entry name>> append-path
115 remaining entry ?glob-directory%
119 :: glob-literal% ( root globs -- )
120 globs unclip :> ( remaining glob )
122 root glob append-path dup exists? [
123 remaining over file-info ?glob-directory%
128 : glob-directory% ( root globs -- )
131 { "**" [ glob-wildcard% ] }
132 [ pair? [ glob-pattern% ] [ glob-literal% ] if ]
135 : split-glob ( glob -- path globs )
140 dup [ path-separator? ] find-last drop
141 [ cut rest ] [ "" swap ] if*
145 : glob-path ( glob -- path globs )
147 dup { [ "**" = not ] [ glob-pattern? ] } 1&& [
148 dup >case-fold <glob> 2array
154 : glob-directory ( glob -- files )
155 glob-path [ glob-directory% ] { } make ;