use
5.20.0;
our
@EXPORT_OK
=
qw/cme initialize_log4perl/
;
use
feature
qw/signatures postderef/
;
no
warnings
qw/experimental::signatures experimental::postderef/
;
my
$force_default_log
;
sub
force_usage_of_default_log_config () {
return
$force_default_log
= 1;
}
my
$legacy_logger
= get_logger(
"Model::Legacy"
) ;
my
$loader_logger
= get_logger(
"Model::Loader"
) ;
my
$logger
= get_logger(
"Model"
) ;
my
$model_storage
;
enum
LegacyTreament
=>
qw/die warn ignore/
;
has
skip_include
=> (
isa
=>
'Bool'
,
is
=>
'ro'
,
default
=> 0 );
has
model_dir
=> (
isa
=>
'Str'
,
is
=>
'ro'
,
default
=>
'Config/Model/models'
);
has
legacy
=> (
isa
=>
'LegacyTreament'
,
is
=>
'ro'
,
default
=>
'warn'
);
has
instances
=> (
isa
=>
'HashRef[Config::Model::Instance]'
,
is
=>
'ro'
,
default
=>
sub
{ {} },
traits
=> [
'Hash'
],
handles
=> {
store_instance
=>
'set'
,
get_instance
=>
'get'
,
has_instance
=>
'defined'
,
},
);
has
raw_models
=> (
isa
=>
'HashRef'
,
is
=>
'ro'
,
default
=>
sub
{ {} },
traits
=> [
'Hash'
],
handles
=> {
raw_model_exists
=>
'exists'
,
raw_model_defined
=>
'defined'
,
raw_model
=>
'get'
,
get_raw_model
=>
'get'
,
store_raw_model
=>
'set'
,
raw_model_names
=>
'keys'
,
},
);
has
normalized_models
=> (
isa
=>
'HashRef'
,
is
=>
'ro'
,
default
=>
sub
{ {} },
traits
=> [
'Hash'
],
handles
=> {
normalized_model_exists
=>
'exists'
,
normalized_model_defined
=>
'defined'
,
normalized_model
=>
'get'
,
store_normalized_model
=>
'set'
,
normalized_model_names
=>
'keys'
,
},
);
has
models
=> (
isa
=>
'HashRef'
,
is
=>
'ro'
,
default
=>
sub
{ {} },
traits
=> [
'Hash'
],
handles
=> {
model_exists
=>
'exists'
,
model_defined
=>
'defined'
,
_get_model
=>
'get'
,
_store_model
=>
'set'
,
},
);
has
model_snippets
=> (
isa
=>
'ArrayRef'
,
is
=>
'ro'
,
default
=>
sub
{ [] },
traits
=> [
'Array'
],
handles
=> {
add_snippet
=>
'push'
,
all_snippets
=>
'elements'
,
},
);
enum
'LOG_LEVELS'
, [
qw/ERROR WARN INFO DEBUG TRACE/
];
has
log_level
=> (
isa
=>
'LOG_LEVELS'
,
is
=>
'ro'
,
);
has
skip_inheritance
=> (
isa
=>
'Bool'
,
is
=>
'ro'
,
default
=> 0,
trigger
=>
sub
{
my
$self
=
shift
;
$self
->show_legacy_issue(
"skip_inheritance is deprecated, use skip_include"
);
$self
->skip_include =
$self
->skip_inheritance;
} );
around
BUILDARGS
=>
sub
(
$orig
,
$class
,
%args
) {
my
%new
;
foreach
my
$k
(
keys
%args
) {
if
(
defined
$args
{
$k
}) {
$new
{
$k
} =
$args
{
$k
};
}
else
{
croak(
"Config::Model new: passing undefined constructor argument is deprecated ($k argument)\n"
);
}
}
return
$class
->
$orig
(
%new
);
};
sub
initialize_log4perl (
@args
) {
if
(
ref
$args
[0]) {
shift
@args
;
}
my
%args
=
@args
;
my
$log4perl_syst_conf_file
= path(
'/etc/log4config-model.conf'
);
my
$home
= File::HomeDir->my_home //
''
;
my
$log4perl_user_conf_file
= path(
$home
.
'/.log4config-model'
);
my
$fallback_conf_file
= path(
$INC
{
"Config/Model.pm"
})
->parent->child(
"Model/log4perl.conf"
) ;
my
$log4perl_file
=
$force_default_log
?
$fallback_conf_file
:
$log4perl_user_conf_file
->is_file ?
$log4perl_user_conf_file
:
$log4perl_syst_conf_file
->is_file ?
$log4perl_syst_conf_file
:
$fallback_conf_file
;
my
%log4perl_conf
=
map
{
split
/\s*=\s*/,
$_
,2; }
grep
{
chomp
; ! /^\s*
my
$verbose
=
$args
{verbose};
if
(
defined
$verbose
) {
my
@loggers
=
ref
$verbose
?
@$verbose
:
$verbose
;
foreach
my
$logger
(
@loggers
) {
$log4perl_conf
{
"log4perl.logger.Verbose.$logger"
} =
"INFO, PlainMsgOnScreen"
;
}
}
Log::Log4perl::init(\
%log4perl_conf
);
return
\
%log4perl_conf
;
}
sub
BUILD {
my
$self
=
shift
;
my
$args
=
shift
;
initialize_log4perl(
verbose
=>
$args
->{verbose})
unless
Log::Log4perl->initialized();
return
;
}
sub
show_legacy_issue {
my
$self
=
shift
;
my
$ref
=
shift
;
my
$behavior
=
shift
||
$self
->legacy;
my
@msg
=
ref
$ref
?
@$ref
:
$ref
;
unshift
@msg
,
"Model "
;
if
(
$behavior
eq
'die'
) {
die
@msg
,
"\n"
;
}
elsif
(
$behavior
eq
'warn'
) {
$legacy_logger
->
warn
(
@msg
);
}
elsif
(
$behavior
eq
'note'
) {
$legacy_logger
->info(
@msg
);
}
return
;
}
sub
_tweak_instance_args {
my
(
$args
) =
@_
;
my
$application
=
$args
->{application} ;
my
$cat
=
''
;
if
(
defined
$application
) {
my
(
$categories
,
$appli_info
,
$appli_map
) = Config::Model::Lister::available_models;
if
(not
$args
->{root_class_name}) {
$args
->{root_class_name} =
$appli_map
->{
$application
} ||
die
"Unknown application $application. Expected one of "
.
join
(
' '
,
sort
keys
%$appli_map
).
"\n"
;
}
$cat
=
$appli_info
->{_category} //
''
;
$args
->{config_dir} //=
$appli_info
->{
$application
}{config_dir};
$args
->{appli_info} =
$appli_info
->{
$application
} // {};
}
my
$app_name
=
$application
;
if
(
$cat
eq
'application'
) {
$application
.=
" in "
. cwd;
}
$args
->{name}
=
delete
$args
->{instance_name}
||
delete
$args
->{name}
||
$app_name
||
'default'
;
return
;
}
sub
cme (
@args
) {
my
%args
=
@args
== 1 ? (
application
=>
$args
[0]) :
@args
;
if
(
my
$force
=
delete
$args
{
'force-load'
}) {
$args
{check} =
'no'
if
$force
;
}
my
$cat
=_tweak_instance_args(\
%args
);
my
$m_args
=
delete
$args
{model_args} // {} ;
$model_storage
//= Config::Model->new(
%$m_args
);
return
$model_storage
->instance(
%args
);
}
sub
instance (
$self
,
@args
) {
my
%args
=
@args
== 1 ? (
application
=>
$args
[0]) :
@args
;
_tweak_instance_args(\
%args
);
if
(
$args
{name} and
$self
->has_instance(
$args
{name}) ) {
return
$self
->get_instance(
$args
{name});
}
croak
"Model: can't create instance without application or root_class_name "
unless
$args
{root_class_name};
if
(
defined
$args
{model_file} ) {
my
$file
=
delete
$args
{model_file};
$self
->load(
$args
{root_class_name},
$file
);
}
my
$i
= Config::Model::Instance->new(
config_model
=>
$self
,
%args
);
$self
->store_instance(
$args
{name},
$i
);
return
$i
;
}
sub
instance_names {
my
$self
=
shift
;
my
@all
=
sort
keys
%{
$self
->instances };
return
@all
;
}
my
@legal_params_to_move
= (
qw/read_config write_config rw_config/
,
'generated_by'
,
qw/class_description author copyright gist license include include_after include_backend class/
);
my
@other_legal_params
=
qw/ author element status description summary level accept/
;
sub
create_config_class (
$self
,
%raw_model
) {
my
$config_class_name
=
delete
$raw_model
{name}
or croak
"create_config_class: no config class name"
;
get_logger(
"Model"
)->info(
"Creating class $config_class_name"
);
if
(
$self
->model_exists(
$config_class_name
) ) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"create_config_class: attempt to clobber $config_class_name"
.
" config class name "
);
}
$self
->store_raw_model(
$config_class_name
, dclone( \
%raw_model
) );
my
$model
=
$self
->normalize_class_parameters(
$config_class_name
, \
%raw_model
);
$self
->store_normalized_model(
$config_class_name
,
$model
);
return
$config_class_name
;
}
sub
merge_included_class {
my
(
$self
,
$config_class_name
) =
@_
;
my
$normalized_model
=
$self
->normalized_model(
$config_class_name
);
my
$model
= dclone
$normalized_model
;
if
(
$self
->skip_include and
defined
$normalized_model
->{include} ) {
my
$inc
=
$normalized_model
->{include};
$model
->{include} =
ref
$inc
?
$inc
: [
$inc
];
$model
->{include_after} =
$normalized_model
->{include_after}
if
defined
$normalized_model
->{include_after};
}
else
{
$self
->include_class(
$config_class_name
,
$model
);
}
if
(
$self
->skip_include and
defined
$normalized_model
->{include_backend} ) {
my
$inc
=
$normalized_model
->{include_backend};
$model
->{include_backend} =
ref
$inc
?
$inc
: [
$inc
];
}
else
{
$self
->include_backend(
$config_class_name
,
$model
);
}
return
$model
;
}
sub
include_backend {
my
$self
=
shift
;
my
$class_name
=
shift
|| croak
"include_backend: undef includer"
;
my
$target_model
=
shift
||
die
"include_backend:: undefined target_model"
;
my
$included_classes
=
delete
$target_model
->{include_backend};
return
()
unless
defined
$included_classes
;
foreach
my
$included_class
(
@$included_classes
) {
my
$included_model
=
$self
->get_model_clone(
$included_class
);
foreach
my
$rw
(
qw/rw_config read_config write_config config_dir/
) {
if
(
$target_model
->{
$rw
} and
$included_model
->{
$rw
}) {
my
$msg
=
"Included $rw from $included_class cannot clobber "
.
"existing data in $class_name"
;
Config::Model::Exception::ModelDeclaration->throw(
error
=>
$msg
);
}
elsif
(
$included_model
->{
$rw
}) {
$target_model
->{
$rw
} =
$included_model
->{
$rw
};
}
}
}
return
;
}
sub
normalize_class_parameters {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$normalized_model
=
shift
||
die
;
my
$model
= {};
my
$raw_name
=
delete
$normalized_model
->{name};
if
(
defined
$raw_name
and
$config_class_name
ne
$raw_name
) {
my
$e
=
"internal: config_class_name $config_class_name ne model name $raw_name"
;
Config::Model::Exception::ModelDeclaration->throw(
error
=>
$e
);
}
my
@element_list
;
my
@compact_list
= @{
$normalized_model
->{element} || [] };
while
(
@compact_list
) {
my
(
$item
,
$info
) =
splice
@compact_list
, 0, 2;
push
@element_list
,
ref
(
$item
) ?
@$item
: (
$item
);
}
if
(
defined
$normalized_model
->{inherit_after} ) {
$self
->show_legacy_issue([
"Model $config_class_name: inherit_after is deprecated "
,
"in favor of include_after"
]);
$normalized_model
->{include_after} =
delete
$normalized_model
->{inherit_after};
}
if
(
defined
$normalized_model
->{inherit} ) {
$self
->show_legacy_issue(
"Model $config_class_name: inherit is deprecated in favor of include"
);
$normalized_model
->{include} =
delete
$normalized_model
->{inherit};
}
foreach
my
$info
(
@legal_params_to_move
) {
next
unless
defined
$normalized_model
->{
$info
};
$model
->{
$info
} =
delete
$normalized_model
->{
$info
};
}
$self
->translate_legacy_backend_info(
$config_class_name
,
$model
);
my
@accept_list
;
my
%accept_hash
;
my
$accept_info
=
delete
$normalized_model
->{
'accept'
} || [];
while
(
@$accept_info
) {
my
$name_match
=
shift
@$accept_info
;
if
(
ref
$name_match
) {
my
$implicit
=
defined
$name_match
->{name_match} ?
''
:
'implicit '
;
unshift
@$accept_info
,
$name_match
;
$name_match
=
delete
$name_match
->{name_match} ||
'.*'
;
$logger
->
warn
(
"class $config_class_name: name_match ($implicit$name_match)"
,
" in accept is deprecated"
);
}
push
@accept_list
,
$name_match
;
$accept_hash
{
$name_match
} =
shift
@$accept_info
;
}
$model
->{
accept
} = \
%accept_hash
;
$model
->{accept_list} = \
@accept_list
;
my
%check_list
;
foreach
(
@element_list
) {
$check_list
{
$_
}++ };
my
@extra
=
grep
{
$check_list
{
$_
} > 1 }
keys
%check_list
;
if
(
@extra
) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"class $config_class_name: @extra element "
.
"is declared more than once. Check the included parts"
);
}
$self
->handle_experience_permission(
$config_class_name
,
$normalized_model
);
foreach
my
$info_name
(
qw/element status description summary level/
) {
my
$raw_compact_info
=
delete
$normalized_model
->{
$info_name
};
next
unless
defined
$raw_compact_info
;
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Data for parameter $info_name of $config_class_name"
.
" is not an array ref"
)
unless
ref
(
$raw_compact_info
) eq
'ARRAY'
;
my
@raw_info
=
@$raw_compact_info
;
while
(
@raw_info
) {
my
(
$item
,
$info
) =
splice
@raw_info
, 0, 2;
my
@element_names
=
ref
(
$item
) ?
@$item
: (
$item
);
if
(
$info_name
eq
'element'
) {
$self
->translate_legacy_info(
$config_class_name
,
$element_names
[0],
$info
);
$self
->handle_experience_permission(
$config_class_name
,
$info
);
foreach
(
@element_names
) {
$model
->{element}{
$_
} = dclone(
$info
); };
}
elsif
(
$info_name
=~ /description|level|summary|status/ ) {
foreach
(
@element_names
) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"create class $config_class_name: '$info_name' "
.
"declaration for non declared element '$_'"
)
unless
defined
$model
->{element}{
$_
};
$model
->{element}{
$_
}{
$info_name
} ||=
$info
;
}
}
else
{
die
"Unexpected element $item in $config_class_name model"
;
}
}
}
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"create class $config_class_name: unexpected "
.
"parameters '"
.
join
(
', '
,
sort
keys
%$normalized_model
) .
"' "
.
"Expected '"
.
join
(
"', '"
,
@legal_params_to_move
,
@other_legal_params
)
.
"'"
)
if
keys
%$normalized_model
;
$model
->{element_list} = \
@element_list
;
return
$model
;
}
sub
handle_experience_permission {
my
(
$self
,
$config_class_name
,
$model
) =
@_
;
if
(
delete
$model
->{permission}) {
die
"$config_class_name: parameter permission is obsolete\n"
;
}
if
(
delete
$model
->{experience}) {
carp
"experience parameter is deprecated"
;
}
return
;
}
sub
translate_legacy_info {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
$self
->translate_warped_node_info(
$config_class_name
,
$elt_name
,
'warped_node'
,
$info
);
if
(
defined
$info
->{warp} ) {
$self
->translate_warp_info(
$config_class_name
,
$elt_name
,
$info
->{type},
$info
->{warp} );
}
$self
->translate_cargo_info(
$config_class_name
,
$elt_name
,
$info
);
if
(
defined
$info
->{cargo}
&&
defined
$info
->{cargo}{type}
&&
$info
->{cargo}{type} eq
'warped_node'
) {
$self
->translate_warped_node_info(
$config_class_name
,
$elt_name
,
'warped_node'
,
$info
->{cargo} );
}
if
(
defined
$info
->{cargo}
and
defined
$info
->{cargo}{warp} ) {
$self
->translate_warp_info(
$config_class_name
,
$elt_name
,
$info
->{cargo}{type},
$info
->{cargo}{warp} );
}
if
(
defined
$info
->{compute} ) {
$self
->translate_compute_info(
$config_class_name
,
$elt_name
,
$info
,
'compute'
);
$self
->translate_allow_compute_override(
$config_class_name
,
$elt_name
,
$info
);
}
if
(
defined
$info
->{cargo}
and
defined
$info
->{cargo}{compute} ) {
$self
->translate_compute_info(
$config_class_name
,
$elt_name
,
$info
->{cargo},
'compute'
);
$self
->translate_allow_compute_override(
$config_class_name
,
$elt_name
,
$info
->{cargo} );
}
if
(
defined
$info
->{refer_to} ) {
$self
->translate_compute_info(
$config_class_name
,
$elt_name
,
$info
,
refer_to
=>
'computed_refer_to'
);
}
if
(
defined
$info
->{cargo}
and
defined
$info
->{cargo}{refer_to} ) {
$self
->translate_compute_info(
$config_class_name
,
$elt_name
,
$info
->{cargo},
refer_to
=>
'computed_refer_to'
);
}
if
(
defined
$info
->{type}
and (
$info
->{type} eq
'list'
or
$info
->{type} eq
'hash'
) ) {
if
(
defined
$info
->{
default
} ) {
$self
->translate_id_default_info(
$config_class_name
,
$elt_name
,
$info
);
}
if
(
defined
$info
->{auto_create} ) {
$self
->translate_id_auto_create(
$config_class_name
,
$elt_name
,
$info
);
}
$self
->translate_id_min_max(
$config_class_name
,
$elt_name
,
$info
);
$self
->translate_id_names(
$config_class_name
,
$elt_name
,
$info
);
if
(
defined
$info
->{warp} ) {
my
$rules_a
=
$info
->{warp}{rules};
my
%h
=
@$rules_a
;
foreach
my
$rule_effect
(
values
%h
) {
$self
->translate_id_names(
$config_class_name
,
$elt_name
,
$rule_effect
);
$self
->translate_id_min_max(
$config_class_name
,
$elt_name
,
$rule_effect
);
next
unless
defined
$rule_effect
->{
default
};
$self
->translate_id_default_info(
$config_class_name
,
$elt_name
,
$rule_effect
);
}
}
$self
->translate_id_class(
$config_class_name
,
$elt_name
,
$info
);
}
if
(
defined
$info
->{type} and (
$info
->{type} eq
'leaf'
) ) {
$self
->translate_legacy_builtin(
$config_class_name
,
$info
,
$info
, );
}
if
(
defined
$info
->{type} and (
$info
->{type} eq
'check_list'
) ) {
$self
->translate_legacy_built_in_list(
$config_class_name
,
$info
,
$info
, );
}
$legacy_logger
->debug(
Data::Dumper->Dump( [
$info
], [
'translated_'
.
$elt_name
] )
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_legacy_backend_info {
my
(
$self
,
$config_class_name
,
$model
) =
@_
;
foreach
my
$config
(
qw/read_config write_config/
) {
my
$ref
=
$model
->{
$config
};
if
(
$ref
and
ref
(
$ref
) eq
'ARRAY'
) {
if
(
@$ref
== 1) {
$model
->{
$config
} =
$ref
->[0];
}
elsif
(
@$ref
> 1){
$self
->show_legacy_issue(
"$config_class_name $config: multiple backends are obsolete. You now must use only one backend."
,
'die'
);
}
}
}
if
(
$model
->{read_config}) {
$self
->show_legacy_issue(
"$config_class_name: read_config specification is deprecated, please move in rw_config"
,
'warn'
);
$model
->{rw_config} =
delete
$model
->{read_config};
}
if
(
$model
->{write_config}) {
$self
->show_legacy_issue(
"$config_class_name: write_config specification is deprecated, please merge with read_config and move in rw_config"
,
'warn'
);
foreach
(
keys
%{
$model
->{write_config}}) {
$model
->{rw_config}{
$_
} =
$model
->{write_config}{
$_
}
}
delete
$model
->{write_config};
}
my
$ref
=
$model
->{
'rw_config'
} ||
return
;
die
"undefined backend in rw_config spec of class $config_class_name\n"
unless
$ref
->{backend} ;
if
(
$ref
->{backend} eq
'custom'
) {
my
$msg
=
"$config_class_name: custom read/write backend is obsolete."
.
" Please replace with a backend inheriting Config::Model::Backend::Any"
;
$self
->show_legacy_issue(
$msg
,
'die'
);
}
if
(
$ref
->{backend} =~ /^(perl|ini|cds)$/ ) {
my
$backend
=
$ref
->{backend};
$self
->show_legacy_issue(
"$config_class_name: deprecated backend '$backend'. Should be '$ {backend}_file'"
,
'warn'
);
$ref
->{backend} .=
"_file"
;
}
if
(
defined
$ref
->{allow_empty} ) {
$self
->show_legacy_issue(
"$config_class_name: backend $ref->{backend}: allow_empty is deprecated. Use auto_create"
,
'warn'
);
$ref
->{auto_create} =
delete
$ref
->{allow_empty};
}
return
;
}
sub
translate_cargo_info {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
my
$c_type
=
delete
$info
->{cargo_type};
return
unless
defined
$c_type
;
$self
->show_legacy_issue(
"$config_class_name->$elt_name: parameter cargo_type is deprecated."
);
my
%cargo
;
if
(
defined
$info
->{cargo_args} ) {
%cargo
= %{
delete
$info
->{cargo_args} };
$self
->show_legacy_issue(
"$config_class_name->$elt_name: parameter cargo_args is deprecated."
);
}
$cargo
{type} =
$c_type
;
if
(
defined
$info
->{config_class_name} ) {
$cargo
{config_class_name} =
delete
$info
->{config_class_name};
$self
->show_legacy_issue([
"$config_class_name->$elt_name: parameter config_class_name is "
,
"deprecated. This one must be specified within cargo. "
,
"Ie. cargo=>{config_class_name => 'FooBar'}"
]);
}
$info
->{cargo} = \
%cargo
;
$legacy_logger
->debug(
Data::Dumper->Dump( [
$info
], [
'translated_'
.
$elt_name
] )
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_id_names {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
$self
->translate_name(
$config_class_name
,
$elt_name
,
$info
,
'allow'
,
'allow_keys'
,
'die'
);
$self
->translate_name(
$config_class_name
,
$elt_name
,
$info
,
'allow_from'
,
'allow_keys_from'
,
'die'
);
$self
->translate_name(
$config_class_name
,
$elt_name
,
$info
,
'follow'
,
'follow_keys_from'
,
'die'
);
return
;
}
sub
translate_name {
my
(
$self
,
$config_class_name
,
$elt_name
,
$info
,
$from
,
$to
,
$legacy
) =
@_
;
if
(
defined
$info
->{
$from
} ) {
$self
->show_legacy_issue(
"$config_class_name->$elt_name: parameter $from is deprecated in favor of $to"
,
$legacy
);
$info
->{
$to
} =
delete
$info
->{
$from
};
}
return
;
}
sub
translate_allow_compute_override {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
if
(
defined
$info
->{allow_compute_override} ) {
$self
->show_legacy_issue(
"$config_class_name->$elt_name: parameter allow_compute_override is deprecated in favor of compute -> allow_override"
);
$info
->{compute}{allow_override} =
delete
$info
->{allow_compute_override};
}
return
;
}
sub
translate_compute_info {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
my
$old_name
=
shift
;
my
$new_name
=
shift
||
$old_name
;
if
(
ref
(
$info
->{
$old_name
} ) eq
'ARRAY'
) {
my
$compute_info
=
delete
$info
->{
$old_name
};
$legacy_logger
->debug(
"translate_compute_info $elt_name input:\n"
,
Data::Dumper->Dump( [
$compute_info
], [
qw/compute_info/
] )
)
if
$legacy_logger
->is_debug;
$self
->show_legacy_issue([
"$config_class_name->$elt_name: specifying compute info with "
,
"an array ref is deprecated"
]);
my
(
$user_formula
,
%var
) =
@$compute_info
;
my
$replace_h
;
foreach
(
keys
%var
) {
$replace_h
=
delete
$var
{
$_
}
if
ref
(
$var
{
$_
} ) };
$user_formula
=~ s/\$(\w+)\{/\
$replace
{/g;
foreach
(
values
%var
) { s/\$(\w+)\{/\
$replace
{/g };
$info
->{
$new_name
} = {
formula
=>
$user_formula
,
variables
=> \
%var
,
};
$info
->{
$new_name
}{replace} =
$replace_h
if
defined
$replace_h
;
$legacy_logger
->debug(
"translate_warp_info $elt_name output:\n"
,
Data::Dumper->Dump( [
$info
->{
$new_name
} ], [
'new_'
.
$new_name
] )
)
if
$legacy_logger
->is_debug;
}
return
;
}
sub
translate_id_class {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
$legacy_logger
->debug(
"translate_id_class $elt_name input:\n"
,
Data::Dumper->Dump( [
$info
], [
qw/info/
] )
)
if
$legacy_logger
->is_debug;
my
$class_overide_param
=
$info
->{type}.
'_class'
;
my
$class_overide
=
$info
->{
$class_overide_param
};
if
(
$class_overide
) {
$info
->{class} =
$class_overide
;
$self
->show_legacy_issue([
"$config_class_name->$elt_name: '$class_overide_param' is deprecated, "
,
"Use 'class' instead."
]);
}
$legacy_logger
->debug(
"translate_id_class $elt_name output:"
,
Data::Dumper->Dump( [
$info
], [
qw/new_info/
])
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_id_default_info {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
$legacy_logger
->debug(
"translate_id_default_info $elt_name input:\n"
,
Data::Dumper->Dump( [
$info
], [
qw/info/
] )
)
if
$legacy_logger
->is_debug;
my
$warn
=
"$config_class_name->$elt_name: 'default' parameter for list or "
.
"hash element is deprecated. "
;
my
$def_info
=
delete
$info
->{
default
};
if
(
ref
(
$def_info
) eq
'HASH'
) {
$info
->{default_with_init} =
$def_info
;
$self
->show_legacy_issue([
$warn
,
"Use default_with_init"
]);
}
elsif
(
ref
(
$def_info
) eq
'ARRAY'
) {
$info
->{default_keys} =
$def_info
;
$self
->show_legacy_issue([
$warn
,
"Use default_keys"
]);
}
else
{
$info
->{default_keys} = [
$def_info
];
$self
->show_legacy_issue([
$warn
,
"Use default_keys"
]);
}
$legacy_logger
->debug(
"translate_id_default_info $elt_name output:"
,
Data::Dumper->Dump( [
$info
], [
qw/new_info/
])
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_id_auto_create {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
$legacy_logger
->debug(
"translate_id_auto_create $elt_name input:"
,
Data::Dumper->Dump( [
$info
], [
qw/info/
] )
)
if
$legacy_logger
->is_debug;
my
$warn
=
"$config_class_name->$elt_name: 'auto_create' parameter for list or "
.
"hash element is deprecated. "
;
my
$ac_info
=
delete
$info
->{auto_create};
if
(
$info
->{type} eq
'hash'
) {
$info
->{auto_create_keys} =
ref
(
$ac_info
) eq
'ARRAY'
?
$ac_info
: [
$ac_info
];
$self
->show_legacy_issue([
$warn
,
"Use auto_create_keys"
]);
}
elsif
(
$info
->{type} eq
'list'
) {
$info
->{auto_create_ids} =
$ac_info
;
$self
->show_legacy_issue([
$warn
,
"Use auto_create_ids"
]);
}
else
{
die
"Unexpected element ($elt_name) type $info->{type} "
,
"for translate_id_auto_create"
;
}
$legacy_logger
->debug(
"translate_id_default_info $elt_name output:\n"
,
Data::Dumper->Dump( [
$info
], [
qw/new_info/
] )
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_id_min_max {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
;
my
$elt_name
=
shift
;
my
$info
=
shift
;
foreach
my
$bad
(
qw/min max/
) {
next
unless
defined
$info
->{
$bad
};
$legacy_logger
->debug(
"translate_id_min_max $elt_name $bad:"
)
if
$legacy_logger
->is_debug;
my
$good
=
$bad
.
'_index'
;
my
$warn
=
"$config_class_name->$elt_name: '$bad' parameter for list or "
.
"hash element is deprecated. Use '$good'"
;
$info
->{
$good
} =
delete
$info
->{
$bad
};
}
return
;
}
sub
translate_warped_node_info {
my
(
$self
,
$config_class_name
,
$elt_name
,
$type
,
$info
) =
@_
;
$legacy_logger
->debug(
"translate_warped_node_info $elt_name input:\n"
,
Data::Dumper->Dump( [
$info
], [
qw/info/
] )
)
if
$legacy_logger
->is_debug;
my
$elt_type
=
$info
->{type} ;
foreach
my
$parm
(
qw/follow rules/
) {
next
unless
$info
->{
$parm
};
next
if
defined
$elt_type
and
$elt_type
ne
'warped_node'
;
$self
->show_legacy_issue(
"$config_class_name->$elt_name: using $parm parameter in "
.
"warped node is deprecated. $parm must be specified in a warp parameter."
);
$info
->{warp}{
$parm
} =
delete
$info
->{
$parm
};
}
$legacy_logger
->debug(
"translate_warped_node_info $elt_name output:\n"
,
Data::Dumper->Dump( [
$info
], [
qw/new_info/
] )
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_warp_info {
my
(
$self
,
$config_class_name
,
$elt_name
,
$type
,
$warp_info
) =
@_
;
$legacy_logger
->debug(
"translate_warp_info $elt_name input:\n"
,
Data::Dumper->Dump( [
$warp_info
], [
qw/warp_info/
] )
)
if
$legacy_logger
->is_debug;
my
$follow
=
$self
->translate_follow_arg(
$config_class_name
,
$elt_name
,
$warp_info
->{follow} );
my
@warper_items
=
values
%$follow
;
my
$multi_follow
=
@warper_items
> 1 ? 1 : 0;
my
$rules
=
$self
->translate_rules_arg(
$config_class_name
,
$elt_name
,
$type
, \
@warper_items
,
$warp_info
->{rules} );
$warp_info
->{follow} =
$follow
;
$warp_info
->{rules} =
$rules
;
$legacy_logger
->debug(
"translate_warp_info $elt_name output:\n"
,
Data::Dumper->Dump( [
$warp_info
], [
qw/new_warp_info/
] )
)
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_multi_follow_legacy_rules {
my
(
$self
,
$config_class_name
,
$elt_name
,
$warper_items
,
$raw_rules
) =
@_
;
my
@rules
;
for
(
my
$r_idx
= 0 ;
$r_idx
<
$#$raw_rules
;
$r_idx
+= 2 ) {
my
$key_set
=
$raw_rules
->[
$r_idx
];
my
@keys
=
ref
(
$key_set
) ?
@$key_set
: (
$key_set
);
if
(
@keys
!=
@$warper_items
and
$key_set
!~ /\$\w+/ ) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Warp rule error in "
.
"'$config_class_name->$elt_name'"
.
": Wrong nb of keys in set '@keys',"
.
" Expected "
.
scalar
@$warper_items
.
" keys"
);
}
my
@bool_expr
;
my
$b_idx
= 0;
foreach
my
$key
(
@keys
) {
if
(
ref
$key
) {
my
@expr
=
map
{
"\$f$b_idx eq '$_'"
}
@$key
;
push
@bool_expr
,
"("
.
join
(
" or "
,
@expr
) .
")"
;
}
elsif
(
$key
!~ /\$\w+/ ) {
push
@bool_expr
,
"\$f$b_idx eq '$key'"
;
}
else
{
push
@bool_expr
,
$key
;
}
$b_idx
++;
}
push
@rules
,
join
(
' and '
,
@bool_expr
),
$raw_rules
->[
$r_idx
+ 1 ];
}
return
@rules
;
}
sub
translate_follow_arg {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
my
$elt_name
=
shift
;
my
$raw_follow
=
shift
;
if
(
ref
(
$raw_follow
) eq
'HASH'
) {
return
$raw_follow
;
}
elsif
(
ref
(
$raw_follow
) eq
'ARRAY'
) {
my
$follow
= {};
my
$idx
= 0;
foreach
(
@$raw_follow
) {
$follow
->{
'f'
.
$idx
++ } =
$_
} ;
return
$follow
;
}
elsif
(
defined
$raw_follow
) {
return
{
f1
=>
$raw_follow
};
}
else
{
return
{};
}
}
sub
translate_rules_arg {
my
(
$self
,
$config_class_name
,
$elt_name
,
$type
,
$warper_items
,
$raw_rules
) =
@_
;
my
$multi_follow
=
@$warper_items
> 1 ? 1 : 0;
my
$follow
=
@$warper_items
;
my
@rules
;
if
(
ref
(
$raw_rules
) eq
'HASH'
) {
my
$h
=
$raw_rules
;
@rules
=
$follow
?
map
{ (
"\$f1 eq '$_'"
,
$h
->{
$_
} ) }
keys
%$h
:
keys
%$h
;
}
elsif
(
ref
(
$raw_rules
) eq
'ARRAY'
) {
if
(
$multi_follow
) {
push
@rules
,
$self
->translate_multi_follow_legacy_rules(
$config_class_name
,
$elt_name
,
$warper_items
,
$raw_rules
);
}
else
{
my
@raw_rules
= @{
$raw_rules
};
for
(
my
$r_idx
= 0 ;
$r_idx
<
$#raw_rules
;
$r_idx
+= 2 ) {
my
$key_set
=
$raw_rules
[
$r_idx
];
my
@keys
=
ref
(
$key_set
) ?
@$key_set
: (
$key_set
);
my
@bool_expr
=
$follow
?
map
{ /\$/ ?
$_
:
"\$f1 eq '$_'"
}
@keys
:
@keys
;
push
@rules
,
join
(
' or '
,
@bool_expr
),
$raw_rules
[
$r_idx
+ 1 ];
}
}
}
elsif
(
defined
$raw_rules
) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Warp rule error in element "
.
"'$config_class_name->$elt_name': "
.
"rules must be a hash ref. Got '$raw_rules'"
);
}
for
(
my
$idx
= 1 ;
$idx
<
@rules
;
$idx
+= 2 ) {
next
unless
(
ref
$rules
[
$idx
] eq
'HASH'
);
$self
->handle_experience_permission(
$config_class_name
,
$rules
[
$idx
] );
next
unless
defined
$type
and
$type
eq
'leaf'
;
$self
->translate_legacy_builtin(
$config_class_name
,
$rules
[
$idx
],
$rules
[
$idx
] );
}
return
\
@rules
;
}
sub
translate_legacy_builtin {
my
(
$self
,
$config_class_name
,
$model
,
$normalized_model
) =
@_
;
my
$raw_builtin_default
=
delete
$normalized_model
->{built_in};
return
unless
defined
$raw_builtin_default
;
$legacy_logger
->debug(
Data::Dumper->Dump( [
$normalized_model
], [
'builtin to translate'
] )
)
if
$legacy_logger
->is_debug;
$self
->show_legacy_issue([
"$config_class_name: parameter 'built_in' is deprecated "
.
"in favor of 'upstream_default'"
]);
$model
->{upstream_default} =
$raw_builtin_default
;
$legacy_logger
->debug( Data::Dumper->Dump( [
$model
], [
'translated_builtin'
] ))
if
$legacy_logger
->is_debug;
return
;
}
sub
translate_legacy_built_in_list {
my
(
$self
,
$config_class_name
,
$model
,
$normalized_model
) =
@_
;
my
$raw_builtin_default
=
delete
$normalized_model
->{built_in_list};
return
unless
defined
$raw_builtin_default
;
$legacy_logger
->debug(
Data::Dumper->Dump( [
$normalized_model
], [
'built_in_list to translate'
] )
)
if
$legacy_logger
->is_debug;
$self
->show_legacy_issue([
"$config_class_name: parameter 'built_in_list' is deprecated "
.
"in favor of 'upstream_default_list'"
]);
$model
->{upstream_default_list} =
$raw_builtin_default
;
$legacy_logger
->debug( Data::Dumper->Dump( [
$model
], [
'translated_built_in_list'
] ))
if
$legacy_logger
->is_debug;
return
;
}
sub
include_class {
my
$self
=
shift
;
my
$class_name
=
shift
|| croak
"include_class: undef includer"
;
my
$target_model
=
shift
||
die
"include_class: undefined target_model"
;
my
$include_class
=
delete
$target_model
->{include};
return
()
unless
defined
$include_class
;
my
$include_after
=
delete
$target_model
->{include_after};
my
@includes
=
ref
$include_class
?
@$include_class
: (
$include_class
);
foreach
my
$inc
(
reverse
@includes
) {
$self
->include_one_class(
$class_name
,
$target_model
,
$inc
,
$include_after
);
}
return
;
}
sub
include_one_class {
my
$self
=
shift
;
my
$class_name
=
shift
|| croak
"include_class: undef includer"
;
my
$target_model
=
shift
|| croak
"include_class: undefined target_model"
;
my
$include_class
=
shift
|| croak
"include_class: undef include_class param"
;
my
$include_after
=
shift
;
get_logger(
'Model'
)->debug(
"class $class_name includes $include_class"
);
if
(
defined
$include_class
and
defined
$self
->{included_class}{
$class_name
}{
$include_class
} ) {
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Recursion error ? $include_class has "
.
"already been included by $class_name."
);
}
$self
->{included_class}{
$class_name
}{
$include_class
} = 1;
my
$included_model
=
$self
->get_model_clone(
$include_class
);
my
$target_list
=
$target_model
->{element_list};
my
$included_list
=
$included_model
->{element_list};
my
$splice_idx
= 0;
if
(
defined
$include_after
and
defined
$included_model
->{element} ) {
my
$idx
= 0;
my
%elt_idx
=
map
{ (
$_
,
$idx
++ ); }
@$target_list
;
if
( not
defined
$elt_idx
{
$include_after
} ) {
my
$msg
=
"Unknown element for 'include_after': "
.
"$include_after, expected "
.
join
(
' '
,
sort
keys
%elt_idx
);
Config::Model::Exception::ModelDeclaration->throw(
error
=>
$msg
);
}
$splice_idx
=
$elt_idx
{
$include_after
} + 1;
}
splice
(
@$target_list
,
$splice_idx
, 0,
@$included_list
);
get_logger(
'Model'
)->debug(
"class $class_name new elt list: @$target_list"
);
my
$target_element
=
$target_model
->{element} ||= {};
foreach
my
$included_elt
(
@$included_list
) {
if
( not
defined
$target_element
->{
$included_elt
} ) {
get_logger(
'Model'
)->debug(
"class $class_name includes elt $included_elt"
);
$target_element
->{
$included_elt
} =
$included_model
->{element}{
$included_elt
};
}
else
{
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Cannot clobber element '$included_elt' in $class_name"
.
" (included from $include_class)"
);
}
}
get_logger(
'Model'
)->debug(
"class $class_name include $include_class done"
);
return
;
}
sub
find_model_file_in_dir (
$model_name
,
$model_path
) {
foreach
my
$ext
(
qw/yml yaml pl/
) {
my
$sub_path
=
$model_name
=~ s!::!/!rg;
my
$path_load_file
=
$model_path
->child(
$sub_path
.
'.'
.
$ext
);
return
$path_load_file
if
$path_load_file
->
exists
;
}
return
;
}
sub
find_model_file_in_inc {
my
(
$self
,
$model_name
,
$load_file
) =
@_
;
my
$path_load_file
;
if
(
$load_file
and
$load_file
=~ m!^/! ) {
$path_load_file
=
$load_file
;
}
elsif
(
$self
->model_dir and
$self
->model_dir =~ m!^/!) {
my
$model_path
= path(
$self
->model_dir);
$path_load_file
= find_model_file_in_dir (
$model_name
,
$model_path
);
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Cannot find $model_name file in $model_path"
)
unless
$path_load_file
;
}
else
{
foreach
my
$inc_str
(
@INC
) {
my
$inc_path
= path(
$inc_str
);
if
(
$load_file
) {
$path_load_file
=
$inc_path
->child(
$load_file
);
}
else
{
my
$sub_path
=
$model_name
=~ s!::!/!rg;
my
$model_path
=
$inc_path
->child(
$self
->model_dir);
foreach
my
$ext
(
qw/yml yaml pl/
) {
$path_load_file
=
$model_path
->child(
$sub_path
.
'.'
.
$ext
);
last
if
$path_load_file
->
exists
;
}
}
last
if
$path_load_file
->
exists
;
}
}
Config::Model::Exception::ModelDeclaration->throw(
error
=>
"Cannot find $model_name file in \@INC"
)
unless
$path_load_file
;
$loader_logger
->debug(
"model $model_name from file $path_load_file"
);
return
$path_load_file
;
}
sub
load_model_plugins {
my
(
$self
,
@model_names
) =
@_
;
my
%model_graft_by_name
;
my
%done
;
foreach
my
$inc_str
(
@INC
) {
foreach
my
$name
(
@model_names
) {
my
$snippet_path
=
$name
;
$snippet_path
=~ s/::/\//g;
my
$snippet_dir
= path(
$inc_str
)->absolute->child(
$self
->model_dir)->child(
$snippet_path
.
'.d'
);
$loader_logger
->trace(
"looking for snippet in $snippet_dir"
);
if
(
$snippet_dir
->is_dir ) {
my
$iter
=
$snippet_dir
->iterator({
recurse
=> 1 });
while
(
my
$snippet_file
=
$iter
->() ) {
next
unless
$snippet_file
=~ /\.pl$/;
my
$snippet_file_rel
=
$snippet_file
->relative(
$inc_str
);
my
$done_key
=
$name
.
':'
.
$snippet_file_rel
;
next
if
$done
{
$done_key
};
$loader_logger
->info(
"Found snippet $snippet_file in $inc_str dir"
);
my
$snippet_model
=
$self
->_load_model_file(
$snippet_file
);
$self
->_merge_model_in_hash( \
%model_graft_by_name
,
$snippet_model
,
$snippet_file_rel
);
$done
{
$done_key
} = 1;
}
}
}
}
return
%model_graft_by_name
;
}
sub
load {
my
$self
=
shift
;
my
$model_name
=
shift
;
my
$load_file
=
shift
;
$loader_logger
->debug(
"called on model $model_name"
);
my
$path_load_file
=
$self
->find_model_file_in_inc(
$model_name
,
$load_file
);
my
%models_by_name
;
my
$model
=
$self
->_load_model_file(
$path_load_file
->absolute);
my
@loaded_classes
=
$self
->_merge_model_in_hash( \
%models_by_name
,
$model
,
$path_load_file
);
$self
->store_raw_model(
$model_name
, dclone( \
%models_by_name
) );
foreach
my
$name
(
keys
%models_by_name
) {
my
$data
=
$self
->normalize_class_parameters(
$name
,
$models_by_name
{
$name
} );
$loader_logger
->debug(
"Store normalized model $name"
);
$self
->store_normalized_model(
$name
,
$data
);
}
my
%model_graft_by_name
=
$self
->load_model_plugins(
sort
keys
%models_by_name
);
foreach
my
$name
(
keys
%model_graft_by_name
) {
$loader_logger
->trace(
"storing snippet for model $name"
);
$self
->add_snippet(
$model_graft_by_name
{
$name
});
}
foreach
my
$snippet
(
$self
->all_snippets ) {
my
$class_to_merge
=
$snippet
->{name};
next
unless
$models_by_name
{
$class_to_merge
};
$self
->augment_config_class_really(
$class_to_merge
,
$snippet
);
}
return
@loaded_classes
;
}
sub
_merge_model_in_hash {
my
(
$self
,
$hash_ref
,
$model
,
$load_file
) =
@_
;
my
@names
;
foreach
my
$config_class_info
(
@$model
) {
my
%data
=
ref
$config_class_info
eq
'HASH'
?
%$config_class_info
:
ref
$config_class_info
eq
'ARRAY'
?
@$config_class_info
: croak
"load $load_file: config_class_info is not a ref"
;
my
$config_class_name
=
$data
{name}
or croak
"load: missing config class name in $load_file"
;
$hash_ref
->{
$config_class_name
} = \
%data
;
push
@names
,
$config_class_name
;
}
return
@names
;
}
sub
_load_model_file {
my
(
$self
,
$load_file
) =
@_
;
$loader_logger
->info(
"load model $load_file"
);
my
$err_msg
=
''
;
my
$model
=
do
$load_file
;
unless
(
$model
) {
if
($@) {
$err_msg
=
"couldn't parse $load_file: $@"
; }
elsif
( not
defined
$model
) {
$err_msg
=
"couldn't do $load_file: $!"
}
else
{
$err_msg
=
"couldn't run $load_file"
; }
}
elsif
(
ref
(
$model
) ne
'ARRAY'
) {
$model
= [
$model
];
}
Config::Model::Exception::ModelDeclaration->throw(
message
=>
"load error: $err_msg"
)
if
$err_msg
;
return
$model
;
}
sub
augment_config_class {
my
(
$self
,
%augment_data
) =
@_
;
my
$config_class_name
=
delete
$augment_data
{name}
|| croak
"augment_config_class: missing class name"
;
$self
->augment_config_class_really(
$config_class_name
, \
%augment_data
);
return
;
}
sub
augment_config_class_really {
my
(
$self
,
$config_class_name
,
$augment_data
) =
@_
;
my
$orig_model
=
$self
->normalized_model(
$config_class_name
);
croak
"unknown class to augment: $config_class_name"
unless
defined
$orig_model
;
my
$model_addendum
=
$self
->normalize_class_parameters(
$config_class_name
, dclone(
$augment_data
) );
my
$merge
= Hash::Merge->new(
'RIGHT_PRECEDENT'
);
my
$new_model
=
$merge
->merge(
$orig_model
,
$model_addendum
);
foreach
my
$list_name
(
qw/element_list accept_list/
) {
my
%seen
;
my
@newlist
;
foreach
my
$elt
( @{
$new_model
->{
$list_name
} } ) {
push
@newlist
,
$elt
unless
$seen
{
$elt
};
$seen
{
$elt
} = 1;
}
$new_model
->{
$list_name
} = \
@newlist
;
}
$self
->store_normalized_model(
$config_class_name
=>
$new_model
);
return
;
}
sub
model {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
"Model::get_model: missing config class name argument"
;
$self
->load(
$config_class_name
)
unless
$self
->normalized_model_exists(
$config_class_name
);
if
( not
$self
->model_defined(
$config_class_name
) ) {
$loader_logger
->debug(
"creating model $config_class_name"
);
my
$model
=
$self
->merge_included_class(
$config_class_name
);
$self
->_store_model(
$config_class_name
,
$model
);
}
return
$self
->_get_model(
$config_class_name
)
|| croak
"get_model error: unknown config class name: $config_class_name"
;
}
sub
get_model {
my
(
$self
,
$model
) =
@_
;
carp
"get_model is deprecated in favor of get_model_clone"
;
return
$self
->get_model_clone(
$model
);
}
sub
get_model_clone {
my
(
$self
,
$model
) =
@_
;
return
dclone(
$self
->model(
$model
));
}
sub
get_model_doc {
my
(
$self
,
$top_class_name
,
$done
) =
@_
;
$done
//= {};
if
( not
defined
$self
->normalized_model(
$top_class_name
) ) {
eval
{
$self
->model(
$top_class_name
); };
if
($@) {
my
$e
= $@;
if
(
$e
->isa(
'Config::Model::Exception::ModelDeclaration'
)) {
Config::Model::Exception::Fatal->throw(
message
=>
"Unknown configuration class : $top_class_name ($@)"
);
}
else
{
$e
->rethrow;
}
}
}
my
@classes
= (
$top_class_name
);
my
%result
;
while
(
@classes
) {
my
$class_name
=
shift
@classes
;
next
if
$done
->{
$class_name
} ;
my
$c_model
=
$self
->model(
$class_name
)
|| croak
"get_model_doc model error : unknown config class name: $class_name"
;
my
$full_name
=
"Config::Model::models::$class_name"
;
my
%see_also
;
my
@pod
= (
"# PODNAME: $full_name"
,
"# ABSTRACT: Configuration class "
.
$class_name
,
''
,
"=encoding utf8"
,
''
,
"=head1 NAME"
,
''
,
"$full_name - Configuration class "
.
$class_name
,
''
,
"=head1 DESCRIPTION"
,
''
,
"Configuration classes used by L<Config::Model>"
,
''
);
my
%legalese
;
my
$i
= 0;
my
$class_desc
=
$c_model
->{class_description};
push
@pod
,
$class_desc
,
''
if
defined
$class_desc
;
my
@elt
= (
"=head1 Elements"
,
''
);
foreach
my
$elt_name
( @{
$c_model
->{element_list} } ) {
my
$elt_info
=
$c_model
->{element}{
$elt_name
};
my
$summary
=
$elt_info
->{summary} ||
''
;
$summary
&&=
" - $summary"
;
push
@elt
,
"=head2 $elt_name$summary"
,
''
;
push
@elt
,
$self
->get_element_description(
$elt_info
),
''
;
foreach
(
$elt_info
,
$elt_info
->{cargo} ) {
if
(
my
$ccn
=
$_
->{config_class_name} ) {
push
@classes
,
$ccn
;
$see_also
{
$ccn
} = 1;
}
if
(
my
$migr
=
$_
->{migrate_from} ) {
push
@elt
,
$self
->get_migrate_doc(
$elt_name
,
'is migrated with'
,
$migr
);
}
if
(
my
$migr
=
$_
->{migrate_values_from} ) {
push
@elt
,
"Note: $elt_name values are migrated from '$migr'"
,
''
;
}
if
(
my
$comp
=
$_
->{compute} ) {
push
@elt
,
$self
->get_migrate_doc(
$elt_name
,
'is computed with'
,
$comp
);
}
}
}
foreach
my
$what
(
qw/author copyright license/
) {
my
$item
=
$c_model
->{
$what
};
push
@{
$legalese
{
$what
} },
$item
if
$item
;
}
my
@end
;
foreach
my
$what
(
qw/author copyright license/
) {
next
unless
@{
$legalese
{
$what
} || [] };
push
@end
,
"=head1 "
.
uc
(
$what
),
''
,
'=over'
,
''
,
(
map
{ (
"=item $_"
,
''
); }
map
{
ref
$_
?
@$_
:
$_
} @{
$legalese
{
$what
} } ),
''
,
'=back'
,
''
;
}
my
@see_also
= (
"=head1 SEE ALSO"
,
''
,
"=over"
,
''
,
"=item *"
,
''
,
"L<cme>"
,
''
,
(
map
{ (
"=item *"
,
''
,
"L<Config::Model::models::$_>"
,
''
); }
sort
keys
%see_also
),
"=back"
,
''
);
$result
{
$full_name
} =
join
(
"\n"
,
@pod
,
@elt
,
@see_also
,
@end
,
'=cut'
,
''
) .
"\n"
;
$done
->{
$class_name
} = 1;
}
return
\
%result
;
}
sub
get_migrate_doc {
my
(
$self
,
$elt_name
,
$desc
,
$migr
) =
@_
;
my
$mv
=
$migr
->{variables};
my
$mform
=
$migr
->{formula};
if
(
$mform
=~ /\n/) {
$mform
=~ s/^/ /mg;
$mform
=
"\n\n$mform\n\n"
; }
else
{
$mform
=
"'C<$mform>' "
}
my
$mdoc
=
"Note: $elt_name $desc ${mform}and with: \n\n=over\n\n=item *\n\n"
.
join
(
"\n\n=item *\n\n"
,
map
{
qq!C<\$$_> => C<$mv->{$_}>!
}
sort
keys
%$mv
);
if
(
my
$rep
=
$migr
->{replace} ) {
$mdoc
.=
"\n\n=item *\n\n"
.
join
(
"\n\n=item *\n\n"
,
map
{
qq!C<\$replace{$_}> => C<$rep->{$_}>!
}
sort
keys
%$rep
);
}
$mdoc
.=
"\n\n=back\n\n"
;
return
(
$mdoc
,
''
);
}
sub
get_element_description {
my
(
$self
,
$elt_info
) =
@_
;
my
$type
=
$elt_info
->{type};
my
$cargo
=
$elt_info
->{cargo};
my
$vt
=
$elt_info
->{value_type};
my
$of
=
''
;
my
$cargo_type
=
$cargo
->{type};
my
$cargo_vt
=
$cargo
->{value_type};
$of
=
" of "
. (
$cargo_vt
or
$cargo_type
)
if
defined
$cargo_type
;
my
$ccn
=
$elt_info
->{config_class_name} ||
$cargo
->{config_class_name};
$of
.=
" of class L<$ccn|Config::Model::models::$ccn> "
if
$ccn
;
my
$desc
=
$elt_info
->{description} ||
''
;
if
(
$desc
) {
$desc
.=
'.'
if
$desc
=~ /\w$/;
$desc
.=
' '
unless
$desc
=~ /\s$/;
}
if
(
my
$status
=
$elt_info
->{status} ) {
$desc
.=
'B<'
.
ucfirst
(
$status
) .
'> '
;
}
my
$info
=
$elt_info
->{mandatory} ?
'Mandatory. '
:
'Optional. '
;
$info
.=
"Type "
. (
$vt
||
$type
) .
$of
.
'. '
;
foreach
my
$name
(
qw/choice/
) {
my
$item
=
$elt_info
->{
$name
};
next
unless
defined
$item
;
$info
.=
"$name: '"
.
join
(
"', '"
,
@$item
) .
"'. "
;
}
my
@default_info
= ();
foreach
my
$name
(
qw/default upstream_default/
) {
my
$item
=
$elt_info
->{
$name
};
next
unless
defined
$item
;
push
@default_info
, [
$name
,
$item
] ;
}
my
$elt_help
=
$self
->get_element_value_help(
$elt_info
);
my
$ret
=
$desc
.
"I< $info > "
;
if
(
@default_info
) {
$ret
.=
"\n\n=over 4\n\n"
;
for
(
@default_info
) {
$ret
.=
"=item $_->[0] value :\n\n$_->[1]\n\n"
; }
$ret
.=
"=back\n\n"
;
}
$ret
.=
$elt_help
;
return
$ret
;
}
sub
get_element_value_help {
my
(
$self
,
$elt_info
) =
@_
;
my
$help
=
$elt_info
->{help};
return
''
unless
defined
$help
;
my
$help_text
=
"\n\nHere are some explanations on the possible values:\n\n=over\n\n"
;
foreach
my
$v
(
sort
keys
%$help
) {
$help_text
.=
"=item '$v'\n\n$help->{$v}\n\n"
;
}
return
$help_text
.
"=back\n\n"
;
}
sub
generate_doc {
my
(
$self
,
$top_class_name
,
$dir_str
,
$done
) =
@_
;
$done
//= {} ;
my
$res
=
$self
->get_model_doc(
$top_class_name
,
$done
);
if
(
defined
$dir_str
and
$dir_str
) {
foreach
my
$class_name
(
sort
keys
%$res
) {
my
$dir
= path(
$dir_str
);
$dir
->mkpath()
unless
$dir
->
exists
;
my
$file_path
=
$class_name
;
$file_path
=~ s!::!/!g;
my
$pl_file
=
$dir
->child(
"$file_path.pl"
);
$pl_file
->parent->mkpath
unless
$pl_file
->parent->
exists
;
my
$pod_file
=
$dir
->child(
"$file_path.pod"
);
my
$old
=
''
;
if
(
$pod_file
->
exists
) {
$old
=
$pod_file
->slurp_utf8;
}
if
(
$old
ne
$res
->{
$class_name
} ) {
$pod_file
->spew_utf8(
$res
->{
$class_name
} );
say
"Wrote documentation in $pod_file"
;
}
}
}
else
{
foreach
my
$class_name
(
sort
keys
%$res
) {
print
"########## $class_name ############ \n\n"
;
print
$res
->{
$class_name
};
}
}
return
;
}
sub
get_element_model {
my
$self
=
shift
;
my
$config_class_name
=
shift
||
die
"Model::get_element_model: missing config class name argument"
;
my
$element_name
=
shift
||
die
"Model::get_element_model: missing element name argument"
;
my
$model
=
$self
->model(
$config_class_name
);
my
$element_m
=
$model
->{element}{
$element_name
}
|| croak
"get_element_model error: unknown element name: $element_name"
;
return
dclone(
$element_m
);
}
sub
get_normalized_model {
my
$self
=
shift
;
my
$config_class_name
=
shift
;
$self
->load(
$config_class_name
)
unless
defined
$self
->normalized_model(
$config_class_name
);
my
$normalized_model
=
$self
->normalized_model(
$config_class_name
)
|| croak
"get_normalized_model error: unknown config class name: $config_class_name"
;
return
dclone(
$normalized_model
);
}
sub
get_element_name (
$self
,
%args
) {
my
$class
=
$args
{class}
|| croak
"get_element_name: missing 'class' parameter"
;
if
(
delete
$args
{
for
}) {
carp
"get_element_name: 'for' parameter is deprecated"
;
}
my
$model
=
$self
->model(
$class
);
my
@result
;
foreach
my
$elt
( @{
$model
->{element_list} } ) {
my
$elt_data
=
$model
->{element}{
$elt
};
my
$l
=
$elt_data
->{level} || get_default_property(
'level'
);
push
@result
,
$elt
if
$l
ne
'hidden'
;
}
return
wantarray
?
@result
:
join
(
' '
,
@result
);
}
sub
get_element_property (
$self
,
%args
) {
my
$elt
=
$args
{element}
|| croak
"get_element_property: missing 'element' parameter"
;
my
$prop
=
$args
{property}
|| croak
"get_element_property: missing 'property' parameter"
;
my
$class
=
$args
{class}
|| croak
"get_element_property:: missing 'class' parameter"
;
my
$model
=
$self
->model(
$class
);
if
( not
defined
$model
->{element}{
$elt
} ) {
$logger
->debug(
"test accept for class $class elt $elt prop $prop"
);
foreach
my
$acc_re
( @{
$model
->{accept_list} } ) {
return
$model
->{
accept
}{
$acc_re
}{
$prop
} || get_default_property(
$prop
)
if
$elt
=~ /^
$acc_re
$/;
}
}
return
$self
->model(
$class
)->{element}{
$elt
}{
$prop
}
|| get_default_property(
$prop
);
}
sub
list_class_element {
my
$self
=
shift
;
my
$pad
=
shift
||
''
;
my
$res
=
''
;
foreach
my
$class_name
(
$self
->normalized_model_names ) {
$res
.=
$self
->list_one_class_element(
$class_name
);
}
return
$res
;
}
sub
list_one_class_element {
my
$self
=
shift
;
my
$class_name
=
shift
;
my
$pad
=
shift
||
''
;
my
$res
=
$pad
.
"Class: $class_name\n"
;
my
$c_model
=
$self
->normalized_model(
$class_name
);
my
$elts
=
$c_model
->{element_list};
return
$res
unless
defined
$elts
and
@$elts
;
foreach
my
$elt_name
(
@$elts
) {
my
$type
=
$c_model
->{element}{
$elt_name
}{type};
$res
.=
$pad
.
" - $elt_name ($type)\n"
;
}
return
$res
;
}
__PACKAGE__->meta->make_immutable;
1;