# Copyright (c) 2023 Yuki Kimoto
# MIT License
class File::Find {
version "0.033";
use File::Find::Info;
use File::Find::Callback;
use Fn;
use Hash;
use StringList;
use Re;
use Sys;
use Sys::OS;
use IO::Dir;
# Fields
has follow : ro byte;
has warn : ro byte;
# Undocumented Fields
has SLnkSeen : Hash of Int;
has file_seen_h : Hash of Int;
has skip_pattern : string;
# Class Methods
static method new : File::Find ($options : object[] = undef) {
my $self = new File::Find;
my $option_names = [
"follow",
"warn",
];
Fn->check_option_names($options, $option_names);
my $options_h = Hash->new($options);
$self->{follow} = (byte)$options_h->get_or_default("follow", 0)->(int);
$self->{SLnkSeen} = Hash->new;
$self->{file_seen_h} = Hash->new;
$self->{skip_pattern} = "^\.{1,2}\z";
$self->{warn} = (byte)$options_h->get_or_default("warn", 0)->(int);
return $self;
}
# Instance Methods
method find : void ($cb : File::Find::Callback, $top_dir : string) {
unless ($top_dir) {
die "The top directory \$top_dir must be defined." ;
}
my $top_dir = copy $top_dir;
# canonicalize directory separators
if (Sys::OS->is_windows) {
Re->s((mutable string)$top_dir, ["[/\\\\]", "g"], "/");
}
# no trailing / unless path is root
unless (&_is_root($top_dir)) {
Re->s((mutable string)$top_dir, "/\z", "");
}
my $warn = $self->{warn};
my $follow = $self->{follow};
my $file_seen_h = $self->{file_seen_h};
my $tree_queue = StringList->new;
my $base_name = (string)undef;
my $is_top_dir = 1;
my $name = $top_dir;
$tree_queue->push($name);
my $info = File::Find::Info->new;
while (1) {
unless ($tree_queue->length > 0) {
last;
}
my $name = $tree_queue->shift;
$info->{name} = $name;
$info->{prune} = 0;
$cb->($info);
if ($info->{prune}) {
next;
}
my $dir = $name;
my $lstat = (Sys::IO::Stat)undef;
eval { $lstat = Sys->lstat($dir); }
if ($@) {
if ($warn) {
warn "[Warning]Sys#lstat method failed. File:$dir, Message:\n----------\n$@\n----------";
}
next;
}
my $stat = (Sys::IO::Stat)undef;
if ($lstat->l) {
unless ($follow) {
next;
}
eval { $stat = Sys->stat($dir); }
if ($@) {
if ($warn) {
warn "[Warning]Sys#stat method failed. File:$dir, Message:\n----------\n$@\n----------";
}
next;
}
}
else {
$stat = $lstat;
}
unless ($stat->d) {
next;
}
if ($follow) {
my $dev = $stat->st_dev;
my $ino = $stat->st_ino;
my $file_id = "$dev/$ino";
if ($file_seen_h->exists($file_id)) {
if ($warn) {
warn "[Warning]The directory $dir has already been processed. st_dev:$dev, st_ino:$ino";
}
next;
}
else {
$file_seen_h->set($file_id, 1);
}
}
# Get the list of files in the current directory.
my $dh = (IO::Dir)undef;
eval { $dh = IO::Dir->new($dir); }
if ($@) {
if ($warn) {
warn "[Warning]IO::Dir#new method failed. Directory:$dir, Message:\n----------\n$@\n----------";
}
next;
}
while (my $base_name = $dh->read) {
if (Re->m($base_name, $self->{skip_pattern})) {
next;
}
my $name = &_is_root($dir) ? "$dir$base_name" : "$dir/$base_name";
$tree_queue->push($name);
}
}
}
private static method _is_root : int ($path : string) {
if (Sys::OS->is_windows) {
return !!Re->m($path, "^(?:[A-Za-z]:)?/\z");
}
return $path eq "/";
}
}