=encoding utf8

=head1 NAME

Sidef::Types::Glob::Dir - Directory manipulation and navigation in Sidef

=head1 DESCRIPTION

The Dir class provides directory manipulation capabilities in Sidef: creation, deletion, navigation, and traversal of directory structures. It's a subclass of L<Sidef::Types::Glob::File>, so every method documented in L<Sidef::Types::Glob::File> is also available on a C<Dir> object -- though a few of them (C<read>, C<write>, C<size>, and so on) don't really make sense for a directory even though they're technically callable.

Paths are decoded/encoded as UTF-8 where the underlying Perl core modules require it.

=head1 SYNOPSIS

    # Create a Dir object
    var dir = Dir("/path/to/directory")
    var home = Dir.home
    var temp = Dir.tmp

    # Directory creation
    dir.create                  # Create a single directory
    dir.create_tree             # Create a directory with any needed parents

    # Directory navigation
    Dir.cwd                     # Get the current working directory
    dir.chdir                   # Change to this directory
    dir.parent                  # Get the logical parent directory
    dir.up                      # Append ".." (unresolved)

    # Directory operations
    dir.is_empty                # Check if the directory is empty
    dir.remove                  # Remove an empty directory
    dir.remove_tree             # Recursively remove a directory and its contents

    # Directory traversal
    dir.find {|item|
        say(item)               # Iterate over the directory and everything below it
    }

    # Path manipulation -- '+' only returns a File if given an actual File object!
    var subdir = (dir + "subdir")          # a Dir, even though it looks like a name
    var file   = (dir + File("file.txt"))  # a File, because the argument already is one
    var parts  = dir.split                 # Split the path into components

=head1 INHERITS

Inherits methods from L<Sidef::Types::Glob::File>.

=head1 CONSTRUCTION

=head2 new

    Dir(path)
    Dir(*path_components)
    Dir.new(path)

Creates a new C<Dir> object. With a single string argument, C<path> is used as-is. With I<more than one> argument, the arguments are joined as path components (via C<File::Spec::catdir>) into a single path. With a single argument that's I<any> kind of reference (a C<File>, another C<Dir>, or anything else), this instead delegates entirely to calling C<.to_dir> on it and returns I<that> result -- so the given object's own class must actually provide a working C<to_dir> method.

    var dir1 = Dir("/home/user")
    var dir2 = Dir("home", "user", "documents")
    var dir3 = Dir(some_file_obj)      # delegates to some_file_obj.to_dir

Aliases: I<call>

=cut

=head1 SPECIAL DIRECTORIES

=head2 root

    Dir.root

Returns a C<Dir> representing the filesystem's root directory (C<"/"> on Unix-like systems).

    say(Dir.root)   #=> Dir("/")

=cut

=head2 home

    Dir.home

Returns a C<Dir> representing the current user's home directory, determined (in order) from the C<HOME> or C<LOGDIR> environment variables, a system user-database lookup (on non-Windows systems), or C<File::HomeDir> as a last resort.

    say(Dir.home)   # e.g. Dir("/home/username")

=cut

=head2 tmp

    Dir.tmp

Returns a C<Dir> representing the system's temporary directory.

    say(Dir.tmp)   # e.g. Dir("/tmp")

Aliases: I<temp>

=cut

=head2 cwd

    Dir.cwd

Returns a C<Dir> representing the current working directory, as an absolute, resolved path.

    say(Dir.cwd)

=cut

=head2 pwd

    Dir.pwd

Returns a C<Dir> representing the current directory as the literal, I<unresolved> string C<".">  -- unlike L</cwd>, this is not turned into an absolute path.

    say(Dir.pwd)   #=> Dir(".")

=cut

=head2 mktemp

    Dir.mktemp(%options)

Creates a new, empty temporary directory (via C<File::Temp::tempdir>) and returns a C<Dir> for it. It's automatically removed when the program exits (C<CLEANUP> is always enabled). C<%options> are forwarded to C<File::Temp::tempdir>.

    var tmpdir = Dir.mktemp
    tmpdir.chdir

Aliases: I<make_tmp>, I<make_temp>

=cut

=head1 PATH MANIPULATION

=head2 concat

    self.concat(other)
    self + other

If C<other> is I<specifically> a C<File> object (not a C<Dir>, and not a plain string, even one that looks like a filename), joins the paths with C<File::Spec::catfile> and returns a C<File>. For anything else -- including a plain string like C<"file.txt">, or a C<Dir> -- joins the paths with C<File::Spec::catdir> instead and returns a C<Dir>. In other words, the only way to get a C<File> back from C<+> is to pass an actual C<File> object; a bare string argument always produces a C<Dir>, regardless of what it looks like.

    var home = Dir.home

    say(home + "Documents")         #=> Dir(".../Documents")
    say(home + "file.txt")          #=> Dir(".../file.txt") -- still a Dir!
    say(home + File("file.txt"))    #=> File(".../file.txt") -- a File, because the argument was one

Aliases: operator C<+>, I<catfile>

=cut

=head2 split

    self.split

Splits the directory path into its components, via C<File::Spec::splitdir>, and returns them as an C<Array> of Strings.

    say(Dir("/home/user/documents").split)   #=> ["", "home", "user", "documents"]

=cut

=head2 parent

    self.parent

Returns a C<Dir> for the logical parent directory, computed textually (via C<File::Basename::dirname>) rather than by consulting the filesystem. Compare with L</up>, which doesn't compute anything -- it just appends a literal C<"..">.

    say(Dir("/home/user/documents").parent)   #=> Dir("/home/user")

=cut

=head2 up

    self.up
    Dir.up

