##---------------------------------------------------------------------------- ## Module Generic - ~/lib/Module/Generic/Scalar.pm ## Version v1.2.0 ## Copyright(c) 2021 DEGUEST Pte. Ltd. ## Author: Jacques Deguest <jack@deguest.jp> ## Created 2021/03/20 ## Modified 2022/02/27 ## All rights reserved ## ## This program is free software; you can redistribute it and/or modify it ## under the same terms as Perl itself. ##---------------------------------------------------------------------------- package Module::Generic::Scalar; BEGIN { use common::sense; use warnings; use warnings::register; use vars qw( $DEBUG ); use Module::Generic::Array; use Module::Generic::Boolean; use Module::Generic::Number; use Module::Generic::Scalar; # 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\""; 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; our $VERSION = 'v1.2.0'; }; use strict; no warnings 'redefine'; # sub new { return( shift->_new( @_ ) ); } sub new { my $this = shift( @_ ); 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] ) ) { warn( "I do not know what to do with \"", $_[0], "\"\n" ) if( $this->_warnings_is_enabled ); return; } elsif( @_ ) { $init = $_[0]; } else { $init = undef(); } return( bless( \$init => ( ref( $this ) || $this ) ) ); } sub append { ${$_[0]} .= $_[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 ) ) { warnings::warn( "No callback type was provided.\n" ) if( warnings::enabled( 'Module::Generic::Scalar' ) ); return; } elsif( $what ne 'add' && $what ne 'remove' ) { warnings::warn( "Callback type provided ($what) is unsupported. Use 'add' or 'remove'.\n" ) if( warnings::enabled( 'Module::Generic::Scalar' ) ); return; } elsif( scalar( @_ ) == 1 ) { warnings::warn( "No callback code was provided. Provide an anonymous subroutine, or reference to existing subroutine.\n" ) if( warnings::enabled( 'Module::Generic::Scalar' ) ); return; } elsif( defined( $code ) && ref( $code ) ne 'CODE' ) { warnings::warn( "Callback provided is not a code reference. Provide an anonymous subroutine, or reference to existing subroutine." ) if( warnings::enabled( 'Module::Generic::Scalar' ) ); return; } 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 ); print( STDERR ref( $self ), "::callback: Any callback left? ", ( $rv ? 'yes' : 'no' ), "\n" ) if( $DEBUG ); untie( $$self ) if( !$tie->has_callback ); 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 # https://gist.github.com/gruber/9f9e8650d68b13ce4d78 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; $copy =~ s{\A\s+}{}, s{\s+\z}{}; $copy = CORE::lc( $copy ) if( not /[[: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 { return( CORE::chomp( ${$_[0]} ) ); } sub chop { 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 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( ${$_[0]} =~ /^[[:alpha:]]+$/ ); } sub is_alpha_numeric { return( ${$_[0]} =~ /^[[:alnum:]]+$/ ); } sub is_empty { return( CORE::length( ${$_[0]} ) == 0 ); } sub is_lower { return( ${$_[0]} =~ /^[[:lower:]]+$/ ); } sub is_numeric { return( Scalar::Util::looks_like_number( ${$_[0]} ) ); } sub is_upper { return( ${$_[0]} =~ /^[[:upper:]]+$/ ); } sub join { return( __PACKAGE__->new( CORE::join( CORE::splice( @_, 1, 1 ), ${ shift( @_ ) }, @_ ) ) ); } sub lc { return( __PACKAGE__->_new( CORE::lc( ${$_[0]} ) ) ); } sub lcfirst { return( __PACKAGE__->_new( CORE::lcfirst( ${$_[0]} ) ) ); } sub left { return( $_[0]->_new( CORE::substr( ${$_[0]}, 0, CORE::int( $_[1] ) ) ) ); } sub length { return( $_[0]->_number( CORE::length( ${$_[0]} ) ) ); } sub like { my $self = shift( @_ ); my $str = shift( @_ ); my @matches = (); my @rv = (); $str = CORE::defined( $str ) ? ref( $str ) eq 'Regexp' ? $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( @_ ); $str = CORE::defined( $str ) ? ref( $str ) eq 'Regexp' ? $str : qr/(?:\Q$str\E)+/ : qr/[[:blank:]\r\n]*/; $$self =~ s/^$str//g; return( $self ); } sub match { my( $self, $re ) = @_; my @matches = (); my @rv = (); $re = CORE::defined( $re ) ? ref( $re ) eq 'Regexp' ? $re : qr/(?:\Q$re\E)+/ : $re; @rv = $$self =~ /$re/; # print( STDERR ref( $self ), "::match: \@rv is: @rv, has ", scalar( @rv ), " element(s): ", Module::Generic->dump( \@rv ), "\n" ); 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 ) || do { $! = Module::Generic::Scalar::IO->error; return; }; 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 pos { return( $_[0]->_number( @_ > 1 ? ( CORE::pos( ${$_[0]} ) = $_[1] ) : CORE::pos( ${$_[0]} ) ) ); } sub prepend { return( shift->substr( 0, 0, shift( @_ ) ) ); } 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' ? $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 = { %+ }; # print( STDERR ref( $self ), "::replace: \@rv contains ", scalar( @rv ), " element(s) and is ", Module::Generic->dump( \@rv ), " and \@matches is ", Module::Generic->dump( \@matches ), "\n" ); # print( STDERR ref( $self ), "::replace: Does caller want an object? ", want('OBJECT') ? 'yes' : 'no', "\n" ); 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' ? $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( @_ ); 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] ) ) { warn( "I do not know what to do with \"", $_[0], "\"\n" ) if( $self->_warnings_is_enabled ); return; } else { $init = shift( @_ ); } $$self = $init; return( $self ); } sub split { my $self = CORE::shift( @_ ); my( $expr, $limit ) = @_; CORE::warn( "No argument was provided to split string in Module::Generic::Scalar::split\n" ) if( !scalar( @_ ) ); unless( ref( $expr ) eq 'Regexp' ) { 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 ) && $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 ) ) ); } sub TO_JSON { CORE::return( ${$_[0]} ); } ## 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 ) = @_; 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( @_ ); return if( !defined( $arr ) ); return( $arr ) if( Scalar::Util::reftype( $arr ) ne 'ARRAY' ); return( Module::Generic::Array->new( $arr ) ); } sub _number { my $self = shift( @_ ); my $num = shift( @_ ); return if( !defined( $num ) ); return( $num ) if( !CORE::length( $num ) ); return( Module::Generic::Number->new( $num ) ); } sub _new { return( shift->Module::Generic::Scalar::new( @_ ) ); } sub _warnings_is_enabled { return( warnings::enabled( ref( $_[0] ) || $_[0] ) ); } # XXX Module::Generic::Scalar::IO class { package Module::Generic::Scalar::IO; use parent qw( IO::Scalar ); use Module::Generic::Exception (); use Scalar::Util (); use overload ( '""' => sub{ ${ *{$_[0]}->{SR} } }, # '""' => 'as_string', fallback => 1, ); our $ERROR = ''; our $VERSION = 'v0.1.0'; # sub as_string # { # my $self = shift( @_ ); # print( STDERR __PACKAGE__, "::as_string: Scalar ref object is: ", overload::StrVal( *$self->{SR} ), "\n" ); # return( ${ *$self->{SR} } ); # } sub close { my $self = CORE::shift( @_ ); untie( *$self ); return( 1 ); } sub error { my $self = shift( @_ ); if( @_ ) { my $opts = {}; if( ref( $_[0] ) eq 'HASH' ) { $opts = shift( @_ ); } else { $opts->{message} = join( '', map( ref( $_ ) eq 'CODE' ? $_->() : $_, @_ ) ); # http server error $opts->{code} = 500; } $opts->{skip_frames} = 1; *$self->{error} = $ERROR = Module::Generic::Exception->new( $opts ); return; } else { return( ref( $self ) ? *$self->{error} : $ERROR ); } } sub length { my $self = CORE::shift( @_ ); return( CORE::length( ${ *$self->{SR} } ) ); } sub line { my $self = shift( @_ ); my $code = shift( @_ ); return( $self->error( "No callback code was provided for line()" ) ) if( !defined( $code ) || ref( $code ) ne 'CODE' ); my $opts = ref( $_[0] ) eq 'HASH' ? shift( @_ ) : { @_ }; $opts->{chomp} //= 0; $opts->{auto_next} //= 0; my $l; while( defined( $l = $self->getline ) ) { chomp( $l ) if( $opts->{chomp} ); local $_ = $l; my $rv = $code->( $l ); if( !defined( $rv ) && !$opts->{auto_next} ) { last; } } return( $self ); } sub object { return( *{ $_[0] }->{SR} ) } sub open { my( $self, $ref ) = @_; # print( STDERR __PACKAGE__, "::open: scalar ref provded is: ", overload::StrVal( $ref ), " (", defined( $$sref ) ? 'undefined' : $$sref, ")\n" ); unless( Scalar::Util::blessed( $ref ) && $ref->isa( 'Module::Generic::Scalar' ) ) { return( $self->error( "Value provided for ", ref( $self ), " is not an Module::Generic::Scalar object." ) ); } # Setup: *$self->{Pos} = 0; # seek position *$self->{SR} = $ref; # scalar reference # print( STDERR __PACKAGE__, "::open: Scalar ref object is: ", overload::StrVal( *$self->{SR} ), "\n" ); return( $self ); } sub opened { return( tied( *{$_[0]} ) ); } sub print { my $self = CORE::shift( @_ ); my $len = CORE::length( ${*$self->{SR}} ); substr( ${*$self->{SR}}, *$self->{Pos}, 0, CORE::join( '', @_ ) . (CORE::defined( $\ ) ? $\ : "" ) ); *$self->{Pos} += ( CORE::length( ${*$self->{SR}} ) - $len ); # print( STDERR __PACKAGE__, "::print: Position is ", *$self->{Pos}, " and length is: ", length( ${*$self->{SR}} ), "\n" ); 1; } sub truncate { my $self = CORE::shift( @_ ); my $removed = CORE::substr( ${*$self->{SR}}, *$self->{Pos}, CORE::length( ${*$self->{SR}} ) - *$self->{Pos}, '' ); return( CORE::length( $removed ) ); } } { package Module::Generic::RegexpCapture; BEGIN { use strict; use warnings; use parent qw( Module::Generic ); use overload ( '""' => sub{ $_[0]->matched }, '0+' => sub{ $_[0]->matched }, fallback => 1, ); our $ERROR = ''; our $VERSION = 'v0.1.0'; }; 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', @_ ) ); } } { 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__