App-Access2CSV
view release on metacpan or search on metacpan
lib/App/Access2CSV/Exporter.pm view on Meta::CPAN
# File tests, evals and child processes below would otherwise leave
# their marks in the caller's $@ and $!
local ($@, $!);
# An undef database is a missing one, not a file called "". Work on a
# copy: get_params hands back the caller's own hash when given one.
# (Params::Get either dies or returns a hash reference - proved in
# t/path.t - so no test of what it returned is needed.)
my $input = { %{ get_params('database', \@_) } };
delete $input->{database} unless defined $input->{database};
my $params = validate_strict(
schema => { database => { type => 'string', min => 1 } },
input => $input,
);
my $database = $params->{database};
# Fail fast, before any output, on problems that affect every table
$self->_check_database($database)
->_verify_dependencies()
->_reset_names();
# Stopping part-way must behave like a failed transaction: the table
# being exported is discarded (its temporary file deleted) and no
# further table is started. Perl's default action for these signals
# is to exit at once, skipping the clean-up, so while run() is active
# they raise an exception instead. A handler the caller has set is
# left alone; everything is restored when run() returns.
local $self->{interrupted};
my @ours = @{ $self->_interrupt_signals() };
local @SIG{@ours} = (sub {
$self->{interrupted} = $_[0];
die $self->_printable($self->i18n('interrupted', { params => [$_[0]] })), "\n";
}) x @ours;
# Premise: the database is now known to be a readable regular file, and
# it is only ever passed to mdbtools as one list argument after "--".
# Conclusion: it is safe to untaint.
$database = _untaint($database);
my $tables = $self->_select_tables($self->_get_tables($database));
# Guard clause: a dry run must not touch the file system, so it leaves
# before mkdir. Premise: _dry_run writes nothing that can fail a table.
# Conclusion: a dry run always succeeds.
if($self->{dry_run}) {
$self->_dry_run($database, $tables);
return set_return($EXIT_OK, { %RUN_STATUS_SCHEMA });
}
# The output directory is only made when there is something to put in it;
# with no tables selected the run goes straight to the summary
my $failed = (@{$tables} ? $self->_make_output_dir() : $self)->_export_all($database, $tables);
return set_return($failed ? $EXIT_FAILURE : $EXIT_OK, { %RUN_STATUS_SCHEMA });
}
# _check_database
# Purpose: Make sure the database is a readable regular file.
# Entry Criteria: $database is a defined, non-empty path.
# Exit Status: Returns $self for chaining; croaks otherwise.
# Side Effects: stat()s the file; sets $!.
sub _check_database :Private {
my ($self, $database) = @_;
# The stat result is reused via "_" so the file is only examined once;
# $! is captured straight away because later calls may overwrite it
if(!-e $database) {
$self->_croak_i18n('database_not_found', { params => [$database, "$!"] });
}
$self->_croak_i18n('database_not_file', { params => [$database] }) unless -f _;
$self->_croak_i18n('database_unreadable', { params => [$database] }) unless -r _;
return $self;
}
# _verify_dependencies
# Purpose: Locate the mdbtools programs in PATH.
# Entry Criteria: None.
# Exit Status: Returns $self; croaks if a required program is missing.
# Side Effects: Sets $self->{programs}; may switch off show_counts (with a
# warning) when mdb-count is unavailable; logs at debug level.
sub _verify_dependencies :Private {
my $self = shift;
my %programs;
foreach my $program (@REQUIRED_PROGRAMS) {
$programs{$program} = $self->_find_program($program)
or $self->_croak_i18n('program_missing', { params => [$program] });
}
# mdb-count is only needed for row counts, so its absence is not fatal.
# _find_program returns a path or false, so one branch decides both
# "store it" and "switch counts off" (nothing is stored and removed).
if($self->{show_counts}) {
if(my $path = $self->_find_program($MDB_COUNT)) {
$programs{$MDB_COUNT} = $path;
} else {
$self->{show_counts} = 0;
$self->_warn('no_row_counter');
}
}
$self->{programs} = \%programs;
return $self;
}
# _find_program
# Purpose: Look up one program in PATH and note where it was found.
# Entry Criteria: $program is a bare program name.
# Exit Status: Returns the full path, or undef if not found.
# Side Effects: Logs the location at debug level when --verbose is on.
sub _find_program :Private {
my ($self, $program) = @_;
# Only absolute paths are trusted. A relative entry in PATH (".", or
# an empty one) would run whatever file of that name is in the current
# directory - a classic way to plant a program.
my ($path) = grep { defined && File::Spec->file_name_is_absolute($_) } which($program);
# An absolute path to an existing program: safe to untaint
$path = _untaint($path) if defined $path;
( run in 2.653 seconds using v1.01-cache-2.11-cpan-036bef1c656 )