Doit
view release on metacpan or search on metacpan
lib/Doit.pm view on Meta::CPAN
error "Scope cleanup failed: $@";
}
} else {
# And eval {} in older perl versions would
# clobber an outside $@. See
# perldoc perl5140delta, "Exception Handling"
$code->();
}
}
}
}
{
package Doit::Util;
use Exporter 'import';
our @EXPORT; BEGIN { @EXPORT = qw(in_directory new_scope_cleanup copy_stat get_sudo_cmd is_in_path get_os_release) }
$INC{'Doit/Util.pm'} = __FILE__; # XXX hack
use Doit::Log;
sub new_scope_cleanup (&) {
my($code) = @_;
my $sc = Doit::ScopeCleanups->new;
$sc->add_scope_cleanup($code);
$sc;
}
sub in_directory (&$) {
my($code, $dir) = @_;
my $scope_cleanup;
if (defined $dir) {
require Cwd;
my $pwd = Cwd::getcwd();
if (!defined $pwd || $pwd eq '') { # XS variant returns undef, PP variant returns '' --- see https://rt.perl.org/Ticket/Display.html?id=132648
warning "No known current working directory";
} else {
$scope_cleanup = new_scope_cleanup
(sub {
chdir $pwd or error "Can't chdir to $pwd: $!";
});
}
chdir $dir
or error "Can't chdir to $dir: $!";
}
$code->();
}
# $src may be a source file or an arrayref with stat information
sub copy_stat ($$;@) {
my($src, $dest, %preserve) = @_;
my @stat = ref $src eq 'ARRAY' ? @$src : stat($src);
error "Can't stat $src: $!" if !@stat;
my $preserve_default = !%preserve;
my $preserve_ownership = exists $preserve{ownership} ? delete $preserve{ownership} : $preserve_default;
my $preserve_mode = exists $preserve{mode} ? delete $preserve{mode} : $preserve_default;
my $preserve_time = exists $preserve{time} ? delete $preserve{time} : $preserve_default;
error "Unhandled preserve values: " . join(" ", %preserve) if %preserve;
if ($preserve_mode) {
chmod $stat[2], $dest
or warning "Can't chmod $dest to " . sprintf("0%o", $stat[2]) . ": $!";
}
if ($preserve_ownership) {
chown $stat[4], $stat[5], $dest
or do {
my $save_err = $!; # otherwise it's lost in the get... calls
warning "Can't chown $dest to " .
(getpwuid($stat[4]))[0] . "/" .
(getgrgid($stat[5]))[0] . ": $save_err";
};
}
if ($preserve_time) {
utime $stat[8], $stat[9], $dest
or warning "Can't utime $dest to " .
scalar(localtime $stat[8]) . "/" .
scalar(localtime $stat[9]) .
": $!";
}
}
sub get_sudo_cmd () {
return () if $> == 0;
return ('sudo');
}
sub is_in_path {
my($prog) = @_;
if (!defined &_file_name_is_absolute) {
if (eval { require File::Spec; defined &File::Spec::file_name_is_absolute }) {
*_file_name_is_absolute = \&File::Spec::file_name_is_absolute;
} else {
*_file_name_is_absolute = sub {
my $file = shift;
my $r;
if ($^O eq 'MSWin32') {
$r = ($file =~ m;^([a-z]:(/|\\)|\\\\|//);i);
} else {
$r = ($file =~ m|^/|);
}
$r;
};
}
}
if (_file_name_is_absolute($prog)) {
if ($^O eq 'MSWin32') {
return $prog if (-f $prog && -x $prog);
return "$prog.bat" if (-f "$prog.bat" && -x "$prog.bat");
return "$prog.com" if (-f "$prog.com" && -x "$prog.com");
return "$prog.exe" if (-f "$prog.exe" && -x "$prog.exe");
return "$prog.cmd" if (-f "$prog.cmd" && -x "$prog.cmd");
} else {
return $prog if -f $prog and -x $prog;
}
}
require Config;
%Config::Config = %Config::Config if 0; # cease -w
my $sep = $Config::Config{'path_sep'} || ':';
foreach (split(/$sep/o, $ENV{PATH})) {
if ($^O eq 'MSWin32') {
lib/Doit.pm view on Meta::CPAN
use Doit::Log;
my $diff_error_shown;
our @diff_cmd;
sub _new {
my $class = shift;
my $self = bless { }, $class;
$self;
}
sub runner {
my($self) = @_;
# XXX hmmm, creating now self-refential data structures ...
$self->{runner} ||= Doit::Runner->new($self);
}
sub dryrunner {
my($self) = @_;
# XXX hmmm, creating now self-refential data structures ...
$self->{dryrunner} ||= Doit::Runner->new($self, dryrun => 1);
}
sub init {
my($class) = @_;
require Getopt::Long;
my $getopt = Getopt::Long::Parser->new;
$getopt->configure(qw(pass_through noauto_abbrev));
$getopt->getoptions(
'dry-run|n' => \my $dry_run,
);
my $doit = $class->_new;
if ($dry_run) {
$doit->dryrunner;
} else {
$doit->runner;
}
}
sub install_generic_cmd {
my($self, $name, $check, $code, $msg) = @_;
if (!$msg) {
$msg = sub { my($self, $args) = @_; $name . ($args ? " @$args" : '') };
}
my $cmd = sub {
my($self, @args) = @_;
my @commands;
my $addinfo = {};
if ($check->($self, \@args, $addinfo)) {
push @commands, {
code => sub { $code->($self, \@args, $addinfo) },
msg => $msg->($self, \@args, $addinfo),
};
}
Doit::Commands->new(@commands);
};
no strict 'refs';
*{"cmd_$name"} = $cmd;
}
sub cmd_chmod {
my($self, @args) = @_;
my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } }
my $quiet = delete $options{quiet};
error "Unhandled options: " . join(" ", %options) if %options;
my($mode, @files) = @args;
my @files_to_change;
for my $file (@files) {
my @s = stat($file);
if (@s) {
if (($s[2] & 07777) != $mode) {
push @files_to_change, $file;
}
} else {
push @files_to_change, $file;
}
}
if (@files_to_change) {
my @commands = {
code => sub {
my $changed_files = chmod $mode, @files_to_change;
if ($changed_files != @files_to_change) {
if (@files_to_change == 1) {
error "chmod failed: $!";
} elsif ($changed_files == 0) {
error "chmod failed on all files: $!";
} else {
error "chmod failed on some files (" . (@files_to_change-$changed_files) . "/" . scalar(@files_to_change) . "): $!";
}
}
},
($quiet ? () : (msg => sprintf("chmod 0%o %s", $mode, join(" ", @files_to_change)))), # shellquote?
rv => scalar @files_to_change,
};
Doit::Commands->new(@commands);
} else {
Doit::Commands->return_zero;
}
}
sub cmd_chown {
my($self, @args) = @_;
my %options; if (@args && ref $args[0] eq 'HASH') { %options = %{ shift @args } }
my $quiet = delete $options{quiet};
error "Unhandled options: " . join(" ", %options) if %options;
my($uid, $gid, @files) = @args;
if (!defined $uid) {
$uid = -1;
} elsif ($uid !~ /^-?\d+$/) {
my $_uid = (getpwnam $uid)[2];
if (!defined $_uid) {
# XXX problem: in dry-run mode the user/group could be
# created in _this_ pass, so this error would happen
# while in wet-run everything would be fine. Good solution?
# * do uid/gid resolution _again_ in the command if it failed here?
# * maintain a virtual list of created users/groups while this run, and
# use this list as a fallback?
error "User '$uid' does not exist";
}
$uid = $_uid;
}
if (!defined $gid) {
$gid = -1;
} elsif ($gid !~ /^-?\d+$/) {
my $_gid = (getgrnam $gid)[2];
if (!defined $_gid) {
error "Group '$gid' does not exist";
}
$gid = $_gid;
}
my @files_to_change;
if ($uid != -1 || $gid != -1) {
for my $file (@files) {
my @s = stat($file);
if (@s) {
if ($uid != -1 && $s[4] != $uid) {
push @files_to_change, $file;
} elsif ($gid != -1 && $s[5] != $gid) {
push @files_to_change, $file;
}
} else {
push @files_to_change, $file;
}
}
}
if (@files_to_change) {
my @commands = {
code => sub {
my $changed_files = chown $uid, $gid, @files_to_change;
lib/Doit.pm view on Meta::CPAN
__PACKAGE__->install_cmd($name);
}
sub install_cmd {
shift; # $class unused
my $cmd = shift;
my $meth = 'cmd_' . $cmd;
my $code = sub {
my($self, @args) = @_;
if ($self->{dryrun}) {
$self->{Doit}->$meth(@args)->show;
} else {
$self->{Doit}->$meth(@args)->doit;
}
};
no strict 'refs';
*{$cmd} = $code;
}
sub add_component {
my($self, $component_or_module) = @_;
my $module;
if ($component_or_module =~ /::/) {
$module = $component_or_module;
} else {
$module = 'Doit::' . ucfirst($component_or_module);
}
for (@{ $self->{components} }) {
return if $_->{module} eq $module;
}
if (!eval qq{ require $module; 1 }) {
Doit::Log::error("Cannot load $module: $@");
}
my $o = $module->new
or Doit::Log::error("Error while calling $module->new");
for my $function ($o->functions) {
my $fullqual = $module.'::'.$function;
my $code = sub {
my($self, @args) = @_;
$self->$fullqual(@args);
};
no strict 'refs';
*{$function} = $code;
}
my $mod_file = do {
(my $relpath = $module) =~ s{::}{/};
$relpath .= '.pm';
};
push @{ $self->{components} }, { module => $module, path => $INC{$mod_file}, relpath => $mod_file };
if ($o->can('add_components')) {
for my $sub_component ($o->add_components) {
$self->add_component($sub_component);
}
}
}
for my $cmd (
qw(chmod chown mkdir rename rmdir symlink unlink utime),
qw(make_path remove_tree), # File::Path
qw(copy move), # File::Copy
qw(run), # IPC::Run
qw(qx info_qx), # qx// and variant which even runs in dry-run mode, both using list syntax
qw(open2 info_open2), # IPC::Open2
qw(open3 info_open3), # IPC::Open3
qw(system info_system), # builtin system with variant
qw(cond_run), # conditional run
qw(touch), # like unix touch
qw(ln_nsf), # like unix ln -nsf
qw(which), # like unix which
qw(create_file_if_nonexisting), # does the half of touch
qw(write_binary), # like File::Slurper
qw(change_file), # own invention
qw(setenv unsetenv), # $ENV manipulation
) {
__PACKAGE__->install_cmd($cmd);
}
sub call_wrapped_method {
my($self, $context, $method, @args) = @_;
my @ret;
if ($context eq 'a') {
@ret = eval { $self->$method(@args) };
} else {
$ret[0] = eval { $self->$method(@args) };
}
if ($@) {
('e', $@);
} else {
('r', @ret);
}
}
# XXX call vs. call_with_runner ???
sub call {
my($self, $sub, @args) = @_;
$sub = 'main::' . $sub if $sub !~ /::/;
no strict 'refs';
&$sub(@args);
}
sub call_with_runner {
my($self, $sub, @args) = @_;
$sub = 'main::' . $sub if $sub !~ /::/;
no strict 'refs';
&$sub($self, @args);
}
# XXX does this belong here?
sub do_ssh_connect {
my($self, $host, %opts) = @_;
my $remote = Doit::SSH->do_connect($host, dry_run => $self->is_dry_run, components => $self->{components}, %opts);
$remote;
}
# XXX does this belong here?
sub do_sudo {
my($self, %opts) = @_;
my $sudo = Doit::Sudo->do_connect(dry_run => $self->is_dry_run, components => $self->{components}, %opts);
( run in 0.877 second using v1.01-cache-2.11-cpan-39bf76dae61 )