# # This is not a runnable script, it is a Perl module, a collection of variables, subroutines, etc. # to be used in other scripts. # # To get help about exported variables and subroutines, please execute the following command: # # perldoc tools.pm # # or see POD (Plain Old Documentation) imbedded to the source... # # #//===----------------------------------------------------------------------===// #// #// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. #// See https://llvm.org/LICENSE.txt for license information. #// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #// #//===----------------------------------------------------------------------===// # =head1 NAME B -- A collection of subroutines which are widely used in Perl scripts. =head1 SYNOPSIS use FindBin; use lib "$FindBin::Bin/lib"; use tools; =head1 DESCRIPTION B Because this collection is small and intended for widely using in particular project, all variables and functions are exported by default. B I have some ideas how to improve this collection, but it is in my long-term plans. Current shape is not ideal, but good enough to use. =cut package tools; use strict; use warnings; use vars qw( @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS ); require Exporter; @ISA = qw( Exporter ); my @vars = qw( $tool ); my @utils = qw( check_opts validate ); my @opts = qw( get_options ); my @print = qw( debug info warning cmdline_error runtime_error question ); my @name = qw( get_vol get_dir get_file get_name get_ext cat_file cat_dir ); my @file = qw( which abs_path rel_path real_path make_dir clean_dir copy_dir move_dir del_dir change_dir copy_file move_file del_file ); my @io = qw( read_file write_file ); my @exec = qw( execute backticks ); my @string = qw{ pad }; @EXPORT = ( @utils, @opts, @vars, @print, @name, @file, @io, @exec, @string ); use UNIVERSAL (); use FindBin; use IO::Handle; use IO::File; use IO::Dir; # Not available on some machines: use IO::Zlib; use Getopt::Long (); use Pod::Usage (); use Carp (); use File::Copy (); use File::Path (); use File::Temp (); use File::Spec (); use POSIX qw{ :fcntl_h :errno_h }; use Cwd (); use Symbol (); use Data::Dumper; use vars qw( $tool $verbose $timestamps ); $tool = $FindBin::Script; my @warning = ( sub {}, \&warning, \&runtime_error ); sub check_opts(\%$;$) { my $opts = shift( @_ ); # Reference to hash containing real options and their values. my $good = shift( @_ ); # Reference to an array containing all known option names. my $msg = shift( @_ ); # Optional (non-mandatory) message. if ( not defined( $msg ) ) { $msg = "unknown option(s) passed"; # Default value for $msg. }; # if # I'll use these hashes as sets of options. my %good = map( ( $_ => 1 ), @$good ); # %good now is filled with all known options. my %bad; # %bad is empty. foreach my $opt ( keys( %$opts ) ) { # For each real option... if ( not exists( $good{ $opt } ) ) { # Look its name in the set of known options... $bad{ $opt } = 1; # Add unknown option to %bad set. delete( $opts->{ $opt } ); # And delete original option. }; # if }; # foreach $opt if ( %bad ) { # If %bad set is not empty... my @caller = caller( 1 ); # Issue a warning. local $Carp::CarpLevel = 2; Carp::cluck( $caller[ 3 ] . ": " . $msg . ": " . join( ", ", sort( keys( %bad ) ) ) ); }; # if return 1; }; # sub check_opts # -------------------------------------------------------------------------------------------------- # Purpose: # Check subroutine arguments. # Synopsis: # my %opts = validate( params => \@_, spec => { ... }, caller => n ); # Arguments: # params -- A reference to subroutine's actual arguments. # spec -- Specification of expected arguments. # caller -- ... # Return value: # A hash of validated options. # Description: # I would like to use Params::Validate module, but it is not a part of default Perl # distribution, so I cannot rely on it. This subroutine resembles to some extent to # Params::Validate::validate_with(). # Specification of expected arguments: # { $opt => { type => $type, default => $default }, ... } # $opt -- String, option name. # $type -- String, expected type(s). Allowed values are "SCALAR", "UNDEF", "BOOLEAN", # "ARRAYREF", "HASHREF", "CODEREF". Multiple types may listed using bar: # "SCALAR|ARRAYREF". The type string is case-insensitive. # $default -- Default value for an option. Will be used if option is not specified or # undefined. # sub validate(@) { my %opts = @_; # Temporary use %opts for parameters of `validate' subroutine. my $params = $opts{ params }; my $caller = ( $opts{ caller } or 0 ) + 1; my $spec = $opts{ spec }; undef( %opts ); # Ok, Clean %opts, now we will collect result of the subroutine. # Find out caller package, filename, line, and subroutine name. my ( $pkg, $file, $line, $subr ) = caller( $caller ); my @errors; # We will collect errors in array not to stop on the first found error. my $error = sub ($) { my $msg = shift( @_ ); push( @errors, "$msg at $file line $line.\n" ); }; # sub # Check options. while ( @$params ) { # Check option name. my $opt = shift( @$params ); if ( not exists( $spec->{ $opt } ) ) { $error->( "Invalid option `$opt'" ); shift( @$params ); # Skip value of unknow option. next; }; # if # Check option value exists. if ( not @$params ) { $error->( "Option `$opt' does not have a value" ); next; }; # if my $val = shift( @$params ); # Check option value type. if ( exists( $spec->{ $opt }->{ type } ) ) { # Type specification exists. Check option value type. my $actual_type; if ( ref( $val ) ne "" ) { $actual_type = ref( $val ) . "REF"; } else { $actual_type = ( defined( $val ) ? "SCALAR" : "UNDEF" ); }; # if my @wanted_types = split( m{\|}, lc( $spec->{ $opt }->{ type } ) ); my $wanted_types = join( "|", map( $_ eq "boolean" ? "scalar|undef" : quotemeta( $_ ), @wanted_types ) ); if ( $actual_type !~ m{\A(?:$wanted_types)\z}i ) { $actual_type = lc( $actual_type ); $wanted_types = lc( join( " or ", map( "`$_'", @wanted_types ) ) ); $error->( "Option `$opt' value type is `$actual_type' but expected to be $wanted_types" ); next; }; # if }; # if if ( exists( $spec->{ $opt }->{ values } ) ) { my $values = $spec->{ $opt }->{ values }; if ( not grep( $_ eq $val, @$values ) ) { $values = join( ", ", map( "`$_'", @$values ) ); $error->( "Option `$opt' value is `$val' but expected to be one of $values" ); next; }; # if }; # if $opts{ $opt } = $val; }; # while # Assign default values. foreach my $opt ( keys( %$spec ) ) { if ( not defined( $opts{ $opt } ) and exists( $spec->{ $opt }->{ default } ) ) { $opts{ $opt } = $spec->{ $opt }->{ default }; }; # if }; # foreach $opt # If we found any errors, raise them. if ( @errors ) { die join( "", @errors ); }; # if return %opts; }; # sub validate # ================================================================================================= # Get option helpers. # ================================================================================================= =head2 Get option helpers. =cut # ------------------------------------------------------------------------------------------------- =head3 get_options B get_options( @arguments ) B It is very simple wrapper arounf Getopt::Long::GetOptions. It passes all arguments to GetOptions, and add definitions for standard help options: --help, --doc, --verbose, and --quiet. When GetOptions finishes, this subroutine checks exit code, if it is non-zero, standard error message is issued and script terminated. If --verbose or --quiet option is specified, C environment variable is set. It is the way to propagate verbose/quiet mode to callee Perl scripts. =cut sub get_options { Getopt::Long::Configure( "no_ignore_case" ); Getopt::Long::GetOptions( "h0|usage" => sub { Pod::Usage::pod2usage( -exitval => 0, -verbose => 0 ); }, "h1|h|help" => sub { Pod::Usage::pod2usage( -exitval => 0, -verbose => 1 ); }, "h2|doc|manual" => sub { Pod::Usage::pod2usage( -exitval => 0, -verbose => 2 ); }, "version" => sub { print( "$tool version $main::VERSION\n" ); exit( 0 ); }, "v|verbose" => sub { ++ $verbose; $ENV{ "tools.pm_verbose" } = $verbose; }, "quiet" => sub { -- $verbose; $ENV{ "tools.pm_verbose" } = $verbose; }, "with-timestamps" => sub { $timestamps = 1; $ENV{ "tools.pm_timestamps" } = $timestamps; }, @_, # Caller arguments are at the end so caller options overrides standard. ) or cmdline_error(); }; # sub get_options # ================================================================================================= # Print utilities. # ================================================================================================= =pod =head2 Print utilities. Each of the print subroutines prepends each line of its output with the name of current script and the type of information, for example: info( "Writing file..." ); will print