Config-Model
view release on metacpan or search on metacpan
lib/Config/Model/Value.pm view on Meta::CPAN
#
# This file is part of Config-Model
#
# This software is Copyright (c) 2005-2022 by Dominique Dumont.
#
# This is free software, licensed under:
#
# The GNU Lesser General Public License, Version 2.1, February 1999
#
package Config::Model::Value 2.162;
use v5.20;
use strict;
use warnings;
use Mouse;
use Mouse::Util::TypeConstraints;
use MouseX::StrictConstructor;
use Parse::RecDescent 1.90.0;
use Data::Dumper ();
use Config::Model::Exception;
use Config::Model::ValueComputer;
use Config::Model::IdElementReference;
use Config::Model::Warper;
use Config::Model::Value::Update;
use Log::Log4perl qw(get_logger :levels);
use Scalar::Util qw/weaken/;
use Carp;
use Storable qw/dclone/;
use Path::Tiny;
use List::Util qw(any) ;
extends qw/Config::Model::AnyThing/;
with "Config::Model::Role::WarpMaster";
with "Config::Model::Role::Grab";
with "Config::Model::Role::HelpAsText";
with "Config::Model::Role::ComputeFunction";
use feature qw/postderef signatures/;
no warnings qw/experimental::postderef experimental::signatures/;
my $logger = get_logger("Tree::Element::Value");
my $user_logger = get_logger("User");
my $change_logger = get_logger("Anything::Change");
my $fix_logger = get_logger("Anything::Fix");
our $nowarning = 0; # global variable to silence warnings. Only used for tests
enum ValueType => qw/boolean enum uniline string integer number reference file dir/;
has fixes => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } );
has [qw/warp compute computed_refer_to backup migrate_from/] =>
( is => 'rw', isa => 'Maybe[HashRef]' );
has compute_obj => (
is => 'ro',
isa => 'Maybe[Config::Model::ValueComputer]',
builder => '_build_compute_obj',
lazy => 1
);
has [qw/write_as/] => ( is => 'rw', isa => 'Maybe[ArrayRef]' );
has [qw/refer_to _data replace_follow/] => ( is => 'rw', isa => 'Maybe[Str]' );
has value_type => ( is => 'rw', isa => 'ValueType' );
my @common_int_params = qw/min max mandatory /;
has \@common_int_params => ( is => 'ro', isa => 'Maybe[Int]' );
my @common_hash_params = qw/replace assert update warn_if_match warn_unless_match warn_if warn_unless help/;
has \@common_hash_params => ( is => 'ro', isa => 'Maybe[HashRef]' );
has update_obj => (
is => 'ro',
isa => 'Undef|Config::Model::Value::Update',
handles => [qw/get_update_value/],
lazy => 1,
builder => sub ($self) {
if (my $ref = $self->update) {
return Config::Model::Value::Update->new(%$ref, location => $self->location);
}
}
);
my @common_list_params = qw/choice/;
has \@common_list_params => ( is => 'ro', isa => 'Maybe[ArrayRef]' );
my @common_str_params = qw/default upstream_default convert match grammar warn/;
has \@common_str_params => ( is => 'ro', isa => 'Maybe[Str]' );
my @warp_accessible_params =
( @common_int_params, @common_str_params, @common_list_params, @common_hash_params );
my @allowed_warp_params = ( @warp_accessible_params, qw/level help/ );
my @backup_list = ( @allowed_warp_params, qw/migrate_from/ );
has compute_is_upstream_default =>
( is => 'ro', isa => 'Bool', lazy => 1, builder => '_compute_is_upstream_default' );
sub _compute_is_upstream_default {
my $self = shift;
return 0 unless defined $self->compute;
return $self->compute_obj->use_as_upstream_default;
}
has compute_is_default =>
( is => 'ro', isa => 'Bool', lazy => 1, builder => '_compute_is_default' );
sub _compute_is_default {
my $self = shift;
return 0 unless defined $self->compute;
return !$self->compute_obj->use_as_upstream_default;
}
has error_list => (
is => 'ro',
isa => 'ArrayRef',
default => sub { [] },
traits => ['Array'],
handles => {
add_error => 'push',
clear_errors => 'clear',
has_error => 'count',
all_errors => 'elements',
is_ok => 'is_empty'
} );
sub error_msg ($self) {
my $msg = '';
if ($self->has_error) {
my @add;
push @add, $self->compute_obj->compute_info if $self->compute_obj;
push @add, $self->{_migrate_from}->compute_info if $self->{_migrate_from};
$msg = join("\n", $self->all_errors, @add);
}
return $msg;
}
has warning_list => (
is => 'ro',
isa => 'ArrayRef',
default => sub { [] },
traits => ['Array'],
handles => {
add_warning => 'push',
clear_warnings => 'clear',
warning_msg => [ join => "\n\t" ],
has_warning => 'count',
has_warnings => 'count',
all_warnings => 'elements',
} );
# as some information must be backed up even though they are not
# attributes, we cannot move code below in BUILD.
around BUILDARGS => sub ($orig, $class, %args) {
my %h = map { ( $_ => $args{$_} ); } grep { defined $args{$_} } @backup_list;
return $class->$orig( backup => dclone( \%h ), %args );
};
sub BUILD {
my $self = shift;
$self->set_properties(); # set will use backup data
# used when self is a warped slave
if ( my $warp_info = $self->warp ) {
$self->{warper} = Config::Model::Warper->new(
warped_object => $self,
%$warp_info,
allowed => \@allowed_warp_params
);
}
$self->_init;
return $self;
}
override 'needs_check' => sub ($self, @args) {
if ($self->instance->layered) {
# don't check value and don't store value in layered mode
return 0;
}
elsif (@args) {
return super();
}
else {
# some items like idElementReference are too complex to propagate
# a change notification back to the value using them. So an error or
# warning must always be rechecked.
return ($self->value_type eq 'reference' or super()) ;
}
};
around notify_change => sub ($orig, $self, %args) {
my $check_done = $args{check_done} || 0;
return if $self->instance->initial_load and not $args{really};
if ($change_logger->is_trace) {
my @a = map { ( $_ => $args{$_} // '<undef>' ); } sort keys %args;
$change_logger->trace( "called while needs_check is ",
$self->needs_check, " for ", $self->name, " with ", join( ' ', @a ) );
}
$self->needs_check(1) unless $check_done;
{
croak "needless change with $args{new}"
if defined $args{old}
and defined $args{new}
and $args{old} eq $args{new};
}
$args{new} = $self->map_write_as( $args{new} );
$args{old} = $self->map_write_as( $args{old} );
$self->$orig( %args, value_type => $self->value_type );
# shake all warped or computed objects that depends on me
foreach my $s ( $self->get_depend_slave ) {
$change_logger->debug( "calling needs_check on slave ", $s->name )
if $change_logger->is_debug;
$s->needs_check(1);
}
return;
lib/Config/Model/Value.pm view on Meta::CPAN
eval { qr/$regexp/; };
if ($@) {
Config::Model::Exception::Model->throw(
object => $self,
error => "Unvalid $what regexp '$regexp': $@"
);
}
my $v = $h->{$regexp};
Config::Model::Exception::Model->throw(
object => $self,
error => "value of $what regexp '$regexp' is not a hash ref but '$v'"
) unless ref $v eq 'HASH';
}
return;
}
sub setup_grammar_check {
my ( $self, $ref ) = @_;
my $str = $self->{grammar} = delete $ref->{grammar};
return unless defined $str;
my $vt = $self->{value_type};
if ( $vt ne 'uniline' and $vt ne 'string' ) {
Config::Model::Exception::Model->throw(
object => $self,
error => "Can't use match regexp with $vt, " . "expected 'uniline' or 'string'"
);
}
my @lines = split /\n/, $str;
chomp @lines;
if ( $lines[0] !~ /^check:/ ) {
$lines[0] = 'check: ' . $lines[0] . ' /\s*\Z/ ';
}
my $actual_grammar = join( "\n", @lines ) . "\n";
$logger->debug( $self->name, " setup_grammar_check with '$actual_grammar'" );
eval { $self->{validation_parser} = Parse::RecDescent->new($actual_grammar); };
if ($@) {
Config::Model::Exception::Model->throw(
object => $self,
error => "Unvalid grammar for '$str': $@"
);
}
return;
}
# warning : call to 'set' are not cumulative. Default value are always
# restored. Lest keeping track of what was modified with 'set' is
# too confusing.
sub set_properties ($self, @args) {
# cleanup all parameters that are handled by warp
for ( @allowed_warp_params ) { delete $self->{$_} }
# merge data passed to the constructor with data passed to set_properties
my %args = ( %{ $self->backup // {} }, @args );
# these are handled by Node or Warper
for ( qw/level/ ) { delete $args{$_} }
if ( $logger->is_trace ) {
$logger->trace( "Leaf '" . $self->name . "' set_properties called with '",
join( "','", sort keys %args ), "'" );
}
if ( defined $args{value_type}
and $args{value_type} eq 'reference'
and not defined $self->{refer_to}
and not defined $self->{computed_refer_to} ) {
Config::Model::Exception::Model->throw(
object => $self,
error => "Missing 'refer_to' or 'computed_refer_to' "
. "parameter with 'reference' value_type "
);
}
for (qw/min max mandatory warn replace_follow assert warn_if warn_unless
write_as/) {
$self->{$_} = delete $args{$_} if defined $args{$_};
}
if ($args{replace}) {
$self->{replace} = delete $args{replace};
my $old = $self->_fetch_no_check;
if (defined $old) {
my $new = $self->apply_replace($old);
$self->_store_value($new);
}
}
$self->set_help( \%args );
$self->set_update( \%args );
$self->set_value_type( \%args );
$self->set_default( \%args );
$self->set_convert( \%args ) if defined $args{convert};
$self->setup_match_regexp( match => \%args ) if defined $args{match};
foreach (qw/warn_if_match warn_unless_match/) {
$self->check_validation_regexp( $_ => \%args ) if defined $args{$_};
}
$self->setup_grammar_check( \%args ) if defined $args{grammar};
# cannot be warped
$self->set_migrate_from( \%args ) if defined $args{migrate_from};
Config::Model::Exception::Model->throw(
object => $self,
error => "write_as is allowed only with boolean values"
) if defined $self->{write_as} and $self->{value_type} ne 'boolean';
Config::Model::Exception::Model->throw(
object => $self,
error => "Unexpected parameters: " . join( ' ', each %args ) ) if scalar keys %args;
if ( $self->has_warped_slaves ) {
my $value = $self->_fetch_no_check;
$self->trigger_warp($value);
lib/Config/Model/Value.pm view on Meta::CPAN
}
elsif ( defined $self->{computed_refer_to} ) {
$self->{ref_object} = Config::Model::IdElementReference->new(
computed_refer_to => $self->{computed_refer_to},
config_elt => $self,
);
# refer_to registration is done for all element that are used as
# variable for complex reference (ie '- $foo' , {foo => '- bar'} )
$self->register_in_other_value( $self->{computed_refer_to}{variables} );
}
else {
croak "value's submit_to_refer_to: undefined refer_to or computed_refer_to";
}
return;
}
sub setup_reference_choice ($self, @args) {
return $self->setup_enum_choice(@args);
}
sub reference_object {
my $self = shift;
return $self->{ref_object};
}
sub built_in {
carp "warning: built_in sub is deprecated, use upstream_default";
goto &upstream_default;
}
sub name {
my $self = shift;
my $name = $self->{parent}->name . ' ' . $self->{element_name};
$name .= ':' . $self->{index_value} if defined $self->{index_value};
return $name;
}
sub get_type {
return 'leaf';
}
sub get_cargo_type {
return 'leaf';
}
sub can_store {
my $self = shift;
if ( not defined $self->compute ) {
return 1;
}
if ( $self->compute_obj->allow_user_override ) {
return 1;
}
return;
}
sub get_default_choice {
my $self = shift;
return @{ $self->{backup}{choice} || [] };
}
sub get_choice {
my $self = shift;
# just in case the reference_object has been changed
if ( defined $self->{refer_to} or defined $self->{computed_refer_to} ) {
$self->{ref_object}->get_choice_from_referred_to;
}
return @{ $self->{choice} || [] };
}
sub get_info {
my $self = shift;
my $type = $self->value_type;
my @choice = $type eq 'enum' ? $self->get_choice : ();
my $choice_str = @choice ? ' (' . join( ',', @choice ) . ')' : '';
my @items = ( 'type: ' . $self->value_type . $choice_str, );
my $std = $self->fetch(qw/mode standard check no/);
if ( defined $self->upstream_default ) {
push @items, "upstream_default value: " . $self->map_write_as( $self->upstream_default );
}
elsif ( defined $std ) {
push @items, "default value: $std";
}
elsif ( defined $self->refer_to ) {
push @items, "reference to: " . $self->refer_to;
}
elsif ( defined $self->computed_refer_to ) {
push @items, "computed reference to: " . $self->computed_refer_to;
}
if ($self->update) {
push @items, "update value from " . $self->update_obj->get_info;
}
my $m = $self->mandatory;
push @items, "is mandatory: " . ( $m ? 'yes' : 'no' ) if defined $m;
foreach my $what (qw/min max warn grammar/) {
my $v = $self->$what();
push @items, "$what value: $v" if defined $v;
}
foreach my $what (qw/warn_if_match warn_unless_match/) {
my $v = $self->$what();
foreach my $k ( keys %$v ) {
push @items, "$what value: $k";
}
}
foreach my $what (qw/write_as/) {
my $v = $self->$what();
push @items, "$what: @$v" if defined $v;
}
( run in 0.634 second using v1.01-cache-2.11-cpan-39bf76dae61 )