Weather-GHCN-Fetch
view release on metacpan or search on metacpan
lib/Weather/GHCN/App/CacheUtil.pm view on Meta::CPAN
use if $OSNAME eq 'MSWin32', 'Win32::Clipboard';
########################################################################
# Global delarations
########################################################################
# is it ok to use Win32::Clipboard?
our $USE_WINCLIP = $OSNAME eq 'MSWin32';
our $Opt; # declared as 'our' for r/w access from 94_ghcn_cacheutil.t
########################################################################
# Constants
########################################################################
const my $EMPTY => q(); # empty string
const my $SPACE => q( ); # space character
const my $COMMA => q(,); # comma character
const my $TAB => qq(\t); # tab character
const my $DASH => q(-); # dash character
const my $TRUE => 1; # perl's usual TRUE
const my $FALSE => not $TRUE; # a dual-var consisting of '' and 0
const my $PROFILE_FILE => '~/.ghcn_fetch.yaml';
########################################################################
# Script Mainline
########################################################################
__PACKAGE__->run( \@ARGV ) unless caller;
#-----------------------------------------------------------------------
=head1 SUBROUTINES
=head2 run ( \@ARGV )
Invoke this subroutine, passing in a reference to @ARGV, in order to
get list of cache contents or remove cache content.
See ghnc_cache.pl -help for details.
=cut
sub run ($progname, $argv_aref) {
$Opt = get_options($argv_aref);
my $ghcn = get_ghcn($Opt->profile, $Opt->cachedir);
my $cache_pto = path($ghcn->cachedir); # pto = Path::Tiny object
if ($Opt->clean) {
my @errors = $ghcn->cache_obj->clean_cache();
if (@errors) {
say {*STDERR} join "\n", @errors;
exit 1;
}
return;
}
# send print output to the Windows clipboard if requested and doable
outclip() if $Opt->outclip and $USE_WINCLIP;
my $alias_href = get_alias_stnids($ghcn->profile_href);
my $files_href = load_cached_files($ghcn, $cache_pto, $alias_href);
if (keys $files_href->%* == 0) {
say {*STDERR} '*I* cache is empty';
return;
}
if ($Opt->remove) {
foreach my $fileid (sort keys $files_href->%*) {
my $file = $files_href->{$fileid};
next unless $file->{INCLUDE};
say {*STDERR} 'Removing ', $file->{PathObj};
$file->{PathObj}->remove;
}
return;
}
my $total_kb = report_daily_files($files_href);
say '';
say "Total cache size: ", commify($total_kb);
say 'Cache location: ', $cache_pto;
# restore print output to stdout
outclip() if $Opt->outclip and $USE_WINCLIP;
return;
}
=head2 filter_files ( \%files )
Given a hash containing Path::Tiny objects representing the files
in the designed ghcn cache folder, apply the various filtering
criteria options and mark those objects which match the criteria by
inserting the key INCLUDE with value 1 in the %files entry for
that object.
Modifies the content of %files. Void return.
=cut
sub filter_files ($files_href) {
foreach my $fileid (sort keys $files_href->%*) {
my $file = $files_href->{$fileid};
my $loc = $Opt->location;
next unless match_type( $file->{Type}, $Opt->type );
next if $Opt->country and $file->{Country} ne $Opt->country;
next if $Opt->state and $file->{State} ne $Opt->state;
my $kb = round($file->{Size} / 1024);
if (defined $Opt->size) {
next unless $Opt->size <= 0
? $kb <= -$Opt->size
lib/Weather/GHCN/App/CacheUtil.pm view on Meta::CPAN
next if $Opt->location and $file->{Location} =~ m{$loc}msi;
} else {
next if $Opt->location and $file->{Location} !~ m{$loc}msi;
}
$file->{INCLUDE} = 1;
}
}
=head2 get_ghcn ($profile, $cachedir)
Returns a Weather::GHCN::StationTable object initialized with a cache
location obtained from $cachedir or, if $cachdir is undefined, from
the cachedir option defined in the user profile specified by
$profile. If errors are encounterd, it dies and produces a list.
=cut
sub get_ghcn ($profile, $cachedir) {
my $ghcn = Weather::GHCN::StationTable->new;
$profile //= $PROFILE_FILE;
my ($opt, @errors) = $ghcn->set_options(
cachedir => $cachedir,
profile => $profile,
);
die @errors if @errors;
return $ghcn;
}
=head2 get_options ( \@ARGV )
B<get_options> encapsulates everything we need to process command line
options, or to set options when invoking this script from a test script.
Normally it's called by passing a reference to @ARGV; from a test script
you'd set up a local array variable to specify the options.
By convention, you should set up a file-scoped lexical variable named
$Opt and set it in the mainline using the return value from this function.
Then all options can be accessed used $Opt->option notation.
=cut
sub get_options ($argv_aref) {
my @options = (
'country:s', # filter by country
'state|prov:s', # filter by state or province
'location:s', # filter by localtime
'remove', # remove cached daily files (except aliases)
'clean', # remove all files from the cache
'invert|v', # invert -location selection criteria
'size|kb:i', # select files by size in Kb
'age:i', # select file if >= age
'type:s', # select based on type
'cachedir:s', # cache location
'profile:s', # profile file
'outclip', # output data to the Windows clipboard
'help','usage|?', # help
);
my %opt;
# create a list of option key names by stripping the various adornments
my @keys = map { (split m{ [!+=:|] }xms)[0] } grep { !ref } @options;
# initialize all possible options to undef
@opt{ @keys } = ( undef ) x @keys;
GetOptionsFromArray($argv_aref, \%opt, @options)
or pod2usage(2);
# Make %opt into an object and name it the same as what we usually
# call the global options object. Note that this doesn't set the
# global -- the script will have to do that using the return value
# from this function. But, what this does is allow us to call
# $Opt->help and other option within this function using the same
# syntax as what we use in the script. This is handy if you need
# to rename option '-foo' to '-bar' because you can do a find/replace
# on '$Opt->foo' and you'll get any instances of it here as well as
# in the script.
## no critic [Capitalization]
## no critic [ProhibitReusedNames]
my $Opt = _wrap_hash \%opt;
pod2usage(1) if $Opt->usage;
pod2usage(-verbose => 2) if $Opt->help;
return $Opt;
}
=head2 get_alias_stnids ( \%profile )
Read the hash obtained from the user profile file and find the alias
definitions. Return a hash of station id's that have been aliased.
=cut
sub get_alias_stnids ($profile_href) {
return {} if not $profile_href;
my $aliases_href = $profile_href->{aliases};
return {} if not $aliases_href;
my %aliases;
foreach my $stn_str (values $aliases_href->%*) {
my @stns = split $COMMA, $stn_str;
foreach my $stn (@stns) {
$aliases{$stn} = 1;
}
}
return \%aliases;
}
=head2 load_cached_files ($ghcn, $cache_pto, \%alias )
Given a Weather::GHCN::StationTable object and a cache Path::Tiny
object, and a hash of which files correspond to aliased stations,
return a hash which combines the file information and the station
information (where applicable) and categorizes each entry by type:
lib/Weather/GHCN/App/CacheUtil.pm view on Meta::CPAN
$file{PathObj} = $pathobj;
$files{$file{StationId}} = \%file;
}
foreach my $pto (@txtfiles) {
my %file;
my $fileid = $pto->basename('.txt');
$fileid =~ s{ \A ghcnd- }{}xms;
$file{StationId} = $fileid;
$file{Location} = $pto->basename;
$file{Type} = 'C';
$file{Size} = $pto->size;
$file{Age} = int -M $pto->stat;
$file{PathObj} = $pto;
$files{$file{StationId}} = \%file;
}
filter_files(\%files);
return \%files;
}
=head2 match_type ($file_type, $match_types)
Cache files are categorized by type: D for .dly files, A for .dly files
that correspond to user aliases, and C for .txt files. The user can
provide a -type option with a string to select based on type. The
string can contain any or all of the three letters. This function
is used to match the file type with the -type option. Returns true
if the $file_type letter (D, A or C) is found in the $match_types
string.
=cut
sub match_type ($file_type, $match_types) {
return $TRUE if not $match_types;
my @types = split //, $match_types;
my $matched = 0;
foreach my $m (@types) {
$matched++ if uc $m eq uc $file_type
}
return $matched++
}
=head2 outclip ()
When called initially, it redirects STDOUT to local variable so that
printing is saved in memory. On the subsequent call, it writes the
content of the variable to the Windows Clipboard and resets STDOUT
to its original state (usually the terminal).
Since Windows::Clipboard is platform specific, calls to this subroutine
should conditional. The following pattern is recommended:
# modules for Windows only
use if $OSNAME eq 'MSWin32', 'Win32::Clipboard';
# is it ok to use Win32::Clipboard?
our $USE_WINCLIP = $OSNAME eq 'MSWin32';
# send print output to the Windows clipboard if requested and doable
outclip() if $Opt->outclip and $USE_WINCLIP;
... print stuff
# restore print output to stdout
outclip() if $Opt->outclip and $USE_WINCLIP;
This subroutine relies on state variables. It cannot be used in a
nested fashion. It is best confined to main:: (or the top-level
subroutine).
=cut
sub outclip () {
state $old_fh;
state $output;
if ($old_fh) {
Win32::Clipboard->new()->Set( $output );
select $old_fh; ## no critic [ProhibitOneArgSelect]
} else {
open my $new_fh, '>', \$output
or die 'Unable to open buffer for write';
$old_fh = select $new_fh; ## no critic (ProhibitOneArgSelect)
}
return;
}
=head2 report_daily_files ($files_href)
Given a hash of the cache file hash objects, each consisting of a
merger of file properties and station properties, this subroutine
will print a report listing those that were flagged for inclusion
by filter_files().
Output is ordered by StationId. Catalog (.txt) files don't have a
station id, so short version of the filename is used. Since those
names are lowercase, they sort last in the the list.
The Type of the file appears in the first column: D for daily weather
data files, A for daily weather data files that correspond to aliases
defined in the user profile, and C for catalog files.
=cut
sub report_daily_files ($files_href) {
printf "%s %-11s %2s %2s %-9s %6s %4s %s\n", qw(T StationId Co St Active Kb Age Location);
my $total_kb = 0;
foreach my $fileid (sort keys $files_href->%*) {
my $file = $files_href->{$fileid};
next unless $file->{INCLUDE};
my $kb = round($file->{Size} / 1024);
$total_kb += $kb;
no warnings 'uninitialized';
( run in 2.861 seconds using v1.01-cache-2.11-cpan-84e82930d8c )