package Util::Parfile;
##############################################################################
#
# DESCRIPTION: This is an interface to an IRAF/FTOOLS parameter file.
#
# HISTORY
# HISTORY: $Log: Parfile.pm,v $
# HISTORY: Revision 1.7 2014/04/30 01:21:52 apsop
# HISTORY: In sub read(), skip blank lines in the parfile, else
# HISTORY: _parse_line will choke.
# HISTORY:
# HISTORY: Revision 1.6 2014/02/27 07:01:07 apsop
# HISTORY: VERSION header now shows CVS Revision
# HISTORY:
# HISTORY: Revision 1.5 2011/01/18 20:43:38 apsop
# HISTORY: Modified subroutine set for better handling of parametr files
# HISTORY:
# HISTORY: Revision 1.4 2007/04/18 22:45:38 apsop
# HISTORY: Bug fix on the error call.
# HISTORY:
# HISTORY: Revision 1.3 2007/01/31 15:43:37 apsop
# HISTORY: Read and write parameter files directly from Perl, instead of calling pset/pget.
# HISTORY:
# HISTORY: Revision 1.2 2006/08/01 20:35:34 apsop
# HISTORY: Add in CVS history indicator.
# HISTORY:
# HISTORY: 1.0 -> 1.1 2002-04-01
# HISTORY: Fixed a minor bug in the error method. When this method is
# HISTORY: called by the class and not by an object, $self->{NAME} is
# HISTORY: meaningless. Made a sort of a cludgly fix by only appending
# HISTORY: the parfile name to the message if the log is defined.
# HISTORY:
# HISTORY: 1.1 -> 1.2 2002-04-19
# HISTORY: Changed the FTOOLS initialization method to take the install
# HISTORY: directory and not the bin directory and also to add the
# HISTORY: lib directory to LD_LIBRARY_PATH, since the FTOOLS initialization
# HISTORY: now only does this when it runs a tool.
# HISTORY:
# HISTORY: 1.2 -> 1.3
# HISTORY: Added special handling for when LD_LIBRARY_PATH is not already set
# HISTORY:
# HISTORY: 1.3 -> 1.4 2003-12-05
# HISTORY: now set LD_LIBRARY_PATh before running pget and then restore it when
# HISTORY: done.
# HISTORY: 1.4 -> 1.5 2005-04-01 Compensate for bug in pset which doesn't
# HISTORY: handle null values properly.
#
# VERSION: $Revision: 1.7 $
#
##############################################################################
require Exporter;
*import = \&Exporter::import;
@EXPORT_OK = qw(isa can);
use IPC::Open3;
#use Util::Log;
use strict;
my $PGET="";
my $PSET="";
my $LOG="";
my $LD_LIBRARY_PATH="";
######################################
# constructor
######################################
sub new { #(name)
my $proto = shift;
my $class = ref($proto) || $proto;
my $self={};
$self->{NAME} = shift;
$self->{CACHE} = {}; # storage to remember params we have read
$self->{PGET_STDERR}="pget_stderr.log";
unlink $self->{PGET_STDERR};
bless($self,$class);
########################################
# make sure the parfile exists
########################################
if( ! -f $self->{NAME} ) {
$self->error("No such parfile");
}
return $self;
}
##############
# ACCESSORS:
##############
############################################
# get or set the name of the parameter file
############################################
sub name {
my $self = shift;
if (@_) { $self->{NAME} = shift }
return $self->{NAME};
}
######################################################################
# get or set the name of the program used to read the parameter file.
######################################################################
sub pget {
my $self = shift;
if (@_) {
#######################
# setting
#######################
$PGET = shift;
if( ! -x $PGET ) {
$self->error("Can't execute $PGET program");
}
}
return $PGET;
}
##########################################################################
# get or set the name of the program used to write to the parameter file.
##########################################################################
sub pset {
my $self = shift;
if (@_) { $PSET = shift }
return $PSET;
}
##########################################################################
# get or set the name of the Util::Log object.
# returns a new Util::NoLog object if there is no Log.
##########################################################################
sub log {
my $self = shift;
if (@_) { $LOG = shift }
if($LOG) {return $LOG}
else {return Util::NoLog->new() }
}
##########################################################################
# set the parfile reader and writer (pget and pset) given the
# FTOOLS instalation directory
##########################################################################
sub ftools {
my $self = shift;
my $ftools = shift;
###############################################
# get the pget/pset binary directory and the
# shared object library directory
###############################################
my $bin="$ftools/bin";
my $lib="$ftools/lib";
$LD_LIBRARY_PATH = $lib;
$self->pget("$bin/pget");
$self->pset("$bin/pset");
}
####################
# METHODS:
####################
##########################################################################
# prepare the LD_LIBRARY path for running pset or pget
##########################################################################
sub set_ld_library_path {
my $self = shift;
$self->{ORIGINAL_PATH}=$ENV{LD_LIBRARY_PATH};
if($LD_LIBRARY_PATH) {
##################################
# need to set the LD_LIBRARY_PATH
# before running pget
##################################
if($ENV{LD_LIBRARY_PATH}) {
$ENV{LD_LIBRARY_PATH}="$LD_LIBRARY_PATH:$ENV{LD_LIBRARY_PATH}";
} else {
$ENV{LD_LIBRARY_PATH}=$LD_LIBRARY_PATH;
}
}
} # end of set_ld_library_path method
##########################################################################
# return LD_LIBRARY path to its value the last time set_ld_library_path
# was run
##########################################################################
sub restore_ld_library_path {
my $self = shift;
$ENV{LD_LIBRARY_PATH}=$self->{ORIGINAL_PATH};
}
##########################################################################
# Parameter values are chached internally to save time when
# doing multiple reads of the same parameter. This can cause
# problems if the parameter is modified by some other means, so this method
# can be used to wipe out all cached values
##########################################################################
sub clear_param_cache {
my $self=shift;
$self->{CACHE}={};
}
sub _parse_line {
my $self = shift;
my $line =shift;
chomp $line;
my @fields;
my $quote=0;
######################################################
# Line delimited by commas. Have to deal with commas
# embedded in quotes.
######################################################
foreach my $s ( split(',', $line, -1) ){
if($quote){
$fields[-1] .= ',' . $s;
}else{
push @fields, $s;
}
$quote = (++$quote)%2 if( $s =~ /"/ && $s !~ /".*"/ );
}
if( @fields != 7 ){
$self->error("Unable to parse parameter line: |$line|");
}
return @fields;
}
############################################################################
# Read a value from the parfile.
# Arguments:
# param - parameter to read from PARFILE
# accuracy - optional, number of dec places to round real values
# Returns: value of param
# Note that parameter values are cached - see "clear_param_cache".
# For real values parameters, the optional
# accuracy argument specifies the number of decimal places to round to.
# For example accuracy=0.01 would round to the nearest hundredth.
###########################################################################
sub read {
my $self = shift;
my $param = shift;
my $accuracy=shift;
#####################################
# make sure the parameter is defined
# to avoid messes with pget
#####################################
if(!$param) {
$self->error("no parameter given to read from");
return "";
}
################################################
# get the parameter value in one of three ways
################################################
my $value;
if($self->{CACHE}->{$param} ) {
###############################################
# already read this param, return stored value
###############################################
$value=$self->{CACHE}->{$param};
} else {
################################################
# first time for this value get it from parfile
################################################
if(open PARFILE, "<$self->{NAME}") {
$value="";
while (<PARFILE>) {
next if /^#/; # comment
next if /^\s*$/; # blank line
my @fields = $self->_parse_line($_);
next unless $fields[0] =~ /^\s*${param}\s*$/;
$value = $fields[3];
$value =~ s/^[\"\s]+//;
$value =~ s/[\"\s]+$//;
# last;
}
close PARFILE;
}else{
$value="";
$self->error("Can't read $param using perl from");
}
#####################################
# save the value for repeated calls
#####################################
$self->{CACHE}->{$param}=$value;
} # end if we need to read the parfile
#########################################################
# round the value if an accuracy was given and return
#########################################################
if(defined $accuracy) {
##########################
# round before returning
##########################
my $round;
if($value>=0) {$round= 0.5 }
if($value<=0) {$round=-0.5}
return int($value/$accuracy + $round) * $accuracy;
} else {
#########################
# return as-is
#########################
return $value
}
} # end of read method
###############################################################
# check if a particular parameter exists in a parfile, and
# return the number of times that the specificed parameter
# appears.
###############################################################
sub has {
my $self=shift;
my $param=shift;
open FILE, "<$self->{NAME}";
my $count=grep /^${param}\s*,/, <FILE>;
close FILE;
return $count;
}
###################################################
# internal error handler
###################################################
sub error {
my $self = shift;
my $message = shift;
if($LOG) {
######################################
# log is defined so write error there
######################################
$message .= " ".$self->{NAME};
$LOG->error(2,$message);
} else {
print STDERR "$message\n";
exit 1;
}
} # end of error method
########################################################
# set a parameter in the parfile.
# Note the parameters are given as reference to a hash.
########################################################
sub set{
my $self=shift;
my $params=shift;
my @keys = keys(%$params);
my @K = sort {$a cmp $b} @keys;
my %Hk;
my $strK = '';
foreach my $k (@K){
$strK .= "$k, ";
$Hk{$k} = 1;
}
$strK =~ s/\,\s+$//;
my $temp = $self->{NAME} .".tmp";
unless( open TEMP, ">$temp" ){
$self->error("Can't open for reading file");
return;
}
my %Par;
if(open PARFILE, "<$self->{NAME}") {
my $changes = 0;
foreach my $line (<PARFILE>) {
# my $param = ( grep $line=~/^\s*$_\s*,/, @keys )[0];
# if($param){
my $param = (split/\,/, $line )[0];
if(exists $Hk{$param}){
my $value = $params->{$param};
$value = '"'. $value .'"' if $value=~/[^\w\.\-\+]/;
my @fields = $self->_parse_line($line);
$fields[3] = $value;
$line = join(',', @fields) ."\n";
$changes++;
$Par{$param} = 1;
}
print TEMP $line;
}
close PARFILE;
if ($changes > scalar(@keys)) {
my $strer = '';
foreach my $k (keys %Par) {
if (!exists $Hk{$k}) {
$strer .= "Param $k does not exist in file $self->{NAME}\n";
}
}
$self->error($strer);
}
}else{
$self->error("Can't read parameters using perl from");
close TEMP;
unlink $temp;
return;
}
close TEMP;
rename $temp, $self->{NAME};
##############################################
# now set the parameters in internal storage
# in case we want to read them later
##############################################
foreach my $key (@keys) {
if( $params->{$key} eq '' ){
delete $self->{CACHE}->{$key};
}else{
$self->{CACHE}->{$key} = $params->{$key};
}
}
}
sub set_pset { #({name1=>value1, name2=>value2 ...})
my $self=shift;
my $params=shift;
#############################################
# loop over all keys to construct an
# argument string for pset
#############################################
my $arg="";
my $key;
foreach $key (keys(%$params)) {
if( $params->{$key} eq '' ){
$arg .= "$key=\" \" ";
}else{
$arg .= "$key=\"$params->{$key}\" ";
}
}
############################################
# run pset to actually set the parameters
############################################
$self->set_ld_library_path();
my $output=`$PSET ./$self->{NAME} $arg 2>&1`;
if($output) {
print "$PSET ./$self->{NAME} $arg\n\n\n";
print "$output\n";
}
$self->restore_ld_library_path();
my $status=0;
if($output || $status) {
$self->error("$output Error setting $arg in");
}
##############################################
# now set the parameters in internal storage
# in case we want to read them later
##############################################
foreach $key (keys(%$params)) {
if( $params->{$key} eq '' ){
delete $self->{CACHE}->{$key};
}else{
$self->{CACHE}->{$key} = $params->{$key};
}
}
} # end of set method
############################################################################
# increments a parameter by a given amount (defaults to 1)
# the user must make sure it is a numerical parameter
############################################################################
sub increment {
my $self = shift;
my $param = shift;
my $increment = shift || 1;
my $value=$self->read($param);
$value += $increment;
$self->set({$param=>$value});
} # end of increment method
1;