Created
September 30, 2016 22:08
-
-
Save gfldex/f76ee074c2d080912b0f87ae65c9bef1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use v6; | |
package Find { | |
enum Type (<File Dir Symlink>); | |
enum Options (<Recursive Keep-going>); | |
} | |
class X::IO::NotADirectory is Exception { | |
has $.path; | |
method message { | |
"«$.path» is not a directory" | |
} | |
} | |
class X::IO::CanNotAccess is Exception { | |
has $.path; | |
method message { | |
"Cannot access «$.path»: permission denied" | |
} | |
} | |
class X::IO::StaleSymlink is Exception { | |
has $.path; | |
method message { | |
"Stale symlink «$.path»" | |
} | |
} | |
class X::Paramenter::Exclusive is Exception { | |
has $.type; | |
method message { | |
"Parameters {$.type} are mutual exclusive" | |
} | |
} | |
class X::Parameter::UnrecognisedOption is Exception { | |
has $.type; | |
has $.unrecognised; | |
method message { | |
"Option { $.unrecognised } not any of { $.type.map({ (.^name ~ '::') xx * Z~ .enums.keys.flat }).flat.join(', ') }" | |
} | |
} | |
sub exclusive-argument(\list, \type){ +(list.grep: * ~~ type) <= 1 } | |
sub find ( | |
IO(Str) $dir where { | |
( .IO.e || fail X::IO::DoesNotExist.new(path => .Str ) ) | |
&& ( .IO.d || fail X::IO::NotADirectory.new(path => .Str) ) | |
&& ( .IO.r || fail X::IO::CanNotAccess.new(path => .Str) ) | |
}, | |
:$name, :$exclude, :$exclude-dir, :$include, :$include-dir, :$extension, | |
:&return-type = { .IO.Str }, | |
:$file = True, :$directory, :$symlink, | |
:$recursive, :$keep-going = True, :$follow-symlink | |
) { | |
my %type-tests = $file.key => {so .f}, $directory.key => {so .d}, $symlink.key => {so .l}; | |
my @tests; | |
@tests.append(%type-tests{$file.key, $directory.key, $symlink.key}); | |
@tests.append({.basename.Str ~~ $name}) with $name; | |
my @exclude-tests; | |
for $exclude.list -> $exclude { | |
@exclude-tests.push({ .Str ~~ $exclude }) if $exclude ~~ Regex; | |
@exclude-tests.push({ $exclude.(.IO) }) if $exclude ~~ Callable ^ Regex; | |
@exclude-tests.push({ .Str.contains($exclude) }) if $exclude ~~ Str; | |
} | |
@tests.append(@exclude-tests.none); | |
my @include-tests; | |
for $include.list -> $include { | |
@include-tests.push({ .Str ~~ $include }) if $include ~~ Regex; | |
@include-tests.push({ $include.(.IO) }) if $include ~~ Callable ^ Regex; | |
@include-tests.push({ .Str.contains($include) }) if $include ~~ Str; | |
} | |
@tests.append(@include-tests.any) if @include-tests; | |
my @extension-tests; | |
for $extension.list -> $test { | |
@extension-tests.push({ .extension ~~ $test }) if $test ~~ Regex; | |
@extension-tests.push({ $test.(.extension) }) if $test ~~ Callable ^ Regex; | |
@extension-tests.push({ $test eq .extension }) if $test ~~ Str; | |
} | |
@tests.append(@extension-tests.any) if @extension-tests; | |
my @dir-tests = $follow-symlink | |
?? { .d && .s || .e && fail X::IO::StaleSymlink.new(:path(.Str)); .d } | |
!! { .d && ! .s }; | |
my @exclude-dir-tests; | |
for $exclude-dir.list -> $exclude { | |
@exclude-dir-tests.push({ .Str ~~ $exclude }) if $exclude ~~ Regex; | |
@exclude-dir-tests.push({ $exclude.(.IO) }) if $exclude ~~ Callable ^ Regex; | |
@exclude-dir-tests.push({ .Str.contains($exclude) }) if $exclude ~~ Str; | |
} | |
@dir-tests.append(@exclude-dir-tests.none); | |
my @include-dir-tests; | |
for $include-dir.list -> $include { | |
@include-dir-tests.push({ .Str ~~ $include }) if $include ~~ Regex; | |
@include-dir-tests.push({ $include.(.IO) }) if $include ~~ Callable ^ Regex; | |
@include-dir-tests.push({ .Str.contains($include) }) if $include ~~ Str; | |
} | |
@dir-tests.append(@include-dir-tests.any) if @include-dir-tests; | |
lazy gather for dir($dir) { | |
CATCH { default { if $keep-going { warn .Str } else { .rethrow } } } | |
take .&return-type if all @tests».(.IO); | |
.IO.dir().sort({.f})».&?BLOCK if $recursive && all @dir-tests».(.IO) | |
} | |
} | |
sub find-simple ( IO(Str) $dir, | |
:$keep-going = True, | |
:$no-thread | |
) { | |
my $channel = Channel.new; | |
my &start = -> ( &c ) { c } if $no-thread; | |
my $promise = start { | |
for dir($dir) { | |
CATCH { default { if $keep-going { note .Str } else { .rethrow } } } | |
if .IO.l && !.IO.e { | |
X::IO::StaleSymlink.new(path=>.Str).throw; | |
} | |
$channel.send(.Str) if .IO.f; | |
$channel.send(.Str ~ '/') if .IO.d; | |
.IO.dir().sort({.e && .f})».&?BLOCK if .IO.e && .IO.d; | |
} | |
LEAVE $channel.close; | |
} | |
return $channel.list but role :: { method channel { $channel } }; | |
} | |
# .say for find(%*ENV<HOME>, :extension('txt',{.contains('~')}), :exclude('covers'), :exclude-dir('.'), Find::File, Find::Recursive, Find::Keep-going); | |
my @l := find-simple(%*ENV<HOME>, :!keep-going, :!no-thread); | |
for @l { | |
# @l.channel.close if $++ > 5000; | |
# .say | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment