123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- # ------------------------------------------------------------------------------
- # NAME
- # Fcm::Util
- #
- # DESCRIPTION
- # This is a package of misc utilities used by the FCM command.
- #
- # COPYRIGHT
- # (C) Crown copyright Met Office. All rights reserved.
- # For further details please refer to the file COPYRIGHT.txt
- # which you should have received as part of this distribution.
- # ------------------------------------------------------------------------------
- use warnings;
- use strict;
- package Fcm::Util;
- require Exporter;
- our @ISA = qw{Exporter};
- sub expand_tilde;
- sub e_report;
- sub find_file_in_path;
- sub get_command_string;
- sub get_rev_of_wc;
- sub get_url_of_wc;
- sub get_url_peg_of_wc;
- sub get_wct;
- sub is_url;
- sub is_wc;
- sub print_command;
- sub run_command;
- sub svn_date;
- sub tidy_url;
- sub touch_file;
- sub w_report;
- our @EXPORT = qw{
- expand_tilde
- e_report
- find_file_in_path
- get_command_string
- get_rev_of_wc
- get_url_of_wc
- get_url_peg_of_wc
- get_wct
- is_url
- is_wc
- print_command
- run_command
- svn_date
- tidy_url
- touch_file
- w_report
- };
- # Standard modules
- use Carp;
- use Cwd;
- use File::Basename;
- use File::Find;
- use File::Path;
- use File::Spec;
- use POSIX qw{strftime SIGINT SIGKILL SIGTERM WEXITSTATUS WIFSIGNALED WTERMSIG};
- # FCM component modules
- use Fcm::Timer;
- # ------------------------------------------------------------------------------
- # Module level variables
- my %svn_info = (); # "svn info" log, (key1 = path,
- # key2 = URL, Revision, Last Changed Rev)
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # %srcdir = &Fcm::Util::find_file_in_path ($file, \@path);
- #
- # DESCRIPTION
- # Search $file in @path. Returns the full path of the $file if it is found
- # in @path. Returns "undef" if $file is not found in @path.
- # ------------------------------------------------------------------------------
- sub find_file_in_path {
- my ($file, $path) = @_;
- for my $dir (@$path) {
- my $full_file = File::Spec->catfile ($dir, $file);
- return $full_file if -e $full_file;
- }
- return undef;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $expanded_path = &Fcm::Util::expand_tilde ($path);
- #
- # DESCRIPTION
- # Returns an expanded path if $path is a path that begins with a tilde (~).
- # ------------------------------------------------------------------------------
- sub expand_tilde {
- my $file = $_[0];
- $file =~ s#^~([^/]*)#$1 ? (getpwnam $1)[7] : ($ENV{HOME} || $ENV{LOGDIR})#ex;
- # Expand . and ..
- while ($file =~ s#/+\.(?:/+|$)#/#g) {next}
- while ($file =~ s#/+[^/]+/+\.\.(?:/+|$)#/#g) {next}
- # Remove trailing /
- $file =~ s#/*$##;
- return $file;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $rc = &Fcm::Util::touch_file ($file);
- #
- # DESCRIPTION
- # Touch $file if it exists. Create $file if it does not exist. Return 1 for
- # success or 0 otherwise.
- # ------------------------------------------------------------------------------
- sub touch_file {
- my $file = $_[0];
- my $rc = 1;
- if (-e $file) {
- my $now = time;
- $rc = utime $now, $now, $file;
- } else {
- mkpath dirname ($file) unless -d dirname ($file);
- $rc = open FILE, '>', $file;
- $rc = close FILE if $rc;
- }
- return $rc;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $flag = &is_wc ([$path]);
- #
- # DESCRIPTION
- # Returns true if current working directory (or $path) is a Subversion
- # working copy.
- # ------------------------------------------------------------------------------
- sub is_wc {
- my $path = @_ ? $_[0] : cwd ();
- if (-d $path) {
- return (-e File::Spec->catfile ($path, qw/.svn format/)) ? 1 : 0;
- } elsif (-f $path) {
- return (-e File::Spec->catfile (dirname ($path), qw/.svn format/)) ? 1 : 0;
- } else {
- return 0;
- }
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $flag = &is_url ($url);
- #
- # DESCRIPTION
- # Returns true if $url is a URL.
- # ------------------------------------------------------------------------------
- sub is_url {
- # This should handle URL beginning with svn://, http:// and svn+ssh://
- return ($_[0] =~ m#^[\+\w]+://#);
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $url = tidy_url($url);
- #
- # DESCRIPTION
- # Returns a tidied version of $url by removing . and .. in the path.
- # ------------------------------------------------------------------------------
- sub tidy_url {
- my ($url) = @_;
- if (!is_url($url)) {
- return $url;
- }
- my $DOT_PATTERN = qr{/+ \. (?:/+|(@|\z))}xms;
- my $DOT_DOT_PATTERN = qr{/+ [^/]+ /+ \.\. (?:/+|(@|\z))}xms;
- my $TRAILING_SLASH_PATTERN = qr{([^/]+) /* (@|\z)}xms;
- my $RIGHT_EVAL = q{'/' . ($1 ? $1 : '')};
- DOT:
- while ($url =~ s{$DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
- next DOT;
- }
- DOT_DOT:
- while ($url =~ s{$DOT_DOT_PATTERN}{$RIGHT_EVAL}eegxms) {
- next DOT_DOT;
- }
- $url =~ s{$TRAILING_SLASH_PATTERN}{$1$2}xms;
- return $url;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $string = &get_wct ([$dir]);
- #
- # DESCRIPTION
- # If current working directory (or $dir) is a Subversion working copy,
- # returns the top directory of this working copy; otherwise returns an empty
- # string.
- # ------------------------------------------------------------------------------
- sub get_wct {
- my $dir = @_ ? $_[0] : cwd ();
- return '' if not &is_wc ($dir);
- my $updir = dirname $dir;
- while (&is_wc ($updir)) {
- $dir = $updir;
- $updir = dirname $dir;
- last if $updir eq $dir;
- }
- return $dir;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $string = &get_url_of_wc ([$path[, $refresh]]);
- #
- # DESCRIPTION
- # If current working directory (or $path) is a Subversion working copy,
- # returns the URL of the associated Subversion repository; otherwise returns
- # an empty string. If $refresh is specified, do not use the cached
- # information.
- # ------------------------------------------------------------------------------
- sub get_url_of_wc {
- my $path = @_ ? $_[0] : cwd ();
- my $refresh = exists $_[1] ? $_[1] : 0;
- my $url = '';
- if (&is_wc ($path)) {
- delete $svn_info{$path} if $refresh;
- &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
- $url = $svn_info{$path}{URL};
- }
- return $url;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $string = &get_url_peg_of_wc ([$path[, $refresh]]);
- #
- # DESCRIPTION
- # If current working directory (or $path) is a Subversion working copy,
- # returns the URL@REV of the associated Subversion repository; otherwise
- # returns an empty string. If $refresh is specified, do not use the cached
- # information.
- # ------------------------------------------------------------------------------
- sub get_url_peg_of_wc {
- my $path = @_ ? $_[0] : cwd ();
- my $refresh = exists $_[1] ? $_[1] : 0;
- my $url = '';
- if (&is_wc ($path)) {
- delete $svn_info{$path} if $refresh;
- &_invoke_svn_info (PATH => $path) unless exists $svn_info{$path};
- $url = $svn_info{$path}{URL} . '@' . $svn_info{$path}{Revision};
- }
- return $url;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # &_invoke_svn_info (PATH => $path);
- #
- # DESCRIPTION
- # The function is internal to this module. It invokes "svn info" on $path to
- # gather information on URL, Revision and Last Changed Rev. The information
- # is stored in a hash table at the module level, so that the information can
- # be re-used.
- # ------------------------------------------------------------------------------
- sub _invoke_svn_info {
- my %args = @_;
- my $path = $args{PATH};
- my $cfg = Fcm::Config->instance();
- return if exists $svn_info{$path};
- # Invoke "svn info" command
- my @info = &run_command (
- [qw/svn info/, $path],
- PRINT => $cfg->verbose > 2, METHOD => 'qx', DEVNULL => 1, ERROR => 'ignore',
- );
- for (@info) {
- chomp;
- if (/^(URL|Revision|Last Changed Rev):\s*(.+)$/) {
- $svn_info{$path}{$1} = $2;
- }
- }
- return;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $string = &get_command_string ($cmd);
- # $string = &get_command_string (\@cmd);
- #
- # DESCRIPTION
- # The function returns a string by converting the list in @cmd or the scalar
- # $cmd to a form, where it can be executed as a shell command.
- # ------------------------------------------------------------------------------
- sub get_command_string {
- my $cmd = $_[0];
- my $return = '';
- if (ref ($cmd) and ref ($cmd) eq 'ARRAY') {
- # $cmd is a reference to an array
- # Print each argument
- for my $i (0 .. @{ $cmd } - 1) {
- my $arg = $cmd->[$i];
- $arg =~ s/./*/g if $i > 0 and $cmd->[$i - 1] eq '--password';
- if ($arg =~ /[\s'"*?]/) {
- # Argument contains a space, quote it
- if (index ($arg, "'") >= 0) {
- # Argument contains an apostrophe, quote it with double quotes
- $return .= ($i > 0 ? ' ' : '') . '"' . $arg . '"';
- } else {
- # Otherwise, quote argument with apostrophes
- $return .= ($i > 0 ? ' ' : '') . "'" . $arg . "'";
- }
- } else {
- # Argument does not contain a space, just print it
- $return .= ($i > 0 ? ' ' : '') . ($arg eq '' ? "''" : $arg);
- }
- }
- } else {
- # $cmd is a scalar, just print it "as is"
- $return = $cmd;
- }
- return $return;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # &print_command ($cmd);
- # &print_command (\@cmd);
- #
- # DESCRIPTION
- # The function prints the list in @cmd or the scalar $cmd, as it would be
- # executed by the shell.
- # ------------------------------------------------------------------------------
- sub print_command {
- my $cmd = $_[0];
- print '=> ', &get_command_string ($cmd) , "\n";
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # @return = &run_command (\@cmd, <OPTIONS>);
- # @return = &run_command ($cmd , <OPTIONS>);
- #
- # DESCRIPTION
- # This function executes the command in the list @cmd or in the scalar $cmd.
- # The remaining are optional arguments in a hash table. Valid options are
- # listed below. If the command is run using "qx", the function returns the
- # standard output from the command. If the command is run using "system", the
- # function returns true on success. By default, the function dies on failure.
- #
- # OPTIONS
- # METHOD => $method - this can be "system", "exec" or "qx". This determines
- # how the command will be executed. If not set, the
- # default is to run the command with "system".
- # PRINT => 1 - if set, print the command before executing it.
- # ERROR => $flag - this should only be set if METHOD is set to "system"
- # or "qx". The $flag can be "die" (default), "warn" or
- # "ignore". If set to "die", the function dies on error.
- # If set to "warn", the function issues a warning on
- # error, and the function returns false. If set to
- # "ignore", the function returns false on error.
- # RC => 1 - if set, must be a reference to a scalar, which will be
- # set to the return code of the command.
- # DEVNULL => 1 - if set, re-direct STDERR to /dev/null before running
- # the command.
- # TIME => 1 - if set, print the command with a timestamp before
- # executing it, and print the time taken when it
- # completes. This option supersedes the PRINT option.
- # ------------------------------------------------------------------------------
- sub run_command {
- my ($cmd, %input_opt_of) = @_;
- my %opt_of = (
- DEVNULL => undef,
- ERROR => 'die',
- METHOD => 'system',
- PRINT => undef,
- RC => undef,
- TIME => undef,
- %input_opt_of,
- );
- local($|) = 1; # Make sure STDOUT is flushed before running command
- # Print the command before execution, if necessary
- if ($opt_of{TIME}) {
- print(timestamp_command(get_command_string($cmd)));
- }
- elsif ($opt_of{PRINT}) {
- print_command($cmd);
- }
- # Re-direct STDERR to /dev/null if necessary
- if ($opt_of{DEVNULL}) {
- no warnings;
- open(OLDERR, ">&STDERR") || croak("Cannot dup STDERR ($!), abort");
- use warnings;
- open(STDERR, '>', File::Spec->devnull())
- || croak("Cannot redirect STDERR ($!), abort");
- # Make sure the channels are unbuffered
- my $select = select();
- select(STDERR); local($|) = 1;
- select($select);
- }
- my @return = ();
- if (ref($cmd) && ref($cmd) eq 'ARRAY') {
- # $cmd is an array
- my @command = @{$cmd};
- if ($opt_of{METHOD} eq 'qx') {
- @return = qx(@command);
- }
- elsif ($opt_of{METHOD} eq 'exec') {
- exec(@command);
- }
- else {
- system(@command);
- @return = $? ? () : (1);
- }
- }
- else {
- # $cmd is an scalar
- if ($opt_of{METHOD} eq 'qx') {
- @return = qx($cmd);
- }
- elsif ($opt_of{METHOD} eq 'exec') {
- exec($cmd);
- }
- else {
- system($cmd);
- @return = $? ? () : (1);
- }
- }
- my $rc = $?;
- # Put STDERR back to normal, if redirected previously
- if ($opt_of{DEVNULL}) {
- close(STDERR);
- open(STDERR, ">&OLDERR") || croak("Cannot dup STDERR ($!), abort");
- }
- # Print the time taken for command after execution, if necessary
- if ($opt_of{TIME}) {
- print(timestamp_command(get_command_string($cmd), 'end'));
- }
- # Signal and return code
- my ($signal, $status) = (WTERMSIG($rc), WEXITSTATUS($rc));
- if (exists($opt_of{RC})) {
- ${$opt_of{RC}} = $status;
- }
- if (WIFSIGNALED($rc) && grep {$signal == $_} (SIGINT, SIGKILL, SIGTERM)) {
- croak(sprintf('%s terminated (%d)', get_command_string($cmd), $signal));
- }
- if ($status && $opt_of{ERROR} ne 'ignore') {
- my $func_ref = $opt_of{ERROR} eq 'warn' ? \&carp : \&croak;
- $func_ref->(sprintf('%s failed (%d)', get_command_string($cmd), $status));
- }
- return @return;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # &e_report (@message);
- #
- # DESCRIPTION
- # The function prints @message to STDERR and aborts with a error.
- # ------------------------------------------------------------------------------
- sub e_report {
- print STDERR @_, "\n" if @_;
- exit 1;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # &w_report (@message);
- #
- # DESCRIPTION
- # The function prints @message to STDERR and returns.
- # ------------------------------------------------------------------------------
- sub w_report {
- print STDERR @_, "\n" if @_;
- return;
- }
- # ------------------------------------------------------------------------------
- # SYNOPSIS
- # $date = &svn_date ($time);
- #
- # DESCRIPTION
- # The function returns a date, formatted as by Subversion. The argument $time
- # is the number of seconds since epoch.
- # ------------------------------------------------------------------------------
- sub svn_date {
- my $time = shift;
- return strftime ('%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)', localtime ($time));
- }
- # ------------------------------------------------------------------------------
- 1;
- __END__
|