Dave Cross: Still Munging Data With Perl: Online event - Mar 27 Learn more

##----------------------------------------------------------------------------
## Module Generic - ~/lib/Module/Generic/Scalar.pm
## Version v1.3.4
## Copyright(c) 2022 DEGUEST Pte. Ltd.
## Author: Jacques Deguest <jack@deguest.jp>
## Created 2021/03/20
## Modified 2023/03/12
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify it
## under the same terms as Perl itself.
##----------------------------------------------------------------------------
BEGIN
{
use warnings;
use vars qw( $DEBUG $ERROR $ERRORS );
use Encode ();
# So that the user can say $obj->isa( 'Module::Generic::Scalar' ) and it would return true
# use parent -norequire, qw( Module::Generic::Scalar );
use Scalar::Util ();
use Want;
use overload (
'""' => 'as_string',
'.=' => sub
{
my( $self, $other, $swap ) = @_;
no warnings 'uninitialized';
if( !CORE::defined( $$self ) )
{
return( $other );
}
elsif( !CORE::defined( $other ) )
{
return( $$self );
}
my $expr;
if( $swap )
{
$expr = "\$other .= \$$self";
return( $other );
}
else
{
$$self .= $other;
return( $self );
}
},
'x' => sub
{
my( $self, $other, $swap ) = @_;
no warnings 'uninitialized';
my $expr = $swap ? "\"$other" x \"$$self\"" : "\"$$self\" x \"$other\"";
local $@;
my $res = eval( $expr );
if( $@ )
{
CORE::warn( $@ );
return;
}
return( $self->new( $res ) );
},
'eq' => sub
{
my( $self, $other, $swap ) = @_;
no warnings 'uninitialized';
if( Scalar::Util::blessed( $other ) && ref( $other ) eq ref( $self ) )
{
return( $$self eq $$other );
}
else
{
return( $$self eq "$other" );
}
},
fallback => 1,
);
$DEBUG = 0;
$ERRORS = {};
our $VERSION = 'v1.3.4';
};
use strict;
no warnings 'redefine';
# sub new { return( shift->_new( @_ ) ); }
sub new
{
my $this = shift( @_ );
my $class = ref( $this ) || $this;
my $init = '';
if( ref( $_[0] ) eq 'SCALAR' || UNIVERSAL::isa( $_[0], 'SCALAR' ) )
{
$init = ${$_[0]};
}
elsif( ref( $_[0] ) eq 'ARRAY' || UNIVERSAL::isa( $_[0], 'ARRAY' ) )
{
$init = CORE::join( '', @{$_[0]} );
}
elsif( ref( $_[0] ) )
{
return( $this->error( "I do not know what to do with \"", overload::StrVal( $_[0] ), "\". ${class} only suport string, scalar reference or array reference." ) );
}
elsif( @_ )
{
$init = $_[0];
}
else
{
$init = undef();
}
return( bless( \$init => ( ref( $this ) || $this ) ) );
}
sub append { ${$_[0]} .= ( ( Scalar::Util::reftype( $_[1] ) // '' ) eq 'SCALAR' ? ${$_[1]} : $_[1] ); return( $_[0] ); }
sub as_array { return( Module::Generic::Array->new( [ ${$_[0]} ] ) ); }
sub as_boolean { return( Module::Generic::Boolean->new( ${$_[0]} ? 1 : 0 ) ); }
sub as_number { return( $_[0]->_number( ${$_[0]} ) ); }
## sub as_string { CORE::defined( ${$_[0]} ) ? return( ${$_[0]} ) : return; }
sub as_string { return( ${$_[0]} ); }
sub callback
{
my $self = CORE::shift( @_ );
my( $what, $code ) = @_;
if( !defined( $what ) )
{
return( $self->error( "No callback type was provided." ) );
}
elsif( $what ne 'add' && $what ne 'remove' )
{
return( $self->error( "Callback type provided ($what) is unsupported. Use 'add' or 'remove'." ) );
}
elsif( scalar( @_ ) == 1 )
{
return( $self->error( "No callback code was provided. Provide an anonymous subroutine, or reference to existing subroutine." ) );
}
elsif( defined( $code ) && ref( $code ) ne 'CODE' )
{
return( $self->error( "Callback provided is not a code reference. Provide an anonymous subroutine, or reference to existing subroutine." ) );
}
if( !defined( $code ) )
{
# undef is passed as an argument, so we remove the callback
if( scalar( @_ ) >= 2 )
{
# The array is not tied, so there is nothing to remove.
my $tie = tied( $$self );
return(1) if( !$tie );
my $rv = $tie->unset_callback( $what );
if( !$tie->has_callback )
{
undef( $tie );
untie( $$self );
}
return( $rv );
}
# Only 1 argument: get mode only
else
{
my $tie = tied( $$self );
return if( !$tie );
return( $tie->get_callback( $what ) );
}
}
# $code is defined, so we have something to set
else
{
my $tie = tied( $$self );
# Not tied yet
if( !$tie )
{
$tie = tie( $$self => 'Module::Generic::Scalar::Tie',
{
data => $self,
debug => $DEBUG,
$what => $code,
}) || return;
return(1);
}
$tie->set_callback( $what => $code ) || return;
return(1);
}
}
# Credits: John Gruber, Aristotle Pagaltzis
sub capitalise
{
my $self = CORE::shift( @_ );
my @small_words = qw( (?<!q&)a an and as at(?!&t) but by en for if in of on or the to v[.]? via vs[.]? );
my $small_re = CORE::join( '|', @small_words );
my $apos = qr/ (?: ['’] [[:lower:]]* )? /x;
my $copy = $$self;
return( $self->_new( $copy ) ) if( !CORE::defined( $copy ) );
$copy =~ s{\A\s+}{};
$copy =~ s{\s+\z}{};
$copy = CORE::lc( $copy ) if( $copy !~ /[[:lower:]]/ );
$copy =~ s{
\b (_*) (?:
( (?<=[ ][/\\]) [[:alpha:]]+ [-_[:alpha:]/\\]+ | # file path or
[-_[:alpha:]]+ [@.:] [-_[:alpha:]@.:/]+ $apos ) # URL, domain, or email
|
( (?i: $small_re ) $apos ) # or small word (case-insensitive)
|
( [[:alpha:]] [[:lower:]'’()\[\]{}]* $apos ) # or word w/o internal caps
|
( [[:alpha:]] [[:alpha:]'’()\[\]{}]* $apos ) # or some other word
) (_*) \b
}{
$1 . (
defined $2 ? $2 # preserve URL, domain, or email
: defined $3 ? "\L$3" # lowercase small word
: defined $4 ? "\u\L$4" # capitalize word w/o internal caps
: $5 # preserve other kinds of word
) . $6
}xeg;
# Exceptions for small words: capitalize at start and end of title
$copy =~ s{
( \A [[:punct:]]* # start of title...
| [:.;?!][ ]+ # or of subsentence...
| [ ]['"“‘(\[][ ]* ) # or of inserted subphrase...
( $small_re ) \b # ... followed by small word
}{$1\u\L$2}xig;
$copy =~ s{
\b ( $small_re ) # small word...
(?= [[:punct:]]* \Z # ... at the end of the title...
| ['"’”)\]] [ ] ) # ... or of an inserted subphrase?
}{\u\L$1}xig;
# Exceptions for small words in hyphenated compound words
# e.g. "in-flight" -> In-Flight
$copy =~ s{
\b
(?<! -) # Negative lookbehind for a hyphen; we don't want to match man-in-the-middle but do want (in-flight)
( $small_re )
(?= -[[:alpha:]]+) # lookahead for "-someword"
}{\u\L$1}xig;
# e.g. "Stand-in" -> "Stand-In" (Stand is already capped at this point)
$copy =~ s{
\b
(?<!…) # Negative lookbehind for a hyphen; we don't want to match man-in-the-middle but do want (stand-in)
( [[:alpha:]]+- ) # $1 = first word and hyphen, should already be properly capped
( $small_re ) # ... followed by small word
(?! - ) # Negative lookahead for another '-'
}{$1\u$2}xig;
return( $self->_new( $copy ) );
}
sub chomp { no warnings 'uninitialized'; return( CORE::chomp( ${$_[0]} ) ); }
sub chop { no warnings 'uninitialized'; return( CORE::chop( ${$_[0]} ) ); }
sub clone
{
my $self = shift( @_ );
if( @_ )
{
return( $self->_new( @_ ) );
}
else
{
return( $self->_new( ${$self} ) );
}
}
sub crypt { return( __PACKAGE__->_new( CORE::crypt( ${$_[0]}, $_[1] ) ) ); }
sub defined { return( CORE::defined( ${$_[0]} ) ); }
sub empty { return( shift->reset( @_ ) ); }
sub error
{
my $self = CORE::shift( @_ );
my $addr = Scalar::Util::refaddr( $self ) || $self;
my $class = ref( $self ) || $self;
my $o;
no strict 'refs';
if( @_ )
{
my $args = {};
# We got an object as first argument. It could be a child from our exception package or from another package
# Either way, we use it as it is
if( ( Scalar::Util::blessed( $_[0] ) && $_[0]->isa( 'Module::Generic::Exception' ) ) ||
Scalar::Util::blessed( $_[0] ) )
{
$o = CORE::shift( @_ );
}
elsif( ref( $_[0] ) eq 'HASH' )
{
$args = CORE::shift( @_ );
}
else
{
$args->{message} = CORE::join( '', CORE::map( ref( $_ ) eq 'CODE' ? $_->() : $_, @_ ) );
}
$args->{class} //= '';
my $ex_class = CORE::length( $args->{class} )
? $args->{class}
: ( defined( ${"${class}\::EXCEPTION_CLASS"} ) && CORE::length( ${"${class}\::EXCEPTION_CLASS"} ) )
? ${"${class}\::EXCEPTION_CLASS"}
: 'Module::Generic::Exception';
unless( CORE::scalar( CORE::keys( %{"${ex_class}\::"} ) ) )
{
my $pl = "use $ex_class;";
local $SIG{__DIE__} = sub{};
local $@;
eval( $pl );
# We have to die, because we have an error within another error
die( "${class}\::error() is unable to load exception class \"$ex_class\": $@" ) if( $@ );
}
$o = $ERRORS->{ $addr } = $ERROR = $ex_class->new( $args );
local $@;
my $enc_str = eval
{
Encode::encode( 'UTF-8', "$o", Encode::FB_CROAK );
};
# Display warnings if warnings for this class is registered and enabled or if not registered
warn( $@ ? $o : $enc_str ) if( $self->_warnings_is_enabled );
if( !$args->{no_return_null_object} && want( 'OBJECT' ) )
{
my $null = Module::Generic::Null->new( $o, { debug => $DEBUG, has_error => 1 });
rreturn( $null );
}
return;
}
if( !$ERRORS->{ $addr } && want( 'OBJECT' ) )
{
my $null = Module::Generic::Null->new( $o, { debug => $DEBUG, wants => 'object' });
rreturn( $null );
}
return( $ERRORS->{ $addr } );
}
sub fc { return( CORE::fc( ${$_[0]} ) eq CORE::fc( $_[1] ) ); }
sub hex { return( $_[0]->_number( CORE::hex( ${$_[0]} ) ) ); }
sub index
{
my $self = shift( @_ );
my( $substr, $pos ) = @_;
return( $self->_number( CORE::index( ${$self}, $substr, $pos ) ) ) if( CORE::defined( $pos ) );
return( $self->_number( CORE::index( ${$self}, $substr ) ) );
}
sub is_alpha { return( CORE::defined( ${$_[0]} ) && ${$_[0]} =~ /^[[:alpha:]]+$/ ); }
sub is_alpha_numeric { return( CORE::defined( ${$_[0]} ) && ${$_[0]} =~ /^[[:alnum:]]+$/ ); }
sub is_empty { return( CORE::length( ${$_[0]} // '' ) == 0 ); }
sub is_lower { return( CORE::defined( ${$_[0]} ) && ${$_[0]} =~ /^[[:lower:]]+$/ ); }
sub is_numeric { return( Scalar::Util::looks_like_number( ${$_[0]} ) ); }
sub is_upper { return( CORE::defined( ${$_[0]} ) && ${$_[0]} =~ /^[[:upper:]]+$/ ); }
sub join { return( __PACKAGE__->new( CORE::join( CORE::splice( @_, 1, 1 ), ${ shift( @_ ) }, @_ ) ) ); }
sub lc { no warnings 'uninitialized'; return( __PACKAGE__->_new( CORE::lc( ${$_[0]} ) ) ); }
sub lcfirst { no warnings 'uninitialized'; return( __PACKAGE__->_new( CORE::lcfirst( ${$_[0]} ) ) ); }
sub left { no warnings 'uninitialized'; return( $_[0]->_new( CORE::substr( ${$_[0]}, 0, CORE::int( $_[1] ) ) ) ); }
sub length { no warnings 'uninitialized'; return( $_[0]->_number( CORE::length( ${$_[0]} ) ) ); }
sub like
{
my $self = shift( @_ );
my $str = shift( @_ );
my @matches = ();
my @rv = ();
no warnings 'uninitialized';
$str = CORE::defined( $str )
? ( ref( $str ) eq 'Regexp' || ref( $str ) eq 'Regexp::Common' )
? $str
: qr/(?:\Q$str\E)+/
: qr/[[:blank:]\r\n]*/;
@rv = $$self =~ /$str/;
if( scalar( @{^CAPTURE} ) )
{
for( my $i = 0; $i < scalar( @{^CAPTURE} ); $i++ )
{
push( @matches, ${^CAPTURE}[$i] );
}
}
# For named captures
my $names = { %+ };
unless( want( 'OBJECT' ) || want( 'SCALAR' ) || want( 'LIST' ) || scalar( @matches ) )
{
return(0);
}
return( Module::Generic::RegexpCapture->new( result => \@rv, capture => \@matches, name => $names ) );
}
sub lower { return( shift->lc ); }
sub ltrim
{
my $self = shift( @_ );
my $str = shift( @_ );
no warnings 'uninitialized';
$str = CORE::defined( $str )
? ( ref( $str ) eq 'Regexp' || ref( $str ) eq 'Regexp::Common' )
? $str
: qr/(?:\Q$str\E)+/
: qr/[[:blank:]\r\n]*/;
$$self =~ s/^$str//g;
return( $self );
}
sub match
{
my( $self, $re ) = @_;
my @matches = ();
my @rv = ();
no warnings 'uninitialized';
$re = CORE::defined( $re )
? ( ref( $re ) eq 'Regexp' || ref( $re ) eq 'Regexp::Common' )
? $re
: qr/(?:\Q$re\E)+/
: $re;
@rv = $$self =~ /$re/;
if( scalar( @{^CAPTURE} ) )
{
for( my $i = 0; $i < scalar( @{^CAPTURE} ); $i++ )
{
push( @matches, ${^CAPTURE}[$i] );
}
}
# For named captures
my $names = { %+ };
unless( want( 'OBJECT' ) || want( 'SCALAR' ) || want( 'LIST' ) || scalar( @matches ) )
{
return(0);
}
return( Module::Generic::RegexpCapture->new( result => \@rv, capture => \@matches, name => $names ) );
}
sub object { return( $_[0] ); }
sub open
{
my $self = shift( @_ );
my $io = Module::Generic::Scalar::IO->new( $self, @_ ) ||
return( $self->pass_error( Module::Generic::Scalar::IO->error ) );
return( $io );
}
sub ord { return( $_[0]->_number( CORE::ord( ${$_[0]} ) ) ); }
sub pack { return( __PACKAGE__->_new( CORE::pack( $_[1], ${$_[0]} ) ) ); }
sub pad
{
my $self = shift( @_ );
my( $n, $str ) = @_;
$str //= ' ';
if( !CORE::length( $n ) )
{
warn( "No number provided to pad the string object.\n" ) if( $self->_warnings_is_enabled );
}
elsif( $n !~ /^\-?\d+$/ )
{
warn( "Number provided \"$n\" to pad string is not an integer.\n" ) if( $self->_warnings_is_enabled );
}
if( $n < 0 )
{
$$self .= ( "$str" x CORE::abs( $n ) );
}
else
{
CORE::substr( $$self, 0, 0 ) = ( "$str" x $n );
}
return( $self );
}
sub pass_error
{
my $self = CORE::shift( @_ );
my $addr = Scalar::Util::refaddr( $self ) || $self;
my $opts = {};
my $err;
my $class;
no strict 'refs';
if( scalar( @_ ) )
{
# Either an hash defining a new error and this will be passed along to error(); or
# an hash with a single property: { class => 'Some::ExceptionClass' }
if( CORE::scalar( @_ ) == 1 && ref( $_[0] ) eq 'HASH' )
{
$opts = $_[0];
}
else
{
# $self->pass_error( $error_object, { class => 'Some::ExceptionClass' } );
if( CORE::scalar( @_ ) > 1 && ref( $_[-1] ) eq 'HASH' )
{
$opts = CORE::pop( @_ );
}
$err = $_[0];
}
}
# We set $class only if the hash provided is a one-element hash and not an error-defining hash
$class = CORE::delete( $opts->{class} ) if( CORE::scalar( CORE::keys( %$opts ) ) == 1 && [CORE::keys( %$opts )]->[0] eq 'class' );
# called with no argument, most likely from the same class to pass on an error
# set up earlier by another method; or
# with an hash containing just one argument class => 'Some::ExceptionClass'
if( !CORE::defined( $err ) && ( !CORE::scalar( @_ ) || CORE::defined( $class ) ) )
{
if( !CORE::defined( $ERRORS->{ $addr } ) )
{
warnings::warnif( "No error object provided and no previous error set either! It seems the previous method call returned a simple undef\n" );
}
else
{
$err = ( CORE::defined( $class ) ? bless( $ERRORS->{ $addr } => $class ) : $ERRORS->{ $addr } );
}
}
elsif( CORE::defined( $err ) &&
Scalar::Util::blessed( $err ) &&
( CORE::scalar( @_ ) == 1 ||
( CORE::scalar( @_ ) == 2 && CORE::defined( $class ) )
) )
{
$ERRORS->{ $addr } = $ERROR = ( CORE::defined( $class ) ? bless( $err => $class ) : $err );
}
# If the error provided is not an object, we call error to create one
else
{
return( $self->error( @_ ) );
}
if( want( 'OBJECT' ) )
{
my $null = Module::Generic::Null->new( $err, { debug => $ERRORS->{ $addr }, has_error => 1 });
rreturn( $null );
}
return;
}
sub pos { return( $_[0]->_number( @_ > 1 ? ( CORE::pos( ${$_[0]} ) = $_[1] ) : CORE::pos( ${$_[0]} ) ) ); }
sub prepend { return( shift->substr( 0, 0, ( ( Scalar::Util::reftype( $_[0] ) // '' ) eq 'SCALAR' ? ${$_[0]} : $_[0] ) ) ); }
sub quotemeta { return( __PACKAGE__->_new( CORE::quotemeta( ${$_[0]} ) ) ); }
sub right { return( $_[0]->_new( CORE::substr( ${$_[0]}, ( CORE::int( $_[1] ) * -1 ) ) ) ); }
sub replace
{
my( $self, $re, $replacement ) = @_;
# Only to test if this was a regular expression. If it was the array will contain successful match, other it will be empty
# @rv will contain the regexp matches or the result of the eval
my @matches = ();
my @rv = ();
$re = CORE::defined( $re )
? ( ref( $re ) eq 'Regexp' || ref( $re ) eq 'Regexp::Common' )
? $re
: qr/(?:\Q$re\E)+/
: $re;
# return( $$self =~ s/$re/$replacement/gs );
@rv = $$self =~ s/$re/$replacement/gs;
if( scalar( @{^CAPTURE} ) )
{
for( my $i = 0; $i < scalar( @{^CAPTURE} ); $i++ )
{
push( @matches, ${^CAPTURE}[$i] );
}
}
# For named captures
my $names = { %+ };
unless( want( 'OBJECT' ) || want( 'SCALAR' ) || want( 'LIST' ) || scalar( @matches ) )
{
return(0);
}
return( Module::Generic::RegexpCapture->new( result => \@rv, capture => \@matches, name => $names ) );
}
sub reset { ${$_[0]} = ''; return( $_[0] ); }
sub reverse { return( __PACKAGE__->_new( CORE::scalar( CORE::reverse( ${$_[0]} ) ) ) ); }
sub rindex
{
my $self = shift( @_ );
my( $substr, $pos ) = @_;
return( $self->_number( CORE::rindex( ${$self}, $substr, $pos ) ) ) if( CORE::defined( $pos ) );
return( $self->_number( CORE::rindex( ${$self}, $substr ) ) );
}
sub rtrim
{
my $self = shift( @_ );
my $str = shift( @_ );
$str = CORE::defined( $str )
? ( ref( $str ) eq 'Regexp' || ref( $str ) eq 'Regexp::Common' )
? $str
: qr/(?:\Q$str\E)+/
: qr/[[:blank:]\r\n]*/;
$$self =~ s/${str}$//g;
return( $self );
}
sub scalar { return( shift->as_string ); }
sub set
{
my $self = CORE::shift( @_ );
if( @_ )
{
my $init;
my $type = Scalar::Util::reftype( $_[0] ) // '';
if( $type eq 'SCALAR' )
{
$init = ${$_[0]};
}
elsif( $type eq 'ARRAY' )
{
$init = CORE::join( '', @{$_[0]} );
}
elsif( ref( $_[0] ) )
{
warn( "I do not know what to do with \"", $_[0], "\" (", overload::StrVal( $_[0] ), ")\n" ) if( $self->_warnings_is_enabled );
return;
}
else
{
$init = shift( @_ );
}
$$self = $init;
}
return( $self );
}
sub split
{
my $self = CORE::shift( @_ );
my( $expr, $limit ) = @_;
if( !scalar( @_ ) )
{
CORE::warn( "No argument was provided to split string in Module::Generic::Scalar::split\n" ) if( $self->_warnings_is_enabled );
# NOTE: As per perlfunc: "If omitted, PATTERN defaults to a single space, " ", triggering the previously described *awk* emulation."
$expr = ' ';
}
unless( ref( $expr ) eq 'Regexp' || ref( $expr ) eq 'Regexp::Common' )
{
if( ref( $expr ) )
{
CORE::warn( "Expression provided is a reference of type '", ref( $expr ), "', but I was expecting either a regular expression or a simple string.\n" );
return;
}
$expr = qr/\Q$expr\E/;
}
my $ref;
$limit = "$limit" if( CORE::defined( $limit ) );
if( CORE::defined( $limit ) && $limit =~ /^\d+$/ )
{
$ref = [ CORE::split( $expr, $$self, $limit ) ];
}
else
{
$ref = [ CORE::split( $expr, $$self ) ];
}
if( Want::want( 'OBJECT' ) ||
Want::want( 'SCALAR' ) )
{
rreturn( $self->_array( $ref ) );
}
elsif( Want::want( 'LIST' ) )
{
rreturn( @$ref );
}
return;
}
sub sprintf { return( __PACKAGE__->_new( CORE::sprintf( ${$_[0]}, @_[1..$#_] ) ) ); }
sub substr
{
my $self = CORE::shift( @_ );
my( $offset, $length, $replacement ) = @_;
return( __PACKAGE__->_new( CORE::substr( ${$self}, $offset, $length, $replacement ) ) ) if( CORE::defined( $length ) && CORE::defined( $replacement ) );
return( __PACKAGE__->_new( CORE::substr( ${$self}, $offset, $length ) ) ) if( CORE::defined( $length ) );
return( __PACKAGE__->_new( CORE::substr( ${$self}, $offset ) ) );
}
# The 3 dash here are just so my editor does not get confused with colouring
sub tr ###
{
my $self = CORE::shift( @_ );
my( $search, $replace, $opts ) = @_;
$opts //= '';
local $@;
eval( "\$\$self =~ CORE::tr/$search/$replace/$opts" );
return( $self );
}
sub trim
{
my $self = shift( @_ );
my $str = shift( @_ );
$str = CORE::defined( $str ) ? CORE::quotemeta( $str ) : qr/[[:blank:]\r\n]*/;
$$self =~ s/^$str|$str$//gs;
return( $self );
}
sub uc { return( __PACKAGE__->_new( CORE::uc( ${$_[0]} ) ) ); }
sub ucfirst { return( __PACKAGE__->_new( CORE::ucfirst( ${$_[0]} ) ) ); }
sub undef
{
my $self = shift( @_ );
$$self = undef;
return( $self );
}
sub unpack
{
my( $self, $tmpl ) = @_;
my $ref = [CORE::unpack( $tmpl, $$self )];
# In scalar context, return the first element, as per the original unpack behaviour
if( Want::want( 'OBJECT' ) )
{
rreturn( $self->_array( $ref ) );
}
elsif( Want::want( 'LIST' ) )
{
rreturn( @$ref );
}
elsif( Want::want( 'SCALAR' ) )
{
rreturn( $ref->[0] );
}
return;
}
sub upper { return( shift->uc ); }
sub _array
{
my $self = shift( @_ );
my $arr = shift( @_ );
if( !defined( $arr ) )
{
if( Want::want( 'OBJECT' ) )
{
# We might have need to specify, because I found a race condition where
# even though the context is object, once in Null, the context became 'code'
return( Module::Generic::Null->new( wants => 'OBJECT' ) );
}
else
{
return;
}
}
return( $arr ) if( ( Scalar::Util::reftype( $arr ) // '' ) ne 'ARRAY' );
return( Module::Generic::Array->new( $arr ) );
}
sub _number
{
my $self = shift( @_ );
my $num = shift( @_ );
if( !defined( $num ) )
{
if( Want::want( 'OBJECT' ) )
{
# We might have need to specify, because I found a race condition where
# even though the context is object, once in Null, the context became 'code'
return( Module::Generic::Null->new( wants => 'OBJECT' ) );
}
else
{
return;
}
}
return( $num ) if( !CORE::length( $num ) );
return( Module::Generic::Number->new( $num ) );
}
sub _new { return( shift->Module::Generic::Scalar::new( @_ ) ); }
sub _warnings_is_enabled
{
my $self = shift( @_ );
# I hate dying, but here this is a show-stopper
die( "Object provided is undef!\n" ) if( @_ && !defined( $_[0] ) );
my $obj = @_ ? shift( @_ ) : $self;
return(0) if( !$self->_warnings_is_registered( $obj ) );
return( warnings::enabled( ref( $obj ) || $obj ) );
}
sub _warnings_is_registered
{
my $self = shift( @_ );
# I hate dying, but here this is a show-stopper
die( "Object provided is undef!\n" ) if( @_ && !defined( $_[0] ) );
my $obj = @_ ? shift( @_ ) : $self;
return(1) if( defined( $warnings::Bits{ ref( $obj ) || $obj } ) );
return(0);
}
sub DESTROY
{
local( $., $@, $!, $^E, $? );
my $self = shift( @_ );
my $addr = Scalar::Util::refaddr( $self );
CORE::delete( $ERRORS->{ $addr } );
};
sub FREEZE
{
my $self = CORE::shift( @_ );
my $serialiser = CORE::shift( @_ ) // '';
my $class = CORE::ref( $self ) || $self;
# Return an array reference rather than a list so this works with Sereal and CBOR
# On or before Sereal version 4.023, Sereal did not support multiple values returned
CORE::return( [$class, $$self] ) if( $serialiser eq 'Sereal' && Sereal::Encoder->VERSION <= version->parse( '4.023' ) );
# But Storable want a list with the first element being the serialised element
CORE::return( $$self );
}
sub STORABLE_freeze { CORE::return( CORE::shift->FREEZE( @_ ) ); }
sub STORABLE_thaw { CORE::return( CORE::shift->THAW( @_ ) ); }
sub THAW
{
my( $self, undef, @args ) = @_;
my( $class, $str );
if( CORE::scalar( @args ) == 1 && CORE::ref( $args[0] ) eq 'ARRAY' )
{
( $class, $str ) = @{$args[0]};
}
else
{
$class = CORE::ref( $self ) || $self;
$str = CORE::shift( @args );
}
my $new;
# Storable pattern requires to modify the object it created rather than returning a new one
if( CORE::ref( $self ) )
{
$$self = $str;
$new = $self;
}
else
{
$new = CORE::return( $class->new( $str ) );
}
CORE::return( $new );
}
sub TO_JSON { CORE::return( ${$_[0]} ); }
# NOTE: Module::Generic::RegexpCapture package
{
package
Module::Generic::RegexpCapture;
BEGIN
{
use strict;
use warnings;
use vars qw( $ERROR $VERSION );
use overload (
'""' => sub{ $_[0]->matched },
'0+' => sub{ $_[0]->matched },
fallback => 1,
);
our $ERROR = '';
our $VERSION = 'v0.1.1';
};
sub init
{
my $self = shift( @_ );
$self->{capture} = [];
$self->{name} = {};
$self->{result} = 0;
$self->{_init_strict_use_sub} = 1;
return( $self->SUPER::init( @_ ) );
}
sub capture { return( shift->_set_get_array_as_object( 'capture', @_ ) ); }
sub matched
{
my $res = shift->result;
# There may be one entry of empty value when there is no match, so we check for length
return( $res->length->scalar ) if( $res->length && length( $res->get(0) ) );
return(0);
}
sub name { return( shift->_set_get_hash_as_object( 'name', @_ ) ); }
sub result { return( shift->_set_get_array_as_object( 'result', @_ ) ); }
sub FREEZE
{
my $self = CORE::shift( @_ );
my $serialiser = CORE::shift( @_ ) // '';
my $class = CORE::ref( $self );
my %hash = %$self;
# Return an array reference rather than a list so this works with Sereal and CBOR
# On or before Sereal version 4.023, Sereal did not support multiple values returned
CORE::return( [$class, \%hash] ) if( $serialiser eq 'Sereal' && Sereal::Encoder->VERSION <= version->parse( '4.023' ) );
# But Storable want a list with the first element being the serialised element
CORE::return( $class, \%hash );
}
sub STORABLE_freeze { return( shift->FREEZE( @_ ) ); }
sub STORABLE_thaw { return( shift->THAW( @_ ) ); }
# NOTE: CBOR will call the THAW method with the stored classname as first argument, the constant string CBOR as second argument, and all values returned by FREEZE as remaining arguments.
# NOTE: Storable calls it with a blessed object it created followed with $cloning and any other arguments initially provided by STORABLE_freeze
sub THAW
{
# STORABLE_thaw would issue $cloning as the 2nd argument, while CBOR would issue
# 'CBOR' as the second value.
my( $self, undef, @args ) = @_;
my $ref = ( CORE::scalar( @args ) == 1 && CORE::ref( $args[0] ) eq 'ARRAY' ) ? CORE::shift( @args ) : \@args;
my $class = ( CORE::defined( $ref ) && CORE::ref( $ref ) eq 'ARRAY' && CORE::scalar( @$ref ) > 1 ) ? CORE::shift( @$ref ) : ( CORE::ref( $self ) || $self );
my $hash = CORE::ref( $ref ) eq 'ARRAY' ? CORE::shift( @$ref ) : {};
my $new;
# Storable pattern requires to modify the object it created rather than returning a new one
if( CORE::ref( $self ) )
{
foreach( CORE::keys( %$hash ) )
{
$self->{ $_ } = CORE::delete( $hash->{ $_ } );
}
$new = $self;
}
else
{
$new = CORE::bless( $hash => $class );
}
CORE::return( $new );
}
}
{
# NOTE: Module::Generic::Scalar::Tie class
package
Module::Generic::Scalar::Tie;
BEGIN
{
use strict;
use warnings;
use Scalar::Util ();
};
our $dummy_callback = sub{1};
sub TIESCALAR
{
my( $class, $opts ) = @_;
$opts //= {};
if( ( Scalar::Util::reftype( $opts ) // '' ) ne 'HASH' )
{
warn( "Options provided (", overload::StrVal( $opts ), ") is not an hash reference\n" );
$opts = {};
}
$opts->{data} //= '';
$opts->{debug} //= 0;
if( CORE::length( $opts->{add} ) && ref( $opts->{add} ) ne 'CODE' )
{
warnings::warn( "Code provided for the scalar add callback is not a code reference.\n" ) if( warnings::enabled( 'Module::Generic::Sscalar' ) || $opts->{debug} );
return;
}
if( CORE::length( $opts->{remove} ) && ref( $opts->{remove} ) ne 'CODE' )
{
warnings::warn( "Code provided for the scalar remove callback is not a code reference.\n" ) if( warnings::enabled( 'Module::Generic::Sscalar' ) || $opts->{debug} );
return;
}
my $ref =
{
callback_add => $opts->{add},
callback_remove => $opts->{remove},
data => ( ( Scalar::Util::reftype( $opts->{data} ) // '' ) eq 'SCALAR' ? \"${$opts->{data}}" : \undef ),
debug => $opts->{debug},
};
print( STDERR ( ref( $class ) || $class ), "::TIESCALAR: Using ", CORE::length( ${$ref->{data}} ), " bytes of data in scalar vs ", CORE::length( ${$opts->{data}} ), " bytes received via opts->data.\n" ) if( $ref->{debug} );
return( bless( $ref => ( ref( $class ) || $class ) ) );
}
sub FETCH
{
my $self = shift( @_ );
return( ${$self->{data}} );
}
sub STORE
{
my( $self, $value ) = @_;
my $index = 0;
my $rv;
# New value is smaller than our current, so this is a removal. It could be partial or total
if( CORE::length( "$value" ) < CORE::length( ${$self->{data}} ) )
{
my $cb = $self->{callback_remove} || $dummy_callback;
if( !$cb )
{
warnings::warn( "No callback remove found. This should not happen.\n" ) if( warnings::enabled( 'Module::Generic::Scalar' ) || $self->{debug} );
$rv = 1;
}
else
{
$rv = $cb->({ type => 'remove', removed => \"${$self->{data}}", added => \$value });
}
}
else
{
my $cb = $self->{callback_add} || $dummy_callback;
if( !$cb )
{
warnings::warn( "No callback add found. This should not happen.\n" ) if( warnings::enabled( 'Module::Generic::Scalar' ) || $self->{debug} );
$rv = 1;
}
else
{
$rv = $cb->({ type => 'add', added => \$value });
}
}
print( STDERR ref( $self ), "::STORE: adding ", CORE::length( "$value" ), " bytes of data ($value) at position $index with current data of ", CORE::length( ${$self->{data}} ), " bytes (", ${$self->{data}}, ") -> callback returned ", ( defined( $rv ) ? 'true' : 'undef' ), "\n" ) if( $self->{debug} );
return if( !defined( $rv ) );
${$self->{data}} = $value;
}
sub has_callback
{
my $self = shift( @_ );
return(1) if( ref( $self->{callback_add} ) eq 'CODE' || ref( $self->{callback_remove} ) eq 'CODE' );
return(0);
}
sub set_callback
{
my( $self, $what, $code ) = @_;
if( !defined( $what ) )
{
warn( "No callback type was provided. Use \"add\" or \"remove\".\n" );
return;
}
elsif( $what ne 'add' && $what ne 'remove' )
{
warn( "Unknown callback type was provided: '$what'. Use \"add\" or \"remove\".\n" );
return;
}
elsif( !defined( $code ) )
{
warn( "No callback anonymous subroutine or subroutine reference was provided.\n" );
return;
}
elsif( ref( $code ) ne 'CODE' )
{
warn( "Callback provided (", overload::StrVal( $code ), ") is not a code reference.\n" );
return;
}
$self->{ "callback_${what}" } = $code;
return(1);
}
sub unset_callback
{
my( $self, $what ) = @_;
if( !defined( $what ) )
{
warn( "No callback type was provided. Use \"add\" or \"remove\".\n" );
return;
}
elsif( $what ne 'add' && $what ne 'remove' )
{
warn( "Unknown callback type was provided: '$what'. Use \"add\" or \"remove\".\n" );
return;
}
$self->{ "callback_${what}" } = undef;
return(1);
}
}
1;
__END__