Returns a new C<Dir> with a literal C<"..">  appended to the path (via C<File::Spec>, so the platform's own separator is used) -- unlike L</parent>, nothing is resolved or simplified. Called on the C<Dir> class itself (with no instance), returns just C<Dir("..")> on its own, unanchored to any particular directory.

    say(Dir("/home/user/documents").up)   #=> Dir("/home/user/documents/..")
    say(Dir.up)                           #=> Dir("..")

=cut

=head2 get_value

    self.get_value

Returns the raw underlying path string (unwrapped from the C<Dir> object). A low-level method, mainly useful for interop rather than everyday Sidef code.

=cut

=head2 to_dir

    self.to_dir

Returns C<self> unchanged (a C<Dir> is already a directory).

=cut

=head2 to_str

    self.to_str

Returns the directory's path as a plain String.

    say(Dir("/home/user").to_str)   #=> "/home/user"

Aliases: I<to_s>

=cut

=head2 dump

    self.dump

Returns a String representation of the C<Dir> object for debugging, in the format C<Dir("path")>.

    say(Dir("/tmp").dump)   #=> Dir("/tmp")

=cut

=head1 NAVIGATION

=head2 chdir

    self.chdir

Changes the process's current working directory to this one. Returns C<true> on success, C<false> otherwise. This is process-wide, not local to any particular scope.

    Dir("/tmp").chdir

=cut

=head2 chroot

    self.chroot

Changes the process's root directory to this one (typically requires superuser privileges). Returns C<true> on success, C<false> otherwise. Like L</chdir>, this affects the entire process.

    Dir("/jail").chroot

=cut

=head1 CREATING AND REMOVING

=head2 create

    self.create

Creates the directory. Its immediate parent must already exist -- for creating any missing parent directories too, see L</create_tree>. Returns C<true> on success, C<false> otherwise.

    Dir("newdir").create

Aliases: I<make>, I<mkdir>

=cut

=head2 create_tree

    self.create_tree

Creates the directory, along with any missing parent directories (like C<mkdir -p>). Returns C<true> if the directory already existed or was successfully created, C<false> otherwise.

    Dir("/path/to/deep/nested/dir").create_tree

Aliases: I<make_tree>, I<mktree>, I<make_path>, I<mkpath>

=cut

=head2 remove

    self.remove

Removes the directory. It must be empty -- for removing a directory and everything inside it, see L</remove_tree>. Returns C<true> on success, C<false> otherwise.

    Dir("empty_dir").remove

Aliases: I<delete>, I<unlink>

=cut

=head2 remove_tree

    self.remove_tree

Recursively removes the directory and everything inside it. This cannot be undone. Returns C<true> on success, C<false> otherwise.

    Dir("old_project").remove_tree

=cut

=head1 CONTENTS AND TRAVERSAL

=head2 is_empty

    self.is_empty

Returns C<true> if the directory contains no entries (other than C<.> and C<..>), C<false> if it has any, or C<nil> if the directory can't be opened at all.

    Dir("test").is_empty && say("Directory is empty")

=cut

=head2 find

    self.find
    self.find(block)

Recursively traverses the directory tree, starting from (and including) the directory itself. With a block, calls it once for every item found (a C<Dir> for each subdirectory, a C<File> for each regular file) and returns C<self>. With no block, collects everything into an C<Array> instead.

    dir.find {|item|
        say(item) if item.is_file
    }

    var all_items = dir.find
    say("Found #{all_items.len} items")

Aliases: I<browse>

=cut

=head2 open

    self.open
    self.open(fh_ref, err_ref=nil)

Opens the directory for reading its entries. With no arguments, returns a C<DirHandle> on success, or C<nil> on failure. With C<fh_ref>, stores the handle through it and returns C<true>/C<false> instead (storing any error message through C<err_ref>, if given).

    var dh = Dir("/tmp").open
    dh.each {|entry| say(entry) }

Aliases: I<open_r>

=cut

=head2 open_w

    self.open_w

B<Not implemented.> Calling this currently raises a runtime "Unimplemented" error -- it's a placeholder for future write-mode directory support, not a harmless no-op.

=cut

=head2 open_rw

    self.open_rw

B<Not implemented.> Like L</open_w>, calling this currently raises a runtime "Unimplemented" error.

=cut

=head1 EXAMPLES

=head2 Basic directory operations

    var project = Dir("my_project")
    project.create_tree
    project.chdir

    var src = (project + "src")   # a Dir, since a plain string always produces one
    var lib = (project + "lib")
    src.create
    lib.create

=head2 Directory traversal

    var docs = Dir("documents")

    # find() includes the starting directory itself in the traversal
    docs.find {|item|
        if (item.is_file && item.match(/\.txt$/)) {
            say(item)
        }
    }

    var all = docs.find
    say("Total items: #{all.len}")

=head2 Temporary directories

    var tmpdir = Dir.mktemp
    say("Using temp directory: #{tmpdir}")

    tmpdir.chdir
    # ... work happens here ...
    # tmpdir is automatically removed when the program exits

=head2 Cleaning up

    var old = Dir("obsolete")
    old.is_empty && old.remove

    var cache = Dir(".cache")
    cache.remove_tree   # removes everything, recursively

=head2 Getting a File, not a Dir, from concatenation

    var home = Dir.home

    var maybe_dir = (home + "notes.txt")           # still a Dir -- plain strings never become Files
    var actually_file = (home + File("notes.txt")) # a File, since the argument was one

=head1 SEE ALSO

L<Sidef::Types::Glob::File>, L<Sidef::Types::Glob::DirHandle>

=cut
