## -*- perl -*- ##---------------------------------------------------------------------------- ## Module Generic - ~/lib/Module/Generic.pm ## Version v0.42.0 ## Copyright(c) 2025 DEGUEST Pte. Ltd. ## Author: Jacques Deguest ## Created 2019/08/24 ## Modified 2025/03/24 ## 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; BEGIN { use v5.26.1; use strict; use warnings; use warnings::register; use vars qw( $MOD_PERL $AUTOLOAD $ERROR $PARAM_CHECKER_LOAD_ERROR $VERBOSE $DEBUG $SILENT_AUTOLOAD $PARAM_CHECKER_LOADED $CALLER_LEVEL $COLOUR_NAME_TO_RGB $true $false $DEBUG_LOG_IO %RE $stderr $stderr_raw $SERIALISER $AUTOLOAD_SUBS $SUB_ATTR_LIST $DATA_POS $HAS_LOCAL_TZ $VERSION_LAX_REGEX $PARSE_DATE_FRACTIONAL1_RE $PARSE_DATE_WITH_MILI_SECONDS_RE $PARSE_DATE_HTTP_RE $PARSE_DATE_NON_STDANDARD_RE $PARSE_DATE_ONLY_RE $PARSE_DATE_ONLY_US_SHORT_RE $PARSE_DATE_ONLY_EU_SHORT_RE $PARSE_DATE_ONLY_US_LONG_RE $PARSE_DATE_ONLY_EU_LONG_RE $PARSE_DATE_DOTTED_ONLY_EU_RE $PARSE_DATE_ROMAN_RE $PARSE_DATE_DIGITS_ONLY_RE $PARSE_DATE_ONLY_JP_RE $PARSE_DATETIME_JP_RE $PARSE_DATE_TIMESTAMP_RE $PARSE_DATETIME_RELATIVE_RE $PARSE_DATES_ALL_RE $PARSE_DATE_NON_STDANDARD2_RE ); use Config; use Class::Load (); use Clone (); use Data::Dump; use Devel::StackTrace; use Encode (); use File::Spec (); use Module::Metadata; # use Nice::Try v1.3.4; use POSIX; use Scalar::Util qw( openhandle ); use Sub::Util (); # use B; # To get some context on what the caller expect. This is used in our error() method to allow chaining without breaking use version; use Want; use Exporter (); our @ISA = qw( Exporter ); our @EXPORT = qw( ); our @EXPORT_OK = qw( subclasses ); our %EXPORT_TAGS = (); our $VERSION = 'v0.42.0'; # local $^W; # mod_perl/2.0.10 if( exists( $ENV{MOD_PERL} ) && ( $MOD_PERL = $ENV{MOD_PERL} =~ /^mod_perl\/(\d+\.[\d\.]+)/ ) ) { select( ( select( STDOUT ), $| = 1 )[ 0 ] ); require Apache2::Log; # For _is_class_loaded method require Apache2::Module; require Apache2::ServerUtil; require Apache2::RequestUtil; require Apache2::ServerRec; require ModPerl::Util; require Apache2::Const; Apache2::Const->import( compile => qw( :log OK ) ); } $VERBOSE = 0; $DEBUG = 0; $SILENT_AUTOLOAD = 1; $PARAM_CHECKER_LOADED = 0; $CALLER_LEVEL = 0; $COLOUR_NAME_TO_RGB = {}; no strict 'refs'; $DEBUG_LOG_IO = undef(); # Can use Sereal also $SERIALISER = 'Storable::Improved'; $AUTOLOAD_SUBS = {}; $SUB_ATTR_LIST = qr{ [[:blank:]\h]* : [[:blank:]\h]* (?: # one attribute (?> # no backtrack (?! \d) \w+ (? \( (?: [^()]++ | (?&nested)++ )*+ \) ) ? ) (?: [[:blank:]\h]* : [[:blank:]\h]* | [[:blank:]\h]ss+ (?! :) ) )* }x; # From version::regex $VERSION_LAX_REGEX = qr/(?^x: (?^x: (?v) (?(?^:[0-9]+) (?: (?^:\.[0-9]+)+ (?^:_[0-9]+)? )?) | (?(?^:[0-9]+)? (?^:\.[0-9]+){2,} (?^:_[0-9]+)?) ) | (?^x: (?(?^:[0-9]+) (?: (?^:\.[0-9]+) | \. )? (?^:_[0-9]+)?) | (?(?^:\.[0-9]+) (?^:_[0-9]+)?) ) )/; use constant HAS_THREADS => ( $Config{useithreads} && $INC{'threads.pm'} ); }; # use strict; # We put it here to avoid 'redefine' error # require Module::Generic::Array; require Module::Generic::Boolean; # require Module::Generic::DateTime; # require Module::Generic::Dynamic; # require Module::Generic::Exception; # require Module::Generic::File; # Module::Generic::File->import( qw( stderr ) ); # require Module::Generic::Hash; # require Module::Generic::Iterator; # require Module::Generic::Null; # require Module::Generic::Number; # require Module::Generic::Scalar; require IO::File; our $stderr = IO::File->new; $stderr->fdopen( fileno( STDERR ), 'w' ); $stderr->binmode( ':utf8' ); $stderr->autoflush( 1 ); our $stderr_raw = IO::File->new; $stderr_raw->fdopen( fileno( STDERR ), 'w' ); $stderr_raw->binmode( ':raw' ); $stderr_raw->autoflush( 1 ); # $stderr = stderr( binmode => 'utf-8', autoflush => 1 ); # $stderr_raw = stderr( binmode => 'raw', autoflush => 1 ); { no warnings 'once'; $true = $Module::Generic::Boolean::true; $false = $Module::Generic::Boolean::false; } # for sub in `perl -ln -E 'say "$1" if( /^sub (\w+)[[:blank:]\v]*(?:\{|\Z|[[:blank:]\v]*:[[:blank:]\v]*lvalue)/ )' ./lib/Module/Generic.pm | LC_COLLATE=C sort -uV`; do echo "sub $sub;"; done sub AUTOLOAD; sub DEBUG; sub FREEZE; sub THAW; sub TO_JSON; sub VERBOSE; sub as_hash; sub clear; sub clear_error; sub clone; sub coloured; sub colour_close; sub colour_closest; sub colour_format; sub colour_open; sub colour_parse; sub colour_to_rgb; sub debug; sub deserialise; sub deserialize; sub dump; sub dumper; sub dumpto_dumper; sub dumpto_printer; sub dump_hex; sub dump_print; sub errno; sub error; sub error_handler; sub false; sub fatal; sub get; sub import; sub init; sub log_handler; sub messagef_colour; sub message_colour; sub new; sub new_array; sub new_datetime; sub new_file; sub new_glob; sub new_hash; sub new_json; sub new_null; sub new_number; sub new_scalar; sub new_tempdir; sub new_tempfile; sub new_version; sub noexec; sub pass_error; sub printer; sub quiet; sub save; sub serialise; sub serialize; sub set; sub subclasses; sub true; sub verbose; sub will; sub _autoload_subs; sub _can; sub _can_overload; sub _get_args_as_array; sub _get_args_as_hash; sub _get_datetime_regexp; sub _get_stack_trace; sub _get_symbol; sub _has_base64; sub _has_symbol; sub _implement_freeze_thaw; sub _instantiate_object; sub _is_a; sub _is_array; sub _is_class_loadable; sub _is_class_loaded; sub _is_code; sub _is_empty; sub _is_glob; sub _is_hash; sub _is_integer; sub _is_ip; sub _is_number; sub _is_object; sub _is_overloaded; sub _is_scalar; sub _is_tty; sub _is_uuid; sub _is_warnings_enabled; sub _list_symbols; sub _load_class; sub _load_classes; sub _lvalue; sub _message; sub _messagef; sub _message_check; sub _message_frame; sub _message_log; sub _message_log_io; sub _obj2h; sub _on_error; sub _parse_timestamp; sub _refaddr; sub _set_get; sub _set_get_array; sub _set_get_array_as_object; sub _set_get_boolean; sub _set_get_callback; sub _set_get_class; sub _set_get_class_array; sub _set_get_class_array_object; sub _set_get_code; sub _set_get_datetime; sub _set_get_enum; sub _set_get_file; sub _set_get_glob; sub _set_get_hash; sub _set_get_hash_as_mix_object; sub _set_get_hash_as_object; sub _set_get_ip; sub _set_get_lvalue; sub _set_get_number; sub _set_get_number_as_object; sub _set_get_number_as_scalar; sub _set_get_number_or_object; sub _set_get_object; sub _set_get_object_array; sub _set_get_object_array2; sub _set_get_object_array_object; sub _set_get_object_lvalue; sub _set_get_object_variant; sub _set_get_object_without_init; sub _set_get_scalar; sub _set_get_scalar_as_object; sub _set_get_scalar_or_object; sub _set_get_uri; sub _set_get_uuid; sub _set_get_version; sub _set_symbol; sub _to_array_object; sub _warnings_is_enabled; sub _warnings_is_registered; sub __colour_data; sub __create_class; sub __dbh; sub __instantiate_object; # no warnings 'redefine'; sub import { my $self = shift( @_ ); my( $pkg, $file, $line ) = caller(); local $Exporter::ExportLevel = 1; Exporter::import( $self, @_ ); our $SILENT_AUTOLOAD; ( my $dir = $pkg ) =~ s/::/\//g; my $path = $INC{ $dir . '.pm' }; if( defined( $path ) ) { # Try absolute path name $path =~ s/^(.*)$dir\.pm$/${1}auto\/$dir\/autosplit.ix/; local $@; eval { local $SIG{ '__DIE__' } = sub{ }; local $SIG{ '__WARN__' } = sub{ }; require $path; }; if( $@ ) { $path = "auto/$dir/autosplit.ix"; local $@; eval { local $SIG{ '__DIE__' } = sub{ }; local $SIG{ '__WARN__' } = sub{ }; require $path; }; } if( $@ ) { CORE::warn( $@ ) unless( $SILENT_AUTOLOAD ); } } } sub new { my $that = shift( @_ ); my $class = ref( $that ) || $that; my $self = {}; no strict 'refs'; if( defined( ${ "${class}\::OBJECT_PERMS" } ) ) { require Module::Generic::Tie; my %hash = (); my $obj = tie( %hash, 'Module::Generic::Tie', 'pkg' => [ __PACKAGE__, $class ], 'perms' => ${ "${class}::OBJECT_PERMS" }, ); $self = \%hash; } bless( $self, $class ); if( defined( ${ "${class}\::LOG_DEBUG" } ) ) { $self->{log_debug} = ${ "${class}::LOG_DEBUG" }; } if( Want::want( 'OBJECT' ) ) { return( $self->init( @_ ) ); } my $new = $self->init( @_ ); # Returned undef; there was an error potentially if( !defined( $new ) ) { # If we are called on an object, we hand it the error so the caller can check it using the object: # my $new = $old->new || die( $old->error ); if( $self->_is_object( $that ) && $that->can( 'pass_error' ) ) { return( $that->pass_error( $self->error ) ); } else { return( $self->pass_error ); } }; return( $new ); } sub new_glob { my $that = shift( @_ ); my $class = ref( $that ) || $that; no warnings 'once'; my $self = bless( \do{ local *FH } => $class ); *$self = {}; if( defined( ${ "${class}\::LOG_DEBUG" } ) ) { *$self->{log_debug} = ${ "${class}::LOG_DEBUG" }; } if( Want::want( 'OBJECT' ) ) { return( $self->init( @_ ) ); } my $new = $self->init( @_ ); if( !defined( $new ) ) { # If we are called on an object, we hand it the error so the caller can check it using the object: # my $new = $old->new || die( $old->error ); if( $self->_is_object( $that ) && $that->can( 'pass_error' ) ) { return( $that->pass_error( $self->error ) ); } else { return( $self->pass_error ); } }; return( $new ); } sub deserialise { my $self = shift( @_ ); my $data; $data = shift( @_ ) if( scalar( @_ ) && ( @_ % 2 ) ); my $opts = $self->_get_args_as_hash( @_ ); $opts->{base64} //= ''; $opts->{data} = $data if( defined( $data ) && length( $data ) ); my $this = $self->_obj2h; my $class = $opts->{serialiser} || $opts->{serializer} || $SERIALISER; return( $self->error( "No serialiser class was provided nor set in \$Module::Generic::SERIALISER" ) ) if( !defined( $class ) || !length( $class ) ); # Well, nothing to do if( ( !defined( $opts->{file} ) || !length( $opts->{file} ) ) && ( !defined( $opts->{io} ) || !length( $opts->{io} ) ) && ( !defined( $opts->{data} ) || !length( $opts->{data} ) ) ) { return( '' ); } # The data provided may be composed only of null bytes, which is the case sometime # when retrieved from memory, and in such case, there is no point passing it to # deserialiser. Even worse, CBOR::XS does not deal with extra null padded data in the first # place, and Sereal would not like a string made only of null bytes elsif( CORE::exists( $opts->{data} ) && CORE::defined( $opts->{data} ) && $opts->{data} =~ /\x{00}$/ ) { ( my $temp = $opts->{data} ) =~ s/\x{00}+$//gs; # There is nothing to do return( '' ) if( !length( $temp ) ); } if( $class eq 'CBOR' || $class eq 'CBOR::XS' ) { $self->_load_class( 'CBOR::XS' ) || return( $self->pass_error ); } else { $self->_load_class( $class ) || return( $self->pass_error ); if( $class eq 'Sereal' ) { $self->_load_class( 'Sereal::Decoder' ) || return( $self->pass_error ); } } # This should be an array with two entries: encoder and decoder handler code reference my $base64; if( defined( $opts->{base64} ) && $opts->{base64} ) { $base64 = $self->_has_base64( $opts->{base64} ); return( $self->error( "base64 option '$opts->{base64}' has been provided for deserialising, but could not get handlers." ) ) if( !$base64 ); if( ref( $base64 ) ne 'ARRAY' || scalar( @$base64 ) < 2 || !defined( $base64->[0] ) || !defined( $base64->[1] ) || ref( $base64->[0] ) ne 'CODE' || ref( $base64->[1] ) ne 'CODE' ) { return( $self->error( "Value returned by _has_base64 is not an array reference containing two code references." ) ); } } if( $class eq 'CBOR' || $class eq 'CBOR::XS' ) { my @options = qw( max_depth max_size allow_unknown allow_sharing allow_cycles forbid_objects pack_strings text_keys text_strings validate_utf8 filter ); my $cbor = CBOR::XS->new; $cbor->allow_sharing(1); for( @options ) { next unless( CORE::exists( $opts->{ $_ } ) ); $cbor->$_( $opts->{ $_ } ); } if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !$f->exists ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( $f->is_directory ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( $f->is_empty ); my $data = $f->load( binmode => 'raw' ); return( $self->pass_error( $f->error ) ) if( !defined( $data ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $data ); ( $ref, my $bytes ) = $cbor->decode_prefix( $decoded ); } else { ( $ref, my $bytes ) = $cbor->decode_prefix( $data ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $ref ); } elsif( exists( $opts->{data} ) ) { return( $self->error( "Data provided to deserialise with $class is empty." ) ) if( !defined( $opts->{data} ) || !length( $opts->{data} ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $opts->{data} ); ( $ref, my $bytes ) = $cbor->decode_prefix( $decoded ); } else { ( $ref, my $bytes ) = $cbor->decode_prefix( $opts->{data} ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $ref ); } else { return( $self->error( "No file and no data was provided to deserialise with $class." ) ); } } elsif( $class eq 'CBOR::Free' ) { if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !$f->exists ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( $f->is_directory ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( $f->is_empty ); my $data = $f->load( binmode => 'raw' ); return( $self->pass_error( $f->error ) ) if( !defined( $data ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $data ); $ref = CBOR::Free::decode( $decoded ); } else { $ref = CBOR::Free::decode( $data ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $ref ); } elsif( exists( $opts->{data} ) ) { return( $self->error( "Data provided to deserialise with $class is empty." ) ) if( !defined( $opts->{data} ) || !length( $opts->{data} ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $opts->{data} ); $ref = CBOR::Free::decode( $decoded ); } else { $ref = CBOR::Free::decode( $opts->{data} ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $ref ); } else { return( $self->error( "No file and no data was provided to deserialise with $class." ) ); } } elsif( $class eq 'JSON' ) { my @options = qw( allow_blessed allow_nonref allow_unknown allow_tags ascii boolean_values canonical convert_blessed filter_json_object filter_json_single_key_object indent latin1 max_depth max_size pretty relaxed space_after space_before utf8 ); my $json = JSON->new; for( @options ) { next unless( CORE::exists( $opts->{ $_ } ) ); if( my $code = $json->can( $_ ) ) { $code->( $json, $opts->{ $_ } ); } } if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !$f->exists ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( $f->is_directory ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( $f->is_empty ); my $data = $f->load( binmode => 'raw' ); return( $self->pass_error( $f->error ) ) if( !defined( $data ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $data ); ( $ref, my $bytes ) = $json->decode_prefix( $decoded ); } else { ( $ref, my $bytes ) = $json->decode_prefix( $data ); } }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $ref ); } elsif( exists( $opts->{data} ) ) { return( $self->error( "Data provided to deserialise with $class is empty." ) ) if( !defined( $opts->{data} ) || !length( $opts->{data} ) ); my $ref; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $opts->{data} ); ( $ref, my $bytes ) = $json->decode_prefix( $decoded ); } else { ( $ref, my $bytes ) = $json->decode_prefix( $opts->{data} ); } }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $ref ); } else { return( $self->error( "No file and no data was provided to deserialise with $class." ) ); } } elsif( $class eq 'Sereal' ) { my @options = qw( refuse_snappy refuse_objects no_bless_objects validate_utf8 max_recursion_depth max_num_hash_entries max_num_array_entries max_string_length max_uncompressed_size incremental alias_smallint alias_varint_under use_undef set_readonly set_readonly_scalars ); my $ref = {}; for( @options ) { $ref->{ $_ } = $opts->{ $_ } if( exists( $opts->{ $_ } ) ); } my $code; my $dec = Sereal::Decoder->new( $ref ); if( exists( $opts->{file} ) && $opts->{file} ) { return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !-e( "$opts->{file}" ) ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( -d( "$opts->{file}" ) ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( -z( "$opts->{file}" ) ); if( defined( $base64 ) ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !$f->exists ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( $f->is_directory ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( $f->is_empty ); my $data = $f->load( binmode => 'raw' ); return( $self->pass_error( $f->error ) ) if( !defined( $data ) ); my $decoded = $base64->[1]->( $data ); my $result; # try-catch local $@; eval { $result = $dec->decode( $decoded ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $result ); } else { my $result; # try-catch local $@; eval { $result = $dec->decode_from_file( "$opts->{file}" => $code ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $result ); } } elsif( exists( $opts->{data} ) ) { return( $self->error( "Data provided to deserialise with $class is empty." ) ) if( !defined( $opts->{data} ) || !length( $opts->{data} ) ); my $is_sereal = sub { my $type = Sereal::Decoder->looks_like_sereal( $_[0] ); # return( $self->error( "Data retrieved from share memory block does not look like sereal data." ) ) if( !$type ); }; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $opts->{data} ); $is_sereal->( $decoded ) if( $self->debug ); $dec->decode( $decoded => $code ); } else { $is_sereal->( $opts->{data} ) if( $self->debug ); $dec->decode( $opts->{data} => $code ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise with $class ", CORE::length( $opts->{data} ), " bytes of data (", ( CORE::length( $opts->{data} ) > 128 ? ( substr( $opts->{data}, 0, 128 ) . '(trimmed)' ) : $opts->{data} ), ": $@" ) ); } } else { return( $self->error( "No file and no data was provided to deserialise with $class." ) ); } return( $code ); } elsif( $class eq 'Storable::Improved' || $class eq 'Storable' ) { if( exists( $opts->{file} ) && $opts->{file} ) { return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !-e( "$opts->{file}" ) ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( -d( "$opts->{file}" ) ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( -z( "$opts->{file}" ) ); # We need to check if the serialised data were created with Storable::store # or by Storable::freeze then stored into a file separately with print or syswrite # The latter would not have the necessary headers # As per Storable documentation, if the following return an hash it is a # valid file with header, otherwise it would return undef my $info = &{"${class}\::file_magic"}( "$opts->{file}" ); if( ref( $info ) eq 'HASH' ) { if( $this->{debug} || $opts->{debug} ) { print( STDOUT <{byteorder} File......... : $info->{file} Header size.. : $info->{hdrsize} Integer size. : $info->{intsize} Long size.... : $info->{longsize} Major version : $info->{major} Minor version : $info->{minor} Net order.... : $info->{netorder} NV size...... : $info->{nvsize} PTR size..... : $info->{ptrsize} Version...... : $info->{version} Version NV... : $info->{version_nv} EOT } if( defined( $base64 ) ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); return( $self->error( "File provided \"$opts->{file}\" does not exist." ) ) if( !$f->exists ); return( $self->error( "File provided \"$opts->{file}\" is actually a directory." ) ) if( $f->is_directory ); return( $self->error( "File provided \"$opts->{file}\" to deserialise is empty." ) ) if( $f->is_empty ); $f->lock( shared => 1 ) if( $opts->{lock} ); my $data = $f->load( binmode => 'raw' ); $f->unlock; return( $self->pass_error( $f->error ) ) if( !defined( $data ) ); my $decoded = $base64->[1]->( $data ); my $result; # try-catch local $@; eval { $result = &{"${class}\::thaw"}( $decoded ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $result ); } elsif( $opts->{lock} ) { my $rv; # try-catch local $@; eval { $rv = &{"${class}\::lock_retrieve"}( "$opts->{file}" ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $rv ); } else { my $rv; # try-catch local $@; eval { $rv = &{"${class}\::retrieve"}( "$opts->{file}" ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $rv ); } } else { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->lock( shared => 1 ) if( $opts->{lock} ); my $data = $f->load( binmode => 'raw' ); $f->unlock; return( $data ) if( !defined( $data ) || !length( $data ) ); my $decoded; # try-catch local $@; eval { $decoded = &{"${class}\::thaw"}( $data ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } # return( &{"${class}\::thaw"}( $data ) ); return( $decoded ); } } elsif( exists( $opts->{data} ) ) { return( $self->error( "Data provided to deserialise with $class is empty." ) ) if( !defined( $opts->{data} ) || !length( $opts->{data} ) ); my $rv; # try-catch local $@; eval { if( defined( $base64 ) ) { my $decoded = $base64->[1]->( $opts->{data} ); $rv = &{"${class}\::thaw"}( $decoded ); } else { $rv = &{"${class}\::thaw"}( $opts->{data} ); } }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $rv ); } elsif( exists( $opts->{io} ) ) { return( $self->error( "File handle provided ($opts->{io}) is not an actual file handle to get data to deserialise." ) ) if( ( Scalar::Util::reftype( $opts->{io} ) // '' ) ne 'GLOB' ); if( defined( $base64 ) ) { my $data = ''; while( read( $opts->{io}, my $buff, 2048 ) ) { $data .= $buff; } my $decoded = $base64->[1]->( $data ); my $rv; # try-catch local $@; eval { $rv = &{"${class}\::thaw"}( $decoded ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $rv ); } else { my $rv; # try-catch local $@; eval { $rv = &{"${class}\::fd_retrieve"}( $opts->{io} ); }; if( $@ ) { return( $self->error( "Error trying to deserialise data with $class: $@" ) ); } return( $rv ); } } else { return( $self->error( "No file and no data was provided to deserialise with $class." ) ); } } else { return( $self->error( "Unsupporterd serialiser \"$class\"." ) ); } } sub deserialize { return( shift->deserialise( @_ ) ); } sub debug { my $self = shift( @_ ); my $class = ( ref( $self ) || $self ); my $this = $self->_obj2h; no strict 'refs'; no warnings 'once'; if( @_ ) { my $flag = shift( @_ ); $this->{debug} = $flag; if( $this->{debug} && !$this->{debug_level} ) { $this->{debug_level} = $this->{debug}; } } return( $this->{debug} || ${"$class\:\:DEBUG"} ); } sub dump { my $self = shift( @_ ); my $opts = {}; if( @_ > 1 && ref( $_[-1] ) eq 'HASH' && exists( $_[-1]->{filter} ) && ref( $_[-1]->{filter} ) eq 'CODE' ) { $opts = pop( @_ ); if( $self->_load_class( 'Data::Pretty' ) ) { return( Data::Pretty::dumpf( @_, $opts->{filter} ) ); } elsif( $self->_load_class( 'Data::Dump' ) ) { return( Data::Dump::dumpf( @_, $opts->{filter} ) ); } else { return( "# Neither Data::Pretty or Data::Dump are installed." ); } } else { if( $self->_load_class( 'Data::Pretty' ) ) { return( Data::Pretty::dump( @_ ) ); } elsif( $self->_load_class( 'Data::Dump' ) ) { return( Data::Dump::dump( @_ ) ); } else { return( "# Neither Data::Pretty or Data::Dump are installed." ); } } } { no warnings 'once'; *dumpto = \&dumpto_dumper; } sub error { my $self = shift( @_ ); my $class = ref( $self ) || $self; our $MOD_PERL; my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $o; no strict 'refs'; no warnings 'once'; my $want_return = { array => sub { return( [] ); }, code => sub { return( sub{} ); }, 'glob' => sub { open( my $tmp, '>', \undef ); return( $tmp ); }, hash => sub { return( {} ); }, object => sub { require Module::Generic::Null; my $null = Module::Generic::Null->new( $o, { debug => $this->{debug}, has_error => 1, wants => 'object' }); return( $null ); }, 'scalar' => sub { my $dummy = undef; return( \$dummy ); }, }; my $want_what = Want::wantref(); # Ensure this is lowercase and at the same time that this is defined $want_what = lc( $want_what // '' ); # What type of expected value we support to prevent perl error upon undef. # By default: object my $want_ok = [qw( object )]; 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 = shift( @_ ); } elsif( ref( $_[0] ) eq 'HASH' ) { $args = shift( @_ ); } else { $args->{message} = join( '', map( ( ref( $_ ) eq 'CODE' && !$this->{_msg_no_exec_sub} ) ? $_->() : $_, @_ ) ); } $args->{class} //= ''; my $max_len = ( CORE::exists( $this->{error_max_length} ) && $this->{error_max_length} =~ /^[-+]?\d+$/ ) ? $this->{error_max_length} : 0; $args->{message} = substr( $args->{message}, 0, $this->{error_max_length} ) if( $max_len > 0 && length( $args->{message} ) > $max_len ); # Reset it $this->{_msg_no_exec_sub} = 0; # Note Taken from Carp to find the right point in the stack to start from my $caller_func; $caller_func = \&{"CORE::GLOBAL::caller"} if( defined( &{"CORE::GLOBAL::caller"} ) ); # What type of expected value we support to prevent perl error upon undef. # By default: object if( exists( $args->{want} ) && Scalar::Util::reftype( $args->{want} // '' ) eq 'ARRAY' ) { $want_ok = CORE::delete( $args->{want} ); } if( scalar( grep( $_ eq 'all', @$want_ok ) ) ) { foreach my $t ( keys( %$want_return ) ) { push( @$want_ok, $t ) unless( scalar( grep( /^$t$/i, @$want_ok ) ) ); } } push( @$want_ok, 'OBJECT' ) unless( scalar( grep( /^object$/i, @$want_ok ) ) ); if( defined( $o ) ) { $this->{error} = ${ $class . '::ERROR' } = $o; } else { $args->{debug} = $self->debug unless( CORE::exists( $args->{debug} ) ); my $ex_class = CORE::length( $args->{class} ) ? $args->{class} : ( CORE::exists( $this->{_exception_class} ) && CORE::length( $this->{_exception_class} ) ) ? $this->{_exception_class} : ( defined( ${"${class}\::EXCEPTION_CLASS"} ) && CORE::length( ${"${class}\::EXCEPTION_CLASS"} ) ) ? ${"${class}\::EXCEPTION_CLASS"} : 'Module::Generic::Exception'; unless( $self->_is_class_loaded( $ex_class ) || scalar( 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( __PACKAGE__ . "::error() is unable to load exception class \"$ex_class\": $@" ) if( $@ ); } $o = $this->{error} = ${ $class . '::ERROR' } = $ex_class->new( $args ); } my $err_callback = $self->_on_error; if( !defined( $err_callback ) && CORE::exists( $args->{callback} ) && ref( $args->{callback} ) eq 'CODE' ) { $err_callback = $args->{callback}; } if( defined( $err_callback ) && ref( $err_callback ) eq 'CODE' ) { local $SIG{__WARN__} = sub{}; local $SIG{__DIE__} = sub{}; local $@; eval { $err_callback->( $self, $o ); }; } # Get the warnings status of the caller. We use caller(1) to skip one frame further, ie our caller's caller # This can be changed by using 'no warnings' my $should_display_warning = 0; my $no_use_warnings = 1; unless( $this->{quiet} ) { # Try to get the warnings status if is enabled at all. $should_display_warning = $self->_warnings_is_enabled; $no_use_warnings = 0; # If no warnings are registered for our package, we display warnings. if( $no_use_warnings && !defined( $warnings::Bits{ $class } ) ) { $no_use_warnings = 0; $should_display_warning = 1; } } if( $no_use_warnings ) { my $call_offset = 0; while( my @call_data = $caller_func ? $caller_func->( $call_offset ) : caller( $call_offset ) ) { my @prev_stack = $caller_func ? $caller_func->( $call_offset - 1 ) : caller( $call_offset - 1 ); unless( $call_offset > 0 && $call_data[0] ne $class && $prev_stack[0] eq $class ) { $call_offset++; next; } last if( $call_data[9] || ( $call_offset > 0 && $prev_stack[0] ne $class ) ); $call_offset++; } my $bitmask = $caller_func ? ($caller_func->( $call_offset ))[9] : ( caller( $call_offset ) )[9]; my $offset = $warnings::Offsets{uninitialized}; $should_display_warning = vec( $bitmask, $offset, 1 ); } my $r; if( $MOD_PERL ) { # try-catch local $@; eval { $r = Apache2::RequestUtil->request; $r->warn( $o->as_string ) if( $r ); }; if( $@ ) { print( STDERR "Error trying to get the global Apache2::ApacheRec: $@\n" ); } } my $err_handler = $self->error_handler; if( $err_handler && ref( $err_handler ) eq 'CODE' ) { $err_handler->( $o ); } elsif( $r ) { if( my $log_handler = $r->get_handlers( 'PerlPrivateErrorHandler' ) ) { $log_handler->( $o ); } else { $r->warn( $o->as_string ) if( $should_display_warning ); } } elsif( $this->{fatal} || $args->{fatal} || ( defined( ${"${class}\::FATAL_ERROR"} ) && ${"${class}\::FATAL_ERROR"} ) ) { # my $enc_str = eval{ Encode::encode( 'UTF-8', "$o", Encode::FB_CROAK ) }; # die( $@ ? $o : $enc_str ); die( $o ); } elsif( $should_display_warning ) { if( $r ) { $r->warn( $o->as_string ); } else { 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( overload::Overloaded( $self ) ) { my $overload_meth_ref = overload::Method( $self, '""' ); my $overload_meth_name = ''; $overload_meth_name = Sub::Util::subname( $overload_meth_ref ) if( ref( $overload_meth_ref ) ); # use Sub::Identify (); # my( $over_file, $over_line ) = Sub::Identify::get_code_location( $overload_meth_ref ); # my( $over_call_pack, $over_call_file, $over_call_line ) = caller(); my $call_sub = $caller_func ? ($caller_func->(1))[3] : (caller(1))[3]; # overloaded method name can be, for example: My::Package::as_string # or, for anonymous sub: My::Package::__ANON__[lib/My/Package.pm:12] # caller sub will reliably be the same, so we use it to check if we are called from an overloaded stringification and return undef right here. # Want::want check of being called in an OBJECT context triggers a perl segmentation fault if( length( $overload_meth_name ) && $overload_meth_name eq $call_sub ) { return; } } # When used inside an lvalue method if( $args->{lvalue} && $args->{assign} ) { return( $data->{__dummy} = 'dummy' ); } # https://metacpan.org/pod/Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef # https://perlmonks.org/index.pl?node_id=741847 # Because in list context this would create a lit with one element undef() # A bare return will return an empty list or an undef scalar # return( undef() ); # return; # As of 2019-10-13, Module::Generic version 0.6, we use this special package Module::Generic::Null to be returned in chain without perl causing the error that a method was called on an undefined value # 2020-05-12: Added the no_return_null_object to instruct not to return a null object # This is especially needed when an error is called from TIEHASH that returns a special object. # A Null object would trigger a fatal perl segmentation fault elsif( !$args->{no_return_null_object} && ( ( $want_what && CORE::exists( $want_return->{ $want_what } ) && scalar( grep( /^$want_what$/i, @$want_ok ) ) ) || $args->{object} ) ) { if( $args->{object} ) { rreturn( $want_return->{object}->() ); } else { rreturn( $want_return->{ $want_what }->() ); } } elsif( $args->{lvalue} && want( 'RVALUE' ) ) { rreturn; } return; } # To avoid the perl error of 'called on undefined value' and so the user can do # $o->error->_message for example without concerning himself/herself whether an exception object is actually set if( !$this->{error} ) { if( $want_what && CORE::exists( $want_return->{ $want_what } ) && scalar( grep( /^$want_what$/i, @$want_ok ) ) ) { rreturn( $want_return->{ $want_what }->() ); } } return( ref( $self ) ? $this->{error} : ${ $class . '::ERROR' } ); } sub error_handler { return( shift->_set_get_code( '_error_handler', @_ ) ); } { no warnings 'once'; *errstr = \&error; } sub fatal { return( shift->_set_get_boolean( 'fatal', @_ ) ); } sub get { my $self = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my @data = map{ $data->{ $_ } } @_; return( wantarray() ? @data : $data[ 0 ] ); } sub init { my $self = shift( @_ ); my $pkg = ref( $self ); no warnings 'uninitialized'; no overloading; my $this = $self->_obj2h; no strict 'refs'; $this->{verbose} = defined( ${ $pkg . '::VERBOSE' } ) ? ${ $pkg . '::VERBOSE' } : 0 if( !length( $this->{verbose} ) ); $this->{debug} = defined( ${ $pkg . '::DEBUG' } ) ? ${ $pkg . '::DEBUG' } : 0 if( !length( $this->{debug} ) ); $this->{version} = ${ $pkg . '::VERSION' } if( !defined( $this->{version} ) && defined( ${ $pkg . '::VERSION' } ) ); # $this->{level} = 0; $this->{_colour_open} = undef unless( CORE::exists( $this->{_colour_open} ) ); $this->{_colour_close} = undef unless( CORE::exists( $this->{_colour_close} ) ); $this->{_exception_class} = 'Module::Generic::Exception' unless( CORE::defined( $this->{_exception_class} ) && CORE::length( $this->{_exception_class} ) ); $this->{_init_params_order} = [] unless( ref( $this->{_init_params_order} ) ); # If no debug level was provided when calling message, this level will be assumed # Example: message( "Hello" ); # If _message_default_level was set to 3, this would be equivalent to message( 3, "Hello" ) $this->{_init_strict_use_sub} = 0 unless( length( $this->{_init_strict_use_sub} ) ); $this->{_log_handler} = '' unless( length( $this->{_log_handler} ) ); $this->{_message_default_level} = 0; $this->{_msg_no_exec_sub} = 0 unless( length( $this->{_msg_no_exec_sub} ) ); $this->{_error_max_length} = '' unless( length( $this->{_error_max_length} ) ); my $data = $this; if( $this->{_data_repo} ) { $this->{ $this->{_data_repo} } = {} if( !$this->{ $this->{_data_repo} } ); $data = $this->{ $this->{_data_repo} }; } # If the calling module wants to set up object cleanup if( $this->{_mod_perl_cleanup} && $MOD_PERL ) { # try-catch local $@; eval { local $SIG{__DIE__}; # Must enable GlobalRequest for this to work. my $r = Apache2::RequestUtil->request; if( $r ) { $r->pool->cleanup_register(sub { map{ delete( $this->{ $_ } ) } keys( %$this ); undef( %$this ); return(1); }); } }; if( $@ ) { print( STDERR "Error trying to get the global Apache2::ApacheRec object and setting up a cleanup handler: $@\n" ); } } @_ = () if( @_ == 1 && !defined( $_[0] ) ); if( @_ ) { my @args = @_; my $vals; if( ref( $args[0] ) eq 'HASH' || ( Scalar::Util::blessed( $args[0] ) && $args[0]->isa( 'Module::Generic::Hash' ) ) ) { my $h = shift( @args ); my $debug_value; $debug_value = CORE::delete( $h->{debug} ) if( CORE::exists( $h->{debug} ) ); $vals = [ %$h ]; unshift( @$vals, debug => $debug_value ) if( CORE::defined( $debug_value ) ); } elsif( ref( $args[0] ) eq 'ARRAY' ) { $vals = $args[0]; } # Special case when there is an undefined value passed (null) even though it is declared as a hash or object elsif( scalar( @args ) == 1 && !defined( $args[0] ) ) { return( $self->error( "Only argument is provided to init ", ref( $self ), " object and its value is undefined." ) ); } elsif( ( scalar( @args ) % 2 ) ) { return( $self->error( sprintf( "Uneven number of parameters provided (%d). Should receive key => value pairs. Parameters provided are: %s", scalar( @args ), join( ', ', @args ) ) ) ); } else { $vals = \@args; } my $order = ( CORE::exists( $this->{_init_params_order} ) && Scalar::Util::reftype( $this->{_init_params_order} ) eq 'ARRAY' ) ? $this->{_init_params_order} : []; if( scalar( @$order ) ) { my $new = []; foreach my $param ( @$order ) { for( my $i = 0; $i < scalar( @$vals ); $i += 2 ) { if( defined( $vals->[$i] ) && $vals->[$i] eq $param ) { push( @$new, splice( @$vals, $i, 2 ) ); } } } if( scalar( @$new ) ) { push( @$new, @$vals ); @$vals = @$new; } } if( CORE::exists( $this->{_init_preprocess} ) && ref( $this->{_init_preprocess} ) eq 'CODE' ) { $vals = eval { $this->{_init_preprocess}->( $vals ); }; # try-catch local $@; if( $@ ) { die( "Pre-processing of init data failed: $@" ); } elsif( Scalar::Util::reftype( $vals // '' ) ne 'ARRAY' ) { die( "Pre-processing of init data returned a ", ( ref( $vals ) // 'string' ), ", but was expecting an array reference." ); } } # Check if there is a debug parameter, and if we find one, set it first so that that # calls to the package subroutines can produce verbose feedback as necessary for( my $i = 0; $i < scalar( @$vals ); $i++ ) { next if( !defined( $vals->[$i] ) ); if( $vals->[$i] eq 'debug' ) { my $v = $vals->[$i + 1]; $self->debug( $v ); CORE::splice( @$vals, $i, 2 ); } } for( my $i = 0; $i < scalar( @$vals ); $i++ ) { my $name = $vals->[ $i ]; my $val = $vals->[ ++$i ]; # Ensure the name has any dash ("-") converted to underscore ("_") my $orig = $name; my $transformed = ( $name =~ tr/-/_/ ); my $meth = $self->can( $name ); if( defined( $meth ) ) { if( !defined( $self->$name( $val ) ) ) { if( defined( $val ) && $self->error ) { warn( "Warning: method $name returned undef while initialising object ", ref( $self ), ": ", ( $self->error ? $self->error->message : '' ), "\n" ); return; } } next; } elsif( $this->{_init_strict_use_sub} ) { $self->error( "Unknown method $name in class $pkg" ); next; } elsif( exists( $data->{ $name } ) || exists( $data->{ $orig } ) ) { # Pre-existing field value looks like a module package and that package is already loaded if( ( index( $data->{ $name }, '::' ) != -1 || $data->{ $name } =~ /^[a-zA-Z][a-zA-Z\_]*[a-zA-Z]$/ ) && $self->_is_class_loaded( $data->{ $name } ) ) { my $thisPack = $data->{ $name }; if( !Scalar::Util::blessed( $val ) ) { return( $self->error( "$name parameter expects a package $thisPack object, but instead got '$val'." ) ); } elsif( !$val->isa( $thisPack ) ) { return( $self->error( "$name parameter expects a package $thisPack object, but instead got an object from package '", ref( $val ), "'." ) ); } } elsif( $this->{_init_strict} ) { if( exists( $data->{ $orig } ) && ref( $data->{ $orig } ) eq 'ARRAY' ) { return( $self->error( "$orig parameter expects an array reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'ARRAY' ); } elsif( exists( $data->{ $orig } ) && ref( $data->{ $orig } ) eq 'HASH' ) { return( $self->error( "$orig parameter expects an hash reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'HASH' ); } elsif( exists( $data->{ $orig } ) && ref( $data->{ $orig } ) eq 'SCALAR' ) { return( $self->error( "$orig parameter expects a scalar reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'SCALAR' ); } elsif( $transformed ) { if( exists( $data->{ $name } ) && ref( $data->{ $name } ) eq 'ARRAY' ) { return( $self->error( "$name parameter expects an array reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'ARRAY' ); } elsif( exists( $data->{ $name } ) && ref( $data->{ $name } ) eq 'HASH' ) { return( $self->error( "$name parameter expects an hash reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'HASH' ); } elsif( exists( $data->{ $name } ) && ref( $data->{ $name } ) eq 'SCALAR' ) { return( $self->error( "$name parameter expects a scalar reference, but instead got '$val'." ) ) if( ( Scalar::Util::reftype( $val ) // '' ) ne 'SCALAR' ); } } } } # The name parameter does not exist else { # If we are strict, we reject next if( $this->{_init_strict} ); } # We passed all tests $data->{ $name } = $val; } } return( $self ); } sub log_handler { return( shift->_set_get_code( '_log_handler', @_ ) ); } { no warnings 'once'; # NOTE: aliasing message to _message *message = \&_message; # NOTE: aliasing messagec to message_colour *messagec = \&message_colour; # NOTE: aliasing message_check to _message_check *message_check = \&_message_check; # NOTE: aliasing message_color to message_colour *message_color = \&message_colour; # NOTE: aliasing message_frame to _message_frame *message_frame = \&_message_frame; # NOTE: aliasing message_log to _message_log *message_log = \&_message_log; # NOTE: aliasing message_log_io to _message_log_io *message_log_io = \&_message_log_io; # NOTE: aliasing messagef to _messagef *messagef = \&_messagef; } sub new_array { my $self = shift( @_ ); require Module::Generic::Array; my $v = Module::Generic::Array->new( @_ ); return( $self->pass_error( Module::Generic::Array->error ) ) if( !defined( $v ) ); return( $v ); } sub new_datetime { my $self = shift( @_ ); require Module::Generic::DateTime; my $v = Module::Generic::DateTime->new( @_ ); return( $self->pass_error( Module::Generic::DateTime->error ) ) if( !defined( $v ) ); return( $v ); } sub new_file { my $self = shift( @_ ); require Module::Generic::File; my $v = Module::Generic::File->new( @_ ); return( $self->pass_error( Module::Generic::File->error ) ) if( !defined( $v ) ); return( $v ); } sub new_hash { my $self = shift( @_ ); require Module::Generic::Hash; my $v = Module::Generic::Hash->new( @_ ); return( $self->pass_error( Module::Generic::Hash->error ) ) if( !defined( $v ) ); return( $v ); } sub new_json { my $self = shift( @_ ); my $opts = $self->_get_args_as_hash( @_ ); $self->_load_class( 'JSON' ) || return( $self->pass_error ); # 'allow_tags' is a real trouble-maker my $j = JSON->new->allow_nonref->allow_blessed->convert_blessed->allow_tags->relaxed; # my $j = JSON->new->allow_nonref->allow_blessed->convert_blessed->relaxed; # Same as in Module::Generic::File::unload_json() my $equi = { order => 'canonical', ordered => 'canonical', sorted => 'canonical', sort => 'canonical', }; foreach my $opt ( keys( %$opts ) ) { my $ref; $ref = $j->can( exists( $equi->{ $opt } ) ? $equi->{ $opt } : $opt ) || do { warn( "Unknown JSON option '${opt}'\n" ) if( $self->_warnings_is_enabled ); next; }; # try-catch local $@; eval { $ref->( $j, $opts->{ $opt } ); }; if( $@ ) { if( $@ =~ /perl[[:blank:]\h]+structure[[:blank:]\h]+exceeds[[:blank:]\h]+maximum[[:blank:]\h]+nesting[[:blank:]\h]+level/i ) { my $max = $j->get_max_depth; return( $self->error( "Unable to set json option ${opt}: $@ (max_depth value is ${max})" ) ); } else { return( $self->error( "Unable to set json option ${opt}: $@" ) ); } } } return( $j ); } sub new_json_safe { my $self = shift( @_ ); my $opts = $self->_get_args_as_hash( @_ ); $self->_load_class( 'Module::Generic::JSON' ) || return( $self->pass_error ); my $defaults = { allow_nonref => 1, allow_blessed => 1, convert_blessed => 1, allow_tags => 1, relaxed => 1, }; foreach my $opt ( keys( %$defaults ) ) { if( !exists( $opts->{ $opt } ) || !defined( $opts->{ $opt } ) ) { $opts->{ $opt } = $defaults->{ $opt }; } } my $j = Module::Generic::JSON->new( %$opts ) || return( $self->pass_error( Module::Generic::JSON->error ) ); return( $j ); } sub new_null { my $self = shift( @_ ); my $opts = $self->_get_args_as_hash( @_ ); my $what; if( CORE::exists( $opts->{type} ) && CORE::length( $opts->{type} // '' ) && $opts->{type} =~ /^(array|code|hash|object|refscalar)$/i ) { $what = $opts->{type}; } else { $what = Want::want( 'LIST' ) ? 'LIST' : Want::want( 'HASH' ) ? 'HASH' : Want::want( 'ARRAY' ) ? 'ARRAY' : Want::want( 'OBJECT' ) ? 'OBJECT' : Want::want( 'CODE' ) ? 'CODE' : Want::want( 'REFSCALAR' ) ? 'REFSCALAR' : Want::want( 'BOOLEAN' ) ? 'BOOLEAN' : Want::want( 'GLOB' ) ? 'GLOB' : Want::want( 'SCALAR' ) ? 'SCALAR' : Want::want( 'VOID' ) ? 'VOID' : ''; } if( $what eq 'OBJECT' ) { require Module::Generic::Null; return( Module::Generic::Null->new( @_ ) ); } elsif( $what eq 'ARRAY' ) { return( [] ); } elsif( $what eq 'HASH' ) { return( {} ); } elsif( $what eq 'CODE' ) { return( sub{ return; } ); } elsif( $what eq 'REFSCALAR' ) { return( \undef ); } else { return; } } sub new_number { my $self = shift( @_ ); require Module::Generic::Number; my $v = Module::Generic::Number->new( @_ ); return( $self->pass_error( Module::Generic::Number->error ) ) if( !defined( $v ) ); return( $v ); } sub new_scalar { my $self = shift( @_ ); require Module::Generic::Scalar; my $v = Module::Generic::Scalar->new( @_ ); return( $self->pass_error( Module::Generic::Scalar->error ) ) if( !defined( $v ) ); return( $v ); } sub new_tempdir { my $self = shift( @_ ); require Module::Generic::File; return( Module::Generic::File::tempdir( @_ ) ); } sub new_tempfile { my $self = shift( @_ ); require Module::Generic::File; return( Module::Generic::File::tempfile( @_ ) ); } sub new_version { my $self = shift( @_ ); my $v = shift( @_ ); if( !defined( $v ) ) { return( $self->error( "No version was provided." ) ); } elsif( !CORE::length( "$v" ) ) { return( $self->error( "Value provided, to create a version object, is empty." ) ); } my $vers; # try-catch local $@; eval { $vers = version->parse( "$v" ); }; if( $@ ) { return( $self->error( "Unable to create a version object from '$v': $@" ) ); } return( $vers ); } sub noexec { $_[0]->{_msg_no_exec_sub} = 1; return( $_[0] ); } # Purpose is to get an error object thrown from, possibly another package, # and make it ours and pass it along # e.g.: # $self->pass_error # $self->pass_error( 'Some error that will be passed to error()' ); # $self->pass_error( $error_object ); # $self->pass_error( $error_object, { class => 'Some::ExceptionClass', code => 400 } ); # $self->pass_error({ class => 'Some::ExceptionClass' }); sub pass_error { my $self = shift( @_ ); my $pack = ref( $self ) || $self; my $this = $self->_obj2h; my $opts = {}; my $err; my $class; my $code; my $callback; 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( scalar( @_ ) == 1 && ref( $_[0] ) eq 'HASH' ) { $opts = $_[0]; } else { # $self->pass_error( $error_object, { class => 'Some::ExceptionClass' } ); if( scalar( @_ ) > 1 && ref( $_[-1] ) eq 'HASH' ) { $opts = pop( @_ ); } $err = $_[0]; } } $err = $opts->{error} if( !defined( $err ) && CORE::exists( $opts->{error} ) && defined( $opts->{error} ) && CORE::length( $opts->{error} ) ); # 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( scalar( keys( %$opts ) ) == 1 && [keys( %$opts )]->[0] eq 'class' ); $class = $opts->{class} if( CORE::exists( $opts->{class} ) && defined( $opts->{class} ) && CORE::length( $opts->{class} ) ); $code = $opts->{code} if( CORE::exists( $opts->{code} ) && defined( $opts->{code} ) && CORE::length( $opts->{code} ) ); $callback = $opts->{callback} if( CORE::exists( $opts->{callback} ) && defined( $opts->{callback} ) && ref( $opts->{callback} ) ); # 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( !defined( $err ) && ( !scalar( @_ ) || defined( $class ) ) ) { my $error = ref( $self ) ? $this->{error} : length( ${ $pack . '::ERROR' } ) ? ${ $pack . '::ERROR' } : undef; if( !defined( $error ) ) { warn( "No error object provided and no previous error set either! It seems the previous method call returned a simple undef\n", $self->_get_stack_trace ); } else { $err = ( defined( $class ) ? bless( $error => $class ) : $error ); $err->code( $code ) if( defined( $code ) ); } } elsif( defined( $err ) && Scalar::Util::blessed( $err ) && ( scalar( @_ ) == 1 || ( scalar( @_ ) == 2 && defined( $class ) ) ) ) { $this->{error} = ${ $pack . '::ERROR' } = ( defined( $class ) ? bless( $err => $class ) : $err ); $this->{error}->code( $code ) if( defined( $code ) ); my $err_callback = $self->_on_error; $err_callback = $callback if( !defined( $err_callback ) && defined( $callback ) ); if( defined( $err_callback ) && ref( $err_callback ) eq 'CODE' ) { local $SIG{__WARN__} = sub{}; local $SIG{__DIE__} = sub{}; local $@; eval { $err_callback->( $self, $this->{error} ); }; } if( $this->{fatal} || ( defined( ${"${class}\::FATAL_ERROR"} ) && ${"${class}\::FATAL_ERROR"} ) ) { die( $this->{error} ); } } # If the error provided is not an object, we call error to create one else { return( $self->error( @_ ) ); } if( want( 'OBJECT' ) ) { require Module::Generic::Null; my $null = Module::Generic::Null->new( $err, { debug => $this->{debug}, has_error => 1 }); rreturn( $null ); } return; } sub quiet { return( shift->_set_get( 'quiet', @_ ) ); } sub serialise { my $self = shift( @_ ); my $data = shift( @_ ); return( $self->error( "No data to serialise was provided." ) ) if( !defined( $data ) || !length( $data ) ); my $opts = $self->_get_args_as_hash( @_ ); my $class = $opts->{serialiser} || $opts->{serializer} || $SERIALISER; return( $self->error( "No serialiser class was provided nor set in \$Module::Generic::SERIALISER" ) ) if( !defined( $class ) || !length( $class ) ); $opts->{base64} //= ''; if( $class eq 'CBOR' || $class eq 'CBOR::XS' ) { $self->_load_class( 'CBOR::XS' ) || return( $self->pass_error ); } else { $self->_load_class( $class ) || return( $self->pass_error ); if( $class eq 'Sereal' ) { $self->_load_class( 'Sereal::Encoder' ) || return( $self->pass_error ); } } # This should be an array with two entries: encoder and decoder handler code reference my $base64; if( defined( $opts->{base64} ) && $opts->{base64} ) { $base64 = $self->_has_base64( $opts->{base64} ); return( $self->error( "base64 option '$opts->{base64}' has been provided for deserialising, but could not get handlers." ) ) if( !$base64 ); if( ref( $base64 ) ne 'ARRAY' || scalar( @$base64 ) < 2 || !defined( $base64->[0] ) || !defined( $base64->[1] ) || ref( $base64->[0] ) ne 'CODE' || ref( $base64->[1] ) ne 'CODE' ) { return( $self->error( "Value returned by _has_base64 is not an array reference containing two code references." ) ); } } if( $class eq 'CBOR' || $class eq 'CBOR::XS' ) { my @options = qw( max_depth max_size allow_unknown allow_sharing allow_cycles forbid_objects pack_strings text_keys text_strings validate_utf8 filter ); my $cbor = CBOR::XS->new; for( @options ) { next unless( CORE::exists( $opts->{ $_ } ) ); if( my $code = $cbor->can( $_ ) ) { $code->( $cbor, $opts->{ $_ } ); } } my $serialised; # try-catch local $@; eval { $serialised = $cbor->encode( $data ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } if( defined( $base64 ) ) { $serialised = $base64->[0]->( $serialised ); } if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->unload( $serialised, { binmode => 'raw' } ) || return( $self->pass_error( $f->error ) ); } return( $serialised ); } if( $class eq 'CBOR::Free' ) { my @options = qw( canonical string_encode_mode preserve_references scalar_references ); my $params = {}; for( @options ) { next unless( CORE::exists( $opts->{ $_ } ) ); $params->{ $_ } = $opts->{ $_ }; } # try-catch local $@; my $serialised; eval { $serialised = CBOR::Free::encode( $data, ( scalar( keys( %$params ) ) ? %$params : () ) ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } if( defined( $base64 ) ) { $serialised = $base64->[0]->( $serialised ); } if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->unload( $serialised, { binmode => 'raw' } ) || return( $self->pass_error( $f->error ) ); } return( $serialised ); } elsif( $class eq 'JSON' ) { my @options = qw( allow_blessed allow_nonref allow_unknown allow_tags ascii boolean_values canonical convert_blessed filter_json_object filter_json_single_key_object indent latin1 max_depth max_size pretty relaxed space_after space_before utf8 ); my $json = JSON->new; for( @options ) { next unless( CORE::exists( $opts->{ $_ } ) ); if( my $code = $json->can( $_ ) ) { $code->( $json, $opts->{ $_ } ); } } # try-catch local $@; my $serialised; eval { $serialised = $json->encode( $data ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } if( defined( $base64 ) ) { $serialised = $base64->[0]->( $serialised ); } if( exists( $opts->{file} ) && $opts->{file} ) { my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->unload( $serialised, { binmode => 'raw' } ) || return( $self->pass_error( $f->error ) ); } return( $serialised ); } elsif( $class eq 'Sereal' ) { my @options = qw( compress compress_threshold compress_level snappy snappy_incr snappy_threshold croak_on_bless freeze_callbacks no_bless_objects undef_unknown stringify_unknown warn_unknown max_recursion_depth canonical canonical_refs sort_keys no_shared_hashkeys dedupe_strings aliased_dedupe_strings protocol_version use_protocol_v1 ); my $ref = {}; for( @options ) { $ref->{ $_ } = $opts->{ $_ } if( exists( $opts->{ $_ } ) ); } my $enc = Sereal::Encoder->new( $ref ); if( exists( $opts->{file} ) && $opts->{file} ) { if( defined( $base64 ) ) { my $serialised = $enc->encode( $data ); $serialised = $base64->[0]->( $serialised ); my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->unload( $serialised, { binmode => 'raw' } ) || return( $self->pass_error( $f->error ) ); return( $serialised ); } else { # try-catch local $@; my $rv; eval { $rv = $enc->encode_to_file( "$opts->{file}", $data, ( exists( $opts->{append} ) ? $opts->{append} : 0 ) ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $rv ); } } else { # try-catch local $@; my $serialised; eval { $serialised = $enc->encode( $data ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } if( defined( $base64 ) ) { return( $base64->[0]->( $serialised ) ); } return( $serialised ); } } elsif( $class eq 'Storable::Improved' || $class eq 'Storable' ) { if( exists( $opts->{file} ) && $opts->{file} ) { if( defined( $base64 ) ) { my $serialised = &{"${class}\::freeze"}( $data ); $serialised = $base64->[0]->( $serialised ); my $f = $self->new_file( $opts->{file} ) || return( $self->pass_error ); $f->unload( $serialised, { binmode => 'raw' } ) || return( $self->pass_error( $f->error ) ); return( $serialised ); } elsif( $opts->{lock} ) { # try-catch local $@; my $rv; eval { $rv = &{"${class}\::lock_store"}( $data => "$opts->{file}" ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $rv ); } else { # try-catch local $@; my $rv; eval { $rv = &{"${class}\::store"}( $data => "$opts->{file}" ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $rv ); } } elsif( exists( $opts->{io} ) ) { return( $self->error( "File handle provided ($opts->{io}) is not an actual file handle to serialise data to." ) ) if( ( Scalar::Util::reftype( $opts->{io} ) // '' ) ne 'GLOB' ); if( defined( $base64 ) ) { my $serialised = &{"${class}\::freeze"}( $data ); $serialised = $base64->[0]->( $serialised ); my $bytes = syswrite( $opts->{io}, $serialised ); return( $self->error( "Unable to write ", CORE::length( $serialised ), " bytes of Storable serialised data to file handle '$opts->{io}': $!" ) ) if( !defined( $bytes ) ); return( $serialised ); } else { # try-catch local $@; my $rv; eval { $rv = &{"${class}\::store_fd"}( $data => $opts->{io} ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } return( $rv ); } } else { # try-catch local $@; my $serialised; eval { $serialised = &{"${class}\::freeze"}( $data ); }; if( $@ ) { return( $self->error( "Error trying to serialise data with $class: $@" ) ); } if( defined( $base64 ) ) { return( $base64->[0]->( $serialised ) ); } return( $serialised ); } } else { return( $self->error( "Unsupporterd serialiser \"$class\"." ) ); } } sub serialize { return( shift->serialise( @_ ) ); } sub set { my $self = shift( @_ ); my %arg = (); if( @_ ) { %arg = ( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my @keys = keys( %arg ); @$data{ @keys } = @arg{ @keys }; } return( scalar( keys( %arg ) ) ); } sub true { return( $Module::Generic::Boolean::true ); } sub false { return( $Module::Generic::Boolean::false ); } sub verbose { my $self = shift( @_ ); my $this = $self->_obj2h; if( @_ ) { my $flag = shift( @_ ); $this->{verbose} = $flag; } return( $this->{verbose} ); } sub will { ( @_ >= 2 && @_ <= 3 ) || die( 'Usage: $obj->can( "method" ) or Module::Generic::will( $obj, "method" )' ); my( $obj, $meth, $level ); if( @_ == 3 && ref( $_[ 1 ] ) ) { $obj = $_[ 1 ]; $meth = $_[ 2 ]; } else { ( $obj, $meth, $level ) = @_; } return if( !ref( $obj ) && index( $obj, '::' ) == -1 ); no strict 'refs'; # Give a chance to UNIVERSAL::can my $ref = undef; if( Scalar::Util::blessed( $obj ) && ( $ref = $obj->can( $meth ) ) ) { return( $ref ); } my $class = ref( $obj ) || $obj; my $origi = $class; if( index( $meth, '::' ) != -1 ) { $origi = substr( $meth, 0, rindex( $meth, '::' ) ); $meth = substr( $meth, rindex( $meth, '::' ) + 2 ); } $ref = \&{ "$class\::$meth" } if( defined( &{ "$class\::$meth" } ) ); return( $ref ) if( defined( $ref ) ); # We do not go further down the rabbit hole if level is greater or equal to 10 $level ||= 0; return if( $level >= 10 ); $level++; # Let's see what Alice has got for us... :-) # We look in the @ISA to see if the method exists in the package from which we # possibly inherited if( @{ "$class\::ISA" } ) { foreach my $pack ( @{ "$class\::ISA" } ) { my $ref = &will( $pack, "$origi\::$meth", $level ); return( $ref ) if( defined( $ref ) ); } } # Then, maybe there is an AUTOLOAD to trap undefined routine? # But, we do not want any loop, do we? # Since will() is called from Module::Generic::AUTOLOAD to check if EXTRA_AUTOLOAD exists # we are not going to call Module::Generic::AUTOLOAD for EXTRA_AUTOLOAD... if( $class ne 'Module::Generic' && $meth ne 'EXTRA_AUTOLOAD' && defined( &{ "$class\::AUTOLOAD" } ) ) { my $sub = sub { $class::AUTOLOAD = "$origi\::$meth"; &{ "$class::AUTOLOAD" }( @_ ); }; return( $sub ); } return; } sub __instantiate_object { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $o; my $callback; my $def = {}; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object" ) ) if( !length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } # try-catch local $@; eval { # https://stackoverflow.com/questions/32608504/how-to-check-if-perl-module-is-available#comment53081298_32608860 # require $class unless( defined( *{"${class}::"} ) ); # Either it passes and returns the class loaded or it raises an error trapped in catch my $rc = Class::Load::load_class( $class ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); if( defined( $callback ) ) { $o = $callback->( $class => [@_], ); } else { $o = scalar( @_ ) ? $class->new( @_ ) : $class->new; } }; if( $@ ) { return( $self->error({ code => 500, message => $@ }) ); } return( $self->error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) ); $o->debug( $this->{debug} ) if( $o->can( 'debug' ) ); return( $o ); } sub _can { my $self = shift( @_ ); no overloading; # Nothing provided return if( !scalar( @_ ) ); return if( !defined( $_[0] ) ); return if( !Scalar::Util::blessed( $_[0] ) ); if( $self->_is_array( $_[1] ) ) { foreach my $meth ( @{$_[1]} ) { return(0) unless( $_[0]->can( $meth ) ); } return(1); } else { return( $_[0]->can( $_[1] ) ); } } sub _get_args_as_array { my $self = shift( @_ ); return( [] ) if( !scalar( @_ ) ); my $ref = []; if( scalar( @_ ) == 1 && $self->_is_array( $_[0] ) ) { $ref = shift( @_ ); } else { $ref = [ @_ ]; } return( $ref ); } sub _get_args_as_hash { my $self = shift( @_ ); return( {} ) if( !scalar( @_ ) ); no warnings 'uninitialized'; my $ref = {}; my $order = $self->new_array; my $need_list = Want::want( 'LIST' ) ? 1 : 0; my $ok = {}; my $process = sub { my $this = shift( @_ ); # Check if among the parameters there is a special args_list one and its value is an array reference if( scalar( grep( !Scalar::Util::blessed( $_ ) && $_ eq 'args_list', @$this ) ) ) { for( my $i = 0; $i < scalar( @$this ); $i++ ) { if( defined( $this->[$i] ) && $this->[$i] eq 'args_list' && defined( $this->[$i+1] ) && ( Scalar::Util::reftype( $this->[$i+1] ) // '' ) eq 'ARRAY' ) { my $list = $this->[$i+1]; @$ok{ @$list } = (1) x scalar( @$list ); last; } } } # If we have a restricted list of parameters, obey it if( scalar( keys( %$ok ) ) ) { for( my $i = 0; $i < scalar( @$this ); $i++ ) { if( exists( $ok->{ $this->[$i] } ) ) { $ref->{ $this->[$i] } = $this->[$i+1]; $order->push( $this->[$i] ) if( $need_list ); splice( @$this, $i, 2 ); $i--; } } } # or, if we have simple a list of key-value pairs, take this and put it into an hash reference elsif( !( scalar( @$this ) % 2 ) ) { $ref = { @$this }; if( $need_list ) { for( my $i = 0; $i < scalar( @$this ); $i += 2 ) { $order->push( $this->[$i] ); } } } return( $ref, $order ); }; # A single hash reference was provided if( scalar( @_ ) == 1 && $self->_is_hash( $_[0] ) ) { $ref = shift( @_ ); $order = $self->new_array( [sort( keys( %$ref ) )] ) if( $need_list ); } elsif( scalar( @_ ) == 1 && ( Scalar::Util::reftype( $_[0] ) // '' ) eq 'ARRAY' || ( scalar( @_ ) == 3 && ( Scalar::Util::reftype( $_[0] ) // '' ) eq 'ARRAY' && defined( $_[1] ) && $_[1] eq 'args_list' && defined( $_[2] ) && ( Scalar::Util::reftype( $_[2] ) // '' ) eq 'ARRAY' ) ) { if( @_ > 1 ) { my $list = $_[2]; @$ok{ @$list } = (1) x scalar( @$list ); } ( $ref, $order ) = $process->( $_[0] ); } else { ( $ref, $order ) = $process->( \@_ ); } return( $need_list ? ( $ref, $order ) : $ref ); } # Call to the actual method doing the work # The reason for doing so is because _instantiate_object() may be inherited, but # _set_get_class or _set_get_hash_as_object created dynamic class which requires to call _instantiate_object # If _instantiate_object is inherited, it will yield unpredictable results sub _instantiate_object { return( shift->__instantiate_object( @_ ) ); } sub _get_stack_trace { my $self = shift( @_ ); my $opts = $self->_get_args_as_hash( @_ ); $opts->{skip_frames} //= 0; my $trace = Devel::StackTrace->new( skip_frames => ( 1 + $opts->{skip_frames} ), indent => 1 ); return( $trace ); } sub _is_a { my $self = shift( @_ ); my $obj = shift( @_ ); my $pkg = shift( @_ ); no overloading; return if( !$obj || !$pkg ); return if( !$self->_is_object( $obj ) ); if( $self->_is_array( $pkg ) ) { for( @$pkg ) { if( $_ !~ /^\w+(?:\:\:\w+)*$/ ) { warn( "Warning only: package name provided \"$_\" contains illegal characters.\n" ); } return(1) if( $obj->isa( $_ ) ); } return(0); } else { if( $pkg !~ /^\w+(?:\:\:\w+)*$/ ) { warn( "Warning only: package name provided \"$pkg\" contains illegal characters.\n" ); } return( $obj->isa( $pkg ) ); } } # UNIVERSAL::isa works for both array or array as objects # sub _is_array { return( UNIVERSAL::isa( $_[1], 'ARRAY' ) ); } sub _is_array { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); my $type = Scalar::Util::reftype( $_[1] ); return(0) if( !defined( $type ) ); return( $type eq 'ARRAY' ); } sub _is_class_loadable { my $self = shift( @_ ); my $class = shift( @_ ) || return(0); my $version = shift( @_ ); no strict 'refs'; my $file = File::Spec->catfile( split( /::/, $class ) ) . '.pm'; my $inc = File::Spec::Unix->catfile( split( /::/, $class ) ) . '.pm'; if( defined( $INC{ $inc } ) ) { if( defined( $version ) ) { my $alter_version = ${"${class}\::VERSION"}; # try-catch local $@; my $rv; eval { $rv = version->parse( $alter_version ) >= version->parse( $version ); }; if( $@ ) { return( $self->error( "An unexpected error occurred while trying to check if module \"$class\" with version '$version' is loadable: $@" ) ); } return( $rv ); } else { return(1); } } foreach my $dir ( @INC ) { my $fpath = File::Spec->catfile( $dir, $file ); next if( !-e( $fpath ) || !-r( $fpath ) || -z( $fpath ) ); if( defined( $version ) ) { my $info = Module::Metadata->new_from_file( $fpath ); my $alter_version = $info->version; # try-catch local $@; my $rv; eval { $rv = version->parse( $alter_version ) >= version->parse( $version ); }; if( $@ ) { return( $self->error( "An unexpected error occurred while trying to check if module \"$class\" with version '$version' is loadable: $@" ) ); } return( $rv ); } return(1); } return(0); } sub _is_class_loaded { my $self = shift( @_ ); my $class = shift( @_ ); if( $MOD_PERL ) { # https://perl.apache.org/docs/2.0/api/Apache2/Module.html#C_loaded_ my $rv = Apache2::Module::loaded( $class ); return(1) if( $rv ); } else { ( my $pm = $class ) =~ s{::}{/}gs; $pm .= '.pm'; return(1) if( CORE::exists( $INC{ $pm } ) ); } no strict 'refs'; my $ns = \%{ $class . '::' }; if( exists( $ns->{ISA} ) || exists( $ns->{BEGIN} ) || ( exists( $ns->{VERSION} ) && Scalar::Util::reftype( \$ns->{VERSION} ) eq 'GLOB' && defined( ${*{\$ns->{VERSION}}{SCALAR}} ) ) ) { return(1); } return(0); } sub _is_code { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); my $type = ref( $_[1] ); return(0) if( !defined( $type ) ); return( $type eq 'CODE' ); } sub _is_glob { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); my $type = Scalar::Util::reftype( $_[1] ); return(0) if( !defined( $type ) ); return( $type eq 'GLOB' ); } sub _is_hash { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); my $type; if( @_ > 2 && defined( $_[2] ) && $_[2] eq 'strict' ) { $type = ref( $_[1] ); } else { $type = Scalar::Util::reftype( $_[1] ); } return(0) if( !defined( $type ) ); return( $type eq 'HASH' ); } sub _is_integer { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) || !length( $_[1] ) ); return( $_[1] =~ /^[\+\-]?\d+$/ ? 1 : 0 ); } sub _is_ip { my $self = shift( @_ ); my $ip = shift( @_ ); return(0) if( !defined( $ip ) || !length( $ip ) ); # Already loaded unless( $RE{net}{IPv4} ) { $self->_load_class( 'Regexp::Common' ) || return( $self->pass_error ); Regexp::Common->import( 'net' ); } # We need to return either 1 or 0. By default, perl return undef for false # supports IPv4 and IPv6 in CIDR notation or not my $ip4or6 = qr/($RE{net}{IPv4}(\/(3[0-2]|[1-2][0-9]|[0-9]))?)|($RE{net}{IPv6}(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)/; return( $ip =~ /^$ip4or6$/ ? 1 : 0 ); } sub _is_number { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) || !length( $_[1] ) ); if( my $isok = Scalar::Util::looks_like_number( $_[1] ) ) { return(1); } # If the hexadecimal value is a string, it will return false, so we use regular expression. elsif( $_[1] =~ /^0x[0-9A-F]+$/i ) { return(1); } $_[0]->_load_class( 'Regexp::Common' ) || return( $_[0]->pass_error ); no warnings 'once'; return( $_[1] =~ /^$Regexp::Common::RE{num}{real}$/ ); } sub _is_object { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); return( Scalar::Util::blessed( $_[1] ) ); } sub _is_scalar { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) ); return( ( Scalar::Util::reftype( $_[1] ) // '' ) eq 'SCALAR' ); } sub _is_uuid { return(0) if( scalar( @_ < 2 ) ); return(0) if( !defined( $_[1] ) || !length( $_[1] ) ); return( $_[1] =~ /^[a-fA-F0-9]{8}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{4}\-[a-fA-F0-9]{12}$/ ? 1 : 0 ); } sub _is_warnings_enabled { return( shift->_warnings_is_enabled( @_ ) ); } sub _load_class { my $self = shift( @_ ); my $class = shift( @_ ) || return( $self->error( "No package name was provided to load." ) ); my $opts = {}; $opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' ); my $args = $self->_get_args_as_array( @_ ); # Get the caller's package so we load the module in context my $caller_class = $opts->{caller} || CORE::caller; # Return if already loaded if( $self->_is_class_loaded( $class ) ) { return( $class ); } my $pl = "package ${caller_class}; use $class"; $pl .= ' ' . $opts->{version} if( CORE::defined( $opts->{version} ) && CORE::length( $opts->{version} ) ); if( scalar( @$args ) ) { $pl .= ' qw( ' . CORE::join( ' ', @$args ) . ' );'; } elsif( $opts->{no_import} ) { $pl .= ' ();'; } local $SIG{__DIE__} = sub{}; local $@; eval( $pl ); return( $self->error( "Unable to load package ${class}: $@" ) ) if( $@ ); return( $self->_is_class_loaded( $class ) ? $class : '' ); } sub _load_classes { my $self = shift( @_ ); my $ref = shift( @_ ) || return( $self->error( "No array reference of classes to load was provided." ) ); return( $self->error( "Value provided is not an array reference." ) ) if( !$self->_is_array( $ref ) ); my $opts = $self->_get_args_as_hash( @_ ); for( @$ref ) { $self->_load_class( $_, $opts ) || return( $self->pass_error ); } return( $self ); } sub _lvalue : lvalue { return( shift->_set_get_callback( @_ ) ); } sub _message { my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no strict 'refs'; no warnings 'once'; if( $this->{verbose} || $this->{debug} || ${ $class . '::DEBUG' } || # Last parameter is an hash and there is a debug property ( scalar( @_ ) && ref( $_[-1] ) eq 'HASH' && CORE::exists( $_[-1]->{debug} ) && $_[-1]->{debug} ) ) { my $r; if( $MOD_PERL ) { # try-catch local $@; eval { $r = Apache2::RequestUtil->request; }; if( $@ ) { print( STDERR "Error trying to get the global Apache2::ApacheRec: $@\n" ); } } local $Module::Generic::TieHash::PAUSED = 1; my $ref; $ref = $self->_message_check( @_ ); return(1) if( !$ref ); my $opts = {}; # NOTE: make sure to update this if there is use of additional parameters if( ref( $ref->[-1] ) eq 'HASH' && scalar( grep( /^(caller_info|colour|color|level|message|no_encoding|no_exec|prefix|skip_frames|type)$/, keys( %{$ref->[-1]} ) ) ) ) { $opts = pop( @$ref ); } my $stackFrame = $self->_message_frame( (caller(1))[3] ) || 0; $stackFrame = 0 unless( $stackFrame =~ /^\d+$/ ); $stackFrame += int( $opts->{skip_frames} ) if( CORE::exists( $opts->{skip_frames} ) ); while( ( (caller( $stackFrame + 1 ))[3] // '' ) =~ /^Module::Generic::(?:_)?(messagef|message|messagec|message_color|message_colour|AUTOLOAD)/ ) { $stackFrame++; } my( $pkg, $file, $line, @otherInfo ) = caller( $stackFrame ); my $sub = ( caller( $stackFrame + 1 ) )[3] // ''; my $sub2 = substr( $sub, rindex( $sub, '::' ) + 2 ); if( ref( $this->{_message_frame} ) eq 'HASH' ) { if( CORE::exists( $this->{_message_frame}->{ $sub2 } ) ) { my $frameNo = int( $this->{_message_frame}->{ $sub2 } ); if( $frameNo > 0 ) { ( $pkg, $file, $line, $sub ) = caller( $frameNo ); $sub2 = substr( $sub, rindex( $sub, '::' ) + 2 ); } } } my $txt; if( $opts->{message} ) { if( ref( $opts->{message} ) eq 'ARRAY' ) { $txt = join( '', map( ( ref( $_ ) eq 'CODE' && !$this->{_msg_no_exec_sub} ) ? $_->() : ( $_ // '' ), @{$opts->{message}} ) ); } else { $txt = $opts->{message}; } } else { $txt = join( '', map( ( ref( $_ ) eq 'CODE' && !$this->{_msg_no_exec_sub} && !$opts->{no_exec} ) ? $_->() : ( $_ // '' ), @$ref ) ); } # Reset it $this->{_msg_no_exec_sub} = 0; # Process colours if needed if( $opts->{colour} || $opts->{color} ) { $txt = $self->colour_parse( $txt ); } my $prefix = CORE::length( $opts->{prefix} ) ? $opts->{prefix} : '##'; no overloading; $opts->{caller_info} = 1 if( !CORE::exists( $opts->{caller_info} ) || !CORE::length( $opts->{caller_info} ) ); my $proc_info = " [PID: $$]"; if( HAS_THREADS ) { my $tid = threads->tid; $proc_info .= ' -> [thread id ' . $tid . ']' if( $tid ); } my $mesg_raw = $opts->{caller_info} ? ( "${pkg}::${sub2}( $self ) [$line]${proc_info}: " . $txt ) : $txt; $mesg_raw =~ s/\n$//gs; my $mesg = "${prefix} " . join( "\n${prefix} ", split( /\n/, $mesg_raw ) ); my $info = { 'formatted' => $mesg, 'message' => $txt, 'file' => $file, 'line' => $line, 'package' => $class, 'sub' => $sub2, 'level' => ( $_[0] =~ /^\d+$/ ? $_[0] : CORE::exists( $opts->{level} ) ? $opts->{level} : 0 ), }; $info->{type} = $opts->{type} if( $opts->{type} ); ## If Mod perl is activated AND we are not using a private log if( $r && !${ "${class}::LOG_DEBUG" } ) { if( my $log_handler = $r->get_handlers( 'PerlPrivateLogHandler' ) ) { $log_handler->( $mesg_raw ); } elsif( $this->{_log_handler} && ref( $this->{_log_handler} ) eq 'CODE' ) { $this->{_log_handler}->( $info ); } else { $r->log->debug( $mesg_raw ); } } # Using ModPerl Server to log elsif( $MOD_PERL && !${ "${class}::LOG_DEBUG" } ) { require Apache2::ServerUtil; my $s = Apache2::ServerUtil->server; $s->log->debug( $mesg ); } # e.g. in our package, we could set the handler using the curry module like $self->{_log_handler} = $self->curry::log elsif( !-t( STDIN ) && $this->{_log_handler} && ref( $this->{_log_handler} ) eq 'CODE' ) { $this->{_log_handler}->( $info ); } elsif( !-t( STDIN ) && ${ $class . '::MESSAGE_HANDLER' } && ref( ${ $class . '::MESSAGE_HANDLER' } ) eq 'CODE' ) { my $h = ${ $class . '::MESSAGE_HANDLER' }; $h->( $info ); } # Or maybe then into a private log file? # This way, even if the log method is superseeded, we can keep using ours without interfering with the other one elsif( $self->_message_log( $mesg, "\n" ) ) { return(1); } # Otherwise just on the stderr else { if( $opts->{no_encoding} ) { $stderr_raw->print( $mesg, "\n" ); } else { $stderr->print( $mesg, "\n" ); } } } return(1); } sub _message_check { my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no warnings 'uninitialized'; no strict 'refs'; if( @_ ) { local $Module::Generic::TieHash::PAUSED = 1; if( $_[0] !~ /^\d/ ) { # The last parameter is an options parameter which has the level property set if( ref( $_[-1] ) eq 'HASH' && CORE::exists( $_[-1]->{level} ) ) { # Then let's use this } elsif( $this->{_message_default_level} =~ /^\d+$/ && $this->{_message_default_level} > 0 ) { unshift( @_, $this->{_message_default_level} ); } else { unshift( @_, 1 ); } } # If the first argument looks line a number, and there is more than 1 argument # and it is greater than 1, and greater than our current debug level # well, we do not output anything then... if( ( $_[0] =~ /^\d+$/ || ( ref( $_[-1] ) eq 'HASH' && CORE::exists( $_[-1]->{level} ) && $_[-1]->{level} =~ /^\d+$/ ) ) && @_ > 1 ) { my $message_level = 0; if( $_[0] =~ /^\d+$/ ) { $message_level = shift( @_ ); } elsif( ref( $_[-1] ) eq 'HASH' && CORE::exists( $_[-1]->{level} ) ) { $message_level = $_[-1]->{level}; } my $target_re = ''; if( ref( ${ "${class}::DEBUG_TARGET" } ) eq 'ARRAY' ) { $target_re = scalar( @${ "${class}::DEBUG_TARGET" } ) ? join( '|', @${ "${class}::DEBUG_TARGET" } ) : ''; } if( ( exists( $this->{debug} ) && int( $this->{debug} ) >= $message_level ) || ( exists( $this->{verbose} ) && int( $this->{verbose} ) >= $message_level ) || ( defined( ${ $class . '::DEBUG' } ) && ${ $class . '::DEBUG' } >= $message_level ) || ( ref( $_[-1] ) eq 'HASH' && CORE::exists( $_[-1]->{debug} ) && defined( $_[-1]->{debug} ) && $_[-1]->{debug} >= $message_level ) || ( exists( $this->{debug_level} ) && int( $this->{debug_level} ) >= $message_level ) || int( $this->{debug} ) >= 100 || ( length( $target_re ) && $class =~ /^$target_re$/ && ${ $class . '::GLOBAL_DEBUG' } >= $message_level ) ) { return( [ @_ ] ); } else { return(0); } } } return(0); } sub _message_frame { my $self = shift( @_ ); my $this = $self->_obj2h; $this->{_message_frame } = {} if( !exists( $this->{_message_frame} ) ); my $mf = $this->{_message_frame}; if( @_ ) { my $args = {}; if( ref( $_[0] ) eq 'HASH' ) { $args = shift( @_ ); my @k = keys( %$args ); @$mf{ @k } = @$args{ @k }; } elsif( !( @_ % 2 ) ) { $args = { @_ }; my @k = keys( %$args ); @$mf{ @k } = @$args{ @k }; } elsif( scalar( @_ ) == 1 ) { my $sub = shift( @_ ); $sub = substr( $sub, rindex( $sub, '::' ) + 2 ) if( index( $sub, '::' ) != -1 ); return( $mf->{ $sub } ); } else { return( $self->error( "I was expecting a key => value pair such as routine => stack frame (integer)" ) ); } } return( $mf ); } sub _message_log { my $self = shift( @_ ); my $io = $self->_message_log_io; return( undef() ) if( !$io ); return( undef() ) if( !Scalar::Util::openhandle( $io ) && $io ); # 2019-06-14: I decided to remove this test, because if a log is provided it should print to it # If we are on the command line, we can easily just do tail -f log_file.txt for example and get the same result as # if it were printed directly on the console my $rc = $io->print( scalar( localtime( time() ) ), " [$$]: ", @_ ) || return( $self->error( "Unable to print to log file: $!" ) ); return( $rc ); } sub _message_log_io { #return( shift->_set_get( 'log_io', @_ ) ); my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no strict 'refs'; if( @_ ) { my $io = shift( @_ ); $self->_set_get( 'log_io', $io ); } elsif( ${ "${class}::LOG_DEBUG" } && !$self->_set_get( 'log_io' ) && ${ "${class}::DEB_LOG" } ) { our $DEB_LOG = ${ "${class}::DEB_LOG" }; unless( $DEBUG_LOG_IO ) { require Module::Generic::File; $DEB_LOG = Module::Generic::File::file( $DEB_LOG ); $DEBUG_LOG_IO = $DEB_LOG->open( '>>', { binmode => 'utf-8', autoflush => 1 }) || die( "Unable to open debug log file $DEB_LOG in append mode: $!\n" ); } $self->_set_get( 'log_io', $DEBUG_LOG_IO ); } return( $self->_set_get( 'log_io' ) ); } sub _messagef { my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no strict 'refs'; if( $this->{verbose} || $this->{debug} || ${ $class . '::DEBUG' } ) { my $level = ( $_[0] =~ /^\d+$/ ? shift( @_ ) : undef() ); my $opts = {}; if( scalar( @_ ) > 1 && ref( $_[-1] ) eq 'HASH' && ( CORE::exists( $_[-1]->{level} ) || CORE::exists( $_[-1]->{type} ) || CORE::exists( $_[-1]->{message} ) || CORE::exists( $_[-1]->{colour} ) ) ) { $opts = pop( @_ ); } $level = $opts->{level} if( !defined( $level ) && CORE::exists( $opts->{level} ) ); my( $ref, $fmt ); if( $opts->{message} ) { if( ref( $opts->{message} ) eq 'ARRAY' ) { $ref = $opts->{message}; $fmt = shift( @$ref ); } else { $fmt = $opts->{message}; $ref = \@_; } } else { $ref = \@_; $fmt = shift( @$ref ); } my $txt = sprintf( $fmt, map( ( ref( $_ ) eq 'CODE' && !$this->{_msg_no_exec_sub} ) ? $_->() : $_, @$ref ) ); $txt = $self->colour_parse( $txt ) if( $opts->{colour} ); $opts->{message} = $txt; $opts->{level} = $level if( defined( $level ) ); return( $self->_message( ( $level || 0 ), $opts ) ); } return(1); } sub _obj2h { my $self = shift( @_ ); # The method that called message was itself called using the package name like My::Package->some_method # We are going to check if global $DEBUG or $VERBOSE variables are set and create the related debug and verbose entry into the hash we return no strict 'refs'; if( !ref( $self ) ) { my $class = $self; my $hash = { debug => ${ "${class}\::DEBUG" }, verbose => ${ "${class}\::VERBOSE" }, error => ${ "${class}\::ERROR" }, }; return( bless( $hash => $class ) ); } elsif( ( Scalar::Util::reftype( $self ) // '' ) eq 'HASH' ) { return( $self ); } elsif( ( Scalar::Util::reftype( $self ) // '' ) eq 'GLOB' ) { if( ref( *$self ) eq 'HASH' ) { return( *$self ); } else { return( \%{*$self} ); } } # Because object may be accessed as My::Package->method or My::Package::method # there is not always an object available, so we need to fake it to avoid error # This is primarly itended for generic methods error(), errstr() to work under any conditions. else { return( {} ); } } sub _refaddr { return( Scalar::Util::refaddr( $_[1] ) ); } sub _set_get { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; if( @_ ) { my $val = ( @_ == 1 ) ? shift( @_ ) : [ @_ ]; $data->{ $field } = $val; } if( wantarray() ) { if( ref( $data->{ $field } ) eq 'ARRAY' ) { return( @{ $data->{ $field } } ); } elsif( ref( $data->{ $field } ) eq 'HASH' ) { return( %{ $data->{ $field } } ); } else { return( ( $data->{ $field } ) ); } } else { return( $data->{ $field } ); } } sub _set_get_array { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; if( @_ ) { my $val = ( @_ == 1 && ( ( Scalar::Util::blessed( $_[0] ) && $_[0]->isa( 'ARRAY' ) ) || ref( $_[0] ) eq 'ARRAY' ) ) ? shift( @_ ) : [ @_ ]; if( !defined( $data->{ $field } ) && want( 'ARRAY' ) ) { # The call context is an array reference. # To avoid the perl of 'Not an ARRAY reference', we return an empty array return( [] ); } $data->{ $field } = $val; } return( $data->{ $field } ); } sub _set_get_array_as_object : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $callbacks = {}; my $def = {}; # If this is set to true, this method will return a list in list context $def->{wantlist} //= 0; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $callbacks = $def->{callbacks} if( CORE::exists( $def->{callbacks} ) && ref( $def->{callbacks} ) eq 'HASH' ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $ctx = $_; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); if( !$data->{ $field } || !$self->_is_object( $data->{ $field } ) ) { require Module::Generic::Array; my $o = Module::Generic::Array->new( ( defined( $data->{ $field } ) && CORE::length( $data->{ $field } ) ) ? $data->{ $field } : [] ); $data->{ $field } = $o; } if( $def->{wantlist} && $ctx->{list} ) { return( $data->{ $field } ? $data->{ $field }->list : () ); } else { return( $data->{ $field } ); } }, set => sub { my $self = shift( @_ ); my $ctx = $_; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); my $val = ( @_ == 1 && ( ( Scalar::Util::blessed( $_[0] ) && $_[0]->isa( 'ARRAY' ) ) || ref( $_[0] ) eq 'ARRAY' ) ) ? shift( @_ ) : [ @_ ]; require Module::Generic::Array; my $o = $data->{ $field }; # Some existing data, like maybe default value if( $o ) { if( !$self->_is_object( $o ) ) { my $tmp = $o; $o = Module::Generic::Array->new( $tmp ); } $o->set( $val ); } else { $o = Module::Generic::Array->new( $val ); $data->{ $field } = $o; if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } } if( !$data->{ $field } || !$self->_is_object( $data->{ $field } ) ) { require Module::Generic::Array; my $o = Module::Generic::Array->new( ( defined( $data->{ $field } ) && CORE::length( $data->{ $field } ) ) ? $data->{ $field } : [] ); $data->{ $field } = $o; } if( $def->{wantlist} && $ctx->{list} ) { return( $data->{ $field } ? $data->{ $field }->list : () ); } else { return( $data->{ $field } ); } }, field => $field, }, @_ ) ); } sub _set_get_boolean : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $callbacks = {}; my $def = {}; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $callbacks = $def->{callbacks} if( CORE::exists( $def->{callbacks} ) && ref( $def->{callbacks} ) eq 'HASH' ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); # If there is a value set, like a default value and it is not an object or at least not one we recognise # We transform it into a Module::Generic::Boolean object if( defined( $data->{ $field } ) && CORE::length( $data->{ $field } ) && ( !Scalar::Util::blessed( $data->{ $field } ) || ( Scalar::Util::blessed( $data->{ $field } ) && !$data->{ $field }->isa( 'Module::Generic::Boolean' ) && !$data->{ $field }->isa( 'JSON::PP::Boolean' ) ) ) ) { my $val = $data->{ $field }; $data->{ $field } = $val ? Module::Generic::Boolean->true : Module::Generic::Boolean->false; } elsif( defined( $data->{ $field } ) && CORE::length( $data->{ $field } ) && Scalar::Util::reftype( $data->{ $field } // '' ) eq 'SCALAR' ) { my $val = $data->{ $field }; $data->{ $field } = $$val ? Module::Generic::Boolean->true : Module::Generic::Boolean->false; } return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $val = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); $val //= ''; no warnings 'uninitialized'; if( Scalar::Util::blessed( $val ) && ( $val->isa( 'JSON::PP::Boolean' ) || $val->isa( 'Module::Generic::Boolean' ) ) ) { $data->{ $field } = $val; } elsif( ( Scalar::Util::reftype( $val ) // '' ) eq 'SCALAR' ) { $data->{ $field } = defined( $$val ) ? $$val ? Module::Generic::Boolean->true : Module::Generic::Boolean->false : Module::Generic::Boolean->false; } elsif( lc( $val ) eq 'true' || lc( $val ) eq 'false' ) { $data->{ $field } = lc( $val ) eq 'true' ? Module::Generic::Boolean->true : Module::Generic::Boolean->false; } else { $data->{ $field } = $val ? Module::Generic::Boolean->true : Module::Generic::Boolean->false; } if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } # If there is a value set, like a default value and it is not an object or at least not one we recognise # We transform it into a Module::Generic::Boolean object if( CORE::length( $data->{ $field } ) && ( !Scalar::Util::blessed( $data->{ $field } ) || ( Scalar::Util::blessed( $data->{ $field } ) && !$data->{ $field }->isa( 'Module::Generic::Boolean' ) && !$data->{ $field }->isa( 'JSON::PP::Boolean' ) ) ) ) { my $val = $data->{ $field }; $data->{ $field } = $val ? Module::Generic::Boolean->true : Module::Generic::Boolean->false; } return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_callback : lvalue { my $self = shift( @_ ); my $def = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my( $getter, $setter, $field ) = @$def{qw( get set field )}; if( defined( $getter ) ) { die( "Getter code value provided is actually not a code reference." ) if( ref( $getter ) ne 'CODE' ); } else { $getter = sub{}; } if( defined( $setter ) ) { die( "Setter code value provided is actually not a code reference." ) if( ref( $setter ) ne 'CODE' ); } else { $setter = sub{}; } die( "Field value specified is empty." ) if( defined( $field ) && !CORE::length( "$field" ) ); my $context = {}; my $args; my @rv; if( want( qw( LVALUE ASSIGN ) ) ) { $args = [want( 'ASSIGN' )]; $context->{assign}++; $context->{lvalue}++; } else { if( @_ ) { $args = [@_]; } if( want( 'LVALUE' ) ) { $context->{lvalue}++; } elsif( want( 'RVALUE' ) ) { $context->{rvalue}++; } my $expect = Want::want( 'LIST' ) ? 'LIST' : Want::want( 'HASH' ) ? 'HASH' : Want::want( 'ARRAY' ) ? 'ARRAY' : Want::want( 'OBJECT' ) ? 'OBJECT' : Want::want( 'CODE' ) ? 'CODE' : Want::want( 'REFSCALAR' ) ? 'REFSCALAR' : Want::want( 'BOOL' ) ? 'BOOLEAN' : Want::want( 'GLOB' ) ? 'GLOB' : Want::want( 'SCALAR' ) ? 'SCALAR' : Want::want( 'VOID' ) ? 'VOID' : ''; $context->{ lc( $expect ) }++ if( length( $expect ) ); $context->{count} = Want::want( 'COUNT' ); } $context->{eval} = $^S; if( CORE::defined( $args ) && scalar( @$args ) ) { # try-catch local $@; local $_ = $context; if( $context->{list} ) { eval{ @rv = $setter->( $self, @$args ) }; } else { eval{ $rv[0] = $setter->( $self, @$args ) }; } $self->error( $@ ) if( $@ ); if( ( !scalar( @rv ) || ( scalar( @rv ) == 1 && !defined( $rv[0] ) ) ) && ( my $has_error = $self->error ) ) { if( $context->{assign} ) { $data->{__lvalue_error} = undef; return( $data->{__lvalue_error} ); } else { return( $self->pass_error ); } } else { if( $context->{assign} ) { if( defined( $field ) ) { return( $data->{ $field } ); } else { return( $data->{__lvalue} = $rv[0] ); } } elsif( $context->{list} ) { return( @rv ); } elsif( $context->{lvalue} ) { if( !$self->_is_object( $rv[0] ) && $context->{object} ) { require Module::Generic::Null; return( Module::Generic::Null->new( wants => 'OBJECT' ) ) if( $context->{lvalue} ); } return( $rv[0] ); } else { if( !$self->_is_object( $rv[0] ) && $context->{object} ) { require Module::Generic::Null; rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) ); } rreturn( $rv[0] ); } return; } } # try-catch local $@; local $_ = $context; if( $context->{list} ) { eval{ @rv = $getter->( $self ) }; } else { eval{ $rv[0] = $getter->( $self ) }; } $self->error( $@ ) if( $@ ); if( !scalar( @rv ) && ( my $has_error = $self->error ) ) { if( $context->{rvalue} ) { if( $context->{object} ) { require Module::Generic::Null; rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) ); } rreturn; } else { if( $context->{object} ) { require Module::Generic::Null; return( Module::Generic::Null->new( wants => 'OBJECT' ) ); } return; } } else { if( $context->{rvalue} ) { if( $context->{list} ) { rreturn( @rv ); } else { if( !$self->_is_object( $rv[0] ) && $context->{object} ) { require Module::Generic::Null; rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) ); } rreturn( $rv[0] ); } } else { if( $context->{list} ) { return( @rv ); } else { if( !$self->_is_object( $rv[0] ) && $context->{object} ) { require Module::Generic::Null; return( Module::Generic::Null->new( wants => 'OBJECT' ) ) if( $context->{lvalue} ); rreturn( Module::Generic::Null->new( wants => 'OBJECT' ) ); } return( $rv[0] ) if( $context->{lvalue} ); rreturn( $rv[0] ); } } } return; } # $self->_set_get_class( 'my_field', { # _class => 'My::Class', # field1 => { type => 'datetime' }, # field2 => { type => 'scalar' }, # field3 => { type => 'boolean' }, # field4 => { type => 'object', class => 'Some::Class' }, # }, @_ ); sub _set_get_class { my $self = shift( @_ ); my $field = shift( @_ ); my $def = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); if( ref( $def ) ne 'HASH' ) { CORE::warn( "Warning only: dynamic class field definition hash ($def) for field \"$field\" is not a hash reference." ); return; } my $class = $self->__create_class( $field, $def ) || die( "Failed to create the dynamic class for field \"$field\".\n" ); if( @_ ) { my $hash = shift( @_ ); $hash->{debug} = $self->debug if( ref( $hash ) eq 'HASH' && !exists( $hash->{debug} ) ); # my $o = $class->new( $hash ); # my $o = $self->__instantiate_object( $field, $class, ( %$hash, debug => $self->debug ) ); my $o = $self->__instantiate_object( $field, $class, $hash ); $data->{ $field } = $o; } if( !$data->{ $field } ) { my $o = $self->__instantiate_object( $field, $class ); $o->debug( $self->debug ) if( $o && $o->can( 'debug' ) ); $data->{ $field } = $o; } return( $data->{ $field } ); } sub _set_get_class_array { my $self = shift( @_ ); my $field = shift( @_ ); my $def = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; if( ref( $def ) ne 'HASH' ) { CORE::warn( "Warning only: dynamic class field definition hash ($def) for field \"$field\" is not a hash reference." ); return; } @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $class = $self->__create_class( $field, $def ) || die( "Failed to create the dynamic class for field \"$field\".\n" ); # return( $self->_set_get_object_array( $field, $class, @_ ) ); if( @_ ) { my $ref = shift( @_ ); return( $self->error( "I was expecting an array ref, but instead got '$ref'. _is_array returned: '", $self->_is_array( $ref ), "'" ) ) if( !$self->_is_array( $ref ) ); my $arr = []; for( my $i = 0; $i < scalar( @$ref ); $i++ ) { if( ref( $ref->[$i] ) ne 'HASH' ) { return( $self->error( "Array offset $i is not a hash reference. I was expecting a hash reference to instantiate an object of class $class." ) ); } my $o = $self->__instantiate_object( $field, $class, $ref->[$i] ) || return( $self->pass_error ); # If an error occurred, we report it to the caller and do not add it, since even if we did add it, it would be undef, because no object would have been created. # And the caller needs to know there has been some errors CORE::push( @$arr, $o ); } $data->{ $field } = $arr; } return( $data->{ $field } ); } sub _set_get_code : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $opts = {}; if( ref( $field ) eq 'HASH' ) { $opts = $field; if( CORE::exists( $opts->{field} ) && defined( $opts->{field} ) && CORE::length( $opts->{field} ) ) { $field = $opts->{field}; } else { $field = undef; } } $opts->{undef_ok} //= 0; $opts->{return_undef} //= 0; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $ctx = $_; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); if( ( !defined( $data->{ $field } ) || !$data->{ $field } ) && !$opts->{return_undef} && $ctx->{object} ) { return( sub{} ); } return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); my $ctx = $_; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); my $v = $arg; if( ( ( $opts->{undef_ok} && defined( $v ) ) || !$opts->{undef_ok} ) && ref( $v ) ne 'CODE' ) { return( $self->error( "Value provided for \"$field\" ($v) is not an anonymous subroutine (code). You can pass as argument something like \$self->curry::my_sub or something like sub { some_code_here; }" ) ); } $data->{ $field } = $v; if( ( !defined( $data->{ $field } ) || !$data->{ $field } ) && !$opts->{return_undef} && $ctx->{object} ) { return( sub{} ); } return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_file : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); no overloading; my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); if( $data->{ $field } && !$self->_is_a( $data->{ $field } => 'Module::Generic::File' ) ) { require Module::Generic::File; $data->{ $field } = Module::Generic::File->new( $data->{ $field } . '' ) || return( $self->error( Module::Generic::File->error ) ); } return( $data->{ $field } ) }, set => sub { my $self = shift( @_ ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $arg = shift( @_ ); require Module::Generic::File; my $val; if( $self->_is_a( $arg => 'Module::Generic::File' ) ) { $val = $arg; } elsif( defined( $arg ) ) { $val = Module::Generic::File->new( $arg ) || return( $self->pass_error( Module::Generic::File->error ) ); } return( $data->{ $field } = $val ); }, field => $field, }, @_ ) ); } sub _set_get_glob : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $arg = shift( @_ ); if( defined( $arg ) && Scalar::Util::reftype( $arg ) ne 'GLOB' ) { return( $self->error( "Method $field takes only a glob, but value provided ($arg) is not supported" ) ); } return( $data->{ $field } = $arg ); }, field => $field, }, @_ ) ); } sub _set_get_hash : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $arg; if( @_ ) { if( ref( $_[0] ) eq 'HASH' ) { $arg = shift( @_ ); } elsif( !( @_ % 2 ) ) { $arg = { @_ }; } else { $arg = shift( @_ ); } } if( defined( $arg ) && ref( $arg ) ne 'HASH' ) { return( $self->error( "Method $field takes only a hash or reference to a hash, but value provided ($arg) is not supported" ) ); } return( $data->{ $field } = $arg ); }, field => $field, }, @_ ) ); } sub _set_get_hash_as_mix_object : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $opts = {}; if( ref( $field ) eq 'HASH' ) { $opts = $field; if( CORE::exists( $opts->{field} ) && defined( $opts->{field} ) && CORE::length( $opts->{field} ) ) { $field = $opts->{field}; } else { $field = undef; } } $opts->{undef_ok} //= 0; $opts->{return_undef} //= 0; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $ctx = $_; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); if( !defined( $data->{ $field } ) ) { # If the call context is either an hash or an object, we instantiate an empty object, and return it, # but we do not affect the current property value of our object if( $ctx->{object} || $ctx->{hash} ) { require Module::Generic::Hash; local $Module::Generic::Hash::DEBUG = $self->debug; my $o = Module::Generic::Hash->new( $data->{ $field } ); return( $o ); } return; } elsif( $data->{ $field } && !$self->_is_object( $data->{ $field } ) ) { require Module::Generic::Hash; local $Module::Generic::Hash::DEBUG = $self->debug; my $o = Module::Generic::Hash->new( $data->{ $field } ); $data->{ $field } = $o; } return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $ctx = $_; my $arg; return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) && !$opts->{undef_ok} ); if( @_ ) { if( ref( $_[0] ) eq 'HASH' ) { $arg = shift( @_ ); } elsif( ref( $_[0] ) eq 'Module::Generic::Hash' ) { $arg = $_[0]->clone; } elsif( ( @_ % 2 ) ) { $arg = { @_ }; } elsif( !defined( $_[0] ) && $opts->{undef_ok} ) { $arg = undef; } else { $arg = shift( @_ ); return( $self->error( "Method $field takes only a hash or reference to a hash, but value provided ($arg) is not supported" ) ); } } my $val = $arg; if( !defined( $val ) ) { $data->{ $field } = undef; } elsif( ref( $val ) eq 'Module::Generic::Hash' ) { return( $data->{ $field } = $val ); } else { require Module::Generic::Hash; local $Module::Generic::Hash::DEBUG = $self->debug; $data->{ $field } = Module::Generic::Hash->new( $val ); } if( !defined( $data->{ $field } ) ) { # If the call context is either an hash or an object, we instantiate an empty object, and return it, # but we do not affect the current property value of our object if( $ctx->{object} || $ctx->{hash} ) { require Module::Generic::Hash; local $Module::Generic::Hash::DEBUG = $self->debug; my $o = Module::Generic::Hash->new( $data->{ $field } ); return( $o ); } return; } elsif( $data->{ $field } && !$self->_is_object( $data->{ $field } ) ) { require Module::Generic::Hash; local $Module::Generic::Hash::DEBUG = $self->debug; my $o = Module::Generic::Hash->new( $data->{ $field } ); $data->{ $field } = $o; } return( $data->{ $field } ); }, field => $field, }, @_ ) ); } # There is no lvalue here on purpose sub _set_get_hash_as_object { my $self = shift( @_ ); my $this = $self->_obj2h; my $field = shift( @_ ) || return( $self->error( "No field provided for _set_get_hash_as_object" ) ); my $class; @_ = () if( @_ == 1 && !defined( $_[0] ) ); no strict 'refs'; if( @_ ) { # No class was provided if( ( Scalar::Util::reftype( $_[0] ) // '' ) eq 'HASH' ) { my $new_class = $field; $new_class =~ tr/-/_/; $new_class =~ s/\_{2,}/_/g; $new_class = join( '', map( ucfirst( lc( $_ ) ), split( /\_/, $new_class ) ) ); $class = ( ref( $self ) || $self ) . "\::${new_class}"; } elsif( ref( $_[0] ) ) { return( $self->error( "Class name in _set_get_hash_as_object helper method cannot be a reference. Received: \"", overload::StrVal( $_[0] // 'undef' ), "\"." ) ); } elsif( CORE::length( $_[0] // '' ) ) { $class = shift( @_ ); } } else { my $new_class = $field; $new_class =~ tr/-/_/; $new_class =~ s/\_{2,}/_/g; $new_class = join( '', map( ucfirst( lc( $_ ) ), split( /\_/, $new_class ) ) ); $class = ( ref( $self ) || $self ) . "\::${new_class}"; } # my $class = shift( @_ ); my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; unless( Class::Load::is_class_loaded( $class ) ) { my $perl .= <debug unless( CORE::exists( $hash->{debug} ) ); my $o = $self->__instantiate_object( $field, $class, $hash ) || return( $self->pass_error ); $data->{ $field } = $o; } if( !$data->{ $field } || !$self->_is_object( $data->{ $field } ) ) { my $o = $data->{ $field } = $self->__instantiate_object( $field, $class, $data->{ $field } ); } return( $data->{ $field } ); } sub _set_get_ip : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $v = $self->_is_a( $data->{ $field }, 'Module::Generic::Scalar' ) ? $data->{ $field } : $self->new_scalar( $data->{ $field } ); if( !$v->defined ) { return; } else { return( $v ); } }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); my $ctx = $_; my $v = $arg; # If the user wants to remove it if( !defined( $v ) ) { $data->{ $field } = $v; } # If the user provided a string, let's check it elsif( length( $v ) && !$self->_is_ip( $v ) ) { return( $self->error( "Value provided ($v) is not a valid ip address." ) ); } $data->{ $field } = $self->new_scalar( $v ); $v = $self->_is_a( $data->{ $field }, 'Module::Generic::Scalar' ) ? $data->{ $field } : $self->new_scalar( $data->{ $field } ); if( !$v->defined ) { return; } else { return( $v ); } }, field => $field, }, @_ ) ); } sub _set_get_lvalue : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $val = shift( @_ ); $data->{ $field } = $val; # lnoreturn; return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_number : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; no overload; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $opts = {}; my $callbacks = {}; if( ref( $field ) eq 'HASH' ) { $opts = $field; if( CORE::exists( $opts->{field} ) && defined( $opts->{field} ) && CORE::length( $opts->{field} ) ) { $field = $opts->{field}; } else { $field = undef; } $callbacks = $opts->{callbacks} if( CORE::exists( $opts->{callbacks} ) && ref( $opts->{callbacks} ) eq 'HASH' ); } # $opts->{undef_ok} //= 0; my $do_callback = sub { if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } }; require Module::Generic::Number; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); if( CORE::length( $data->{ $field } ) && !ref( $data->{ $field } ) ) { my $v = Module::Generic::Number->new( $data->{ $field } ); $data->{ $field } = $v if( defined( $v ) ); } return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); if( ( !defined( $_[0] ) || !scalar( @_ ) ) ) { if( CORE::exists( $opts->{undef_ok} ) && !$opts->{undef_ok} ) { return( $self->error( "Number provided is undef, which is not permitted for '${field}'" ) ); } else { $data->{ $field } = shift( @_ ); } } else { my $v = Module::Generic::Number->new( shift( @_ ) ); return( $self->pass_error( Module::Generic::Number->error ) ) if( !defined( $v ) ); $data->{ $field } = $v; } $do_callback->(); if( CORE::length( $data->{ $field } // '' ) && !ref( $data->{ $field } ) ) { my $v = Module::Generic::Number->new( $data->{ $field } ); $data->{ $field } = $v if( defined( $v ) ); } return( $data->{ $field } ); }, field => $field }, @_ ) ); } sub _set_get_number_as_object : lvalue { return( shift->_set_get_number( @_ ) ); } sub _set_get_number_as_scalar : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; no overload; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $callbacks = {}; my $def = {}; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $callbacks = $def->{callbacks} if( CORE::exists( $def->{callbacks} ) && ref( $def->{callbacks} ) eq 'HASH' ); } my $do_callback = sub { if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } }; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); my $v = $arg; require Regexp::Common; Regexp::Common->import( 'number' ); # If the user wants to remove it if( defined( $v ) && $v !~ /^$RE{num}{real}$/ ) { return( $self->error( "Method $field takes only a number, but value provided ($arg) is not a number" ) ); } $data->{ $field } = $v; $do_callback->(); return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_number_or_object { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; if( @_ ) { if( ref( $_[0] ) eq 'HASH' || Scalar::Util::blessed( $_[0] ) ) { return( $self->_set_get_object( $field, $class, @_ ) ); } else { return( $self->_set_get_number( $field, @_ ) ); } } if( !CORE::length( $data->{ $field } // '' ) && want( 'OBJECT' ) ) { require Module::Generic::Null; return( Module::Generic::Null->new( wants => 'OBJECT' ) ); } return( $data->{ $field } ); } sub _set_get_object { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; no overloading; my $def = {}; # no_init my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object" ) ) if( !length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } # Parameters are provided to instantiate the object if( @_ ) { if( scalar( @_ ) == 1 ) { # User removed the value by passing it an undefined value if( !defined( $_[0] ) ) { $data->{ $field } = undef(); } # User pass an object elsif( Scalar::Util::blessed( $_[0] ) ) { my $o = shift( @_ ); if( ref( $class ) eq 'ARRAY' ) { my $ok = 0; foreach my $c ( @$class ) { if( $o->isa( $c ) ) { $ok++, last; } } return( $self->error( "Object provided (", ref( $o ), ") for $field does not match any of the possible classes: '", join( "', '", @$class ), "'." ) ) if( !$ok ); } else { return( $self->error( "Object provided (", ref( $o ), ") for $field is not a valid $class object" ) ) if( !$o->isa( "$class" ) ); } $data->{ $field } = $o; } else { $class = $class->[0] if( ref( $class ) eq 'ARRAY' ); my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, @_ ) || do { if( $class->can( 'error' ) ) { return( $self->pass_error( $class->error ) ); } else { return( $self->error( "Unable to instantiate an object for class \"$class\" and values provided: '", join( "', '", @_ ), "'." ) ); } }; $data->{ $field } = $o; } } elsif( $def->{no_init} && !$data->{ $field } ) { # We do nothing } else { $class = $class->[0] if( ref( $class ) eq 'ARRAY' ); # There is already an object, so we pass any argument to the existing object if( $data->{ $field } && $self->_is_a( $data->{ $field }, $class ) ) { warn( "Re-setting existing object '", overload::StrVal( $data->{ $field } // 'undef' ), "' for field '$field' and class '$class'\n" ); } my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, @_ ) || do { if( $class->can( 'error' ) ) { return( $self->pass_error( $class->error ) ); } else { return( $self->error( "Unable to instantiate an object for class \"$class\" with no value provided." ) ); } }; $data->{ $field } = $o; } } # If nothing has been set for this field, ie no object, but we are called in chain # we set a dummy object that will just call itself to avoid perl complaining about undefined value calling a method if( !$data->{ $field } && want( 'OBJECT' ) ) { if( $def->{no_init} ) { require Module::Generic::Null; my $null = Module::Generic::Null->new( '', { debug => $this->{debug} }); return( $null ); } else { $class = $class->[0] if( ref( $class ) eq 'ARRAY' ); my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, @_ ) || do { if( $class->can( 'error' ) ) { return( $self->pass_error( $class->error ) ); } else { return( $self->error( "Unable to instantiate an object for class \"$class\" with no value provided." ) ); } }; $data->{ $field } = $o; return( $o ); } } return( $data->{ $field } ); } sub _set_get_object_lvalue : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); no overloading; my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); my $ctx = $_; if( !defined( $arg ) ) { $data->{ $field } = undef(); } # User pass an object elsif( Scalar::Util::blessed( $arg ) ) { if( ref( $class ) eq 'ARRAY' ) { my $ok = 0; foreach my $c ( @$class ) { if( $arg->isa( $c ) ) { $ok++, last; } } return( $self->error( "Object provided (" . ref( $arg ) . ") for $field does not match any of the possible classes: '" . join( "', '", @$class ) . "'." ) ); } else { if( !$arg->isa( "$class" ) ) { return( $self->error( "Object provided (" . ref( $arg ) . ") for $field is not a valid $class object" ) ); } } $data->{ $field } = $arg; } else { return( $self->error( "Value provided (" . overload::StrVal( $arg // '' ) . " is not an object." ) ); } # We need to return something else than our object, or by virtue of perl's way of working # we would return our object as coded below, and that object will be assigned the # very value we will have passed in assignment ! return( $data->{__dummy} = 'dummy' ) if( $ctx->{assign} ); return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_object_without_init { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; no overloading; my $def = {}; my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object" ) ) if( !length( $field // '' ) ); # Callback used to instantiate the object if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } if( @_ ) { if( scalar( @_ ) == 1 ) { # User removed the value by passing it an undefined value if( !defined( $_[0] ) ) { $data->{ $field } = undef(); } # User pass an object elsif( Scalar::Util::blessed( $_[0] ) ) { my $o = shift( @_ ); if( ref( $class ) eq 'ARRAY' ) { my $ok = 0; foreach my $c ( @$class ) { if( $o->isa( $c ) ) { $ok++, last; } } return( $self->error( "Object provided (", ref( $o ), ") for $field does not match any of the possible classes: '", join( "', '", @$class ), "'." ) ) if( !$ok ); } else { return( $self->error( "Object provided (", ref( $o ), ") for $field is not a valid $class object" ) ) if( !$o->isa( "$class" ) ); } $data->{ $field } = $o; } else { # return( $self->error( "Only undef or an ", ( ref( $class ) eq 'ARRAY' ? join( ', ', @$class ) : $class ), " object can be provided." ) ); my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, @_ ) || do { if( $class->can( 'error' ) ) { return( $self->pass_error( $class->error ) ); } else { return( $self->error( "Unable to instantiate an object for class \"$class\" and values provided: '", join( "', '", @_ ), "'." ) ); } }; $data->{ $field } = $o; } } else { # return( $self->error( "Only undef or an ", ( ref( $class ) eq 'ARRAY' ? join( ', ', @$class ) : $class ), " object can be provided." ) ); my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, @_ ) || do { if( $class->can( 'error' ) ) { return( $self->pass_error( $class->error ) ); } else { return( $self->error( "Unable to instantiate an object for class \"$class\" with no value provided." ) ); } }; $data->{ $field } = $o; } } # If nothing has been set for this field, ie no object, but we are called in chain, this will fail on purpose. # To avoid this, use _set_get_object return( $data->{ $field } ); } sub _set_get_object_array2 { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $def = {}; my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object" ) ) if( !length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } if( @_ ) { my $data_to_process = shift( @_ ); return( $self->error( "I was expecting an array ref, but instead got '$data_to_process'. _is_array returned: '", $self->_is_array( $data_to_process ), "'" ) ) if( !$self->_is_array( $data_to_process ) ); my $arr1 = []; foreach my $ref ( @$data_to_process ) { return( $self->error( "I was expecting an embeded array ref, but instead got '$ref'." ) ) if( ref( $ref ) ne 'ARRAY' ); my $arr = []; for( my $i = 0; $i < scalar( @$ref ); $i++ ) { my $o; if( defined( $ref->[$i] ) ) { return( $self->error( "Parameter provided for adding object of class $class is not a reference." ) ) if( !ref( $ref->[$i] ) ); if( Scalar::Util::blessed( $ref->[$i] ) ) { return( $self->error( "Array offset $i contains an object from class ", $ref->[$i], ", but was expecting an object of class $class." ) ) if( !$ref->[$i]->isa( $class ) ); $o = $ref->[$i]; } elsif( ref( $ref->[$i] ) eq 'HASH' ) { #$o = $class->new( $h, $ref->[$i] ); $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, $ref->[$i] ); } else { $self->error( "Warning only: data provided to instaantiate object of class $class is not a hash reference" ); } } else { #$o = $class->new( $h ); $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class ); } return( $self->error( "Unable to instantiate an object of class $class: ", $class->error ) ) if( !defined( $o ) ); # $o->{_parent} = $self->{_parent}; push( @$arr, $o ); } push( @$arr1, $arr ); } $data->{ $field } = $arr1; } return( $data->{ $field } ); } sub _set_get_object_array { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); my $def = {}; my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object_array" ) ) if( !CORE::length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } $def->{empty_ok} //= 0; $def->{skip_empty} //= 0; my $process = sub { my $ref = shift( @_ ); return( $self->error( "I was expecting an array ref, but instead got '$ref'. _is_array returned: '", $self->_is_array( $ref ), "'" ) ) if( !$self->_is_array( $ref ) ); my $arr = []; for( my $i = 0; $i < scalar( @$ref ); $i++ ) { if( defined( $ref->[$i] ) || $def->{empty_ok} ) { # return( $self->error( "Array offset $i is not a reference. I was expecting an object of class $class or an hash reference to instantiate an object." ) ) if( !ref( $ref->[$i] ) ); if( Scalar::Util::blessed( $ref->[$i] ) ) { return( $self->error( "Array offset $i contains an object from class ", $ref->[$i], ", but was expecting an object of class $class." ) ) if( !$ref->[$i]->isa( $class ) ); push( @$arr, $ref->[$i] ); } elsif( $self->_is_empty( $ref->[$i] ) && $def->{skip_empty} ) { next; } else { my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, $ref->[$i] ) || return( $self->pass_error ); push( @$arr, $o ); } } elsif( $def->{skip_empty} ) { next; } else { return( $self->error( "Array offset $i contains an undefined value. I was expecting an object of class $class." ) ); # my $o = $self->_instantiate_object( $field, $class ) || return( $self->pass_error ); # push( @$arr, $o ); } } return( $arr ); }; if( @_ ) { $data->{ $field } = $process->( @_ ); } # For example, if the object property is set at init, without using a method if( $data->{ $field } && ref( $data->{ $field } ) ne 'ARRAY' ) { $data->{ $field } = $process->( $data->{ $field } ); } return( $data->{ $field } ); } sub _set_get_object_array_object { my $self = shift( @_ ); my $field = shift( @_ ) || return( $self->error( "No field name was provided for this array of object." ) ); my $class = shift( @_ ) || return( $self->error( "No class was provided for this array of objects." ) ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; @_ = () if( scalar( @_ ) == 1 && !defined( $_[0] ) ); require Module::Generic::Array; my $def = {}; my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object_array" ) ) if( !CORE::length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } else { $def = { field => $field }; } my $process = sub { my $that = ( scalar( @_ ) == 1 && UNIVERSAL::isa( $_[0], 'ARRAY' ) ) ? shift( @_ ) : [ @_ ]; my $ref = $self->_set_get_object_array( $def, $class, $that ) || return( $self->pass_error ); return( Module::Generic::Array->new( $ref ) ); }; if( @_ ) { $data->{ $field } = $process->( @_ ); } ## Default value so that call to the caller's method like my_sub->length will not produce something like "Can't call method "length" on an undefined value" ## Also, this will make it possible to set default value in caller's object and we would turn it into array object. if( !$data->{ $field } || !$self->_is_a( $data->{ $field }, 'Module::Generic::Array' ) ) { $data->{ $field } = $process->( CORE::defined( $data->{ $field } ) ? $data->{ $field } : () ); } return( $data->{ $field } ); } sub _set_get_object_variant { my $self = shift( @_ ); my $field = shift( @_ ); # The class precisely depends on what we find looking ahead my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $def = {}; my $callback; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } return( $self->error( "No property 'field' was provided in the parameters of _set_get_object_array" ) ) if( !CORE::length( $field // '' ) ); if( CORE::exists( $def->{callback} ) && defined( $def->{callback} ) && ref( $def->{callback} ) eq 'CODE' ) { $callback = $def->{callback}; } } my $process = sub { if( ref( $_[0] ) eq 'HASH' ) { my $o = $self->_instantiate_object( $field, $class, @_ ); return( $o ); } # An array of objects hash elsif( ref( $_[0] ) eq 'ARRAY' ) { my $arr = shift( @_ ); my $res = []; foreach my $data ( @$arr ) { my $o = $self->_instantiate_object( { field => $field, ( defined( $callback ) ? ( callback => $callback ) : () ) }, $class, $data ) || return( $self->error( "Unable to create object: ", $self->error ) ); push( @$res, $o ); } return( $res ); } }; if( @_ ) { $data->{ $field } = $process->( @_ ); } return( $data->{ $field } ); } sub _set_get_scalar : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $callbacks = {}; my $def = {}; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $callbacks = $def->{callbacks} if( CORE::exists( $def->{callbacks} ) && ref( $def->{callbacks} ) eq 'HASH' ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $v = $data->{ $field }; # If we have a callback, call it and get the resulting value if( scalar( keys( %$callbacks ) ) && CORE::exists( $callbacks->{get} ) && ref( $callbacks->{get} ) eq 'CODE' ) { $v = $callbacks->{get}->( $self, $v ); } return( $v ); }, set => sub { my $self = shift( @_ ); my $val = ( @_ == 1 ) ? shift( @_ ) : join( '', @_ ); # Just in case, we force stringification # $val = "$val" if( defined( $val ) ); if( ref( $val ) eq 'HASH' || ref( $val ) eq 'ARRAY' ) { return( $self->error( "Method $field takes only a scalar, but value provided ($val) is a reference" ) ); } # return( $data->{ $field } = $val ); $data->{ $field } = $val; if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } return( $data->{ $field } ); }, field => $field, }, @_ ) ); } sub _set_get_scalar_as_object : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $callbacks = {}; my $def = {}; if( ref( $field ) eq 'HASH' ) { $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $callbacks = $def->{callbacks} if( CORE::exists( $def->{callbacks} ) && ref( $def->{callbacks} ) eq 'HASH' ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); my $ctx = $_; if( !$self->_is_object( $data->{ $field } ) || ( $self->_is_object( $data->{ $field } ) && ref( $data->{ $field } ) ne ref( $self ) ) ) { require Module::Generic::Scalar; $data->{ $field } = Module::Generic::Scalar->new( $data->{ $field } ); } my $v = $data->{ $field }; # If we have a callback, call it and get the resulting value if( scalar( keys( %$callbacks ) ) && CORE::exists( $callbacks->{get} ) && ref( $callbacks->{get} ) eq 'CODE' ) { $v = $callbacks->{get}->( $self, $v ); } if( !CORE::defined( $v ) || !$v->defined ) { # 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' ) ); if( $ctx->{object} && CORE::defined( $v ) ) { return( $v ); } else { return; } } else { return( $v ); } }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); my $ctx = $_; my $val; if( defined( $arg ) && ( ref( $arg ) eq 'SCALAR' || UNIVERSAL::isa( $arg, 'SCALAR' ) ) ) { $val = $$arg; } elsif( defined( $arg ) && ref( $arg ) && $self->_is_object( $arg ) && overload::Overloaded( $arg ) && overload::Method( $arg, '""' ) ) { no warnings 'uninitialized'; $val = "$arg"; } elsif( defined( $arg ) && ref( $arg ) ) { return( $self->error( "I was expecting a string or a scalar reference, but instead got '$arg'" ) ); } else { $val = $arg; } my $o = $data->{ $field }; if( ref( $o ) ) { $o->set( $val ); } else { require Module::Generic::Scalar; $data->{ $field } = Module::Generic::Scalar->new( $val ); } if( scalar( keys( %$callbacks ) ) && ( CORE::exists( $callbacks->{add} ) || CORE::exists( $callbacks->{set} ) ) ) { my $coderef; foreach my $t ( qw( add set ) ) { if( CORE::exists( $callbacks->{ $t } ) ) { $coderef = ref( $callbacks->{ $t } ) eq 'CODE' ? $callbacks->{ $t } : $self->can( $callbacks->{ $t } ); last if( defined( $coderef ) ); } } if( defined( $coderef ) && ref( $coderef ) eq 'CODE' ) { $coderef->( $self, $data->{ $field } ); } } if( !$self->_is_object( $data->{ $field } ) || ( $self->_is_object( $data->{ $field } ) && ref( $data->{ $field } ) ne ref( $self ) ) ) { require Module::Generic::Scalar; $data->{ $field } = Module::Generic::Scalar->new( $data->{ $field } ); } my $v = $data->{ $field }; if( !$v->defined ) { # 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' ) ); if( $ctx->{object} ) { return( $v ); } else { return; } } else { return( $v ); } }, field => $field, }, @_ ) ); } sub _set_get_scalar_or_object { my $self = shift( @_ ); my $field = shift( @_ ); my $class = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; if( @_ ) { if( ref( $_[0] ) eq 'HASH' || Scalar::Util::blessed( $_[0] ) ) { return( $self->_set_get_object( $field, $class, @_ ) ); } else { return( $self->_set_get_scalar( $field, @_ ) ); } } if( !$data->{ $field } && want( 'OBJECT' ) ) { require Module::Generic::Null; my $null = Module::Generic::Null->new({ debug => $this->{debug}, has_error => 1 }); rreturn( $null ); } return( $data->{ $field } ); } sub _set_get_uri : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $uri_class = 'URI'; if( ref( $field ) eq 'HASH' ) { my $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $uri_class = $def->{class} if( CORE::exists( $def->{class} ) && ref( $def->{class} ) eq 'HASH' ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); $self->_load_class( $uri_class ) || return( $self->pass_error ); # Data was pre-set or directly set but is not an URI object, so we convert it now if( $data->{ $field } && !$self->_is_a( $data->{ $field }, $uri_class ) ) { # Force stringification if this is an overloaded value $data->{ $field } = $uri_class->new( $data->{ $field } . '' ); } return( $data->{ $field } ); }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); $self->_load_class( $uri_class ) || return( $self->pass_error ); my $str = $arg; if( Scalar::Util::blessed( $str ) && $str->isa( $uri_class ) ) { $data->{ $field } = $str; } elsif( defined( $str ) && ( $str =~ /^[a-zA-Z]+:\/{2}/ || $str =~ /^urn\:[a-z]+\:/ || $str =~ /^[a-z]+\:/ ) ) { $data->{ $field } = $uri_class->new( $str ); warn( "$uri_class subclass is missing to handle this specific URI '$str'\n" ) if( !$data->{ $field }->has_recognized_scheme ); } # Is it an absolute path? elsif( substr( $str, 0, 1 ) eq '/' ) { $data->{ $field } = $uri_class->new( $str ); } elsif( defined( $str ) ) { # try-catch local $@; eval { die( "Cannot use a reference as an URI. Received '$str'" ) if( ref( $str ) && !$self->_is_object( $str ) ); my $u = $uri_class->new( $str ); $data->{ $field } = $u; }; if( $@ ) { return( $self->error( "URI value provided '$str' does not look like an URI, so I do not know what to do with it: $@" ) ); } } else { $data->{ $field } = undef(); } # Data was pre-set or directly set but is not an URI object, so we convert it now if( $data->{ $field } && !$self->_is_a( $data->{ $field }, $uri_class ) ) { # Force stringification if this is an overloaded value $data->{ $field } = $uri_class->new( $data->{ $field } . '' ); } return( $data->{ $field } ); }, field => $field, }, @_ ) ); } # Universally Unique Identifier sub _set_get_uuid : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); my $v = $self->_is_a( $data->{ $field }, 'Module::Generic::Scalar' ) ? $data->{ $field } : $self->new_scalar( $data->{ $field } ); if( !$v->defined ) { return; } else { return( $v ); } }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); my $v = $arg; # If the user wants to remove it if( !defined( $v ) ) { $data->{ $field } = $v; } # If the user provided a string, let's check it elsif( length( $v ) && !$self->_is_uuid( $v ) ) { return( $self->error( "Value provided is not a valid uuid." ) ); } $v = $data->{ $field } = $self->new_scalar( $v ); if( !$v->defined ) { return; } else { return( $v ); } }, field => $field, }, @_ ) ); } sub _set_get_version : lvalue { my $self = shift( @_ ); my $field = shift( @_ ); my $this = $self->_obj2h; my $data = $this->{_data_repo} ? $this->{ $this->{_data_repo} } : $this; my $version_class = 'version'; if( ref( $field ) eq 'HASH' ) { my $def = $field; if( CORE::exists( $def->{field} ) && defined( $def->{field} ) && CORE::length( $def->{field} ) ) { $field = $def->{field}; } else { $field = undef; } $version_class = $def->{class} if( CORE::exists( $def->{class} ) ); } return( $self->_set_get_callback({ get => sub { my $self = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); $self->_load_class( $version_class ) || return( $self->pass_error ); if( !CORE::defined( $data->{ $field } ) ) { return; } else { my $v = $data->{ $field }; if( CORE::length( "$v" ) && !$self->_is_a( $v => $version_class ) ) { # try-catch local $@; eval { $v = $version_class->can( 'parse' ) ? $version_class->parse( "$v" ) : $version_class->new( "$v" ); }; if( $@ ) { warn( "Value set for property '${field}' is not a valid version: $@\n" ); } } return( $v ); } }, set => sub { my $self = shift( @_ ); my $arg = shift( @_ ); return( $self->error( "No field name was provided." ) ) if( !defined( $field ) ); $self->_load_class( $version_class ) || return( $self->pass_error ); my $v = $arg; my $version; # If the user wants to remove it if( !defined( $v ) ) { $data->{ $field } = $v; } elsif( $self->_is_a( $v => $version_class ) ) { $version = $v; } # If the user provided a string, let's check it elsif( length( $v ) ) { my $error; if( $v !~ /^$VERSION_LAX_REGEX$/ ) { $error = "Value provided is not a valid version."; } else { # try-catch local $@; eval { $version = $version_class->can( 'parse' ) ? $version_class->parse( "$v" ) : $version_class->new( "$v" ); }; if( $@ ) { $error = "Value provided is not a valid version: $@"; } } return( $self->error( $error ) ) if( defined( $error ) ); } $data->{ $field } = $version; if( !CORE::defined( $data->{ $field } ) ) { return; } else { my $v = $data->{ $field }; if( CORE::length( "$v" ) && !$self->_is_a( $v => $version_class ) ) { # try-catch local $@; eval { $v = $version_class->can( 'parse' ) ? $version_class->parse( "$v" ) : $version_class->new( "$v" ); }; if( $@ ) { warn( "Value set for property '${field}' is not a valid version: $@\n" ); } } return( $v ); } }, field => $field, }, @_ ) ); } sub _to_array_object { my $self = shift( @_ ); my $data = scalar( @_ ) == 1 && $self->_is_array( $_[0] ) ? shift( @_ ) : ( scalar( @_ ) == 0 || ( scalar( @_ ) == 1 && !defined( $_[0] ) ) ) ? [] : [ @_ ]; return( $self->new_array( $data ) ); } # $self->_warnings_is_enabled() # $self->_warnings_is_enabled( $other_object ); 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 _autoload_subs { $AUTOLOAD_SUBS = { # NOTE: as_hash() as_hash => <<'PERL', sub as_hash { my $self = shift( @_ ); my $p = $self->_get_args_as_hash( @_ ); # $p = shift( @_ ) if( scalar( @_ ) == 1 && ref( $_[0] ) eq 'HASH' ); $p->{convert_array} //= 1; my $me = $self->_obj2h; my $seen = $p->{seen} || {}; my $levels = $p->{levels} || []; my $keys = $p->{fields} || []; my $added_subs = CORE::exists( $me->{_added_method} ) && ref( $me->{_added_method} ) eq 'HASH' ? $me->{_added_method} : {}; my $crawl; $crawl = sub { my $this = shift( @_ ); my $rval = ref( $this ) ? $this : \$this; my( $dataref, $class, $type, $id ); my $strval = $dataref = overload::StrVal( $rval // 'undef' ); # Parse $strval without using regexps, in order not to clobber $1, $2,... if( ( my $i = rindex( $dataref, '=' ) ) >= 0 ) { $class = substr( $dataref, 0, $i ); $dataref = substr( $dataref, $i + 1 ); } if( ( my $i = index( $dataref, "(0x" ) ) >= 0 ) { $type = substr( $dataref, 0, $i ); $id = substr( $dataref, $i + 2, -1 ); } my $levels = shift( @_ ); my $prefix = join( '->', @$levels ) . ':'; if( defined( $class ) ) { if( $class eq 'JSON::PP::Boolean' || $class eq 'Module::Generic::Boolean' ) { return( $$this ? 1 : 0 ); } # NOTE: Not sure why I did this, because as_hash is about converting into hash, not stringifying everything # elsif( $this->can( 'as_hash' ) && # overload::Overloaded( $this ) && # overload::Method( $this, '""' ) ) # { # return( $this . '' ); # } elsif( $this->can( 'as_hash' ) ) { if( $self->_is_array( $this ) && !$p->{convert_array} ) { return( $this ); } elsif( ++$seen->{ Scalar::Util::refaddr( $this ) } < 2 ) { my $old_debug; $old_debug = $this->debug if( $this->can( 'debug' ) ); my $rv = $this->as_hash( { %$p, seen => $seen, levels => $levels } ); $this->debug( $old_debug ) if( defined( $old_debug ) ); if( Scalar::Util::blessed( $rv ) ) { return( $crawl->( $rv, [@$levels, $strval] ) ); } else { return( $rv ); } } else { return( $this ); } } # If the object can be overloaded, and has no TO_JSON method we get its string representation here. # If it has a TO_JSON and we are asked to return data for json, we let the JSON module call the TO_JSON method # NOTE: Not sure why I did this, because as_hash is about converting into hash, not stringifying everything # elsif( overload::Overloaded( $this ) && # overload::Method( $this, '""' ) ) # { # if( $p->{json} && $this->can( 'TO_JSON' ) ) # { # return( $this ); # } # else # { # return( "$this" ); # } # } else { return( $this ); } } elsif( $type eq 'HASH' ) { my $hash = {}; foreach my $k ( keys( %$this ) ) { if( ref( $this->{ $k } ) ) { if( ++$seen->{ Scalar::Util::refaddr( $this->{ $k } ) } > 1 ) { next; } } my $rv = $crawl->( $this->{ $k }, [@$levels, $k] ); $hash->{ $k } = $rv; } return( $hash ); } elsif( $type eq 'ARRAY' ) { my $array = []; for( my $i = 0; $i < scalar( @$this ); $i++ ) { if( ref( $this->[$i] ) ) { if( ++$seen->{ Scalar::Util::refaddr( $this->[$i] ) } > 1 ) { next; } } my $rv = $crawl->( $this->[$i], [@$levels, "[$i]"] ); push( @$array, $rv ); } return( $array ); } elsif( !ref( $this ) ) { defined( $this ) ? return( $this ) : return; } elsif( $type eq 'SCALAR' ) { my $str = $$this; return( \$str ); } elsif( $type eq 'CODE' ) { return( $this ); } elsif( $type eq 'GLOB' ) { return( $this ); } elsif( $type eq 'VSTRING' ) { return( $this ); } else { die( "$prefix: Unknown reference ", overload::StrVal( $this // 'undef' ), " with value $this" ); } }; my $ref = {}; my @keys = (); if( $self->_is_array( $keys ) && scalar( @$keys ) ) { @keys = @$keys; } else { @keys = grep( !/^(debug|verbose)$/, keys( %$me ) ); push( @keys, 'debug' ) if( $self->_has_symbol( 'debug' ) ); push( @keys, 'verbose' ) if( $self->_has_symbol( 'verbose' ) ); } foreach my $k ( @keys ) { next if( substr( $k, 0, 1 ) eq '_' ); next if( CORE::exists( $added_subs->{ $k } ) ); my $rv = $crawl->( $me->{ $k }, [@$levels, $k] ); next if( !defined( $rv ) ); $ref->{ $k } = $rv; } return( $ref ); } PERL # NOTE: clear() clear => <<'PERL', sub clear { return( shift->clear_error ); } PERL # NOTE: clear_error() clear_error => <<'PERL', sub clear_error { my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no strict 'refs'; $this->{error} = ${ "$class\::ERROR" } = ''; return( $self ); } PERL # NOTE: clone() clone => <<'PERL', sub clone { my $self = shift( @_ ); my $new; # try-catch local $@; eval { if( $self->_is_object( $self ) ) { $new = Clone::clone( $self ); } else { $new = $self->new; } }; if( $@ ) { return( $self->error( "Error cloning object \"", overload::StrVal( $self // 'undef' ), "\": $@" ) ); } return( $new ); } PERL # NOTE: colour_close() colour_close => <<'PERL', sub colour_close { return( shift->_set_get( '_colour_close', @_ ) ); } PERL # NOTE: colour_closest() colour_closest => <<'PERL', sub colour_closest { my $self = shift( @_ ); my $colour = uc( shift( @_ ) ); my $this = $self->_obj2h; my $colours = { '000000000' => 'black', '000000255' => 'blue', '000255000' => 'green', '000255255' => 'cyan', '255000000' => 'red', '255000255' => 'magenta', '255255000' => 'yellow', '255255255' => 'white', }; my( $red, $green, $blue ) = ( '', '', '' ); our $COLOUR_NAME_TO_RGB; if( $colour =~ /^[A-Z]+([A-Z\s]+)*$/ ) { if( !scalar( keys( %$COLOUR_NAME_TO_RGB ) ) ) { my $colour_data = $self->__colour_data; local $@; $COLOUR_NAME_TO_RGB = eval( $colour_data ); if( $@ ) { return( $self->error( "An error occurred loading data from __colour_data: $@" ) ); } } if( CORE::exists( $COLOUR_NAME_TO_RGB->{ lc( $colour ) } ) ) { ( $red, $green, $blue ) = @{$COLOUR_NAME_TO_RGB->{ lc( $colour ) }}; } } # Colour all in decimal?? elsif( $colour =~ /^\d{9}$/ ) { $red = substr( $colour, 0, 3 ); $green = substr( $colour, 3, 3 ); $blue = substr( $colour, 6, 3 ); } # Colour in hexadecimal, convert it elsif( $colour =~ /^[A-F0-9]+$/ ) { $red = hex( substr( $colour, 0, 2 ) ); $green = hex( substr( $colour, 2, 2 ) ); $blue = hex( substr( $colour, 4, 2 ) ); } # Clueless else { # Not undef, but rather empty string. Undef is associated with an error return( '' ); } my $dec_colour = CORE::sprintf( '%3d%3d%3d', $red, $green, $blue ); my $last = ''; my @colours = reverse( sort( keys( %$colours ) ) ); $red = CORE::sprintf( '%03d', $red ); $green = CORE::sprintf( '%03d', $green ); $blue = CORE::sprintf( '%03d', $blue ); my $cur = CORE::sprintf( '%03d%03d%03d', $red, $green, $blue ); my( $red_ok, $green_ok, $blue_ok ) = ( 0, 0, 0 ); for( my $i = 0; $i < scalar( @colours ); $i++ ) { my $r = CORE::sprintf( '%03d', substr( $colours[ $i ], 0, 3 ) ); my $g = CORE::sprintf( '%03d', substr( $colours[ $i ], 3, 3 ) ); my $b = CORE::sprintf( '%03d', substr( $colours[ $i ], 6, 3 ) ); my $r_p = CORE::sprintf( '%03d', substr( $colours[ $i - 1 ], 0, 3 ) ); my $g_p = CORE::sprintf( '%03d', substr( $colours[ $i - 1 ], 3, 3 ) ); my $b_p = CORE::sprintf( '%03d', substr( $colours[ $i - 1 ], 6, 3 ) ); if( $red == $r || ( $red < $r && $red > int( $r / 2 ) ) || ( $red > $r && $red < int( $r_p / 2 ) && $r_p ) || $red > $r ) { $red_ok++; } if( $red_ok ) { if( $green == $g || ( $green < $g && $green > int( $g / 2 ) ) || ( $green > $g && $green < int( $g_p / 2 ) && $g_p ) || $green > $g ) { $blue_ok++; } } if( $blue_ok ) { if( $blue == $b || ( $blue < $b && $blue > int( $b / 2 ) ) || ( $blue > $b && $blue < int( $b_p / 2 ) && $b_p ) || $blue > $b ) { $last = $colours[ $i ]; last; } } } return( $colours->{ $last } ); } PERL # NOTE: colour_format() colour_format => <<'PERL', sub colour_format { my $self = shift( @_ ); # style, colour or color and text my $opts = shift( @_ ); return( $self->error( "Parameter hash provided is not an hash reference." ) ) if( !$self->_is_hash( $opts ) ); my $this = $self->_obj2h; # To make it possible to use either text or message property $opts->{text} = CORE::delete( $opts->{message} ) if( CORE::length( $opts->{message} ) && !CORE::length( $opts->{text} ) ); return( $self->error( "No text was provided to format." ) ) if( !CORE::length( $opts->{text} ) ); $opts->{colour} //= CORE::delete( $opts->{color} ) || CORE::delete( $opts->{fg_colour} ) || CORE::delete( $opts->{fg_color} ) || CORE::delete( $opts->{fgcolour} ) || CORE::delete( $opts->{fgcolor} ); $opts->{bgcolour} //= CORE::delete( $opts->{bgcolor} ) || CORE::delete( $opts->{bg_colour} ) || CORE::delete( $opts->{bg_color} ); my $bold = "\e[1m"; my $underline = "\e[4m"; my $reverse = "\e[7m"; my $normal = "\e[m"; my $cls = "\e[H\e[2J"; my $styles = { # Bold b => 1, bold => 1, strong => 1, # Italic i => 3, italic => 3, # Underline u => 4, underline => 4, underlined => 4, blink => 5, # Reverse r => 7, reverse => 7, reversed => 7, # Concealed c => 8, conceal => 8, concealed => 8, strike => 9, striked => 9, striken => 9, }; my $convert_24_To_8bits = sub { my( $r, $g, $b ) = @_; return( ( POSIX::floor( $r * 7 / 255 ) << 5 ) + ( POSIX::floor( $g * 7 / 255 ) << 2 ) + ( POSIX::floor( $b * 3 / 255 ) ) ); }; # opacity * original + (1-opacity)*background = resulting pixel # https://stackoverflow.com/a/746934/4814971 my $colour_with_alpha = sub { my( $r, $g, $b, $a, $bg ) = @_; ## Assuming a white background (255) my( $bg_r, $bg_g, $bg_b ) = ( 255, 255, 255 ); if( ref( $bg ) eq 'HASH' ) { ( $bg_r, $bg_g, $bg_b ) = @$bg{qw( red green blue )}; } $r = POSIX::round( ( $a * $r ) + ( ( 1 - $a ) * $bg_r ) ); $g = POSIX::round( ( $a * $g ) + ( ( 1 - $a ) * $bg_g ) ); $b = POSIX::round( ( $a * $b ) + ( ( 1 - $a ) * $bg_b ) ); return( [$r, $g, $b] ); }; my $check_colour = sub { my $col = shift( @_ ); # $colours or $bg_colours my $map = shift( @_ ); my $code; my $light; # Example: 'light red' or 'light_red' if( $col =~ /^(?:(?bright|light)[[:blank:]\_]+)? (? (?:[a-zA-Z]+)(?:[[:blank:]]+\w+)? | (?rgb[a]?)\([[:blank:]]*(?\d{1,3})[[:blank:]]*\,[[:blank:]]*(?\d{1,3})[[:blank:]]*\,[[:blank:]]*(?\d{1,3}) (?:[[:blank:]]*\,[[:blank:]]*(?\d(?:\.\d+)?))?[[:blank:]]* \) )$/xi ) { my %regexp = %+; ( $light, $col ) = ( $+{light}, $+{colour} ); if( CORE::length( $+{rgb_type} ) && CORE::length( $+{red} ) && CORE::length( $+{green} ) && CORE::length( $+{blue} ) ) { if( $+{opacity} || $light ) { my $opacity = CORE::length( $+{opacity} ) ? $+{opacity} : $light ? 0.5 : 1; $col = CORE::sprintf( 'rgba(%03d%03d%03d,%.1f)', $+{red}, $+{green}, $+{blue}, $opacity ); } else { $col = CORE::sprintf( 'rgb(%03d%03d%03d)', $+{red}, $+{green}, $+{blue} ); } } else { } } elsif( $col =~ /^(?rgb[a]?)\([[:blank:]]*(?\d{1,3})[[:blank:]]*\,[[:blank:]]*(?\d{1,3})[[:blank:]]*\,[[:blank:]]*(?\d{1,3})[[:blank:]]*(?:\,[[:blank:]]*(?\d(?:\.\d+)?))?[[:blank:]]*\)$/i ) { if( $+{opacity} ) { $col = CORE::sprintf( 'rgba(%03d%03d%03d,%.1f)', $+{red}, $+{green}, $+{blue}, $+{opacity} ); } else { $col = CORE::sprintf( '%03d%03d%03d', $+{red}, $+{green}, $+{blue} ); } } else { } my $col_ref; if( $col =~ /^rgb[a]?\((?\d{3})(?\d{3})(?\d{3})\)$/i ) { $col_ref = {}; %$col_ref = %+; return({ _24bits => [@$col_ref{qw( red green blue )}], _8bits => $convert_24_To_8bits->( @$col_ref{qw( red green blue )} ) }); } # Treating opacity to make things lighter; not ideal, but standard scheme elsif( $col =~ /^rgba\((?\d{3})(?\d{3})(?\d{3})[[:blank:]]*\,[[:blank:]]*(?\d(?:\.\d)?)\)$/i ) { $col_ref = {}; %$col_ref = %+; if( $+{opacity} ) { my $opacity = $+{opacity}; my $bg; if( $opts->{bgcolour} ) { $bg = $self->colour_to_rgb( $opts->{bgcolour} ); } my $new_col = $colour_with_alpha->( @$col_ref{qw( red green blue )}, $opacity, $bg ); @$col_ref{qw( red green blue )} = @$new_col; } return({ _24bits => [@$col_ref{qw( red green blue )}], _8bits => $convert_24_To_8bits->( @$col_ref{qw( red green blue )} ) }); } elsif( $self->_message( 9, "Checking if rgb value exists for colour '$col'" ) && ( $col_ref = $self->colour_to_rgb( $col ) ) ) { # $code = $map->{ $col }; return({ _24bits => [@$col_ref{qw( red green blue )}], _8bits => $convert_24_To_8bits->( @$col_ref{qw( red green blue )} ) }); } else { return( {} ); } # my $is_bg = ( CORE::substr( $code, 0, 1 ) == 4 ); # if( CORE::length( $code ) && $light ) # { # ## If the colour is a background colour, replace 4 by 10 (e.g.: 42 becomes 103) # ## and if foreground colour, replace 3 by 9 # CORE::substr( $code, 0, 1 ) = ( $is_bg ? 10 : 9 ); # } # return( $code ); }; my $data = []; my $data8 = []; my $params = []; # 8 bits parameters compatible my $params8 = []; if( $opts->{colour} || $opts->{color} || $opts->{fgcolour} || $opts->{fgcolor} || $opts->{fg_colour} || $opts->{fg_color} ) { $opts->{colour} ||= CORE::delete( $opts->{color} ) || CORE::delete( $opts->{fg_colour} ) || CORE::delete( $opts->{fg_color} ) || CORE::delete( $opts->{fgcolour} ) || CORE::delete( $opts->{fgcolor} ); # my $col_ref = $check_colour->( $opts->{colour}, $colours ); my $col_ref = $check_colour->( $opts->{colour} ); # CORE::push( @$params, $col ) if( CORE::length( $col ) ); if( scalar( keys( %$col_ref ) ) ) { CORE::push( @$params8, sprintf( '38;5;%d', $col_ref->{_8bits} ) ); CORE::push( @$params, sprintf( '38;2;%d;%d;%d', @{$col_ref->{_24bits}} ) ); } else { } } if( $opts->{bgcolour} || $opts->{bgcolor} || $opts->{bg_colour} || $opts->{bg_color} ) { $opts->{bgcolour} ||= CORE::delete( $opts->{bgcolor} ) || CORE::delete( $opts->{bg_colour} ) || CORE::delete( $opts->{bg_color} ); # my $col_ref = $check_colour->( $opts->{bgcolour}, $bg_colours ); my $col_ref = $check_colour->( $opts->{bgcolour} ); ## CORE::push( @$params, $col ) if( CORE::length( $col ) ); if( scalar( keys( %$col_ref ) ) ) { CORE::push( @$params8, sprintf( '48;5;%d', $col_ref->{_8bits} ) ); CORE::push( @$params, sprintf( '48;2;%d;%d;%d', @{$col_ref->{_24bits}} ) ); } else { } } if( $opts->{style} ) { my $those_styles = [CORE::split( /\|/, $opts->{style} )]; foreach my $s ( @$those_styles ) { if( CORE::exists( $styles->{lc($s)} ) ) { CORE::push( @$params, $styles->{lc($s)} ); # We add the 8 bits compliant version only if any colour was provided, i.e. # This is not just a style definition CORE::push( @$params8, $styles->{lc($s)} ) if( scalar( @$params8 ) ); } } } CORE::push( @$data, "\e[" . CORE::join( ';', @$params8 ) . "m" ) if( scalar( @$params8 ) ); CORE::push( @$data, "\e[" . CORE::join( ';', @$params ) . "m" ) if( scalar( @$params ) ); # If the text contains libe breaks, we must stop the formatting before, or else there would be an ugly formatting on the entire screen following the line break if( scalar( @$params ) && $opts->{text} =~ /\n+/ ) { my $text_parts = [CORE::split( /\n/, $opts->{text} )]; my $fmt = CORE::join( '', @$data ); my $fmt8 = CORE::join( '', @$data8 ); for( my $i = 0; $i < scalar( @$text_parts ); $i++ ) { # Empty due to \n repeated next if( !CORE::length( $text_parts->[$i] ) ); $text_parts->[$i] = $fmt . $text_parts->[$i] . $normal; } $opts->{text} = CORE::join( "\n", @$text_parts ); CORE::push( @$data, $opts->{text} ); } else { CORE::push( @$data, $opts->{text} ); CORE::push( @$data, $normal, $normal ) if( scalar( @$params ) ); } return( CORE::join( '', @$data ) ); } PERL # NOTE: colour_open() colour_open => <<'PERL', sub colour_open { return( shift->_set_get( '_colour_open', @_ ) ); } PERL # NOTE: colour_parse() colour_parse => <<'PERL', sub colour_parse { my $self = shift( @_ ); my $txt = join( '', @_ ); my $this = $self->_obj2h; my @opens = ( '{', '<' ); my @closes = ( '}', '>' ); my $cust_open = $self->colour_open; my $cust_close = $self->colour_close; if( defined( $cust_open ) && length( $cust_open ) && !scalar( grep( /^\Q$cust_open\E$/, @opens ) ) ) { push( @opens, $cust_open ); } if( defined( $cust_close ) && length( $cust_close ) && !scalar( grep( /^\Q$cust_close\E$/, @closes ) ) ) { push( @closes, $cust_close ); } my $open = join( '|', @opens ); my $close = join( '|', @closes ); my $is_tty = $self->_is_tty; no strict; my $re = qr/ (? (?$open)(?!\/)(?.*?)(?$close) (? (?: (?> [^$open|$close]+ ) | (?R) )*+ ) \g{open}\/\g{close} ) /x; my $colour_re = qr/(?:(?:bright|light)[[:blank:]])?(?:[a-zA-Z]+(?:[[:blank:]]+[\w\-]+)?|rgb[a]?\([[:blank:]]*\d{1,3}[[:blank:]]*\,[[:blank:]]*\d{1,3}[[:blank:]]*\,[[:blank:]]*\d{1,3}[[:blank:]]*(?:\,[[:blank:]]*\d(?:\.\d)?)?[[:blank:]]*\))/; my $style_re = qr/(?:bold|faint|italic|underline|blink|reverse|conceal|strike)/; my $parse; $parse = sub { my $str = shift( @_ ); 1 while( $str =~ s{$re} { my $re = { %- }; my $catch = substr( $str, $-[0], $+[0] - $-[0] ); my $all = $+{all}; my $ct = $+{content}; # Are we connected to a tty ? if( !$is_tty ) { # Return the content without formatting then $ct; } else { my $params = $+{params}; if( index( $ct, $open ) != -1 && index( $ct, $close ) != -1 ) { $ct = $parse->( $ct ); } my $def = {}; if( $params =~ /^[[:blank:]]*(?:(?$style_re)[[:blank:]]+)?(?$colour_re)(?:[[:blank:]]+(?$style_re))?(?:[[:blank:]]+on[[:blank:]]+(?$colour_re))?[[:blank:]]*$/i ) { my $style = $+{style1} || $+{style2}; my $fg = $+{fg_colour}; my $bg = $+{bg_colour}; $def = { style => $style, colour => $fg, bg_colour => $bg, }; } else { local $SIG{__WARN__} = sub{}; local $SIG{__DIE__} = sub{}; local $@; my @res = eval( $params ); $def = { @res } if( scalar( @res ) && !( scalar( @res ) % 2 ) ); if( $@ || ref( $def ) ne 'HASH' ) { my $err = $@ || "Invalid styling \"${params}\""; $def = {}; } } if( scalar( keys( %$def ) ) ) { if( !defined( $ct ) || !CORE::length( $ct // '' ) ) { ''; } else { $def->{text} = $ct; my $res = $self->colour_format( $def ); length( $res ) ? $res : $catch; } } else { $catch; } } }gex ); return( $str ); }; return( $parse->( $txt ) ); } PERL # NOTE: colour_to_rgb() colour_to_rgb => <<'PERL', sub colour_to_rgb { my $self = shift( @_ ); my $colour = lc( shift( @_ ) ); my $this = $self->_obj2h; my( $red, $green, $blue ) = ( '', '', '' ); our $COLOUR_NAME_TO_RGB; if( $colour =~ /^[A-Za-z]+([\w\-]+)*([[:blank:]]+\w+)?$/ ) { if( !scalar( keys( %$COLOUR_NAME_TO_RGB ) ) ) { my $colour_data = $self->__colour_data; local $@; $COLOUR_NAME_TO_RGB = eval( $colour_data ); if( $@ ) { return( $self->error( "An error occurred loading data from __colour_data: $@" ) ); } } if( CORE::exists( $COLOUR_NAME_TO_RGB->{ $colour } ) ) { ( $red, $green, $blue ) = @{$COLOUR_NAME_TO_RGB->{ $colour }}; } else { return( '' ); } } ## Colour all in decimal?? elsif( $colour =~ /^\d{9}$/ ) { $red = substr( $colour, 0, 3 ); $green = substr( $colour, 3, 3 ); $blue = substr( $colour, 6, 3 ); } ## Colour in hexadecimal, convert it elsif( $colour =~ /^[A-F0-9]+$/ ) { $red = hex( substr( $colour, 0, 2 ) ); $green = hex( substr( $colour, 2, 2 ) ); $blue = hex( substr( $colour, 4, 2 ) ); } ## Clueless else { ## Not undef, but rather empty string. Undef is associated with an error return( '' ); } return({ red => $red, green => $green, blue => $blue }); } PERL # NOTE: coloured() coloured => <<'PERL', sub coloured { my $self = shift( @_ ); my $pref = shift( @_ ); my $text = CORE::join( '', @_ ); my $this = $self->_obj2h; my( $style, $fg, $bg ); ## my $colour_re = qr/(?:(?:bright|light)[[:blank:]])?[a-zA-Z]+/; my $colour_re = qr/(?:(?:bright|light)[[:blank:]])?(?:[a-zA-Z]+(?:[[:blank:]]+[\w\-]+)?|rgb[a]?\([[:blank:]]*\d{1,3}[[:blank:]]*\,[[:blank:]]*\d{1,3}[[:blank:]]*\,[[:blank:]]*\d{1,3}[[:blank:]]*(?:\,[[:blank:]]*\d(?:\.\d)?)?[[:blank:]]*\))/; my $style_re = qr/(?:bold|faint|italic|underline|blink|reverse|conceal|strike)/; if( $pref =~ /^(?:(?$style_re)[[:blank:]]+)?(?$colour_re)(?:[[:blank:]]+(?$style_re))?(?:[[:blank:]]+on[[:blank:]]+(?$colour_re))?$/i ) { $style = $+{style1} || $+{style2}; $fg = $+{fg_colour}; $bg = $+{bg_colour}; return( $self->colour_format({ text => $text, style => $style, colour => $fg, bg_colour => $bg }) ); } else { return( '' ); } } PERL # NOTE: dump_hex() dump_hex => <<'PERL', sub dump_hex { my $self = shift( @_ ); my $rv; # try-catch local $@; eval { require Devel::Hexdump; $rv = Devel::Hexdump::xd( shift( @_ ) ); }; if( $@ ) { return( $self->error( "Devel::Hexdump is not installed on your system." ) ); } return( $rv ); } PERL # NOTE: dump_print() dump_print => <<'PERL', # For backward compatibility and traceability sub dump_print { return( shift->dumpto_printer( @_ ) ); } PERL # NOTE: dumper() dumper => <<'PERL', sub dumper { my $self = shift( @_ ); my $opts = {}; $opts = pop( @_ ) if( scalar( @_ ) > 1 && ref( $_[-1] ) eq 'HASH' ); my $rv; # try-catch local $@; eval { no warnings 'once'; require Data::Dumper; # local $Data::Dumper::Sortkeys = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 1; local $Data::Dumper::Useqq = 1; local $Data::Dumper::Maxdepth = $opts->{depth} if( CORE::length( $opts->{depth} ) ); local $Data::Dumper::Sortkeys = sub { my $h = shift( @_ ); return( [ sort( grep{ ref( $h->{ $_ } ) !~ /^(DateTime|DateTime\:\:)/ } keys( %$h ) ) ] ); }; $rv = Data::Dumper::Dumper( @_ ); }; if( $@ ) { return( $self->error( "Data::Dumper is not installed on your system." ) ); } return( $rv ); } PERL # NOTE: dumpto_printer() dumpto_printer => <<'PERL', sub dumpto_printer { my $self = shift( @_ ); my( $data, $file ) = @_; require Module::Generic::File; $file = Module::Generic::File::file( $file ); my $fh = $file->open( '>', { binmode => 'utf-8', autoflush => 1 }) || die( "Unable to create file '$file': $!\n" ); $fh->print( Data::Dump::dump( $data ), "\n" ); $fh->close; # 666 so it can work under command line and web alike chmod( 0666, $file ); return(1); } PERL # NOTE: dumpto_dumper() dumpto_dumper => <<'PERL', sub dumpto_dumper { my $self = shift( @_ ); my( $data, $file ) = @_; my $rv; # try-catch local $@; eval { require Data::Dumper; local $Data::Dumper::Sortkeys = 1; local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 1; local $Data::Dumper::Useqq = 1; require Module::Generic::File; $file = Module::Generic::File::file( $file ); my $fh = $file->open( '>', { autoflush => 1 }) || die( "Unable to create file '$file': $!\n" ); if( ref( $data ) ) { $fh->print( Data::Dumper::Dumper( $data ), "\n" ); } else { $fh->binmode( ':utf8' ); $fh->print( $data ); } $fh->close; # 666 so it can work under command line and web alike chmod( 0666, $file ); $rv = 1; }; if( $@ ) { return( $self->error( "Unable to dump data to \"$file\" using Data::Dumper: $@" ) ); } return( $rv ); } PERL # NOTE: errno() errno => <<'PERL', sub errno { my $self = shift( @_ ); my $this = $self->_obj2h; if( @_ ) { $this->{errno} = shift( @_ ) if( $_[ 0 ] =~ /^\-?\d+$/ ); return( $self->error( @_ ) ) if( @_ ); } return( $this->{errno} ); } PERL # NOTE: message_colour() message_colour => <<'PERL', sub message_colour { my $self = shift( @_ ); my $this = $self->_obj2h; my $opts = {}; my $args = [@_]; if( scalar( @$args ) > 1 && ref( $args->[-1] ) eq 'HASH' && ( CORE::exists( $args->[-1]->{level} ) || CORE::exists( $args->[-1]->{type} ) || CORE::exists( $args->[-1]->{message} ) ) ) { $opts = pop( @$args ); } $opts->{colour} = 1; return( $self->_message( @$args, $opts ) ); } PERL # NOTE: messagef_colour() messagef_colour => <<'PERL', sub messagef_colour { my $self = shift( @_ ); my $this = $self->_obj2h; my $opts = {}; my $args = [@_]; no strict 'refs'; if( scalar( @$args ) > 1 && ref( $args->[-1] ) eq 'HASH' && ( CORE::exists( $args->[-1]->{level} ) || CORE::exists( $args->[-1]->{type} ) || CORE::exists( $args->[-1]->{message} ) ) ) { $opts = pop( @$args ); } $opts->{colour} = 1; return( $self->_messagef( @$args, $opts ) ); } PERL # NOTE: printer() printer => <<'PERL', sub printer { my $self = shift( @_ ); my $opts = {}; $opts = pop( @_ ) if( scalar( @_ ) > 1 && ref( $_[-1] ) eq 'HASH' ); my $rv; # try-catch local $@; eval { local $SIG{__WARN__} = sub{ }; require Data::Printer; if( scalar( keys( %$opts ) ) ) { $rv = Data::Printer::np( @_, %$opts ); } else { $rv = Data::Printer::np( @_ ); } }; if( $@ ) { return( $self->error( "Data::Printer is not installed on your system." ) ); } return( $rv ); } PERL # NOTE: save() save => <<'PERL', sub save { my $self = shift( @_ ); my $this = $self->_obj2h; my $opts = {}; $opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' ); my( $file, $data ); if( @_ == 2 ) { $opts->{data} = shift( @_ ); $opts->{file} = shift( @_ ); } return( $self->error( "No file was provided to save data to." ) ) if( !$opts->{file} ); require Module::Generic::File; $file = Module::Generic::File::file( $opts->{file} ); my $fh = $file->open( '>', { ( $opts->{encoding} ? ( binmode => $opts->{encoding} ) : () ), autoflush => 1, }) || return( $self->error( "Unable to open file \"$file\" in write mode: $!" ) ); if( !defined( $fh->print( ref( $opts->{data} ) eq 'SCALAR' ? ${$opts->{data}} : $opts->{data} ) ) ) { return( $self->error( "Unable to write data to file \"$file\": $!" ) ) } $fh->close; my $bytes = -s( $opts->{file} ); return( $bytes ); } PERL # NOTE: subclasses() subclasses => <<'PERL', sub subclasses { my $self = shift( @_ ); my $that = ''; $that = @_ ? shift( @_ ) : $self; my $base = ref( $that ) || $that; $base =~ s,::,/,g; $base .= '.pm'; require IO::Dir; # remove '.pm' my $dir = substr( $INC{ $base }, 0, ( length( $INC{ $base } ) ) - 3 ); my @packages = (); my $io = IO::Dir->open( $dir ); if( defined( $io ) ) { @packages = map{ substr( $_, 0, length( $_ ) - 3 ) } grep{ substr( $_, -3 ) eq '.pm' && -f( "$dir/$_" ) } $io->read(); $io->close || warn( "Unable to close directory \"$dir\": $!\n" ); } else { warn( "Unable to open directory \"$dir\": $!\n" ); } return( wantarray() ? @packages : \@packages ); } PERL __dbh => <<'PERL', sub __dbh { my $self = shift( @_ ); my $class = ref( $self ) || $self; my $this = $self->_obj2h; no strict 'refs'; if( !$this->{__dbh} ) { return( '' ) if( !${ "$class\::DB_DSN" } ); require DBI; ## Connecting to database my $db_opt = {}; $db_opt->{RaiseError} = ${ "$class\::DB_RAISE_ERROR" } if( length( ${ "$class\::DB_RAISE_ERROR" } ) ); $db_opt->{AutoCommit} = ${ "$class\::DB_AUTO_COMMIT" } if( length( ${ "$class\::DB_AUTO_COMMIT" } ) ); $db_opt->{PrintError} = ${ "$class\::DB_PRINT_ERROR" } if( length( ${ "$class\::DB_PRINT_ERROR" } ) ); $db_opt->{ShowErrorStatement} = ${ "$class\::DB_SHOW_ERROR_STATEMENT" } if( length( ${ "$class\::DB_SHOW_ERROR_STATEMENT" } ) ); $db_opt->{client_encoding} = ${ "$class\::DB_CLIENT_ENCODING" } if( length( ${ "$class\::DB_CLIENT_ENCODING" } ) ); my $dbh = DBI->connect_cached( ${ "$class\::DB_DSN" } ) || die( "Unable to connect to sql database with dsn '", ${ "$class\::DB_DSN" }, "'\n" ); $dbh->{pg_server_prepare} = 1 if( ${ "$class\::DB_SERVER_PREPARE" } ); $this->{__dbh} = $dbh; } return( $this->{__dbh} ); } PERL # NOTE: __colour_data() __colour_data => <<'PERL', # Initially those data were stored after the __END__, but it seems some module is interfering with # and so those data could not be loaded reliably # This is called once by colour_to_rgb to generate the hash reference COLOUR_NAME_TO_RGB sub __colour_data { my $colour_data = < ['240','248','255'],'aliceblue' => ['240','248','255'],'antique white' => ['250','235','215'],'antiquewhite' => ['250','235','215'],'antiquewhite1' => ['255','239','219'],'antiquewhite2' => ['238','223','204'],'antiquewhite3' => ['205','192','176'],'antiquewhite4' => ['139','131','120'],'aquamarine' => ['127','255','212'],'aquamarine1' => ['127','255','212'],'aquamarine2' => ['118','238','198'],'aquamarine3' => ['102','205','170'],'aquamarine4' => ['69','139','116'],'azure' => ['240','255','255'],'azure1' => ['240','255','255'],'azure2' => ['224','238','238'],'azure3' => ['193','205','205'],'azure4' => ['131','139','139'],'beige' => ['245','245','220'],'bisque' => ['255','228','196'],'bisque1' => ['255','228','196'],'bisque2' => ['238','213','183'],'bisque3' => ['205','183','158'],'bisque4' => ['139','125','107'],'black' => ['0','0','0'],'blanched almond' => ['255','235','205'],'blanchedalmond' => ['255','235','205'],'blue' => ['0','0','255'],'blue violet' => ['138','43','226'],'blue1' => ['0','0','255'],'blue2' => ['0','0','238'],'blue3' => ['0','0','205'],'blue4' => ['0','0','139'],'blueviolet' => ['138','43','226'],'brown' => ['165','42','42'],'brown1' => ['255','64','64'],'brown2' => ['238','59','59'],'brown3' => ['205','51','51'],'brown4' => ['139','35','35'],'burlywood' => ['222','184','135'],'burlywood1' => ['255','211','155'],'burlywood2' => ['238','197','145'],'burlywood3' => ['205','170','125'],'burlywood4' => ['139','115','85'],'cadet blue' => ['95','158','160'],'cadetblue' => ['95','158','160'],'cadetblue1' => ['152','245','255'],'cadetblue2' => ['142','229','238'],'cadetblue3' => ['122','197','205'],'cadetblue4' => ['83','134','139'],'chartreuse' => ['127','255','0'],'chartreuse1' => ['127','255','0'],'chartreuse2' => ['118','238','0'],'chartreuse3' => ['102','205','0'],'chartreuse4' => ['69','139','0'],'chocolate' => ['210','105','30'],'chocolate1' => ['255','127','36'],'chocolate2' => ['238','118','33'],'chocolate3' => ['205','102','29'],'chocolate4' => ['139','69','19'],'coral' => ['255','127','80'],'coral1' => ['255','114','86'],'coral2' => ['238','106','80'],'coral3' => ['205','91','69'],'coral4' => ['139','62','47'],'cornflower blue' => ['100','149','237'],'cornflowerblue' => ['100','149','237'],'cornsilk' => ['255','248','220'],'cornsilk1' => ['255','248','220'],'cornsilk2' => ['238','232','205'],'cornsilk3' => ['205','200','177'],'cornsilk4' => ['139','136','120'],'cyan' => ['0','255','255'],'cyan1' => ['0','255','255'],'cyan2' => ['0','238','238'],'cyan3' => ['0','205','205'],'cyan4' => ['0','139','139'],'dark blue' => ['0','0','139'],'dark cyan' => ['0','139','139'],'dark goldenrod' => ['184','134','11'],'dark gray' => ['169','169','169'],'dark green' => ['0','100','0'],'dark grey' => ['169','169','169'],'dark khaki' => ['189','183','107'],'dark magenta' => ['139','0','139'],'dark olive green' => ['85','107','47'],'dark orange' => ['255','140','0'],'dark orchid' => ['153','50','204'],'dark red' => ['139','0','0'],'dark salmon' => ['233','150','122'],'dark sea green' => ['143','188','143'],'dark slate blue' => ['72','61','139'],'dark slate gray' => ['47','79','79'],'dark slate grey' => ['47','79','79'],'dark turquoise' => ['0','206','209'],'dark violet' => ['148','0','211'],'darkblue' => ['0','0','139'],'darkcyan' => ['0','139','139'],'darkgoldenrod' => ['184','134','11'],'darkgoldenrod1' => ['255','185','15'],'darkgoldenrod2' => ['238','173','14'],'darkgoldenrod3' => ['205','149','12'],'darkgoldenrod4' => ['139','101','8'],'darkgray' => ['169','169','169'],'darkgreen' => ['0','100','0'],'darkgrey' => ['169','169','169'],'darkkhaki' => ['189','183','107'],'darkmagenta' => ['139','0','139'],'darkolivegreen' => ['85','107','47'],'darkolivegreen1' => ['202','255','112'],'darkolivegreen2' => ['188','238','104'],'darkolivegreen3' => ['162','205','90'],'darkolivegreen4' => ['110','139','61'],'darkorange' => ['255','140','0'],'darkorange1' => ['255','127','0'],'darkorange2' => ['238','118','0'],'darkorange3' => ['205','102','0'],'darkorange4' => ['139','69','0'],'darkorchid' => ['153','50','204'],'darkorchid1' => ['191','62','255'],'darkorchid2' => ['178','58','238'],'darkorchid3' => ['154','50','205'],'darkorchid4' => ['104','34','139'],'darkred' => ['139','0','0'],'darksalmon' => ['233','150','122'],'darkseagreen' => ['143','188','143'],'darkseagreen1' => ['193','255','193'],'darkseagreen2' => ['180','238','180'],'darkseagreen3' => ['155','205','155'],'darkseagreen4' => ['105','139','105'],'darkslateblue' => ['72','61','139'],'darkslategray' => ['47','79','79'],'darkslategray1' => ['151','255','255'],'darkslategray2' => ['141','238','238'],'darkslategray3' => ['121','205','205'],'darkslategray4' => ['82','139','139'],'darkslategrey' => ['47','79','79'],'darkturquoise' => ['0','206','209'],'darkviolet' => ['148','0','211'],'deep pink' => ['255','20','147'],'deep sky blue' => ['0','191','255'],'deeppink' => ['255','20','147'],'deeppink1' => ['255','20','147'],'deeppink2' => ['238','18','137'],'deeppink3' => ['205','16','118'],'deeppink4' => ['139','10','80'],'deepskyblue' => ['0','191','255'],'deepskyblue1' => ['0','191','255'],'deepskyblue2' => ['0','178','238'],'deepskyblue3' => ['0','154','205'],'deepskyblue4' => ['0','104','139'],'dim gray' => ['105','105','105'],'dim grey' => ['105','105','105'],'dimgray' => ['105','105','105'],'dimgrey' => ['105','105','105'],'dodger blue' => ['30','144','255'],'dodgerblue' => ['30','144','255'],'dodgerblue1' => ['30','144','255'],'dodgerblue2' => ['28','134','238'],'dodgerblue3' => ['24','116','205'],'dodgerblue4' => ['16','78','139'],'firebrick' => ['178','34','34'],'firebrick1' => ['255','48','48'],'firebrick2' => ['238','44','44'],'firebrick3' => ['205','38','38'],'firebrick4' => ['139','26','26'],'floral white' => ['255','250','240'],'floralwhite' => ['255','250','240'],'forest green' => ['34','139','34'],'forestgreen' => ['34','139','34'],'gainsboro' => ['220','220','220'],'ghost white' => ['248','248','255'],'ghostwhite' => ['248','248','255'],'gold' => ['255','215','0'],'gold1' => ['255','215','0'],'gold2' => ['238','201','0'],'gold3' => ['205','173','0'],'gold4' => ['139','117','0'],'goldenrod' => ['218','165','32'],'goldenrod1' => ['255','193','37'],'goldenrod2' => ['238','180','34'],'goldenrod3' => ['205','155','29'],'goldenrod4' => ['139','105','20'],'gray' => ['190','190','190'],'gray0' => ['0','0','0'],'gray1' => ['3','3','3'],'gray10' => ['26','26','26'],'gray100' => ['255','255','255'],'gray11' => ['28','28','28'],'gray12' => ['31','31','31'],'gray13' => ['33','33','33'],'gray14' => ['36','36','36'],'gray15' => ['38','38','38'],'gray16' => ['41','41','41'],'gray17' => ['43','43','43'],'gray18' => ['46','46','46'],'gray19' => ['48','48','48'],'gray2' => ['5','5','5'],'gray20' => ['51','51','51'],'gray21' => ['54','54','54'],'gray22' => ['56','56','56'],'gray23' => ['59','59','59'],'gray24' => ['61','61','61'],'gray25' => ['64','64','64'],'gray26' => ['66','66','66'],'gray27' => ['69','69','69'],'gray28' => ['71','71','71'],'gray29' => ['74','74','74'],'gray3' => ['8','8','8'],'gray30' => ['77','77','77'],'gray31' => ['79','79','79'],'gray32' => ['82','82','82'],'gray33' => ['84','84','84'],'gray34' => ['87','87','87'],'gray35' => ['89','89','89'],'gray36' => ['92','92','92'],'gray37' => ['94','94','94'],'gray38' => ['97','97','97'],'gray39' => ['99','99','99'],'gray4' => ['10','10','10'],'gray40' => ['102','102','102'],'gray41' => ['105','105','105'],'gray42' => ['107','107','107'],'gray43' => ['110','110','110'],'gray44' => ['112','112','112'],'gray45' => ['115','115','115'],'gray46' => ['117','117','117'],'gray47' => ['120','120','120'],'gray48' => ['122','122','122'],'gray49' => ['125','125','125'],'gray5' => ['13','13','13'],'gray50' => ['127','127','127'],'gray51' => ['130','130','130'],'gray52' => ['133','133','133'],'gray53' => ['135','135','135'],'gray54' => ['138','138','138'],'gray55' => ['140','140','140'],'gray56' => ['143','143','143'],'gray57' => ['145','145','145'],'gray58' => ['148','148','148'],'gray59' => ['150','150','150'],'gray6' => ['15','15','15'],'gray60' => ['153','153','153'],'gray61' => ['156','156','156'],'gray62' => ['158','158','158'],'gray63' => ['161','161','161'],'gray64' => ['163','163','163'],'gray65' => ['166','166','166'],'gray66' => ['168','168','168'],'gray67' => ['171','171','171'],'gray68' => ['173','173','173'],'gray69' => ['176','176','176'],'gray7' => ['18','18','18'],'gray70' => ['179','179','179'],'gray71' => ['181','181','181'],'gray72' => ['184','184','184'],'gray73' => ['186','186','186'],'gray74' => ['189','189','189'],'gray75' => ['191','191','191'],'gray76' => ['194','194','194'],'gray77' => ['196','196','196'],'gray78' => ['199','199','199'],'gray79' => ['201','201','201'],'gray8' => ['20','20','20'],'gray80' => ['204','204','204'],'gray81' => ['207','207','207'],'gray82' => ['209','209','209'],'gray83' => ['212','212','212'],'gray84' => ['214','214','214'],'gray85' => ['217','217','217'],'gray86' => ['219','219','219'],'gray87' => ['222','222','222'],'gray88' => ['224','224','224'],'gray89' => ['227','227','227'],'gray9' => ['23','23','23'],'gray90' => ['229','229','229'],'gray91' => ['232','232','232'],'gray92' => ['235','235','235'],'gray93' => ['237','237','237'],'gray94' => ['240','240','240'],'gray95' => ['242','242','242'],'gray96' => ['245','245','245'],'gray97' => ['247','247','247'],'gray98' => ['250','250','250'],'gray99' => ['252','252','252'],'green' => ['0','255','0'],'green yellow' => ['173','255','47'],'green1' => ['0','255','0'],'green2' => ['0','238','0'],'green3' => ['0','205','0'],'green4' => ['0','139','0'],'greenyellow' => ['173','255','47'],'grey' => ['190','190','190'],'grey0' => ['0','0','0'],'grey1' => ['3','3','3'],'grey10' => ['26','26','26'],'grey100' => ['255','255','255'],'grey11' => ['28','28','28'],'grey12' => ['31','31','31'],'grey13' => ['33','33','33'],'grey14' => ['36','36','36'],'grey15' => ['38','38','38'],'grey16' => ['41','41','41'],'grey17' => ['43','43','43'],'grey18' => ['46','46','46'],'grey19' => ['48','48','48'],'grey2' => ['5','5','5'],'grey20' => ['51','51','51'],'grey21' => ['54','54','54'],'grey22' => ['56','56','56'],'grey23' => ['59','59','59'],'grey24' => ['61','61','61'],'grey25' => ['64','64','64'],'grey26' => ['66','66','66'],'grey27' => ['69','69','69'],'grey28' => ['71','71','71'],'grey29' => ['74','74','74'],'grey3' => ['8','8','8'],'grey30' => ['77','77','77'],'grey31' => ['79','79','79'],'grey32' => ['82','82','82'],'grey33' => ['84','84','84'],'grey34' => ['87','87','87'],'grey35' => ['89','89','89'],'grey36' => ['92','92','92'],'grey37' => ['94','94','94'],'grey38' => ['97','97','97'],'grey39' => ['99','99','99'],'grey4' => ['10','10','10'],'grey40' => ['102','102','102'],'grey41' => ['105','105','105'],'grey42' => ['107','107','107'],'grey43' => ['110','110','110'],'grey44' => ['112','112','112'],'grey45' => ['115','115','115'],'grey46' => ['117','117','117'],'grey47' => ['120','120','120'],'grey48' => ['122','122','122'],'grey49' => ['125','125','125'],'grey5' => ['13','13','13'],'grey50' => ['127','127','127'],'grey51' => ['130','130','130'],'grey52' => ['133','133','133'],'grey53' => ['135','135','135'],'grey54' => ['138','138','138'],'grey55' => ['140','140','140'],'grey56' => ['143','143','143'],'grey57' => ['145','145','145'],'grey58' => ['148','148','148'],'grey59' => ['150','150','150'],'grey6' => ['15','15','15'],'grey60' => ['153','153','153'],'grey61' => ['156','156','156'],'grey62' => ['158','158','158'],'grey63' => ['161','161','161'],'grey64' => ['163','163','163'],'grey65' => ['166','166','166'],'grey66' => ['168','168','168'],'grey67' => ['171','171','171'],'grey68' => ['173','173','173'],'grey69' => ['176','176','176'],'grey7' => ['18','18','18'],'grey70' => ['179','179','179'],'grey71' => ['181','181','181'],'grey72' => ['184','184','184'],'grey73' => ['186','186','186'],'grey74' => ['189','189','189'],'grey75' => ['191','191','191'],'grey76' => ['194','194','194'],'grey77' => ['196','196','196'],'grey78' => ['199','199','199'],'grey79' => ['201','201','201'],'grey8' => ['20','20','20'],'grey80' => ['204','204','204'],'grey81' => ['207','207','207'],'grey82' => ['209','209','209'],'grey83' => ['212','212','212'],'grey84' => ['214','214','214'],'grey85' => ['217','217','217'],'grey86' => ['219','219','219'],'grey87' => ['222','222','222'],'grey88' => ['224','224','224'],'grey89' => ['227','227','227'],'grey9' => ['23','23','23'],'grey90' => ['229','229','229'],'grey91' => ['232','232','232'],'grey92' => ['235','235','235'],'grey93' => ['237','237','237'],'grey94' => ['240','240','240'],'grey95' => ['242','242','242'],'grey96' => ['245','245','245'],'grey97' => ['247','247','247'],'grey98' => ['250','250','250'],'grey99' => ['252','252','252'],'honeydew' => ['240','255','240'],'honeydew1' => ['240','255','240'],'honeydew2' => ['224','238','224'],'honeydew3' => ['193','205','193'],'honeydew4' => ['131','139','131'],'hot pink' => ['255','105','180'],'hotpink' => ['255','105','180'],'hotpink1' => ['255','110','180'],'hotpink2' => ['238','106','167'],'hotpink3' => ['205','96','144'],'hotpink4' => ['139','58','98'],'indian red' => ['205','92','92'],'indianred' => ['205','92','92'],'indianred1' => ['255','106','106'],'indianred2' => ['238','99','99'],'indianred3' => ['205','85','85'],'indianred4' => ['139','58','58'],'ivory' => ['255','255','240'],'ivory1' => ['255','255','240'],'ivory2' => ['238','238','224'],'ivory3' => ['205','205','193'],'ivory4' => ['139','139','131'],'khaki' => ['240','230','140'],'khaki1' => ['255','246','143'],'khaki2' => ['238','230','133'],'khaki3' => ['205','198','115'],'khaki4' => ['139','134','78'],'lavender' => ['230','230','250'],'lavender blush' => ['255','240','245'],'lavenderblush' => ['255','240','245'],'lavenderblush1' => ['255','240','245'],'lavenderblush2' => ['238','224','229'],'lavenderblush3' => ['205','193','197'],'lavenderblush4' => ['139','131','134'],'lawn green' => ['124','252','0'],'lawngreen' => ['124','252','0'],'lemon chiffon' => ['255','250','205'],'lemonchiffon' => ['255','250','205'],'lemonchiffon1' => ['255','250','205'],'lemonchiffon2' => ['238','233','191'],'lemonchiffon3' => ['205','201','165'],'lemonchiffon4' => ['139','137','112'],'light blue' => ['173','216','230'],'light coral' => ['240','128','128'],'light cyan' => ['224','255','255'],'light goldenrod' => ['238','221','130'],'light goldenrod yellow' => ['250','250','210'],'light gray' => ['211','211','211'],'light green' => ['144','238','144'],'light grey' => ['211','211','211'],'light pink' => ['255','182','193'],'light salmon' => ['255','160','122'],'light sea green' => ['32','178','170'],'light sky blue' => ['135','206','250'],'light slate blue' => ['132','112','255'],'light slate gray' => ['119','136','153'],'light slate grey' => ['119','136','153'],'light steel blue' => ['176','196','222'],'light yellow' => ['255','255','224'],'lightblue' => ['173','216','230'],'lightblue1' => ['191','239','255'],'lightblue2' => ['178','223','238'],'lightblue3' => ['154','192','205'],'lightblue4' => ['104','131','139'],'lightcoral' => ['240','128','128'],'lightcyan' => ['224','255','255'],'lightcyan1' => ['224','255','255'],'lightcyan2' => ['209','238','238'],'lightcyan3' => ['180','205','205'],'lightcyan4' => ['122','139','139'],'lightgoldenrod' => ['238','221','130'],'lightgoldenrod1' => ['255','236','139'],'lightgoldenrod2' => ['238','220','130'],'lightgoldenrod3' => ['205','190','112'],'lightgoldenrod4' => ['139','129','76'],'lightgoldenrodyellow' => ['250','250','210'],'lightgray' => ['211','211','211'],'lightgreen' => ['144','238','144'],'lightgrey' => ['211','211','211'],'lightpink' => ['255','182','193'],'lightpink1' => ['255','174','185'],'lightpink2' => ['238','162','173'],'lightpink3' => ['205','140','149'],'lightpink4' => ['139','95','101'],'lightsalmon' => ['255','160','122'],'lightsalmon1' => ['255','160','122'],'lightsalmon2' => ['238','149','114'],'lightsalmon3' => ['205','129','98'],'lightsalmon4' => ['139','87','66'],'lightseagreen' => ['32','178','170'],'lightskyblue' => ['135','206','250'],'lightskyblue1' => ['176','226','255'],'lightskyblue2' => ['164','211','238'],'lightskyblue3' => ['141','182','205'],'lightskyblue4' => ['96','123','139'],'lightslateblue' => ['132','112','255'],'lightslategray' => ['119','136','153'],'lightslategrey' => ['119','136','153'],'lightsteelblue' => ['176','196','222'],'lightsteelblue1' => ['202','225','255'],'lightsteelblue2' => ['188','210','238'],'lightsteelblue3' => ['162','181','205'],'lightsteelblue4' => ['110','123','139'],'lightyellow' => ['255','255','224'],'lightyellow1' => ['255','255','224'],'lightyellow2' => ['238','238','209'],'lightyellow3' => ['205','205','180'],'lightyellow4' => ['139','139','122'],'lime green' => ['50','205','50'],'limegreen' => ['50','205','50'],'linen' => ['250','240','230'],'magenta' => ['255','0','255'],'magenta1' => ['255','0','255'],'magenta2' => ['238','0','238'],'magenta3' => ['205','0','205'],'magenta4' => ['139','0','139'],'maroon' => ['176','48','96'],'maroon1' => ['255','52','179'],'maroon2' => ['238','48','167'],'maroon3' => ['205','41','144'],'maroon4' => ['139','28','98'],'medium aquamarine' => ['102','205','170'],'medium blue' => ['0','0','205'],'medium orchid' => ['186','85','211'],'medium purple' => ['147','112','219'],'medium sea green' => ['60','179','113'],'medium slate blue' => ['123','104','238'],'medium spring green' => ['0','250','154'],'medium turquoise' => ['72','209','204'],'medium violet red' => ['199','21','133'],'mediumaquamarine' => ['102','205','170'],'mediumblue' => ['0','0','205'],'mediumorchid' => ['186','85','211'],'mediumorchid1' => ['224','102','255'],'mediumorchid2' => ['209','95','238'],'mediumorchid3' => ['180','82','205'],'mediumorchid4' => ['122','55','139'],'mediumpurple' => ['147','112','219'],'mediumpurple1' => ['171','130','255'],'mediumpurple2' => ['159','121','238'],'mediumpurple3' => ['137','104','205'],'mediumpurple4' => ['93','71','139'],'mediumseagreen' => ['60','179','113'],'mediumslateblue' => ['123','104','238'],'mediumspringgreen' => ['0','250','154'],'mediumturquoise' => ['72','209','204'],'mediumvioletred' => ['199','21','133'],'midnight blue' => ['25','25','112'],'midnightblue' => ['25','25','112'],'mint cream' => ['245','255','250'],'mintcream' => ['245','255','250'],'misty rose' => ['255','228','225'],'mistyrose' => ['255','228','225'],'mistyrose1' => ['255','228','225'],'mistyrose2' => ['238','213','210'],'mistyrose3' => ['205','183','181'],'mistyrose4' => ['139','125','123'],'moccasin' => ['255','228','181'],'navajo white' => ['255','222','173'],'navajowhite' => ['255','222','173'],'navajowhite1' => ['255','222','173'],'navajowhite2' => ['238','207','161'],'navajowhite3' => ['205','179','139'],'navajowhite4' => ['139','121','94'],'navy' => ['0','0','128'],'navy blue' => ['0','0','128'],'navyblue' => ['0','0','128'],'old lace' => ['253','245','230'],'oldlace' => ['253','245','230'],'olive drab' => ['107','142','35'],'olivedrab' => ['107','142','35'],'olivedrab1' => ['192','255','62'],'olivedrab2' => ['179','238','58'],'olivedrab3' => ['154','205','50'],'olivedrab4' => ['105','139','34'],'orange' => ['255','165','0'],'orange red' => ['255','69','0'],'orange1' => ['255','165','0'],'orange2' => ['238','154','0'],'orange3' => ['205','133','0'],'orange4' => ['139','90','0'],'orangered' => ['255','69','0'],'orangered1' => ['255','69','0'],'orangered2' => ['238','64','0'],'orangered3' => ['205','55','0'],'orangered4' => ['139','37','0'],'orchid' => ['218','112','214'],'orchid1' => ['255','131','250'],'orchid2' => ['238','122','233'],'orchid3' => ['205','105','201'],'orchid4' => ['139','71','137'],'pale goldenrod' => ['238','232','170'],'pale green' => ['152','251','152'],'pale turquoise' => ['175','238','238'],'pale violet red' => ['219','112','147'],'palegoldenrod' => ['238','232','170'],'palegreen' => ['152','251','152'],'palegreen1' => ['154','255','154'],'palegreen2' => ['144','238','144'],'palegreen3' => ['124','205','124'],'palegreen4' => ['84','139','84'],'paleturquoise' => ['175','238','238'],'paleturquoise1' => ['187','255','255'],'paleturquoise2' => ['174','238','238'],'paleturquoise3' => ['150','205','205'],'paleturquoise4' => ['102','139','139'],'palevioletred' => ['219','112','147'],'palevioletred1' => ['255','130','171'],'palevioletred2' => ['238','121','159'],'palevioletred3' => ['205','104','137'],'palevioletred4' => ['139','71','93'],'papaya whip' => ['255','239','213'],'papayawhip' => ['255','239','213'],'peach puff' => ['255','218','185'],'peachpuff' => ['255','218','185'],'peachpuff1' => ['255','218','185'],'peachpuff2' => ['238','203','173'],'peachpuff3' => ['205','175','149'],'peachpuff4' => ['139','119','101'],'peru' => ['205','133','63'],'pink' => ['255','192','203'],'pink1' => ['255','181','197'],'pink2' => ['238','169','184'],'pink3' => ['205','145','158'],'pink4' => ['139','99','108'],'plum' => ['221','160','221'],'plum1' => ['255','187','255'],'plum2' => ['238','174','238'],'plum3' => ['205','150','205'],'plum4' => ['139','102','139'],'powder blue' => ['176','224','230'],'powderblue' => ['176','224','230'],'purple' => ['160','32','240'],'purple1' => ['155','48','255'],'purple2' => ['145','44','238'],'purple3' => ['125','38','205'],'purple4' => ['85','26','139'],'red' => ['255','0','0'],'red1' => ['255','0','0'],'red2' => ['238','0','0'],'red3' => ['205','0','0'],'red4' => ['139','0','0'],'rosy brown' => ['188','143','143'],'rosybrown' => ['188','143','143'],'rosybrown1' => ['255','193','193'],'rosybrown2' => ['238','180','180'],'rosybrown3' => ['205','155','155'],'rosybrown4' => ['139','105','105'],'royal blue' => ['65','105','225'],'royalblue' => ['65','105','225'],'royalblue1' => ['72','118','255'],'royalblue2' => ['67','110','238'],'royalblue3' => ['58','95','205'],'royalblue4' => ['39','64','139'],'saddle brown' => ['139','69','19'],'saddlebrown' => ['139','69','19'],'salmon' => ['250','128','114'],'salmon1' => ['255','140','105'],'salmon2' => ['238','130','98'],'salmon3' => ['205','112','84'],'salmon4' => ['139','76','57'],'sandy brown' => ['244','164','96'],'sandybrown' => ['244','164','96'],'sea green' => ['46','139','87'],'seagreen' => ['46','139','87'],'seagreen1' => ['84','255','159'],'seagreen2' => ['78','238','148'],'seagreen3' => ['67','205','128'],'seagreen4' => ['46','139','87'],'seashell' => ['255','245','238'],'seashell1' => ['255','245','238'],'seashell2' => ['238','229','222'],'seashell3' => ['205','197','191'],'seashell4' => ['139','134','130'],'sienna' => ['160','82','45'],'sienna1' => ['255','130','71'],'sienna2' => ['238','121','66'],'sienna3' => ['205','104','57'],'sienna4' => ['139','71','38'],'sky blue' => ['135','206','235'],'skyblue' => ['135','206','235'],'skyblue1' => ['135','206','255'],'skyblue2' => ['126','192','238'],'skyblue3' => ['108','166','205'],'skyblue4' => ['74','112','139'],'slate blue' => ['106','90','205'],'slate gray' => ['112','128','144'],'slate grey' => ['112','128','144'],'slateblue' => ['106','90','205'],'slateblue1' => ['131','111','255'],'slateblue2' => ['122','103','238'],'slateblue3' => ['105','89','205'],'slateblue4' => ['71','60','139'],'slategray' => ['112','128','144'],'slategray1' => ['198','226','255'],'slategray2' => ['185','211','238'],'slategray3' => ['159','182','205'],'slategray4' => ['108','123','139'],'slategrey' => ['112','128','144'],'snow' => ['255','250','250'],'snow1' => ['255','250','250'],'snow2' => ['238','233','233'],'snow3' => ['205','201','201'],'snow4' => ['139','137','137'],'spring green' => ['0','255','127'],'springgreen' => ['0','255','127'],'springgreen1' => ['0','255','127'],'springgreen2' => ['0','238','118'],'springgreen3' => ['0','205','102'],'springgreen4' => ['0','139','69'],'steel blue' => ['70','130','180'],'steelblue' => ['70','130','180'],'steelblue1' => ['99','184','255'],'steelblue2' => ['92','172','238'],'steelblue3' => ['79','148','205'],'steelblue4' => ['54','100','139'],'tan' => ['210','180','140'],'tan1' => ['255','165','79'],'tan2' => ['238','154','73'],'tan3' => ['205','133','63'],'tan4' => ['139','90','43'],'thistle' => ['216','191','216'],'thistle1' => ['255','225','255'],'thistle2' => ['238','210','238'],'thistle3' => ['205','181','205'],'thistle4' => ['139','123','139'],'tomato' => ['255','99','71'],'tomato1' => ['255','99','71'],'tomato2' => ['238','92','66'],'tomato3' => ['205','79','57'],'tomato4' => ['139','54','38'],'turquoise' => ['64','224','208'],'turquoise1' => ['0','245','255'],'turquoise2' => ['0','229','238'],'turquoise3' => ['0','197','205'],'turquoise4' => ['0','134','139'],'violet' => ['238','130','238'],'violet red' => ['208','32','144'],'violetred' => ['208','32','144'],'violetred1' => ['255','62','150'],'violetred2' => ['238','58','140'],'violetred3' => ['205','50','120'],'violetred4' => ['139','34','82'],'wheat' => ['245','222','179'],'wheat1' => ['255','231','186'],'wheat2' => ['238','216','174'],'wheat3' => ['205','186','150'],'wheat4' => ['139','126','102'],'white' => ['255','255','255'],'white smoke' => ['245','245','245'],'whitesmoke' => ['245','245','245'],'yellow' => ['255','255','0'],'yellow green' => ['154','205','50'],'yellow1' => ['255','255','0'],'yellow2' => ['238','238','0'],'yellow3' => ['205','205','0'],'yellow4' => ['139','139','0'],'yellowgreen' => ['154','205','50']} EOT } PERL # NOTE: __create_class() __create_class => <<'PERL', sub __create_class { my $self = shift( @_ ); my $field = shift( @_ ) || return( $self->error( "No field was provided to create a dynamic class." ) ); my $def = shift( @_ ); my $class; if( $def->{_class} ) { $class = $def->{_class}; } else { my $new_class = $field; $new_class =~ tr/-/_/; $new_class =~ s/\_{2,}/_/g; $new_class = join( '', map( ucfirst( lc( $_ ) ), split( /\_/, $new_class ) ) ); $class = ( ref( $self ) || $self ) . "\::${new_class}"; } if( Class::Load::is_class_loaded( $class ) ) { my $ref = eval( "\\%${class}::" ); } unless( Class::Load::is_class_loaded( $class ) ) { my $type2func = { array => '_set_get_array', # Alias for 'array_as_object' array_object => '_set_get_array_as_object', array_as_object => '_set_get_array_as_object', boolean => '_set_get_boolean', class => '_set_get_class', class_array => '_set_get_class_array', class_array_object => '_set_get_class_array_object', code => '_set_get_code', datetime => '_set_get_datetime', decimal => '_set_get_number', file => '_set_get_file', float => '_set_get_number', glob => '_set_get_glob', hash => '_set_get_hash', hash_as_object => '_set_get_hash_as_mix_object', integer => '_set_get_number', ip => '_set_get_ip', long => '_set_get_number', number => '_set_get_number', object => '_set_get_object', object_no_init => '_set_get_object_without_init', object_array => '_set_get_object_array', object_array_object => '_set_get_object_array_object', scalar => '_set_get_scalar', scalar_as_object => '_set_get_scalar_as_object', scalar_or_object => '_set_get_scalar_or_object', uri => '_set_get_uri', uuid => '_set_get_uuid', version => '_set_get_version', }; # Alias $type2func->{string} = $type2func->{scalar}; my $perl = <{_fields} = [qw( EOT $perl .= join( ' ', sort( keys( %$def ) ) ) . "\n"; $perl .= <SUPER::init( \@_ ) ); } EOT my $call_sub = ( split( /::/, ( caller(1) )[3] ) )[-1]; my $call_frame = $call_sub eq '_set_get_class' ? 1 : 0; my( $pack, $file, $line ) = caller( $call_frame ); my $code_lines = []; foreach my $f ( sort( keys( %$def ) ) ) { my $info; # Allow for lazy field => $type_value definition instead of field => { type => $type_value } # Also helps trap if the definition is not an hash as we expect and avoid a perl error if( !ref( $def->{ $f } // '' ) ) { if( !defined( $def->{ $f } ) ) { warn( "Warning only: _set_get_class was called from package ${pack} at line ${line} in file ${file}, but the type provided has value 'undef', so we are skipping this field \"${f}\" in the creation of our virtual class.\n" ); next; } $info = { type => $def->{ $f } }; } elsif( ref( $def->{ $f } ) eq 'HASH' ) { $info = $def->{ $f }; if( !CORE::exists( $info->{type} ) || !CORE::length( $info->{type} // '' ) ) { warn( "Warning only: _set_get_class was called from package ${pack} at line ${line} in file ${file}, but the hash reference provided for this field \"${f}\" does not contain the property \"type\".\n" ); next; } } else { warn( "Warning only: _set_get_class was called from package ${pack} at line ${line} in file ${file}, but the type provided for this field \"${f}\" is unsupported: '", overload::StrVal( $def->{ $f } ), "'" ); next; } my $type = lc( $info->{type} ); # Convenience $info->{class} = $info->{package} if( $info->{package} && !length( $info->{class} ) ); if( !defined( $type ) ) { warn( "_set_get_class was called from package ${pack} at line ${line} in file ${file}, but the type provided is undefined for this field \"${f}\".\n" ); next; } elsif( !CORE::exists( $type2func->{ $type } ) ) { warn( "Warning only: _set_get_class was called from package ${pack} at line ${line} in file ${file}, but the type provided \"${type}\" is unknown to us, so we are skipping this field \"${f}\" in the creation of our virtual class.\n" . ( $type eq 'url' ? qq{Maybe you meant to use "uri" instead of "url" ?\n} : '' ) ); next; } my $func = $type2func->{ $type }; if( $type eq 'object' || $type eq 'object_no_init' || $type eq 'scalar_or_object' || $type eq 'object_array_object' || $type eq 'object_array' ) { if( !$info->{class} && !$info->{package} ) { warn( "Warning only: _set_get_class was called from package ${pack} at line ${line} in file ${file}, and class \"${class}\" field \"${f}\" is to require an object, but no object class name was provided. Use the \"class\" or \"package\" property parameter. So we are skipping this field \"${f}\" in the creation of our virtual class.\n" ); next; } my $this_class = $info->{class} || $info->{package}; CORE::push( @$code_lines, "sub $f { return( shift->${func}( '${f}', '${this_class}', \@_ ) ); }" ); } elsif( $type eq 'class' || $type eq 'class_array' || $type eq 'class_array_object' ) { my $this_def = $info->{definition} // $info->{def}; if( !CORE::exists( $info->{definition} ) && !CORE::exists( $info->{def} ) ) { warn( "Warning only: No dynamic class fields definition was provided for this field \"${f}\". Skipping this field.\n" ); next; } elsif( ref( $this_def ) ne 'HASH' ) { warn( "Warning only: I was expecting a fields definition hash reference for dynamic class field \"${f}\", but instead got '${this_def}'. Skipping this field.\n" ); next; } # my $d = Data::Dumper->new( [ $this_def ] ); # $d->Indent( 0 ); # $d->Purity( 1 ); # $d->Pad( '' ); # $d->Terse( 1 ); # $d->Sortkeys( 1 ); # my $hash_str = $d->Dump; my $hash_str = Data::Dump::dump( $this_def ); CORE::push( @$code_lines, "sub ${f} { return( shift->${func}( '${f}', ${hash_str}, \@_ ) ); }" ); } elsif( $type eq 'version' && ( exists( $info->{def} ) || exists( $info->{definition} ) ) ) { my $this_def = $info->{definition} // $info->{def}; my $hash_str = Data::Dump::dump( $this_def ); CORE::push( @$code_lines, "sub ${f} { return( shift->${func}( '${f}', ${hash_str}, \@_ ) ); }" ); } else { CORE::push( @$code_lines, "sub ${f} { return( shift->${func}( '${f}', \@_ ) ); }" ); } } CORE::push( @$code_lines, "sub _fields { return( shift->_set_get_array_as_object( '_fields', \@_ ) ); }" ); $perl .= join( "\n\n", @$code_lines ); $perl .= <as_hash ); } 1; EOT local $@; my $rc = eval( $perl ); die( "Unable to dynamically create module $class: $@" ) if( $@ ); } return( $class ); } PERL # NOTE: _can_overload _can_overload => <<'PERL', sub _can_overload { my $self = shift( @_ ); no overloading; # Nothing provided return if( !scalar( @_ ) ); return if( !defined( $_[0] ) ); return if( !Scalar::Util::blessed( $_[0] ) ); if( $self->_is_array( $_[1] ) ) { foreach my $op ( @{$_[1]} ) { return(0) unless( overload::Method( $_[0] => $op ) ); } return(1); } else { return( overload::Method( $_[0] => $_[1] ) ); } } PERL # NOTE: _get_datetime_regexp _get_datetime_regexp => <<'PERL', sub _get_datetime_regexp { my $self = shift( @_ ); my $elem = shift( @_ ); use utf8; unless( defined( $PARSE_DATE_FRACTIONAL1_RE ) ) { my $aliases = [qw( JST )]; if( $self->_load_class( 'DateTime::TimeZone::Catalog::Extend', { version => 'v0.2.0' } ) ) { $aliases = DateTime::TimeZone::Catalog::Extend->aliases; } my $tz_aliases = join( '|', @$aliases ); $PARSE_DATE_FRACTIONAL1_RE = qr/ (?\d{4}) (?[^\d\+]) (?\d{1,2}) [^\d\+] (?\d{1,2}) (?[\s\t]+) (?\d{1,2}) (?[^\d\+]) (?\d{1,2}) (?:[^\d\+](?\d{1,2}))? (? (?: (?[[:blank:]]*) (?[-+]?\d{2,4}) ) | (?: (?(?:[[:blank:]]+|[-+])) (?$tz_aliases) ) )? /x; } # 2019-06-19 23:23:57.000000000+0900 # From PostgreSQL: 2019-06-20 11:02:36.306917+09 # From SQLite: 2019-06-20 02:03:14 # From MySQL: 2019-06-20 11:04:01 # ISO 8601: 2019-06-20T11:08:27 # ISO 8601: 2019-06-20T11:08:27Z # 2022-11-17T08:12:31+0900 unless( defined( $PARSE_DATE_WITH_MILI_SECONDS_RE ) ) { $PARSE_DATE_WITH_MILI_SECONDS_RE = qr/ (?\d{4}) (?[-|\/]) (?\d{1,2}) [-|\/] (?\d{1,2}) (?[[:blank:]]+|T) (?: (?