Skip to content

Instantly share code, notes, and snippets.

@gfldex
Created September 30, 2016 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gfldex/f76ee074c2d080912b0f87ae65c9bef1 to your computer and use it in GitHub Desktop.
Save gfldex/f76ee074c2d080912b0f87ae65c9bef1 to your computer and use it in GitHub Desktop.
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