162 lines
4.2 KiB
Factor
162 lines
4.2 KiB
Factor
! Copyright (C) 2007, 2009 Slava Pestov, Daniel Ehrenberg.
|
|
! See http://factorcode.org/license.txt for BSD license.
|
|
USING: accessors arrays combinators combinators.short-circuit
|
|
io.directories io.files io.files.info io.pathnames kernel locals
|
|
make peg.ebnf regexp regexp.combinators sequences strings system
|
|
unicode multiline ;
|
|
IN: globs
|
|
|
|
: not-path-separator ( -- sep )
|
|
os windows? R/ [^\/\\]/ R/ [^\/]/ ? ; foldable
|
|
|
|
: wild-path-separator ( -- sep )
|
|
os windows? R/ [^\/\\][\/\\]|[^\/\\]/ R/ [^\/][\/]|[^\/]/ ? ; foldable
|
|
|
|
EBNF: <glob> [=[
|
|
|
|
Character = "\\" .:c => [[ c 1string <literal> ]]
|
|
| !(","|"}") . => [[ 1string <literal> ]]
|
|
|
|
RangeCharacter = !("]") .
|
|
|
|
Range = RangeCharacter:a "-" RangeCharacter:b => [[ a b <char-range> ]]
|
|
| RangeCharacter => [[ 1string <literal> ]]
|
|
|
|
StartRange = .:a "-" RangeCharacter:b => [[ a b <char-range> ]]
|
|
| . => [[ 1string <literal> ]]
|
|
|
|
Ranges = StartRange:s Range*:r => [[ r s prefix ]]
|
|
|
|
CharClass = "^"?:n Ranges:e => [[ e <or> n [ <not> ] when ]]
|
|
|
|
AlternationBody = Concatenation:c "," AlternationBody:a => [[ a c prefix ]]
|
|
| Concatenation => [[ 1array ]]
|
|
|
|
Element = "**" => [[ wild-path-separator <zero-or-more> ]]
|
|
| "*" => [[ not-path-separator <zero-or-more> ]]
|
|
| "?" => [[ not-path-separator ]]
|
|
| "[" CharClass:c "]" => [[ c ]]
|
|
| "{" AlternationBody:b "}" => [[ b <or> ]]
|
|
| Character
|
|
|
|
Concatenation = Element* => [[ <sequence> ]]
|
|
|
|
End = !(.)
|
|
|
|
Main = Concatenation End
|
|
|
|
]=]
|
|
|
|
: glob-matches? ( input glob -- ? )
|
|
[ >case-fold ] bi@ <glob> matches? ;
|
|
|
|
: glob-pattern? ( string -- ? )
|
|
[ "\\*?[{" member? ] any? ;
|
|
|
|
<PRIVATE
|
|
|
|
! TODO: simplify
|
|
! TODO: handle two more test cases
|
|
! TODO: make case-fold an option, off by default
|
|
! TODO: maybe make case-fold an option on regexp
|
|
|
|
DEFER: glob%
|
|
|
|
: glob-entries ( path -- entries )
|
|
directory-entries [ name>> "." head? ] reject ;
|
|
|
|
: ?glob% ( root remaining entry -- )
|
|
over empty? [
|
|
2drop ,
|
|
] [
|
|
directory? [ glob% ] [ 2drop ] if
|
|
] if ;
|
|
|
|
:: glob-wildcard% ( root globs -- )
|
|
globs ?second :> next-glob
|
|
next-glob dup pair? [ second ] [ drop f ] if :> next-glob-regexp
|
|
|
|
root glob-entries [| entry |
|
|
root entry name>> append-path
|
|
{
|
|
{ [ next-glob not ] [ dup , ] }
|
|
{ [ next-glob empty? ] [ entry directory? [ dup , ] when ] }
|
|
[
|
|
next-glob-regexp [
|
|
entry name>> >case-fold next-glob-regexp matches?
|
|
] [
|
|
{
|
|
[ next-glob "**" = ]
|
|
[ entry name>> next-glob = ]
|
|
} 0||
|
|
] if [
|
|
globs 2 tail [
|
|
dup ,
|
|
] [
|
|
entry directory? [
|
|
dupd glob%
|
|
] [
|
|
drop
|
|
] if
|
|
] if-empty
|
|
] when
|
|
]
|
|
} cond
|
|
|
|
{ [ entry directory? ] [ next-glob ] } 0&& [
|
|
globs glob%
|
|
] [
|
|
drop
|
|
] if
|
|
] each ;
|
|
|
|
:: glob-pattern% ( root globs -- )
|
|
globs unclip second :> ( remaining glob )
|
|
|
|
root glob-entries [| entry |
|
|
entry name>> >case-fold glob matches? [
|
|
root entry name>> append-path
|
|
remaining entry ?glob%
|
|
] when
|
|
] each ;
|
|
|
|
:: glob-literal% ( root globs -- )
|
|
globs unclip :> ( remaining glob )
|
|
|
|
root glob append-path dup exists? [
|
|
remaining over file-info ?glob%
|
|
] [
|
|
drop
|
|
] if ;
|
|
|
|
: glob% ( root globs -- )
|
|
dup ?first {
|
|
{ f [ 2drop ] }
|
|
{ "**" [ glob-wildcard% ] }
|
|
[ pair? [ glob-pattern% ] [ glob-literal% ] if ]
|
|
} case ;
|
|
|
|
: split-glob ( glob -- path globs )
|
|
{ } [
|
|
over glob-pattern?
|
|
] [
|
|
[
|
|
dup [ path-separator? ] find-last drop
|
|
[ cut rest ] [ "" swap ] if*
|
|
] dip swap prefix
|
|
] while ;
|
|
|
|
: glob-path ( glob -- path globs )
|
|
split-glob [
|
|
dup { [ "**" = not ] [ glob-pattern? ] } 1&& [
|
|
dup >case-fold <glob> 2array
|
|
] when
|
|
] map ;
|
|
|
|
PRIVATE>
|
|
|
|
: glob ( glob -- files )
|
|
glob-path [
|
|
[ 1array f swap ] when-empty glob%
|
|
] { } make ;
|