=head1 NAME

Doit::Util - utility functions for Doit

=head1 SYNOPSIS

    use Doit::Util;
    use Doit::Util qw(in_directory new_scope_cleanup copy_stat get_sudo_cmd get_os_release);

=head1 DESCRIPTION

=head2 FUNCTIONS

=head3 in_directory

    in_directory {
       ... code ...
    } $directory;

Temporary change to the specified I<$directory> and execute the code
in the enclosed block. After execution, the previous directory is
restored. Dies if the specified I<$directory> does not exist.

B<Note>: commands like C<last> or C<return> apply to the enclosed block,
not to the outer function. As this is confusing and the behavior may
change it's best to avoid them.

B<Note>: the directory change only applies to the current context, i.e.
not to ssh/sudo/... runners, even if running on the same system. So
the following does B<not> work as expected:

    my $root_doit = $doit->do_sudo;
    in_directory {
        $doit->copy("/from", "to");
        $root_doit->chown(0, 0, "to"); # will not use $directory/to!
    } $directory;

If I<$directory> is C<undef>, then the enclosed block will still be
executed without changing the current directory.

The environment variable C<PWD> is also temporarily changed to the
specified I<$directory>.

=head3 new_scope_cleanup

    {
        my $scope_cleanup = new_scope_cleanup(sub { say "This is printed when leaving the current block" });
        ... code ...
    }
    # at this point the cleanups are executed

Register a callback which is executed when leaving the current block
E<0x2014> either at the end of the block, or when using C<return>, C<goto>
or similar commands. Note that the return value of the
C<new_scope_cleanup> needs to be assigned to a lexical variable E<0x2014> if
this lexical variable goes out of scope, then the callback will be executed.

=head3 copy_stat

    copy_stat($source_file, $dest_file);
    copy_stat($source_file, $dest_file, { ownership => 1, mode => 1, time => 1 });

Copy file properties from I<$source_file> to I<$dest_file>. By
default, all possible properties (ownership: owner and group; mode;
time: atime and mtime) are copied, but this can be limitted by
specifying a third parameter as shown above.

If some of the properties cannot be copied, then just warnings will be
issued.

=head3 get_sudo_cmd

Return an empty list if using C<sudo> is not required to become the
root user (i.e. the current user is already the root user), otherwise
returns a list with C<sudo>. Handy for writing commands like:

    $doit->system(get_sudo_cmd(), 'apt-get', '-y', 'install', @missing_packages);

=head3 get_os_release

Return a hash reference of F</etc/os-release> or
F</usr/lib/os-release> contents, or undef if such files do not exist.
Usually the hash will contain keys like

    ID => 'ubuntu',
    VERSION_ID => '22.04'

The file is parsed only once and cached for subsequent calls. To force
a cache refresh use the option

    refresh => 1

To specify another file or files than F</etc/os-release> (probably
only useful for test purposes) use the option

    file => "/path/to/another/os-release"
    file => ["/path/to/another/os-release", "/another/path/to/another/os-release"]

See also L<https://www.freedesktop.org/software/systemd/man/latest/os-release.html>.

=head1 AUTHOR

Slaven Rezic <srezic@cpan.org>

=head1 COPYRIGHT

Copyright (c) 2022,2023,2024 Slaven Rezic. All rights reserved.
This module is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 SEE ALSO

L<Doit>, L<perlfunc/stat EXPR>, L<sudo(8)>.

=cut
